summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-12 22:06:14 +0000
committertommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-12 22:06:14 +0000
commit051236f654c97138dd90a81c84e2b4dcaeb29918 (patch)
treeba065c97767f081d19f804d3a196b3ebba5d0609
parent507c71074588f718be1808bc0ffe51f3733f6eb7 (diff)
downloadchromium_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
-rw-r--r--chrome_frame/bho.cc181
-rw-r--r--chrome_frame/bho.h41
-rw-r--r--chrome_frame/chrome_active_document.cc38
-rw-r--r--chrome_frame/chrome_frame.gyp14
-rw-r--r--chrome_frame/chrome_frame_activex_base.h2
-rw-r--r--chrome_frame/chrome_protocol.cc3
-rw-r--r--chrome_frame/http_negotiate.cc25
-rw-r--r--chrome_frame/protocol_sink_wrap.cc1
-rw-r--r--chrome_frame/test/data/full_tab_get_target_cf.html3
-rw-r--r--chrome_frame/test/data/full_tab_post_target_cf.html3
-rw-r--r--chrome_frame/test/test_with_web_server.cc50
-rw-r--r--chrome_frame/test/urlmon_moniker_integration_test.cc327
-rw-r--r--chrome_frame/test/urlmon_moniker_tests.h142
-rw-r--r--chrome_frame/test/urlmon_moniker_unittest.cc226
-rw-r--r--chrome_frame/urlmon_bind_status_callback.cc286
-rw-r--r--chrome_frame/urlmon_bind_status_callback.h98
-rw-r--r--chrome_frame/urlmon_moniker.cc609
-rw-r--r--chrome_frame/urlmon_moniker.h404
-rw-r--r--chrome_frame/urlmon_moniker_base.h156
-rw-r--r--chrome_frame/urlmon_url_request.cc107
-rw-r--r--chrome_frame/urlmon_url_request.h41
-rw-r--r--chrome_frame/urlmon_url_request_private.h9
-rw-r--r--chrome_frame/utils.cc13
-rw-r--r--chrome_frame/utils.h18
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.