You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过编程方式在Chromium浏览器中启用Microsoft Active Accessibility模式以获取标签页URL

如何通过编程方式在Chromium浏览器中启用Microsoft Active Accessibility模式以获取标签页URL

我完全懂你现在的困扰——想要通过无障碍API抓取Chrome标签页的URL,但Chrome为了性能默认不开启完整无障碍支持,你照着Chromium文档尝试触发那个“蜜罐”检测却没成功对吧?下面我来帮你理清问题根源,再给你修正后的可行方案。

首先先明确Chrome的无障碍检测逻辑,这是Chromium官方的说明:

出于性能考虑,Chromium会等待检测到辅助技术的存在后,才会启用对无障碍API的完整支持。在Windows平台上,Chrome会发送带有EVENT_SYSTEM_ALERT事件和自定义对象ID为1的NotifyWinEvent,如果后续收到针对该自定义对象ID的WM_GETOBJECT调用,就会认为辅助技术正在运行。

对应到Chromium代码里的kIdScreenReaderHoneyPot(值为1),当MSAA客户端响应这个假事件的查询时,只会先启用基础无障碍支持,完整的无障碍功能(包括能获取标签页URL的完整树结构)需要后续访问更高级的无障碍API才会被触发。

你的现有代码存在的问题

  1. 你设置了EVENT_SYSTEM_ALERT的钩子,但在回调里只是获取了Chrome的IAccessible对象,没有针对那个关键的对象ID=1的情况做处理,没触发Chrome的蜜罐检测逻辑
  2. 即使触发了蜜罐检测,你也没有访问足够的无障碍API来让Chrome启用完整的支持,导致还是无法获取到标签页的URL节点
  3. 遍历无障碍树的逻辑没有处理Chrome的多层嵌套结构,可能提前返回了

修正后的可行方案

下面是调整后的代码,它会正确触发Chrome的无障碍检测逻辑,然后遍历完整的无障碍树获取标签页信息:

#include <string>
#include <iostream>
#include <windows.h>
#include <oleacc.h>
#include <atlbase.h>
#include <Poco/UnicodeConverter.h>

#pragma comment(lib, "oleacc.lib")

// Chromium的屏幕阅读器蜜罐对象ID,值为1
const DWORD kIdScreenReaderHoneyPot = 1;

// 获取IAccessible对象的角色
int GetAccessibleRole(IAccessible* pAcc) {
    VARIANT varRole;
    VariantInit(&varRole);
    if (!pAcc || FAILED(pAcc->get_accRole(CComVariant(CHILDID_SELF), &varRole)) || varRole.vt != VT_I4) {
        VariantClear(&varRole);
        return 0;
    }
    int role = varRole.intVal;
    VariantClear(&varRole);
    return role;
}

// 获取IAccessible对象的名称
std::wstring GetAccessibleName(IAccessible* pAcc) {
    CComBSTR bstrName;
    if (!pAcc || FAILED(pAcc->get_accName(CComVariant(CHILDID_SELF), &bstrName)) || !bstrName.m_str) {
        return L"";
    }
    return std::wstring(bstrName.m_str);
}

// 获取IAccessible对象的值(对应Chrome标签页的URL)
std::wstring GetAccessibleValue(IAccessible* pAcc) {
    CComBSTR bstrValue;
    if (!pAcc || FAILED(pAcc->get_accValue(CComVariant(CHILDID_SELF), &bstrValue)) || !bstrValue.m_str) {
        return L"";
    }
    return std::wstring(bstrValue.m_str);
}

