Chrome: browser rules stop matching in windows that have shown a New Tab Page (`chrome://newtab-footer/` WebArea is picked as the tab URL)
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 72/100
調査の方向性
Utilities/Accessibility/QueryWebAreaService.swift の fetchScrollArea() から始めてください。ここでは DFS が、スキームが chrome-extension ではない最初の AXWebArea を返しています。NTP を表示したことがあるウィンドウでは、chrome://newtab-footer/ を持つフッターの WebArea が先にポップされます。影響を受けるウィンドウで issue の dump.swift スニペットを実行し、選ばれた URL を確認してください。フッターの WebArea がスキップされ(たとえば chrome スキームも除外する、または URL が AXDocument と一致する WebArea を選ぶなど)、NTP を表示したウィンドウ内の github.com のタブが自身の URL に解決されれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Summary
Current Chrome keeps a hidden NewTabFooterWebView (chrome://newtab-footer/, the NTP footer showing "Managed by your organization" / extension attribution) in every browser window that has displayed a New Tab Page. It stays in the window's accessibility tree for the lifetime of the window, as a sibling of the active tab's AXWebArea.
QueryWebAreaService.fetchScrollArea() returns the first AXWebArea its DFS reaches and only filters the chrome-extension scheme. In such windows the footer WebArea is reached first, so the current URL resolves to chrome://newtab-footer/, no browser rule matches, and the window keeps whatever input source was active (reason=noChanges). Which site is open in the window makes no difference.
Managed/enterprise Chrome shows the footer by default, so this hits every such user after a single ⌘T in a window. Related observation in a comment on #42.
Environment
- Input Source Pro 2.14.0 (build 1182)
- macOS 26.7.1 (25G241)
- Google Chrome 157.0.8103.26, enterprise-managed (NTP footer visible)
- Chrome URL switching enabled, Accessibility granted
- Rule:
DOMAIN example.com → Squirrel (Rime)
Steps to reproduce
The NTP footer must be visible. On an unmanaged Mac, either of these makes Chrome show it:
-
Extension attribution: install any extension that overrides the New Tab Page (e.g. "Speed Dial"). The NTP then shows " has changed this page" in the footer.
-
Management notice: apply any harmless platform policy, which marks the browser as managed:
defaults write com.google.Chrome ShowHomeButton -bool true # restart Chrome; chrome://policy lists it and the NTP footer shows "Managed by your organization" # cleanup afterwards: defaults delete com.google.Chrome ShowHomeButton
Then:
- Add a browser rule, e.g.
DOMAIN github.com → <any CJK input source>; enable URL switching for Chrome. - In a Chrome window, press ⌘T once (the footer is visible on the new tab), then open
github.comin that window. - Switch to another app and back to the Chrome window.
Expected: the rule fires and the input source switches.
Actual: no switch. The diagnostics log shows state.published … reason=noChanges pid=<chrome> and no switch.* entry.
- Drag the same tab into a new window and repeat step 3: the rule fires (
switch.target … reason=appSpecified).
Closing the tab and restoring it with ⌘⇧T keeps the problem; the footer WebView belongs to the window (BrowserView), and only recreating the window removes it.
To inspect what fetchScrollArea() sees, run this from a terminal with Accessibility permission while the affected window is focused (swift -module-cache-path /tmp/swift-cache dump.swift):
import ApplicationServices
import AppKit
let chrome = NSRunningApplication.runningApplications(withBundleIdentifier: "com.google.Chrome").first!
let app = AXUIElementCreateApplication(chrome.processIdentifier)
func attr(_ e: AXUIElement, _ a: String) -> AnyObject? { var v: AnyObject?; AXUIElementCopyAttributeValue(e, a as CFString, &v); return v }
func str(_ e: AXUIElement, _ a: String) -> String { if let u = attr(e, a) as? URL { return u.absoluteString }; return (attr(e, a) as? String) ?? "" }
let win = attr(app, kAXFocusedWindowAttribute) as! AXUIElement
print("AXDocument =", str(win, kAXDocumentAttribute))
var stack = [win]
while let el = stack.popLast() { // same traversal order as fetchScrollArea()
let role = str(el, kAXRoleAttribute)
if role == "AXWebArea" { print("AXWebArea url =", str(el, kAXURLAttribute)); continue }
let kids = (role == "AXTable" || role == "AXOutline")
? (attr(el, kAXVisibleRowsAttribute) as? [AXUIElement]) ?? []
: (attr(el, kAXChildrenAttribute) as? [AXUIElement]) ?? []
stack.append(contentsOf: kids)
}
The first printed AXWebArea is the one Input Source Pro uses. In an affected window it is chrome://newtab-footer/.
Evidence
Replicating fetchScrollArea() (DFS with popLast, same filter) over the AX tree of each Chrome window:
=== window[0] main=1 title=<page on github.com>
AXDocument=https://github.com/...
webArea#1 url=chrome://newtab-footer/ <== picked
path=AXWindow > AXGroup[0/4] > AXGroup[0/1] > AXGroup[0/1] > AXGroup[0/1] > AXGroup[1/4] > AXGroup[0/2] > AXScrollArea[2/3] > AXWebArea[0/1]
webArea#2 url=https://github.com/...
path=AXWindow > AXGroup[0/4] > AXGroup[0/1] > AXGroup[0/1] > AXGroup[0/1] > AXGroup[1/4] > AXGroup[0/2] > AXScrollArea[1/3] > AXWebArea[0/1]
=== window[1] main=0 title=<page on github.com, window never showed an NTP>
AXDocument=https://github.com/...
webArea#1 url=https://github.com/... <== picked
path=AXWindow > ... > AXGroup[0/2] > AXScrollArea[1/2] > AXWebArea[0/1]
Windows with the footer have three AXScrollArea children; the footer is the last one, so the DFS pops it first. The window's AXDocument is correct in both cases.
Diagnostics log (IndicatorDiagnosticsEnabled), affected window:
state.published source=com.apple.keylayout.US reason=noChanges pid=1099
activation.mode mode=autoShow app=com.google.Chrome pid=1099
Same tab after moving it to a fresh window:
switch.target source=im.rime.inputmethod.Squirrel.Hans mode=im.rime.inputmethod.Squirrel.Hans strategy=nil
switch.TISSelect source=im.rime.inputmethod.Squirrel.Hans reason=target status=0
state.published source=im.rime.inputmethod.Squirrel.Hans::im.rime.inputmethod.Squirrel.Hans reason=appSpecified pid=1099
Code path
-
Browser.getCurrentTabURL()(Utilities/BrowserURL.swift) usesQueryWebAreaService.perform()for Chromium browsers. -
QueryWebAreaService.fetchScrollArea()(Utilities/Accessibility/QueryWebAreaService.swift):if element.role == "AXWebArea", element.url?.scheme != "chrome-extension" { return element } -
normalizedTabURL()mapschrome://newtab/andchrome://new-tab-page/to.newtab;chrome://newtab-footer/passes through unchanged intogetBrowserRule(url:).
Suggested fix
Minimal:
if element.role == "AXWebArea",
let scheme = element.url?.scheme,
scheme != "chrome-extension",
scheme != "chrome" // chrome://newtab-footer/ and other internal WebUI panes
{
return element
}
More robust: when the window exposes an AXDocument URL, return the WebArea whose URL equals it (the Comet/Aside path already trusts documentURL), and fall back to the DFS result otherwise. This also covers future hidden WebUI panes such as side panels.
Workaround
New Tab Page → right-click the footer → "Hide footer on new tab page", then close and reopen the affected Chrome windows (per profile).
I can send a PR for either variant.
- 主要言語
- Swift
- スター
- 3.6k
- フォーク
- 144
- 平均マージ
- 4時間 16分
- マージ済み PR(30日)
- 3
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
runjuu/InputSourcePro のほかの issue
-
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
runjuu/InputSourcePro#152 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 55/100
runjuu/InputSourcePro#150 ·
-
難易度 3/5 半日 初心者へのやさしさ 30/100
runjuu/InputSourcePro#148 ·
-
難易度 3/5 半日 初心者へのやさしさ 50/100
runjuu/InputSourcePro#147 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 56/100
runjuu/InputSourcePro#145 ·
runjuu/InputSourcePro の issue をすべて見る
似ている issue
-
@-mention suggestions don't match a display name typed without its spaces (Element Web does)対応中かも @dwong716 が今日担当しました。 オープンT-Defect
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
element-hq/element-x-ios#6457 ·
メンテナーはふだん 1 日以内に返信
-
needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
manaflow-ai/cmux#32617 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
skiptools/skip-fuse-ui#166 ·
メンテナーはふだん 2 日以内に返信
-
Requirement is wrongオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
remko/age-plugin-se#25 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信