| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome_frame/bho.h" | |
| 6 | |
| 7 #include <shlguid.h> | |
| 8 | |
| 9 #include "base/files/file_path.h" | |
| 10 #include "base/logging.h" | |
| 11 #include "base/path_service.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/stringprintf.h" | |
| 14 #include "base/time/time.h" | |
| 15 #include "base/win/scoped_bstr.h" | |
| 16 #include "chrome_frame/buggy_bho_handling.h" | |
| 17 #include "chrome_frame/crash_reporting/crash_metrics.h" | |
| 18 #include "chrome_frame/extra_system_apis.h" | |
| 19 #include "chrome_frame/html_utils.h" | |
| 20 #include "chrome_frame/http_negotiate.h" | |
| 21 #include "chrome_frame/metrics_service.h" | |
| 22 #include "chrome_frame/protocol_sink_wrap.h" | |
| 23 #include "chrome_frame/turndown_prompt/turndown_prompt.h" | |
| 24 #include "chrome_frame/urlmon_moniker.h" | |
| 25 #include "chrome_frame/utils.h" | |
| 26 #include "chrome_frame/vtable_patch_manager.h" | |
| 27 | |
| 28 static const int kIBrowserServiceOnHttpEquivIndex = 30; | |
| 29 static const DWORD kMaxHttpConnections = 6; | |
| 30 | |
| 31 PatchHelper g_patch_helper; | |
| 32 | |
| 33 BEGIN_VTABLE_PATCHES(IBrowserService) | |
| 34 VTABLE_PATCH_ENTRY(kIBrowserServiceOnHttpEquivIndex, Bho::OnHttpEquiv) | |
| 35 END_VTABLE_PATCHES() | |
| 36 | |
| 37 _ATL_FUNC_INFO Bho::kBeforeNavigate2Info = { | |
| 38 CC_STDCALL, VT_EMPTY, 7, { | |
| 39 VT_DISPATCH, | |
| 40 VT_VARIANT | VT_BYREF, | |
| 41 VT_VARIANT | VT_BYREF, | |
| 42 VT_VARIANT | VT_BYREF, | |
| 43 VT_VARIANT | VT_BYREF, | |
| 44 VT_VARIANT | VT_BYREF, | |
| 45 VT_BOOL | VT_BYREF | |
| 46 } | |
| 47 }; | |
| 48 | |
| 49 _ATL_FUNC_INFO Bho::kNavigateComplete2Info = { | |
| 50 CC_STDCALL, VT_EMPTY, 2, { | |
| 51 VT_DISPATCH, | |
| 52 VT_VARIANT | VT_BYREF | |
| 53 } | |
| 54 }; | |
| 55 | |
| 56 _ATL_FUNC_INFO Bho::kDocumentCompleteInfo = { | |
| 57 CC_STDCALL, VT_EMPTY, 2, { | |
| 58 VT_DISPATCH, | |
| 59 VT_VARIANT | VT_BYREF | |
| 60 } | |
| 61 }; | |
| 62 | |
| 63 Bho::Bho() { | |
| 64 } | |
| 65 | |
| 66 HRESULT Bho::FinalConstruct() { | |
| 67 return S_OK; | |
| 68 } | |
| 69 | |
| 70 void Bho::FinalRelease() { | |
| 71 } | |
| 72 | |
| 73 STDMETHODIMP Bho::SetSite(IUnknown* site) { | |
| 74 HRESULT hr = S_OK; | |
| 75 if (site) { | |
| 76 base::TimeTicks start = base::TimeTicks::Now(); | |
| 77 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 78 web_browser2.QueryFrom(site); | |
| 79 if (web_browser2) { | |
| 80 hr = DispEventAdvise(web_browser2, &DIID_DWebBrowserEvents2); | |
| 81 DCHECK(SUCCEEDED(hr)) << "DispEventAdvise failed. Error: " << hr; | |
| 82 | |
| 83 turndown_prompt::Configure(web_browser2); | |
| 84 } | |
| 85 | |
| 86 if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) { | |
| 87 base::win::ScopedComPtr<IBrowserService> browser_service; | |
| 88 hr = DoQueryService(SID_SShellBrowser, site, browser_service.Receive()); | |
| 89 DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed." | |
| 90 << " Site: " << site << " Error: " << hr; | |
| 91 if (browser_service) { | |
| 92 g_patch_helper.PatchBrowserService(browser_service); | |
| 93 DCHECK(SUCCEEDED(hr)) << "vtable_patch::PatchInterfaceMethods failed." | |
| 94 << " Site: " << site << " Error: " << hr; | |
| 95 } | |
| 96 } | |
| 97 // Save away our BHO instance in TLS which enables it to be referenced by | |
| 98 // our active document/activex instances to query referrer and other | |
| 99 // information for a URL. | |
| 100 AddRef(); | |
| 101 RegisterThreadInstance(); | |
| 102 MetricsService::Start(); | |
| 103 | |
| 104 if (!IncreaseWinInetConnections(kMaxHttpConnections)) { | |
| 105 DLOG(WARNING) << "Failed to bump up HTTP connections. Error:" | |
| 106 << ::GetLastError(); | |
| 107 } | |
| 108 | |
| 109 base::TimeDelta delta = base::TimeTicks::Now() - start; | |
| 110 UMA_HISTOGRAM_TIMES("ChromeFrame.BhoLoadSetSite", delta); | |
| 111 } else { | |
| 112 UnregisterThreadInstance(); | |
| 113 buggy_bho::BuggyBhoTls::DestroyInstance(); | |
| 114 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 115 web_browser2.QueryFrom(m_spUnkSite); | |
| 116 DispEventUnadvise(web_browser2, &DIID_DWebBrowserEvents2); | |
| 117 Release(); | |
| 118 } | |
| 119 | |
| 120 return IObjectWithSiteImpl<Bho>::SetSite(site); | |
| 121 } | |
| 122 | |
| 123 STDMETHODIMP Bho::BeforeNavigate2(IDispatch* dispatch, VARIANT* url, | |
| 124 VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data, | |
| 125 VARIANT* headers, VARIANT_BOOL* cancel) { | |
| 126 if (!url || url->vt != VT_BSTR || url->bstrVal == NULL) { | |
| 127 DLOG(WARNING) << "Invalid URL passed in"; | |
| 128 return S_OK; | |
| 129 } | |
| 130 | |
| 131 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 132 if (dispatch) | |
| 133 web_browser2.QueryFrom(dispatch); | |
| 134 | |
| 135 if (!web_browser2) { | |
| 136 NOTREACHED() << "Can't find WebBrowser2 with given dispatch"; | |
| 137 return S_OK; | |
| 138 } | |
| 139 | |
| 140 DVLOG(1) << "BeforeNavigate2: " << url->bstrVal; | |
| 141 | |
| 142 base::win::ScopedComPtr<IBrowserService> browser_service; | |
| 143 DoQueryService(SID_SShellBrowser, web_browser2, browser_service.Receive()); | |
| 144 if (!browser_service || !CheckForCFNavigation(browser_service, false)) { | |
| 145 // TODO(tommi): Remove? Isn't this done below by calling set_referrer("")? | |
| 146 referrer_.clear(); | |
| 147 } | |
| 148 | |
| 149 VARIANT_BOOL is_top_level = VARIANT_FALSE; | |
| 150 web_browser2->get_TopLevelContainer(&is_top_level); | |
| 151 if (is_top_level) { | |
| 152 set_url(url->bstrVal); | |
| 153 set_referrer(""); | |
| 154 set_post_data(post_data); | |
| 155 set_headers(headers); | |
| 156 } | |
| 157 return S_OK; | |
| 158 } | |
| 159 | |
| 160 STDMETHODIMP_(void) Bho::NavigateComplete2(IDispatch* dispatch, VARIANT* url) { | |
| 161 DVLOG(1) << __FUNCTION__; | |
| 162 } | |
| 163 | |
| 164 STDMETHODIMP_(void) Bho::DocumentComplete(IDispatch* dispatch, VARIANT* url) { | |
| 165 DVLOG(1) << __FUNCTION__; | |
| 166 | |
| 167 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 168 if (dispatch) | |
| 169 web_browser2.QueryFrom(dispatch); | |
| 170 | |
| 171 if (web_browser2) { | |
| 172 VARIANT_BOOL is_top_level = VARIANT_FALSE; | |
| 173 web_browser2->get_TopLevelContainer(&is_top_level); | |
| 174 if (is_top_level) { | |
| 175 CrashMetricsReporter::GetInstance()->IncrementMetric( | |
| 176 CrashMetricsReporter::NAVIGATION_COUNT); | |
| 177 } | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 namespace { | |
| 182 | |
| 183 // See comments in Bho::OnHttpEquiv for details. | |
| 184 void ClearDocumentContents(IUnknown* browser) { | |
| 185 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 186 if (SUCCEEDED(DoQueryService(SID_SWebBrowserApp, browser, | |
| 187 web_browser2.Receive()))) { | |
| 188 base::win::ScopedComPtr<IDispatch> doc_disp; | |
| 189 web_browser2->get_Document(doc_disp.Receive()); | |
| 190 base::win::ScopedComPtr<IHTMLDocument2> doc; | |
| 191 if (doc_disp && SUCCEEDED(doc.QueryFrom(doc_disp))) { | |
| 192 SAFEARRAY* sa = ::SafeArrayCreateVector(VT_UI1, 0, 0); | |
| 193 doc->write(sa); | |
| 194 ::SafeArrayDestroy(sa); | |
| 195 } | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 // Returns true if the currently loaded document in the browser has | |
| 200 // any embedded items such as a frame or an iframe. | |
| 201 bool DocumentHasEmbeddedItems(IUnknown* browser) { | |
| 202 bool has_embedded_items = false; | |
| 203 | |
| 204 base::win::ScopedComPtr<IWebBrowser2> web_browser2; | |
| 205 base::win::ScopedComPtr<IDispatch> document; | |
| 206 if (SUCCEEDED(DoQueryService(SID_SWebBrowserApp, browser, | |
| 207 web_browser2.Receive())) && | |
| 208 SUCCEEDED(web_browser2->get_Document(document.Receive()))) { | |
| 209 base::win::ScopedComPtr<IOleContainer> container; | |
| 210 if (SUCCEEDED(container.QueryFrom(document))) { | |
| 211 base::win::ScopedComPtr<IEnumUnknown> enumerator; | |
| 212 container->EnumObjects(OLECONTF_EMBEDDINGS, enumerator.Receive()); | |
| 213 if (enumerator) { | |
| 214 base::win::ScopedComPtr<IUnknown> unk; | |
| 215 DWORD fetched = 0; | |
| 216 while (!has_embedded_items && | |
| 217 SUCCEEDED(enumerator->Next(1, unk.Receive(), &fetched)) | |
| 218 && fetched) { | |
| 219 // If a top level document has embedded iframes then the theory is | |
| 220 // that first the top level document finishes loading and then the | |
| 221 // iframes load. We should only treat an embedded element as an | |
| 222 // iframe if it supports the IWebBrowser interface. | |
| 223 base::win::ScopedComPtr<IWebBrowser2> embedded_web_browser2; | |
| 224 if (SUCCEEDED(embedded_web_browser2.QueryFrom(unk))) { | |
| 225 // If we initiate a top level navigation then at times MSHTML | |
| 226 // creates a temporary IWebBrowser2 interface which basically shows | |
| 227 // up as a temporary iframe in the parent document. It is not clear | |
| 228 // as to how we can detect this. I tried the usual stuff like | |
| 229 // getting to the parent IHTMLWindow2 interface. They all end up | |
| 230 // pointing to dummy tear off interfaces owned by MSHTML. | |
| 231 // As a temporary workaround, we found that the location url in | |
| 232 // this case is about:blank. We now check for the same and don't | |
| 233 // treat it as an iframe. This should be fine in most cases as we | |
| 234 // hit this code only when the actual page has a meta tag. However | |
| 235 // this would break for cases like the initial src url for an | |
| 236 // iframe pointing to about:blank and the page then writing to it | |
| 237 // via document.write. | |
| 238 // TODO(ananta) | |
| 239 // Revisit this and come up with a better approach. | |
| 240 base::win::ScopedBstr location_url; | |
| 241 embedded_web_browser2->get_LocationURL(location_url.Receive()); | |
| 242 | |
| 243 std::wstring location_url_string; | |
| 244 location_url_string.assign(location_url, location_url.Length()); | |
| 245 | |
| 246 if (!LowerCaseEqualsASCII(location_url_string, "about:blank")) { | |
| 247 has_embedded_items = true; | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 fetched = 0; | |
| 252 unk.Release(); | |
| 253 } | |
| 254 } | |
| 255 } | |
| 256 } | |
| 257 | |
| 258 return has_embedded_items; | |
| 259 } | |
| 260 | |
| 261 } // end namespace | |
| 262 | |
| 263 HRESULT Bho::OnHttpEquiv(IBrowserService_OnHttpEquiv_Fn original_httpequiv, | |
| 264 IBrowserService* browser, IShellView* shell_view, BOOL done, | |
| 265 VARIANT* in_arg, VARIANT* out_arg) { | |
| 266 DVLOG(1) << __FUNCTION__ << " done:" << done; | |
| 267 | |
| 268 // OnHttpEquiv with 'done' set to TRUE is called for all pages. | |
| 269 // 0 or more calls with done set to FALSE are made. | |
| 270 // When done is FALSE, the current moniker may not represent the page | |
| 271 // being navigated to so we always have to wait for done to be TRUE | |
| 272 // before re-initiating the navigation. | |
| 273 | |
| 274 if (!done && in_arg && VT_BSTR == V_VT(in_arg)) { | |
| 275 if (StrStrI(V_BSTR(in_arg), kChromeContentPrefix)) { | |
| 276 // OnHttpEquiv is invoked for meta tags within sub frames as well. | |
| 277 // We want to switch renderers only for the top level frame. | |
| 278 // The theory here is that if there are any existing embedded items | |
| 279 // (frames or iframes) in the current document, then the http-equiv | |
| 280 // notification is coming from those and not the top level document. | |
| 281 // The embedded items should only be created once the top level | |
| 282 // doc has been created. | |
| 283 if (!DocumentHasEmbeddedItems(browser)) { | |
| 284 NavigationManager* mgr = NavigationManager::GetThreadInstance(); | |
| 285 DCHECK(mgr); | |
| 286 DVLOG(1) << "Found tag in page. Marking browser." | |
| 287 << base::StringPrintf(" tid=0x%08X", ::GetCurrentThreadId()); | |
| 288 if (mgr) { | |
| 289 // TODO(tommi): See if we can't figure out a cleaner way to avoid | |
| 290 // this. For some documents we can hit a problem here. When we | |
| 291 // attempt to navigate the document again in CF, mshtml can "complete" | |
| 292 // the current navigation (if all data is available) and fire off | |
| 293 // script events such as onload and even render the page. | |
| 294 // This will happen inside NavigateBrowserToMoniker below. | |
| 295 // To work around this, we clear the contents of the document before | |
| 296 // opening it up in CF. | |
| 297 ClearDocumentContents(browser); | |
| 298 mgr->NavigateToCurrentUrlInCF(browser); | |
| 299 } | |
| 300 } | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 return original_httpequiv(browser, shell_view, done, in_arg, out_arg); | |
| 305 } | |
| 306 | |
| 307 // static | |
| 308 void Bho::ProcessOptInUrls(IWebBrowser2* browser, BSTR url) { | |
| 309 if (!browser || !url) { | |
| 310 NOTREACHED(); | |
| 311 return; | |
| 312 } | |
| 313 | |
| 314 #ifndef NDEBUG | |
| 315 // This check must already have been made. | |
| 316 VARIANT_BOOL is_top_level = VARIANT_FALSE; | |
| 317 browser->get_TopLevelContainer(&is_top_level); | |
| 318 DCHECK(is_top_level); | |
| 319 #endif | |
| 320 | |
| 321 std::wstring current_url(url, SysStringLen(url)); | |
| 322 if (IsValidUrlScheme(GURL(current_url), false)) { | |
| 323 bool cf_protocol = StartsWith(current_url, kChromeProtocolPrefix, false); | |
| 324 if (!cf_protocol && IsChrome(RendererTypeForUrl(current_url))) { | |
| 325 DVLOG(1) << "Opt-in URL. Switching to cf."; | |
| 326 base::win::ScopedComPtr<IBrowserService> browser_service; | |
| 327 DoQueryService(SID_SShellBrowser, browser, browser_service.Receive()); | |
| 328 DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed."; | |
| 329 MarkBrowserOnThreadForCFNavigation(browser_service); | |
| 330 } | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 bool PatchHelper::InitializeAndPatchProtocolsIfNeeded() { | |
| 335 bool ret = false; | |
| 336 | |
| 337 _pAtlModule->m_csStaticDataInitAndTypeInfo.Lock(); | |
| 338 | |
| 339 if (state_ == UNKNOWN) { | |
| 340 g_trans_hooks.InstallHooks(); | |
| 341 HttpNegotiatePatch::Initialize(); | |
| 342 state_ = PATCH_PROTOCOL; | |
| 343 ret = true; | |
| 344 } | |
| 345 | |
| 346 _pAtlModule->m_csStaticDataInitAndTypeInfo.Unlock(); | |
| 347 | |
| 348 return ret; | |
| 349 } | |
| 350 | |
| 351 void PatchHelper::PatchBrowserService(IBrowserService* browser_service) { | |
| 352 DCHECK(state_ == PATCH_IBROWSER); | |
| 353 if (!IS_PATCHED(IBrowserService)) { | |
| 354 vtable_patch::PatchInterfaceMethods(browser_service, | |
| 355 IBrowserService_PatchInfo); | |
| 356 } | |
| 357 } | |
| 358 | |
| 359 void PatchHelper::UnpatchIfNeeded() { | |
| 360 if (state_ == PATCH_PROTOCOL) { | |
| 361 g_trans_hooks.RevertHooks(); | |
| 362 HttpNegotiatePatch::Uninitialize(); | |
| 363 } | |
| 364 state_ = UNKNOWN; | |
| 365 } | |
| OLD | NEW |