Chrome: browser rules stop matching in windows that have shown a New Tab Page (`chrome://newtab-footer/` WebArea is picked as the tab URL)

オープン 初心者向け
#153 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

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

評価

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

調査の方向性

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:

  1. Add a browser rule, e.g. DOMAIN github.com → <any CJK input source>; enable URL switching for Chrome.
  2. In a Chrome window, press ⌘T once (the footer is visible on the new tab), then open github.com in that window.
  3. 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.

  1. 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) uses QueryWebAreaService.perform() for Chromium browsers.

  • QueryWebAreaService.fetchScrollArea() (Utilities/Accessibility/QueryWebAreaService.swift):

    if element.role == "AXWebArea",
       element.url?.scheme != "chrome-extension"
    {
        return element
    }
    
  • normalizedTabURL() maps chrome://newtab/ and chrome://new-tab-page/ to .newtab; chrome://newtab-footer/ passes through unchanged into getBrowserRule(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 を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

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

runjuu/InputSourcePro のほかの issue

runjuu/InputSourcePro の issue をすべて見る

似ている issue

Swift の issue をもっと見る

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

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