Home | History | Annotate | Download | only in plugins
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/plugins/plugin_observer.h"
      6 
      7 #include "base/auto_reset.h"
      8 #include "base/bind.h"
      9 #include "base/debug/crash_logging.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/stl_util.h"
     12 #include "base/strings/utf_string_conversions.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/content_settings/host_content_settings_map.h"
     15 #include "chrome/browser/infobars/infobar_service.h"
     16 #include "chrome/browser/infobars/simple_alert_infobar_delegate.h"
     17 #include "chrome/browser/lifetime/application_lifetime.h"
     18 #include "chrome/browser/metrics/metrics_services_manager.h"
     19 #include "chrome/browser/plugins/plugin_finder.h"
     20 #include "chrome/browser/plugins/plugin_infobar_delegates.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
     23 #include "chrome/common/render_messages.h"
     24 #include "chrome/common/url_constants.h"
     25 #include "components/infobars/core/confirm_infobar_delegate.h"
     26 #include "components/infobars/core/infobar.h"
     27 #include "content/public/browser/plugin_service.h"
     28 #include "content/public/browser/render_frame_host.h"
     29 #include "content/public/browser/render_view_host.h"
     30 #include "content/public/browser/web_contents.h"
     31 #include "content/public/browser/web_contents_delegate.h"
     32 #include "content/public/common/webplugininfo.h"
     33 #include "grit/generated_resources.h"
     34 #include "grit/theme_resources.h"
     35 #include "ui/base/l10n/l10n_util.h"
     36 
     37 #if defined(ENABLE_PLUGIN_INSTALLATION)
     38 #if defined(OS_WIN)
     39 #include "base/win/metro.h"
     40 #endif
     41 #include "chrome/browser/plugins/plugin_installer.h"
     42 #include "chrome/browser/plugins/plugin_installer_observer.h"
     43 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
     44 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
     45 
     46 using content::OpenURLParams;
     47 using content::PluginService;
     48 using content::Referrer;
     49 using content::WebContents;
     50 
     51 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PluginObserver);
     52 
     53 namespace {
     54 
     55 #if defined(ENABLE_PLUGIN_INSTALLATION)
     56 
     57 // ConfirmInstallDialogDelegate ------------------------------------------------
     58 
     59 class ConfirmInstallDialogDelegate : public TabModalConfirmDialogDelegate,
     60                                      public WeakPluginInstallerObserver {
     61  public:
     62   ConfirmInstallDialogDelegate(content::WebContents* web_contents,
     63                                PluginInstaller* installer,
     64                                scoped_ptr<PluginMetadata> plugin_metadata);
     65 
     66   // TabModalConfirmDialogDelegate methods:
     67   virtual base::string16 GetTitle() OVERRIDE;
     68   virtual base::string16 GetDialogMessage() OVERRIDE;
     69   virtual base::string16 GetAcceptButtonTitle() OVERRIDE;
     70   virtual void OnAccepted() OVERRIDE;
     71   virtual void OnCanceled() OVERRIDE;
     72 
     73   // WeakPluginInstallerObserver methods:
     74   virtual void DownloadStarted() OVERRIDE;
     75   virtual void OnlyWeakObserversLeft() OVERRIDE;
     76 
     77  private:
     78   content::WebContents* web_contents_;
     79   scoped_ptr<PluginMetadata> plugin_metadata_;
     80 };
     81 
     82 ConfirmInstallDialogDelegate::ConfirmInstallDialogDelegate(
     83     content::WebContents* web_contents,
     84     PluginInstaller* installer,
     85     scoped_ptr<PluginMetadata> plugin_metadata)
     86     : TabModalConfirmDialogDelegate(web_contents),
     87       WeakPluginInstallerObserver(installer),
     88       web_contents_(web_contents),
     89       plugin_metadata_(plugin_metadata.Pass()) {
     90 }
     91 
     92 base::string16 ConfirmInstallDialogDelegate::GetTitle() {
     93   return l10n_util::GetStringFUTF16(
     94       IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_TITLE, plugin_metadata_->name());
     95 }
     96 
     97 base::string16 ConfirmInstallDialogDelegate::GetDialogMessage() {
     98   return l10n_util::GetStringFUTF16(IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_MSG,
     99                                     plugin_metadata_->name());
    100 }
    101 
    102 base::string16 ConfirmInstallDialogDelegate::GetAcceptButtonTitle() {
    103   return l10n_util::GetStringUTF16(
    104       IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_ACCEPT_BUTTON);
    105 }
    106 
    107 void ConfirmInstallDialogDelegate::OnAccepted() {
    108   installer()->StartInstalling(plugin_metadata_->plugin_url(), web_contents_);
    109 }
    110 
    111 void ConfirmInstallDialogDelegate::OnCanceled() {
    112 }
    113 
    114 void ConfirmInstallDialogDelegate::DownloadStarted() {
    115   Cancel();
    116 }
    117 
    118 void ConfirmInstallDialogDelegate::OnlyWeakObserversLeft() {
    119   Cancel();
    120 }
    121 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    122 
    123 // ReloadPluginInfoBarDelegate -------------------------------------------------
    124 
    125 class ReloadPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
    126  public:
    127   static void Create(InfoBarService* infobar_service,
    128                      content::NavigationController* controller,
    129                      const base::string16& message);
    130 
    131  private:
    132   ReloadPluginInfoBarDelegate(content::NavigationController* controller,
    133                               const base::string16& message);
    134   virtual ~ReloadPluginInfoBarDelegate();
    135 
    136   // ConfirmInfobarDelegate:
    137   virtual int GetIconID() const OVERRIDE;
    138   virtual base::string16 GetMessageText() const OVERRIDE;
    139   virtual int GetButtons() const OVERRIDE;
    140   virtual base::string16 GetButtonLabel(InfoBarButton button) const OVERRIDE;
    141   virtual bool Accept() OVERRIDE;
    142 
    143   content::NavigationController* controller_;
    144   base::string16 message_;
    145 };
    146 
    147 // static
    148 void ReloadPluginInfoBarDelegate::Create(
    149     InfoBarService* infobar_service,
    150     content::NavigationController* controller,
    151     const base::string16& message) {
    152   infobar_service->AddInfoBar(
    153       ConfirmInfoBarDelegate::CreateInfoBar(scoped_ptr<ConfirmInfoBarDelegate>(
    154           new ReloadPluginInfoBarDelegate(controller, message))));
    155 }
    156 
    157 ReloadPluginInfoBarDelegate::ReloadPluginInfoBarDelegate(
    158     content::NavigationController* controller,
    159     const base::string16& message)
    160     : controller_(controller),
    161       message_(message) {}
    162 
    163 ReloadPluginInfoBarDelegate::~ReloadPluginInfoBarDelegate(){ }
    164 
    165 int ReloadPluginInfoBarDelegate::GetIconID() const {
    166   return IDR_INFOBAR_PLUGIN_CRASHED;
    167 }
    168 
    169 base::string16 ReloadPluginInfoBarDelegate::GetMessageText() const {
    170   return message_;
    171 }
    172 
    173 int ReloadPluginInfoBarDelegate::GetButtons() const {
    174   return BUTTON_OK;
    175 }
    176 
    177 base::string16 ReloadPluginInfoBarDelegate::GetButtonLabel(
    178     InfoBarButton button) const {
    179   DCHECK_EQ(BUTTON_OK, button);
    180   return l10n_util::GetStringUTF16(IDS_RELOAD_PAGE_WITH_PLUGIN);
    181 }
    182 
    183 bool ReloadPluginInfoBarDelegate::Accept() {
    184   controller_->Reload(true);
    185   return true;
    186 }
    187 
    188 }  // namespace
    189 
    190 // PluginObserver -------------------------------------------------------------
    191 
    192 #if defined(ENABLE_PLUGIN_INSTALLATION)
    193 class PluginObserver::PluginPlaceholderHost : public PluginInstallerObserver {
    194  public:
    195   PluginPlaceholderHost(PluginObserver* observer,
    196                         int routing_id,
    197                         base::string16 plugin_name,
    198                         PluginInstaller* installer)
    199       : PluginInstallerObserver(installer),
    200         observer_(observer),
    201         routing_id_(routing_id) {
    202     DCHECK(installer);
    203     switch (installer->state()) {
    204       case PluginInstaller::INSTALLER_STATE_IDLE: {
    205         observer->Send(new ChromeViewMsg_FoundMissingPlugin(routing_id_,
    206                                                             plugin_name));
    207         break;
    208       }
    209       case PluginInstaller::INSTALLER_STATE_DOWNLOADING: {
    210         DownloadStarted();
    211         break;
    212       }
    213     }
    214   }
    215 
    216   // PluginInstallerObserver methods:
    217   virtual void DownloadStarted() OVERRIDE {
    218     observer_->Send(new ChromeViewMsg_StartedDownloadingPlugin(routing_id_));
    219   }
    220 
    221   virtual void DownloadError(const std::string& msg) OVERRIDE {
    222     observer_->Send(new ChromeViewMsg_ErrorDownloadingPlugin(routing_id_, msg));
    223   }
    224 
    225   virtual void DownloadCancelled() OVERRIDE {
    226     observer_->Send(new ChromeViewMsg_CancelledDownloadingPlugin(routing_id_));
    227   }
    228 
    229   virtual void DownloadFinished() OVERRIDE {
    230     observer_->Send(new ChromeViewMsg_FinishedDownloadingPlugin(routing_id_));
    231   }
    232 
    233  private:
    234   // Weak pointer; owns us.
    235   PluginObserver* observer_;
    236 
    237   int routing_id_;
    238 };
    239 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    240 
    241 PluginObserver::PluginObserver(content::WebContents* web_contents)
    242     : content::WebContentsObserver(web_contents),
    243       weak_ptr_factory_(this) {
    244 }
    245 
    246 PluginObserver::~PluginObserver() {
    247 #if defined(ENABLE_PLUGIN_INSTALLATION)
    248   STLDeleteValues(&plugin_placeholders_);
    249 #endif
    250 }
    251 
    252 void PluginObserver::RenderFrameCreated(
    253     content::RenderFrameHost* render_frame_host) {
    254 #if defined(OS_WIN)
    255   // If the window belongs to the Ash desktop, before we navigate we need
    256   // to tell the renderview that NPAPI plugins are not supported so it does
    257   // not try to instantiate them. The final decision is actually done in
    258   // the IO thread by PluginInfoMessageFilter of this proces,s but it's more
    259   // complex to manage a map of Ash views in PluginInfoMessageFilter than
    260   // just telling the renderer via IPC.
    261 
    262   // TODO(shrikant): Implement solution which will help associate
    263   // render_view_host/webcontents/view/window instance with host desktop.
    264   // Refer to issue http://crbug.com/317940.
    265   // When non-active tabs are restored they are not added in view/window parent
    266   // hierarchy (chrome::CreateRestoredTab/CreateParams). Normally we traverse
    267   // parent hierarchy to identify containing desktop (like in function
    268   // chrome::GetHostDesktopTypeForNativeView).
    269   // Possible issue with chrome::GetActiveDesktop, is that it's global
    270   // state, which remembers last active desktop, which may break in scenarios
    271   // where we have instances on both Ash and Native desktop.
    272 
    273   // We will do both tests. Both have some factor of unreliability.
    274   aura::Window* window = web_contents()->GetNativeView();
    275   if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH ||
    276       chrome::GetHostDesktopTypeForNativeView(window) ==
    277       chrome::HOST_DESKTOP_TYPE_ASH) {
    278     int routing_id = render_frame_host->GetRoutingID();
    279     render_frame_host->Send(new ChromeViewMsg_NPAPINotSupported(routing_id));
    280   }
    281 #endif
    282 }
    283 
    284 void PluginObserver::PluginCrashed(const base::FilePath& plugin_path,
    285                                    base::ProcessId plugin_pid) {
    286   DCHECK(!plugin_path.value().empty());
    287 
    288   base::string16 plugin_name =
    289       PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    290   base::string16 infobar_text;
    291 #if defined(OS_WIN)
    292   // Find out whether the plugin process is still alive.
    293   // Note: Although the chances are slim, it is possible that after the plugin
    294   // process died, |plugin_pid| has been reused by a new process. The
    295   // consequence is that we will display |IDS_PLUGIN_DISCONNECTED_PROMPT| rather
    296   // than |IDS_PLUGIN_CRASHED_PROMPT| to the user, which seems acceptable.
    297   base::ProcessHandle plugin_handle = base::kNullProcessHandle;
    298   bool open_result = base::OpenProcessHandleWithAccess(
    299       plugin_pid, PROCESS_QUERY_INFORMATION | SYNCHRONIZE, &plugin_handle);
    300   bool is_running = false;
    301   if (open_result) {
    302     is_running = base::GetTerminationStatus(plugin_handle, NULL) ==
    303         base::TERMINATION_STATUS_STILL_RUNNING;
    304     base::CloseProcessHandle(plugin_handle);
    305   }
    306 
    307   if (is_running) {
    308     infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_DISCONNECTED_PROMPT,
    309                                               plugin_name);
    310     UMA_HISTOGRAM_COUNTS("Plugin.ShowDisconnectedInfobar", 1);
    311   } else {
    312     infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
    313                                               plugin_name);
    314     UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
    315   }
    316 #else
    317   // Calling the POSIX version of base::GetTerminationStatus() may affect other
    318   // code which is interested in the process termination status. (Please see the
    319   // comment of the function.) Therefore, a better way is needed to distinguish
    320   // disconnections from crashes.
    321   infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
    322                                             plugin_name);
    323   UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
    324 #endif
    325 
    326   ReloadPluginInfoBarDelegate::Create(
    327       InfoBarService::FromWebContents(web_contents()),
    328       &web_contents()->GetController(),
    329       infobar_text);
    330 }
    331 
    332 bool PluginObserver::OnMessageReceived(
    333       const IPC::Message& message,
    334       content::RenderFrameHost* render_frame_host) {
    335   IPC_BEGIN_MESSAGE_MAP(PluginObserver, message)
    336     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedOutdatedPlugin,
    337                         OnBlockedOutdatedPlugin)
    338     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedUnauthorizedPlugin,
    339                         OnBlockedUnauthorizedPlugin)
    340     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NPAPINotSupported,
    341                         OnNPAPINotSupported)
    342 #if defined(ENABLE_PLUGIN_INSTALLATION)
    343     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_FindMissingPlugin,
    344                         OnFindMissingPlugin)
    345 #endif
    346 
    347     IPC_MESSAGE_UNHANDLED(return false)
    348   IPC_END_MESSAGE_MAP()
    349 
    350   return true;
    351 }
    352 
    353 bool PluginObserver::OnMessageReceived(const IPC::Message& message) {
    354   IPC_BEGIN_MESSAGE_MAP(PluginObserver, message)
    355 #if defined(ENABLE_PLUGIN_INSTALLATION)
    356     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RemovePluginPlaceholderHost,
    357                         OnRemovePluginPlaceholderHost)
    358 #endif
    359     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_OpenAboutPlugins,
    360                         OnOpenAboutPlugins)
    361     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CouldNotLoadPlugin,
    362                         OnCouldNotLoadPlugin)
    363 
    364     IPC_MESSAGE_UNHANDLED(return false)
    365   IPC_END_MESSAGE_MAP()
    366 
    367   return true;
    368 }
    369 
    370 void PluginObserver::OnBlockedUnauthorizedPlugin(
    371     const base::string16& name,
    372     const std::string& identifier) {
    373   UnauthorizedPluginInfoBarDelegate::Create(
    374       InfoBarService::FromWebContents(web_contents()),
    375       Profile::FromBrowserContext(web_contents()->GetBrowserContext())->
    376           GetHostContentSettingsMap(),
    377       name, identifier);
    378 }
    379 
    380 void PluginObserver::OnBlockedOutdatedPlugin(int placeholder_id,
    381                                              const std::string& identifier) {
    382 #if defined(ENABLE_PLUGIN_INSTALLATION)
    383   PluginFinder* finder = PluginFinder::GetInstance();
    384   // Find plugin to update.
    385   PluginInstaller* installer = NULL;
    386   scoped_ptr<PluginMetadata> plugin;
    387   if (finder->FindPluginWithIdentifier(identifier, &installer, &plugin)) {
    388     plugin_placeholders_[placeholder_id] = new PluginPlaceholderHost(
    389         this, placeholder_id, plugin->name(), installer);
    390     OutdatedPluginInfoBarDelegate::Create(InfoBarService::FromWebContents(
    391         web_contents()), installer, plugin.Pass());
    392   } else {
    393     NOTREACHED();
    394   }
    395 #else
    396   // If we don't support third-party plug-in installation, we shouldn't have
    397   // outdated plug-ins.
    398   NOTREACHED();
    399 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    400 }
    401 
    402 #if defined(ENABLE_PLUGIN_INSTALLATION)
    403 void PluginObserver::OnFindMissingPlugin(int placeholder_id,
    404                                          const std::string& mime_type) {
    405   std::string lang = "en-US";  // Oh yes.
    406   scoped_ptr<PluginMetadata> plugin_metadata;
    407   PluginInstaller* installer = NULL;
    408   bool found_plugin = PluginFinder::GetInstance()->FindPlugin(
    409       mime_type, lang, &installer, &plugin_metadata);
    410   if (!found_plugin) {
    411     Send(new ChromeViewMsg_DidNotFindMissingPlugin(placeholder_id));
    412     return;
    413   }
    414   DCHECK(installer);
    415   DCHECK(plugin_metadata.get());
    416 
    417   plugin_placeholders_[placeholder_id] =
    418       new PluginPlaceholderHost(this, placeholder_id, plugin_metadata->name(),
    419                                 installer);
    420   PluginInstallerInfoBarDelegate::Create(
    421       InfoBarService::FromWebContents(web_contents()), installer,
    422       plugin_metadata.Pass(),
    423       base::Bind(&PluginObserver::InstallMissingPlugin,
    424                  weak_ptr_factory_.GetWeakPtr(), installer));
    425 }
    426 
    427 void PluginObserver::InstallMissingPlugin(
    428     PluginInstaller* installer,
    429     const PluginMetadata* plugin_metadata) {
    430   if (plugin_metadata->url_for_display()) {
    431     installer->OpenDownloadURL(plugin_metadata->plugin_url(), web_contents());
    432   } else {
    433     TabModalConfirmDialog::Create(
    434         new ConfirmInstallDialogDelegate(
    435             web_contents(), installer, plugin_metadata->Clone()),
    436         web_contents());
    437   }
    438 }
    439 
    440 void PluginObserver::OnRemovePluginPlaceholderHost(int placeholder_id) {
    441   std::map<int, PluginPlaceholderHost*>::iterator it =
    442       plugin_placeholders_.find(placeholder_id);
    443   if (it == plugin_placeholders_.end()) {
    444     NOTREACHED();
    445     return;
    446   }
    447   delete it->second;
    448   plugin_placeholders_.erase(it);
    449 }
    450 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    451 
    452 void PluginObserver::OnOpenAboutPlugins() {
    453   web_contents()->OpenURL(OpenURLParams(
    454       GURL(chrome::kChromeUIPluginsURL),
    455       content::Referrer(web_contents()->GetURL(),
    456                         blink::WebReferrerPolicyDefault),
    457       NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
    458 }
    459 
    460 void PluginObserver::OnCouldNotLoadPlugin(const base::FilePath& plugin_path) {
    461   g_browser_process->GetMetricsServicesManager()->OnPluginLoadingError(
    462       plugin_path);
    463   base::string16 plugin_name =
    464       PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    465   SimpleAlertInfoBarDelegate::Create(
    466       InfoBarService::FromWebContents(web_contents()),
    467       IDR_INFOBAR_PLUGIN_CRASHED,
    468       l10n_util::GetStringFUTF16(IDS_PLUGIN_INITIALIZATION_ERROR_PROMPT,
    469                                  plugin_name),
    470       true);
    471 }
    472 
    473 void PluginObserver::OnNPAPINotSupported(const std::string& identifier) {
    474 #if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION)
    475 #if !defined(USE_AURA)
    476   DCHECK(base::win::IsMetroProcess());
    477 #endif
    478 
    479   Profile* profile =
    480       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    481   if (profile->IsOffTheRecord())
    482     return;
    483   HostContentSettingsMap* content_settings =
    484       profile->GetHostContentSettingsMap();
    485   if (content_settings->GetContentSetting(
    486       web_contents()->GetURL(),
    487       web_contents()->GetURL(),
    488       CONTENT_SETTINGS_TYPE_METRO_SWITCH_TO_DESKTOP,
    489       std::string()) == CONTENT_SETTING_BLOCK)
    490     return;
    491 
    492   scoped_ptr<PluginMetadata> plugin;
    493   bool ret = PluginFinder::GetInstance()->FindPluginWithIdentifier(
    494       identifier, NULL, &plugin);
    495   DCHECK(ret);
    496 
    497   PluginMetroModeInfoBarDelegate::Create(
    498       InfoBarService::FromWebContents(web_contents()),
    499       PluginMetroModeInfoBarDelegate::DESKTOP_MODE_REQUIRED, plugin->name());
    500 #endif
    501 }
    502