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 } 366