- add sources.
[platform/framework/web/crosswalk.git] / src / chrome_frame / bho.cc
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 }