summaryrefslogtreecommitdiffstats
path: root/chrome_frame
diff options
context:
space:
mode:
Diffstat (limited to 'chrome_frame')
-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.