// 递归遍历无障碍树,查找标签页的文档节点
HRESULT TraverseAccessibleTree(CComPtr<IAccessible> pAcc) {
    if (!pAcc) return E_INVALIDARG;

    long childCount = 0;
    HRESULT hr = pAcc->get_accChildCount(&childCount);
    if (FAILED(hr)) return hr;

    if (childCount == 0) return S_OK;

    CComVariant* pChildVariants = new CComVariant[childCount];
    long fetchedCount = 0;
    hr = ::AccessibleChildren(pAcc, 0, childCount, pChildVariants, &fetchedCount);
    if (FAILED(hr)) {
        delete[] pChildVariants;
        return hr;
    }

    for (long i = 0; i < fetchedCount; ++i) {
        CComVariant& varChild = pChildVariants[i];
        if (varChild.vt == VT_DISPATCH) {
            CComPtr<IDispatch> pDisp = varChild.pdispVal;
            CComQIPtr<IAccessible> pChildAcc = pDisp;
            if (pChildAcc) {
                int role = GetAccessibleRole(pChildAcc);
                // 查找Chrome标签页对应的文档节点(ROLE_SYSTEM_DOCUMENT)
                if (role == ROLE_SYSTEM_DOCUMENT) {
                    std::wstring name = GetAccessibleName(pChildAcc);
                    std::wstring value = GetAccessibleValue(pChildAcc);
                    if (!name.empty() && !value.empty()) {
                        std::string u8Name, u8Value;
                        Poco::UnicodeConverter::convert(name, u8Name);
                        Poco::UnicodeConverter::convert(value, u8Value);
                        std::cout << "标签页标题: " << u8Name << std::endl;
                        std::cout << "标签页URL: " << u8Value << std::endl;
                        // 找到一个就返回,也可以继续遍历找所有标签页
                        delete[] pChildVariants;
                        return S_FALSE;
                    }
                }
                // 递归遍历子节点
                if (TraverseAccessibleTree(pChildAcc) == S_FALSE) {
                    delete[] pChildVariants;
                    return S_FALSE;
                }
            }
        } else if (varChild.vt == VT_I4) {
            // 处理子控件ID的情况
            CComVariant varChildId(varChild.lVal);
            CComPtr<IAccessible> pChildAcc;
            hr = pAcc->QueryInterface(IID_IAccessible, (void**)&pChildAcc);
            if (SUCCEEDED(hr)) {
                int role = GetAccessibleRole(pChildAcc);
                if (role == ROLE_SYSTEM_DOCUMENT) {
                    std::wstring name = GetAccessibleName(pChildAcc);
                    std::wstring value = GetAccessibleValue(pChildAcc);
                    if (!name.empty() && !value.empty()) {
                        std::string u8Name, u8Value;
                        Poco::UnicodeConverter::convert(name, u8Name);
                        Poco::UnicodeConverter::convert(value, u8Value);
                        std::cout << "标签页标题: " << u8Name << std::endl;
                        std::cout << "标签页URL: " << u8Value << std::endl;
                        delete[] pChildVariants;
                        return S_FALSE;
                    }
                }
                TraverseAccessibleTree(pChildAcc);
            }
        }
    }

    delete[] pChildVariants;
    return S_OK;
}

// Windows事件回调函数
void CALLBACK WinEventProc(HWINEVENTHOOK hHook, DWORD event, HWND hwnd, 
                           LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) {
    if (event != EVENT_SYSTEM_ALERT) return;

    // 确认是Chrome窗口
    char className[256];
    if (GetClassNameA(hwnd, className, sizeof(className)) == 0) return;
    if (strstr(className, "Chrome") == nullptr) return;

    // 针对蜜罐对象ID=1发送WM_GETOBJECT查询,触发Chrome的无障碍检测
    if (idObject == kIdScreenReaderHoneyPot) {
        LRESULT result = SendMessageW(hwnd, WM_GETOBJECT, 0, OBJID_CLIENT);
        // 验证是否获取到IAccessible对象,触发后续逻辑
        CComPtr<IAccessible> pAcc;
        HRESULT hr = ObjectFromLresult(result, IID_IAccessible, 0, (void**)&pAcc);
        if (SUCCEEDED(hr)) {
            // 访问一些无障碍属性,触发Chrome启用完整支持
            VARIANT varName;
            pAcc->get_accName(CComVariant(CHILDID_SELF), &varName);
            VariantClear(&varName);
        }
    }
}

HWINEVENTHOOK g_hEventHook = nullptr;

