diff options
author | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-12 22:06:14 +0000 |
---|---|---|
committer | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-12 22:06:14 +0000 |
commit | 051236f654c97138dd90a81c84e2b4dcaeb29918 (patch) | |
tree | ba065c97767f081d19f804d3a196b3ebba5d0609 /chrome_frame/urlmon_moniker.h | |
parent | 507c71074588f718be1808bc0ffe51f3733f6eb7 (diff) | |
download | chromium_src-051236f654c97138dd90a81c84e2b4dcaeb29918.zip chromium_src-051236f654c97138dd90a81c84e2b4dcaeb29918.tar.gz chromium_src-051236f654c97138dd90a81c84e2b4dcaeb29918.tar.bz2 |
Fix #1 for multiple request issue (both POST and GET) after activating the onhttpequiv approach.
There's a ton going on here. Brief summary: I'm introducing a caching layer for top level page requests that we then use again when switching the document from mshtml to cf.
Also in this change list:
* Removing the previous way of shifting the document moniker
over to the worker thread when a new chrome document is
created. Instead we use a request cache object.
* Refactoring the part of the Bho class that offered access
to it via TLS. This was needed for better testability but
also makes (imho) the separation clearer and will possibly
help in the future if we don't have a Bho object.
* Added a bit of logging that I'd prefer to keep in there until we're confident enough with onhttpequiv.
* Enabling two previously disabled tests (the ones I added for multiple requests)
* Adding several more unit tests for the new code.
Review URL: http://codereview.chromium.org/668187
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@41495 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame/urlmon_moniker.h')
-rw-r--r-- | chrome_frame/urlmon_moniker.h | 404 |
1 files changed, 404 insertions, 0 deletions
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_ + |