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.
5 #include "chrome_frame/bho.h"
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"
28 static const int kIBrowserServiceOnHttpEquivIndex
= 30;
29 static const DWORD kMaxHttpConnections
= 6;
31 PatchHelper g_patch_helper
;
33 BEGIN_VTABLE_PATCHES(IBrowserService
)
34 VTABLE_PATCH_ENTRY(kIBrowserServiceOnHttpEquivIndex
, Bho::OnHttpEquiv
)
37 _ATL_FUNC_INFO
Bho::kBeforeNavigate2Info
= {
38 CC_STDCALL
, VT_EMPTY
, 7, {
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
,
49 _ATL_FUNC_INFO
Bho::kNavigateComplete2Info
= {
50 CC_STDCALL
, VT_EMPTY
, 2, {
56 _ATL_FUNC_INFO
Bho::kDocumentCompleteInfo
= {
57 CC_STDCALL
, VT_EMPTY
, 2, {
66 HRESULT
Bho::FinalConstruct() {
70 void Bho::FinalRelease() {
73 STDMETHODIMP
Bho::SetSite(IUnknown
* site
) {
76 base::TimeTicks start
= base::TimeTicks::Now();
77 base::win::ScopedComPtr
<IWebBrowser2
> web_browser2
;
78 web_browser2
.QueryFrom(site
);
80 hr
= DispEventAdvise(web_browser2
, &DIID_DWebBrowserEvents2
);
81 DCHECK(SUCCEEDED(hr
)) << "DispEventAdvise failed. Error: " << hr
;
83 turndown_prompt::Configure(web_browser2
);
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
;
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.
101 RegisterThreadInstance();
102 MetricsService::Start();
104 if (!IncreaseWinInetConnections(kMaxHttpConnections
)) {
105 DLOG(WARNING
) << "Failed to bump up HTTP connections. Error:"
109 base::TimeDelta delta
= base::TimeTicks::Now() - start
;
110 UMA_HISTOGRAM_TIMES("ChromeFrame.BhoLoadSetSite", delta
);
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
);
120 return IObjectWithSiteImpl
<Bho
>::SetSite(site
);
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";
131 base::win::ScopedComPtr
<IWebBrowser2
> web_browser2
;
133 web_browser2
.QueryFrom(dispatch
);
136 NOTREACHED() << "Can't find WebBrowser2 with given dispatch";
140 DVLOG(1) << "BeforeNavigate2: " << url
->bstrVal
;
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("")?
149 VARIANT_BOOL is_top_level
= VARIANT_FALSE
;
150 web_browser2
->get_TopLevelContainer(&is_top_level
);
152 set_url(url
->bstrVal
);
154 set_post_data(post_data
);
155 set_headers(headers
);
160 STDMETHODIMP_(void) Bho::NavigateComplete2(IDispatch
* dispatch
, VARIANT
* url
) {
161 DVLOG(1) << __FUNCTION__
;
164 STDMETHODIMP_(void) Bho::DocumentComplete(IDispatch
* dispatch
, VARIANT
* url
) {
165 DVLOG(1) << __FUNCTION__
;
167 base::win::ScopedComPtr
<IWebBrowser2
> web_browser2
;
169 web_browser2
.QueryFrom(dispatch
);
172 VARIANT_BOOL is_top_level
= VARIANT_FALSE
;
173 web_browser2
->get_TopLevelContainer(&is_top_level
);
175 CrashMetricsReporter::GetInstance()->IncrementMetric(
176 CrashMetricsReporter::NAVIGATION_COUNT
);
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);
194 ::SafeArrayDestroy(sa
);
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;
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());
214 base::win::ScopedComPtr
<IUnknown
> unk
;
216 while (!has_embedded_items
&&
217 SUCCEEDED(enumerator
->Next(1, unk
.Receive(), &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.
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());
243 std::wstring location_url_string
;
244 location_url_string
.assign(location_url
, location_url
.Length());
246 if (!LowerCaseEqualsASCII(location_url_string
, "about:blank")) {
247 has_embedded_items
= true;
258 return has_embedded_items
;
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
;
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.
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();
286 DVLOG(1) << "Found tag in page. Marking browser."
287 << base::StringPrintf(" tid=0x%08X", ::GetCurrentThreadId());
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
);
304 return original_httpequiv(browser
, shell_view
, done
, in_arg
, out_arg
);
308 void Bho::ProcessOptInUrls(IWebBrowser2
* browser
, BSTR url
) {
309 if (!browser
|| !url
) {
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
);
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
);
334 bool PatchHelper::InitializeAndPatchProtocolsIfNeeded() {
337 _pAtlModule
->m_csStaticDataInitAndTypeInfo
.Lock();
339 if (state_
== UNKNOWN
) {
340 g_trans_hooks
.InstallHooks();
341 HttpNegotiatePatch::Initialize();
342 state_
= PATCH_PROTOCOL
;
346 _pAtlModule
->m_csStaticDataInitAndTypeInfo
.Unlock();
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
);
359 void PatchHelper::UnpatchIfNeeded() {
360 if (state_
== PATCH_PROTOCOL
) {
361 g_trans_hooks
.RevertHooks();
362 HttpNegotiatePatch::Uninitialize();