diff options
author | stoyan@chromium.org <stoyan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-01 19:48:36 +0000 |
---|---|---|
committer | stoyan@chromium.org <stoyan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-01 19:48:36 +0000 |
commit | 3eb07da6e868e49e28cb5a6d8a57037f2c45a20e (patch) | |
tree | b9e05e275e38b176b1473e0f9e66075706ca238b /chrome_frame | |
parent | 55750b57e30ecc9f3657d1ebd08853e232ba4e23 (diff) | |
download | chromium_src-3eb07da6e868e49e28cb5a6d8a57037f2c45a20e.zip chromium_src-3eb07da6e868e49e28cb5a6d8a57037f2c45a20e.tar.gz chromium_src-3eb07da6e868e49e28cb5a6d8a57037f2c45a20e.tar.bz2 |
TEST=new tests added.
Review URL: http://codereview.chromium.org/545093
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37728 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
22 files changed, 1496 insertions, 1016 deletions
diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc index 553b46f..202128b 100644 --- a/chrome_frame/chrome_active_document.cc +++ b/chrome_frame/chrome_active_document.cc @@ -54,6 +54,7 @@ bool g_first_launch_by_process_ = true; ChromeActiveDocument::ChromeActiveDocument() : first_navigation_(true), is_automation_client_reused_(false) { + url_fetcher_.set_frame_busting(false); memset(&navigation_info_, 0, sizeof(navigation_info_)); } @@ -68,7 +69,7 @@ HRESULT ChromeActiveDocument::FinalConstruct() { DLOG(INFO) << "Reusing automation client instance from " << cached_document; DCHECK(automation_client_.get() != NULL); - automation_client_->Reinitialize(this); + automation_client_->Reinitialize(this, &url_fetcher_); is_automation_client_reused_ = true; } else { // The FinalConstruct implementation in the ChromeFrameActivexBase class @@ -102,6 +103,8 @@ ChromeActiveDocument::~ChromeActiveDocument() { if (find_dialog_.IsWindow()) { find_dialog_.DestroyWindow(); } + // ChromeFramePlugin + Base::Uninitialize(); } // Override DoVerb @@ -174,10 +177,6 @@ STDMETHODIMP ChromeActiveDocument::IsDirty() { return S_FALSE; } -bool ChromeActiveDocument::is_frame_busting_enabled() { - return false; -} - void ChromeActiveDocument::OnAutomationServerReady() { Base::OnAutomationServerReady(); Base::GiveFocusToChrome(); @@ -231,18 +230,7 @@ STDMETHODIMP ChromeActiveDocument::Load(BOOL fully_avalable, } if (!is_chrome_protocol) { - CComObject<UrlmonUrlRequest>* new_request = NULL; - CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); - new_request->AddRef(); - - if (SUCCEEDED(new_request->ConnectToExistingMoniker(moniker_name, - bind_context, - url))) { - base_url_request_.swap(&new_request); - DCHECK(new_request == NULL); - } else { - new_request->Release(); - } + url_fetcher_.UseMonikerForUrl(moniker_name, bind_context, url); } UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.FullTabLaunchType", @@ -883,12 +871,15 @@ bool ChromeActiveDocument::LaunchUrl(const std::wstring& url, } } - if (!is_automation_client_reused_ && - !InitializeAutomation(GetHostProcessName(false), L"", IsIEInPrivate())) { - return false; - } + if (is_automation_client_reused_) + return true; - return true; + automation_client_->SetUrlFetcher(&url_fetcher_); + + if (InitializeAutomation(GetHostProcessName(false), L"", IsIEInPrivate())) + return true; + + return false; } HRESULT ChromeActiveDocument::SetPageFontSize(const GUID* cmd_group_guid, diff --git a/chrome_frame/chrome_active_document.h b/chrome_frame/chrome_active_document.h index 45df8b7..dc6511d 100644 --- a/chrome_frame/chrome_active_document.h +++ b/chrome_frame/chrome_active_document.h @@ -238,10 +238,6 @@ END_EXEC_COMMAND_MAP() bool PreProcessContextMenu(HMENU menu); bool HandleContextMenuCommand(UINT cmd, const IPC::ContextMenuParams& params); - // Should connections initiated by this class try to block - // responses served with the X-Frame-Options header? - bool is_frame_busting_enabled(); - // ChromeFramePlugin overrides. virtual void OnAutomationServerReady(); diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index 280c214..7f844cf 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -183,8 +183,9 @@ '../testing/gtest.gyp:gtest', '../third_party/libxml/libxml.gyp:libxml', '../third_party/libxslt/libxslt.gyp:libxslt', - 'chrome_frame_strings', + 'chrome_frame_ie', 'chrome_frame_npapi', + 'chrome_frame_strings', 'npchrome_frame', 'xulrunner_sdk', ], @@ -203,6 +204,7 @@ 'test/test_server.cc', 'test/test_server.h', 'test/test_server_test.cc', + 'test/url_request_test.cc', 'chrome_frame_automation.cc', 'chrome_frame_histograms.h', 'chrome_frame_histograms.cc', @@ -581,6 +583,7 @@ 'extra_system_apis.h', 'urlmon_url_request.cc', 'urlmon_url_request.h', + 'urlmon_url_request_private.h', 'urlmon_upload_data_stream.cc', 'urlmon_upload_data_stream.h', 'vtable_patch_manager.cc', diff --git a/chrome_frame/chrome_frame_activex.cc b/chrome_frame/chrome_frame_activex.cc index e8fa70a..295ceaa 100644 --- a/chrome_frame/chrome_frame_activex.cc +++ b/chrome_frame/chrome_frame_activex.cc @@ -42,6 +42,9 @@ ChromeFrameActivex::~ChromeFrameActivex() { DCHECK(onload_.size() == 0); DCHECK(onreadystatechanged_.size() == 0); DCHECK(onextensionready_.size() == 0); + + // ChromeFramePlugin::Uninitialize() + Base::Uninitialize(); } LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam, @@ -315,6 +318,8 @@ HRESULT ChromeFrameActivex::IOleObject_SetClientSite( profile_name.assign(profile_name_arg, profile_name_arg.Length()); } + url_fetcher_.set_frame_busting(!is_privileged_); + automation_client_->SetUrlFetcher(&url_fetcher_); if (!InitializeAutomation(profile_name, chrome_extra_arguments, IsIEInPrivate())) { return E_FAIL; diff --git a/chrome_frame/chrome_frame_activex_base.h b/chrome_frame/chrome_frame_activex_base.h index d7abc01..af71a11 100644 --- a/chrome_frame/chrome_frame_activex_base.h +++ b/chrome_frame/chrome_frame_activex_base.h @@ -165,8 +165,7 @@ class ATL_NO_VTABLE ChromeFrameActivexBase : // NOLINT public: ChromeFrameActivexBase() - : ready_state_(READYSTATE_UNINITIALIZED), - worker_thread_("ChromeFrameWorker_Thread") { + : ready_state_(READYSTATE_UNINITIALIZED) { m_bWindowOnly = TRUE; } @@ -467,59 +466,16 @@ END_MSG_MAP() web_browser2->put_Visible(VARIANT_TRUE); } - virtual void OnRequestStart(int tab_handle, int request_id, - const IPC::AutomationURLRequest& request_info) { - // The worker thread may have been stopped. This could happen if the - // ActiveX instance was reused. - if (!worker_thread_.message_loop()) { - base::Thread::Options options; - options.message_loop_type = MessageLoop::TYPE_UI; - worker_thread_.StartWithOptions(options); - worker_thread_.message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &Base::OnWorkerStart)); - } - - scoped_refptr<CComObject<UrlmonUrlRequest> > request; - if (base_url_request_.get() && - GURL(base_url_request_->url()) == GURL(request_info.url)) { - request.swap(base_url_request_); - } else { - CComObject<UrlmonUrlRequest>* new_request = NULL; - CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); - request = new_request; - } - - DCHECK(request.get() != NULL); - - if (request->Initialize( - automation_client_.get(), tab_handle, request_id, request_info.url, - request_info.method, request_info.referrer, - request_info.extra_request_headers, request_info.upload_data.get(), - static_cast<T*>(this)->is_frame_busting_enabled())) { - request->set_worker_thread(&worker_thread_); - // If Start is successful, it will add a self reference. - request->Start(); - request->set_parent_window(m_hWnd); - } - } - - virtual void OnRequestRead(int tab_handle, int request_id, - int bytes_to_read) { - automation_client_->ReadRequest(request_id, bytes_to_read); - } - - virtual void OnRequestEnd(int tab_handle, int request_id, - const URLRequestStatus& status) { - automation_client_->RemoveRequest(request_id, true); - } - virtual void OnDownloadRequestInHost(int tab_handle, int request_id) { DLOG(INFO) << "TODO: Let the host browser handle this download"; - PluginUrlRequest* request = automation_client_->LookupRequest(request_id); - if (request) { - static_cast<UrlmonUrlRequest*>(request)->TransferToHost(doc_site_); + ScopedComPtr<IBindCtx> bind_context; + ScopedComPtr<IMoniker> moniker; + url_fetcher_.StealMonikerFromRequest(request_id, moniker.Receive()); + if (moniker) { + ::CreateBindCtx(0, bind_context.Receive()); + DCHECK(bind_context); + NavigateBrowserToMoniker(doc_site_, moniker, NULL, bind_context, NULL); } - automation_client_->RemoveRequest(request_id, false); } virtual void OnSetCookieAsync(int tab_handle, const GURL& url, @@ -569,26 +525,7 @@ END_MSG_MAP() LRESULT OnDestroy(UINT message, WPARAM wparam, LPARAM lparam, BOOL& handled) { // NO_LINT - if (worker_thread_.message_loop()) { - if (automation_client_.get()) - automation_client_->CleanupRequests(); - - worker_thread_.message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &Base::OnWorkerStop)); - - MSG msg = {0}; - while (GetMessage(&msg, NULL, WM_USER, - WM_WORKER_THREAD_UNINITIALIZED_MSG)) { - if (msg.hwnd == m_hWnd && - msg.message == WM_WORKER_THREAD_UNINITIALIZED_MSG) { - break; - } - - TranslateMessage(&msg); - DispatchMessage(&msg); - } - worker_thread_.Stop(); - } + DLOG(INFO) << __FUNCTION__; return 0; } @@ -1111,17 +1048,6 @@ END_MSG_MAP() } protected: - // The following functions are called to initialize and uninitialize the - // worker thread. - void OnWorkerStart() { - CoInitialize(NULL); - } - - void OnWorkerStop() { - CoUninitialize(); - PostMessage(WM_WORKER_THREAD_UNINITIALIZED_MSG, 0, 0); - } - ScopedBstr url_; ScopedComPtr<IOleDocumentSite> doc_site_; @@ -1142,10 +1068,9 @@ END_MSG_MAP() EventHandlers onprivatemessage_; EventHandlers onextensionready_; - // The UrlmonUrlRequest instance instantiated for downloading the base URL. - scoped_refptr<CComObject<UrlmonUrlRequest> > base_url_request_; - - base::Thread worker_thread_; + // Handle network requests when host network stack is used. Passed to the + // automation client on initialization. + UrlmonUrlRequestManager url_fetcher_; }; #endif // CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc index 482fb2b..5d05068 100644 --- a/chrome_frame/chrome_frame_automation.cc +++ b/chrome_frame/chrome_frame_automation.cc @@ -408,6 +408,7 @@ ChromeFrameAutomationClient::ChromeFrameAutomationClient() handle_top_level_requests_(false), tab_handle_(-1), external_tab_cookie_(NULL), + url_fetcher_(NULL), navigate_after_initialization_(false) { } @@ -493,8 +494,10 @@ void ChromeFrameAutomationClient::Uninitialize() { tab_ = NULL; // scoped_refptr::Release } - // Clean up any outstanding requests - CleanupRequests(); + if (url_fetcher_) { + // Clean up any outstanding requests + url_fetcher_->StopAllRequests(); + } // Wait for the background thread to exit. ReleaseAutomationServer(); @@ -850,10 +853,54 @@ void ChromeFrameAutomationClient::InitializeComplete( // kind of beings. // By default we marshal the IPC message to the main/GUI thread and from there // we safely invoke chrome_frame_delegate_->OnMessageReceived(msg). + + +bool ChromeFrameAutomationClient::ProcessUrlRequestMessage(TabProxy* tab, + const IPC::Message& msg, bool ui_thread) { + // Either directly call appropriate url_fetcher function + // or postpone call to the UI thread. + bool invoke = ui_thread || thread_safe_url_fetcher_; + uint16 msg_type = msg.type(); + switch (msg_type) { + default: + return false; + + case AutomationMsg_RequestStart::ID: + if (invoke) + AutomationMsg_RequestStart::Dispatch(&msg, url_fetcher_, + &PluginUrlRequestManager::StartUrlRequest); + break; + + case AutomationMsg_RequestRead::ID: + if (invoke) + AutomationMsg_RequestRead::Dispatch(&msg, url_fetcher_, + &PluginUrlRequestManager::ReadUrlRequest); + break; + + case AutomationMsg_RequestEnd::ID: + if (invoke) + AutomationMsg_RequestEnd::Dispatch(&msg, url_fetcher_, + &PluginUrlRequestManager::EndUrlRequest); + break; + } + + if (!invoke) { + PostTask(FROM_HERE, NewRunnableMethod(this, + &ChromeFrameAutomationClient::ProcessUrlRequestMessage, + tab, msg, true)); + } + + return true; +} + void ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab, const IPC::Message& msg) { DCHECK(tab == tab_.get()); + // Quickly process network related messages. + if (url_fetcher_ && ProcessUrlRequestMessage(tab, msg, false)) + return; + // Early check to avoid needless marshaling if (chrome_frame_delegate_ == NULL) return; @@ -998,119 +1045,9 @@ void ChromeFrameAutomationClient::PrintTab() { tab_->PrintAsync(); } -// IPC:Message::Sender implementation -bool ChromeFrameAutomationClient::Send(IPC::Message* msg) { - if (automation_server_) { - return automation_server_->Send(msg); - } - return false; -} - -bool ChromeFrameAutomationClient::AddRequest(PluginUrlRequest* request) { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - - if (!request) { - NOTREACHED(); - return false; - } - -#ifndef NDEBUG - RequestMap::const_iterator it = request_map_.find(request->id()); - scoped_refptr<PluginUrlRequest> other(request_map_.end() == it ? - NULL : (*it).second); - DCHECK(other.get() == NULL); -#endif - request_map_[request->id()] = request; - return true; -} - -bool ChromeFrameAutomationClient::ReadRequest( - int request_id, int bytes_to_read) { - bool result = false; - PluginUrlRequest* request = LookupRequest(request_id); - if (request) - result = request->Read(bytes_to_read); - - return result; -} - -void ChromeFrameAutomationClient::RemoveRequest(PluginUrlRequest* request) { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - - // We check if the request pointer passed in is actually present in the map - // before going ahead and deleting it. This avoids the issue where we would - // incorrectly delete a different request with the same request id. - if (IsValidRequest(request)) { - request_map_.erase(request->id()); - } -} - -void ChromeFrameAutomationClient::RemoveRequest(int request_id, bool abort) { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - PluginUrlRequest* request = LookupRequest(request_id); - if (request) { - if (abort) { - // The request object will get removed asynchronously. - request->Stop(); - } else { - request_map_.erase(request_id); - } - } -} - -PluginUrlRequest* ChromeFrameAutomationClient::LookupRequest( - int request_id) const { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - PluginUrlRequest* request = NULL; - RequestMap::const_iterator it = request_map_.find(request_id); - if (request_map_.end() != it) - request = (*it).second; - return request; -} - -bool ChromeFrameAutomationClient::IsValidRequest( - PluginUrlRequest* request) const { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - bool is_valid = false; - // if request is invalid then request->id() won't work - // hence perform reverse map lookup for validity of the - // request pointer. - if (request) { - for (RequestMap::const_iterator it = request_map_.begin(); - it != request_map_.end(); it++) { - if (request == (*it).second) { - is_valid = true; - break; - } - } - } - - return is_valid; -} - -void ChromeFrameAutomationClient::CleanupRequests() { - DCHECK_EQ(PlatformThread::CurrentId(), ui_thread_id_); - - std::vector<scoped_refptr<PluginUrlRequest> > request_list; - // We copy the pending requests into a temporary vector as the Stop - // function in the request could also try to delete the request from - // the request map and the iterator could end up being invalid. - RequestMap::iterator index = request_map_.begin(); - while (index != request_map_.end()) { - PluginUrlRequest* request = (*index).second; - DCHECK(request != NULL); - request_list.push_back(request); - index++; - } - request_map_.clear(); - - for (unsigned int index = 0; index < request_list.size(); ++index) { - request_list[index]->Stop(); - } -} - bool ChromeFrameAutomationClient::Reinitialize( - ChromeFrameDelegate* delegate) { + ChromeFrameDelegate* delegate, + PluginUrlRequestManager* url_fetcher) { if (!tab_.get() || !::IsWindow(chrome_window_)) { NOTREACHED(); DLOG(WARNING) << "ChromeFrameAutomationClient instance reused " @@ -1123,8 +1060,9 @@ bool ChromeFrameAutomationClient::Reinitialize( return false; } - CleanupRequests(); + url_fetcher_->StopAllRequests(); chrome_frame_delegate_ = delegate; + SetUrlFetcher(url_fetcher); SetParentWindow(NULL); return true; } @@ -1146,6 +1084,43 @@ void ChromeFrameAutomationClient::SetPageFontSize( return; } - Send(new AutomationMsg_SetPageFontSize(0, tab_handle_, font_size)); + automation_server_->Send( + new AutomationMsg_SetPageFontSize(0, tab_handle_, font_size)); +} + + +////////////////////////////////////////////////////////////////////////// +// PluginUrlRequestDelegate implementation. +// Forward network related responses to Chrome. + +void ChromeFrameAutomationClient::OnResponseStarted(int request_id, + const char* mime_type, const char* headers, int size, + base::Time last_modified, const std::string& peristent_cookies, + const std::string& redirect_url, int redirect_status) { + const IPC::AutomationURLResponse response = { + mime_type, + headers ? headers : "", + size, + last_modified, + peristent_cookies, + redirect_url, + redirect_status + }; + + automation_server_->Send(new AutomationMsg_RequestStarted(0, + tab_->handle(), request_id, response)); +} + +void ChromeFrameAutomationClient::OnReadComplete(int request_id, + const void* buffer, int len) { + std::string data(reinterpret_cast<const char*>(buffer), len); + automation_server_->Send(new AutomationMsg_RequestData(0, tab_->handle(), + request_id, data)); +} + +void ChromeFrameAutomationClient::OnResponseEnd(int request_id, + const URLRequestStatus& status) { + automation_server_->Send(new AutomationMsg_RequestEnd(0, tab_->handle(), + request_id, status)); } diff --git a/chrome_frame/chrome_frame_automation.h b/chrome_frame/chrome_frame_automation.h index aa62948..4cfa187 100644 --- a/chrome_frame/chrome_frame_automation.h +++ b/chrome_frame/chrome_frame_automation.h @@ -148,7 +148,8 @@ class ProxyFactory { class ChromeFrameAutomationClient : public CWindowImpl<ChromeFrameAutomationClient>, public TaskMarshallerThroughWindowsMessages<ChromeFrameAutomationClient>, - public PluginRequestHandler, + public base::RefCountedThreadSafe<ChromeFrameAutomationClient>, + public PluginUrlRequestDelegate, public TabProxy::TabProxyDelegate, public ProxyFactory::LaunchDelegate { public: @@ -227,18 +228,6 @@ class ChromeFrameAutomationClient // the whole tab. void PrintTab(); - // PluginRequestHandler - bool AddRequest(PluginUrlRequest* request); - void RemoveRequest(PluginUrlRequest* request); - virtual bool Send(IPC::Message* msg); - - // URL request related - bool ReadRequest(int request_id, int bytes_to_read); - void RemoveRequest(int request_id, bool abort); - PluginUrlRequest* LookupRequest(int request_id) const; - bool IsValidRequest(PluginUrlRequest* request) const; - void CleanupRequests(); - void set_use_chrome_network(bool use_chrome_network) { use_chrome_network_ = use_chrome_network; } @@ -258,7 +247,8 @@ class ChromeFrameAutomationClient // Called if the same instance of the ChromeFrameAutomationClient object // is reused. - bool Reinitialize(ChromeFrameDelegate* chrome_frame_delegate); + bool Reinitialize(ChromeFrameDelegate* chrome_frame_delegate, + PluginUrlRequestManager* url_fetcher); // Attaches an existing external tab to this automation client instance. void AttachExternalTab(intptr_t external_tab_cookie); @@ -280,8 +270,6 @@ class ChromeFrameAutomationClient void InitializeComplete(AutomationLaunchResult result); private: - typedef std::map<int, scoped_refptr<PluginUrlRequest> > RequestMap; - // Usage: From bkgnd thread invoke: // CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_, // ChromeFrameDelegate::Something, @@ -323,9 +311,6 @@ class ChromeFrameAutomationClient // Keeps track of the version of Chrome we're talking to. std::string automation_server_version_; - // Map of outstanding requests - RequestMap request_map_; - typedef enum InitializationState { UNINITIALIZED = 0, INITIALIZING, @@ -346,6 +331,31 @@ class ChromeFrameAutomationClient bool navigate_after_initialization_; ChromeFrameLaunchParams chrome_launch_params_; + + // When host network stack is used, this object is in charge of + // handling network requests. + PluginUrlRequestManager* url_fetcher_; + bool thread_safe_url_fetcher_; + + bool ProcessUrlRequestMessage(TabProxy* tab, const IPC::Message& msg, + bool ui_thread); + + // PluginUrlRequestDelegate implementation. Simply adds tab's handle + // as parameter and forwards to Chrome via IPC. + virtual void OnResponseStarted(int request_id, const char* mime_type, + const char* headers, int size, base::Time last_modified, + const std::string& peristent_cookies, const std::string& redirect_url, + int redirect_status); + virtual void OnReadComplete(int request_id, const void* buffer, int len); + virtual void OnResponseEnd(int request_id, const URLRequestStatus& status); + + public: + void SetUrlFetcher(PluginUrlRequestManager* url_fetcher) { + DCHECK(url_fetcher != NULL); + url_fetcher_ = url_fetcher; + thread_safe_url_fetcher_ = url_fetcher->IsThreadSafe(); + url_fetcher_->set_delegate(this); + } }; #endif // CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_ diff --git a/chrome_frame/chrome_frame_npapi.cc b/chrome_frame/chrome_frame_npapi.cc index b2a8acb..8f21f93 100644 --- a/chrome_frame/chrome_frame_npapi.cc +++ b/chrome_frame/chrome_frame_npapi.cc @@ -269,9 +269,13 @@ bool ChromeFrameNPAPI::Initialize(NPMIMEType mime_type, NPP instance, if (chrome_network_arg_set) automation_client_->set_use_chrome_network(chrome_network_arg); - } + // Setup Url fetcher. + url_fetcher_.set_NPPInstance(instance_); + url_fetcher_.set_frame_busting(!is_privileged_); + automation_client_->SetUrlFetcher(&url_fetcher_); + // TODO(joshia): Initialize navigation here and send proxy config as // part of LaunchSettings /* @@ -434,12 +438,7 @@ void ChromeFrameNPAPI::UrlNotify(const char* url, NPReason reason, npapi::PopPopupsEnabledState(instance_); } - // It is now safe to release the additional reference on the request - NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); - if (request) { - request->Stop(); - request->Release(); - } + url_fetcher_.UrlNotify(url, reason, notify_data); } void ChromeFrameNPAPI::OnAcceleratorPressed(int tab_handle, @@ -513,32 +512,6 @@ void ChromeFrameNPAPI::OnOpenURL(int tab_handle, npapi::GetURLNotify(instance_, url.spec().c_str(), target.c_str(), NULL); } -void ChromeFrameNPAPI::OnRequestStart(int tab_handle, int request_id, - const IPC::AutomationURLRequest& request) { - scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_)); - DCHECK(new_request); - if (new_request->Initialize(automation_client_.get(), tab_handle, - request_id, request.url, request.method, - request.referrer, request.extra_request_headers, - request.upload_data.get(), !is_privileged_)) { - if (new_request->Start()) { - // Keep additional reference on request for NPSTREAM - // This will be released in NPP_UrlNotify - new_request->AddRef(); - } - } -} - -void ChromeFrameNPAPI::OnRequestRead(int tab_handle, int request_id, - int bytes_to_read) { - automation_client_->ReadRequest(request_id, bytes_to_read); -} - -void ChromeFrameNPAPI::OnRequestEnd(int tab_handle, int request_id, - const URLRequestStatus& status) { - automation_client_->RemoveRequest(request_id, true); -} - void ChromeFrameNPAPI::OnSetCookieAsync(int tab_handle, const GURL& url, const std::string& cookie) { // Use the newer NPAPI way if available @@ -1438,24 +1411,6 @@ bool ChromeFrameNPAPI::GetBrowserIncognitoMode() { return incognito_mode; } -NPAPIUrlRequest* ChromeFrameNPAPI::ValidateRequest( - NPP instance, void* notify_data) { - ChromeFrameNPAPI* plugin_instance = - ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); - if (plugin_instance) { - return plugin_instance->RequestFromNotifyData(notify_data); - } - - return NULL; -} - -NPAPIUrlRequest* ChromeFrameNPAPI::RequestFromNotifyData( - void* notify_data) const { - NPAPIUrlRequest* request = reinterpret_cast<NPAPIUrlRequest*>(notify_data); - DCHECK(request ? automation_client_->IsValidRequest(request) : 1); - return request; -} - bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd, const IPC::ContextMenuParams& params) { if (cmd == IDC_ABOUT_CHROME_FRAME) { @@ -1463,3 +1418,21 @@ bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd, } return false; } + +bool ChromeFrameNPAPI::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stream_type) { + return url_fetcher_.NewStream(type, stream, seekable, stream_type); +} + +int32 ChromeFrameNPAPI::WriteReady(NPStream* stream) { + return url_fetcher_.WriteReady(stream); +} + +int32 ChromeFrameNPAPI::Write(NPStream* stream, int32 offset, int32 len, + void* buffer) { + return url_fetcher_.Write(stream, offset, len, buffer); +} + +NPError ChromeFrameNPAPI::DestroyStream(NPStream* stream, NPReason reason) { + return url_fetcher_.DestroyStream(stream, reason); +} diff --git a/chrome_frame/chrome_frame_npapi.h b/chrome_frame/chrome_frame_npapi.h index 45b4028..c5e17cb 100644 --- a/chrome_frame/chrome_frame_npapi.h +++ b/chrome_frame/chrome_frame_npapi.h @@ -65,6 +65,9 @@ class ChromeFrameNPAPI void UrlNotify(const char* url, NPReason reason, void* notify_data); bool NewStream(NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stream_type); + int32 WriteReady(NPStream* stream); + int32 Write(NPStream* stream, int32 offset, int32 len, void* buffer); + NPError DestroyStream(NPStream* stream, NPReason reason); void Print(NPPrint* print_info); @@ -99,10 +102,6 @@ class ChromeFrameNPAPI // which represents our plugin class. static ChromeFrameNPAPI* ChromeFrameInstanceFromNPObject(void* object); - // Return a UrlRequest instance associated with the given instance and - // stream combination. - static NPAPIUrlRequest* ValidateRequest(NPP instance, void* notify_data); - BEGIN_MSG_MAP(ChromeFrameNPAPI) MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) CHAIN_MSG_MAP(Base) @@ -137,12 +136,6 @@ END_MSG_MAP() const std::string& message, const std::string& origin, const std::string& target); - virtual void OnRequestStart(int tab_handle, int request_id, - const IPC::AutomationURLRequest& request); - virtual void OnRequestRead(int tab_handle, int request_id, - int bytes_to_read); - virtual void OnRequestEnd(int tab_handle, int request_id, - const URLRequestStatus& status); virtual void OnSetCookieAsync(int tab_handle, const GURL& url, const std::string& cookie); @@ -283,9 +276,6 @@ END_MSG_MAP() // Host function to compile-time asserts over members of this class. static void CompileAsserts(); - // Get request from the stream notify data - NPAPIUrlRequest* RequestFromNotifyData(void* notify_data) const; - static LRESULT CALLBACK DropKillFocusHook(int code, WPARAM wparam, LPARAM lparam); // NO_LINT @@ -342,6 +332,8 @@ END_MSG_MAP() // The value of src property keeping the current URL. std::string src_; + // Used to fetch network resources when host network stack is in use. + NPAPIUrlRequestManager url_fetcher_; }; #endif // CHROME_FRAME_CHROME_FRAME_NPAPI_H_ diff --git a/chrome_frame/chrome_frame_npapi_entrypoints.cc b/chrome_frame/chrome_frame_npapi_entrypoints.cc index 17a0953..ef5af01 100644 --- a/chrome_frame/chrome_frame_npapi_entrypoints.cc +++ b/chrome_frame/chrome_frame_npapi_entrypoints.cc @@ -119,27 +119,23 @@ NPError NPP_SetWindow(NPP instance, NPWindow* window_info) { NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stream_type) { - NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( - instance, stream->notifyData); - if (url_request) { - if (!url_request->OnStreamCreated(type, stream)) - return NPERR_GENERIC_ERROR; + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance == NULL) { + return NPERR_INVALID_INSTANCE_ERROR; } - // We need to return the requested stream mode if we are returning a success - // code. If we don't do this it causes Opera to blow up. - *stream_type = NP_NORMAL; - return NPERR_NO_ERROR; + return plugin_instance->NewStream(type, stream, seekable, stream_type); } NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { - NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( - instance, stream->notifyData); - if (url_request) { - url_request->OnStreamDestroyed(reason); + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance == NULL) { + return NPERR_INVALID_INSTANCE_ERROR; } - return NPERR_NO_ERROR; + return plugin_instance->DestroyStream(stream, reason); } NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { @@ -163,24 +159,25 @@ NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { int32 NPP_WriteReady(NPP instance, NPStream* stream) { static const int kMaxBytesForPluginConsumption = 0x7FFFFFFF; - NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( - instance, stream->notifyData); - if (url_request) { - return url_request->OnWriteReady(); + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) { + return kMaxBytesForPluginConsumption; } - return kMaxBytesForPluginConsumption; + return plugin_instance->WriteReady(stream); } int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer) { - NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( - instance, stream->notifyData); - if (url_request) { - return url_request->OnWrite(buffer, len); - } + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) + return len; - return len; + return plugin_instance->Write(stream, offset, len, buffer); } void NPP_URLNotify(NPP instance, const char* url, NPReason reason, diff --git a/chrome_frame/http_negotiate.cc b/chrome_frame/http_negotiate.cc index 5ca49d4..240d9ff 100644 --- a/chrome_frame/http_negotiate.cc +++ b/chrome_frame/http_negotiate.cc @@ -52,6 +52,45 @@ BEGIN_VTABLE_PATCHES(IInternetProtocolSink) HttpNegotiatePatch::ReportProgress) END_VTABLE_PATCHES() +class SimpleBindStatusCallback : public CComObjectRootEx<CComSingleThreadModel>, + public IBindStatusCallback { + public: + BEGIN_COM_MAP(SimpleBindStatusCallback) + COM_INTERFACE_ENTRY(IBindStatusCallback) + END_COM_MAP() + + // IBindStatusCallback implementation + STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding) { + return E_NOTIMPL; + } + + STDMETHOD(GetPriority)(LONG* priority) { + return E_NOTIMPL; + } + STDMETHOD(OnLowResource)(DWORD reserved) { + return E_NOTIMPL; + } + + STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress, + ULONG status_code, LPCWSTR status_text) { + return E_NOTIMPL; + } + STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error) { + return E_NOTIMPL; + } + + STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info) { + return E_NOTIMPL; + } + + STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc, + STGMEDIUM* storage) { + return E_NOTIMPL; + } + STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object) { + return E_NOTIMPL; + } +}; HttpNegotiatePatch::HttpNegotiatePatch() { } @@ -65,10 +104,9 @@ bool HttpNegotiatePatch::Initialize() { DLOG(WARNING) << __FUNCTION__ << " called more than once."; return true; } - - // Use our UrlmonUrlRequest class as we need a temporary object that + // Use our SimpleBindStatusCallback class as we need a temporary object that // implements IBindStatusCallback. - CComObjectStackEx<UrlmonUrlRequest> request; + CComObjectStackEx<SimpleBindStatusCallback> request; ScopedComPtr<IBindCtx> bind_ctx; HRESULT hr = CreateAsyncBindCtx(0, &request, NULL, bind_ctx.Receive()); DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx"; diff --git a/chrome_frame/npapi_url_request.cc b/chrome_frame/npapi_url_request.cc index 33052a1..d7d9a2f 100644 --- a/chrome_frame/npapi_url_request.cc +++ b/chrome_frame/npapi_url_request.cc @@ -33,6 +33,39 @@ #include "chrome_frame/np_browser_functions.h" #include "net/base/net_errors.h" +class NPAPIUrlRequest : public PluginUrlRequest { + public: + explicit NPAPIUrlRequest(NPP instance); + ~NPAPIUrlRequest(); + + virtual bool Start(); + virtual void Stop(); + virtual bool Read(int bytes_to_read); + + // Called from NPAPI + bool OnStreamCreated(const char* mime_type, NPStream* stream); + NPError OnStreamDestroyed(NPReason reason); + int OnWriteReady(); + int OnWrite(void* buffer, int len); + + // Thread unsafe implementation of ref counting, since + // this will be called on the plugin UI thread only. + virtual unsigned long API_CALL AddRef(); + virtual unsigned long API_CALL Release(); + + private: + PluginUrlRequestDelegate* delegate_; + unsigned long ref_count_; + NPP instance_; + NPStream* stream_; + size_t pending_read_size_; + URLRequestStatus status_; + + PlatformThreadId thread_; + static int instance_count_; + DISALLOW_COPY_AND_ASSIGN(NPAPIUrlRequest); +}; + int NPAPIUrlRequest::instance_count_ = 0; NPAPIUrlRequest::NPAPIUrlRequest(NPP instance) @@ -62,9 +95,7 @@ bool NPAPIUrlRequest::Start() { NOTREACHED() << "PluginUrlRequest only supports 'GET'/'POST'"; } - if (NPERR_NO_ERROR == result) { - request_handler()->AddRequest(this); - } else { + if (NPERR_NO_ERROR != result) { int os_error = net::ERR_FAILED; switch (result) { case NPERR_INVALID_URL: @@ -74,7 +105,8 @@ bool NPAPIUrlRequest::Start() { break; } - OnResponseEnd(URLRequestStatus(URLRequestStatus::FAILED, os_error)); + delegate_->OnResponseEnd(id(), + URLRequestStatus(URLRequestStatus::FAILED, os_error)); return false; } @@ -88,10 +120,6 @@ void NPAPIUrlRequest::Stop() { npapi::DestroyStream(instance_, stream_, NPRES_USER_BREAK); stream_ = NULL; } - - request_handler()->RemoveRequest(this); - if (!status_.is_io_pending()) - OnResponseEnd(status_); } bool NPAPIUrlRequest::Read(int bytes_to_read) { @@ -105,13 +133,13 @@ bool NPAPIUrlRequest::OnStreamCreated(const char* mime_type, NPStream* stream) { // TODO(iyengar) // Add support for passing persistent cookies and information about any URL // redirects to Chrome. - OnResponseStarted(mime_type, stream->headers, stream->end, + delegate_->OnResponseStarted(id(), mime_type, stream->headers, stream->end, base::Time::FromTimeT(stream->lastmodified), std::string(), std::string(), 0); return true; } -void NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) { +NPError NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) { URLRequestStatus::Status status = URLRequestStatus::FAILED; switch (reason) { case NPRES_DONE: @@ -128,6 +156,9 @@ void NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) { status_.set_os_error(net::ERR_CONNECTION_CLOSED); break; } + + delegate_->OnResponseEnd(id(), status_); + return NPERR_NO_ERROR; } int NPAPIUrlRequest::OnWriteReady() { @@ -136,7 +167,7 @@ int NPAPIUrlRequest::OnWriteReady() { int NPAPIUrlRequest::OnWrite(void* buffer, int len) { pending_read_size_ = 0; - OnReadComplete(buffer, len); + delegate_->OnReadComplete(id(), buffer, len); return len; } @@ -155,3 +186,123 @@ STDMETHODIMP_(ULONG) NPAPIUrlRequest::Release() { return ret; } +NPAPIUrlRequestManager::NPAPIUrlRequestManager() : instance_(NULL) { +} + +NPAPIUrlRequestManager::~NPAPIUrlRequestManager() { + StopAll(); +} + +// PluginUrlRequestManager implementation +bool NPAPIUrlRequestManager::IsThreadSafe() { + return false; +} + +void NPAPIUrlRequestManager::StartRequest(int request_id, + const IPC::AutomationURLRequest& request_info) { + scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_)); + DCHECK(new_request); + if (new_request->Initialize(this, request_id, request_info.url, + request_info.method, request_info.referrer, + request_info.extra_request_headers, request_info.upload_data.get(), + enable_frame_busting_)) { + // Add to map. + DCHECK(NULL == request_map_[request_id].get()); + request_map_[request_id] = new_request; + if (new_request->Start()) { + // Keep additional reference on request for NPSTREAM + // This will be released in NPP_UrlNotify + new_request->AddRef(); + } + } +} + +void NPAPIUrlRequestManager::ReadRequest(int request_id, int bytes_to_read) { + scoped_refptr<NPAPIUrlRequest> request = request_map_[request_id]; + DCHECK(request.get()); + if (request) + request->Read(bytes_to_read); +} + +void NPAPIUrlRequestManager::EndRequest(int request_id) { + scoped_refptr<NPAPIUrlRequest> request = request_map_[request_id]; + if (request) + request->Stop(); +} + +void NPAPIUrlRequestManager::StopAll() { + for (RequestMap::iterator index = request_map_.begin(); + index != request_map_.end(); + ++index) { + scoped_refptr<NPAPIUrlRequest> request = (*index).second; + request->Stop(); + } +} + +// PluginRequestDelegate implementation. +// Callbacks from NPAPIUrlRequest. Simply forward to the delegate. +void NPAPIUrlRequestManager::OnResponseStarted(int request_id, + const char* mime_type, const char* headers, int size, + base::Time last_modified, const std::string& peristent_cookies, + const std::string& redirect_url, int redirect_status) { + delegate_->OnResponseStarted(request_id, mime_type, headers, size, + last_modified, peristent_cookies, redirect_url, redirect_status); +} + +void NPAPIUrlRequestManager::OnReadComplete(int request_id, const void* buffer, + int len) { + delegate_->OnReadComplete(request_id, buffer, len); +} + +void NPAPIUrlRequestManager::OnResponseEnd(int request_id, + const URLRequestStatus& status) { + // Delete from map. + RequestMap::iterator it = request_map_.find(request_id); + DCHECK(request_map_.end() != it); + scoped_refptr<NPAPIUrlRequest> request = (*it).second; + request_map_.erase(it); + + // Inform delegate unless canceled. + if (status.status() != URLRequestStatus::CANCELED) + delegate_->OnResponseEnd(request_id, status); +} + +// Notifications from browser. Find the NPAPIUrlRequest and forward to it. +bool NPAPIUrlRequestManager::NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stream_type) { + NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); + DCHECK(request_map_.find(request->id()) != request_map_.end()); + // We need to return the requested stream mode if we are returning a success + // code. If we don't do this it causes Opera to blow up. + *stream_type = NP_NORMAL; + return request->OnStreamCreated(type, stream); +} + +int32 NPAPIUrlRequestManager::WriteReady(NPStream* stream) { + NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); + DCHECK(request_map_.find(request->id()) != request_map_.end()); + return request->OnWriteReady(); +} + +int32 NPAPIUrlRequestManager::Write(NPStream* stream, int32 offset, + int32 len, void* buffer) { + NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); + DCHECK(request_map_.find(request->id()) != request_map_.end()); + return request->OnWrite(buffer, len); +} + +NPError NPAPIUrlRequestManager::DestroyStream(NPStream* stream, + NPReason reason) { + NPAPIUrlRequest* request = RequestFromNotifyData(stream->notifyData); + DCHECK(request_map_.find(request->id()) != request_map_.end()); + return request->OnStreamDestroyed(reason); +} + +void NPAPIUrlRequestManager::UrlNotify(const char* url, NPReason reason, + void* notify_data) { + NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); + if (request) { + request->Stop(); + request->Release(); + } +} diff --git a/chrome_frame/npapi_url_request.h b/chrome_frame/npapi_url_request.h index 9151fcf..87e40a9 100644 --- a/chrome_frame/npapi_url_request.h +++ b/chrome_frame/npapi_url_request.h @@ -5,40 +5,58 @@ #ifndef CHROME_FRAME_NPAPI_URL_REQUEST_H_ #define CHROME_FRAME_NPAPI_URL_REQUEST_H_ +#include <map> + #include "base/platform_thread.h" #include "chrome_frame/plugin_url_request.h" #include "third_party/WebKit/WebCore/bridge/npapi.h" -class NPAPIUrlRequest : public PluginUrlRequest { +class NPAPIUrlRequest; +class NPAPIUrlRequestManager : public PluginUrlRequestManager, + public PluginUrlRequestDelegate { public: - explicit NPAPIUrlRequest(NPP instance); - ~NPAPIUrlRequest(); - - virtual bool Start(); - virtual void Stop(); - virtual bool Read(int bytes_to_read); - - // Called from NPAPI - bool OnStreamCreated(const char* mime_type, NPStream* stream); - void OnStreamDestroyed(NPReason reason); - int OnWriteReady(); - int OnWrite(void* buffer, int len); - - // Thread unsafe implementation of ref counting, since - // this will be called on the plugin UI thread only. - virtual unsigned long API_CALL AddRef(); - virtual unsigned long API_CALL Release(); + NPAPIUrlRequestManager(); + ~NPAPIUrlRequestManager(); + + void set_NPPInstance(NPP instance) { + instance_ = instance; + } + + // Notifications from the browser. We find the appropriate NPAPIUrlRequest + // and forward the call. + bool NewStream(NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stream_type); + int32 WriteReady(NPStream* stream); + int32 Write(NPStream* stream, int32 offset, int32 len, void* buffer); + NPError DestroyStream(NPStream* stream, NPReason reason); + void UrlNotify(const char* url, NPReason reason, void* notify_data); private: - unsigned long ref_count_; - NPP instance_; - NPStream* stream_; - size_t pending_read_size_; - URLRequestStatus status_; + // PluginUrlRequestManager implementation. Called from AutomationClient. + virtual bool IsThreadSafe(); + virtual void StartRequest(int request_id, + const IPC::AutomationURLRequest& request_info); + virtual void ReadRequest(int request_id, int bytes_to_read); + virtual void EndRequest(int request_id); + virtual void StopAll(); + + // Outstanding requests map. + typedef std::map<int, scoped_refptr<NPAPIUrlRequest> > RequestMap; + RequestMap request_map_; + + // PluginUrlRequestDelegate implementation. Forwards back to delegate. + virtual void OnResponseStarted(int request_id, const char* mime_type, + const char* headers, int size, + base::Time last_modified, const std::string& peristent_cookies, + const std::string& redirect_url, int redirect_status); + virtual void OnReadComplete(int request_id, const void* buffer, int len); + virtual void OnResponseEnd(int request_id, const URLRequestStatus& status); + + static inline NPAPIUrlRequest* RequestFromNotifyData(void* notify_data) { + return reinterpret_cast<NPAPIUrlRequest*>(notify_data); + } - PlatformThreadId thread_; - static int instance_count_; - DISALLOW_COPY_AND_ASSIGN(NPAPIUrlRequest); + NPP instance_; }; #endif // CHROME_FRAME_NPAPI_URL_REQUEST_H_ diff --git a/chrome_frame/plugin_url_request.cc b/chrome_frame/plugin_url_request.cc index 325ac44..ac746c6 100644 --- a/chrome_frame/plugin_url_request.cc +++ b/chrome_frame/plugin_url_request.cc @@ -8,24 +8,20 @@ #include "chrome_frame/np_browser_functions.h" PluginUrlRequest::PluginUrlRequest() - : request_handler_(NULL), - tab_(0), + : delegate_(NULL), remote_request_id_(-1), post_data_len_(0), - status_(URLRequestStatus::IO_PENDING), - frame_busting_enabled_(false) { + enable_frame_busting_(false) { } PluginUrlRequest::~PluginUrlRequest() { } -bool PluginUrlRequest::Initialize(PluginRequestHandler* request_handler, - int tab, int remote_request_id, const std::string& url, - const std::string& method, const std::string& referrer, - const std::string& extra_headers, net::UploadData* upload_data, - bool enable_frame_busting) { - request_handler_ = request_handler; - tab_ = tab; +bool PluginUrlRequest::Initialize(PluginUrlRequestDelegate* delegate, + int remote_request_id, const std::string& url, const std::string& method, + const std::string& referrer, const std::string& extra_headers, + net::UploadData* upload_data, bool enable_frame_busting) { + delegate_ = delegate; remote_request_id_ = remote_request_id; url_ = url; method_ = method; @@ -49,38 +45,7 @@ bool PluginUrlRequest::Initialize(PluginRequestHandler* request_handler, } } - frame_busting_enabled_ = enable_frame_busting; + enable_frame_busting_ = enable_frame_busting; return true; } - -void PluginUrlRequest::OnResponseStarted(const char* mime_type, - const char* headers, int size, base::Time last_modified, - const std::string& persistent_cookies, - const std::string& redirect_url, int redirect_status) { - const IPC::AutomationURLResponse response = { - mime_type, - headers ? headers : "", - size, - last_modified, - persistent_cookies, - redirect_url, - redirect_status - }; - request_handler_->Send(new AutomationMsg_RequestStarted(0, tab_, - remote_request_id_, response)); -} - -void PluginUrlRequest::OnResponseEnd(const URLRequestStatus& status) { - DCHECK(!status.is_io_pending()); - DCHECK(status.is_success() || status.os_error()); - request_handler_->Send(new AutomationMsg_RequestEnd(0, tab_, - remote_request_id_, status)); -} - -void PluginUrlRequest::OnReadComplete(const void* buffer, int len) { - std::string data(reinterpret_cast<const char*>(buffer), len); - request_handler_->Send(new AutomationMsg_RequestData(0, tab_, - remote_request_id_, data)); -} - diff --git a/chrome_frame/plugin_url_request.h b/chrome_frame/plugin_url_request.h index 4993770..efd6741 100644 --- a/chrome_frame/plugin_url_request.h +++ b/chrome_frame/plugin_url_request.h @@ -18,43 +18,11 @@ #include "net/url_request/url_request_status.h" class PluginUrlRequest; +class PluginUrlRequestDelegate; +class PluginUrlRequestManager; -// Interface for a class that keeps a collection of outstanding -// reqeusts and offers an outgoing channel. -class PluginRequestHandler - : public IPC::Message::Sender, - public base::RefCountedThreadSafe<PluginRequestHandler> { +class DECLSPEC_NOVTABLE PluginUrlRequestDelegate { public: - virtual bool AddRequest(PluginUrlRequest* request) = 0; - virtual void RemoveRequest(PluginUrlRequest* request) = 0; -}; - -// A reference counting solution whose method's are compatible with -// scoped_refptr and COM's IUnknown. Don't cast this object directly over to -// IUnknown though since IUnknown's first method is QueryInterface. -class UrlRequestReference { - public: - virtual unsigned long API_CALL AddRef() = 0; // NOLINT - virtual unsigned long API_CALL Release() = 0; // NOLINT -}; - -class PluginUrlRequest : public UrlRequestReference { - public: - PluginUrlRequest(); - ~PluginUrlRequest(); - - bool Initialize(PluginRequestHandler* handler, int tab, - int remote_request_id, const std::string& url, - const std::string& method, const std::string& referrer, - const std::string& extra_headers, - net::UploadData* upload_data, - bool intercept_frame_options); - - // Called in response to automation IPCs - virtual bool Start() = 0; - virtual void Stop() = 0; - virtual bool Read(int bytes_to_read) = 0; - // Persistent cookies are read from the host browser and passed off to Chrome // These cookies are sent when we receive a response for every URL request // initiated by Chrome. Ideally we should only send cookies for the top level @@ -63,17 +31,77 @@ class PluginUrlRequest : public UrlRequestReference { // Additionally cookies for a URL should be sent once for the page. This // is not done now as it is difficult to track URLs, specifically if they // are redirected, etc. - void OnResponseStarted(const char* mime_type, const char* headers, int size, - base::Time last_modified, const std::string& peristent_cookies, - const std::string& redirect_url, int redirect_status); + virtual void OnResponseStarted(int request_id, const char* mime_type, + const char* headers, int size, base::Time last_modified, + const std::string& peristent_cookies, const std::string& redirect_url, + int redirect_status) = 0; + virtual void OnReadComplete(int request_id, const void* buffer, int len) = 0; + virtual void OnResponseEnd(int request_id, const URLRequestStatus& status) = 0; + protected: + PluginUrlRequestDelegate() {} + ~PluginUrlRequestDelegate() {} +}; - void OnReadComplete(const void* buffer, int len); - void OnResponseEnd(const URLRequestStatus& status); +class DECLSPEC_NOVTABLE PluginUrlRequestManager { + public: + PluginUrlRequestManager() : delegate_(NULL), enable_frame_busting_(true) {} + virtual ~PluginUrlRequestManager() {} + + void set_frame_busting(bool enable) { + enable_frame_busting_ = enable; + } + + virtual void set_delegate(PluginUrlRequestDelegate* delegate) { + delegate_ = delegate; + } + + virtual bool IsThreadSafe() = 0; + + // These are called directly from Automation Client when network related + // automation messages are received from Chrome. + // Strip 'tab' handle and forward to the virtual methods implemented by + // derived classes. + void StartUrlRequest(int tab, int request_id, + const IPC::AutomationURLRequest& request_info) { + StartRequest(request_id, request_info); + } + + void ReadUrlRequest(int tab, int request_id, int bytes_to_read) { + ReadRequest(request_id, bytes_to_read); + } + + void EndUrlRequest(int tab, int request_id, const URLRequestStatus& s) { + EndRequest(request_id); + } - PluginRequestHandler* request_handler() const { - return request_handler_; + void StopAllRequests() { + StopAll(); } + protected: + PluginUrlRequestDelegate* delegate_; + bool enable_frame_busting_; + + private: + virtual void StartRequest(int request_id, + const IPC::AutomationURLRequest& request_info) = 0; + virtual void ReadRequest(int request_id, int bytes_to_read) = 0; + virtual void EndRequest(int request_id) = 0; + virtual void StopAll() = 0; +}; + +// Used as base class. Holds Url request properties (url, method, referrer..) +class PluginUrlRequest { + public: + PluginUrlRequest(); + ~PluginUrlRequest(); + + bool Initialize(PluginUrlRequestDelegate* delegate, + int remote_request_id, const std::string& url, const std::string& method, + const std::string& referrer, const std::string& extra_headers, + net::UploadData* upload_data, bool enable_frame_busting_); + + // Accessors. int id() const { return remote_request_id_; } @@ -86,10 +114,6 @@ class PluginUrlRequest : public UrlRequestReference { return method_; } - void set_method(const std::string& new_method) { - method_ = new_method; - } - const std::string& referrer() const { return referrer_; } @@ -102,6 +126,7 @@ class PluginUrlRequest : public UrlRequestReference { return post_data_len_; } + protected: HRESULT get_upload_data(IStream** ret) { DCHECK(ret); if (!upload_data_.get()) @@ -111,26 +136,19 @@ class PluginUrlRequest : public UrlRequestReference { return S_OK; } + void set_url(const std::string& url) { + url_ = url; + } + void ClearPostData() { upload_data_.Release(); post_data_len_ = 0; } - bool is_done() const { - return (URLRequestStatus::IO_PENDING != status_); - } - - void set_url(const std::string& url) { - url_ = url; - } - - protected: void SendData(); - bool frame_busting_enabled_; + bool enable_frame_busting_; - private: - scoped_refptr<PluginRequestHandler> request_handler_; - int tab_; + PluginUrlRequestDelegate* delegate_; int remote_request_id_; uint64 post_data_len_; std::string url_; @@ -138,7 +156,6 @@ class PluginUrlRequest : public UrlRequestReference { std::string referrer_; std::string extra_headers_; ScopedComPtr<IStream> upload_data_; - URLRequestStatus::Status status_; }; #endif // CHROME_FRAME_PLUGIN_URL_REQUEST_H_ diff --git a/chrome_frame/test/chrome_frame_test_utils.h b/chrome_frame/test/chrome_frame_test_utils.h index 277fc52..57e356c 100644 --- a/chrome_frame/test/chrome_frame_test_utils.h +++ b/chrome_frame/test/chrome_frame_test_utils.h @@ -130,6 +130,16 @@ class TimedMsgLoop { MessageLoopForUI loop_; }; +// Saves typing. It's somewhat hard to create a wrapper around +// testing::InvokeWithoutArgs since it returns a +// non-public (testing::internal) type. +#define QUIT_LOOP(loop) testing::InvokeWithoutArgs(\ + CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::Quit)) + +#define QUIT_LOOP_SOON(loop, seconds) testing::InvokeWithoutArgs(\ + CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::QuitAfter, \ + seconds)) + // Launches IE as a COM server and returns the corresponding IWebBrowser2 // interface pointer. // Returns S_OK on success. diff --git a/chrome_frame/test/chrome_frame_unittests.cc b/chrome_frame/test/chrome_frame_unittests.cc index 2795de5..f343ff2c 100644 --- a/chrome_frame/test/chrome_frame_unittests.cc +++ b/chrome_frame/test/chrome_frame_unittests.cc @@ -21,7 +21,6 @@ #include "chrome_frame/test/chrome_frame_unittests.h" #include "chrome_frame/chrome_frame_automation.h" #include "chrome_frame/chrome_frame_delegate.h" -#include "chrome_frame/crash_reporting/vectored_handler-impl.h" #include "chrome_frame/test/chrome_frame_test_utils.h" #include "chrome_frame/test_utils.h" #include "chrome/common/chrome_switches.h" @@ -892,16 +891,6 @@ template <> struct RunnableMethodTraits<chrome_frame_test::TimedMsgLoop> { void ReleaseCallee(chrome_frame_test::TimedMsgLoop* obj) {} }; -// Saves typing. It's somewhat hard to create a wrapper around -// testing::InvokeWithoutArgs since it returns a -// non-public (testing::internal) type. -#define QUIT_LOOP(loop) testing::InvokeWithoutArgs(\ - CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::Quit)) - -#define QUIT_LOOP_SOON(loop, seconds) testing::InvokeWithoutArgs(\ - CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::QuitAfter, \ - seconds)) - // We mock ChromeFrameDelegate only. The rest is with real AutomationProxy TEST(CFACWithChrome, CreateTooFast) { MockCFDelegate cfd; diff --git a/chrome_frame/test/url_request_test.cc b/chrome_frame/test/url_request_test.cc new file mode 100644 index 0000000..e095aa5 --- /dev/null +++ b/chrome_frame/test/url_request_test.cc @@ -0,0 +1,152 @@ +// 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 <atlbase.h>
+#include <atlcom.h>
+#include "app/win_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gmock_mutant.h"
+
+#include "chrome_frame/urlmon_url_request.h"
+#include "chrome_frame/urlmon_url_request_private.h"
+#include "chrome_frame/test/chrome_frame_test_utils.h"
+#include "chrome_frame/test/http_server.h"
+using testing::CreateFunctor;
+
+const int kChromeFrameLongNavigationTimeoutInSeconds = 10;
+
+class MockUrlDelegate : public PluginUrlRequestDelegate {
+ public:
+ MOCK_METHOD8(OnResponseStarted, void(int request_id, const char* mime_type,
+ const char* headers, int size,
+ base::Time last_modified, const std::string& peristent_cookies,
+ const std::string& redirect_url, int redirect_status));
+ MOCK_METHOD3(OnReadComplete, void(int request_id, const void* buffer,
+ int len));
+ MOCK_METHOD2(OnResponseEnd, void(int request_id,
+ const URLRequestStatus& status));
+
+ static bool ImplementsThreadSafeReferenceCounting() {
+ return false;
+ }
+ void AddRef() {}
+ void Release() {}
+
+ void PostponeReadRequest(chrome_frame_test::TimedMsgLoop* loop,
+ UrlmonUrlRequest* request, int bytes_to_read) {
+ loop->PostDelayedTask(FROM_HERE, NewRunnableMethod(this,
+ &MockUrlDelegate::Read, request, bytes_to_read), 0);
+ }
+
+ private:
+ void Read(UrlmonUrlRequest* request, int bytes_to_read) {
+ request->Read(bytes_to_read);
+ }
+};
+
+// Simplest UrlmonUrlRequest. Retrieve a file from local web server.
+TEST(UrlmonUrlRequestTest, Simple1) {
+ MockUrlDelegate mock;
+ ChromeFrameHTTPServer server;
+ chrome_frame_test::TimedMsgLoop loop;
+ win_util::ScopedCOMInitializer init_com;
+ CComObjectStackEx<UrlmonUrlRequest> request;
+
+ server.SetUp();
+ request.AddRef();
+ request.Initialize(&mock, 1, // request_id
+ server.Resolve(L"chrome_frame_window_open.html").spec(),
+ "get",
+ "", // referrer
+ "", // extra request
+ NULL, // upload data
+ true); // frame busting
+
+ EXPECT_CALL(mock, OnResponseStarted(1, testing::_, testing::_, testing::_,
+ testing::_, testing::_, testing::_,
+ testing::_))
+ .Times(1)
+ .WillOnce(testing::IgnoreResult(testing::InvokeWithoutArgs(CreateFunctor(
+ &request, &UrlmonUrlRequest::Read, 512))));
+
+
+ EXPECT_CALL(mock, OnReadComplete(1, testing::_, testing::Gt(0)))
+ .Times(testing::AtLeast(1))
+ .WillRepeatedly(testing::InvokeWithoutArgs(CreateFunctor(&mock,
+ &MockUrlDelegate::PostponeReadRequest, &loop, &request, 64)));
+
+ EXPECT_CALL(mock, OnResponseEnd(1, testing::_))
+ .Times(1)
+ .WillOnce(QUIT_LOOP_SOON(loop, 2));
+
+ request.Start();
+ loop.RunFor(kChromeFrameLongNavigationTimeoutInSeconds);
+ request.Release();
+ server.TearDown();
+}
+
+// Simplest test - retrieve file from local web server.
+TEST(UrlmonUrlRequestManagerTest, Simple1) {
+ MockUrlDelegate mock;
+ ChromeFrameHTTPServer server;
+ chrome_frame_test::TimedMsgLoop loop;
+ server.SetUp();
+ scoped_ptr<UrlmonUrlRequestManager> mgr(new UrlmonUrlRequestManager());
+ mgr->set_delegate(&mock);
+ IPC::AutomationURLRequest r1 = {
+ server.Resolve(L"chrome_frame_window_open.html").spec(), "get" };
+
+ EXPECT_CALL(mock, OnResponseStarted(1, testing::_, testing::_, testing::_,
+ testing::_, testing::_, testing::_, testing::_))
+ .Times(1)
+ .WillOnce(testing::InvokeWithoutArgs(CreateFunctor(mgr.get(),
+ &PluginUrlRequestManager::ReadUrlRequest, 0, 1, 512)));
+
+ EXPECT_CALL(mock, OnReadComplete(1, testing::_, testing::Gt(0)))
+ .Times(testing::AtLeast(1))
+ .WillRepeatedly(testing::InvokeWithoutArgs(CreateFunctor(mgr.get(),
+ &PluginUrlRequestManager::ReadUrlRequest, 0, 1, 2)));
+
+ EXPECT_CALL(mock, OnResponseEnd(1, testing::_))
+ .Times(1)
+ .WillOnce(QUIT_LOOP_SOON(loop, 2));
+
+ mgr->StartUrlRequest(0, 1, r1);
+ loop.RunFor(kChromeFrameLongNavigationTimeoutInSeconds);
+ mgr.reset();
+ server.TearDown();
+
+}
+
+TEST(UrlmonUrlRequestManagerTest, Abort1) {
+ MockUrlDelegate mock;
+ ChromeFrameHTTPServer server;
+ chrome_frame_test::TimedMsgLoop loop;
+ server.SetUp();
+ scoped_ptr<UrlmonUrlRequestManager> mgr(new UrlmonUrlRequestManager());
+ mgr->set_delegate(&mock);
+ IPC::AutomationURLRequest r1 = {
+ server.Resolve(L"chrome_frame_window_open.html").spec(), "get" };
+
+ EXPECT_CALL(mock, OnResponseStarted(1, testing::_, testing::_, testing::_,
+ testing::_, testing::_, testing::_, testing::_))
+ .Times(1)
+ .WillOnce(testing::DoAll(
+ testing::InvokeWithoutArgs(CreateFunctor(mgr.get(),
+ &PluginUrlRequestManager::EndUrlRequest, 0, 1, URLRequestStatus())),
+ testing::InvokeWithoutArgs(CreateFunctor(mgr.get(),
+ &PluginUrlRequestManager::ReadUrlRequest, 0, 1, 2))));
+
+ EXPECT_CALL(mock, OnReadComplete(1, testing::_, testing::Gt(0)))
+ .Times(0);
+
+ EXPECT_CALL(mock, OnResponseEnd(1, testing::_))
+ .Times(1)
+ .WillOnce(QUIT_LOOP_SOON(loop, 2));
+
+ mgr->StartUrlRequest(0, 1, r1);
+ loop.RunFor(kChromeFrameLongNavigationTimeoutInSeconds);
+ mgr.reset();
+ server.TearDown();
+}
diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc index bf78269..42b1000 100644 --- a/chrome_frame/urlmon_url_request.cc +++ b/chrome_frame/urlmon_url_request.cc @@ -11,9 +11,9 @@ #include "base/string_util.h" #include "base/logging.h" #include "base/message_loop.h" -#include "chrome_frame/chrome_frame_activex_base.h" #include "chrome_frame/extra_system_apis.h" #include "chrome_frame/html_utils.h" +#include "chrome_frame/urlmon_url_request_private.h" #include "chrome_frame/urlmon_upload_data_stream.h" #include "chrome_frame/utils.h" #include "net/http/http_util.h" @@ -21,7 +21,6 @@ static const LARGE_INTEGER kZero = {0}; static const ULARGE_INTEGER kUnsignedZero = {0}; -int UrlmonUrlRequest::instance_count_ = 0; // This class wraps the IBindCtx interface which is passed in when our active // document object is instantiated. The IBindCtx interface is created on @@ -222,14 +221,25 @@ class WrappedBindContext : public IBindCtx, ScopedComPtr<IMarshal> standard_marshal_; }; +STDMETHODIMP UrlmonUrlRequest::SendStream::Write(const void * buffer, + ULONG size, + ULONG* size_written) { + DCHECK(request_); + int size_to_write = static_cast<int>( + std::min(static_cast<ULONG>(MAXINT), size)); + request_->delegate_->OnReadComplete(request_->id(), buffer, + size_to_write); + if (size_written) + *size_written = size_to_write; + return S_OK; +} + +int UrlmonUrlRequest::instance_count_ = 0; + UrlmonUrlRequest::UrlmonUrlRequest() : pending_read_size_(0), - status_(URLRequestStatus::FAILED, net::ERR_FAILED), - thread_(PlatformThread::CurrentId()), - redirect_status_(0), - parent_window_(NULL), - worker_thread_(NULL), - ignore_redirect_stop_binding_error_(false) { + thread_(NULL), + parent_window_(NULL) { DLOG(INFO) << StringPrintf("Created request. Obj: %X", this) << " Count: " << ++instance_count_; } @@ -240,141 +250,100 @@ UrlmonUrlRequest::~UrlmonUrlRequest() { } bool UrlmonUrlRequest::Start() { - DCHECK_EQ(PlatformThread::CurrentId(), thread_); - - if (!worker_thread_ || !worker_thread_->message_loop()) { - NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized"; - return false; - } - - Create(HWND_MESSAGE); - if (!IsWindow()) { - NOTREACHED() << "Failed to create urlmon message window: " - << GetLastError(); - return false; + thread_ = PlatformThread::CurrentId(); + status_.Start(); + HRESULT hr = StartAsyncDownload(); + if (FAILED(hr)) { + status_.set_result(URLRequestStatus::FAILED, HresultToNetError(hr)); + NotifyDelegateAndDie(); } - - // Take a self reference to maintain COM lifetime. This will be released - // in OnFinalMessage - AddRef(); - request_handler()->AddRequest(this); - - worker_thread_->message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::StartAsync)); - return true; } void UrlmonUrlRequest::Stop() { - DCHECK_EQ(PlatformThread::CurrentId(), thread_); - - if (!worker_thread_ || !worker_thread_->message_loop()) { - NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized"; - return; - } - - // We can remove the request from the map safely here if it is still valid. - // There is an additional reference on the UrlmonUrlRequest instance which - // is released when the task scheduled by the EndRequest function executes. - request_handler()->RemoveRequest(this); - - worker_thread_->message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::StopAsync)); -} - -void UrlmonUrlRequest::StartAsync() { - DCHECK(worker_thread_ != NULL); - - status_.set_status(URLRequestStatus::IO_PENDING); - HRESULT hr = StartAsyncDownload(); - if (FAILED(hr)) { - // Do not call EndRequest() here since it will attempt to free references - // that have not been established. - status_.set_os_error(HresultToNetError(hr)); - status_.set_status(URLRequestStatus::FAILED); - DLOG(ERROR) << "StartAsyncDownload failed"; - EndRequest(); - return; - } -} + DCHECK_EQ(thread_, PlatformThread::CurrentId()); + DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); + Status::State state = status_.get_state(); + switch (state) { + case Status::WORKING: + status_.Cancel(); + binding_->Abort(); + break; -void UrlmonUrlRequest::StopAsync() { - DCHECK(worker_thread_ != NULL); + case Status::ABORTING: + status_.Cancel(); + break; - if (binding_) { - binding_->Abort(); - } else { - status_.set_status(URLRequestStatus::CANCELED); - status_.set_os_error(net::ERR_FAILED); - EndRequest(); + case Status::DONE: + status_.Cancel(); + NotifyDelegateAndDie(); + break; } } -void UrlmonUrlRequest::OnFinalMessage(HWND window) { - m_hWnd = NULL; - // Release the outstanding reference in the context of the UI thread to - // ensure that our instance gets deleted in the same thread which created it. - Release(); -} - bool UrlmonUrlRequest::Read(int bytes_to_read) { - DCHECK_EQ(PlatformThread::CurrentId(), thread_); - - DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this); - - if (!worker_thread_ || !worker_thread_->message_loop()) { - NOTREACHED() << __FUNCTION__ << " Urlmon request thread not initialized"; + DCHECK_EQ(thread_, PlatformThread::CurrentId()); + // Re-entrancy check. Thou shall not call Read() while processOnReadComplete!! + DCHECK_EQ(0, pending_read_size_); + if (pending_read_size_ != 0) return false; - } - - worker_thread_->message_loop()->PostTask( - FROM_HERE, NewRunnableMethod(this, &UrlmonUrlRequest::ReadAsync, - bytes_to_read)); - return true; -} -void UrlmonUrlRequest::TransferToHost(IUnknown* host) { - DCHECK_EQ(PlatformThread::CurrentId(), thread_); - DCHECK(host); - DCHECK(moniker_); - if (moniker_) { - ScopedComPtr<IBindCtx> bind_context; - CreateBindCtx(0, bind_context.Receive()); - DCHECK(bind_context); - NavigateBrowserToMoniker(host, moniker_, NULL, bind_context, NULL); - moniker_.Release(); + DCHECK((status_.get_state() != Status::DONE) == (binding_ != NULL)); + if (status_.get_state() == Status::ABORTING) { + return true; } -} -void UrlmonUrlRequest::ReadAsync(int bytes_to_read) { // Send cached data if available. CComObjectStackEx<SendStream> send_stream; send_stream.Initialize(this); size_t bytes_copied = 0; - if (cached_data_.is_valid() && cached_data_.Read(&send_stream, bytes_to_read, - &bytes_copied)) { + if (delegate_ && cached_data_.is_valid() && + cached_data_.Read(&send_stream, bytes_to_read, &bytes_copied)) { DLOG(INFO) << StringPrintf("URL: %s Obj: %X - bytes read from cache: %d", - url().c_str(), this, bytes_copied); - return; + url().c_str(), this, bytes_copied); + return true; } - // if the request is finished or there's nothing more to read - // then end the request - if (!status_.is_io_pending() || !binding_) { - DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished. Status: %d", - url().c_str(), this, status_.status()); - EndRequest(); - return; + if (status_.get_state() == Status::WORKING) { + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + "- Read pending for: " << bytes_to_read; + pending_read_size_ = bytes_to_read; + } else { + DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished.", + url().c_str(), this); + NotifyDelegateAndDie(); } - pending_read_size_ = bytes_to_read; - DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << - "- Read pending for: " << bytes_to_read; + return true; } -STDMETHODIMP UrlmonUrlRequest::OnStartBinding( - DWORD reserved, IBinding *binding) { +HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker, + IBindCtx* context, + const std::wstring& url) { + if (!moniker || url.empty()) { + NOTREACHED() << "Invalid arguments"; + return E_INVALIDARG; + } + + DCHECK(moniker_.get() == NULL); + DCHECK(bind_context_.get() == NULL); + + bind_context_ = context; + moniker_ = moniker; + set_url(WideToUTF8(url)); + return S_OK; +} + +void UrlmonUrlRequest::StealMoniker(IMoniker** moniker) { + // Could be called in any thread. There should be no race + // since moniker_ is not released while we are in manager's request map. + *moniker = moniker_.Detach(); +} + +STDMETHODIMP UrlmonUrlRequest::OnStartBinding(DWORD reserved, + IBinding *binding) { + DCHECK_EQ(thread_, PlatformThread::CurrentId()); binding_ = binding; return S_OK; } @@ -392,35 +361,16 @@ STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) { STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress, ULONG status_code, LPCWSTR status_text) { - static const int kDefaultHttpRedirectCode = 302; - + DCHECK_EQ(thread_, PlatformThread::CurrentId()); switch (status_code) { case BINDSTATUS_REDIRECTING: { + DLOG(INFO) << "URL: " << url() << " redirected to " << status_text; // Fetch the redirect status as they aren't all equal (307 in particular // retains the HTTP request verb). - // We assume that valid redirect codes are 301, 302, 303 and 307. If we - // receive anything else we would abort the request which would - // eventually result in the request getting cancelled in Chrome. - int redirect_status = GetHttpResponseStatus(); - DCHECK(status_text != NULL); - DLOG(INFO) << "URL: " << url() << " redirected to " - << status_text; - redirect_url_ = status_text; - // At times we receive invalid redirect codes like 0, 200, etc. We - // default to 302 in this case. - if (!net::HttpResponseHeaders::IsRedirectResponseCode(redirect_status)) - redirect_status = kDefaultHttpRedirectCode; - redirect_status_ = redirect_status; - // Chrome should decide whether a redirect has to be followed. To achieve - // this we send over a fake response to Chrome and abort the redirect. - std::string headers = GetHttpHeaders(); - OnResponse(0, UTF8ToWide(headers).c_str(), NULL, NULL); - ignore_redirect_stop_binding_error_ = true; - DCHECK(binding_ != NULL); - if (binding_) { - binding_->Abort(); - binding_ = NULL; - } + int http_code = GetHttpResponseStatus(); + status_.SetRedirected(http_code, WideToUTF8(status_text)); + // Abort. We will inform Chrome in OnStopBinding callback. + binding_->Abort(); return E_ABORT; } @@ -434,39 +384,51 @@ STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress, } STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { - DCHECK(worker_thread_ != NULL); - DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id()); - + DCHECK_EQ(thread_, PlatformThread::CurrentId()); DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << - " - Request stopped, Result: " << std::hex << result << - " Status: " << status_.status(); + " - 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. + status_.Done(); + + if (state == Status::WORKING) { + status_.set_result(result); + + // The code below seems easy but it is not. :) + // we cannot have pending read and data_avail at the same time. + DCHECK(!(pending_read_size_ > 0 && cached_data_.is_valid())); + + // We have some data, but Chrome has not yet read it. Wait until Chrome + // read the remaining of the data and then send the error/success code. + if (cached_data_.is_valid()) { + ReleaseBindings(); + return S_OK; + } - if (FAILED(result)) { - status_.set_status(URLRequestStatus::FAILED); - status_.set_os_error(HresultToNetError(result)); - EndRequest(); - } else { - status_.set_status(URLRequestStatus::SUCCESS); - status_.set_os_error(0); + NotifyDelegateAndDie(); + return S_OK; + } + + // Status::ABORTING + if (status_.was_redirected()) { + // Just release bindings here. Chrome will issue EndRequest(request_id) + // after processing headers we had provided. + std::string headers = GetHttpHeaders(); + OnResponse(0, UTF8ToWide(headers).c_str(), NULL, NULL); ReleaseBindings(); - // In most cases we receive the end request notification from Chrome. - // However at times requests can complete without us receiving any - // data. In this case we need to inform Chrome that this request has been - // completed to prevent Chrome from waiting forever for data for this - // request. - if (pending_read_size_) { - pending_read_size_ = 0; - OnResponseEnd(status_); - } + return S_OK; } + // Stop invoked. + NotifyDelegateAndDie(); return S_OK; } STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, BINDINFO *bind_info) { - DCHECK(worker_thread_ != NULL); - DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id()); if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL)) return E_INVALIDARG; @@ -485,9 +447,8 @@ STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, upload_data = true; } else { NOTREACHED() << "Unknown HTTP method."; - status_.set_status(URLRequestStatus::FAILED); - status_.set_os_error(net::ERR_METHOD_NOT_SUPPORTED); - EndRequest(); + status_.set_result(URLRequestStatus::FAILED, net::ERR_METHOD_NOT_SUPPORTED); + NotifyDelegateAndDie(); return E_FAIL; } @@ -519,9 +480,6 @@ STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size, FORMATETC* formatetc, STGMEDIUM* storage) { - DCHECK(worker_thread_ != NULL); - DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id()); - DLOG(INFO) << StringPrintf("URL: %s Obj: %X - Bytes available: %d", url().c_str(), this, size); @@ -563,7 +521,6 @@ STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size, } if (BSCF_LASTDATANOTIFICATION & flags) { - status_.set_status(URLRequestStatus::SUCCESS); DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << " - end of data."; } @@ -581,9 +538,7 @@ STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown* object) { STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, const wchar_t* current_headers, DWORD reserved, wchar_t** additional_headers) { - DCHECK(worker_thread_ != NULL); - DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id()); - + DCHECK_EQ(thread_, PlatformThread::CurrentId()); if (!additional_headers) { NOTREACHED(); return E_POINTER; @@ -592,7 +547,7 @@ STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, DLOG(INFO) << "URL: " << url << " Obj: " << std::hex << this << " - Request headers: \n" << current_headers; - if (!binding_) { + if (status_.get_state() == Status::ABORTING) { // At times the BINDSTATUS_REDIRECTING notification which is sent to the // IBindStatusCallback interface does not have an accompanying HTTP // redirect status code, i.e. the attempt to query the HTTP status code @@ -602,7 +557,6 @@ STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, // However urlmon still tries to establish a transaction with the // redirected URL which confuses the web server. // Fix is to abort the attempted transaction. - DCHECK(ignore_redirect_stop_binding_error_); DLOG(WARNING) << __FUNCTION__ << ": Aborting connection to URL:" << url @@ -649,13 +603,10 @@ STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, const wchar_t* response_headers, const wchar_t* request_headers, wchar_t** additional_headers) { - DCHECK(worker_thread_ != NULL); DLOG(INFO) << __FUNCTION__ << " " << url() << std::endl << " headers: " << std::endl << response_headers; - DCHECK_EQ(PlatformThread::CurrentId(), worker_thread_->thread_id()); - + DCHECK_EQ(thread_, PlatformThread::CurrentId()); if (!binding_) { - DCHECK(redirect_url_.empty() == false); DLOG(WARNING) << __FUNCTION__ << ": Ignoring as the binding was aborted due to a redirect"; return S_OK; @@ -679,7 +630,7 @@ STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, // NOTE(slightlyoff): We don't use net::HttpResponseHeaders here because // of lingering ICU/base_noicu issues. - if (frame_busting_enabled_) { + if (enable_frame_busting_) { std::string http_headers = net::HttpUtil::AssembleRawHeaders( raw_headers.c_str(), raw_headers.length()); if (http_utils::HasFrameBustingHeader(http_headers)) { @@ -689,36 +640,44 @@ STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, } } - std::wstring url_for_persistent_cookies = - redirect_url_.empty() ? UTF8ToWide(url()) : redirect_url_; + std::string url_for_persistent_cookies; std::string persistent_cookies; - DWORD cookie_size = 0; // NOLINT - // Note that there's really no way for us here to distinguish session cookies - // from persistent cookies here. Session cookies should get filtered - // out on the chrome side as to not be added again. - InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, NULL, - &cookie_size); - if (cookie_size) { - scoped_array<wchar_t> cookies(new wchar_t[cookie_size + 1]); - if (!InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, - cookies.get(), &cookie_size)) { - NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError(); - } else { - persistent_cookies = WideToUTF8(cookies.get()); + if (status_.was_redirected()) + url_for_persistent_cookies = status_.get_redirection().utf8_url; + + if (url_for_persistent_cookies.empty()) + url_for_persistent_cookies = url(); + + // Grab cookies for the specific Url from WININET. + { + DWORD cookie_size = 0; // NOLINT + std::wstring url = UTF8ToWide(url_for_persistent_cookies); + + // Note that there's really no way for us here to distinguish session + // cookies from persistent cookies here. Session cookies should get + // filtered out on the chrome side as to not be added again. + InternetGetCookie(url.c_str(), NULL, NULL, &cookie_size); + if (cookie_size) { + scoped_array<wchar_t> cookies(new wchar_t[cookie_size + 1]); + if (!InternetGetCookie(url.c_str(), NULL, cookies.get(), &cookie_size)) { + NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError(); + } else { + persistent_cookies = WideToUTF8(cookies.get()); + } } } - OnResponseStarted("", - raw_headers.c_str(), - 0, - base::Time(), + // Inform the delegate. + delegate_->OnResponseStarted(id(), + "", // mime_type + raw_headers.c_str(), // headers + 0, // size + base::Time(), // last_modified persistent_cookies, - redirect_url_.empty() ? std::string() : - WideToUTF8(redirect_url_), - redirect_status_); - + status_.get_redirection().utf8_url, + status_.get_redirection().http_code); return S_OK; } @@ -818,41 +777,6 @@ STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) { return hr; } -HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker, - IBindCtx* context, - const std::wstring& url) { - if (!moniker || url.empty()) { - NOTREACHED() << "Invalid arguments"; - return E_INVALIDARG; - } - - DCHECK(moniker_.get() == NULL); - DCHECK(bind_context_.get() == NULL); - - CComObject<WrappedBindContext>* bind_context = NULL; - HRESULT hr = CComObject<WrappedBindContext>::CreateInstance(&bind_context); - if (FAILED(hr)) { - NOTREACHED() << "Failed to instantiate wrapped bind context. Error:" << hr; - return hr; - } - - bind_context->AddRef(); - hr = bind_context->Initialize(context); - DCHECK(SUCCEEDED(hr)); - - hr = bind_context->QueryInterface(bind_context_.Receive()); - bind_context->Release(); - - if (FAILED(hr)) { - NOTREACHED() << "Failed to QI for IBindCtx on wrapper. Error:" << hr; - return hr; - } - - moniker_ = moniker; - set_url(WideToUTF8(url)); - return S_OK; -} - HRESULT UrlmonUrlRequest::StartAsyncDownload() { HRESULT hr = E_FAIL; if (moniker_.get() == NULL) { @@ -874,6 +798,12 @@ HRESULT UrlmonUrlRequest::StartAsyncDownload() { ScopedComPtr<IStream> stream; hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream), reinterpret_cast<void**>(stream.Receive())); + // Even if hr == S_OK, binding_ could be NULL if the entire request + // finish synchronously but then we still get all the callbacks etc. + if (hr == S_OK) { + DCHECK(binding_ != NULL || status_.get_state() == Status::DONE); + } + if (FAILED(hr)) { // TODO(joshia): Look into. This currently fails for: // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged @@ -891,46 +821,15 @@ HRESULT UrlmonUrlRequest::StartAsyncDownload() { return hr; } -void UrlmonUrlRequest::EndRequest() { +void UrlmonUrlRequest::NotifyDelegateAndDie() { + DCHECK_EQ(thread_, PlatformThread::CurrentId()); DLOG(INFO) << __FUNCTION__; - - // In case of a redirect notification we prevent urlmon from following the - // redirect and rely on Chrome, in which case AutomationMsg_RequestEnd - // IPC will be sent over by Chrome to end this request. - if (!ignore_redirect_stop_binding_error_) { - // Special case. If the last request was a redirect and the current OS - // error value is E_ACCESSDENIED, that means an unsafe redirect was - // attempted. In that case, correct the OS error value to be the more - // specific ERR_UNSAFE_REDIRECT error value. - if (!status_.is_success() && status_.os_error() == net::ERR_ACCESS_DENIED) { - int status = GetHttpResponseStatus(); - if (status >= 300 && status < 400) { - redirect_status_ = status; // store the latest redirect status value. - status_.set_os_error(net::ERR_UNSAFE_REDIRECT); - } - } - OnResponseEnd(status_); - } else { - ignore_redirect_stop_binding_error_ = false; - } - + PluginUrlRequestDelegate* delegate = delegate_; + delegate_ = NULL; ReleaseBindings(); - // Remove the request mapping and release the outstanding reference to us in - // the context of the UI thread. - // We should not access any members of the UrlmonUrlRequest object after this - // as the object would be deleted. - PostTask(FROM_HERE, - NewRunnableMethod(this, &UrlmonUrlRequest::EndRequestInternal)); -} - -void UrlmonUrlRequest::EndRequestInternal() { - // The request object could have been removed from the map in the - // OnRequestEnd callback which executes on receiving the - // AutomationMsg_RequestEnd IPC from Chrome. - request_handler()->RemoveRequest(this); - // The current instance could get destroyed in the context of DestroyWindow. - // We should not access the object after this. - DestroyWindow(); + if (delegate) { + delegate->OnResponseEnd(id(), status_.get_result()); + } } int UrlmonUrlRequest::GetHttpResponseStatus() const { @@ -1096,3 +995,236 @@ net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) { } return ret; } + + +bool UrlmonUrlRequestManager::IsThreadSafe() { + return true; +} + +void UrlmonUrlRequestManager::UseMonikerForUrl(IMoniker* moniker, + IBindCtx* bind_ctx, + const std::wstring& url) { + DCHECK(NULL == moniker_for_url_.get()); + moniker_for_url_.reset(new MonikerForUrl()); + moniker_for_url_->moniker = moniker; + moniker_for_url_->url = url; + + CComObject<WrappedBindContext>* ctx = NULL; + CComObject<WrappedBindContext>::CreateInstance(&ctx); + ctx->Initialize(bind_ctx); + ctx->QueryInterface(moniker_for_url_->bind_ctx.Receive()); + DCHECK(moniker_for_url_->bind_ctx.get()); +} + +void UrlmonUrlRequestManager::StartRequest(int request_id, + const IPC::AutomationURLRequest& request_info) { + if (stopping_) { + return; + } + + if (!worker_thread_.IsRunning()) + worker_thread_.Start(); + + 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(); + } + } + + worker_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, &UrlmonUrlRequestManager::StartRequestWorker, + request_id, request_info, use_moniker)); +} + +void UrlmonUrlRequestManager::StartRequestWorker(int request_id, + const IPC::AutomationURLRequest& request_info, + MonikerForUrl* use_moniker) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + scoped_ptr<MonikerForUrl> moniker_for_url(use_moniker); + + if (stopping_) + return; + + DCHECK(LookupRequest(request_id).get() == NULL); + + CComObject<UrlmonUrlRequest>* new_request = NULL; + CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); + + new_request->Initialize(static_cast<PluginUrlRequestDelegate*>(this), + request_id, + request_info.url, + request_info.method, + request_info.referrer, + request_info.extra_request_headers, + request_info.upload_data, + enable_frame_busting_); + + // Shall we use an existing moniker? + if (moniker_for_url.get()) { + new_request->ConnectToExistingMoniker(moniker_for_url->moniker, + moniker_for_url->bind_ctx, + moniker_for_url->url); + } + + DCHECK(LookupRequest(request_id).get() == NULL); + request_map_[request_id] = new_request; + map_empty_.Reset(); + + new_request->Start(); +} + +void UrlmonUrlRequestManager::ReadRequest(int request_id, int bytes_to_read) { + if (stopping_) + return; + + worker_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &UrlmonUrlRequestManager::ReadRequestWorker, request_id, bytes_to_read)); +} + +void UrlmonUrlRequestManager::ReadRequestWorker(int request_id, + int bytes_to_read) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id); + // if zero, it may just have had network error. + if (request) { + request->Read(bytes_to_read); + } +} + +void UrlmonUrlRequestManager::EndRequest(int request_id) { + if (stopping_) + return; + + worker_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &UrlmonUrlRequestManager::EndRequestWorker, request_id)); +} + +void UrlmonUrlRequestManager::EndRequestWorker(int request_id) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id); + if (request) { + request->Stop(); + } +} + +void UrlmonUrlRequestManager::StopAll() { + if (stopping_) + return; + + stopping_ = true; + + if (!worker_thread_.IsRunning()) + return; + + worker_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &UrlmonUrlRequestManager::StopAllWorker)); + + // Note we may not call worker_thread_.Stop() here. The MessageLoop's quit + // task will be serialized after request::Stop tasks, but requests may + // not quit immediately. CoUninitialize has a modal message loop, but it + // does not help in this case. + // Normally we call binding->Abort() and expect OnStopBinding() callback + // where we inform UrlmonUrlRequestManager that request is dead. + // The problem is that while waiting for OnStopBinding(), Quit Task may be + // picked up and executed, thus exiting the thread. + map_empty_.Wait(); + worker_thread_.Stop(); + DCHECK_EQ(0, UrlmonUrlRequest::instance_count_); +} + +void UrlmonUrlRequestManager::StopAllWorker() { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + DCHECK_EQ(true, stopping_); + + std::vector<scoped_refptr<UrlmonUrlRequest> > request_list; + // We copy the pending requests into a temporary vector as the Stop + // function in the request could also try to delete the request from + // the request map and the iterator could end up being invalid. + for (RequestMap::iterator it = request_map_.begin(); + it != request_map_.end(); ++it) { + DCHECK(it->second != NULL); + request_list.push_back(it->second); + } + + for (std::vector<scoped_refptr<UrlmonUrlRequest> >::size_type index = 0; + index < request_list.size(); ++index) { + request_list[index]->Stop(); + } +} + +void UrlmonUrlRequestManager::OnResponseStarted(int request_id, + const char* mime_type, const char* headers, int size, + base::Time last_modified, const std::string& peristent_cookies, + const std::string& redirect_url, int redirect_status) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + DCHECK(LookupRequest(request_id).get() != NULL); + delegate_->OnResponseStarted(request_id, mime_type, headers, size, + last_modified, peristent_cookies, redirect_url, redirect_status); +} + +void UrlmonUrlRequestManager::OnReadComplete(int request_id, const void* buffer, + int len) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + DCHECK(LookupRequest(request_id).get() != NULL); + delegate_->OnReadComplete(request_id, buffer, len); +} + +void UrlmonUrlRequestManager::OnResponseEnd(int request_id, + const URLRequestStatus& status) { + DCHECK_EQ(worker_thread_.thread_id(), PlatformThread::CurrentId()); + RequestMap::size_type n = request_map_.erase(request_id); + DCHECK_EQ(1, n); + + if (request_map_.size() == 0) + map_empty_.Signal(); + + // Inform delegate unless the request has been explicitly cancelled. + if (status.status() != URLRequestStatus::CANCELED) + delegate_->OnResponseEnd(request_id, status); +} + +scoped_refptr<UrlmonUrlRequest> UrlmonUrlRequestManager::LookupRequest( + int request_id) { + RequestMap::iterator it = request_map_.find(request_id); + if (request_map_.end() != it) + return it->second; + return NULL; +} + +UrlmonUrlRequestManager::UrlmonUrlRequestManager() + : stopping_(false), worker_thread_("UrlMon fetch thread"), + map_empty_(true, true) { +} + +UrlmonUrlRequestManager::~UrlmonUrlRequestManager() { + StopAll(); +} + +// Called from UI thread. +void UrlmonUrlRequestManager::StealMonikerFromRequest(int request_id, + IMoniker** moniker) { + if (stopping_) + return; + + base::WaitableEvent done(true, false); + worker_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &UrlmonUrlRequestManager::StealMonikerFromRequestWorker, + request_id, moniker, &done)); + + // Wait until moniker is grabbed from a request in the worker thread. + done.Wait(); +} + +void UrlmonUrlRequestManager::StealMonikerFromRequestWorker(int request_id, + IMoniker** moniker, base::WaitableEvent* done) { + if (!stopping_) { + scoped_refptr<UrlmonUrlRequest> request = LookupRequest(request_id); + if (request) { + request->StealMoniker(moniker); + request->Stop(); + } + } + + done->Signal(); +} diff --git a/chrome_frame/urlmon_url_request.h b/chrome_frame/urlmon_url_request.h index d4abee5..e47edcc 100644 --- a/chrome_frame/urlmon_url_request.h +++ b/chrome_frame/urlmon_url_request.h @@ -8,263 +8,77 @@ #include <urlmon.h> #include <atlbase.h> #include <atlcom.h> -#include <atlwin.h> -#include <algorithm> #include <string> -#include "base/lock.h" -#include "base/platform_thread.h" -#include "base/thread.h" #include "base/scoped_comptr_win.h" +#include "base/thread.h" +#include "base/waitable_event.h" #include "chrome_frame/plugin_url_request.h" -#include "chrome_frame/chrome_frame_delegate.h" +#include "chrome_frame/utils.h" -#include "net/base/net_errors.h" -#include "net/base/upload_data.h" +class UrlmonUrlRequest; -class UrlmonUrlRequest - : public CComObjectRootEx<CComMultiThreadModel>, - public PluginUrlRequest, - public IServiceProviderImpl<UrlmonUrlRequest>, - public IBindStatusCallback, - public IHttpNegotiate, - public IAuthenticate, - public IHttpSecurity, - public CWindowImpl<UrlmonUrlRequest>, - public TaskMarshallerThroughWindowsMessages<UrlmonUrlRequest> { +class UrlmonUrlRequestManager : + public PluginUrlRequestManager, + public PluginUrlRequestDelegate { public: - typedef TaskMarshallerThroughWindowsMessages<UrlmonUrlRequest> - TaskMarshaller; - - UrlmonUrlRequest(); - ~UrlmonUrlRequest(); - -BEGIN_COM_MAP(UrlmonUrlRequest) - COM_INTERFACE_ENTRY(IHttpNegotiate) - COM_INTERFACE_ENTRY(IServiceProvider) - COM_INTERFACE_ENTRY(IBindStatusCallback) - COM_INTERFACE_ENTRY(IWindowForBindingUI) - COM_INTERFACE_ENTRY(IAuthenticate) - COM_INTERFACE_ENTRY(IHttpSecurity) -END_COM_MAP() - -BEGIN_SERVICE_MAP(UrlmonUrlRequest) - SERVICE_ENTRY(IID_IHttpNegotiate); -END_SERVICE_MAP() - -BEGIN_MSG_MAP(UrlmonUrlRequest) - CHAIN_MSG_MAP(TaskMarshaller) -END_MSG_MAP() - - // PluginUrlRequest implementation - virtual bool Start(); - virtual void Stop(); - virtual bool Read(int bytes_to_read); - - void TransferToHost(IUnknown* host); - - // IBindStatusCallback implementation - STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding); - STDMETHOD(GetPriority)(LONG* priority); - STDMETHOD(OnLowResource)(DWORD reserved); - STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress, - ULONG status_code, LPCWSTR status_text); - STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error); - STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info); - STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc, - STGMEDIUM* storage); - STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object); - - // IHttpNegotiate implementation - STDMETHOD(BeginningTransaction)(const wchar_t* url, - const wchar_t* current_headers, DWORD reserved, - wchar_t** additional_headers); - STDMETHOD(OnResponse)(DWORD dwResponseCode, const wchar_t* response_headers, - const wchar_t* request_headers, wchar_t** additional_headers); - - // IWindowForBindingUI implementation. This interface is used typically to - // query the window handle which URLMON uses as the parent of error dialogs. - STDMETHOD(GetWindow)(REFGUID guid_reason, HWND* parent_window); - - // IAuthenticate implementation. Used to return the parent window for the - // dialog displayed by IE for authenticating with a proxy. - STDMETHOD(Authenticate)(HWND* parent_window, LPWSTR* user_name, - LPWSTR* password); - - // IHttpSecurity implementation. - STDMETHOD(OnSecurityProblem)(DWORD problem); - - HRESULT ConnectToExistingMoniker(IMoniker* moniker, IBindCtx* context, - const std::wstring& url); - - void set_parent_window(HWND parent_window) { - parent_window_ = parent_window; - } - - // Needed to support PostTask. - static bool ImplementsThreadSafeReferenceCounting() { - return true; - } - - // URL requests are handled on this thread. - void set_worker_thread(base::Thread* worker_thread) { - worker_thread_ = worker_thread; - } - - virtual void OnFinalMessage(HWND window); - - protected: - // The following functions issue and handle Urlmon requests on the dedicated - // Urlmon thread. - void StartAsync(); - void StopAsync(); - void ReadAsync(int bytes_to_read); - void ReleaseBindings(); - - static const size_t kCopyChunkSize = 32 * 1024; - // URL requests are handled on this thread. - base::Thread* worker_thread_; - - // A fake stream class to make it easier to copy received data using - // IStream::CopyTo instead of allocating temporary buffers and keeping - // track of data copied so far. - class SendStream - : public CComObjectRoot, - public IStream { - public: - SendStream() { - } - - BEGIN_COM_MAP(SendStream) - COM_INTERFACE_ENTRY(IStream) - COM_INTERFACE_ENTRY(ISequentialStream) - END_COM_MAP() + UrlmonUrlRequestManager(); + ~UrlmonUrlRequestManager(); - void Initialize(UrlmonUrlRequest* request) { - request_ = request; - } - - STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written) { - DCHECK(request_); - int size_to_write = static_cast<int>( - std::min(static_cast<ULONG>(MAXINT), size)); - request_->OnReadComplete(buffer, size_to_write); - if (size_written) - *size_written = size_to_write; - return S_OK; - } - - STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(SetSize)(ULARGE_INTEGER new_size) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read, - ULARGE_INTEGER* written) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(Commit)(DWORD flags) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(Revert)() { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, - DWORD type) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, - DWORD type) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag) { - return E_NOTIMPL; - } - - STDMETHOD(Clone)(IStream** stream) { - DCHECK(false) << __FUNCTION__; - return E_NOTIMPL; - } - - protected: - scoped_refptr<UrlmonUrlRequest> request_; - DISALLOW_COPY_AND_ASSIGN(SendStream); - }; - - // Manage data caching. Note: this class supports cache - // size less than 2GB - class Cache { - public: - // Adds data to the end of the cache. - bool Append(IStream* source, size_t* bytes_copied); - - // Reads from the cache. - bool Read(IStream* dest, size_t size, size_t* bytes_copied); - - // Returns the size of the cache. - size_t Size() const; - - // Returns true if the cache has valid data. - bool is_valid() const { - return Size() != 0; - } - - protected: - std::vector<byte> cache_; - char read_buffer_[kCopyChunkSize]; - }; - - HRESULT StartAsyncDownload(); - // Sends over the response end notification to chrome, releases the bindings - // and releases the initial reference on the UrlmonUrlRequest object. - // After this function is called we should not attempt to access any members - // as the object could become invalid at any point. - void EndRequest(); - // Executes in the context of the UI thread and releases the outstanding - // reference to us. It also deletes the request mapping for this instance. - void EndRequestInternal(); - int GetHttpResponseStatus() const; - std::string GetHttpHeaders() const; - - static net::Error HresultToNetError(HRESULT hr); + // Use specific moniker and bind context 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 StealMonikerFromRequest(int request_id, IMoniker** moniker); private: - std::wstring redirect_url_; - int redirect_status_; - ScopedComPtr<IBinding> binding_; - ScopedComPtr<IMoniker> moniker_; - ScopedComPtr<IBindCtx> bind_context_; - Cache cached_data_; - size_t pending_read_size_; - URLRequestStatus status_; - - PlatformThreadId thread_; - static int instance_count_; - HWND parent_window_; - // Set to true if a redirect notification was aborted. - bool ignore_redirect_stop_binding_error_; - - DISALLOW_COPY_AND_ASSIGN(UrlmonUrlRequest); + struct MonikerForUrl { + ScopedComPtr<IMoniker> moniker; + ScopedComPtr<IBindCtx> bind_ctx; + std::wstring url; + }; + + friend class MessageLoop; + friend struct RunnableMethodTraits<UrlmonUrlRequestManager>; + static bool ImplementsThreadSafeReferenceCounting() { return true; } + void AddRef() {} + void Release() {} + + // PluginUrlRequestManager implementation. + virtual bool IsThreadSafe(); + virtual void StartRequest(int request_id, + const IPC::AutomationURLRequest& request_info); + virtual void ReadRequest(int request_id, int bytes_to_read); + virtual void EndRequest(int request_id); + virtual void StopAll(); + + // PluginUrlRequestDelegate implementation + virtual void OnResponseStarted(int request_id, const char* mime_type, + const char* headers, int size, base::Time last_modified, + const std::string& peristent_cookies, const std::string& redirect_url, + int redirect_status); + virtual void OnReadComplete(int request_id, const void* buffer, int len); + virtual void OnResponseEnd(int request_id, const URLRequestStatus& status); + + // Methods executed in worker thread. + void StartRequestWorker(int request_id, + const IPC::AutomationURLRequest& request_info, + MonikerForUrl* moniker_for_url); + void ReadRequestWorker(int request_id, int bytes_to_read); + void EndRequestWorker(int request_id); + void StopAllWorker(); + void StealMonikerFromRequestWorker(int request_id, IMoniker** moniker, + base::WaitableEvent* done); + + // Map for (request_id <-> UrlmonUrlRequest) + typedef std::map<int, scoped_refptr<UrlmonUrlRequest> > RequestMap; + RequestMap request_map_; + scoped_refptr<UrlmonUrlRequest> LookupRequest(int request_id); + + scoped_ptr<MonikerForUrl> moniker_for_url_; + STAThread worker_thread_; + base::WaitableEvent map_empty_; + bool stopping_; }; #endif // CHROME_FRAME_URLMON_URL_REQUEST_H_ diff --git a/chrome_frame/urlmon_url_request_private.h b/chrome_frame/urlmon_url_request_private.h new file mode 100644 index 0000000..9b42904 --- /dev/null +++ b/chrome_frame/urlmon_url_request_private.h @@ -0,0 +1,307 @@ +// 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_URL_REQUEST_PRIVATE_H_ +#define CHROME_FRAME_URLMON_URL_REQUEST_PRIVATE_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <string> +#include <vector> + +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_status.h" + +class UrlmonUrlRequest + : public CComObjectRootEx<CComMultiThreadModel>, + public PluginUrlRequest, + public IServiceProviderImpl<UrlmonUrlRequest>, + public IBindStatusCallback, + public IHttpNegotiate, + public IAuthenticate, + public IHttpSecurity { + public: + static int instance_count_; + virtual bool Start(); + virtual void Stop(); + virtual bool Read(int bytes_to_read); + + // Special function needed by ActiveDocument::Load() + HRESULT ConnectToExistingMoniker(IMoniker* moniker, IBindCtx* context, + const std::wstring& url); + + // Used from "OnDownloadRequestInHost". + void StealMoniker(IMoniker** moniker); + + // Parent Window for UrlMon error dialogs + void set_parent_window(HWND parent_window) { + parent_window_ = parent_window; + } + + protected: + UrlmonUrlRequest(); + ~UrlmonUrlRequest(); + + BEGIN_COM_MAP(UrlmonUrlRequest) + COM_INTERFACE_ENTRY(IHttpNegotiate) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(IBindStatusCallback) + COM_INTERFACE_ENTRY(IWindowForBindingUI) + COM_INTERFACE_ENTRY(IAuthenticate) + COM_INTERFACE_ENTRY(IHttpSecurity) + END_COM_MAP() + + BEGIN_SERVICE_MAP(UrlmonUrlRequest) + SERVICE_ENTRY(IID_IHttpNegotiate); + END_SERVICE_MAP() + + + // IBindStatusCallback implementation + STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding); + STDMETHOD(GetPriority)(LONG* priority); + STDMETHOD(OnLowResource)(DWORD reserved); + STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress, + ULONG status_code, LPCWSTR status_text); + STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error); + STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info); + STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc, + STGMEDIUM* storage); + STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object); + + // IHttpNegotiate implementation + STDMETHOD(BeginningTransaction)(const wchar_t* url, + const wchar_t* current_headers, DWORD reserved, + wchar_t** additional_headers); + STDMETHOD(OnResponse)(DWORD dwResponseCode, const wchar_t* response_headers, + const wchar_t* request_headers, wchar_t** additional_headers); + + // IWindowForBindingUI implementation. This interface is used typically to + // query the window handle which URLMON uses as the parent of error dialogs. + STDMETHOD(GetWindow)(REFGUID guid_reason, HWND* parent_window); + + // IAuthenticate implementation. Used to return the parent window for the + // dialog displayed by IE for authenticating with a proxy. + STDMETHOD(Authenticate)(HWND* parent_window, LPWSTR* user_name, + LPWSTR* password); + + // IHttpSecurity implementation. + STDMETHOD(OnSecurityProblem)(DWORD problem); + + protected: + void ReleaseBindings(); + + static const size_t kCopyChunkSize = 32 * 1024; + // A fake stream class to make it easier to copy received data using + // IStream::CopyTo instead of allocating temporary buffers and keeping + // track of data copied so far. + class SendStream : public CComObjectRoot, public IStream { + public: + SendStream() { + } + + BEGIN_COM_MAP(SendStream) + COM_INTERFACE_ENTRY(IStream) + COM_INTERFACE_ENTRY(ISequentialStream) + END_COM_MAP() + + void Initialize(UrlmonUrlRequest* request) { + request_ = request; + } + + STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written); + STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(SetSize)(ULARGE_INTEGER new_size) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read, + ULARGE_INTEGER* written) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Commit)(DWORD flags) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Revert)() { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag) { + return E_NOTIMPL; + } + + STDMETHOD(Clone)(IStream** stream) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + protected: + scoped_refptr<UrlmonUrlRequest> request_; + DISALLOW_COPY_AND_ASSIGN(SendStream); + }; + + // Manage data caching. Note: this class supports cache + // size less than 2GB + class Cache { + public: + // Adds data to the end of the cache. + bool Append(IStream* source, size_t* bytes_copied); + + // Reads from the cache. + bool Read(IStream* dest, size_t size, size_t* bytes_copied); + + // Returns the size of the cache. + size_t Size() const; + + // Returns true if the cache has valid data. + bool is_valid() const { + return Size() != 0; + } + + protected: + std::vector<byte> cache_; + char read_buffer_[kCopyChunkSize]; + }; + + HRESULT StartAsyncDownload(); + void NotifyDelegateAndDie(); + int GetHttpResponseStatus() const; + std::string GetHttpHeaders() const; + static net::Error HresultToNetError(HRESULT hr); + + private: + // This class simplifies tracking the progress of operation. We have 3 main + // states: DONE, WORKING and ABORTING. + // When in [DONE] or [ABORTING] state, there is additional information + // about the result of operation. + // Start(), SetRedirected(), Cancel() and Done() methods trigger the state + // change. See comments bellow. + class Status { + public: + enum State {DONE, ABORTING, WORKING}; + struct Redirection { + Redirection() : http_code(0) { } + int http_code; + std::string utf8_url; + }; + + Status() : state_(Status::DONE) { + } + + State get_state() const { + return state_; + } + + // Switch from [DONE] to [WORKING]. + void Start() { + DCHECK_EQ(state_, DONE); + state_ = WORKING; + } + + // Save redirection information and switch to [ABORTING] state. + // Assumes binding_->Abort() will be called! + void SetRedirected(int http_code, const std::string& utf8_url) { + DCHECK_EQ(state_, WORKING); + DCHECK_EQ(result_.status(), URLRequestStatus::SUCCESS); + redirect_.utf8_url = utf8_url; + + // At times we receive invalid redirect codes like 0, 200, etc. We + // default to 302 in this case. + redirect_.http_code = http_code; + if (!net::HttpResponseHeaders::IsRedirectResponseCode(http_code)) + redirect_.http_code = 302; + + state_ = ABORTING; + } + + // Set the result as URLRequestStatus::CANCELED. + // Switch to [ABORTING] state (if not already in that state). + void Cancel() { + if (state_ == DONE) + return; + + if (state_ == WORKING) { + state_ = ABORTING; + } else { + // state_ == ABORTING + redirect_.http_code = 0; + redirect_.utf8_url.clear(); + } + + set_result(URLRequestStatus::CANCELED, 0); + } + + void Done() { + state_ = DONE; + } + + bool was_redirected() const { + return redirect_.http_code != 0; + } + + const Redirection& get_redirection() const { + return redirect_; + } + + const URLRequestStatus& get_result() const { + return result_; + } + + void set_result(URLRequestStatus::Status status, int os_error) { + result_.set_status(status); + result_.set_os_error(os_error); + } + + void set_result(HRESULT hr) { + result_.set_status(FAILED(hr)? URLRequestStatus::FAILED: + URLRequestStatus::SUCCESS); + result_.set_os_error(HresultToNetError(hr)); + } + + private: + Redirection redirect_; + State state_; + URLRequestStatus result_; + }; + + Status status_; + ScopedComPtr<IBinding> binding_; + ScopedComPtr<IMoniker> moniker_; + ScopedComPtr<IBindCtx> bind_context_; + Cache cached_data_; + size_t pending_read_size_; + PlatformThreadId thread_; + HWND parent_window_; + + DISALLOW_COPY_AND_ASSIGN(UrlmonUrlRequest); +}; + +#endif // CHROME_FRAME_URLMON_URL_REQUEST_PRIVATE_H_ diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index f42062c..e90a0d4 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -12,6 +12,7 @@ #include "base/basictypes.h" #include "base/logging.h" +#include "base/thread.h" // utils.h : Various utility functions and classes @@ -315,6 +316,25 @@ STDMETHODIMP QueryInterfaceIfDelegateSupports(void* obj, REFIID iid, extern const wchar_t kChromeFrameHeadlessMode[]; +// Thread that enters STA and has a UI message loop. +class STAThread : public base::Thread { + public: + explicit STAThread(const char *name) : Thread(name) {} + bool Start() { + return StartWithOptions(Options(MessageLoop::TYPE_UI, 0)); + } + protected: + // Called just prior to starting the message loop + virtual void Init() { + ::CoInitialize(0); + } + + // Called just after the message loop ends + virtual void CleanUp() { + ::CoUninitialize(); + } +}; + // The urls retrieved from the IMoniker interface don't contain the anchor // portion of the actual url navigated to. This function checks whether the // url passed in the bho_url parameter contains an anchor and if yes checks |