2

There are several answered questions about this on stackoverflow, but they seem to be outdated and don't work anymore. Chrome has changed its structure entirely. If I try the AccessibleObjectFromEvent technique, then I just get NULL values for the accName and accValue. It seems that there are solutions for python, however I could not find any solution for C++. How can I retrieve the active Tab URL in C++?

Brotcrunsher
  • 1,964
  • 10
  • 32
  • I'm sure this is a dupe. Search a bit more. – David Heffernan Jan 29 '18 at 16:39
  • Have you tried to debug the code? Checking the return values of `get_accValue()` and `get_accName()` should give some clues. – zett42 Jan 29 '18 at 16:53
  • @DavidHeffernan I looked again, through most of the newest (~1 year old) c++ questions and could not find a solution, only the old one which does not work anymore. – Brotcrunsher Jan 29 '18 at 17:20
  • @zett42 As stated, both return NULL. – Brotcrunsher Jan 29 '18 at 17:20
  • It's still a dupe. And if you can see how to do it with UIAutomation from Python then you can just translate to C++. Or do you need somebody else to do the job for you. – David Heffernan Jan 29 '18 at 18:20
  • @Brotcrunsher I don't mean the pointers, but the *return* value of the function, e. g. `HRESULT hr = pAcc->get_accValue(varChild, &bstrValue);` -- what is the value of `hr` after the function returned? – zett42 Jan 29 '18 at 18:40
  • I've got [this code](https://stackoverflow.com/a/26062192/7571258) to work after making these changes: `VARIANT varSelf; varSelf.vt = VT_I4; varSelf.lVal = CHILDID_SELF;` and then `pAcc->get_accName(varSelf, &bstrName)` and `pAcc->get_accValue(varSelf, &bstrValue)` – zett42 Jan 29 '18 at 22:06

2 Answers2

14

We need UI Automation

Using the Inspect tool in Window SDK, we can get the property name for Chrome's address bar:

Name:           "Address and search bar"
ControlType:    UIA_EditControlTypeId

We will look for UIA_EditControlTypeId because it is language independent.

But there can be another edit box in the html document which may show up before the main toolbar. Therefore we need to skip the html document, and find the 'pane' which contains the address bar. This pane is called "Google Chrome" and there should be only one of them. The address bar is the child of this unique "Google Chrome pane".

There is no address bar if the browser is in full screen mode. We need a backup method to find the address. For example you can look it up in the document. But the document may not be available either, for example when Chrome has been minimized.

The following example uses ATL COM classes, it requires Visual Studio

#define UNICODE
#include <Windows.h>
#include <stdio.h>
#include <AtlBase.h>
#include <AtlCom.h>
#include <UIAutomation.h>

//this method fails if browser is in full-screen mode
bool find_url(IUIAutomation* uia, IUIAutomationElement* root)
{
    // The root window has several childs, 
    // one of them is a "pane" named "Google Chrome"
    // This contains the toolbar. Find this "Google Chrome" pane:
    CComPtr<IUIAutomationElement> pane;
    CComPtr<IUIAutomationCondition> pane_cond;
    uia->CreatePropertyCondition(UIA_ControlTypePropertyId,
        CComVariant(UIA_PaneControlTypeId), &pane_cond);

    CComPtr<IUIAutomationElementArray> arr;
    if FAILED(root->FindAll(TreeScope_Children, pane_cond, &arr))
        return false;

    int count = 0;
    arr->get_Length(&count);
    for (int i = 0; i < count; i++)
    {
        CComBSTR name;
        if SUCCEEDED(arr->GetElement(i, &pane))
            if SUCCEEDED(pane->get_CurrentName(&name))
                if (wcscmp(name, L"Google Chrome") == 0)
                    break;
        pane.Release();
    }

    if (!pane)
        return false;

    //look for first UIA_EditControlTypeId under "Google Chrome" pane
    CComPtr<IUIAutomationElement> url;
    CComPtr<IUIAutomationCondition> url_cond;
    uia->CreatePropertyCondition(UIA_ControlTypePropertyId, 
        CComVariant(UIA_EditControlTypeId), &url_cond);
    if FAILED(pane->FindFirst(TreeScope_Descendants, url_cond, &url))
        return false;

    //get value of `url`
    CComVariant var;
    if FAILED(url->GetCurrentPropertyValue(UIA_ValueValuePropertyId, &var)) 
        return false;
    if (!var.bstrVal)
        return false;
    wprintf(L"find_url: %s\n", var.bstrVal);

    //set new address ...
    IValueProvider* pattern = nullptr;
    if (FAILED(url->GetCurrentPattern(UIA_ValuePatternId, (IUnknown**)&pattern)))
        return false;
    //pattern->SetValue(L"somewhere.com");
    pattern->Release();

    INPUT input[2] = { INPUT_KEYBOARD };
    input[0].ki.wVk = VK_RETURN;
    input[1] = input[0];
    input[1].ki.dwFlags |= KEYEVENTF_KEYUP;
    SendInput(2, input, sizeof(INPUT));

    return true;
}
    
