Home | History | Annotate | Download | only in chrome_frame
      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