/** * Event: OnAttachForgeExtensions */ void __stdcall CBrowserHelperObject::OnAttachForgeExtensions(IDispatch *dispatch, const wstring& location, const wstring& eventsource) { m_isAttached = false; auto manifest = _AtlModule.moduleManifest; CComPtr<IDispatch> disp = nullptr; CComQIPtr<IHTMLWindow2, &IID_IHTMLWindow2> htmlWindow2 = nullptr; HRESULT hr = S_OK; for (;;) { CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> webBrowser2(dispatch); BreakOnNullWithErrorLog(webBrowser2, L"BrowserHelperObject::OnAttachForgeExtensions failed to obtain IWebBrowser2"); logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions -> " + boost::lexical_cast<wstring>(dispatch) + L" -> " + location + L" -> " + eventsource); // get interfaces hr = webBrowser2->get_Document(&disp); BreakOnNullWithErrorLog(disp, L"BrowserHelperObject::OnAttachForgeExtensions get_Document failed"); BreakOnFailed(hr); logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IDispatch -> " + boost::lexical_cast<wstring>(disp)); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); BreakOnNullWithErrorLog(htmlDocument2, L"BrowserHelperObject::OnAttachForgeExtensions IHTMLDocument2 failed"); logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IHTMLDocument2 -> " + boost::lexical_cast<wstring>(htmlDocument2)); hr = htmlDocument2->get_parentWindow(&htmlWindow2); BreakOnNullWithErrorLog(htmlWindow2, L"BrowserHelperObject::OnAttachForgeExtensions IHTMLWindow2 failed"); BreakOnFailed(hr); logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IHTMLWindow2 -> " + boost::lexical_cast<wstring>(htmlWindow2)); CComQIPtr<IDispatchEx> htmlWindow2Ex(htmlWindow2); BreakOnNullWithErrorLog(htmlWindow2Ex, L"BrowserHelperObject::OnAttachForgeExtensions IHTMLWindow2Ex failed"); // Attach NativeAccessible (forge.tabs.*) if (m_nativeAccessible) { logger->error(L"BrowserHelperObject::OnAttachForgeExtensions resetting nativeAccessible"); m_nativeAccessible.reset(); } m_nativeAccessible = NativeAccessible::pointer(new NativeAccessible(webBrowser2)); hr = Attach::NativeTabs(htmlWindow2Ex, L"accessible", m_nativeAccessible.get()); BreakOnFailedWithErrorLog(hr, L"BrowserHelperObject::OnAttachForgeExtensions failed to attach NativeExtensions -> " + logger->parse(hr)); // Attach NativeExtensions hr = Attach::NativeExtensions(manifest->uuid, htmlWindow2Ex, L"extensions", m_instanceId, location, &m_nativeExtensions.p); BreakOnFailedWithErrorLog(hr, L"BrowserHelperObject::OnAttachForgeExtensions failed to attach NativeExtensions -> " + logger->parse(hr)); logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions attached NativeExtensions"); // Attach NativeMessaging hr = Attach::NativeMessaging(manifest->uuid, htmlWindow2Ex, L"messaging", m_instanceId, &m_nativeMessaging.p); BreakOnFailedWithErrorLog(hr, L"BrowserHelperObject::OnAttachForgeExtensions failed to attach NativeMessaging -> " + logger->parse(hr)); /// finally logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions attached NativeMessaging"); m_isAttached = true; break; } }
/** * Event: OnAttachForgeExtensions */ void __stdcall CBrowserHelperObject::OnAttachForgeExtensions(IDispatch *dispatch, const wstring& location, const wstring& eventsource) { m_isAttached = false; CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> webBrowser2(dispatch); if (!webBrowser2) { logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L"failed to obtain IWebBrowser2"); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions" L" -> " + boost::lexical_cast<wstring>(dispatch) + L" -> " + location + L" -> " + eventsource); Manifest::pointer manifest = _AtlModule.moduleManifest; // get interfaces CComPtr<IDispatch> disp; webBrowser2->get_Document(&disp); if (!disp) { logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions get_Document failed"); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L" IDispatch -> " + boost::lexical_cast<wstring>(disp)); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); if (!htmlDocument2) { logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IHTMLDocument2 failed"); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L" IHTMLDocument2 -> " + boost::lexical_cast<wstring>(htmlDocument2)); CComQIPtr<IHTMLWindow2, &IID_IHTMLWindow2> htmlWindow2; htmlDocument2->get_parentWindow(&htmlWindow2); if (!htmlWindow2) { logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IHTMLWindow2 failed"); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L" IHTMLWindow2 -> " + boost::lexical_cast<wstring>(htmlWindow2)); CComQIPtr<IDispatchEx> htmlWindow2Ex(htmlWindow2); if (!htmlWindow2Ex) { logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions IHTMLWindow2Ex failed"); return; } HRESULT hr; // Attach NativeAccessible (forge.tabs.*) if (m_nativeAccessible) { logger->error(L"BrowserHelperObject::OnAttachForgeExtensions resetting nativeAccessible"); m_nativeAccessible.reset(); } m_nativeAccessible = NativeAccessible::pointer(new NativeAccessible(webBrowser2)); hr = Attach::NativeTabs(htmlWindow2Ex, L"accessible", m_nativeAccessible.get()); if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnAttachForgeExtensions " L"failed to attach NativeExtensions" L" -> " + logger->parse(hr)); return; } // Attach NativeExtensions hr = Attach::NativeExtensions(manifest->uuid, htmlWindow2Ex, L"extensions", m_instanceId, location, &m_nativeExtensions.p); // "T** operator&() throw()" asserts on p==NULL if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnAttachForgeExtensions " L"failed to attach NativeExtensions" L" -> " + logger->parse(hr)); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L"attached NativeExtensions"); // Attach NativeMessaging hr = Attach::NativeMessaging(manifest->uuid, htmlWindow2Ex, L"messaging", m_instanceId, &m_nativeMessaging.p); // "T** operator&() throw()" asserts on p==NULL if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnAttachForgeExtensions " L"failed to attach NativeMessaging" L" -> " + logger->parse(hr)); return; } logger->debug(L"BrowserHelperObject::OnAttachForgeExtensions " L"attached NativeMessaging"); m_isAttached = true; }
/** * Event: OnDocumentComplete */ void __stdcall CBrowserHelperObject::OnDocumentComplete(IDispatch *dispatch, VARIANT *url) { Manifest::pointer manifest = _AtlModule.moduleManifest; CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> webBrowser2(dispatch); if (!webBrowser2) { logger->debug(L"BrowserHelperObject::OnDocumentComplete " L"failed to obtain IWebBrowser2"); return; } // VARIANT *url chops off file:\\\ for local filesystem CComBSTR bstr; webBrowser2->get_LocationURL(&bstr); wstring location(bstr); if (location == L"" && url->bstrVal) { // get_LocationURL fails in the most egregious of ways location = url->bstrVal; } if (location == L"" && url->bstrVal == NULL) { logger->debug(L"BrowserHelperObject::OnDocumentComplete " L"blank location, not interested" L" -> " + manifest->uuid + L" -> " + location); return; } // match location against manifest std::pair<wstringvector, wstringvector> match = this->MatchManifest(webBrowser2, manifest, location); if (match.first.size() == 0 && match.second.size() == 0) { logger->debug(L"BrowserHelperObject::OnDocumentComplete not interested" L" -> " + manifest->uuid + L" -> " + wstring(url->bstrVal) + L" -> " + location); return; } logger->debug(L"BrowserHelperObject::OnDocumentComplete" L" -> " + manifest->uuid + L" -> " + wstring(url->bstrVal) + L" -> " + location); // get IHTMLWindow2 CComPtr<IDispatch> disp; webBrowser2->get_Document(&disp); if (!disp) { logger->debug(L"BrowserHelperObject::OnDocumentComplete get_Document failed"); return; } CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); if (!htmlDocument2) { logger->debug(L"BrowserHelperObject::OnDocumentComplete IHTMLDocument2 failed"); return; } CComQIPtr<IHTMLWindow2, &IID_IHTMLWindow2> htmlWindow2; htmlDocument2->get_parentWindow(&htmlWindow2); if (!htmlWindow2) { logger->debug(L"BrowserHelperObject::OnDocumentComplete IHTMLWindow2 failed"); return; } // attach forge extensions when pages like target=_blank didn't trigger navComplete event if (!m_isAttached) { this->OnAttachForgeExtensions(dispatch, location, L"OnDocumentComplete"); } // Inject styles wstringset dupes; HTMLDocument document(webBrowser2); ScriptExtensions::scriptvector matches = m_scriptExtensions->read_styles(match.first); ScriptExtensions::scriptvector::const_iterator i = matches.begin(); for (; i != matches.end(); i++) { if (dupes.find(i->first) != dupes.end()) { logger->debug(L"BrowserHelperObject::OnDocumentComplete already injected -> " + i->first); continue; } wstringpointer style = i->second; if (!style) { logger->debug(L"BrowserHelperObject::OnDocumentComplete invalid stylesheet -> " + i->first); continue; } HRESULT hr; hr = document.InjectStyle(style); if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnDocumentComplete failed to inject style" L" -> " + i->first + L" -> " + logger->parse(hr)); continue; } dupes.insert(i->first); logger->debug(L"BrowserHelperObject::OnDocumentComplete injected: " + i->first); } // Inject scripts dupes.clear(); matches = m_scriptExtensions->read_scripts(match.second); i = matches.begin(); for (; i != matches.end(); i++) { if (dupes.find(i->first) != dupes.end()) { logger->debug(L"BrowserHelperObject::OnDocumentComplete already injected -> " + i->first); continue; } wstringpointer script = i->second; if (!script) { logger->debug(L"BrowserHelperObject::OnDocumentComplete invalid script -> " + i->first); continue; } HRESULT hr; //hr = document.InjectScript(script); //hr = document.InjectScriptTag(HTMLDocument::attrScriptType, i->first); CComVariant ret; hr = htmlWindow2->execScript(CComBSTR(script->c_str()), L"javascript", &ret); if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnDocumentComplete failed to inject script" L" -> " + i->first + L" -> " + logger->parse(hr)); continue; } dupes.insert(i->first); logger->debug(L"BrowserHelperObject::OnDocumentComplete injected" L" -> " + location + L" -> " + i->first); } /*// Test in-process IAccessible access SHANDLE_PTR phwnd; hr = webBrowser2->get_HWND(&phwnd); HWND hwnd = reinterpret_cast<HWND>(phwnd); AccessibleBrowser ab(hwnd); wstringvector tabs = ab.tabs(); for (wstringvector::const_iterator tab = tabs.begin(); tab != tabs.end(); tab++) { logger->debug(L"BrowserHelperObject::OnDocumentComplete -> " + *tab); }*/ }
/** * Helper: MatchManifest -> IWebBrowser2 -> Manifest -> location -> (styles . scripts) */ std::pair<wstringvector, wstringvector> CBrowserHelperObject::MatchManifest(IWebBrowser2 *webBrowser2, const Manifest::pointer& manifest, const wstring& location) { // Do nothing for special urls if (!(location.find(L"http:") == 0 || location.find(L"https:") == 0 || location.find(L"file:") == 0 /*|| location.find(L"about:") == 0*/)) { logger->debug(L"BrowserHelperObject::MatchManifest boring url" L" -> " + location); return std::pair<wstringvector, wstringvector>(); } // is this a html document? CComPtr<IDispatch> disp; webBrowser2->get_Document(&disp); if (!disp) { logger->debug(L"BrowserHelperObject::MatchManifest " L"no document"); return std::pair<wstringvector, wstringvector>(); } CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); if (!htmlDocument2) { logger->debug(L"BrowserHelperObject::MatchManifest " L"not an IHTMLDocument2"); return std::pair<wstringvector, wstringvector>(); } // is this an iframe ? wstring locationtop = location; bool is_not_iframe = m_webBrowser2.IsEqualObject(webBrowser2); if (is_not_iframe) { } else { CComPtr<IDispatch> disp_parent; HRESULT hr = webBrowser2->get_Parent(&disp_parent); if (FAILED(hr) || !disp_parent) { logger->debug(L"BrowserHelperObject::MatchManifest not an iframe 1" L" -> " + location); return std::pair<wstringvector, wstringvector>(); } CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2_parent(disp_parent); if (!htmlDocument2_parent) { logger->debug(L"BrowserHelperObject::MatchManifest not an iframe 2" L" -> " + location); return std::pair<wstringvector, wstringvector>(); } CComBSTR bstr; htmlDocument2_parent->get_URL(&bstr); locationtop = bstr; } // check content_scripts.matches, content_scripts.all_frames std::pair<wstringvector, wstringvector> ret; Manifest::ContentScripts::const_iterator script = manifest->content_scripts.begin(); for (; script != manifest->content_scripts.end(); script++) { // check for exclude_matches bool exclude = false; wstringvector::const_iterator exclude_match = script->exclude_matches.begin(); for (; exclude_match != script->exclude_matches.end(); exclude_match++) { if (wstring_match_wild(*exclude_match, location)) { logger->debug(L"BrowserHelperObject::MatchManifest exclude " L" -> " + *exclude_match + L" -> " + location); exclude = true; } } // check for matches and add if, and only if there are no excludes for this activation wstringvector::const_iterator match = script->matches.begin(); for (; match != script->matches.end(); match++) { bool matches = wstring_match_wild(*match, location); if ((matches && is_not_iframe && !exclude) || // matches top-level || (matches && script->all_frames && !exclude)) { // matches iframe ret.first.insert(ret.first.end(), script->css.begin(), script->css.end()); ret.second.insert(ret.second.end(), script->js.begin(), script->js.end()); } } } return ret; }
// Implement refresh(F5 click, etc) based on // http://www.codeproject.com/Articles/3632/Detecting-the-IE-Refresh-button-using-IWebBrowser2 void CBrowserHelperObject::OnRefresh() { auto manifest = _AtlModule.moduleManifest; wstring location; wstringset dupes; CComBSTR bstr = nullptr; CComPtr<IDispatch> disp = nullptr; CComQIPtr<IHTMLWindow2, &IID_IHTMLWindow2> htmlWindow2 = nullptr; HRESULT hr = S_OK; for (;;) { // VARIANT *url chops off file:\\\ for local filesystem CComQIPtr<IWebBrowser2, &IID_IWebBrowser2> webBrowser2(m_webBrowser2); BreakOnNull(webBrowser2, hr); hr = webBrowser2->get_LocationURL(&bstr); BreakOnFailed(hr); location = wstring(bstr); // was m_strUrl if (location.empty()) { logger->debug(L"BrowserHelperObject::OnRefresh blank location, not interested -> " + manifest->uuid + L" -> " + location); break; } // match location against manifest auto& match = MatchManifest(webBrowser2, manifest, location); if (match.first.empty() && match.second.empty()) { logger->debug(L"BrowserHelperObject::OnRefresh not interested -> " + manifest->uuid + L" -> " + location); break; } logger->debug(L"BrowserHelperObject::OnRefresh -> " + manifest->uuid + L" -> " + location); // get IHTMLWindow2 hr = webBrowser2->get_Document(&disp); BreakOnNullWithErrorLog(disp, L"BrowserHelperObject::OnRefresh get_Document failed"); BreakOnFailed(hr); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); BreakOnNullWithErrorLog(htmlDocument2, L"BrowserHelperObject::OnRefresh IHTMLDocument2 failed"); hr = htmlDocument2->get_parentWindow(&htmlWindow2); BreakOnNullWithErrorLog(htmlWindow2, L"BrowserHelperObject::OnRefresh IHTMLWindow2 failed"); BreakOnFailed(hr); // attach forge extensions when pages like target=_blank didn't trigger navComplete event if (!m_isAttached) OnAttachForgeExtensions(m_webBrowser2, location, L"OnRefresh"); // was L"OnDocumentComplete" // Inject styles HTMLDocument document(webBrowser2); auto& matches = m_scriptExtensions->read_styles(match.first); for (auto& i : matches) { if (dupes.find(i.first) != dupes.end()) { logger->debug(L"BrowserHelperObject::OnRefresh already injected -> " + i.first); continue; } auto style = i.second; if (!style) { logger->debug(L"BrowserHelperObject::OnRefresh invalid stylesheet -> " + i.first); continue; } hr = document.InjectStyle(style); if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnRefresh failed to inject style -> " + i.first + L" -> " + logger->parse(hr)); continue; } dupes.insert(i.first); logger->debug(L"BrowserHelperObject::OnRefresh injected: " + i.first); } // Inject scripts dupes.clear(); matches = m_scriptExtensions->read_scripts(match.second); for (auto& i : matches) { if (dupes.find(i.first) != dupes.end()) { logger->debug(L"BrowserHelperObject::OnRefresh already injected -> " + i.first); continue; } auto script = i.second; if (!script) { logger->debug(L"BrowserHelperObject::OnRefresh invalid script -> " + i.first); continue; } CComVariant ret; hr = htmlWindow2->execScript(CComBSTR(script->c_str()), L"javascript", &ret); if (FAILED(hr)) { logger->error(L"BrowserHelperObject::OnRefresh failed to inject script -> " + i.first + L" -> " + logger->parse(hr)); continue; } dupes.insert(i.first); logger->debug(L"BrowserHelperObject::OnRefresh injected -> " + location + L" -> " + i.first); } break; } }
/** * Helper: MatchManifest -> IWebBrowser2 -> Manifest -> location -> (styles . scripts) */ std::pair<wstringvector, wstringvector> CBrowserHelperObject::MatchManifest(IWebBrowser2 *webBrowser2, const Manifest::pointer& manifest, const wstring& location) { CComPtr<IDispatch> disp = nullptr; CComPtr<IDispatch> disp_parent = nullptr; CComBSTR bstr = nullptr; std::pair<wstringvector, wstringvector> ret; HRESULT hr = S_OK; for (;;) { // Do nothing for special urls if (!(location.find(L"http:") == 0 || location.find(L"https:") == 0 || location.find(L"file:") == 0)) { logger->debug(L"BrowserHelperObject::MatchManifest boring url -> " + location); break; } // is this a html document? hr = webBrowser2->get_Document(&disp); BreakOnNullWithErrorLog(disp, L"BrowserHelperObject::MatchManifest no document"); BreakOnFailed(hr); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2(disp); BreakOnNullWithErrorLog(htmlDocument2, L"BrowserHelperObject::MatchManifest not an IHTMLDocument2"); // is this an iframe ? wstring locationtop = location; bool const is_not_iframe = m_webBrowser2.IsEqualObject(webBrowser2); if (!is_not_iframe) { hr = webBrowser2->get_Parent(&disp_parent); BreakOnNullWithErrorLog(disp_parent, L"BrowserHelperObject::MatchManifest not an iframe 1 -> " + location); BreakOnFailed(hr); CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2> htmlDocument2_parent(disp_parent); BreakOnNullWithErrorLog(htmlDocument2_parent, L"BrowserHelperObject::MatchManifest not an iframe 2 -> " + location); hr = htmlDocument2_parent->get_URL(&bstr); BreakOnFailed(hr); locationtop = bstr; } // check content_scripts.matches, content_scripts.all_frames for (auto& script : manifest->content_scripts) { // check for exclude_matches bool exclude = false; for (auto& exclude_match : script.exclude_matches) if (wstring_match_wild(exclude_match, location)) { logger->debug(L"BrowserHelperObject::MatchManifest exclude -> " + exclude_match + L" -> " + location); exclude = true; } // check for matches and add if, and only if there are no excludes for this activation for (auto& match : script.matches) { bool matches = wstring_match_wild(match, location); if ( (is_not_iframe || script.all_frames) && matches && !exclude) { ret.first.insert(ret.first.end(), script.css.begin(), script.css.end()); ret.second.insert(ret.second.end(), script.js.begin(), script.js.end()); } } } break; } return ret; }