Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Pressing Tab in a page's context menu throws "Maximum call stack size exceeded"

クローズ 初心者向け
#1,356 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
clojure, react

調査の方向性

まず deps/shui/src/logseq/shui/popup/core.cljs を確認し、x-popup の onOpenChange ハンドラー、特に "focus-out" の処理を読んでください。issue にあるページメニューの Tab シーケンスを再現してください。フォーカスがメニューから離れられ、メニューが閉じ、maximum-call-stack エラーが発生しなければ完了です。

索引モデルが issue の本文から書いたものです。

説明

Steps

On a DB graph, on the web app:

  1. Open a page. Here a page with a few blocks; a tag page gives the same result.
  2. Right-click the page title. The page menu opens ("Add to Favorites", "Delete page", ...).
  3. Press Tab.

Expected

Focus leaves the menu and the menu closes.

Actual

RangeError: Maximum call stack size exceeded. in Base UI's focus guard handlers, reported to the window as uncaught. The menu stays open.

The steps reproduced it 5 of 5 times (Playwright, trusted input, a new graph each time). Shift+Tab then Tab in the same menu reproduced it 5 of 5 times on an ordinary page and 5 of 5 on a tag page; Tab, Tab 3 of 3. The same keys in a block's context menu (right-click a bullet) did not reproduce it (0 of 5 for Shift+Tab, Tab and 0 of 5 for Tab, Tab).

Cause (read in code, checked on a page without Logseq)

x-popup in deps/shui/src/logseq/shui/popup/core.cljs renders a shui popup as a Base UI Menu (the lockfile has @base-ui/react 1.6.0) with a hidden trigger button (:tab-index -1) and a portal, and its onOpenChange handler cancels every close whose reason is "focus-out":

focus-transition? (= reason "focus-out")
;; ...
(if (or (not last-popup?)
        target-toggle?
        opening-outside-press?
        menu-transition?
        focus-transition?)
  (some-> e (.cancel))
  ...)

The trigger and this cancel both came with 50abc4b93 ("fix: stabilize imperative popup toggles"). Before it, x-popup rendered no trigger and canceled a focus-out close only when a close target was inside the popup content (popup-focus-retained?).

While the menu is open, Base UI keeps tabbable focus guards next to the trigger (useTriggerFocusGuards), around the portal (FloatingPortal, "outside"), and inside the portal around the popup (FloatingFocusManager, "inside"). Tab from the menu starts a cycle of 4 guard handlers, each calling .focus() inside the previous one's focus handler:

  1. The guard after the trigger (handleFocusTargetFocus) asks the menu to close with reason "focus-out". x-popup cancels the close, so the menu and its guards stay, and the handler focuses the next tabbable element after itself, the portal's outside guard.
  2. The outside guard, entered from outside the portal, focuses the inside guard before the popup.
  3. That guard, entered from outside the portal, focuses the tabbable element after the focused one (itself), which is the inside guard after the popup.
  4. The inside guard after the popup, entered from inside the portal, focuses its nextFocusableElement, the guard after the trigger. The cycle returns to 1.

A focus log of the steps shows this order repeating, and the error's stack recorded 120 frames deep shows the 4 handlers in turn (handleFocusTargetFocus, the FloatingPortal guard's onFocus, the 2 FloatingFocusManager guards' onFocus). The close in step 1 is the only way out of the cycle: a close that goes through unmounts the guards.

The same loop happens on a page with only React 19.2.6 and @base-ui/react, a Menu opened on mount with the trigger x-popup renders and an onOpenChange that cancels "focus-out" closes as x-popup does:

<Menu.Root
  open={open}
  onOpenChange={(next, details) => {
    if (!next && details.reason === "focus-out") {
      details.cancel();
      return;
    }
    setOpen(next);
  }}
>
  <Menu.Trigger
    render={
      <button
        tabIndex={-1}
        aria-hidden
        style={{ position: "fixed", top: -10000, left: -10000, width: 1, height: 1, opacity: 0, pointerEvents: "none" }}
      />
    }
  />
  <Menu.Portal>
    <Menu.Positioner>
      <Menu.Popup>
        <Menu.Item>Add to Favorites</Menu.Item>
        <Menu.Item>Delete page</Menu.Item>
      </Menu.Popup>
    </Menu.Positioner>
  </Menu.Portal>
</Menu.Root>

With @base-ui/react 1.6.0, 1 Tab overflowed the stack 3 of 3 times, and so did Tab, Tab and Shift+Tab, Tab (3 of 3 each). With the close let through (the details.cancel() branch removed), each sequence gave 0 of 3 and the menu closed.

Upstream: the same error from the same Base UI handler is mui/base-ui#5715 (a non-modal Popover whose trigger is the only tabbable element on the page), fixed by mui/base-ui#5733, merged on 2026-09-21 and in no release yet (the latest release, 1.8.0, is from 2026-09-04). That fix does not end this loop. With the preview build of #5733 from pkg.pr.new (its useTriggerFocusGuards matches the merged code), the page above overflowed the stack 3 of 3 times for each of the 3 key sequences with the cancel and 0 of 3 without it. A Base UI upgrade alone leaves the error in Logseq; the cancel of "focus-out" closes in x-popup has to change.

Found by a monkey test (gremlins.js with trusted Playwright input) of the web build of master 16c4ed1a0: 9 of 28 runs overflowed the stack in these guard handlers. The top frame is handleFocusTargetFocus in 1 run and a guard's onFocus in 8, both pairs of frames from the cycle above. In seed 109 it came from a trusted Tab at action 143. ddmin reduced that run to 6 actions: "/node" typed with no editor open, a synthetic click on a table cell, a right-click left of the page title (the page menu opened), Shift+Tab, Tab, and "/image" typed. Replayed with the recorded 20 ms between actions, the 6 actions reproduced it 0 of 5 times; with a screenshot saved after each action, 2 of 2. The steps above are read from those screenshots and leave out the first 2 actions and the last.

主要言語
言語のデータがありません
スター
28
フォーク
2
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトの環境構築ファイルはまだ確認していません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

logseq/db-test のほかの issue

logseq/db-test の issue をすべて見る

似ている issue

Accessibility の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。