| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome_frame/ready_mode/ready_mode.h" |
| |
| #include <atlbase.h> |
| #include <shlguid.h> |
| |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/memory/linked_ptr.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/win/scoped_bstr.h" |
| #include "base/win/scoped_comptr.h" |
| #include "base/win/win_util.h" |
| #include "chrome/installer/util/browser_distribution.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "chrome_frame/infobars/infobar_manager.h" |
| #include "chrome_frame/ready_mode/internal/ready_mode_web_browser_adapter.h" |
| #include "chrome_frame/ready_mode/internal/ready_prompt_content.h" |
| #include "chrome_frame/ready_mode/internal/registry_ready_mode_state.h" |
| #include "chrome_frame/ready_mode/internal/url_launcher.h" |
| #include "chrome_frame/utils.h" |
| |
| namespace { |
| |
| // Temporarily disable Ready Mode for 36 hours when the user so indicates. |
| const int kTemporaryDeclineDurationMinutes = 60 * 36; |
| |
| class BrowserObserver; |
| |
| // A helper for BrowserObserver to observe the user's choice in the Ready Mode |
| // prompt. |
| class StateObserver : public RegistryReadyModeState::Observer { |
| public: |
| explicit StateObserver(const base::WeakPtr<BrowserObserver>& ready_mode_ui); |
| ~StateObserver(); |
| |
| // RegistryReadyModeState::Observer implementation |
| virtual void OnStateChange(ReadyModeStatus status); |
| |
| private: |
| base::WeakPtr<BrowserObserver> ready_mode_ui_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StateObserver); |
| }; // class StateObserver |
| |
| // Manages the Ready Mode UI in response to browsing ChromeFrame- or Host- |
| // rendered pages. Shows the Ready Mode prompt when the user browses to a GCF- |
| // enabled page. Hides the prompt when the user begins navigating to a new |
| // domain or when they navigate to a new page in the same domain that is not |
| // GCF enabled. |
| // |
| // Uses InstallerAdapter and RegistryReadyMode to query and update the |
| // installation state. Uninstalls the ReadyModeWebBrowserAdapter when the user |
| // temporarily or permanently exits Ready Mode (decline or accept Chrome Frame). |
| // If the user declines Chrome Frame, the current page is reloaded in the Host |
| // renderer. |
| class BrowserObserver : public ReadyModeWebBrowserAdapter::Observer { |
| public: |
| BrowserObserver(ready_mode::Delegate* chrome_frame, |
| IWebBrowser2* web_browser, |
| ReadyModeWebBrowserAdapter* adapter); |
| |
| // ReadyModeWebBrowserAdapter::Observer implementation |
| virtual void OnNavigateTo(const std::wstring& url); |
| virtual void OnRenderInChromeFrame(const std::wstring& url); |
| virtual void OnRenderInHost(const std::wstring& url); |
| |
| private: |
| friend class StateObserver; |
| |
| // Called by the StateObserver |
| void OnReadyModeDisabled(); |
| void OnReadyModeAccepted(); |
| |
| // Helpers for showing infobar prompts |
| void ShowPrompt(); |
| void Hide(); |
| InfobarManager* GetInfobarManager(); |
| |
| GURL rendered_url_; |
| linked_ptr<ready_mode::Delegate> chrome_frame_; |
| base::win::ScopedComPtr<IWebBrowser2> web_browser_; |
| // The adapter owns us, so we use a weak reference |
| ReadyModeWebBrowserAdapter* adapter_; |
| base::WeakPtrFactory<BrowserObserver> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BrowserObserver); |
| }; // class BrowserObserver |
| |
| // Implements launching of a URL in an instance of IWebBrowser2. |
| class UrlLauncherImpl : public UrlLauncher { |
| public: |
| explicit UrlLauncherImpl(IWebBrowser2* web_browser); |
| |
| // UrlLauncher implementation |
| void LaunchUrl(const std::wstring& url); |
| |
| private: |
| base::win::ScopedComPtr<IWebBrowser2> web_browser_; |
| }; // class UrlLaucherImpl |
| |
| UrlLauncherImpl::UrlLauncherImpl(IWebBrowser2* web_browser) { |
| DCHECK(web_browser); |
| web_browser_ = web_browser; |
| } |
| |
| void UrlLauncherImpl::LaunchUrl(const std::wstring& url) { |
| VARIANT flags = { VT_I4 }; |
| V_I4(&flags) = navOpenInNewWindow; |
| base::win::ScopedBstr location(url.c_str()); |
| |
| HRESULT hr = web_browser_->Navigate(location, &flags, NULL, NULL, NULL); |
| DLOG_IF(ERROR, FAILED(hr)) << "Failed to invoke Navigate on IWebBrowser2. " |
| << "Error: " << hr; |
| } |
| |
| StateObserver::StateObserver( |
| const base::WeakPtr<BrowserObserver>& ready_mode_ui) |
| : ready_mode_ui_(ready_mode_ui) { |
| } |
| |
| StateObserver::~StateObserver() { |
| } |
| |
| void StateObserver::OnStateChange(ReadyModeStatus status) { |
| if (ready_mode_ui_ == NULL) |
| return; |
| |
| switch (status) { |
| case READY_MODE_PERMANENTLY_DECLINED: |
| case READY_MODE_TEMPORARILY_DECLINED: |
| ready_mode_ui_->OnReadyModeDisabled(); |
| break; |
| |
| case READY_MODE_ACCEPTED: |
| ready_mode_ui_->OnReadyModeAccepted(); |
| break; |
| |
| case READY_MODE_ACTIVE: |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| BrowserObserver::BrowserObserver(ready_mode::Delegate* chrome_frame, |
| IWebBrowser2* web_browser, |
| ReadyModeWebBrowserAdapter* adapter) |
| : web_browser_(web_browser), |
| chrome_frame_(chrome_frame), |
| adapter_(adapter), |
| weak_ptr_factory_(this) { |
| } |
| |
| void BrowserObserver::OnNavigateTo(const std::wstring& url) { |
| if (!net::registry_controlled_domains::SameDomainOrHost( |
| GURL(url), |
| rendered_url_, |
| net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) { |
| rendered_url_ = GURL(); |
| Hide(); |
| } |
| } |
| |
| void BrowserObserver::OnRenderInChromeFrame(const std::wstring& url) { |
| ShowPrompt(); |
| rendered_url_ = GURL(url); |
| } |
| |
| void BrowserObserver::OnRenderInHost(const std::wstring& url) { |
| Hide(); |
| rendered_url_ = GURL(url); |
| } |
| |
| void BrowserObserver::OnReadyModeDisabled() { |
| // We don't hold a reference to the adapter, since it owns us (in order to |
| // break circular dependency). But we should still AddRef it before |
| // invocation. |
| base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> reference(adapter_); |
| |
| // adapter_->Uninitialize may delete us, so we should not refer to members |
| // after that point. |
| base::win::ScopedComPtr<IWebBrowser2> web_browser(web_browser_); |
| |
| chrome_frame_->DisableChromeFrame(); |
| adapter_->Uninitialize(); |
| |
| VARIANT flags = { VT_I4 }; |
| V_I4(&flags) = navNoHistory; |
| base::win::ScopedBstr location; |
| |
| HRESULT hr = web_browser->get_LocationURL(location.Receive()); |
| DLOG_IF(ERROR, FAILED(hr)) << "Failed to get current location from " |
| << "IWebBrowser2. Error: " << hr; |
| |
| if (SUCCEEDED(hr)) { |
| hr = web_browser->Navigate(location, &flags, NULL, NULL, NULL); |
| DLOG_IF(ERROR, FAILED(hr)) << "Failed to invoke Navigate on IWebBrowser2. " |
| << "Error: " << hr; |
| } |
| } |
| |
| void BrowserObserver::OnReadyModeAccepted() { |
| // See comment in OnReadyModeDisabled. |
| base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> reference(adapter_); |
| adapter_->Uninitialize(); |
| } |
| |
| void BrowserObserver::ShowPrompt() { |
| // This pointer is self-managed and not guaranteed to survive handling of |
| // Windows events. |
| InfobarManager* infobar_manager = GetInfobarManager(); |
| |
| if (infobar_manager) { |
| // Owned by ready_mode_state |
| scoped_ptr<RegistryReadyModeState::Observer> ready_mode_state_observer( |
| new StateObserver(weak_ptr_factory_.GetWeakPtr())); |
| |
| BrowserDistribution* dist = |
| BrowserDistribution::GetSpecificDistribution( |
| BrowserDistribution::CHROME_BINARIES); |
| |
| // Owned by infobar_content |
| scoped_ptr<ReadyModeState> ready_mode_state(new RegistryReadyModeState( |
| dist->GetStateKey(), |
| base::TimeDelta::FromMinutes(kTemporaryDeclineDurationMinutes), |
| ready_mode_state_observer.release())); |
| |
| // Owned by infobar_content |
| scoped_ptr<UrlLauncher> url_launcher(new UrlLauncherImpl(web_browser_)); |
| |
| // Owned by infobar_manager |
| scoped_ptr<InfobarContent> infobar_content(new ReadyPromptContent( |
| ready_mode_state.release(), url_launcher.release())); |
| |
| infobar_manager->Show(infobar_content.release(), TOP_INFOBAR); |
| } |
| } |
| |
| void BrowserObserver::Hide() { |
| InfobarManager* infobar_manager = GetInfobarManager(); |
| if (infobar_manager) |
| infobar_manager->HideAll(); |
| } |
| |
| InfobarManager* BrowserObserver::GetInfobarManager() { |
| HRESULT hr = NOERROR; |
| |
| base::win::ScopedComPtr<IOleWindow> ole_window; |
| hr = DoQueryService(SID_SShellBrowser, web_browser_, ole_window.Receive()); |
| if (FAILED(hr) || ole_window == NULL) { |
| DLOG(ERROR) << "Failed to query SID_SShellBrowser from IWebBrowser2. " |
| << "Error: " << hr; |
| return NULL; |
| } |
| |
| HWND web_browserhwnd = NULL; |
| hr = ole_window->GetWindow(&web_browserhwnd); |
| if (FAILED(hr) || web_browserhwnd == NULL) { |
| DLOG(ERROR) << "Failed to query HWND from IOleWindow. " |
| << "Error: " << hr; |
| return NULL; |
| } |
| |
| return InfobarManager::Get(web_browserhwnd); |
| } |
| |
| // Wraps an existing Delegate so that ownership may be shared. |
| class DelegateWrapper : public ready_mode::Delegate { |
| public: |
| explicit DelegateWrapper(linked_ptr<ready_mode::Delegate> wrapped); |
| |
| // ready_mode::Delegate implementation |
| virtual void DisableChromeFrame(); |
| |
| private: |
| linked_ptr<ready_mode::Delegate> wrapped_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DelegateWrapper); |
| }; // class DelegateWrapper |
| |
| DelegateWrapper::DelegateWrapper(linked_ptr<ready_mode::Delegate> wrapped) |
| : wrapped_(wrapped) { |
| } |
| |
| void DelegateWrapper::DisableChromeFrame() { |
| wrapped_->DisableChromeFrame(); |
| } |
| |
| // Attempts to create a ReadyModeWebBrowserAdapter instance. |
| bool CreateWebBrowserAdapter(ReadyModeWebBrowserAdapter** pointer) { |
| *pointer = NULL; |
| |
| CComObject<ReadyModeWebBrowserAdapter>* com_object; |
| HRESULT hr = |
| CComObject<ReadyModeWebBrowserAdapter>::CreateInstance(&com_object); |
| |
| if (FAILED(hr)) { |
| DLOG(ERROR) << "Failed to create instance of ReadyModeWebBrowserAdapter. " |
| << "Error: " << hr; |
| return false; |
| } |
| |
| com_object->AddRef(); |
| *pointer = com_object; |
| return true; |
| } |
| |
| // Attempts to install Ready Mode prompts in the provided web browser. Will |
| // notify the provided Delegate if the user declines Chrome Frame temporarily or |
| // permanently. |
| bool InstallPrompts(linked_ptr<ready_mode::Delegate> delegate, |
| IWebBrowser2* web_browser) { |
| base::win::ScopedComPtr<ReadyModeWebBrowserAdapter, NULL> adapter; |
| |
| if (!CreateWebBrowserAdapter(adapter.Receive())) |
| return false; |
| |
| // Wrap the original delegate so that we can share it with the |
| // ReadyModeWebBrowserAdapter |
| scoped_ptr<DelegateWrapper> delegate_wrapper(new DelegateWrapper(delegate)); |
| |
| // Pass ownership of our delegate to the BrowserObserver |
| scoped_ptr<ReadyModeWebBrowserAdapter::Observer> browser_observer( |
| new BrowserObserver(delegate_wrapper.release(), web_browser, adapter)); |
| |
| // Owns the BrowserObserver |
| return adapter->Initialize(web_browser, browser_observer.release()); |
| } |
| |
| // Checks if the provided status implies disabling Chrome Frame functionality. |
| bool ShouldDisableChromeFrame(ReadyModeStatus status) { |
| switch (status) { |
| case READY_MODE_PERMANENTLY_DECLINED: |
| case READY_MODE_TEMPORARILY_DECLINED: |
| case READY_MODE_TEMPORARY_DECLINE_EXPIRED: |
| return true; |
| |
| case READY_MODE_ACCEPTED: |
| case READY_MODE_ACTIVE: |
| return false; |
| |
| default: |
| NOTREACHED(); |
| return true; |
| } |
| } |
| |
| } // namespace |
| |
| namespace ready_mode { |
| |
| // Determines the current Ready Mode state. If it is active, attempts to set up |
| // prompting. If we cannot set up prompting, attempts to temporarily disable |
| // Ready Mode. In the end, if Ready Mode is disabled, pass that information on |
| // to the Delegate, so that it may disabled Chrome Frame functionality. |
| void Configure(Delegate* chrome_frame, IWebBrowser2* web_browser) { |
| // Take ownership of the delegate |
| linked_ptr<Delegate> delegate(chrome_frame); |
| chrome_frame = NULL; |
| BrowserDistribution* dist = |
| BrowserDistribution::GetSpecificDistribution( |
| BrowserDistribution::CHROME_BINARIES); |
| |
| RegistryReadyModeState ready_mode_state( |
| dist->GetStateKey(), |
| base::TimeDelta::FromMinutes(kTemporaryDeclineDurationMinutes), |
| NULL); // NULL => no observer required |
| |
| ReadyModeStatus status = ready_mode_state.GetStatus(); |
| |
| // If the user temporarily declined Chrome Frame, but the timeout has elapsed, |
| // attempt to revert to active Ready Mode state. |
| if (status == READY_MODE_TEMPORARY_DECLINE_EXPIRED) { |
| ready_mode_state.ExpireTemporaryDecline(); |
| status = ready_mode_state.GetStatus(); |
| } |
| |
| // If Ready Mode is active, attempt to set up prompting. |
| if (status == READY_MODE_ACTIVE) { |
| if (!InstallPrompts(delegate, web_browser)) { |
| // Failed to set up prompting. Turn off Ready Mode for now. |
| ready_mode_state.TemporarilyDeclineChromeFrame(); |
| status = ready_mode_state.GetStatus(); |
| } |
| } |
| |
| // Depending on the state we finally end up in, tell our Delegate to disable |
| // Chrome Frame functionality. |
| if (ShouldDisableChromeFrame(status)) |
| delegate->DisableChromeFrame(); |
| } |
| |
| } // namespace ready_mode |