int main()
{
    //find the first visible chrome window
    HWND hwnd = nullptr;
    while (true)
    {
        hwnd = FindWindowEx(nullptr, hwnd, L"Chrome_WidgetWin_1", nullptr);
        if (!hwnd)
            return 0;
        if (IsWindowVisible(hwnd) && GetWindowTextLength(hwnd) > 0)
            break;
    }

    //CoInitializeEx(nullptr, COINIT_MULTITHREADED);//<- pick the right one
    CoInitialize();
    CComPtr<IUIAutomation> uia;
    if SUCCEEDED(uia.CoCreateInstance(CLSID_CUIAutomation))
    {
        CComPtr<IUIAutomationElement> root;
        if SUCCEEDED(uia->ElementFromHandle(hwnd, &root))
            find_url(uia, root);
        uia.Release();
    }

    CoUninitialize();
    return 0;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • In my case, `found` is always `NULL`, which indicates that no `"Address and search bar"` could be found. Am I missing something? I have read that chrome should be started with the "--enable-accessibility --force-renderer-accessibility" parameters, which did not help either. Probably these parameters are obsolete as well. – Brotcrunsher Jan 29 '18 at 21:07
  • Is your code exactly same as mine? Maybe *"Address and search bar"* is English only. I updated the answer to find the first edit control, or find the edit control with integer id instead. Use the Inspect tool on your computer to verify. Inspect may already be installed on your computer: `C:\Program Files (x86)\Windows Kits\8.1\bin\x64\inspect.exe` – Barmak Shemirani Jan 29 '18 at 23:04
  • Indeed, it was a localization issue. I am using a German OS and Chrome and in this case, it's named `"Adress- und Suchleiste"`. The previous code works with this change. This current code does not work however, because the first edit control is something that has an empty name `""` and the id seems to change when I restart chrome. Thanks a lot, you were really helpful! – Brotcrunsher Jan 29 '18 at 23:29
  • That's good. I made some improvement, can you try again with `0xC354`. There was a problem before. It didn't find the right tab if Chrome's Task Manager was active. – Barmak Shemirani Jan 30 '18 at 02:52
  • Works like a charm, without the localization issue! – Brotcrunsher Jan 30 '18 at 09:42
  • Regarding if(!hwnd) break; in case the URL is redirected to another URL, you will miss the address eventually gone to. Better to change it to if(!hwnd) continue; – Michael Haephrati Aug 03 '19 at 16:25
  • @MichaelHaephrati Your suggestion will cause infinite loop, as `FindWindowEx(0, hwnd...)` will keep repeating itself. – Barmak Shemirani Aug 03 '19 at 16:53
  • @BarmakShemirani you may be right but just so you know, if there is a slight delay before the final URL is displayed your code might not capture the URL. – Michael Haephrati Aug 03 '19 at 17:15
  • @MichaelHaephrati You'll have to wait before calling `GetCurrentPropertyValue`, for example using `Sleep` with some arbitrary value. Or continuously monitory the value for the first few seconds. – Barmak Shemirani Aug 03 '19 at 18:37
  • @BarmakShemiran, could you please update your answer accordingly? There are several examples that won't be fetched with your current version (try smith.com ) but I won't use Sleep() because you can't know the exact amount of time to wait, was it depends on browser, internet speed, etc. Maybe you can think of a better way? – Michael Haephrati Aug 03 '19 at 19:15
  • @MichaelHaephrati There is no easy way to do that reliably without using chrome engine and receiving a notification after redirect. This post is only about chrome browser. The redirect could be done by the server, html meta tag, javascript... with 0 second delay or 5 second delay... You can check this [link](https://www.google.com/search?&q=detect+url+forwarding+chrome) or ask a separate question. – Barmak Shemirani Aug 03 '19 at 23:38
  • Windows? What about other OSes? – Mariusz Jaskółka Jul 02 '20 at 16:32
  • @jaskmar, I only know Windows. You can post a new question with a [linux], [mac], or other specific tags. – Barmak Shemirani Jul 02 '20 at 17:38
0

This part unfortunately never gives name what result in a throw error

   if SUCCEEDED(pane->get_CurrentName(&name))
       if (wcscmp(name, L"Google Chrome") == 0)
          break;

I have change it for

        pane->get_CurrentClassName(&name);
        uint8_t length = SysStringLen(name);
        if (!length) //not correct element had length 23 "Intermediate D3D Window"; get_CurrentName gives nothing :(C
            break;

Chrome Version 114.0.5735.199 (Offizieller Build) (64-Bit)

PiotrBzdrega
  • 38
  • 1
  • 7