1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome_frame/urlmon_moniker.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "chrome_frame/bho.h"
14 #include "chrome_frame/bind_context_info.h"
15 #include "chrome_frame/chrome_active_document.h"
16 #include "chrome_frame/exception_barrier.h"
17 #include "chrome_frame/urlmon_bind_status_callback.h"
18 #include "chrome_frame/utils.h"
19 #include "chrome_frame/vtable_patch_manager.h"
20 #include "net/http/http_util.h"
22 static const int kMonikerBindToObject
= 8;
23 static const int kMonikerBindToStorage
= kMonikerBindToObject
+ 1;
25 base::LazyInstance
<base::ThreadLocalPointer
<NavigationManager
> >
26 NavigationManager::thread_singleton_
= LAZY_INSTANCE_INITIALIZER
;
28 BEGIN_VTABLE_PATCHES(IMoniker
)
29 VTABLE_PATCH_ENTRY(kMonikerBindToObject
, MonikerPatch::BindToObject
)
30 VTABLE_PATCH_ENTRY(kMonikerBindToStorage
, MonikerPatch::BindToStorage
)
33 ////////////////////////////
35 HRESULT
NavigationManager::NavigateToCurrentUrlInCF(IBrowserService
* browser
) {
37 DVLOG(1) << __FUNCTION__
<< " " << url();
39 MarkBrowserOnThreadForCFNavigation(browser
);
42 base::win::ScopedComPtr
<IShellBrowser
> shell_browser
;
43 base::win::ScopedComPtr
<IBindCtx
> bind_context
;
44 hr
= ::CreateAsyncBindCtxEx(NULL
, 0, NULL
, NULL
, bind_context
.Receive(), 0);
46 base::win::ScopedComPtr
<IMoniker
> moniker
;
49 SUCCEEDED(hr
= ::CreateURLMonikerEx(NULL
, url_
.c_str(), moniker
.Receive(),
52 // If there's a referrer, preserve it.
54 if (!referrer_
.empty()) {
55 headers
= base::StringPrintf(L
"Referer: %ls\r\n\r\n",
56 ASCIIToWide(referrer_
).c_str());
59 // Pass in URL fragments if applicable.
60 std::wstring fragment
;
61 GURL
parsed_moniker_url(url_
);
62 if (parsed_moniker_url
.has_ref()) {
63 fragment
= UTF8ToWide(parsed_moniker_url
.ref());
66 VARIANT flags
= { VT_I4
};
67 V_VT(&flags
) = navNoHistory
| navOpenInNewWindow
;
69 hr
= NavigateBrowserToMoniker(browser
, moniker
, headers
.c_str(),
70 bind_context
, fragment
.c_str(), NULL
, &flags
);
71 DVLOG(1) << base::StringPrintf("NavigateBrowserToMoniker: 0x%08X", hr
);
78 bool NavigationManager::IsTopLevelUrl(const wchar_t* url
) {
79 return CompareUrlsWithoutFragment(url_
.c_str(), url
);
82 /////////////////////////////////////////
84 NavigationManager
* NavigationManager::GetThreadInstance() {
85 return thread_singleton_
.Pointer()->Get();
88 void NavigationManager::RegisterThreadInstance() {
89 DCHECK(GetThreadInstance() == NULL
);
90 thread_singleton_
.Pointer()->Set(this);
93 void NavigationManager::UnregisterThreadInstance() {
94 DCHECK(GetThreadInstance() == this);
95 thread_singleton_
.Pointer()->Set(NULL
);
98 /////////////////////////////////////////
101 bool MonikerPatch::Initialize() {
102 if (IS_PATCHED(IMoniker
)) {
103 DLOG(WARNING
) << __FUNCTION__
<< " called more than once.";
107 base::win::ScopedComPtr
<IMoniker
> moniker
;
108 HRESULT hr
= ::CreateURLMoniker(NULL
, L
"http://localhost/",
110 DCHECK(SUCCEEDED(hr
));
112 hr
= vtable_patch::PatchInterfaceMethods(moniker
, IMoniker_PatchInfo
);
113 DLOG_IF(ERROR
, FAILED(hr
)) << base::StringPrintf(
114 "patch failed 0x%08X", hr
);
117 return SUCCEEDED(hr
);
121 void MonikerPatch::Uninitialize() {
122 vtable_patch::UnpatchInterfaceMethods(IMoniker_PatchInfo
);
125 bool ShouldWrapCallback(IMoniker
* moniker
, REFIID iid
, IBindCtx
* bind_context
) {
126 CComHeapPtr
<WCHAR
> url
;
127 HRESULT hr
= moniker
->GetDisplayName(bind_context
, NULL
, &url
);
129 DVLOG(1) << __FUNCTION__
130 << base::StringPrintf(" GetDisplayName failed. Error: 0x%x", hr
);
134 if (!IsEqualIID(IID_IStream
, iid
)) {
135 DVLOG(1) << __FUNCTION__
<< " Url: " << url
136 << " Not wrapping: IID is not IStream.";
140 base::win::ScopedComPtr
<BindContextInfo
> info
;
141 BindContextInfo::FromBindContext(bind_context
, info
.Receive());
143 if (info
&& info
->chrome_request()) {
144 DVLOG(1) << __FUNCTION__
<< " Url: " << url
145 << " Not wrapping: request from chrome frame.";
149 NavigationManager
* mgr
= NavigationManager::GetThreadInstance();
151 DVLOG(1) << __FUNCTION__
<< " Url: " << url
152 << " No navigation manager to wrap";
156 // Check whether request comes from MSHTML by checking for IInternetBindInfo.
157 // We prefer to avoid wrapping if BindToStorage is called from AcroPDF.dll
158 // (as a result of OnObjectAvailable)
159 base::win::ScopedComPtr
<IUnknown
> bscb_holder
;
160 if (S_OK
== bind_context
->GetObjectParam(L
"_BSCB_Holder_",
161 bscb_holder
.Receive())) {
162 base::win::ScopedComPtr
<IBindStatusCallback
> bscb
;
163 if (S_OK
!= DoQueryService(IID_IBindStatusCallback
, bscb_holder
,
170 base::win::ScopedComPtr
<IInternetBindInfo
> bind_info
;
171 if (S_OK
!= bind_info
.QueryFrom(bscb
))
176 // Use the IsSubFrameRequest function to determine if a request is a top
177 // level request. Something like this.
178 // base::win::ScopedComPtr<IUnknown> bscb_holder;
179 // bind_context->GetObjectParam(L"_BSCB_Holder_", bscb_holder.Receive());
180 // if (bscb_holder) {
181 // base::win::ScopedComPtr<IHttpNegotiate> http_negotiate;
182 // http_negotiate.QueryFrom(bscb_holder);
183 // if (http_negotiate && !IsSubFrameRequest(http_negotiate))
186 // There are some cases where the IsSubFrameRequest function can return
187 // incorrect results.
188 bool should_wrap
= mgr
->IsTopLevelUrl(url
);
190 DVLOG(1) << __FUNCTION__
<< " Url: " << url
191 << " Not wrapping: Not top level url.";
197 HRESULT
MonikerPatch::BindToObject(IMoniker_BindToObject_Fn original
,
198 IMoniker
* me
, IBindCtx
* bind_ctx
,
199 IMoniker
* to_left
, REFIID iid
, void** obj
) {
200 DVLOG(1) << __FUNCTION__
;
201 DCHECK(to_left
== NULL
);
203 ExceptionBarrierReportOnlyModule barrier
;
206 // Bind context is marked for switch when we sniff data in BSCBStorageBind
207 // and determine that the renderer to be used is Chrome.
208 base::win::ScopedComPtr
<BindContextInfo
> info
;
209 BindContextInfo::FromBindContext(bind_ctx
, info
.Receive());
212 if (info
->is_switching()) {
213 // We could implement the BindToObject ourselves here but instead we
214 // simply register Chrome Frame ActiveDoc as a handler for 'text/html'
215 // in this bind context. This makes urlmon instantiate CF Active doc
216 // instead of mshtml.
217 const char* media_types
[] = { "text/html" };
218 CLSID classes
[] = { CLSID_ChromeActiveDocument
};
219 hr
= RegisterMediaTypeClass(bind_ctx
, arraysize(media_types
), media_types
,
222 // In case the binding begins with BindToObject we do not need
223 // to cache the data in the sniffing code.
224 info
->set_no_cache(true);
228 hr
= original(me
, bind_ctx
, to_left
, iid
, obj
);
233 HRESULT
MonikerPatch::BindToStorage(IMoniker_BindToStorage_Fn original
,
234 IMoniker
* me
, IBindCtx
* bind_ctx
,
235 IMoniker
* to_left
, REFIID iid
, void** obj
) {
236 DCHECK(to_left
== NULL
);
238 // Report a crash if the crash is in our own module.
239 ExceptionBarrierReportOnlyModule barrier
;
242 scoped_refptr
<BSCBStorageBind
> auto_release_callback
;
243 CComObject
<BSCBStorageBind
>* callback
= NULL
;
244 if (ShouldWrapCallback(me
, iid
, bind_ctx
)) {
245 hr
= CComObject
<BSCBStorageBind
>::CreateInstance(&callback
);
246 DCHECK(SUCCEEDED(hr
));
247 auto_release_callback
= callback
;
248 DCHECK_EQ(callback
->m_dwRef
, 1);
249 hr
= callback
->Initialize(me
, bind_ctx
);
250 DCHECK(SUCCEEDED(hr
));
253 hr
= original(me
, bind_ctx
, to_left
, iid
, obj
);
255 // If the binding terminates before the data could be played back
256 // now is the chance. Sometimes OnStopBinding happens after this returns
257 // and then it's too late.
258 if ((S_OK
== hr
) && callback
)
259 callback
->MayPlayBack(BSCF_LASTDATANOTIFICATION
);