diff options
Diffstat (limited to 'chrome_frame/urlmon_bind_status_callback.cc')
-rw-r--r-- | chrome_frame/urlmon_bind_status_callback.cc | 469 |
1 files changed, 237 insertions, 232 deletions
diff --git a/chrome_frame/urlmon_bind_status_callback.cc b/chrome_frame/urlmon_bind_status_callback.cc index ec51c57..cb90bf2 100644 --- a/chrome_frame/urlmon_bind_status_callback.cc +++ b/chrome_frame/urlmon_bind_status_callback.cc @@ -4,307 +4,312 @@ #include "chrome_frame/urlmon_bind_status_callback.h" +#include <mshtml.h> #include <shlguid.h> #include "base/logging.h" #include "base/string_util.h" +#include "base/utf_string_conversions.h" -CFUrlmonBindStatusCallback::CFUrlmonBindStatusCallback() - : only_buffer_(false), data_(new RequestData()) { - DLOG(INFO) << __FUNCTION__ << me(); -} - -CFUrlmonBindStatusCallback::~CFUrlmonBindStatusCallback() { - DLOG(INFO) << __FUNCTION__ << me(); -} +#include "chrome_frame/urlmon_moniker.h" +#include "chrome_tab.h" // NOLINT -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; -} +// A helper to given feed data to the specified |bscb| using +// CacheStream instance. +HRESULT CacheStream::BSCBFeedData(IBindStatusCallback* bscb, const char* data, + size_t size, CLIPFORMAT clip_format, + size_t flags) { + if (!bscb) { + NOTREACHED() << "invalid IBindStatusCallback"; + return E_INVALIDARG; + } -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() << "Failed to find registered bind status callback"; - ::RevokeBindStatusCallback(bind_ctx_, this); - bind_ctx_.Release(); - hr = E_UNEXPECTED; + // We can't use a CComObjectStackEx here since mshtml will hold + // onto the stream pointer. + CComObject<CacheStream>* cache_stream = NULL; + HRESULT hr = CComObject<CacheStream>::CreateInstance(&cache_stream); + if (FAILED(hr)) { + NOTREACHED(); + return hr; } + cache_stream->AddRef(); + cache_stream->Initialize(data, size); + + FORMATETC format_etc = { clip_format, NULL, DVASPECT_CONTENT, -1, + TYMED_ISTREAM }; + STGMEDIUM medium = {0}; + medium.tymed = TYMED_ISTREAM; + medium.pstm = cache_stream; + + hr = bscb->OnDataAvailable(flags, size, &format_etc, &medium); + + cache_stream->Release(); 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); - } +void CacheStream::Initialize(const char* cache, size_t size) { + cache_ = cache; + size_ = size; + position_ = 0; +} + +// Read is the only call that we expect. Return E_PENDING if there +// is no more data to serve. Otherwise this will result in a +// read with 0 bytes indicating that no more data is available. +STDMETHODIMP CacheStream::Read(void* pv, ULONG cb, ULONG* read) { + if (!pv || !read) + return E_INVALIDARG; + + // Default to E_PENDING to signal that this is a partial data. + HRESULT hr = E_PENDING; + if (position_ < size_) { + *read = std::min(size_ - position_, size_t(cb)); + memcpy(pv, cache_ + position_, *read); + position_ += *read; + hr = S_OK; } + 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); +///////////////////////////////////////////////////////////////////// + +bool SniffData::InitializeCache(const std::wstring& url) { + url_ = url; + renderer_type_ = UNDETERMINED; + + const int kAllocationSize = 32 * 1024; + HGLOBAL memory = GlobalAlloc(0, kAllocationSize); + HRESULT hr = CreateStreamOnHGlobal(memory, TRUE, cache_.Receive()); if (FAILED(hr)) { + GlobalFree(memory); NOTREACHED(); - return hr; + return false; } - binding_delegate_ = binding_delegate; - DCHECK_EQ(binding_delegate->m_dwRef, 1); - binding_delegate_->SetDelegate(binding); - - return delegate_->OnStartBinding(reserved, binding_delegate_); + return true; } -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; +HRESULT SniffData::ReadIntoCache(IStream* stream, bool force_determination) { + if (!stream) { + NOTREACHED(); + return E_INVALIDARG; } - 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()); - } + HRESULT hr = S_OK; + while (SUCCEEDED(hr)) { + const size_t kChunkSize = 4 * 1024; + char buffer[kChunkSize]; + DWORD read = 0; + hr = stream->Read(buffer, sizeof(buffer), &read); + if (read) { + DWORD written = 0; + cache_->Write(buffer, read, &written); + size_ += written; } - if (only_buffer_) { - hresult = INET_E_TERMINATED_BIND; - DLOG(INFO) << " - changed to INET_E_TERMINATED_BIND"; - } + if ((S_FALSE == hr) || !read) + break; } - // Hold a reference to ourselves while we release the bind context - // and disconnect the callback. - AddRef(); + if (force_determination || (size() >= kMaxSniffSize)) { + DetermineRendererType(); + } - HRESULT hr = delegate_->OnStopBinding(hresult, error); + return hr; +} - if (bind_ctx_) { - ::RevokeBindStatusCallback(bind_ctx_, this); - bind_ctx_.Release(); +HRESULT SniffData::DrainCache(IBindStatusCallback* bscb, DWORD bscf, + CLIPFORMAT clip_format) { + if (!is_cache_valid()) { + return S_OK; } - binding_delegate_.Release(); - - // After this call, this object might be gone. - Release(); + // Ideally we could just use the cache_ IStream implementation but + // can't use it here since we have to return E_PENDING for the + // last call + HGLOBAL memory = NULL; + HRESULT hr = GetHGlobalFromStream(cache_, &memory); + if (SUCCEEDED(hr) && memory) { + char* buffer = reinterpret_cast<char*>(GlobalLock(memory)); + hr = CacheStream::BSCBFeedData(bscb, buffer, size_, clip_format, bscf); + GlobalUnlock(memory); + } + size_ = 0; + cache_.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) { - DCHECK(format_etc); -#ifndef NDEBUG - wchar_t clip_fmt_name[MAX_PATH] = {0}; - if (format_etc) { - ::GetClipboardFormatNameW(format_etc->cfFormat, clip_fmt_name, - arraysize(clip_fmt_name)); +// Scan the buffer or OptIn URL list and decide if the renderer is +// to be switched +void SniffData::DetermineRendererType() { + if (is_undetermined()) { + if (IsOptInUrl(url_.c_str())) { + renderer_type_ = CHROME; + } else { + renderer_type_ = OTHER; + if (is_cache_valid() && cache_) { + HGLOBAL memory = NULL; + GetHGlobalFromStream(cache_, &memory); + char* buffer = reinterpret_cast<char*>(GlobalLock(memory)); + + std::wstring html_contents; + // TODO(joshia): detect and handle different content encodings + if (buffer && size_) { + UTF8ToWide(buffer, size_, &html_contents); + GlobalUnlock(memory); + } + + // Note that document_contents_ may have NULL characters in it. While + // browsers may handle this properly, we don't and will stop scanning + // for the XUACompat content value if we encounter one. + std::wstring xua_compat_content; + UtilGetXUACompatContentValue(html_contents, &xua_compat_content); + if (StrStrI(xua_compat_content.c_str(), kChromeContentPrefix)) { + renderer_type_ = CHROME; + } + } + } } - DLOG(INFO) << __FUNCTION__ << me() - << StringPrintf(" tid=%i original fmt=%ls", - PlatformThread::CurrentId(), clip_fmt_name); +} - if (!stgmed) { - NOTREACHED() << "Invalid STGMEDIUM received"; - return delegate_->OnDataAvailable(bscf, size, format_etc, stgmed); - } +///////////////////////////////////////////////////////////////////// - if (stgmed->tymed != TYMED_ISTREAM) { - DLOG(INFO) << "Not handling medium:" << stgmed->tymed; - return delegate_->OnDataAvailable(bscf, size, format_etc, stgmed); - } +HRESULT BSCBStorageBind::Initialize(IMoniker* moniker, IBindCtx* bind_ctx) { + DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", + PlatformThread::CurrentId()); - if (bscf & BSCF_FIRSTDATANOTIFICATION) { - DLOG(INFO) << "first data notification"; + HRESULT hr = AttachToBind(bind_ctx); + if (FAILED(hr)) { + NOTREACHED() << __FUNCTION__ << me() << "AttachToBind error: " << hr; + return hr; } -#endif - HRESULT hr = S_OK; - size_t bytes_read = 0; - if (!only_buffer_) { - hr = data_->DelegateDataRead(delegate_, bscf, size, format_etc, stgmed, - &bytes_read); + if (!delegate()) { + NOTREACHED() << __FUNCTION__ << me() << "No existing callback: " << hr; + return E_FAIL; } - DLOG(INFO) << __FUNCTION__ << StringPrintf(" - 0x%08x", hr); - if (hr == INET_E_TERMINATED_BIND) { - // Check if the content type is CF's mime type. - UINT cf_format = ::RegisterClipboardFormatW(kChromeMimeType); - bool override_bind_results = (format_etc->cfFormat == cf_format); - if (!override_bind_results) { - ScopedComPtr<IBrowserService> browser_service; - DoQueryService(SID_SShellBrowser, delegate_, browser_service.Receive()); - override_bind_results = (browser_service != NULL) && - CheckForCFNavigation(browser_service, false); - } + std::wstring url = GetActualUrlFromMoniker(moniker, bind_ctx, + std::wstring()); + data_sniffer_.InitializeCache(url); + return hr; +} - if (override_bind_results) { - // 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); - } - } +STDMETHODIMP BSCBStorageBind::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 (only_buffer_) { - data_->CacheAll(stgmed->pstm); - DCHECK(hr == S_OK); + HRESULT hr = S_OK; + if (data_sniffer_.is_undetermined()) { + Progress new_progress = { progress, progress_max, status_code, + status_text ? status_text : std::wstring() }; + saved_progress_.push_back(new_progress); + } else { + hr = CallbackImpl::OnProgress(progress, progress_max, status_code, + status_text); } return hr; } -HRESULT CFUrlmonBindStatusCallback::OnObjectAvailable(REFIID iid, - IUnknown* unk) { +// Refer to urlmon_moniker.h for explanation of how things work. +STDMETHODIMP BSCBStorageBind::OnDataAvailable(DWORD flags, DWORD size, + FORMATETC* format_etc, + STGMEDIUM* stgmed) { DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", PlatformThread::CurrentId()); - return delegate_->OnObjectAvailable(iid, unk); -} + if (!stgmed || !format_etc) { + DLOG(INFO) << __FUNCTION__ << me() << "Invalid stgmed or format_etc"; + return CallbackImpl::OnDataAvailable(flags, size, format_etc, stgmed); + } -// 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); -} + if ((stgmed->tymed != TYMED_ISTREAM) || !stgmed->pstm) { + DLOG(INFO) << __FUNCTION__ << me() << "stgmedium is not a valid stream"; + return CallbackImpl::OnDataAvailable(flags, size, format_etc, stgmed); + } -HRESULT CFUrlmonBindStatusCallback::BeginningTransaction(LPCWSTR url, - LPCWSTR headers, - DWORD reserved, - LPWSTR* additional_headers) { - DLOG(INFO) << __FUNCTION__ << me() << StringPrintf(" tid=%i", - PlatformThread::CurrentId()); + HRESULT hr = S_OK; + if (!clip_format_) + clip_format_ = format_etc->cfFormat; + + if (data_sniffer_.is_undetermined()) { + bool force_determination = !!(flags & + (BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE)); + hr = data_sniffer_.ReadIntoCache(stgmed->pstm, force_determination); + // If we don't have sufficient data to determine renderer type + // wait for the next data notification. + if (data_sniffer_.is_undetermined()) + return S_OK; + } + + DCHECK(!data_sniffer_.is_undetermined()); - ScopedComPtr<IHttpNegotiate> http_negotiate; - HRESULT hr = http_negotiate.QueryFrom(delegate_); - if (SUCCEEDED(hr)) { - hr = http_negotiate->BeginningTransaction(url, headers, reserved, - additional_headers); + if (data_sniffer_.is_cache_valid()) { + hr = MayPlayBack(flags); + DCHECK(!data_sniffer_.is_cache_valid()); } else { - hr = S_OK; + hr = CallbackImpl::OnDataAvailable(flags, size, format_etc, stgmed); } - 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", +STDMETHODIMP BSCBStorageBind::OnStopBinding(HRESULT hresult, LPCWSTR error) { + DLOG(INFO) << __FUNCTION__ << StringPrintf(" tid=%i", PlatformThread::CurrentId()); + HRESULT hr = MayPlayBack(BSCF_LASTDATANOTIFICATION); + return CallbackImpl::OnStopBinding(hresult, error); +} - data_->headers()->OnResponse(response_code, response_headers, - request_headers); +// Play back the cached data to the delegate. Normally this would happen +// when we have read enough data to determine the renderer. In this case +// we first play back the data from the cache and then go into a 'pass +// through' mode. In some cases we may end up getting OnStopBinding +// before we get a chance to determine. Also it's possible that the +// BindToStorage call will return before OnStopBinding is sent. Hence +// This is called from 3 places and it's important to maintain the +// exact sequence of calls. +// Once the data is played back, calling this again is a no op. +HRESULT BSCBStorageBind::MayPlayBack(DWORD flags) { + // Force renderer type determination if not already done since + // we want to play back data now. + data_sniffer_.DetermineRendererType(); + DCHECK(!data_sniffer_.is_undetermined()); - 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); + HRESULT hr = S_OK; + if (data_sniffer_.is_chrome()) { + // Remember clip format. If we are switching to chrome, then in order + // to make mshtml return INET_E_TERMINATED_BIND and reissue navigation + // with the same bind context, we have to return a mime type that is + // special cased by mshtml. + static const CLIPFORMAT kMagicClipFormat = + RegisterClipboardFormat(CFSTR_MIME_MPEG); + clip_format_ = kMagicClipFormat; } else { - hr = S_OK; + if (!saved_progress_.empty()) { + for (std::vector<Progress>::iterator i = saved_progress_.begin(); + i != saved_progress_.end(); i++) { + const wchar_t* status_text = i->status_text_.empty() ? + NULL : i->status_text_.c_str(); + CallbackImpl::OnProgress(i->progress_, i->progress_max_, + i->status_code_, status_text); + } + saved_progress_.clear(); + } } - 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); -} + if (data_sniffer_.is_cache_valid()) { + hr = data_sniffer_.DrainCache(delegate(), + flags | BSCF_FIRSTDATANOTIFICATION, clip_format_); + if (data_sniffer_.is_chrome()) + NavigationManager::AttachCFObject(bind_ctx_); + } -HRESULT CFUrlmonBindStatusCallback::GetSerializedClientCertContext( - BYTE** cert, - DWORD* cert_size) { - ScopedComPtr<IHttpNegotiate3> http_negotiate; - http_negotiate.QueryFrom(delegate_); - return http_negotiate->GetSerializedClientCertContext(cert, cert_size); + return hr; } |