diff options
Diffstat (limited to 'chrome_frame')
24 files changed, 2534 insertions, 263 deletions
diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc index e275707..c5c8aa0 100644 --- a/chrome_frame/bho.cc +++ b/chrome_frame/bho.cc @@ -5,30 +5,24 @@ #include "chrome_frame/bho.h" #include <shlguid.h> -#include <shobjidl.h> #include "base/file_path.h" #include "base/logging.h" #include "base/path_service.h" #include "base/registry.h" #include "base/scoped_bstr_win.h" -#include "base/scoped_comptr_win.h" #include "base/scoped_variant_win.h" #include "base/string_util.h" #include "chrome_tab.h" // NOLINT #include "chrome_frame/extra_system_apis.h" #include "chrome_frame/http_negotiate.h" #include "chrome_frame/protocol_sink_wrap.h" +#include "chrome_frame/urlmon_moniker.h" #include "chrome_frame/utils.h" #include "chrome_frame/vtable_patch_manager.h" -#include "net/http/http_util.h" -const wchar_t kPatchProtocols[] = L"PatchProtocols"; static const int kIBrowserServiceOnHttpEquivIndex = 30; -base::LazyInstance<base::ThreadLocalPointer<Bho> > - Bho::bho_current_thread_instance_(base::LINKER_INITIALIZED); - PatchHelper g_patch_helper; BEGIN_VTABLE_PATCHES(IBrowserService) @@ -47,6 +41,13 @@ _ATL_FUNC_INFO Bho::kBeforeNavigate2Info = { } }; +_ATL_FUNC_INFO Bho::kNavigateComplete2Info = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + Bho::Bho() { } @@ -82,9 +83,9 @@ STDMETHODIMP Bho::SetSite(IUnknown* site) { // our active document/activex instances to query referrer and other // information for a URL. AddRef(); - bho_current_thread_instance_.Pointer()->Set(this); + RegisterThreadInstance(); } else { - bho_current_thread_instance_.Pointer()->Set(NULL); + UnregisterThreadInstance(); Release(); } @@ -114,62 +115,19 @@ STDMETHODIMP Bho::BeforeNavigate2(IDispatch* dispatch, VARIANT* url, if (!browser_service || !CheckForCFNavigation(browser_service, false)) { referrer_.clear(); } - url_ = url->bstrVal; - ProcessOptInUrls(web_browser2, url->bstrVal); - return S_OK; -} - -HRESULT Bho::NavigateToCurrentUrlInCF(IBrowserService* browser) { - DCHECK(browser); - MarkBrowserOnThreadForCFNavigation(browser); - ScopedComPtr<IBindCtx> bind_context; - ScopedComPtr<IMoniker> moniker; - HRESULT hr = ::CreateBindCtx(0, bind_context.Receive()); - DCHECK(bind_context); - if (SUCCEEDED(hr) && - SUCCEEDED(hr = ::CreateURLMonikerEx(NULL, url_.c_str(), moniker.Receive(), - URL_MK_UNIFORM))) { - DCHECK(SUCCEEDED(hr)); - if (SUCCEEDED(hr)) { -#ifndef NDEBUG - // We've just created a new moniker for a URL that has just been fetched. - // However, the moniker used for the current navigation should still - // be running. - // The documentation for IMoniker::IsRunning() states that if the - // moniker is already running (i.e. an equal moniker already exists), - // then the return value from IsRunning will be S_OK (or 0) and - // S_FALSE (1) when the moniker is not running. - // http://msdn.microsoft.com/en-us/library/ms678475(VS.85).aspx - // However, knowing that the IsRunning implementation relies on - // the bind context and that the bind context uses ole32's - // IRunningObjectTable::IsRunning to do its bidding, the return value - // is actually TRUE (or 1) when the moniker is running and FALSE (0) - // when it is not running. Yup, the opposite of what you'd expect :-) - // http://msdn.microsoft.com/en-us/library/ms682169(VS.85).aspx - HRESULT running = moniker->IsRunning(bind_context, NULL, NULL); - DCHECK(running == TRUE) << "Moniker not already running?"; -#endif - - // If there's a referrer, preserve it. - std::wstring headers; - if (!referrer_.empty()) { - headers = StringPrintf(L"Referer: %ls\r\n\r\n", - ASCIIToWide(referrer_).c_str()); - } - - // Pass in URL fragments if applicable. - std::wstring fragment; - GURL parsed_moniker_url(url_); - if (parsed_moniker_url.has_ref()) { - fragment = UTF8ToWide(parsed_moniker_url.ref()); - } - hr = NavigateBrowserToMoniker(browser, moniker, headers.c_str(), - bind_context, fragment.c_str()); - } + VARIANT_BOOL is_top_level = VARIANT_FALSE; + web_browser2->get_TopLevelContainer(&is_top_level); + if (is_top_level) { + set_url(url->bstrVal); + ProcessOptInUrls(web_browser2, url->bstrVal); } - return hr; + return S_OK; +} + +STDMETHODIMP_(void) Bho::NavigateComplete2(IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; } namespace { @@ -239,11 +197,11 @@ HRESULT Bho::OnHttpEquiv(IBrowserService_OnHttpEquiv_Fn original_httpequiv, // The embedded items should only be created once the top level // doc has been created. if (!DocumentHasEmbeddedItems(browser)) { - Bho* bho = Bho::GetCurrentThreadBhoInstance(); - DCHECK(bho); - DLOG(INFO) << "Found tag in page. Marking browser." << bho->url() << + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + DCHECK(mgr); + DLOG(INFO) << "Found tag in page. Marking browser." << StringPrintf(" tid=0x%08X", ::GetCurrentThreadId()); - if (bho) { + if (mgr) { // TODO(tommi): See if we can't figure out a cleaner way to avoid // this. For some documents we can hit a problem here. When we // attempt to navigate the document again in CF, mshtml can "complete" @@ -253,62 +211,19 @@ HRESULT Bho::OnHttpEquiv(IBrowserService_OnHttpEquiv_Fn original_httpequiv, // To work around this, we clear the contents of the document before // opening it up in CF. ClearDocumentContents(browser); - bho->NavigateToCurrentUrlInCF(browser); + mgr->NavigateToCurrentUrlInCF(browser); } } } + } else if (done) { + DLOG(INFO) << "Releasing cached data."; + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + mgr->ReleaseRequestData(); } return original_httpequiv(browser, shell_view, done, in_arg, out_arg); } -void Bho::OnBeginningTransaction(IWebBrowser2* browser, const wchar_t* url, - const wchar_t* headers, - const wchar_t* additional_headers) { - if (!browser) - return; - - if (url_.compare(url) != 0) { - DLOG(INFO) << __FUNCTION__ << " not processing headers for url: " << url - << " current url is: " << url_; - return; - } - - VARIANT_BOOL is_top_level = VARIANT_FALSE; - browser->get_TopLevelContainer(&is_top_level); - if (is_top_level) { - ScopedComPtr<IBrowserService> browser_service; - DoQueryService(SID_SShellBrowser, browser, browser_service.Receive()); - if (!browser_service || !CheckForCFNavigation(browser_service, false)) { - // Save away the referrer in case our active document needs it to initiate - // navigation in chrome. - referrer_.clear(); - const wchar_t* both_headers[] = { headers, additional_headers }; - for (int i = 0; referrer_.empty() && i < arraysize(both_headers); ++i) { - if (!both_headers[i]) - continue; - std::string raw_headers_utf8 = WideToUTF8(both_headers[i]); - std::string http_headers = - net::HttpUtil::AssembleRawHeaders(raw_headers_utf8.c_str(), - raw_headers_utf8.length()); - net::HttpUtil::HeadersIterator it(http_headers.begin(), - http_headers.end(), "\r\n"); - while (it.GetNext()) { - if (LowerCaseEqualsASCII(it.name(), "referer")) { - referrer_ = it.values(); - break; - } - } - } - } - } -} - -Bho* Bho::GetCurrentThreadBhoInstance() { - DCHECK(bho_current_thread_instance_.Pointer()->Get() != NULL); - return bho_current_thread_instance_.Pointer()->Get(); -} - // static void Bho::ProcessOptInUrls(IWebBrowser2* browser, BSTR url) { if (!browser || !url) { @@ -316,19 +231,22 @@ void Bho::ProcessOptInUrls(IWebBrowser2* browser, BSTR url) { return; } +#ifndef NDEBUG + // This check must already have been made. VARIANT_BOOL is_top_level = VARIANT_FALSE; browser->get_TopLevelContainer(&is_top_level); - if (is_top_level) { - std::wstring current_url(url, SysStringLen(url)); - if (IsValidUrlScheme(current_url, false)) { - bool cf_protocol = StartsWith(current_url, kChromeProtocolPrefix, false); - if (!cf_protocol && IsOptInUrl(current_url.c_str())) { - DLOG(INFO) << "Opt-in URL. Switching to cf."; - ScopedComPtr<IBrowserService> browser_service; - DoQueryService(SID_SShellBrowser, browser, browser_service.Receive()); - DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed."; - MarkBrowserOnThreadForCFNavigation(browser_service); - } + DCHECK(is_top_level); +#endif + + std::wstring current_url(url, SysStringLen(url)); + if (IsValidUrlScheme(current_url, false)) { + bool cf_protocol = StartsWith(current_url, kChromeProtocolPrefix, false); + if (!cf_protocol && IsOptInUrl(current_url.c_str())) { + DLOG(INFO) << "Opt-in URL. Switching to cf."; + ScopedComPtr<IBrowserService> browser_service; + DoQueryService(SID_SShellBrowser, browser, browser_service.Receive()); + DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed."; + MarkBrowserOnThreadForCFNavigation(browser_service); } } } @@ -362,12 +280,20 @@ bool PatchHelper::InitializeAndPatchProtocolsIfNeeded() { HttpNegotiatePatch::Initialize(); - bool patch_protocol = GetConfigBool(false, kPatchProtocols); - if (patch_protocol) { + ProtocolPatchMethod patch_method = + static_cast<ProtocolPatchMethod>( + GetConfigInt(PATCH_METHOD_IBROWSER, kPatchProtocols)); + + if (patch_method == PATCH_METHOD_INET_PROTOCOL) { ProtocolSinkWrap::PatchProtocolHandlers(); state_ = PATCH_PROTOCOL; } else { + DCHECK(patch_method == PATCH_METHOD_IBROWSER || + patch_method == PATCH_METHOD_IBROWSER_AND_MONIKER); state_ = PATCH_IBROWSER; + if (patch_method == PATCH_METHOD_IBROWSER_AND_MONIKER) { + MonikerPatch::Initialize(); + } } ret = true; } @@ -390,6 +316,7 @@ void PatchHelper::UnpatchIfNeeded() { ProtocolSinkWrap::UnpatchProtocolHandlers(); } else if (state_ == PATCH_IBROWSER) { vtable_patch::UnpatchInterfaceMethods(IBrowserService_PatchInfo); + MonikerPatch::Uninitialize(); } HttpNegotiatePatch::Uninitialize(); diff --git a/chrome_frame/bho.h b/chrome_frame/bho.h index c82f000..9a36795 100644 --- a/chrome_frame/bho.h +++ b/chrome_frame/bho.h @@ -14,10 +14,11 @@ #include <string> -#include "base/lazy_instance.h" -#include "base/thread_local.h" +#include "base/scoped_comptr_win.h" #include "chrome_tab.h" // NOLINT #include "chrome_frame/resource.h" +#include "chrome_frame/urlmon_moniker.h" +#include "chrome_frame/urlmon_url_request.h" #include "grit/chrome_frame_resources.h" class PatchHelper { @@ -47,7 +48,8 @@ class ATL_NO_VTABLE Bho : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<Bho, &CLSID_ChromeFrameBHO>, public IObjectWithSiteImpl<Bho>, - public IDispEventSimpleImpl<0, Bho, &DIID_DWebBrowserEvents2> { + public IDispEventSimpleImpl<0, Bho, &DIID_DWebBrowserEvents2>, + public NavigationManager { public: typedef HRESULT (STDMETHODCALLTYPE* IBrowserService_OnHttpEquiv_Fn)( IBrowserService* browser, IShellView* shell_view, BOOL done, @@ -64,9 +66,10 @@ END_COM_MAP() BEGIN_SINK_MAP(Bho) SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, BeforeNavigate2, &kBeforeNavigate2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, + NavigateComplete2, &kNavigateComplete2Info) END_SINK_MAP() - // Lifetime management methods Bho(); HRESULT FinalConstruct(); @@ -77,51 +80,27 @@ END_SINK_MAP() STDMETHOD(BeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers, VARIANT_BOOL* cancel); - - HRESULT NavigateToCurrentUrlInCF(IBrowserService* browser); + STDMETHOD_(void, NavigateComplete2)(IDispatch* dispatch, VARIANT* url); // mshtml sends an IOleCommandTarget::Exec of OLECMDID_HTTPEQUIV // (and OLECMDID_HTTPEQUIV_DONE) as soon as it parses a meta tag. // It also sends contents of the meta tag as an argument. IEFrame // handles this in IBrowserService::OnHttpEquiv. So this allows // us to sniff the META tag by simply patching it. The renderer - // switching can be achieved by cancelling original navigation + // switching can be achieved by canceling original navigation // and issuing a new one using IWebBrowser2->Navigate2. static HRESULT STDMETHODCALLTYPE OnHttpEquiv( IBrowserService_OnHttpEquiv_Fn original_httpequiv, IBrowserService* browser, IShellView* shell_view, BOOL done, VARIANT* in_arg, VARIANT* out_arg); - const std::string& referrer() const { - return referrer_; - } - - const std::wstring& url() const { - return url_; - } - - // Called from HttpNegotiatePatch::BeginningTransaction when a request is - // being issued. We check the url and headers and see if there is a referrer - // header that we need to cache. - void OnBeginningTransaction(IWebBrowser2* browser, const wchar_t* url, - const wchar_t* headers, - const wchar_t* additional_headers); - - // Returns the Bho instance for the current thread. This is returned from - // TLS. - static Bho* GetCurrentThreadBhoInstance(); - static void ProcessOptInUrls(IWebBrowser2* browser, BSTR url); protected: bool PatchProtocolHandler(const CLSID& handler_clsid); - std::string referrer_; - std::wstring url_; - - static base::LazyInstance<base::ThreadLocalPointer<Bho> > - bho_current_thread_instance_; static _ATL_FUNC_INFO kBeforeNavigate2Info; + static _ATL_FUNC_INFO kNavigateComplete2Info; }; #endif // CHROME_FRAME_BHO_H_ diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc index e4a26bd..08efdd2 100644 --- a/chrome_frame/chrome_active_document.cc +++ b/chrome_frame/chrome_active_document.cc @@ -209,16 +209,30 @@ STDMETHODIMP ChromeActiveDocument::Load(BOOL fully_avalable, SetClientSite(client_site); DoQueryService(IID_INewWindowManager, client_site, popup_manager_.Receive()); + + // See if mshtml parsed the html header for us. If so, we need to + // clear the browser service flag that we use to indicate that this + // browser instance is navigating to a CF document. + ScopedComPtr<IBrowserService> browser_service; + DoQueryService(SID_SShellBrowser, client_site, browser_service.Receive()); + if (browser_service) { + bool flagged = CheckForCFNavigation(browser_service, true); + DLOG_IF(INFO, flagged) << "Cleared flagged browser service"; + } } - Bho* chrome_frame_bho = Bho::GetCurrentThreadBhoInstance(); + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + DCHECK(mgr); // If the original URL contains an anchor, then the URL queried // from the moniker does not contain the anchor. To workaround // this we retrieve the URL from our BHO. - std::wstring url = GetActualUrlFromMoniker( - moniker_name, bind_context, - chrome_frame_bho ? chrome_frame_bho->url() : std::wstring()); + std::wstring url(GetActualUrlFromMoniker(moniker_name, bind_context, + mgr ? mgr->url() : std::wstring())); + + scoped_refptr<RequestData> data(mgr->GetActiveRequestData(url.c_str())); + DLOG_IF(INFO, data) << "Got active request data"; + DLOG_IF(WARNING, data.get() == NULL) << "NO active request data"; // The is_new_navigation variable indicates if this a navigation initiated // by typing in a URL for e.g. in the IE address bar, or from Chrome by @@ -237,8 +251,8 @@ STDMETHODIMP ChromeActiveDocument::Load(BOOL fully_avalable, return E_INVALIDARG; } - if (!is_chrome_protocol) { - url_fetcher_.UseMonikerForUrl(moniker_name, bind_context, url); + if (!is_chrome_protocol && data) { + url_fetcher_.UseRequestDataForUrl(data, url); } THREAD_SAFE_UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.FullTabLaunchType", @@ -595,6 +609,12 @@ void ChromeActiveDocument::UpdateNavigationState( if (new_navigation_info.url.is_valid()) { url_.Allocate(UTF8ToWide(new_navigation_info.url.spec()).c_str()); + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + DCHECK(mgr); + if (mgr) { + mgr->set_url(url_); + mgr->ReleaseRequestData(); + } } if (is_internal_navigation) { @@ -910,9 +930,9 @@ bool ChromeActiveDocument::LaunchUrl(const std::wstring& url, WideToUTF8(url_, url_.Length(), &utf8_url); std::string referrer; - Bho* chrome_frame_bho = Bho::GetCurrentThreadBhoInstance(); - if (chrome_frame_bho) - referrer = chrome_frame_bho->referrer(); + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) + referrer = mgr->referrer(); if (!automation_client_->InitiateNavigation(utf8_url, referrer, diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index 2fbc268..e16be31 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -126,19 +126,21 @@ 'sources': [ 'chrome_tab.h', 'chrome_tab.idl', + 'chrome_frame_histograms.h', + 'chrome_frame_histograms.cc', 'chrome_frame_npapi_unittest.cc', 'chrome_frame_unittest_main.cc', 'chrome_launcher_unittest.cc', 'test/com_message_event_unittest.cc', 'test/html_util_unittests.cc', 'test/http_negotiate_unittest.cc', + 'test/urlmon_moniker_tests.h', + 'test/urlmon_moniker_unittest.cc', 'test/util_unittests.cc', 'unittest_precompile.h', 'unittest_precompile.cc', 'urlmon_upload_data_stream.cc', 'urlmon_upload_data_stream_unittest.cc', - 'chrome_frame_histograms.h', - 'chrome_frame_histograms.cc', ], 'include_dirs': [ # To allow including "chrome_tab.h" @@ -214,6 +216,8 @@ 'test/test_server_test.cc', 'test/test_with_web_server.cc', 'test/test_with_web_server.h', + 'test/urlmon_moniker_tests.h', + 'test/urlmon_moniker_integration_test.cc', 'test/url_request_test.cc', 'test/window_watchdog.cc', 'test/window_watchdog.h', @@ -600,6 +604,7 @@ 'function_stub.h', 'http_negotiate.h', 'http_negotiate.cc', + 'iids.cc', 'in_place_menu.h', 'ole_document_impl.h', 'protocol_sink_wrap.cc', @@ -607,6 +612,10 @@ 'sync_msg_reply_dispatcher.cc', 'sync_msg_reply_dispatcher.h', 'extra_system_apis.h', + 'urlmon_bind_status_callback.h', + 'urlmon_bind_status_callback.cc', + 'urlmon_moniker.h', + 'urlmon_moniker.cc', 'urlmon_url_request.cc', 'urlmon_url_request.h', 'urlmon_url_request_private.h', @@ -672,7 +681,6 @@ 'resources/tlb_resource.rc', 'chrome_tab.rgs', 'chrome_tab_version.rc.version', - 'iids.cc', 'resource.h', ], 'include_dirs': [ diff --git a/chrome_frame/chrome_frame_activex_base.h b/chrome_frame/chrome_frame_activex_base.h index 438e652..263b2b9 100644 --- a/chrome_frame/chrome_frame_activex_base.h +++ b/chrome_frame/chrome_frame_activex_base.h @@ -386,7 +386,7 @@ END_MSG_MAP() } virtual void OnDownloadRequestInHost(int tab_handle, int request_id) { - DLOG(INFO) << "TODO: Let the host browser handle this download"; + DLOG(INFO) << "Let the host browser handle this download"; ScopedComPtr<IBindCtx> bind_context; ScopedComPtr<IMoniker> moniker; url_fetcher_.StealMonikerFromRequest(request_id, moniker.Receive()); diff --git a/chrome_frame/chrome_protocol.cc b/chrome_frame/chrome_protocol.cc index 87ca34e..56b0be7 100644 --- a/chrome_frame/chrome_protocol.cc +++ b/chrome_frame/chrome_protocol.cc @@ -6,8 +6,7 @@ #include "chrome_frame/chrome_protocol.h" #include "base/logging.h" - -static const wchar_t* kChromeMimeType = L"application/chromepage"; +#include "chrome_frame/utils.h" // ChromeProtocol diff --git a/chrome_frame/http_negotiate.cc b/chrome_frame/http_negotiate.cc index f9c9d48..a627ad8 100644 --- a/chrome_frame/http_negotiate.cc +++ b/chrome_frame/http_negotiate.cc @@ -15,13 +15,13 @@ #include "chrome_frame/bho.h" #include "chrome_frame/html_utils.h" #include "chrome_frame/urlmon_url_request.h" +#include "chrome_frame/urlmon_moniker.h" #include "chrome_frame/utils.h" #include "chrome_frame/vtable_patch_manager.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" -const wchar_t kChromeMimeType[] = L"application/chromepage"; const char kUACompatibleHttpHeader[] = "x-ua-compatible"; // From the latest urlmon.h. Symbol name prepended with LOCAL_ to @@ -207,10 +207,18 @@ HRESULT HttpNegotiatePatch::BeginningTransaction( ScopedComPtr<IWebBrowser2> browser2; DoQueryService(IID_ITargetFrame2, me, browser2.Receive()); if (browser2) { - Bho* bho = Bho::GetCurrentThreadBhoInstance(); - if (bho) { - bho->OnBeginningTransaction(browser2, url, headers, *additional_headers); + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) { + VARIANT_BOOL is_top_level = VARIANT_FALSE; + browser2->get_TopLevelContainer(&is_top_level); + mgr->OnBeginningTransaction(is_top_level != VARIANT_FALSE, url, headers, + *additional_headers); + DLOG(INFO) << "called OnBeginningTransaction " << is_top_level; + } else { + DLOG(INFO) << "No NavigationManager"; } + } else { + DLOG(INFO) << "No IWebBrowser2"; } static const char kLowerCaseUserAgent[] = "user-agent"; @@ -314,12 +322,13 @@ HRESULT HttpNegotiatePatch::StartBinding( HRESULT HttpNegotiatePatch::ReportProgress( IInternetProtocolSink_ReportProgress_Fn original, IInternetProtocolSink* me, ULONG status_code, LPCWSTR status_text) { + DLOG(INFO) << __FUNCTION__ + << StringPrintf(" %i %ls", status_code, status_text); if (status_code == BINDSTATUS_MIMETYPEAVAILABLE || status_code == BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE || status_code == LOCAL_BINDSTATUS_SERVER_MIMETYPEAVAILABLE) { bool render_in_chrome_frame = false; bool is_top_level_request = !IsSubFrameRequest(me); - // NOTE: After switching over to using the onhttpequiv notification from // mshtml we can expect to see sub frames being created even before the // owning document has completed loading. In particular frames whose @@ -332,6 +341,8 @@ HRESULT HttpNegotiatePatch::ReportProgress( render_in_chrome_frame = CheckForCFNavigation(browser, true); } + DLOG_IF(INFO, !render_in_chrome_frame) << " - browser not tagged"; + if (!render_in_chrome_frame) { // Check to see if we need to alter the mime type that gets reported // by inspecting the raw header information: @@ -342,6 +353,7 @@ HRESULT HttpNegotiatePatch::ReportProgress( if (!win_inet_http_info || FAILED(hr)) { hr = DoQueryService(IID_IWinInetHttpInfo, me, win_inet_http_info.Receive()); + DLOG_IF(WARNING, FAILED(hr)) << "Failed to get IWinInetHttpInfo"; } // Note that it has been observed that getting an IWinInetHttpInfo will @@ -350,6 +362,8 @@ HRESULT HttpNegotiatePatch::ReportProgress( if (win_inet_http_info) { // We have headers: check to see if the server is requesting CF via // the X-UA-Compatible: chrome=1 HTTP header. + // TODO(tommi): use HTTP_QUERY_CUSTOM instead of fetching and parsing + // all the headers. std::string headers(GetRawHttpHeaders(win_inet_http_info)); net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); @@ -368,6 +382,7 @@ HRESULT HttpNegotiatePatch::ReportProgress( } if (render_in_chrome_frame) { + DLOG(INFO) << "- changing mime type to " << kChromeMimeType; status_text = kChromeMimeType; } } diff --git a/chrome_frame/protocol_sink_wrap.cc b/chrome_frame/protocol_sink_wrap.cc index 0b6449f..683ec0f 100644 --- a/chrome_frame/protocol_sink_wrap.cc +++ b/chrome_frame/protocol_sink_wrap.cc @@ -22,7 +22,6 @@ #define BINDSTATUS_SERVER_MIMETYPEAVAILABLE 54 #endif -static const wchar_t* kChromeMimeType = L"application/chromepage"; static const char kTextHtmlMimeType[] = "text/html"; const wchar_t kUrlMonDllName[] = L"urlmon.dll"; diff --git a/chrome_frame/test/data/full_tab_get_target_cf.html b/chrome_frame/test/data/full_tab_get_target_cf.html index a5e4b25..0ee9ac8 100644 --- a/chrome_frame/test/data/full_tab_get_target_cf.html +++ b/chrome_frame/test/data/full_tab_get_target_cf.html @@ -18,6 +18,7 @@ </script> <body onload="onLoad();"> - This page should be rendered in chrome. + This page should be rendered in chrome.<br /> + <a href="/quit?OK">Quit OK</a> </body> </html> diff --git a/chrome_frame/test/data/full_tab_post_target_cf.html b/chrome_frame/test/data/full_tab_post_target_cf.html index 30601b4..41acc64 100644 --- a/chrome_frame/test/data/full_tab_post_target_cf.html +++ b/chrome_frame/test/data/full_tab_post_target_cf.html @@ -18,6 +18,7 @@ </script> <body onload="onLoad();"> - This page should be rendered in chrome. + This page should be rendered in chrome.<br /> + <a href="/quit?OK">Quit OK</a> </body> </html> diff --git a/chrome_frame/test/test_with_web_server.cc b/chrome_frame/test/test_with_web_server.cc index 52d32dd..ec5d541 100644 --- a/chrome_frame/test/test_with_web_server.cc +++ b/chrome_frame/test/test_with_web_server.cc @@ -693,9 +693,17 @@ TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_NavigateOut) { const wchar_t kReferrerMainTest[] = L"files/referrer_main.html"; -// Marking as FLAKY as this intermittently fails on the builder. -// http://code.google.com/p/chromium/issues/detail?id=34812 -TEST_F(ChromeFrameTestWithWebServer, FLAKY_FullTabModeIE_ReferrerTest) { +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_ReferrerTest) { + // At the moment the moniker patch is only enabled if the below + // registry config value is set to PATCH_METHOD_IBROWSER_AND_MONIKER. + ProtocolPatchMethod patch_method = + static_cast<ProtocolPatchMethod>( + GetConfigInt(PATCH_METHOD_IBROWSER, kPatchProtocols)); + if (patch_method != PATCH_METHOD_IBROWSER_AND_MONIKER) { + DLOG(ERROR) << "Not running test. Moniker patch not enabled."; + return; + } + SimpleBrowserTest(IE, kReferrerMainTest, L"FullTab_ReferrerTest"); } @@ -779,12 +787,18 @@ TEST_F(ChromeFrameTestWithWebServer, ASSERT_TRUE(CheckResultFile(L"FullTab_AnchorURLNavigateTest", "OK")); } -// DISABLED as it currently fails for both approaches for switching -// renderers (httpequiv and IInternetProtocol). -// TODO(tommi): Enable this test once the issue has been fixed. -TEST_F(ChromeFrameTestWithWebServer, DISABLED_FullTabModeIE_TestPostReissue) { - // Test whether POST-ing a form from an mshtml page to a CF page will cause - // the request to get reissued. It should not. +// Test whether POST-ing a form from an mshtml page to a CF page will cause +// the request to get reissued. It should not. +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_TestPostReissue) { + // At the moment the moniker patch is only enabled if the below + // registry config value is set to PATCH_METHOD_IBROWSER_AND_MONIKER. + ProtocolPatchMethod patch_method = + static_cast<ProtocolPatchMethod>( + GetConfigInt(PATCH_METHOD_IBROWSER, kPatchProtocols)); + if (patch_method != PATCH_METHOD_IBROWSER_AND_MONIKER) { + DLOG(ERROR) << "Not running test. Moniker patch not enabled."; + return; + } MessageLoopForUI loop; // must come before the server. @@ -814,12 +828,18 @@ TEST_F(ChromeFrameTestWithWebServer, DISABLED_FullTabModeIE_TestPostReissue) { } } -// DISABLED as it currently fails for both approaches for switching -// renderers (httpequiv and IInternetProtocol). -// TODO(tommi): Enable this test once the issue has been fixed. -TEST_F(ChromeFrameTestWithWebServer, DISABLED_FullTabModeIE_TestMultipleGet) { - // Test whether following a link from an mshtml page to a CF page will cause - // multiple network requests. It should not. +// Test whether following a link from an mshtml page to a CF page will cause +// multiple network requests. It should not. +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_TestMultipleGet) { + // At the moment the moniker patch is only enabled if the below + // registry config value is set to PATCH_METHOD_IBROWSER_AND_MONIKER. + ProtocolPatchMethod patch_method = + static_cast<ProtocolPatchMethod>( + GetConfigInt(PATCH_METHOD_IBROWSER, kPatchProtocols)); + if (patch_method != PATCH_METHOD_IBROWSER_AND_MONIKER) { + DLOG(ERROR) << "Not running test. Moniker patch not enabled."; + return; + } MessageLoopForUI loop; // must come before the server. diff --git a/chrome_frame/test/urlmon_moniker_integration_test.cc b/chrome_frame/test/urlmon_moniker_integration_test.cc new file mode 100644 index 0000000..dab6d80 --- /dev/null +++ b/chrome_frame/test/urlmon_moniker_integration_test.cc @@ -0,0 +1,327 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <atlbase.h> +#include <atlcom.h> + +#include "base/scoped_comptr_win.h" +#include "base/thread.h" +#include "chrome_frame/bho.h" +#include "chrome_frame/urlmon_moniker.h" +#include "chrome_frame/test/test_server.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/urlmon_moniker_tests.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING +#include "testing/gmock_mutant.h" + +using testing::_; +using testing::CreateFunctor; +using testing::Eq; +using testing::Invoke; +using testing::SetArgumentPointee; +using testing::StrEq; +using testing::Return; +using testing::WithArg; +using testing::WithArgs; + +static int kUrlmonMonikerTimeoutSec = 5; + +namespace { +const char kTestContent[] = "<html><head>" + "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" + "</head><body>Test HTML content</body></html>"; +} // end namespace + +class UrlmonMonikerTest : public testing::Test { + protected: + UrlmonMonikerTest() { + } +}; + +TEST_F(UrlmonMonikerTest, MonikerPatch) { + EXPECT_EQ(true, MonikerPatch::Initialize()); + EXPECT_EQ(true, MonikerPatch::Initialize()); // Should be ok to call twice. + MonikerPatch::Uninitialize(); +} + +// Runs an HTTP server on a worker thread that has a message loop. +class RunTestServer : public base::Thread { + public: + RunTestServer() + : base::Thread("TestServer"), + default_response_("/", kTestContent), + ready_(::CreateEvent(NULL, TRUE, FALSE, NULL)) { + } + + bool Start() { + bool ret = StartWithOptions(Options(MessageLoop::TYPE_UI, 0)); + if (ret) { + message_loop()->PostTask(FROM_HERE, + NewRunnableFunction(&RunTestServer::StartServer, this)); + wait_until_ready(); + } + return ret; + } + + static void StartServer(RunTestServer* me) { + me->server_.reset(new test_server::SimpleWebServer(43210)); + me->server_->AddResponse(&me->default_response_); + ::SetEvent(me->ready_); + } + + bool wait_until_ready() { + return ::WaitForSingleObject(ready_, kUrlmonMonikerTimeoutSec * 1000) + == WAIT_OBJECT_0; + } + + protected: + scoped_ptr<test_server::SimpleWebServer> server_; + test_server::SimpleResponse default_response_; + ScopedHandle ready_; +}; + +// Helper class for running tests that rely on the NavigationManager. +class UrlmonMonikerTestManager { + public: + explicit UrlmonMonikerTestManager(const wchar_t* test_url) { + mock_mgr_.RegisterThreadInstance(); + mock_mgr_.set_url(test_url); + EXPECT_EQ(true, MonikerPatch::Initialize()); + } + + ~UrlmonMonikerTestManager() { + MonikerPatch::Uninitialize(); + mock_mgr_.UnregisterThreadInstance(); + } + + chrome_frame_test::TimedMsgLoop& loop() { + return loop_; + } + + TestNavigationManager& nav_manager() { + return mock_mgr_; + } + + protected: + TestNavigationManager mock_mgr_; + chrome_frame_test::TimedMsgLoop loop_; +}; + +// Wraps the MockBindStatusCallbackImpl mock object and allows the user +// to specify expectations on the callback object. +class UrlmonMonikerTestCallback { + public: + explicit UrlmonMonikerTestCallback(UrlmonMonikerTestManager* mgr) + : mgr_(mgr) { + } + + ~UrlmonMonikerTestCallback() { + } + + typedef enum GetBindInfoExpectations { + EXPECT_NO_CALL, + REQUEST_SYNCHRONOUS, + REQUEST_ASYNCHRONOUS, + } GET_BIND_INFO_EXPECTATION; + + // Sets gmock expectations for the IBindStatusCallback mock object. + void SetCallbackExpectations(GetBindInfoExpectations bind_info_handling, + HRESULT data_available_response, + bool quit_loop_on_stop) { + EXPECT_CALL(callback_, OnProgress(_, _, _, _)) + .WillRepeatedly(Return(S_OK)); + + if (bind_info_handling == REQUEST_ASYNCHRONOUS) { + EXPECT_CALL(callback_, GetBindInfo(_, _)) + .WillOnce(DoAll( + WithArgs<0, 1>( + Invoke(&MockBindStatusCallbackImpl::SetAsyncBindInfo)), + Return(S_OK))); + } else if (bind_info_handling == REQUEST_SYNCHRONOUS) { + EXPECT_CALL(callback_, GetBindInfo(_, _)) + .WillOnce(DoAll( + WithArgs<0, 1>( + Invoke(&MockBindStatusCallbackImpl::SetSyncBindInfo)), + Return(S_OK))); + } else { + DCHECK(bind_info_handling == EXPECT_NO_CALL); + } + + EXPECT_CALL(callback_, OnStartBinding(_, _)) + .WillOnce(Return(S_OK)); + + EXPECT_CALL(callback_, OnDataAvailable(_, _, _, _)) + .WillRepeatedly(Return(data_available_response)); + + if (quit_loop_on_stop) { + // When expecting asynchronous + EXPECT_CALL(callback_, OnStopBinding(data_available_response, _)) + .WillOnce(DoAll(QUIT_LOOP(mgr_->loop()), Return(S_OK))); + } else { + EXPECT_CALL(callback_, OnStopBinding(data_available_response, _)) + .WillOnce(Return(S_OK)); + } + } + + HRESULT CreateUrlMonikerAndBindToStorage(const wchar_t* url, + IBindCtx** bind_ctx) { + ScopedComPtr<IMoniker> moniker; + HRESULT hr = CreateURLMoniker(NULL, url, moniker.Receive()); + EXPECT_TRUE(moniker != NULL); + if (moniker) { + ScopedComPtr<IBindCtx> context; + ::CreateAsyncBindCtx(0, callback(), NULL, context.Receive()); + DCHECK(context); + ScopedComPtr<IStream> stream; + hr = moniker->BindToStorage(context, NULL, IID_IStream, + reinterpret_cast<void**>(stream.Receive())); + if (SUCCEEDED(hr) && bind_ctx) + *bind_ctx = context.Detach(); + } + return hr; + } + + IBindStatusCallback* callback() { + return &callback_; + } + + protected: + CComObjectStackEx<MockBindStatusCallbackImpl> callback_; + UrlmonMonikerTestManager* mgr_; +}; + +// Tests synchronously binding to a moniker and downloading the target. +TEST_F(UrlmonMonikerTest, BindToStorageSynchronous) { + const wchar_t test_url[] = L"http://localhost:43210/"; + UrlmonMonikerTestManager test(test_url); + UrlmonMonikerTestCallback callback(&test); + + RunTestServer server_thread; + EXPECT_TRUE(server_thread.Start()); + + callback.SetCallbackExpectations( + UrlmonMonikerTestCallback::REQUEST_SYNCHRONOUS, S_OK, false); + + ScopedComPtr<IBindCtx> bind_ctx; + HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, + bind_ctx.Receive()); + // The download should have happened synchronously, so we don't expect + // MK_S_ASYNCHRONOUS or any errors. + EXPECT_EQ(S_OK, hr); + + IBindCtx* release = bind_ctx.Detach(); + EXPECT_EQ(0, release->Release()); + + server_thread.Stop(); + + EXPECT_FALSE(test.nav_manager().HasRequestData()); +} + +// Tests asynchronously binding to a moniker and downloading the target. +TEST_F(UrlmonMonikerTest, BindToStorageAsynchronous) { + const wchar_t test_url[] = L"http://localhost:43210/"; + UrlmonMonikerTestManager test(test_url); + UrlmonMonikerTestCallback callback(&test); + + test_server::SimpleWebServer server(43210); + test_server::SimpleResponse default_response("/", kTestContent); + server.AddResponse(&default_response); + + callback.SetCallbackExpectations( + UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, S_OK, true); + + ScopedComPtr<IBindCtx> bind_ctx; + HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, + bind_ctx.Receive()); + EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); + test.loop().RunFor(kUrlmonMonikerTimeoutSec); + + IBindCtx* release = bind_ctx.Detach(); + EXPECT_EQ(0, release->Release()); + + EXPECT_FALSE(test.nav_manager().HasRequestData()); +} + +// Downloads a document asynchronously and then verifies that the downloaded +// contents were cached and the cache contents are correct. +TEST_F(UrlmonMonikerTest, BindToStorageSwitchContent) { + const wchar_t test_url[] = L"http://localhost:43210/"; + UrlmonMonikerTestManager test(test_url); + UrlmonMonikerTestCallback callback(&test); + + test_server::SimpleWebServer server(43210); + test_server::SimpleResponse default_response("/", kTestContent); + server.AddResponse(&default_response); + + callback.SetCallbackExpectations( + UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, INET_E_TERMINATED_BIND, + true); + + HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, NULL); + EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); + test.loop().RunFor(kUrlmonMonikerTimeoutSec); + + scoped_refptr<RequestData> request_data( + test.nav_manager().GetActiveRequestData(test_url)); + EXPECT_TRUE(request_data != NULL); + + if (request_data) { + EXPECT_EQ(request_data->GetCachedContentSize(), + arraysize(kTestContent) - 1); + ScopedComPtr<IStream> stream; + request_data->GetResetCachedContentStream(stream.Receive()); + EXPECT_TRUE(stream != NULL); + if (stream) { + char buffer[0xffff]; + DWORD read = 0; + stream->Read(buffer, sizeof(buffer), &read); + EXPECT_EQ(read, arraysize(kTestContent) - 1); + EXPECT_EQ(0, memcmp(buffer, kTestContent, read)); + } + } +} + +// Fetches content asynchronously first to cache it and then +// verifies that fetching the cached content the same way works as expected +// and happens synchronously. +TEST_F(UrlmonMonikerTest, BindToStorageCachedContent) { + const wchar_t test_url[] = L"http://localhost:43210/"; + UrlmonMonikerTestManager test(test_url); + UrlmonMonikerTestCallback callback(&test); + + test_server::SimpleWebServer server(43210); + test_server::SimpleResponse default_response("/", kTestContent); + server.AddResponse(&default_response); + + // First set of expectations. Download the contents + // asynchronously. This should populate the cache so that + // the second request should be served synchronously without + // going to the server. + callback.SetCallbackExpectations( + UrlmonMonikerTestCallback::REQUEST_ASYNCHRONOUS, INET_E_TERMINATED_BIND, + true); + + HRESULT hr = callback.CreateUrlMonikerAndBindToStorage(test_url, NULL); + EXPECT_EQ(MK_S_ASYNCHRONOUS, hr); + test.loop().RunFor(kUrlmonMonikerTimeoutSec); + + scoped_refptr<RequestData> request_data( + test.nav_manager().GetActiveRequestData(test_url)); + EXPECT_TRUE(request_data != NULL); + + if (request_data) { + // This time, just accept the content as normal. + UrlmonMonikerTestCallback callback2(&test); + callback2.SetCallbackExpectations( + UrlmonMonikerTestCallback::EXPECT_NO_CALL, S_OK, false); + hr = callback2.CreateUrlMonikerAndBindToStorage(test_url, NULL); + // S_OK means that the operation completed synchronously. + // Otherwise we'd get MK_S_ASYNCHRONOUS. + EXPECT_EQ(S_OK, hr); + } +} + diff --git a/chrome_frame/test/urlmon_moniker_tests.h b/chrome_frame/test/urlmon_moniker_tests.h new file mode 100644 index 0000000..4c5b832 --- /dev/null +++ b/chrome_frame/test/urlmon_moniker_tests.h @@ -0,0 +1,142 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_TEST_URLMON_MONIKER_TESTS_H_ +#define CHROME_FRAME_TEST_URLMON_MONIKER_TESTS_H_ + +#include <atlbase.h> +#include <atlcom.h> + +#include "chrome_frame/bho.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +class MockBindingImpl + : public SimpleBindingImpl, + public IServiceProvider { + public: +BEGIN_COM_MAP(MockBindingImpl) + COM_INTERFACE_ENTRY(IBinding) + COM_INTERFACE_ENTRY(IServiceProvider) +END_COM_MAP() + + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Abort, + HRESULT ()); // NOLINT + MOCK_METHOD3_WITH_CALLTYPE(__stdcall, QueryService, + HRESULT (REFGUID svc, // NOLINT + REFIID riid, + void** obj)); + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, GetBindResult, + HRESULT (CLSID* protocol, // NOLINT + DWORD* result_code, + LPOLESTR* result, + DWORD* reserved)); +}; + +class MockHttpNegotiateImpl + : public CComObjectRootEx<CComSingleThreadModel>, + public IHttpNegotiate { + public: +BEGIN_COM_MAP(MockHttpNegotiateImpl) + COM_INTERFACE_ENTRY(IHttpNegotiate) +END_COM_MAP() + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, BeginningTransaction, + HRESULT (LPCWSTR url, // NOLINT + LPCWSTR headers, + DWORD reserved, + LPWSTR* additional)); + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, OnResponse, + HRESULT (DWORD code, // NOLINT + LPCWSTR response_headers, + LPCWSTR request_headers, + LPWSTR* additional)); +}; + +class MockBindStatusCallbackImpl + : public CComObjectRootEx<CComSingleThreadModel>, + public IBindStatusCallback { + public: +BEGIN_COM_MAP(MockBindStatusCallbackImpl) + COM_INTERFACE_ENTRY(IBindStatusCallback) +END_COM_MAP() + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, OnStartBinding, + HRESULT (DWORD reserved, IBinding* binding)); // NOLINT + + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, GetPriority, + HRESULT (LONG* priority)); // NOLINT + + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, OnLowResource, + HRESULT (DWORD reserved)); // NOLINT + + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, OnProgress, + HRESULT (ULONG progress, // NOLINT + ULONG max, + ULONG status, + LPCWSTR text)); + + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, OnStopBinding, + HRESULT (HRESULT hr, // NOLINT + LPCWSTR error)); + + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetBindInfo, + HRESULT (DWORD* flags, // NOLINT + BINDINFO* bind_info)); + + MOCK_METHOD4_WITH_CALLTYPE(__stdcall, OnDataAvailable, + HRESULT (DWORD flags, // NOLINT + DWORD size, + FORMATETC* format, + STGMEDIUM* storage)); + + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, OnObjectAvailable, + HRESULT (REFIID riid, // NOLINT + IUnknown* unk)); + + static void ReadAllData(STGMEDIUM* storage) { + DCHECK(storage); + DCHECK(storage->tymed == TYMED_ISTREAM); + char buffer[0xff]; + HRESULT hr; + DWORD read = 0; + do { + hr = storage->pstm->Read(buffer, sizeof(buffer), &read); + } while (hr == S_OK && read); + } + + static void SetSyncBindInfo(DWORD* flags, BINDINFO* bind_info) { + DCHECK(flags); + DCHECK(bind_info); + *flags = BINDF_PULLDATA | BINDF_NOWRITECACHE; + DCHECK(bind_info->cbSize >= sizeof(BINDINFO)); + bind_info->dwBindVerb = BINDVERB_GET; + memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); + bind_info->grfBindInfoF = 0; + bind_info->szCustomVerb = NULL; + } + + static void SetAsyncBindInfo(DWORD* flags, BINDINFO* bind_info) { + DCHECK(flags); + DCHECK(bind_info); + *flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA | + BINDF_NOWRITECACHE; + DCHECK(bind_info->cbSize >= sizeof(BINDINFO)); + bind_info->dwBindVerb = BINDVERB_GET; + memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); + bind_info->grfBindInfoF = 0; + bind_info->szCustomVerb = NULL; + } +}; + +class TestNavigationManager : public NavigationManager { + public: + TestNavigationManager() { + } + + bool HasRequestData() const { + return request_data_.get() != NULL; + } +}; + +#endif // CHROME_FRAME_TEST_URLMON_MONIKER_TESTS_H_ + diff --git a/chrome_frame/test/urlmon_moniker_unittest.cc b/chrome_frame/test/urlmon_moniker_unittest.cc new file mode 100644 index 0000000..08795fc --- /dev/null +++ b/chrome_frame/test/urlmon_moniker_unittest.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <atlbase.h> +#include <atlcom.h> + +#include "base/scoped_comptr_win.h" +#include "chrome_frame/bho.h" +#include "chrome_frame/urlmon_moniker.h" +#include "chrome_frame/test/urlmon_moniker_tests.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Return; +using testing::WithArg; +using testing::WithArgs; +using testing::SetArgumentPointee; +using testing::StrEq; +using testing::Eq; + +class UrlmonMonikerTest : public testing::Test { + protected: + UrlmonMonikerTest() { + } +}; + +// Tests the ReadStreamCache class by writing content into a stream object +// and verify that reading that stream through the ReadStreamCache class +// reads the correct content and also verifies that ReadStreamCache caches +// all the reads. +TEST_F(UrlmonMonikerTest, ReadStreamCache) { + CComObjectStackEx<ReadStreamCache> read_stream; + EXPECT_EQ(NULL, read_stream.cache()); + + ScopedComPtr<IStream> test_stream; + ::CreateStreamOnHGlobal(NULL, TRUE, test_stream.Receive()); + EXPECT_TRUE(NULL != test_stream); + const char test_string[] = "ReadStreamCacheTest"; + DWORD written; + EXPECT_HRESULT_SUCCEEDED(test_stream->Write(test_string, sizeof(test_string), + &written)); + EXPECT_HRESULT_SUCCEEDED(RewindStream(test_stream)); + + read_stream.SetDelegate(test_stream); + + char buffer[0xff]; + DWORD read = 0; + EXPECT_HRESULT_SUCCEEDED(read_stream.Read(buffer, sizeof(buffer), &read)); + EXPECT_EQ(read, sizeof(test_string)); + EXPECT_EQ(read_stream.GetCacheSize(), sizeof(test_string)); + read_stream.RewindCache(); + IStream* cache = read_stream.cache(); + EXPECT_TRUE(NULL != cache); + if (cache) { + read = 0; + EXPECT_HRESULT_SUCCEEDED(cache->Read(buffer, sizeof(buffer), &read)); + EXPECT_EQ(read, sizeof(test_string)); + EXPECT_EQ(0, memcmp(test_string, buffer, sizeof(test_string))); + } +} + +// Verifies that we can override bind results by using the SimpleBindingImpl +// class. +TEST_F(UrlmonMonikerTest, SimpleBindingImpl1) { + CComObjectStackEx<SimpleBindingImpl> test; + ScopedComPtr<IBinding> binding; + binding.QueryFrom(&test); + EXPECT_TRUE(binding != NULL); + test.OverrideBindResults(E_INVALIDARG); + DWORD hr = E_UNEXPECTED; + EXPECT_HRESULT_SUCCEEDED(binding->GetBindResult(NULL, &hr, NULL, NULL)); + EXPECT_EQ(E_INVALIDARG, hr); + test.OverrideBindResults(E_ACCESSDENIED); + // {1AF15145-104B-4bd8-AA4F-97CEFD40D370} - just something non-null. + GUID guid = { 0x1af15145, 0x104b, 0x4bd8, + { 0xaa, 0x4f, 0x97, 0xce, 0xfd, 0x40, 0xd3, 0x70 } }; + EXPECT_HRESULT_SUCCEEDED(binding->GetBindResult(&guid, &hr, NULL, NULL)); + EXPECT_EQ(E_ACCESSDENIED, hr); + EXPECT_TRUE(guid == GUID_NULL); +} + +// Tests the SimpleBindingImpl class with a delegate. Verifies that the +// delegate gets called and also that we can override the bind results. +TEST_F(UrlmonMonikerTest, SimpleBindingImpl2) { + CComObjectStackEx<MockBindingImpl> mock; + CComObjectStackEx<SimpleBindingImpl> test; + + EXPECT_CALL(mock, QueryService(_, _, _)) + .WillOnce(Return(E_ACCESSDENIED)); + EXPECT_CALL(mock, GetBindResult(_, _, _, _)) + .WillRepeatedly(DoAll(SetArgumentPointee<1>(E_ACCESSDENIED), + Return(S_OK))); + EXPECT_CALL(mock, Abort()) + .WillOnce(Return(E_ACCESSDENIED)); + + ScopedComPtr<IServiceProvider> svc; + svc.QueryFrom(test.GetUnknown()); + EXPECT_TRUE(svc == NULL); + + test.SetDelegate(&mock); + + // Now we should have access to IServiceProvider + svc.QueryFrom(test.GetUnknown()); + EXPECT_TRUE(svc != NULL); + + ScopedComPtr<IUnknown> unk; + EXPECT_EQ(E_ACCESSDENIED, svc->QueryService(GUID_NULL, IID_NULL, + reinterpret_cast<void**>(unk.Receive()))); + + // Call through to the mock's GetBindResult implementation. + DWORD result; + test.GetBindResult(NULL, &result, NULL, NULL); + EXPECT_TRUE(result == E_ACCESSDENIED); + // Let the binding override the result code. + test.OverrideBindResults(INET_E_TERMINATED_BIND); + test.GetBindResult(NULL, &result, NULL, NULL); + EXPECT_TRUE(result == INET_E_TERMINATED_BIND); + + EXPECT_EQ(E_ACCESSDENIED, test.Abort()); +} + +// Tests the RequestData class. Content is fed to the object via OnX methods +// and then we verify that the cached content is correct by calling +// FireHttpNegotiateEvents and see if the notifications contain the correct +// content. +TEST_F(UrlmonMonikerTest, RequestHeaders) { + scoped_refptr<RequestData> test(new RequestData()); + test->Initialize(NULL); + + const wchar_t url[] = L"http://www.chromium.org"; + const wchar_t begin_headers[] = L"Cookie: endilega\r\n"; + const wchar_t request_headers[] = L"GET / HTTP/1.0\r\nHost: cough\r\n\rn"; + const wchar_t response_headers[] = L"HTTP 200 OK\r\nHave-a: good-day\r\n\r\n"; + const wchar_t additional_headers[] = L"Referer: http://foo.com\r\n"; + + // Null pointers should be ignored. + RequestHeaders* headers = test->headers(); + EXPECT_TRUE(NULL != headers); + headers->OnBeginningTransaction(NULL, NULL, NULL); + headers->OnBeginningTransaction(url, begin_headers, additional_headers); + headers->OnResponse(500, NULL, NULL); + headers->OnResponse(200, response_headers, request_headers); + + CComObjectStackEx<MockHttpNegotiateImpl> mock; + EXPECT_CALL(mock, BeginningTransaction(StrEq(url), StrEq(begin_headers), + _, _)) + .WillOnce(Return(S_OK)); + EXPECT_CALL(mock, OnResponse(Eq(200), StrEq(response_headers), + StrEq(request_headers), _)) + .WillOnce(Return(S_OK)); + + EXPECT_EQ(0, headers->request_url().compare(url)); + + headers->FireHttpNegotiateEvents(&mock); +} + +// Tests the HTML content portion of the RequestData class. +// Here we provide content in the form of a stream object and call +// OnDataAvailable to make the object cache the contents. +// Then we fetch the cached content stream by calling +// GetResetCachedContentStream and verify that it's contents are correct. +// In order to also test when data is cached outside of OnDataAvailable +// calls, we also call DelegateDataRead. In this test we simply use the +// original content again which will make the end results that the cached +// content should be double the test content. +TEST_F(UrlmonMonikerTest, RequestDataContent) { + scoped_refptr<RequestData> test(new RequestData()); + test->Initialize(NULL); + + CComObjectStackEx<MockBindStatusCallbackImpl> mock; + + ScopedComPtr<IStream> data; + ::CreateStreamOnHGlobal(NULL, TRUE, data.Receive()); + const char content[] = "<html><head>" + "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" + "</head><body>Test HTML content</body></html>"; + data->Write(content, sizeof(content) - 1, NULL); + STATSTG stat = {0}; + data->Stat(&stat, STATFLAG_NONAME); + DCHECK(stat.cbSize.LowPart == (sizeof(content) - 1)); + RewindStream(data); + + FORMATETC format = {0}; + format.cfFormat = ::RegisterClipboardFormat(L"application/chromepage"); + format.tymed = TYMED_ISTREAM; + STGMEDIUM storage = {0}; + storage.tymed = TYMED_ISTREAM; + storage.pstm = data; + + DWORD flags = BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | + BSCF_DATAFULLYAVAILABLE; + + EXPECT_CALL(mock, OnDataAvailable(Eq(flags), Eq(stat.cbSize.LowPart), _, _)) + .WillOnce(DoAll( + WithArgs<3>(testing::Invoke( + &MockBindStatusCallbackImpl::ReadAllData)), + Return(S_OK))); + + size_t bytes_read = 0; + test->DelegateDataRead(&mock, flags, stat.cbSize.LowPart, &format, &storage, + &bytes_read); + + DCHECK(bytes_read == stat.cbSize.LowPart); + DCHECK(test->GetCachedContentSize() == bytes_read); + + // Also test that the CacheAll method appends the stream. + RewindStream(data); + test->CacheAll(data); + DCHECK(test->GetCachedContentSize() == (bytes_read * 2)); + + ScopedComPtr<IStream> cache; + EXPECT_HRESULT_SUCCEEDED(test->GetResetCachedContentStream(cache.Receive())); + if (cache) { + char buffer[0xffff]; + DCHECK((bytes_read * 2) <= sizeof(buffer)); + DWORD read = 0; + cache->Read(buffer, sizeof(buffer), &read); + EXPECT_EQ(read, bytes_read * 2); + EXPECT_EQ(0, memcmp(content, buffer, sizeof(content) - 1)); + EXPECT_EQ(0, memcmp(content, buffer + sizeof(content) - 1, + sizeof(content) - 1)); + } +} + diff --git a/chrome_frame/urlmon_bind_status_callback.cc b/chrome_frame/urlmon_bind_status_callback.cc new file mode 100644 index 0000000..ec7fefc --- /dev/null +++ b/chrome_frame/urlmon_bind_status_callback.cc @@ -0,0 +1,286 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/urlmon_bind_status_callback.h" + +#include "base/logging.h" +#include "base/string_util.h" + +CFUrlmonBindStatusCallback::CFUrlmonBindStatusCallback() + : only_buffer_(false), data_(new RequestData()) { + DLOG(INFO) << __FUNCTION__ << me(); +} + +CFUrlmonBindStatusCallback::~CFUrlmonBindStatusCallback() { + DLOG(INFO) << __FUNCTION__ << me(); +} + +std::string CFUrlmonBindStatusCallback::me() { + return StringPrintf(" obj=0x%08X", + static_cast<CFUrlmonBindStatusCallback*>(this)); +} + +HRESULT CFUrlmonBindStatusCallback::DelegateQI(void* obj, REFIID iid, + void** ret, DWORD cookie) { + CFUrlmonBindStatusCallback* me = + reinterpret_cast<CFUrlmonBindStatusCallback*>(obj); + HRESULT hr = me->delegate_.QueryInterface(iid, ret); + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::Initialize(IBindCtx* bind_ctx, + RequestHeaders* headers) { + DLOG(INFO) << __FUNCTION__ << me(); + DCHECK(bind_ctx); + DCHECK(!binding_delegate_.get()); + // headers may be NULL. + + data_->Initialize(headers); + + bind_ctx_ = bind_ctx; + + // Replace the bind context callback with ours. + HRESULT hr = ::RegisterBindStatusCallback(bind_ctx, this, + delegate_.Receive(), 0); + if (!delegate_) { + NOTREACHED(); + hr = E_UNEXPECTED; + } + + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::QueryService(REFGUID service, REFIID iid, + void** object) { + HRESULT hr = E_NOINTERFACE; + if (delegate_) { + ScopedComPtr<IServiceProvider> svc; + svc.QueryFrom(delegate_); + if (svc) { + hr = svc->QueryService(service, iid, object); + } + } + return hr; +} + +// IBindStatusCallback +HRESULT CFUrlmonBindStatusCallback::OnStartBinding(DWORD reserved, + IBinding* binding) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + DCHECK(!binding_delegate_.get()); + + CComObject<SimpleBindingImpl>* binding_delegate; + HRESULT hr = CComObject<SimpleBindingImpl>::CreateInstance(&binding_delegate); + if (FAILED(hr)) { + NOTREACHED(); + return hr; + } + + binding_delegate_ = binding_delegate; + DCHECK_EQ(binding_delegate->m_dwRef, 1); + binding_delegate_->SetDelegate(binding); + + return delegate_->OnStartBinding(reserved, binding_delegate_); +} + +HRESULT CFUrlmonBindStatusCallback::GetPriority(LONG* priority) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + return delegate_->GetPriority(priority); +} + +HRESULT CFUrlmonBindStatusCallback::OnLowResource(DWORD reserved) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + return delegate_->OnLowResource(reserved); +} + +HRESULT CFUrlmonBindStatusCallback::OnProgress(ULONG progress, + ULONG progress_max, + ULONG status_code, + LPCWSTR status_text) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" status=%i tid=%i %ls", + status_code, PlatformThread::CurrentId(), status_text); + if (status_code == BINDSTATUS_REDIRECTING && status_text) { + redirected_url_ = status_text; + } + return delegate_->OnProgress(progress, progress_max, status_code, + status_text); +} + +HRESULT CFUrlmonBindStatusCallback::OnStopBinding(HRESULT hresult, + LPCWSTR error) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" hr=0x%08X '%ls' tid=%i", + hresult, error, PlatformThread::CurrentId()); + if (SUCCEEDED(hresult)) { + // Notify the BHO that this is the one and only RequestData object. + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + DCHECK(mgr); + if (mgr && data_->GetCachedContentSize()) { + mgr->SetActiveRequestData(data_); + if (!redirected_url_.empty()) { + mgr->set_url(redirected_url_.c_str()); + } + } + + if (only_buffer_) { + hresult = INET_E_TERMINATED_BIND; + DLOG(INFO) << " - changed to INET_E_TERMINATED_BIND"; + } + } + + // Hold a reference to ourselves while we release the bind context + // and disconnect the callback. + AddRef(); + + HRESULT hr = delegate_->OnStopBinding(hresult, error); + + if (bind_ctx_) { + ::RevokeBindStatusCallback(bind_ctx_, this); + bind_ctx_.Release(); + } + + binding_delegate_.Release(); + + // After this call, this object might be gone. + Release(); + + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::GetBindInfo(DWORD* bindf, + BINDINFO* bind_info) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + return delegate_->GetBindInfo(bindf, bind_info); +} + +HRESULT CFUrlmonBindStatusCallback::OnDataAvailable(DWORD bscf, DWORD size, + FORMATETC* format_etc, + STGMEDIUM* stgmed) { +#ifndef NDEBUG + wchar_t clip_fmt_name[MAX_PATH] = {0}; + if (format_etc) { + ::GetClipboardFormatNameW(format_etc->cfFormat, clip_fmt_name, + arraysize(clip_fmt_name)); + } + DLOG(INFO) << __FUNCTION__ << me() + << StringPrintf(" tid=%i original fmt=%ls", + PlatformThread::CurrentId(), clip_fmt_name); + + DCHECK(stgmed); + DCHECK(stgmed->tymed == TYMED_ISTREAM); + + if (bscf & BSCF_FIRSTDATANOTIFICATION) { + DLOG(INFO) << "first data notification"; + } +#endif + + HRESULT hr = S_OK; + size_t bytes_read = 0; + if (!only_buffer_) { + hr = data_->DelegateDataRead(delegate_, bscf, size, format_etc, stgmed, + &bytes_read); + } + + DLOG(INFO) << __FUNCTION__ << StringPrintf(" - 0x%08x", hr); + if (hr == INET_E_TERMINATED_BIND) { + // We want to complete fetching the entire document even though the + // delegate isn't interested in continuing. + // This happens when we switch from mshtml to CF. + // We take over and buffer the document and once we're done, we report + // INET_E_TERMINATED to mshtml so that it will continue as usual. + hr = S_OK; + only_buffer_ = true; + binding_delegate_->OverrideBindResults(INET_E_TERMINATED_BIND); + } + + if (only_buffer_) { + data_->CacheAll(stgmed->pstm); + DCHECK(hr == S_OK); + } + + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::OnObjectAvailable(REFIID iid, + IUnknown* unk) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + return delegate_->OnObjectAvailable(iid, unk); +} + +// IBindStatusCallbackEx +HRESULT CFUrlmonBindStatusCallback::GetBindInfoEx(DWORD* bindf, + BINDINFO* bind_info, + DWORD* bindf2, + DWORD* reserved) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + ScopedComPtr<IBindStatusCallbackEx> bscbex; + bscbex.QueryFrom(delegate_); + return bscbex->GetBindInfoEx(bindf, bind_info, bindf2, reserved); +} + +HRESULT CFUrlmonBindStatusCallback::BeginningTransaction(LPCWSTR url, + LPCWSTR headers, + DWORD reserved, + LPWSTR* additional_headers) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + + ScopedComPtr<IHttpNegotiate> http_negotiate; + HRESULT hr = http_negotiate.QueryFrom(delegate_); + if (SUCCEEDED(hr)) { + hr = http_negotiate->BeginningTransaction(url, headers, reserved, + additional_headers); + } else { + hr = S_OK; + } + + data_->headers()->OnBeginningTransaction(url, headers, + additional_headers && *additional_headers ? *additional_headers : NULL); + + DLOG_IF(ERROR, FAILED(hr)) << __FUNCTION__; + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::OnResponse(DWORD response_code, + LPCWSTR response_headers, + LPCWSTR request_headers, + LPWSTR* additional_headers) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); + + data_->headers()->OnResponse(response_code, response_headers, + request_headers); + + ScopedComPtr<IHttpNegotiate> http_negotiate; + HRESULT hr = http_negotiate.QueryFrom(delegate_); + if (SUCCEEDED(hr)) { + hr = http_negotiate->OnResponse(response_code, response_headers, + request_headers, additional_headers); + } else { + hr = S_OK; + } + return hr; +} + +HRESULT CFUrlmonBindStatusCallback::GetRootSecurityId(BYTE* security_id, + DWORD* security_id_size, + DWORD_PTR reserved) { + ScopedComPtr<IHttpNegotiate2> http_negotiate; + http_negotiate.QueryFrom(delegate_); + return http_negotiate->GetRootSecurityId(security_id, security_id_size, + reserved); +} + +HRESULT CFUrlmonBindStatusCallback::GetSerializedClientCertContext( + BYTE** cert, + DWORD* cert_size) { + ScopedComPtr<IHttpNegotiate3> http_negotiate; + http_negotiate.QueryFrom(delegate_); + return http_negotiate->GetSerializedClientCertContext(cert, cert_size); +} diff --git a/chrome_frame/urlmon_bind_status_callback.h b/chrome_frame/urlmon_bind_status_callback.h new file mode 100644 index 0000000..d8ede6d --- /dev/null +++ b/chrome_frame/urlmon_bind_status_callback.h @@ -0,0 +1,98 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_URLMON_BIND_STATUS_CALLBACK_H_ +#define CHROME_FRAME_URLMON_BIND_STATUS_CALLBACK_H_ + +#include <atlbase.h> +#include <atlcom.h> + +#include "chrome_frame/urlmon_moniker.h" + +// Our implementation of IBindStatusCallback that allows us to sit on the +// sidelines and cache all the data that passes by (using the RequestData +// class). That cache can then be used by other monikers for the same URL +// that immediately follow. +class CFUrlmonBindStatusCallback + : public CComObjectRootEx<CComMultiThreadModel>, + public IBindStatusCallbackEx, + public IHttpNegotiate3, + public IServiceProvider { + public: + CFUrlmonBindStatusCallback(); + ~CFUrlmonBindStatusCallback(); + + // used for logging. + std::string me(); + +BEGIN_COM_MAP(CFUrlmonBindStatusCallback) + COM_INTERFACE_ENTRY(IBindStatusCallback) + COM_INTERFACE_ENTRY(IHttpNegotiate) + COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IBindStatusCallbackEx) + COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IHttpNegotiate2) + COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IHttpNegotiate3) + COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IServiceProvider) + COM_INTERFACE_ENTRY_FUNC_BLIND(0, DelegateQI) +END_COM_MAP() + + static STDMETHODIMP DelegateQI(void* obj, REFIID iid, void** ret, + DWORD cookie); + + HRESULT Initialize(IBindCtx* bind_ctx, RequestHeaders* headers); + + // For the COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS macro. + IBindStatusCallback* delegate() const { + return delegate_; + } + + RequestData* request_data() { + return data_; + } + + // IServiceProvider + STDMETHOD(QueryService)(REFGUID service, REFIID iid, void** object); + + // IBindStatusCallback + STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding); + STDMETHOD(GetPriority)(LONG* priority); + STDMETHOD(OnLowResource)(DWORD reserved); + STDMETHOD(OnProgress)(ULONG progress, ULONG progress_max, ULONG status_code, + LPCWSTR status_text); + STDMETHOD(OnStopBinding)(HRESULT hresult, LPCWSTR error); + STDMETHOD(GetBindInfo)(DWORD* bindf, BINDINFO* bind_info); + STDMETHOD(OnDataAvailable)(DWORD bscf, DWORD size, FORMATETC* format_etc, + STGMEDIUM* stgmed); + STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* unk); + + // IBindStatusCallbackEx + STDMETHOD(GetBindInfoEx)(DWORD* bindf, BINDINFO* bind_info, DWORD* bindf2, + DWORD* reserved); + + // IHttpNegotiate + STDMETHOD(BeginningTransaction)(LPCWSTR url, LPCWSTR headers, DWORD reserved, + LPWSTR* additional_headers); + STDMETHOD(OnResponse)(DWORD response_code, LPCWSTR response_headers, + LPCWSTR request_headers, LPWSTR* additional_headers); + + // IHttpNegotiate2 + STDMETHOD(GetRootSecurityId)(BYTE* security_id, DWORD* security_id_size, + DWORD_PTR reserved); + + // IHttpNegotiate3 + STDMETHOD(GetSerializedClientCertContext)(BYTE** cert, DWORD* cert_size); + + protected: + ScopedComPtr<IBindStatusCallback> delegate_; + ScopedComPtr<IBindCtx> bind_ctx_; + ScopedComPtr<SimpleBindingImpl, &GUID_NULL> binding_delegate_; + bool only_buffer_; + scoped_refptr<RequestData> data_; + std::wstring redirected_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(CFUrlmonBindStatusCallback); +}; + +#endif // CHROME_FRAME_URLMON_BIND_STATUS_CALLBACK_H_ + diff --git a/chrome_frame/urlmon_moniker.cc b/chrome_frame/urlmon_moniker.cc new file mode 100644 index 0000000..e98420d --- /dev/null +++ b/chrome_frame/urlmon_moniker.cc @@ -0,0 +1,609 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_frame/urlmon_moniker.h" + +#include <shlguid.h> + +#include "base/string_util.h" +#include "chrome_frame/bho.h" +#include "chrome_frame/urlmon_bind_status_callback.h" +#include "chrome_frame/vtable_patch_manager.h" +#include "net/http/http_util.h" + +static const int kMonikerBindToObject = 8; +static const int kMonikerBindToStorage = kMonikerBindToObject + 1; + +base::LazyInstance<base::ThreadLocalPointer<NavigationManager> > + NavigationManager::thread_singleton_(base::LINKER_INITIALIZED); + +BEGIN_VTABLE_PATCHES(IMoniker) + VTABLE_PATCH_ENTRY(kMonikerBindToObject, MonikerPatch::BindToObject) + VTABLE_PATCH_ENTRY(kMonikerBindToStorage, MonikerPatch::BindToStorage) +END_VTABLE_PATCHES() + +namespace { +std::string FindReferrerFromHeaders(const wchar_t* headers, + const wchar_t* additional_headers) { + std::string referrer; + + const wchar_t* both_headers[] = { headers, additional_headers }; + for (int i = 0; referrer.empty() && i < arraysize(both_headers); ++i) { + if (!both_headers[i]) + continue; + std::string raw_headers_utf8 = WideToUTF8(both_headers[i]); + std::string http_headers = + net::HttpUtil::AssembleRawHeaders(raw_headers_utf8.c_str(), + raw_headers_utf8.length()); + net::HttpUtil::HeadersIterator it(http_headers.begin(), + http_headers.end(), "\r\n"); + while (it.GetNext()) { + if (LowerCaseEqualsASCII(it.name(), "referer")) { + referrer = it.values(); + break; + } + } + } + + return referrer; +} + +} // end namespace + +void ReadStreamCache::RewindCache() const { + DCHECK(cache_); + RewindStream(cache_); +} + +HRESULT ReadStreamCache::WriteToCache(const void* data, ULONG size, + ULONG* written) { + HRESULT hr = S_OK; + if (!cache_) { + hr = ::CreateStreamOnHGlobal(NULL, TRUE, cache_.Receive()); + DCHECK(cache_); + } + + if (SUCCEEDED(hr)) + hr = cache_->Write(data, size, written); + + return hr; +} + +size_t ReadStreamCache::GetCacheSize() const { + size_t ret = 0; + if (cache_) { + STATSTG stg = {0}; + cache_->Stat(&stg, STATFLAG_NONAME); + DCHECK_EQ(stg.cbSize.HighPart, 0); + ret = stg.cbSize.LowPart; + } + + return ret; +} + +HRESULT ReadStreamCache::Read(void* pv, ULONG cb, ULONG* read) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + ::GetCurrentThreadId()); + DCHECK(delegate_); + DWORD read_bytes = 0; + HRESULT hr = delegate_->Read(pv, cb, &read_bytes); + if (read) + *read = read_bytes; + + // Note that Read() might return E_PENDING so we just check if anything + // was read rather than check hr. + if (read_bytes) { + DWORD written = 0; + WriteToCache(pv, read_bytes, &written); + DCHECK(read_bytes == written); + } + + return hr; +} + +///////////////////////////// + +HRESULT SimpleBindingImpl::GetBindResult(CLSID* protocol, DWORD* result_code, + LPOLESTR* result, DWORD* reserved) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", + ::GetCurrentThreadId()); + + HRESULT hr = S_OK; + + if (protocol) + *protocol = GUID_NULL; + + if (result) + *result = NULL; + + if (delegate_) + hr = delegate_->GetBindResult(protocol, result_code, result, reserved); + + if (SUCCEEDED(hr) && bind_results_ != S_OK) + *result_code = bind_results_; + + return hr; +} + +HRESULT SimpleBindingImpl::DelegateQI(void* obj, REFIID iid, void** ret, + DWORD cookie) { + SimpleBindingImpl* me = reinterpret_cast<SimpleBindingImpl*>(obj); + HRESULT hr = E_NOINTERFACE; + if (me->delegate_) + hr = me->delegate_.QueryInterface(iid, ret); + DLOG(INFO) << __FUNCTION__ << " " << GuidToString(iid) + << StringPrintf(" 0x%08X", hr); + return hr; +} + +/////////////////////////////// + +void RequestHeaders::OnBeginningTransaction(const wchar_t* url, + const wchar_t* headers, + const wchar_t* additional_headers) { + if (url) + request_url_ = url; + + if (headers) + begin_request_headers_ = headers; + + if (additional_headers) + additional_request_headers_ = additional_headers; + + DLOG(INFO) << __FUNCTION__ << "\n " << request_url_ << "\n " + << begin_request_headers_ << "\n " + << additional_request_headers_; + + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) { + DCHECK(mgr->IsTopLevelUrl(request_url_.c_str())); + mgr->SetActiveRequestHeaders(this); + } +} + +void RequestHeaders::OnResponse(DWORD response_code, + const wchar_t* response_headers, + const wchar_t* request_headers) { + response_code_ = response_code; + + if (response_headers) + response_headers_ = response_headers; + + if (request_headers) + request_headers_ = request_headers; + +#ifndef NDEBUG + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) { + // For some reason we might not have gotten an OnBeginningTransaction call! + // DCHECK(mgr->IsTopLevelUrl(request_url_.c_str())); + // DCHECK(mgr->GetActiveRequestHeaders() == this); + mgr->SetActiveRequestHeaders(this); + } +#endif +} + +std::string RequestHeaders::GetReferrer() { + std::string referrer = FindReferrerFromHeaders(begin_request_headers_.c_str(), + additional_request_headers_.c_str()); + if (referrer.empty()) { + DLOG(INFO) << request_url_ << "\nbheaders: " << begin_request_headers_ + << "\nadd:" << additional_request_headers_ << "\nreq: " << request_headers_; + } + return referrer; +} + +HRESULT RequestHeaders::FireHttpNegotiateEvents(IHttpNegotiate* http) const { + DCHECK(http); + + LPOLESTR additional_headers = NULL; + HRESULT hr = http->BeginningTransaction(request_url_.c_str(), + begin_request_headers_.c_str(), 0, &additional_headers); + DCHECK(SUCCEEDED(hr)); + ::CoTaskMemFree(additional_headers); + + // Also notify the navigation manager on this thread if one exists. + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) { + mgr->OnBeginningTransaction(true, request_url_.c_str(), + begin_request_headers_.c_str(), additional_request_headers_.c_str()); + } + + additional_headers = NULL; + hr = http->OnResponse(response_code_, response_headers_.c_str(), + request_headers_.c_str(), &additional_headers); + DCHECK(SUCCEEDED(hr)); + if (additional_headers) { + ::CoTaskMemFree(additional_headers); + additional_headers = NULL; + } + + return hr; +} + +/////////////////////////////// + +RequestData::RequestData() { + ZeroMemory(&format_, sizeof(format_)); + format_.dwAspect = 1; + format_.lindex = -1; + format_.tymed = TYMED_ISTREAM; + CComObject<ReadStreamCache>* stream = NULL; + CComObject<ReadStreamCache>::CreateInstance(&stream); + DCHECK(stream); + stream_delegate_ = stream; + DCHECK_EQ(stream->m_dwRef, 1); +} + +RequestData::~RequestData() { +} + +void RequestData::Initialize(RequestHeaders* headers) { + if (headers) { + headers_ = headers; + } else { + headers_ = new RequestHeaders(); + } +} + +HRESULT RequestData::DelegateDataRead(IBindStatusCallback* callback, + DWORD flags, DWORD size, + FORMATETC* format, STGMEDIUM* storage, + size_t* bytes_read) { + DCHECK(callback); + DCHECK(storage); + if ((flags & BSCF_FIRSTDATANOTIFICATION) && format) { + format_ = *format; + } + + IStream* original = storage->pstm; + DCHECK(original); + if (!original) + return E_POINTER; + + size_t size_before = stream_delegate_->GetCacheSize(); + stream_delegate_->SetDelegate(storage->pstm); + storage->pstm = stream_delegate_; + HRESULT hr = callback->OnDataAvailable(flags, size, format, storage); + storage->pstm = original; + if (bytes_read) + *bytes_read = stream_delegate_->GetCacheSize() - size_before; + return hr; +} + +void RequestData::CacheAll(IStream* data) { + DCHECK(data); + char buffer[4096]; + HRESULT hr = S_OK; + while (hr == S_OK) { + DWORD read = 0; + hr = data->Read(buffer, arraysize(buffer), &read); + if (read) { + DWORD written = 0; + stream_delegate_->WriteToCache(buffer, read, &written); + DCHECK(read == written); + } else { + // Just in case Read() completed with S_OK but zero bytes. + break; + } + } +} + +HRESULT RequestData::GetResetCachedContentStream(IStream** clone) { + DCHECK(clone); + IStream* cache = stream_delegate_->cache(); + if (!cache) + return HRESULT_FROM_WIN32(ERROR_EMPTY); + HRESULT hr = cache->Clone(clone); + if (SUCCEEDED(hr)) { + RewindStream(*clone); + } else { + NOTREACHED(); + } + return hr; +} + +//////////////////////////// + +HRESULT NavigationManager::NavigateToCurrentUrlInCF(IBrowserService* browser) { + DCHECK(browser); + DLOG(INFO) << __FUNCTION__ << " " << url(); + + MarkBrowserOnThreadForCFNavigation(browser); + + ScopedComPtr<IBindCtx> bind_context; + ScopedComPtr<IMoniker> moniker; + HRESULT hr = ::CreateBindCtx(0, bind_context.Receive()); + DCHECK(bind_context); + if (SUCCEEDED(hr) && + SUCCEEDED(hr = ::CreateURLMonikerEx(NULL, url_.c_str(), moniker.Receive(), + URL_MK_UNIFORM))) { + if (SUCCEEDED(hr)) { + // If there's a referrer, preserve it. + std::wstring headers; + if (!referrer_.empty()) { + headers = StringPrintf(L"Referer: %ls\r\n\r\n", + ASCIIToWide(referrer_).c_str()); + } + + // Pass in URL fragments if applicable. + std::wstring fragment; + GURL parsed_moniker_url(url_); + if (parsed_moniker_url.has_ref()) { + fragment = UTF8ToWide(parsed_moniker_url.ref()); + } + + hr = NavigateBrowserToMoniker(browser, moniker, headers.c_str(), + bind_context, fragment.c_str()); + DLOG(INFO) << StringPrintf("NavigateBrowserToMoniker: 0x%08X", hr); + } + } + + return hr; +} + +void NavigationManager::SetActiveRequestData(RequestData* request_data) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" 0x%08X", request_data); + DCHECK(request_data_ == NULL || request_data == NULL); + DLOG_IF(ERROR, url_.empty() && request_data) + << "attempt to cache data after url has been cleared"; + // Clear the current request headers as the request data must have the + // correct set of headers. + DCHECK(request_data == NULL || request_data->headers() != NULL); + request_headers_ = NULL; + + if (request_data == NULL || !url_.empty()) { + request_data_ = request_data; + } +} + +void NavigationManager::OnBeginningTransaction(bool is_top_level, + const wchar_t* url, const wchar_t* headers, + const wchar_t* additional_headers) { + DCHECK(url != NULL); + + // We've seen this happen on the first page load in IE8 (it might also happen + // in other versions of IE) and for some reason we did not get a call to + // BeginningTransaction the first time it happened and this time around we're + // using a cached data object that didn't get the request headers or URL. + DLOG_IF(ERROR, lstrlenW(url) == 0) << __FUNCTION__ << "Invalid URL."; + + if (!is_top_level) + return; + + if (url_.compare(url) != 0) { + DLOG(INFO) << __FUNCTION__ << " not processing headers for url: " << url + << " current url is: " << url_; + return; + } + + // Save away the referrer in case our active document needs it to initiate + // navigation in chrome. + referrer_ = FindReferrerFromHeaders(headers, additional_headers); +} + +///////////////////////////////////////// + +NavigationManager* NavigationManager::GetThreadInstance() { + return thread_singleton_.Pointer()->Get(); +} + +void NavigationManager::RegisterThreadInstance() { + DCHECK(GetThreadInstance() == NULL); + thread_singleton_.Pointer()->Set(this); +} + +void NavigationManager::UnregisterThreadInstance() { + DCHECK(GetThreadInstance() == this); + thread_singleton_.Pointer()->Set(NULL); +} + +///////////////////////////////////////// + +// static +bool MonikerPatch::Initialize() { + if (IS_PATCHED(IMoniker)) { + DLOG(WARNING) << __FUNCTION__ << " called more than once."; + return true; + } + + ScopedComPtr<IMoniker> moniker; + HRESULT hr = ::CreateURLMoniker(NULL, L"http://localhost/", + moniker.Receive()); + DCHECK(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + hr = vtable_patch::PatchInterfaceMethods(moniker, IMoniker_PatchInfo); + DLOG_IF(ERROR, FAILED(hr)) << StringPrintf("patch failed 0x%08X", hr); + } + + return SUCCEEDED(hr); +} + +// static +void MonikerPatch::Uninitialize() { + vtable_patch::UnpatchInterfaceMethods(IMoniker_PatchInfo); +} + +// static +HRESULT MonikerPatch::BindToObject(IMoniker_BindToObject_Fn original, + IMoniker* me, IBindCtx* bind_ctx, + IMoniker* to_left, REFIID iid, void** obj) { + DLOG(INFO) << __FUNCTION__; + DCHECK(to_left == NULL); + CComHeapPtr<WCHAR> url; + HRESULT hr = me->GetDisplayName(bind_ctx, NULL, &url); + DCHECK(SUCCEEDED(hr)); + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + DCHECK(mgr); + bool interest = mgr ? mgr->IsTopLevelUrl(url) : false; + if (interest) { + scoped_refptr<RequestData> request_data(mgr->GetActiveRequestData(url)); + if (request_data) { + DLOG(INFO) << " got cached content"; + mgr->set_referrer(request_data->headers()->GetReferrer()); + DLOG(INFO) << "referrer is: " << mgr->referrer(); + // Create a new CF document object and initialize it with + // the already cached data. + ScopedComPtr<IUnknown> cf_doc; + hr = cf_doc.CreateInstance(CLSID_ChromeActiveDocument); + DCHECK(SUCCEEDED(hr)); + ScopedComPtr<IPersistMoniker> persist_moniker; + hr = persist_moniker.QueryFrom(cf_doc); + DCHECK(SUCCEEDED(hr)); + hr = persist_moniker->Load(TRUE, me, bind_ctx, STGM_READ); + DCHECK(SUCCEEDED(hr)); + hr = persist_moniker.QueryInterface(iid, obj); + DCHECK(SUCCEEDED(hr)); + } else { + DLOG(INFO) << " creating callback object"; + CComObject<CFUrlmonBindStatusCallback>* callback = NULL; + hr = CComObject<CFUrlmonBindStatusCallback>::CreateInstance( + &callback); + callback->AddRef(); + hr = callback->Initialize(bind_ctx, mgr->GetActiveRequestHeaders()); + DCHECK(SUCCEEDED(hr)); + hr = original(me, bind_ctx, to_left, iid, obj); + callback->Release(); + if (SUCCEEDED(hr) && (*obj) == NULL) { + DCHECK(hr == MK_S_ASYNCHRONOUS); + } + } + } else { + DLOG(INFO) << " -- calling original. (no interest)"; + hr = original(me, bind_ctx, to_left, iid, obj); + } + + return hr; +} + +// static +HRESULT MonikerPatch::BindToStorage(IMoniker_BindToStorage_Fn original, + IMoniker* me, IBindCtx* bind_ctx, + IMoniker* to_left, REFIID iid, void** obj) { + DLOG(INFO) << __FUNCTION__; + DCHECK(iid == IID_IStream); + DCHECK(to_left == NULL); + + HRESULT hr = E_UNEXPECTED; + NavigationManager* mgr = NavigationManager::GetThreadInstance(); + if (mgr) { + CComHeapPtr<WCHAR> url; + hr = me->GetDisplayName(bind_ctx, NULL, &url); + DCHECK(SUCCEEDED(hr)); + bool interest = mgr->IsTopLevelUrl(url); + DLOG(INFO) << "interest: " << interest << " url " << url; + if (interest) { + scoped_refptr<RequestData> request_data(mgr->GetActiveRequestData(url)); + if (request_data) { + CComObject<SimpleBindingImpl>* binding = NULL; + CComObject<SimpleBindingImpl>::CreateInstance(&binding); + binding->OverrideBindResults(INET_E_TERMINATED_BIND); + binding->AddRef(); + hr = BindToStorageFromCache(bind_ctx, kChromeMimeType, request_data, + binding, reinterpret_cast<IStream**>(obj)); + binding->Release(); + } else { + DLOG(INFO) << " creating callback object"; + CComObject<CFUrlmonBindStatusCallback>* callback = NULL; + hr = CComObject<CFUrlmonBindStatusCallback>::CreateInstance( + &callback); + callback->AddRef(); + hr = callback->Initialize(bind_ctx, mgr->GetActiveRequestHeaders()); + DCHECK(SUCCEEDED(hr)); + hr = original(me, bind_ctx, to_left, iid, obj); + callback->Release(); + if (SUCCEEDED(hr) && (*obj) == NULL) { + DCHECK(hr == MK_S_ASYNCHRONOUS); + } + } + } else { + hr = original(me, bind_ctx, to_left, iid, obj); + } + } else { + // We usually only get here when we're issuing requests from our + // worker thread. However there are some exceptions such as when + // windows media player is issuing requests, so we don't DCHECK. + hr = original(me, bind_ctx, to_left, iid, obj); + } + + return hr; +} + +HRESULT MonikerPatch::BindToStorageFromCache(IBindCtx* bind_ctx, + const wchar_t* mime_type, + RequestData* data, + SimpleBindingImpl* binding, + IStream** cache_out) { + DCHECK(bind_ctx); + // mime_type may be NULL + DCHECK(data); + DCHECK(binding); + // cache_out may be NULL + + ScopedComPtr<IUnknown> holder; + HRESULT hr = bind_ctx->GetObjectParam(L"_BSCB_Holder_", holder.Receive()); + DCHECK(holder); + ScopedComPtr<IBindStatusCallback> bscb; + ScopedComPtr<IHttpNegotiate> http_negotiate; + DoQueryService(IID_IBindStatusCallback, holder, bscb.Receive()); + DoQueryService(IID_IHttpNegotiate, bscb, http_negotiate.Receive()); + if (!bscb) { + NOTREACHED(); + return E_NOINTERFACE; + } + + hr = bscb->OnStartBinding(0, binding); + DCHECK(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + // Fire BeginningTransaction and OnResponse events. + if (http_negotiate) + data->headers()->FireHttpNegotiateEvents(http_negotiate); + + FORMATETC format_etc = data->format(); + if (mime_type) { + // If a non NULL mime type was specified, override the clipboard format. + format_etc.cfFormat = ::RegisterClipboardFormatW(mime_type); + DCHECK(format_etc.cfFormat); + } + + const wchar_t* mime_type_available = mime_type; + wchar_t format_name[MAX_PATH] = {0}; + if (!mime_type_available) { + ::GetClipboardFormatName(format_etc.cfFormat, format_name, + arraysize(format_name)); + mime_type_available = format_name; + } + DCHECK(mime_type_available); + hr = bscb->OnProgress(0, 0, BINDSTATUS_MIMETYPEAVAILABLE, + mime_type_available); + + ScopedComPtr<IStream> cache; + data->GetResetCachedContentStream(cache.Receive()); + + STGMEDIUM medium = {0}; + medium.tymed = TYMED_ISTREAM; + medium.pstm = cache; + DLOG(INFO) << __FUNCTION__ << " total bytes available: " + << data->GetCachedContentSize(); + hr = bscb->OnDataAvailable( + BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | + BSCF_DATAFULLYAVAILABLE, data->GetCachedContentSize(), &format_etc, + &medium); + + // OnDataAvailable might report a failure such as INET_E_TERMINATED_BIND + // when mshtml decides not to handle the request. + // We ignore the return value from OnStopBinding and return whatever + // OnDataAvailable gave us. + if (FAILED(hr)) { + binding->OverrideBindResults(hr); + } else if (cache_out) { + *cache_out = cache.Detach(); + } + + bscb->OnStopBinding(hr, NULL); + } + + return hr; +} + diff --git a/chrome_frame/urlmon_moniker.h b/chrome_frame/urlmon_moniker.h new file mode 100644 index 0000000..aaab2ee --- /dev/null +++ b/chrome_frame/urlmon_moniker.h @@ -0,0 +1,404 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_URLMON_MONIKER_H_ +#define CHROME_FRAME_URLMON_MONIKER_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <urlmon.h> +#include <string> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/scoped_comptr_win.h" +#include "base/thread_local.h" + +#include "chrome_frame/urlmon_moniker_base.h" +#include "chrome_frame/utils.h" + +// This file contains classes that are used to cache the contents of a top-level +// http request (not for sub frames) while that request is parsed for the +// presence of a meta tag indicating that the page should be rendered in CF. + +// Here are a few scenarios we handle and how the classes come to play. + +// +// Scenario 1: Non CF url navigation through address bar (www.msn.com) +// - Bho::BeforeNavigate - top level url = www.msn.com +// - MSHTML -> MonikerPatch::BindToStorage. +// (IEFrame starts this by calling mshtml!*SuperNavigate*) +// - request_data is NULL +// - check if the url is a top level url +// - iff the url is a top level url, we switch in our own callback object +// and hook it up to the bind context (CFUrlmonBindStatusCallback) +// The callback object caches the document in memory. +// - otherwise just call the original +// - Bho::OnHttpEquiv is called with done == TRUE. At this point we determine +// that the page did not have the CF meta tag in it and we delete the cache. +// - The page loads in mshtml. +// + +// +// Scenario 2: CF navigation through address bar URL +// - Bho::BeforeNavigate - top level url = http://wave.google.com/ +// - MSHTML -> MonikerPatch::BindToStorage. +// (IEFrame starts this by calling mshtml!*SuperNavigate*) +// - request_data is NULL +// - check if the url is a top level url +// - iff the url is a top level url (in this case, yes), we switch in our own +// callback object and hook it up to the bind context +// (CFUrlmonBindStatusCallback) +// - As the document is fetched, the callback object caches the +// document in memory. +// - Bho::OnHttpEquiv (done == FALSE) +// - mgr->NavigateToCurrentUrlInCF +// - Set TLS (MarkBrowserOnThreadForCFNavigation) +// - Create new bind context, moniker for the URL +// - NavigateBrowserToMoniker with new moniker, bind context +// - Our callback _may_ get an error from mshtml indicating that mshtml +// isn't interested in the data anymore (since we started a new +// navigation). If that happens, our callback class +// (CFUrlmonBindStatusCallback) will continue to cache the document +// until all of it has been retrieved successfully. When the data +// is all read, we report INET_E_TERMINATE_BIND (see SimpleBindingImpl) +// as the end result. +// - In the case where all of the data has been downloaded before +// - OnHttpEquiv is called, we will already have the cache but the +// end bind status in the callback will be S_OK. +// - Bho::BeforeNavigate2 - top level url = http://wave.google.com/ +// - IEFrame -> MonikerPatch::BindToStorage +// - request_data is not NULL since we now have a cached copy of the content. +// - We call BindToStorageFromCache. +// - HttpNegotiatePatch::ReportProgress +// - Check TLS (CheckForCFNavigation) and report chrome mime type +// - IEFrame does the following: +// - Creates a new moniker +// - Calls MonikerPatch::BindToObject +// - We create an instance of ChromeActiveDocument and initialize it +// with the cached document. +// - ChromeActiveDocument gives the UrlmonUrlRequestManager the cached +// contents (RequestData) which the manager will use as the content +// when serving up content for the CF document. +// + +// +// Scenario 3: CF navigation through mshtml link +// Same as scenario #2. +// + +// +// Scenario 4: CF navigation through link click in chrome loads non CF page +// - Link click comes to ChromeActiveDocument::OnOpenURL +// - web_browser->Navigate with URL +// - [Scenario 1] +// + +// +// Scenario 5: CF navigation through link click in chrome loads CF page +// - Link click comes to ChromeActiveDocument::OnOpenURL +// - web_browser->Navigate with URL +// - [Scenario 2] +// + +// An implementation of IStream that delegates to another source +// but caches all data that is read from the source. +class ReadStreamCache + : public CComObjectRootEx<CComSingleThreadModel>, + public DelegatingReadStream { + public: + ReadStreamCache() { + DLOG(INFO) << __FUNCTION__; + } + + ~ReadStreamCache() { + DLOG(INFO) << __FUNCTION__ << " cache: " << GetCacheSize(); + } + +BEGIN_COM_MAP(ReadStreamCache) + COM_INTERFACE_ENTRY(IStream) + COM_INTERFACE_ENTRY(ISequentialStream) +END_COM_MAP() + + IStream* cache() const { + return cache_; + } + + void RewindCache() const; + + HRESULT WriteToCache(const void* data, ULONG size, ULONG* written); + + // Returns how many bytes we've cached. Used for logging. + size_t GetCacheSize() const; + + // ISequentialStream. + STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read); + + protected: + ScopedComPtr<IStream> cache_; + + private: + DISALLOW_COPY_AND_ASSIGN(ReadStreamCache); +}; + +// Basic implementation of IBinding with the option of offering a way +// to override the bind result error value. +class SimpleBindingImpl + : public CComObjectRootEx<CComSingleThreadModel>, + public DelegatingBinding { + public: + SimpleBindingImpl() : bind_results_(S_OK) { + } + + ~SimpleBindingImpl() { + } + +BEGIN_COM_MAP(SimpleBindingImpl) + COM_INTERFACE_ENTRY(IBinding) + COM_INTERFACE_ENTRY_FUNC_BLIND(0, DelegateQI) +END_COM_MAP() + + static STDMETHODIMP DelegateQI(void* obj, REFIID iid, void** ret, + DWORD cookie); + + STDMETHOD(GetBindResult)(CLSID* protocol, DWORD* result_code, + LPOLESTR* result, DWORD* reserved); + + void OverrideBindResults(HRESULT results) { + bind_results_ = results; + } + + protected: + HRESULT bind_results_; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleBindingImpl); +}; + +class RequestHeaders + : public base::RefCountedThreadSafe<RequestHeaders> { + public: + RequestHeaders() : response_code_(-1) { + } + + ~RequestHeaders() { + } + + void OnBeginningTransaction(const wchar_t* url, const wchar_t* headers, + const wchar_t* additional_headers); + + void OnResponse(DWORD response_code, const wchar_t* response_headers, + const wchar_t* request_headers); + + const std::wstring& request_url() const { + return request_url_; + } + + // Invokes BeginningTransaction and OnResponse on the |http| object + // providing already cached headers and status values. + // Any additional headers returned from either of the two methods are ignored. + HRESULT FireHttpNegotiateEvents(IHttpNegotiate* http) const; + + std::string GetReferrer(); + + protected: + std::wstring request_url_; + std::wstring begin_request_headers_; + std::wstring additional_request_headers_; + std::wstring request_headers_; + std::wstring response_headers_; + DWORD response_code_; + + private: + DISALLOW_COPY_AND_ASSIGN(RequestHeaders); +}; + +// Holds cached data for a urlmon request. +class RequestData + : public base::RefCountedThreadSafe<RequestData> { + public: + RequestData(); + ~RequestData(); + + void Initialize(RequestHeaders* headers); + + // Calls IBindStatusCallback::OnDataAvailable and caches any data that is + // read during that operation. + // We also cache the format of the data stream if available during the first + // call to this method. + HRESULT DelegateDataRead(IBindStatusCallback* callback, DWORD flags, + DWORD size, FORMATETC* format, STGMEDIUM* storage, + size_t* bytes_read); + + // Reads everything that's available from |data| into a cached stream. + void CacheAll(IStream* data); + + // Returns a new stream object to read the cache. + // The returned stream object's seek pointer is at pos 0. + HRESULT GetResetCachedContentStream(IStream** clone); + + size_t GetCachedContentSize() const { + return stream_delegate_->GetCacheSize(); + } + + const FORMATETC& format() const { + return format_; + } + + RequestHeaders* headers() const { + return headers_; + } + + void set_headers(RequestHeaders* headers) { + DCHECK(headers); + DCHECK(headers_ == NULL); + headers_ = headers; + } + + protected: + ScopedComPtr<ReadStreamCache, &GUID_NULL> stream_delegate_; + FORMATETC format_; + scoped_refptr<RequestHeaders> headers_; + + private: + DISALLOW_COPY_AND_ASSIGN(RequestData); +}; + +// This class is the link between a few static, moniker related functions to +// the bho. The specific services needed by those functions are abstracted into +// this interface for easier testability. +class NavigationManager { + public: + NavigationManager() { + } + + // Returns the Bho instance for the current thread. This is returned from + // TLS. Returns NULL if no instance exists on the current thread. + static NavigationManager* GetThreadInstance(); + + void RegisterThreadInstance(); + void UnregisterThreadInstance(); + + virtual ~NavigationManager() { + DCHECK(GetThreadInstance() != this); + } + + // Returns the url of the current top level navigation. + const std::wstring& url() const { + return url_; + } + + // Called to set the current top level URL that's being navigated to. + void set_url(const wchar_t* url) { + DLOG(INFO) << __FUNCTION__ << " " << url; + url_ = url; + } + + // Returns the referrer header value of the current top level navigation. + const std::string& referrer() const { + return referrer_; + } + + void set_referrer(const std::string& referrer) { + referrer_ = referrer; + } + + // Called when a top level navigation has finished and we don't need to + // keep the cached content around anymore. + virtual void ReleaseRequestData() { + DLOG(INFO) << __FUNCTION__; + url_.clear(); + SetActiveRequestData(NULL); + } + + // Return true if this is a URL that represents a top-level + // document that might have to be rendered in CF. + virtual bool IsTopLevelUrl(const wchar_t* url) { + return lstrcmpiW(url_.c_str(), url) == 0; + } + + // Called from HttpNegotiatePatch::BeginningTransaction when a request is + // being issued. We check the url and headers and see if there is a referrer + // header that we need to cache. + virtual void OnBeginningTransaction(bool is_top_level, const wchar_t* url, + const wchar_t* headers, + const wchar_t* additional_headers); + + // Called when we've detected the http-equiv meta tag in the current page + // and need to switch over from mshtml to CF. + virtual HRESULT NavigateToCurrentUrlInCF(IBrowserService* browser); + + virtual void SetActiveRequestData(RequestData* request_data); + + // When BindToObject is called on a URL before BindToStorage is called, + // the request and response headers are reported on that moniker. + // Later BindToStorage is called to fetch the content. We use the + // RequestHeaders class to carry over the headers to the RequestData object + // that will be created to hold the content. + virtual void SetActiveRequestHeaders(RequestHeaders* request_headers) { + request_headers_ = request_headers; + } + + virtual RequestHeaders* GetActiveRequestHeaders() { + return request_headers_; + } + + virtual RequestData* GetActiveRequestData(const wchar_t* url) { + return IsTopLevelUrl(url) ? request_data_.get() : NULL; + } + + protected: + std::string referrer_; + std::wstring url_; + scoped_refptr<RequestData> request_data_; + scoped_refptr<RequestHeaders> request_headers_; + static base::LazyInstance<base::ThreadLocalPointer<NavigationManager> > + thread_singleton_; + + private: + DISALLOW_COPY_AND_ASSIGN(NavigationManager); +}; + +// static-only class that manages an IMoniker patch. +// We need this patch to stay in the loop when top-level HTML content is +// downloaded that might have the CF http-equiv meta tag. +// When we detect candidates for those requests, we add our own callback +// object (as explained at the top of this file) and use it to cache the +// original document contents in order to avoid multiple network trips +// if we need to switch the renderer over to CF. +class MonikerPatch { + MonikerPatch() {} // no instances should be created of this class. + public: + // Patches two IMoniker methods, BindToObject and BindToStorage. + static bool Initialize(); + + // Nullifies the IMoniker patches. + static void Uninitialize(); + + // Typedefs for IMoniker methods. + typedef HRESULT (STDMETHODCALLTYPE* IMoniker_BindToObject_Fn)(IMoniker* me, + IBindCtx* bind_ctx, IMoniker* to_left, REFIID iid, void** obj); + typedef HRESULT (STDMETHODCALLTYPE* IMoniker_BindToStorage_Fn)(IMoniker* me, + IBindCtx* bind_ctx, IMoniker* to_left, REFIID iid, void** obj); + + static STDMETHODIMP BindToObject(IMoniker_BindToObject_Fn original, + IMoniker* me, IBindCtx* bind_ctx, + IMoniker* to_left, REFIID iid, void** obj); + + static STDMETHODIMP BindToStorage(IMoniker_BindToStorage_Fn original, + IMoniker* me, IBindCtx* bind_ctx, + IMoniker* to_left, REFIID iid, void** obj); + + // Reads content from cache (owned by RequestData) and simulates a regular + // binding by calling the expected methods on the callback object bound to + // the bind context. + static HRESULT BindToStorageFromCache(IBindCtx* bind_ctx, + const wchar_t* mime_type, + RequestData* data, + SimpleBindingImpl* binding, + IStream** cache_out); +}; + +#endif // CHROME_FRAME_URLMON_MONIKER_H_ + diff --git a/chrome_frame/urlmon_moniker_base.h b/chrome_frame/urlmon_moniker_base.h new file mode 100644 index 0000000..6dd022c --- /dev/null +++ b/chrome_frame/urlmon_moniker_base.h @@ -0,0 +1,156 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_FRAME_URLMON_MONIKER_BASE_H_ +#define CHROME_FRAME_URLMON_MONIKER_BASE_H_ + +#include <atlbase.h> +#include <atlcom.h> + +// An implementation of IStream (minus IUnknown) that delegates to +// another source. Most of the methods contain a NOTREACHED() as the +// class is used by the ReadStreamCache class where we don't expect those +// calls under normal circumstances. +class DelegatingReadStream : public IStream { + public: + DelegatingReadStream() { + } + + ~DelegatingReadStream() { + } + + void SetDelegate(IStream* delegate) { + delegate_ = delegate; + } + + // ISequentialStream. + STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read) { + return delegate_->Read(pv, cb, read); + } + + STDMETHOD(Write)(const void* pv, ULONG cb, ULONG* written) { + NOTREACHED(); + return delegate_->Write(pv, cb, written); + } + + // IStream. + STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) { + NOTREACHED(); + return delegate_->Seek(move, origin, new_pos); + } + + STDMETHOD(SetSize)(ULARGE_INTEGER new_size) { + NOTREACHED(); + return delegate_->SetSize(new_size); + } + + STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read, + ULARGE_INTEGER* written) { + NOTREACHED(); + return delegate_->CopyTo(stream, cb, read, written); + } + + STDMETHOD(Commit)(DWORD commit_flags) { + NOTREACHED(); + return delegate_->Commit(commit_flags); + } + + STDMETHOD(Revert)() { + NOTREACHED(); + return delegate_->Revert(); + } + + STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) { + NOTREACHED(); + return delegate_->LockRegion(offset, cb, lock_type); + } + + STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD lock_type) { + NOTREACHED(); + return delegate_->UnlockRegion(offset, cb, lock_type); + } + + STDMETHOD(Stat)(STATSTG* stat_stg, DWORD stat_flag) { + DCHECK(delegate_); + return delegate_->Stat(stat_stg, stat_flag); + } + + STDMETHOD(Clone)(IStream** stream) { + NOTREACHED(); + return delegate_->Clone(stream); + } + + protected: + ScopedComPtr<IStream> delegate_; +}; + +// A very basic implementation of IBinding that forwards all calls +// to a delegate. +class DelegatingBinding : public IBinding { + public: + DelegatingBinding() { + } + + ~DelegatingBinding() { + } + + IBinding* delegate() const { + return delegate_; + } + + void SetDelegate(IBinding* delegate) { + delegate_ = delegate; + } + + STDMETHOD(Abort)() { + DCHECK(delegate_); + if (!delegate_) + return S_OK; + return delegate_->Abort(); + } + + STDMETHOD(Suspend)() { + DCHECK(delegate_); + if (!delegate_) + return E_FAIL; + return delegate_->Suspend(); + } + + STDMETHOD(Resume)() { + DCHECK(delegate_); + if (!delegate_) + return E_FAIL; + return delegate_->Resume(); + } + + STDMETHOD(SetPriority)(LONG priority) { + DCHECK(delegate_); + if (!delegate_) + return E_FAIL; + return delegate_->SetPriority(priority); + } + + STDMETHOD(GetPriority)(LONG* priority) { + DCHECK(delegate_); + if (!delegate_) + return E_FAIL; + return delegate_->GetPriority(priority); + } + + STDMETHOD(GetBindResult)(CLSID* protocol, DWORD* result_code, + LPOLESTR* result, DWORD* reserved) { + DCHECK(delegate_); + if (!delegate_) + return E_FAIL; + return delegate_->GetBindResult(protocol, result_code, result, reserved); + } + + protected: + ScopedComPtr<IBinding> delegate_; +}; + +#endif // CHROME_FRAME_URLMON_MONIKER_BASE_H_ + diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc index dbb50d6..ae077c2 100644 --- a/chrome_frame/urlmon_url_request.cc +++ b/chrome_frame/urlmon_url_request.cc @@ -54,7 +54,21 @@ bool UrlmonUrlRequest::Start() { // StartAsyncDownload if BindToStorage finishes synchronously with an error. // Grab a reference to protect against this. scoped_refptr<UrlmonUrlRequest> ref(this); - HRESULT hr = StartAsyncDownload(); + HRESULT hr = E_UNEXPECTED; + if (request_data_) { + DCHECK(bind_context_ == NULL); + hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive()); + DCHECK(SUCCEEDED(hr)); + CComObject<SimpleBindingImpl>* binding = NULL; + CComObject<SimpleBindingImpl>::CreateInstance(&binding); + binding->AddRef(); + hr = MonikerPatch::BindToStorageFromCache(bind_context_, NULL, + request_data_, binding, NULL); + binding->Release(); + } else { + hr = StartAsyncDownload(); + } + if (FAILED(hr) && status_.get_state() != UrlmonUrlRequest::Status::DONE) { status_.Done(); status_.set_result(URLRequestStatus::FAILED, HresultToNetError(hr)); @@ -123,26 +137,8 @@ bool UrlmonUrlRequest::Read(int bytes_to_read) { return true; } -HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker, - BIND_OPTS* bind_opts, - const std::wstring& url) { - if (!moniker || url.empty() || !bind_opts) { - NOTREACHED() << "Invalid arguments"; - return E_INVALIDARG; - } - - DCHECK(moniker_.get() == NULL); - DCHECK(bind_context_.get() == NULL); - - HRESULT hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive()); - if (FAILED(hr)) { - NOTREACHED() << "Failed to create bind context"; - return hr; - } - - bind_context_->SetBindOptions(bind_opts); - moniker_ = moniker; - set_url(WideToUTF8(url)); +HRESULT UrlmonUrlRequest::SetRequestData(RequestData* data) { + request_data_ = data; return S_OK; } @@ -205,6 +201,7 @@ STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { " - Request stopped, Result: " << std::hex << result; DCHECK(status_.get_state() == Status::WORKING || status_.get_state() == Status::ABORTING); + Status::State state = status_.get_state(); // Mark we a are done. @@ -269,8 +266,7 @@ STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { } STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, - BINDINFO *bind_info) { - + BINDINFO* bind_info) { if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL)) return E_INVALIDARG; @@ -446,6 +442,8 @@ STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, wchar_t** additional_headers) { DLOG(INFO) << __FUNCTION__ << " " << url() << std::endl << " headers: " << std::endl << response_headers; + DLOG(INFO) << __FUNCTION__ + << StringPrintf(" this=0x%08X, tid=%i", this, ::GetCurrentThreadId()); DCHECK_EQ(thread_, PlatformThread::CurrentId()); DCHECK(binding_ != NULL); @@ -477,6 +475,8 @@ STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, } } + DLOG(INFO) << "Calling OnResponseStarted"; + // Inform the delegate. headers_received_ = true; delegate_->OnResponseStarted(id(), @@ -586,8 +586,11 @@ STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) { } HRESULT UrlmonUrlRequest::StartAsyncDownload() { + DLOG(INFO) << __FUNCTION__ + << StringPrintf(" this=0x%08X, tid=%i", this, ::GetCurrentThreadId()); HRESULT hr = E_FAIL; if (moniker_.get() == NULL) { + DLOG(INFO) << "Creating a new moniker for " << url(); std::wstring wide_url = UTF8ToWide(url()); hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(), URL_MK_UNIFORM); @@ -646,6 +649,7 @@ void UrlmonUrlRequest::NotifyDelegateAndDie() { } int UrlmonUrlRequest::GetHttpResponseStatus() const { + DLOG(INFO) << __FUNCTION__; if (binding_ == NULL) { DLOG(WARNING) << "GetHttpResponseStatus - no binding_"; return 0; @@ -705,6 +709,7 @@ size_t UrlmonUrlRequest::Cache::Size() const { bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size, size_t* bytes_copied) { + DLOG(INFO) << __FUNCTION__; if (!dest || !size || !is_valid()) { NOTREACHED(); return false; @@ -715,7 +720,7 @@ bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size, size_t bytes_to_write = (size <= Size() ? size : Size()); dest->Write(&cache_[0], bytes_to_write, - reinterpret_cast<unsigned long*>(&size_written)); + reinterpret_cast<unsigned long*>(&size_written)); // NOLINT DCHECK(size_written == bytes_to_write); cache_.erase(cache_.begin(), cache_.begin() + bytes_to_write); @@ -814,40 +819,36 @@ bool UrlmonUrlRequestManager::IsThreadSafe() { return true; } -void UrlmonUrlRequestManager::UseMonikerForUrl(IMoniker* moniker, - IBindCtx* bind_ctx, - const std::wstring& url) { - DCHECK(NULL == moniker_for_url_.get()); - DCHECK(bind_ctx != NULL); - - moniker_for_url_.reset(new MonikerForUrl()); - bind_ctx->GetBindOptions(&moniker_for_url_->bind_opts); - moniker_for_url_->moniker = moniker; - moniker_for_url_->url = url; +void UrlmonUrlRequestManager::UseRequestDataForUrl(RequestData* data, + const std::wstring& url) { + DCHECK(data); + DCHECK(request_data_for_url_.get() == NULL); + request_data_for_url_.reset(new RequestDataForUrl(data, url)); } void UrlmonUrlRequestManager::StartRequest(int request_id, const IPC::AutomationURLRequest& request_info) { - MonikerForUrl* use_moniker = NULL; - if (moniker_for_url_.get()) { - if (GURL(moniker_for_url_->url) == GURL(request_info.url)) { - use_moniker = moniker_for_url_.release(); + DLOG(INFO) << __FUNCTION__; + RequestDataForUrl* use_request = NULL; + if (request_data_for_url_.get()) { + if (GURL(request_data_for_url_->url()) == GURL(request_info.url)) { + use_request = request_data_for_url_.release(); } } bool posted = ExecuteInWorkerThread(FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequestManager::StartRequestWorker, - request_id, request_info, use_moniker)); + request_id, request_info, use_request)); if (!posted) { - delete use_moniker; + delete use_request; } } void UrlmonUrlRequestManager::StartRequestWorker(int request_id, const IPC::AutomationURLRequest& request_info, - MonikerForUrl* use_moniker) { + RequestDataForUrl* use_request) { DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); - scoped_ptr<MonikerForUrl> moniker_for_url(use_moniker); + scoped_ptr<RequestDataForUrl> request_for_url(use_request); if (stopping_) return; @@ -867,11 +868,9 @@ void UrlmonUrlRequestManager::StartRequestWorker(int request_id, enable_frame_busting_); new_request->set_parent_window(err_dialog_parent_wnd_); - // Shall we use an existing moniker? - if (moniker_for_url.get()) { - new_request->ConnectToExistingMoniker(moniker_for_url->moniker, - &moniker_for_url->bind_opts, - moniker_for_url->url); + // Shall we use previously fetched data? + if (request_for_url.get()) { + new_request->SetRequestData(request_for_url->request_data()); } DCHECK(LookupRequest(request_id).get() == NULL); @@ -881,12 +880,14 @@ void UrlmonUrlRequestManager::StartRequestWorker(int request_id, } void UrlmonUrlRequestManager::ReadRequest(int request_id, int bytes_to_read) { + DLOG(INFO) << __FUNCTION__; ExecuteInWorkerThread(FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequestManager::ReadRequestWorker, request_id, bytes_to_read)); } void UrlmonUrlRequestManager::ReadRequestWorker(int request_id, int bytes_to_read) { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id); // if zero, it may just have had network error. @@ -896,11 +897,13 @@ void UrlmonUrlRequestManager::ReadRequestWorker(int request_id, } void UrlmonUrlRequestManager::EndRequest(int request_id) { + DLOG(INFO) << __FUNCTION__; ExecuteInWorkerThread(FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequestManager::EndRequestWorker, request_id)); } void UrlmonUrlRequestManager::EndRequestWorker(int request_id) { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id); if (request) { @@ -909,6 +912,7 @@ void UrlmonUrlRequestManager::EndRequestWorker(int request_id) { } void UrlmonUrlRequestManager::StopAll() { + DLOG(INFO) << __FUNCTION__; do { AutoLock lock(worker_thread_access_); if (stopping_) @@ -923,7 +927,7 @@ void UrlmonUrlRequestManager::StopAll() { // to the worker thread. worker_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequestManager::StopAllWorker)); - } while(0); + } while (0); // Note we may not call worker_thread_.Stop() here. The MessageLoop's quit // task will be serialized after request::Stop tasks, but requests may @@ -941,6 +945,7 @@ void UrlmonUrlRequestManager::StopAll() { } void UrlmonUrlRequestManager::StopAllWorker() { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); DCHECK_EQ(true, stopping_); @@ -964,6 +969,7 @@ void UrlmonUrlRequestManager::OnResponseStarted(int request_id, const char* mime_type, const char* headers, int size, base::Time last_modified, const std::string& redirect_url, int redirect_status) { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); DCHECK(LookupRequest(request_id).get() != NULL); delegate_->OnResponseStarted(request_id, mime_type, headers, size, @@ -972,6 +978,7 @@ void UrlmonUrlRequestManager::OnResponseStarted(int request_id, void UrlmonUrlRequestManager::OnReadComplete(int request_id, const void* buffer, int len) { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); DCHECK(LookupRequest(request_id).get() != NULL); delegate_->OnReadComplete(request_id, buffer, len); @@ -979,6 +986,7 @@ void UrlmonUrlRequestManager::OnReadComplete(int request_id, const void* buffer, void UrlmonUrlRequestManager::OnResponseEnd(int request_id, const URLRequestStatus& status) { + DLOG(INFO) << __FUNCTION__; DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); RequestMap::size_type n = request_map_.erase(request_id); DCHECK_EQ(1, n); @@ -1050,4 +1058,5 @@ bool UrlmonUrlRequestManager::ExecuteInWorkerThread( void UrlmonUrlRequestManager::SetErrorDialogsParentWindow(HWND window) { err_dialog_parent_wnd_ = window; -}
\ No newline at end of file +} + diff --git a/chrome_frame/urlmon_url_request.h b/chrome_frame/urlmon_url_request.h index 6fbc147..5382314 100644 --- a/chrome_frame/urlmon_url_request.h +++ b/chrome_frame/urlmon_url_request.h @@ -8,6 +8,7 @@ #include <urlmon.h> #include <atlbase.h> #include <atlcom.h> +#include <map> #include <string> #include "base/lock.h" @@ -15,10 +16,32 @@ #include "base/thread.h" #include "base/waitable_event.h" #include "chrome_frame/plugin_url_request.h" +#include "chrome_frame/urlmon_moniker.h" #include "chrome_frame/utils.h" class UrlmonUrlRequest; +class RequestDataForUrl { + public: + RequestDataForUrl(RequestData* data, const std::wstring& url) + : request_data_(data), url_(url) { + } + ~RequestDataForUrl() { + } + + const std::wstring& url() const { + return url_; + } + + RequestData* request_data() const { + return request_data_; + } + + protected: + scoped_refptr<RequestData> request_data_; + std::wstring url_; +}; + class UrlmonUrlRequestManager : public PluginUrlRequestManager, public PluginUrlRequestDelegate { @@ -26,22 +49,12 @@ class UrlmonUrlRequestManager UrlmonUrlRequestManager(); ~UrlmonUrlRequestManager(); - // Use specific moniker and bind context when Chrome request this url. + // Use cached data when Chrome request this url. // Used from ChromeActiveDocument's implementation of IPersistMoniker::Load(). - void UseMonikerForUrl(IMoniker* moniker, IBindCtx* bind_ctx, - const std::wstring& url); + void UseRequestDataForUrl(RequestData* data, const std::wstring& url); void StealMonikerFromRequest(int request_id, IMoniker** moniker); void SetErrorDialogsParentWindow(HWND window); private: - struct MonikerForUrl { - MonikerForUrl() { - memset(&bind_opts, 0, sizeof(bind_opts)); - } - ScopedComPtr<IMoniker> moniker; - BIND_OPTS bind_opts; - std::wstring url; - }; - friend class MessageLoop; friend struct RunnableMethodTraits<UrlmonUrlRequestManager>; static bool ImplementsThreadSafeReferenceCounting() { return true; } @@ -68,7 +81,7 @@ class UrlmonUrlRequestManager // Methods executed in worker thread. void StartRequestWorker(int request_id, const IPC::AutomationURLRequest& request_info, - MonikerForUrl* moniker_for_url); + RequestDataForUrl* use_request); void ReadRequestWorker(int request_id, int bytes_to_read); void EndRequestWorker(int request_id); void StopAllWorker(); @@ -80,7 +93,7 @@ class UrlmonUrlRequestManager RequestMap request_map_; scoped_refptr<UrlmonUrlRequest> LookupRequest(int request_id); - scoped_ptr<MonikerForUrl> moniker_for_url_; + scoped_ptr<RequestDataForUrl> request_data_for_url_; STAThread worker_thread_; base::WaitableEvent map_empty_; bool stopping_; diff --git a/chrome_frame/urlmon_url_request_private.h b/chrome_frame/urlmon_url_request_private.h index 13a40d4..1f76057 100644 --- a/chrome_frame/urlmon_url_request_private.h +++ b/chrome_frame/urlmon_url_request_private.h @@ -14,6 +14,8 @@ #include "net/http/http_response_headers.h" #include "net/url_request/url_request_status.h" +class RequestData; + class UrlmonUrlRequest : public CComObjectRootEx<CComMultiThreadModel>, public PluginUrlRequest, @@ -28,9 +30,7 @@ class UrlmonUrlRequest virtual bool Read(int bytes_to_read); // Special function needed by ActiveDocument::Load() - HRESULT ConnectToExistingMoniker(IMoniker* moniker, - BIND_OPTS* bind_opts, - const std::wstring& url); + HRESULT SetRequestData(RequestData* data); // Used from "OnDownloadRequestInHost". void StealMoniker(IMoniker** moniker); @@ -163,7 +163,7 @@ class UrlmonUrlRequest return E_NOTIMPL; } - protected: + protected: scoped_refptr<UrlmonUrlRequest> request_; DISALLOW_COPY_AND_ASSIGN(SendStream); }; @@ -296,6 +296,7 @@ class UrlmonUrlRequest ScopedComPtr<IBinding> binding_; ScopedComPtr<IMoniker> moniker_; ScopedComPtr<IBindCtx> bind_context_; + scoped_refptr<RequestData> request_data_; Cache cached_data_; size_t pending_read_size_; PlatformThreadId thread_; diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc index 560d4dc..f58b47d 100644 --- a/chrome_frame/utils.cc +++ b/chrome_frame/utils.cc @@ -35,6 +35,8 @@ const wchar_t kXUACompatValue[] = L"x-ua-compatible"; const wchar_t kBodyTag[] = L"body"; const wchar_t kChromeContentPrefix[] = L"chrome="; const wchar_t kChromeProtocolPrefix[] = L"gcf:"; +const wchar_t kChromeMimeType[] = L"application/chromepage"; +const wchar_t kPatchProtocols[] = L"PatchProtocols"; static const wchar_t kChromeFrameConfigKey[] = L"Software\\Google\\ChromeFrame"; @@ -868,6 +870,17 @@ bool IsTopLevelWindow(HWND window) { return !parent || (parent == GetDesktopWindow()); } +HRESULT RewindStream(IStream* stream) { + HRESULT hr = E_POINTER; + if (stream) { + LARGE_INTEGER zero = {0}; + ULARGE_INTEGER new_pos = {0}; + hr = stream->Seek(zero, STREAM_SEEK_SET, &new_pos); + } + + return hr; +} + std::wstring GuidToString(const GUID& guid) { std::wstring ret; ::StringFromGUID2(guid, WriteInto(&ret, 39), 39); diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index adbd65e..6209248 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -22,6 +22,21 @@ extern const wchar_t kChromeContentPrefix[]; extern const wchar_t kChromeProtocolPrefix[]; extern const wchar_t kChromeFrameHeadlessMode[]; extern const wchar_t kEnableGCFProtocol[]; +extern const wchar_t kChromeMimeType[]; + +typedef enum ProtocolPatchMethod { + PATCH_METHOD_IBROWSER = 0, + PATCH_METHOD_INET_PROTOCOL, // 1 + PATCH_METHOD_IBROWSER_AND_MONIKER, // 2 +}; + +// A REG_DWORD config value that maps to the ProtocolPatchMethod enum. +// To get the config value, call: +// ProtocolPatchMethod patch_method = +// static_cast<ProtocolPatchMethod>( +// GetConfigInt(PATCH_METHOD_IBROWSER, kPatchProtocols)); +extern const wchar_t kPatchProtocols[]; + // This function is very similar to the AtlRegisterTypeLib function except // that it takes a parameter that specifies whether to register the typelib @@ -351,6 +366,9 @@ std::wstring GetActualUrlFromMoniker(IMoniker* moniker, // Checks if a window is a top level window bool IsTopLevelWindow(HWND window); +// Seeks a stream back to position 0. +HRESULT RewindStream(IStream* stream); + extern Lock g_ChromeFrameHistogramLock; // Thread safe versions of the UMA histogram macros we use for ChromeFrame. |