// 设置Windows事件钩子
void SetupWinEventHook() {
    if (g_hEventHook != nullptr) return;
    CoInitialize(nullptr);
    g_hEventHook = SetWinEventHook(
        EVENT_SYSTEM_ALERT, EVENT_SYSTEM_ALERT,
        nullptr, WinEventProc,
        0, 0,
        WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS
    );
}

// 移除钩子
void RemoveWinEventHook() {
    if (g_hEventHook == nullptr) return;
    UnhookWinEvent(g_hEventHook);
    g_hEventHook = nullptr;
    CoUninitialize();
}

// 查找Chrome主窗口
HWND FindChromeMainWindow() {
    HWND hChromeWnd = nullptr;
    EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
        wchar_t className[256];
        if (GetClassNameW(hwnd, className, sizeof(className)) == 0) return TRUE;
        if (wcscmp(className, L"Chrome_WidgetWin_1") != 0) return TRUE;

        // 排除空标题的窗口(比如Chrome的后台窗口)
        wchar_t title[256];
        if (GetWindowTextW(hwnd, title, sizeof(title)) == 0) return TRUE;
        if (wcslen(title) == 0) return TRUE;

        *reinterpret_cast<HWND*>(lParam) = hwnd;
        return FALSE;
    }, reinterpret_cast<LPARAM>(&hChromeWnd));
    return hChromeWnd;
}

int _tmain(int argc, _TCHAR* argv[]) {
    // 先设置事件钩子,监听Chrome的Alert事件
    SetupWinEventHook();

    // 等待Chrome启动或者找到现有窗口
    HWND hChromeWnd = FindChromeMainWindow();
    if (hChromeWnd == nullptr) {
        std::cout << "未找到Chrome窗口,请先打开Chrome浏览器" << std::endl;
        RemoveWinEventHook();
        return 1;
    }

    // 初始化COM
    HRESULT hr = CoInitialize(nullptr);
    if (FAILED(hr)) {
        std::cout << "COM初始化失败" << std::endl;
        RemoveWinEventHook();
        return 1;
    }

    // 获取Chrome主窗口的IAccessible对象
    CComPtr<IAccessible> pMainAcc;
    hr = AccessibleObjectFromWindow(hChromeWnd, OBJID_CLIENT, IID_IAccessible, (void**)&pMainAcc);
    if (FAILED(hr) || !pMainAcc) {
        std::cout << "无法获取Chrome的IAccessible对象" << std::endl;
        CoUninitialize();
        RemoveWinEventHook();
        return 1;
    }

    // 等待Chrome完成无障碍初始化
    Sleep(2000);

    // 遍历无障碍树获取标签页信息
    std::cout << "正在获取Chrome标签页信息..." << std::endl;
    TraverseAccessibleTree(pMainAcc);

    // 清理资源
    RemoveWinEventHook();
    CoUninitialize();
    return 0;
}

关键调整点说明

  1. 正确触发蜜罐检测:在WinEventProc里,当收到Chrome的EVENT_SYSTEM_ALERT事件且对象ID为1时,主动发送WM_GETOBJECT并获取IAccessible对象,触发Chrome的OnScreenReaderHoneyPotQueried()逻辑
  2. 触发完整无障碍支持:在获取到IAccessible对象后,主动访问get_accName属性,让Chrome认为这是一个真正的辅助技术,从而启用完整的无障碍API支持
  3. 优化无障碍树遍历:完善了对不同类型子节点(VT_DISPATCH和VT_I4)的处理,确保能找到Chrome标签页对应的ROLE_SYSTEM_DOCUMENT节点
  4. 窗口查找逻辑优化:只找带有标题的Chrome_WidgetWin_1窗口,排除Chrome的后台空窗口

验证步骤

  1. 关闭所有Chrome窗口,重新打开Chrome(确保没有提前启用无障碍支持)
  2. 运行你的程序,程序会自动找到Chrome窗口并触发无障碍检测
  3. 打开Chrome的chrome://accessibility页面,你应该能看到“无障碍支持已启用”的提示
  4. 程序会输出当前活跃标签页的标题和URL

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:44:35