diff options
-rw-r--r-- | chrome_frame/bho.cc | 6 | ||||
-rw-r--r-- | chrome_frame/buggy_bho_handling.cc | 164 | ||||
-rw-r--r-- | chrome_frame/buggy_bho_handling.h | 45 | ||||
-rw-r--r-- | chrome_frame/chrome_active_document.cc | 22 | ||||
-rw-r--r-- | chrome_frame/utils.cc | 20 | ||||
-rw-r--r-- | chrome_frame/utils.h | 5 |
6 files changed, 157 insertions, 105 deletions
diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc index 2de3bc8d..0cd3a4a 100644 --- a/chrome_frame/bho.cc +++ b/chrome_frame/bho.cc @@ -12,7 +12,7 @@ #include "base/string_util.h" #include "base/stringprintf.h" #include "base/win/scoped_bstr.h" -#include "chrome_tab.h" // NOLINT +#include "chrome_frame/buggy_bho_handling.h" #include "chrome_frame/crash_reporting/crash_metrics.h" #include "chrome_frame/extra_system_apis.h" #include "chrome_frame/html_utils.h" @@ -138,6 +138,10 @@ STDMETHODIMP Bho::SetSite(IUnknown* site) { MetricsService::Start(); } else { UnregisterThreadInstance(); + buggy_bho::BuggyBhoTls::DestroyInstance(); + ScopedComPtr<IWebBrowser2> web_browser2; + web_browser2.QueryFrom(m_spUnkSite); + DispEventUnadvise(web_browser2, &DIID_DWebBrowserEvents2); Release(); } diff --git a/chrome_frame/buggy_bho_handling.cc b/chrome_frame/buggy_bho_handling.cc index 0805033..bcd2427 100644 --- a/chrome_frame/buggy_bho_handling.cc +++ b/chrome_frame/buggy_bho_handling.cc @@ -75,27 +75,105 @@ bool IsBuggyBho(HMODULE mod) { return false; } -BuggyBhoTls::BuggyBhoTls() : previous_instance_(s_bad_object_tls_.Get()) { +BuggyBhoTls::BuggyBhoTls() + : patched_(false) { + DCHECK(s_bad_object_tls_.Get() == NULL); s_bad_object_tls_.Set(this); } BuggyBhoTls::~BuggyBhoTls() { - DCHECK(FromCurrentThread() == this); - s_bad_object_tls_.Set(previous_instance_); + DCHECK(BuggyBhoTls::GetInstance() == this); + s_bad_object_tls_.Set(NULL); } void BuggyBhoTls::AddBuggyObject(IDispatch* obj) { bad_objects_.push_back(obj); } -bool BuggyBhoTls::IsBuggyObject(IDispatch* obj) const { - return std::find(bad_objects_.begin(), bad_objects_.end(), obj) != - bad_objects_.end(); +bool BuggyBhoTls::ShouldSkipInvoke(IDispatch* obj) const { + DCHECK(web_browser2_ != NULL); + if (IsChromeFrameDocument(web_browser2_)) { + return std::find(bad_objects_.begin(), bad_objects_.end(), obj) != + bad_objects_.end(); + } + return false; } // static -BuggyBhoTls* BuggyBhoTls::FromCurrentThread() { - return s_bad_object_tls_.Get(); +BuggyBhoTls* BuggyBhoTls::GetInstance() { + BuggyBhoTls* tls_instance = s_bad_object_tls_.Get(); + if (!tls_instance) { + tls_instance = new BuggyBhoTls(); + DCHECK(s_bad_object_tls_.Get() != NULL); + } + return tls_instance; +} + +// static +void BuggyBhoTls::DestroyInstance() { + BuggyBhoTls* tls_instance = s_bad_object_tls_.Get(); + if (tls_instance) { + delete tls_instance; + DCHECK(s_bad_object_tls_.Get() == NULL); + } +} + +HRESULT BuggyBhoTls::PatchBuggyBHOs(IWebBrowser2* browser) { + if (patched_) + return S_FALSE; + + DCHECK(browser); + DCHECK(web_browser2_ == NULL); + + ScopedComPtr<IConnectionPointContainer> cpc; + HRESULT hr = cpc.QueryFrom(browser); + if (SUCCEEDED(hr)) { + const GUID sinks[] = { DIID_DWebBrowserEvents2, DIID_DWebBrowserEvents }; + for (size_t i = 0; i < arraysize(sinks); ++i) { + ScopedComPtr<IConnectionPoint> cp; + cpc->FindConnectionPoint(sinks[i], cp.Receive()); + if (cp) { + ScopedComPtr<IEnumConnections> connections; + cp->EnumConnections(connections.Receive()); + if (connections) { + CONNECTDATA cd = {0}; + DWORD fetched = 0; + while (connections->Next(1, &cd, &fetched) == S_OK && fetched) { + PatchIfBuggy(cd.pUnk, sinks[i]); + cd.pUnk->Release(); + fetched = 0; + } + } + } + } + } + patched_ = true; + web_browser2_ = browser; + return hr; +} + +bool BuggyBhoTls::PatchIfBuggy(IUnknown* unk, const IID& diid) { + DCHECK(unk); + PROC* methods = *reinterpret_cast<PROC**>(unk); + HMODULE mod = GetModuleFromAddress(methods[0]); + if (!IsBuggyBho(mod)) + return false; + + ScopedComPtr<IDispatch> disp; + HRESULT hr = unk->QueryInterface(diid, + reinterpret_cast<void**>(disp.Receive())); + if (FAILED(hr)) // Sometimes only IDispatch QI is supported + hr = disp.QueryFrom(unk); + DCHECK(SUCCEEDED(hr)); + + if (SUCCEEDED(hr)) { + const int kInvokeIndex = 6; + DCHECK(static_cast<IUnknown*>(disp) == unk); + if (SUCCEEDED(PatchInvokeMethod(&methods[kInvokeIndex]))) { + AddBuggyObject(disp); + } + } + return false; } // static @@ -106,8 +184,10 @@ STDMETHODIMP BuggyBhoTls::BuggyBhoInvoke(InvokeFunc original, IDispatch* me, UINT* err) { DVLOG(1) << __FUNCTION__; - const BuggyBhoTls* tls = BuggyBhoTls::FromCurrentThread(); - if (tls && tls->IsBuggyObject(me)) { + DCHECK(BuggyBhoTls::GetInstance()) + << "You must first have an instance of BuggyBhoTls on this thread"; + if (BuggyBhoTls::GetInstance() && + BuggyBhoTls::GetInstance()->ShouldSkipInvoke(me)) { // Ignore this call and avoid the bug. // TODO(tommi): Maybe we should check a specific list of DISPIDs too? return S_OK; @@ -147,71 +227,7 @@ HRESULT BuggyBhoTls::PatchInvokeMethod(PROC* invoke) { ::FlushInstructionCache(::GetCurrentProcess(), invoke, sizeof(PROC)); } } - ::VirtualProtect(invoke, sizeof(PROC), flags, &flags); - - return hr; -} - -// static -bool BuggyBhoTls::PatchIfBuggy(CONNECTDATA* cd, const IID& diid) { - DCHECK(cd); - PROC* methods = *reinterpret_cast<PROC**>(cd->pUnk); - HMODULE mod = GetModuleFromAddress(methods[0]); - if (!IsBuggyBho(mod)) - return false; - - ScopedComPtr<IDispatch> disp; - HRESULT hr = cd->pUnk->QueryInterface(diid, - reinterpret_cast<void**>(disp.Receive())); - if (FAILED(hr)) // Sometimes only IDispatch QI is supported - hr = disp.QueryFrom(cd->pUnk); - DCHECK(SUCCEEDED(hr)); - - if (SUCCEEDED(hr)) { - const int kInvokeIndex = 6; - DCHECK(static_cast<IUnknown*>(disp) == cd->pUnk); - if (SUCCEEDED(PatchInvokeMethod(&methods[kInvokeIndex]))) { - BuggyBhoTls* tls = BuggyBhoTls::FromCurrentThread(); - DCHECK(tls); - if (tls) { - tls->AddBuggyObject(disp); - } - } - } - - return false; -} - -// static -HRESULT BuggyBhoTls::PatchBuggyBHOs(IWebBrowser2* browser) { - DCHECK(browser); - DCHECK(BuggyBhoTls::FromCurrentThread()) - << "You must first have an instance of BuggyBhoTls on this thread"; - - ScopedComPtr<IConnectionPointContainer> cpc; - HRESULT hr = cpc.QueryFrom(browser); - if (SUCCEEDED(hr)) { - const GUID sinks[] = { DIID_DWebBrowserEvents2, DIID_DWebBrowserEvents }; - for (size_t i = 0; i < arraysize(sinks); ++i) { - ScopedComPtr<IConnectionPoint> cp; - cpc->FindConnectionPoint(sinks[i], cp.Receive()); - if (cp) { - ScopedComPtr<IEnumConnections> connections; - cp->EnumConnections(connections.Receive()); - if (connections) { - CONNECTDATA cd = {0}; - DWORD fetched = 0; - while (connections->Next(1, &cd, &fetched) == S_OK && fetched) { - PatchIfBuggy(&cd, sinks[i]); - cd.pUnk->Release(); - fetched = 0; - } - } - } - } - } - return hr; } diff --git a/chrome_frame/buggy_bho_handling.h b/chrome_frame/buggy_bho_handling.h index 4f1e7f5..83c635a 100644 --- a/chrome_frame/buggy_bho_handling.h +++ b/chrome_frame/buggy_bho_handling.h @@ -12,6 +12,7 @@ #include <vector> #include "base/threading/thread_local.h" +#include "base/win/scoped_comptr.h" namespace buggy_bho { @@ -21,23 +22,32 @@ typedef HRESULT (__stdcall* InvokeFunc)(IDispatch* me, DISPID dispid, DISPPARAMS* params, VARIANT* result, EXCEPINFO* ei, UINT* err); -// Construct an instance of this class on the stack when firing web browser +// Construct a per thread instance of this class before firing web browser // events that can be sent to buggy BHOs. This class will intercept those // BHOs (see list in cc file) and ignore notifications to those components -// for as long as the BuggyBhoTls instance on the stack lives. +// as long as ChromeFrame is the active view. class BuggyBhoTls { public: - BuggyBhoTls(); - ~BuggyBhoTls(); - - // Call after instantiating an instance of BuggyBhoTls. This method traverses - // the list of DWebBrowserEvents and DWebBrowserEvents2 subscribers and checks - // if any of the sinks belong to a list of known-to-be-buggy BHOs. + // This method traverses the list of DWebBrowserEvents and DWebBrowserEvents2 + // subscribers and checks if any of the sinks belong to a list of + // known-to-be-buggy BHOs. // For each of those, a patch will be applied that temporarily ignores certain // invokes. - static HRESULT PatchBuggyBHOs(IWebBrowser2* browser); + HRESULT PatchBuggyBHOs(IWebBrowser2* browser); + + // Returns the instance of the BuggyBhoTls object for the current thread. + static BuggyBhoTls* GetInstance(); + + // Destroys the BuggyBhoTls instance foe the current thread. + static void DestroyInstance(); + + void set_web_browser(IWebBrowser2* web_browser2) { + web_browser2_ = web_browser2; + } protected: + BuggyBhoTls(); + ~BuggyBhoTls(); // internal implementation: // Called when a buggy instance is found to be subscribing to browser events. @@ -48,15 +58,12 @@ class BuggyBhoTls { // object running on the same thread (e.g. IE6) with one running CF and the // other MSHTML. We don't want to drop events being fired by MSHTML, only // events fired by CF since these BHOs should handle MSHTML correctly. - bool IsBuggyObject(IDispatch* obj) const; + bool ShouldSkipInvoke(IDispatch* obj) const; // Static, protected member methods - // Returns the currently registered (TLS) BuggyBhoTls instance or NULL. - static BuggyBhoTls* FromCurrentThread(); - // Patches a subscriber if it belongs to a buggy dll. - static bool PatchIfBuggy(CONNECTDATA* cd, const IID& diid); + bool PatchIfBuggy(IUnknown* unk, const IID& diid); // Patches the IDispatch::Invoke method. static HRESULT PatchInvokeMethod(PROC* invoke); @@ -71,14 +78,12 @@ class BuggyBhoTls { protected: // List of buggy subscribers. std::vector<IDispatch*> bad_objects_; - - // Pointer to a previous instance of BuggyBhoTls on this thread if any. - // Under regular circumstances, this will be NULL. However, there's a chance - // that we could get reentrant calls, hence we maintain a stack. - BuggyBhoTls* previous_instance_; - // Where we store the current thread's instance. static base::ThreadLocalPointer<BuggyBhoTls> s_bad_object_tls_; + // The IWebBrowser2 instance for this thread. + base::win::ScopedComPtr<IWebBrowser2> web_browser2_; + // Set to true when we are done patching the event sinks of buggy bho's. + bool patched_; }; } // end namespace buggy_bho diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc index 7e78931..2fc4a00 100644 --- a/chrome_frame/chrome_active_document.cc +++ b/chrome_frame/chrome_active_document.cc @@ -738,6 +738,19 @@ void ChromeActiveDocument::UpdateNavigationState( OLECMDEXECOPT_DODEFAULT, secure_lock_status.AsInput(), NULL); } + // A number of poorly written bho's crash in their event sink callbacks if + // chrome frame is the currently loaded document. This is because they expect + // chrome frame to implement interfaces like IHTMLDocument, etc. We patch the + // event sink's of these bho's and don't invoke the event sink if chrome + // frame is the currently loaded document. + if (GetConfigBool(true, kEnableBuggyBhoIntercept)) { + ScopedComPtr<IWebBrowser2> wb2; + DoQueryService(SID_SWebBrowserApp, m_spClientSite, wb2.Receive()); + if (wb2 && buggy_bho::BuggyBhoTls::GetInstance()) { + buggy_bho::BuggyBhoTls::GetInstance()->PatchBuggyBHOs(wb2); + } + } + // Ideally all navigations should come to Chrome Frame so that we can call // BeforeNavigate2 on installed BHOs and give them a chance to cancel the // navigation. However, in practice what happens is as below: @@ -768,15 +781,6 @@ void ChromeActiveDocument::UpdateNavigationState( ScopedComPtr<IDocObjectService> doc_object_svc; ScopedComPtr<IWebBrowserEventsService> web_browser_events_svc; - buggy_bho::BuggyBhoTls bad_bho_tls; - if (GetConfigBool(true, kEnableBuggyBhoIntercept)) { - ScopedComPtr<IWebBrowser2> wb2; - DoQueryService(SID_SWebBrowserApp, m_spClientSite, wb2.Receive()); - if (wb2) { - buggy_bho::BuggyBhoTls::PatchBuggyBHOs(wb2); - } - } - DoQueryService(__uuidof(web_browser_events_svc), m_spClientSite, web_browser_events_svc.Receive()); diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc index 210d6488..049294c 100644 --- a/chrome_frame/utils.cc +++ b/chrome_frame/utils.cc @@ -39,6 +39,8 @@ #include "net/base/escape.h" #include "net/http/http_util.h" +#include "chrome_tab.h" // NOLINT + using base::win::RegKey; using base::win::ScopedComPtr; @@ -1583,3 +1585,21 @@ std::wstring GetCurrentModuleVersion() { DCHECK(module_version_info.get() != NULL); return module_version_info->file_version(); } + +bool IsChromeFrameDocument(IWebBrowser2* web_browser) { + if (!web_browser) + return false; + + ScopedComPtr<IDispatch> doc; + web_browser->get_Document(doc.Receive()); + if (doc) { + // Detect if CF is rendering based on whether the document is a + // ChromeActiveDocument. Detecting based on hwnd is problematic as + // the CF Active Document window may not have been created yet. + ScopedComPtr<IChromeFrame> chrome_frame; + chrome_frame.QueryFrom(doc); + return chrome_frame.get() != NULL; + } + return false; +} + diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index 6b8b0ee..5847917 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -8,7 +8,6 @@ #include <OAidl.h> #include <windows.h> #include <wininet.h> - #include <string> #include <vector> @@ -23,6 +22,7 @@ class FilePath; interface IBrowserService; +interface IWebBrowser2; // utils.h : Various utility functions and classes @@ -615,4 +615,7 @@ bool CheckXUaCompatibleDirective(const std::string& directive, // Returns the version of the current module as a string. std::wstring GetCurrentModuleVersion(); +// Returns true if ChromeFrame is the currently loaded document. +bool IsChromeFrameDocument(IWebBrowser2* web_browser); + #endif // CHROME_FRAME_UTILS_H_ |