Имеется: браузер (IE, Chrome или Firefox), уже запущенный пользователем.
Требуется: написать программу, которая получит URL, который в данный момент введён в адресной строке.
Давайте подумаем, каким образом эту простенькую задачу решить НЕ получится:
1. FindWindow + GetWindowText
Первая идея — найти окно браузера, в нём дочернее окно адресной строки и взять URL оттуда. Практика показывает, что отдельное дочернее окно для адресной строки имеет только IE. FF и Chrome кросплатформенны, поэтому предпочитают весь свой контент отрисовывать самостоятельно.
2. Браузерное расширение, которое отдаст URL нашей программе (например, через запрос к localhost)
Можно. Но во-первых, для трёх браузеров нужно будет написать 3 разных расширения, а во-вторых, для FF и Chrome мы будем вынуждены распространять его только через их магазины расширений. Писать программу, работоспособность которой зависит от того, зачешется ли сегодня левая пятка модератора — нет уж, увольте.
3. Давайте напишем сниффер и посмотрим что там пользователь открывал
4. Давайте воспользуемся Remote Debugging Protocol ну или каким-нибудь Selenium-ом
Не подходит из-за ограничения условий исходной задачи: браузер уже запущен, мы не можем запустить новый подконтрольный экземпляр, нам нужно взаимодействовать с уже имеющимся.
5. Может быть, хуки?
Ну, внедриться-то мы в браузер сможем. А на что вешать хуки? Для IE всё ясно — SetWindowText для окна адресной строки (но с ним и более простой способ №1 проходил). А в FF и Chrome у нас нет каких-то чётко определённых объектов и интерфейсов, на которые мы можем завязаться. Можно что-то сделать с конкретной версией браузера, но универсального решения не получится.
6. Скриншот окна браузера, определение положения адресной строки, распознавание текста с картинки!
Уже как-то начинает смахивать на отчаяние, правда? Прикинем все варианты цветовых схем ОС, разрешений, масштабов, учтём наличие в браузере плагинов, цветовых схем, нестандартного расположения элементов, right-to-left языковых локалей ну и закончим случаем, когда окно адресной строки слишком узкое, чтобы вместить URL полностью.
7. Ваш вариант
А напишите в комментариях, какие ещё решения вам приходят в голову и мы подумаем, получится или нет.
А теперь один из правильных ответов: мы воспользуемся уже старенькой, но весьма стабильной и поддерживаемой всеми браузерами во всех ОС с Win95 до Win10 технологией Microsoft Active Accessibility, которая даст нам возможность не только получить текущий URL (при чём одинаковым образом для всех браузеров), но и вообще дать доступ ко всему контенту браузера — от самого родительского окна с его заголовком, меню, тулбаром, вкладками и до содержимого открытой веб-страницы вплоть до самого последнего её элемента.
Введение
Microsoft Active Accessibility (MSAA) придумали аж в 1997-ом году и сделали её для того, чтобы стало возможным писать экранные лупы, приложения для чтения текста с экрана и создания прочих программ, улучшающим взаимодействие с компьютером людей с ограниченными физическими возможностями (проблемы со зрением, слухом и т.д.). Поддержка технологии в IE появилась давно, в FF и Chrome тоже была добавлена чуть позже. С выходом Vista появилось улучшение — Windows Automation API, однако и старый добрый MSAA никуда не делся, отлично работает с последними ОС и браузерами.
Код
В общем, ничего сложного в коде нет. Входной точкой для нас будет родительское окно браузера, которое можно получить по его ClassID:
FindWindow(L"IEFrame", NULL); // IE
FindWindow(L"MozillaWindowClass", NULL); // Firefox
FindWindow(L"Chrome_WidgetWin_1", NULL); // Chrome. Этот код может сработать, но вообще-то документация (http://ift.tt/18SwqVJ) рекомендует перебирать все окна, класс которых начинающиеся с "Chrome", на случай, если им взбредёт в голову изменить название класса. Из практики можно добавить, что перебирать нужно окна с таким class name и непустым заголовком.
Дальше нужно у этого окна получить указатель на COM-интерфейс IAccessible
::AccessibleObjectFromWindow(hWndChrome, OBJID_CLIENT, IID_IAccessible, (void**)(&pAccMain));
Да, перед этим не забудьте:
- Подключить заголовочный файл #include «oleacc.h»
- Прилинковать Oleacc.lib
- Инициализировать COM вызовом функции ::CoInitialize(NULL);
Это очень важно не забыть! Без этого у вас что-то может начать работать, но в непредвиденные моменты вы получите странные ошибки. Также возможна ситуация, когда никаких ошибок не будет, но вы просто не получите часть данных. В общем, очень подлая и совершенное не поддающаяся отладке ошибка.
Итак, у нас есть указатель на IAccessible. Что это такое? Это корневой узел дерева, описывающего весь браузер — окно, заголовок, меню, тулбары, адресную строку, контент страницы, статусбар. Как бы это всё увидеть в наглядном виде? Нет ничего проще! Microsoft для этого предоставляет утилиту inspect.exe (поставляется с Windows SDK, у меня лежит в папке C:\Program Files (x86)\Windows Kits\8.0\bin\x64). Разработчики Хромиума рекомендуют утилиту aViewer.
Давайте посмотрим, как выглядят деревья доступных элементов браузеров:
IE
Chrome
Firefox
Как мы видим, адресная строка доступна через интерфейс IAccessible во всех браузерах. Названия элементов, положение в дереве в разных браузерах разное, но в общем-то для доступа к адресной строке нам нужно только пара функций: возможность получения имени и значения текущего элемента и возможность получения детей текущего элемента дерева.
И то и другое пишется просто, вот финальный код, получающий текущий URL для Chrome.
#include "stdafx.h"
#include <string>
#include <iostream>
#include "windows.h"
#include "oleacc.h"
#include "atlbase.h"
std::wstring GetName(IAccessible *pAcc)
{
CComBSTR bstrName;
if (!pAcc || FAILED(pAcc->get_accName(CComVariant((int)CHILDID_SELF), &bstrName)) || !bstrName.m_str)
return L"";
return bstrName.m_str;
}
HRESULT WalkTreeWithAccessibleChildren(CComPtr<IAccessible> pAcc)
{
long childCount = 0;
long returnCount = 0;
HRESULT hr = pAcc->get_accChildCount(&childCount);
if (childCount == 0)
return S_OK;
CComVariant* pArray = new CComVariant[childCount];
hr = ::AccessibleChildren(pAcc, 0L, childCount, pArray, &returnCount);
if (FAILED(hr))
return hr;
for (int x = 0; x < returnCount; x++)
{
CComVariant vtChild = pArray[x];
if (vtChild.vt != VT_DISPATCH)
continue;
CComPtr<IDispatch> pDisp = vtChild.pdispVal;
CComQIPtr<IAccessible> pAccChild = pDisp;
if (!pAccChild)
continue;
std::wstring name = GetName(pAccChild).data();
if (name.find(L"Адресная строка и строка поиска") != -1)
{
CComBSTR bstrValue;
if (SUCCEEDED(pAccChild->get_accValue(CComVariant((int)CHILDID_SELF), &bstrValue)) && bstrValue.m_str)
std::wcout << std::wstring(bstrValue.m_str).c_str();
return S_FALSE;
}
if (WalkTreeWithAccessibleChildren(pAccChild) == S_FALSE)
return S_FALSE;
}
delete[] pArray;
return S_OK;
}
HWND hWndChrome = NULL;
BOOL CALLBACK FindChromeWindowProc(HWND hwnd, LPARAM lParam)
{
wchar_t className[100];
if (GetClassName(hwnd, className, 100) == 0 || wcscmp(className, L"Chrome_WidgetWin_1") != 0)
return TRUE;
wchar_t title[1000];
if (GetWindowText(hwnd, title, 1000) == 0 || wcslen(title) == 0)
return TRUE;
hWndChrome = hwnd;
return FALSE;
}
int _tmain(int argc, _TCHAR* argv[])
{
::CoInitialize(NULL);
EnumWindows(FindChromeWindowProc, 0);
if (hWndChrome == NULL)
return 0;
CComPtr<IAccessible> pAccMain;
HRESULT hr = ::AccessibleObjectFromWindow(hWndChrome, 1, IID_IAccessible, (void**)(&pAccMain)); // 1 - захардкоженный идентификатор ловушки
CComPtr<IAccessible> pAccMain2;
::AccessibleObjectFromWindow(hWndChrome, OBJID_CLIENT, IID_IAccessible, (void**)(&pAccMain2));
WalkTreeWithAccessibleChildren(pAccMain2);
return 0;
}
Результат работы:
Для остальных браузеров всё аналогично.
Мелкий нюанс
Технология MSAA в Chrome по-умолчанию отключена. Это связано с архитектурой Хрома: его разделение на процессы приводит к тому, что ни в каком одном процессе нет информации обо всём дереве элементов, необходимых MSAA. Разработчики Хрома не дураки и предусмотрели включение сбора этой информации и её кеширование в главном процессе. Но поскольку это всё несколько ресурсозатратно, а технология MSAA нужна относительно небольшому количеству людей — они её по-умолчанию выключили. Включить её можно двумя способами:
- Ручной: пойти в Хроме по ссылке chrome://accessibility и включить
- Программный: Хром создаёт специальную «ловушку», которой можно послать сообщение о том, что в системе присутствует приложение, использующее MSAA. Отправить в эту ловушку сообщение можно вот так:
CComPtr<IAccessible> pAccMain;
HRESULT hr = ::AccessibleObjectFromWindow(hwnd, 1, IID_IAccessible, (void**)(&pAccMain)); // hwnd - главное окно Хрома, 1 - захардкоженный идентификатор ловушки
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий