// Copyright 2013 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/turndown_prompt/turndown_prompt.h" #include #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/process/launch.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 "chrome/installer/util/google_update_settings.h" #include "chrome/installer/util/install_util.h" #include "chrome/installer/util/installation_state.h" #include "chrome/installer/util/util_constants.h" #include "chrome_frame/infobars/infobar_manager.h" #include "chrome_frame/policy_settings.h" #include "chrome_frame/ready_mode/internal/ready_mode_web_browser_adapter.h" #include "chrome_frame/ready_mode/internal/url_launcher.h" #include "chrome_frame/simple_resource_loader.h" #include "chrome_frame/turndown_prompt/reshow_state.h" #include "chrome_frame/turndown_prompt/turndown_prompt_content.h" #include "chrome_frame/utils.h" #include "grit/chromium_strings.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" namespace { // Time between showings of the turndown prompt. const int kTurndownPromptReshowDeltaMinutes = 60 * 24 * 7; void OnUninstallClicked(UrlLauncher* url_launcher); // Manages the Turndown UI in response to browsing ChromeFrame-rendered // pages. class BrowserObserver : public ReadyModeWebBrowserAdapter::Observer { public: BrowserObserver(IWebBrowser2* web_browser, ReadyModeWebBrowserAdapter* adapter); // ReadyModeWebBrowserAdapter::Observer implementation virtual void OnNavigateTo(const string16& url); virtual void OnRenderInChromeFrame(const string16& url); virtual void OnRenderInHost(const string16& url); private: // Shows the turndown prompt if it hasn't been seen since // kTurndownPromptReshowDeltaMinutes. void ShowPrompt(); void Hide(); // Returns a self-managed pointer that is not guaranteed to survive handling // of Windows events. For safety's sake, retrieve this pointer for each use // and do not store it for use outside of scope. InfobarManager* GetInfobarManager(); GURL rendered_url_; base::win::ScopedComPtr web_browser_; ReadyModeWebBrowserAdapter* adapter_; DISALLOW_COPY_AND_ASSIGN(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 string16& url); private: base::win::ScopedComPtr web_browser_; }; UrlLauncherImpl::UrlLauncherImpl(IWebBrowser2* web_browser) { DCHECK(web_browser); web_browser_ = web_browser; } void UrlLauncherImpl::LaunchUrl(const string16& 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; } BrowserObserver::BrowserObserver(IWebBrowser2* web_browser, ReadyModeWebBrowserAdapter* adapter) : web_browser_(web_browser), adapter_(adapter) { } void BrowserObserver::OnNavigateTo(const string16& 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 string16& url) { ShowPrompt(); rendered_url_ = GURL(url); } void BrowserObserver::OnRenderInHost(const string16& url) { Hide(); rendered_url_ = GURL(url); } void BrowserObserver::ShowPrompt() { turndown_prompt::ReshowState reshow_state( base::TimeDelta::FromMinutes(kTurndownPromptReshowDeltaMinutes)); // Short-circuit if the prompt shouldn't be shown again yet. if (!reshow_state.HasReshowDeltaExpired(base::Time::Now())) return; // This pointer is self-managed and not guaranteed to survive handling of // Windows events. For safety's sake, retrieve this pointer for each use and // do not store it for use outside of scope. InfobarManager* infobar_manager = GetInfobarManager(); if (infobar_manager) { // Owned by infobar_content scoped_ptr url_launcher(new UrlLauncherImpl(web_browser_)); // Owned by infobar_manager scoped_ptr infobar_content(new TurndownPromptContent( url_launcher.release(), base::Bind(&OnUninstallClicked, base::Owned(new UrlLauncherImpl(web_browser_))))); if (infobar_manager->Show(infobar_content.release(), TOP_INFOBAR)) { // Update state in the registry that the prompt was shown. reshow_state.MarkShown(base::Time::Now()); } } } void BrowserObserver::Hide() { InfobarManager* infobar_manager = GetInfobarManager(); if (infobar_manager) infobar_manager->HideAll(); } InfobarManager* BrowserObserver::GetInfobarManager() { HRESULT hr = NOERROR; base::win::ScopedComPtr 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_browser_hwnd = NULL; hr = ole_window->GetWindow(&web_browser_hwnd); if (FAILED(hr) || web_browser_hwnd == NULL) { DLOG(ERROR) << "Failed to query HWND from IOleWindow. " << "Error: " << hr; return NULL; } return InfobarManager::Get(web_browser_hwnd); } // Returns true if the module into which this code is linked is installed at // system-level. bool IsCurrentModuleSystemLevel() { base::FilePath dll_path; if (PathService::Get(base::DIR_MODULE, &dll_path)) return !InstallUtil::IsPerUserInstall(dll_path.value().c_str()); return false; } // Attempts to create a ReadyModeWebBrowserAdapter instance. bool CreateWebBrowserAdapter(ReadyModeWebBrowserAdapter** adapter) { *adapter = NULL; CComObject* com_object; HRESULT hr = CComObject::CreateInstance(&com_object); if (FAILED(hr)) { DLOG(ERROR) << "Failed to create instance of ReadyModeWebBrowserAdapter. " << "Error: " << hr; return false; } com_object->AddRef(); *adapter = com_object; return true; } // Attempts to install Turndown prompts in the provided web browser. bool InstallPrompts(IWebBrowser2* web_browser) { base::win::ScopedComPtr adapter; if (!CreateWebBrowserAdapter(adapter.Receive())) return false; // Pass ownership of our delegate to the BrowserObserver scoped_ptr browser_observer( new BrowserObserver(web_browser, adapter)); // Owns the BrowserObserver return adapter->Initialize(web_browser, browser_observer.release()); } // Launches the Chrome Frame uninstaller in response to user action. This // implementation should not be used to uninstall MSI-based versions of GCF, // since those must be removed by way of Windows Installer machinery. void LaunchChromeFrameUninstaller() { BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_FRAME); const bool system_level = IsCurrentModuleSystemLevel(); installer::ProductState product_state; if (!product_state.Initialize(system_level, dist)) { DLOG(ERROR) << "Chrome frame isn't installed at " << (system_level ? "system" : "user") << " level."; return; } CommandLine uninstall_command(product_state.uninstall_command()); if (uninstall_command.GetProgram().empty()) { DLOG(ERROR) << "No uninstall command found in registry."; return; } // Force Uninstall silences the prompt to reboot to complete uninstall. uninstall_command.AppendSwitch(installer::switches::kForceUninstall); VLOG(1) << "Uninstalling Chrome Frame with command: " << uninstall_command.GetCommandLineString(); base::LaunchProcess(uninstall_command, base::LaunchOptions(), NULL); } void LaunchLearnMoreURL(UrlLauncher* url_launcher) { url_launcher->LaunchUrl(SimpleResourceLoader::Get( IDS_CHROME_FRAME_TURNDOWN_LEARN_MORE_URL)); } void OnUninstallClicked(UrlLauncher* url_launcher) { LaunchChromeFrameUninstaller(); LaunchLearnMoreURL(url_launcher); } } // namespace namespace turndown_prompt { bool IsPromptSuppressed() { // See if this is an MSI install of GCF or if updates have been disabled. BrowserDistribution* dist = BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_FRAME); const bool system_level = IsCurrentModuleSystemLevel(); bool multi_install = false; installer::ProductState product_state; if (product_state.Initialize(system_level, dist)) { if (product_state.is_msi()) return true; multi_install = product_state.is_multi_install(); } if (multi_install) { dist = BrowserDistribution::GetSpecificDistribution( BrowserDistribution::CHROME_BINARIES); } if (GoogleUpdateSettings::GetAppUpdatePolicy(dist->GetAppGuid(), NULL) == GoogleUpdateSettings::UPDATES_DISABLED) { return true; } // See if the prompt is explicitly suppressed via GP. return PolicySettings::GetInstance()->suppress_turndown_prompt(); } void Configure(IWebBrowser2* web_browser) { if (!IsPromptSuppressed()) InstallPrompts(web_browser); } } // namespace turndown_prompt