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_bind_status_callback.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/platform_thread.h"
15 #include "chrome_frame/bind_context_info.h"
16 #include "chrome_frame/chrome_tab.h"
17 #include "chrome_frame/exception_barrier.h"
18 #include "chrome_frame/urlmon_moniker.h"
21 // A helper to given feed data to the specified |bscb| using
22 // CacheStream instance.
23 HRESULT
CacheStream::BSCBFeedData(IBindStatusCallback
* bscb
, const char* data
,
24 size_t size
, CLIPFORMAT clip_format
,
25 size_t flags
, bool eof
) {
27 NOTREACHED() << "invalid IBindStatusCallback";
31 // We can't use a CComObjectStackEx here since mshtml will hold
32 // onto the stream pointer.
33 CComObject
<CacheStream
>* cache_stream
= NULL
;
34 HRESULT hr
= CComObject
<CacheStream
>::CreateInstance(&cache_stream
);
40 scoped_refptr
<CacheStream
> cache_ref
= cache_stream
;
41 hr
= cache_stream
->Initialize(data
, size
, eof
);
45 FORMATETC format_etc
= { clip_format
, NULL
, DVASPECT_CONTENT
, -1,
47 STGMEDIUM medium
= {0};
48 medium
.tymed
= TYMED_ISTREAM
;
49 medium
.pstm
= cache_stream
;
51 hr
= bscb
->OnDataAvailable(flags
, size
, &format_etc
, &medium
);
55 HRESULT
CacheStream::Initialize(const char* cache
, size_t size
, bool eof
) {
60 cache_
.reset(new char[size
]);
62 memcpy(cache_
.get(), cache
, size
);
65 DLOG(ERROR
) << "failed to allocate cache stream.";
72 // Read is the only call that we expect. Return E_PENDING if there
73 // is no more data to serve. Otherwise this will result in a
74 // read with 0 bytes indicating that no more data is available.
75 STDMETHODIMP
CacheStream::Read(void* pv
, ULONG cb
, ULONG
* read
) {
84 // Default to E_PENDING to signal that this is a partial data.
85 HRESULT hr
= eof_
? S_FALSE
: E_PENDING
;
86 if (position_
< size_
) {
87 *read
= std::min(size_
- position_
, size_t(cb
));
88 memcpy(pv
, cache_
.get() + position_
, *read
);
97 /////////////////////////////////////////////////////////////////////
99 HRESULT
SniffData::InitializeCache(const std::wstring
& url
) {
101 renderer_type_
= UNDETERMINED
;
103 const int kInitialSize
= 4 * 1024; // 4K
104 HGLOBAL mem
= GlobalAlloc(0, kInitialSize
);
105 DCHECK(mem
) << "GlobalAlloc failed: " << GetLastError();
107 HRESULT hr
= CreateStreamOnHGlobal(mem
, TRUE
, cache_
.Receive());
109 ULARGE_INTEGER size
= {0};
110 cache_
->SetSize(size
);
112 DLOG(ERROR
) << "CreateStreamOnHGlobal failed: " << hr
;
118 HRESULT
SniffData::ReadIntoCache(IStream
* stream
, bool force_determination
) {
125 while (SUCCEEDED(hr
)) {
126 const size_t kChunkSize
= 4 * 1024;
127 char buffer
[kChunkSize
];
129 hr
= stream
->Read(buffer
, sizeof(buffer
), &read
);
132 cache_
->Write(buffer
, read
, &written
);
136 if ((S_FALSE
== hr
) || !read
)
140 bool last_chance
= force_determination
|| (size() >= kMaxSniffSize
);
141 eof_
= force_determination
;
142 DetermineRendererType(last_chance
);
146 HRESULT
SniffData::DrainCache(IBindStatusCallback
* bscb
, DWORD bscf
,
147 CLIPFORMAT clip_format
) {
148 if (!is_cache_valid()) {
152 // Ideally we could just use the cache_ IStream implementation but
153 // can't use it here since we have to return E_PENDING for the
155 HGLOBAL memory
= NULL
;
156 HRESULT hr
= GetHGlobalFromStream(cache_
, &memory
);
157 if (SUCCEEDED(hr
) && memory
) {
158 char* buffer
= reinterpret_cast<char*>(GlobalLock(memory
));
159 hr
= CacheStream::BSCBFeedData(bscb
, buffer
, size_
, clip_format
, bscf
,
161 GlobalUnlock(memory
);
169 // Scan the buffer or OptIn URL list and decide if the renderer is
170 // to be switched. Last chance means there's no more data.
171 void SniffData::DetermineRendererType(bool last_chance
) {
172 if (is_undetermined()) {
174 renderer_type_
= OTHER
;
175 if (IsChrome(RendererTypeForUrl(url_
))) {
176 renderer_type_
= CHROME
;
178 if (is_cache_valid() && cache_
) {
179 HGLOBAL memory
= NULL
;
180 GetHGlobalFromStream(cache_
, &memory
);
181 const char* buffer
= reinterpret_cast<const char*>(GlobalLock(memory
));
183 std::wstring html_contents
;
184 // TODO(joshia): detect and handle different content encodings
185 if (buffer
&& size_
) {
186 UTF8ToWide(buffer
, std::min(size_
, kMaxSniffSize
), &html_contents
);
187 GlobalUnlock(memory
);
190 // Note that document_contents_ may have NULL characters in it. While
191 // browsers may handle this properly, we don't and will stop scanning
192 // for the XUACompat content value if we encounter one.
193 std::wstring xua_compat_content
;
194 UtilGetXUACompatContentValue(html_contents
, &xua_compat_content
);
195 if (StrStrI(xua_compat_content
.c_str(), kChromeContentPrefix
)) {
196 renderer_type_
= CHROME
;
200 DVLOG(1) << __FUNCTION__
<< "Url: " << url_
<< base::StringPrintf(
201 "Renderer type: %s", renderer_type_
== CHROME
? "CHROME" : "OTHER");
205 /////////////////////////////////////////////////////////////////////
207 BSCBStorageBind::BSCBStorageBind() : clip_format_(CF_NULL
) {
210 BSCBStorageBind::~BSCBStorageBind() {
211 std::for_each(saved_progress_
.begin(), saved_progress_
.end(),
212 utils::DeleteObject());
215 HRESULT
BSCBStorageBind::Initialize(IMoniker
* moniker
, IBindCtx
* bind_ctx
) {
216 DVLOG(1) << __FUNCTION__
<< me()
217 << base::StringPrintf(" tid=%i", base::PlatformThread::CurrentId());
219 std::wstring url
= GetActualUrlFromMoniker(moniker
, bind_ctx
,
221 HRESULT hr
= data_sniffer_
.InitializeCache(url
);
225 hr
= AttachToBind(bind_ctx
);
227 NOTREACHED() << __FUNCTION__
<< me() << "AttachToBind error: " << hr
;
232 NOTREACHED() << __FUNCTION__
<< me() << "No existing callback: " << hr
;
239 STDMETHODIMP
BSCBStorageBind::OnProgress(ULONG progress
, ULONG progress_max
,
240 ULONG status_code
, LPCWSTR status_text
) {
241 DVLOG(1) << __FUNCTION__
<< me()
242 << base::StringPrintf(" status=%i tid=%i %ls", status_code
,
243 base::PlatformThread::CurrentId(),
245 // Report all crashes in the exception handler if we wrap the callback.
246 // Note that this avoids having the VEH report a crash if an SEH earlier in
247 // the chain handles the exception.
248 ExceptionBarrier barrier
;
253 // ChromeFrame will not be informed of any redirects which occur while we
254 // switch into Chrome. This will only break the moniker patch which is
255 // legacy and needs to be deleted.
257 if (ShouldCacheProgress(status_code
)) {
258 saved_progress_
.push_back(new Progress(progress
, progress_max
, status_code
,
261 hr
= CallbackImpl::OnProgress(progress
, progress_max
, status_code
,
268 // Refer to urlmon_moniker.h for explanation of how things work.
269 STDMETHODIMP
BSCBStorageBind::OnDataAvailable(DWORD flags
, DWORD size
,
270 FORMATETC
* format_etc
,
272 DVLOG(1) << __FUNCTION__
273 << base::StringPrintf(" tid=%i", base::PlatformThread::CurrentId());
274 // Report all crashes in the exception handler if we wrap the callback.
275 // Note that this avoids having the VEH report a crash if an SEH earlier in
276 // the chain handles the exception.
277 ExceptionBarrier barrier
;
278 // Do not touch anything other than text/html.
279 bool is_interesting
= (format_etc
&& stgmed
&& stgmed
->pstm
&&
280 stgmed
->tymed
== TYMED_ISTREAM
&&
281 IsTextHtmlClipFormat(format_etc
->cfFormat
));
283 if (!is_interesting
) {
284 // Play back report progress so far.
286 return CallbackImpl::OnDataAvailable(flags
, size
, format_etc
, stgmed
);
291 clip_format_
= format_etc
->cfFormat
;
293 if (data_sniffer_
.is_undetermined()) {
294 bool force_determination
= !!(flags
&
295 (BSCF_LASTDATANOTIFICATION
| BSCF_DATAFULLYAVAILABLE
));
296 hr
= data_sniffer_
.ReadIntoCache(stgmed
->pstm
, force_determination
);
297 // If we don't have sufficient data to determine renderer type
298 // wait for the next data notification.
299 if (data_sniffer_
.is_undetermined())
303 DCHECK(!data_sniffer_
.is_undetermined());
305 if (data_sniffer_
.is_cache_valid()) {
306 hr
= MayPlayBack(flags
);
307 DCHECK(!data_sniffer_
.is_cache_valid());
309 hr
= CallbackImpl::OnDataAvailable(flags
, size
, format_etc
, stgmed
);
314 STDMETHODIMP
BSCBStorageBind::OnStopBinding(HRESULT hresult
, LPCWSTR error
) {
315 DVLOG(1) << __FUNCTION__
316 << base::StringPrintf(" tid=%i", base::PlatformThread::CurrentId());
317 // Report all crashes in the exception handler if we wrap the callback.
318 // Note that this avoids having the VEH report a crash if an SEH earlier in
319 // the chain handles the exception.
320 ExceptionBarrier barrier
;
322 HRESULT hr
= MayPlayBack(BSCF_LASTDATANOTIFICATION
);
323 hr
= CallbackImpl::OnStopBinding(hresult
, error
);
328 // Play back the cached data to the delegate. Normally this would happen
329 // when we have read enough data to determine the renderer. In this case
330 // we first play back the data from the cache and then go into a 'pass
331 // through' mode. In some cases we may end up getting OnStopBinding
332 // before we get a chance to determine. Also it's possible that the
333 // BindToStorage call will return before OnStopBinding is sent. Hence
334 // This is called from 3 places and it's important to maintain the
335 // exact sequence of calls.
336 // Once the data is played back, calling this again is a no op.
337 HRESULT
BSCBStorageBind::MayPlayBack(DWORD flags
) {
338 // Force renderer type determination if not already done since
339 // we want to play back data now.
340 data_sniffer_
.DetermineRendererType(true);
341 DCHECK(!data_sniffer_
.is_undetermined());
344 if (data_sniffer_
.is_chrome()) {
345 // Remember clip format. If we are switching to chrome, then in order
346 // to make mshtml return INET_E_TERMINATED_BIND and reissue navigation
347 // with the same bind context, we have to return a mime type that is
348 // special cased by mshtml.
349 static const CLIPFORMAT kMagicClipFormat
=
350 RegisterClipboardFormat(CFSTR_MIME_MPEG
);
351 clip_format_
= kMagicClipFormat
;
353 if (!saved_progress_
.empty()) {
354 for (ProgressVector::iterator i
= saved_progress_
.begin();
355 i
!= saved_progress_
.end(); i
++) {
357 // We don't really expect a race condition here but just for sake
358 // of completeness we check.
361 CallbackImpl::OnProgress(p
->progress(), p
->progress_max(),
362 p
->status_code(), p
->status_text());
366 saved_progress_
.clear();
370 if (data_sniffer_
.is_cache_valid()) {
371 if (data_sniffer_
.is_chrome()) {
372 base::win::ScopedComPtr
<BindContextInfo
> info
;
373 BindContextInfo::FromBindContext(bind_ctx_
, info
.Receive());
376 info
->SetToSwitch(data_sniffer_
.cache_
);
380 hr
= data_sniffer_
.DrainCache(delegate(),
381 flags
| BSCF_FIRSTDATANOTIFICATION
, clip_format_
);
382 DLOG_IF(WARNING
, INET_E_TERMINATED_BIND
!= hr
) << __FUNCTION__
<<
383 " mshtml OnDataAvailable returned: " << std::hex
<< hr
;
389 // We cache and suppress sending progress notifications till
390 // we get the first OnDataAvailable. This is to prevent
391 // mshtml from making up its mind about the mime type.
392 // However, this is the invasive part of the patch and
393 // could trip other software that's due to mistimed progress
394 // notifications. It is probably not a good idea to hide redirects
395 // and some cookie notifications.
397 // We only need to suppress data notifications like
398 // BINDSTATUS_MIMETYPEAVAILABLE,
399 // BINDSTATUS_CACHEFILENAMEAVAILABLE etc.
401 // This is an atempt to reduce the exposure by starting to
402 // cache only when we receive one of the interesting progress
404 bool BSCBStorageBind::ShouldCacheProgress(unsigned long status_code
) const {
405 // We need to cache progress notifications only if we haven't yet figured
406 // out which way the request is going.
407 if (data_sniffer_
.is_undetermined()) {
408 // If we are already caching then continue.
409 if (!saved_progress_
.empty())
411 // Start caching only if we see one of the interesting progress
413 switch (status_code
) {
414 case BINDSTATUS_BEGINDOWNLOADDATA
:
415 case BINDSTATUS_DOWNLOADINGDATA
:
416 case BINDSTATUS_USINGCACHEDCOPY
:
417 case BINDSTATUS_MIMETYPEAVAILABLE
:
418 case BINDSTATUS_CACHEFILENAMEAVAILABLE
:
419 case BINDSTATUS_SERVER_MIMETYPEAVAILABLE
: