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/confirm_infobar_delegate.h"
     16 #include "chrome/browser/infobars/infobar_service.h"
     17 #include "chrome/browser/infobars/simple_alert_infobar_delegate.h"
     18 #include "chrome/browser/lifetime/application_lifetime.h"
     19 #include "chrome/browser/metrics/metrics_service.h"
     20 #include "chrome/browser/plugins/plugin_finder.h"
     21 #include "chrome/browser/plugins/plugin_infobar_delegates.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
     24 #include "chrome/common/render_messages.h"
     25 #include "chrome/common/url_constants.h"
     26 #include "content/public/browser/plugin_service.h"
     27 #include "content/public/browser/render_view_host.h"
     28 #include "content/public/browser/web_contents.h"
     29 #include "content/public/browser/web_contents_delegate.h"
     30 #include "content/public/browser/web_contents_view.h"
     31 #include "content/public/common/webplugininfo.h"
     32 #include "grit/generated_resources.h"
     33 #include "grit/theme_resources.h"
     34 #include "ui/base/l10n/l10n_util.h"
     35 
     36 #if defined(ENABLE_PLUGIN_INSTALLATION)
     37 #if defined(OS_WIN)
     38 #include "base/win/metro.h"
     39 #endif
     40 #include "chrome/browser/plugins/plugin_installer.h"
     41 #include "chrome/browser/plugins/plugin_installer_observer.h"
     42 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
     43 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
     44 
     45 using content::OpenURLParams;
     46 using content::PluginService;
     47 using content::Referrer;
     48 using content::WebContents;
     49 
     50 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PluginObserver);
     51 
     52 namespace {
     53 
     54 #if defined(ENABLE_PLUGIN_INSTALLATION)
     55 
     56 // ConfirmInstallDialogDelegate ------------------------------------------------
     57 
     58 class ConfirmInstallDialogDelegate : public TabModalConfirmDialogDelegate,
     59                                      public WeakPluginInstallerObserver {
     60  public:
     61   ConfirmInstallDialogDelegate(content::WebContents* web_contents,
     62                                PluginInstaller* installer,
     63                                scoped_ptr<PluginMetadata> plugin_metadata);
     64 
     65   // TabModalConfirmDialogDelegate methods:
     66   virtual string16 GetTitle() OVERRIDE;
     67   virtual string16 GetMessage() OVERRIDE;
     68   virtual string16 GetAcceptButtonTitle() OVERRIDE;
     69   virtual void OnAccepted() OVERRIDE;
     70   virtual void OnCanceled() OVERRIDE;
     71 
     72   // WeakPluginInstallerObserver methods:
     73   virtual void DownloadStarted() OVERRIDE;
     74   virtual void OnlyWeakObserversLeft() OVERRIDE;
     75 
     76  private:
     77   content::WebContents* web_contents_;
     78   scoped_ptr<PluginMetadata> plugin_metadata_;
     79 };
     80 
     81 ConfirmInstallDialogDelegate::ConfirmInstallDialogDelegate(
     82     content::WebContents* web_contents,
     83     PluginInstaller* installer,
     84     scoped_ptr<PluginMetadata> plugin_metadata)
     85     : TabModalConfirmDialogDelegate(web_contents),
     86       WeakPluginInstallerObserver(installer),
     87       web_contents_(web_contents),
     88       plugin_metadata_(plugin_metadata.Pass()) {
     89 }
     90 
     91 string16 ConfirmInstallDialogDelegate::GetTitle() {
     92   return l10n_util::GetStringFUTF16(
     93       IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_TITLE, plugin_metadata_->name());
     94 }
     95 
     96 string16 ConfirmInstallDialogDelegate::GetMessage() {
     97   return l10n_util::GetStringFUTF16(IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_MSG,
     98                                     plugin_metadata_->name());
     99 }
    100 
    101 string16 ConfirmInstallDialogDelegate::GetAcceptButtonTitle() {
    102   return l10n_util::GetStringUTF16(
    103       IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_ACCEPT_BUTTON);
    104 }
    105 
    106 void ConfirmInstallDialogDelegate::OnAccepted() {
    107   installer()->StartInstalling(plugin_metadata_->plugin_url(), web_contents_);
    108 }
    109 
    110 void ConfirmInstallDialogDelegate::OnCanceled() {
    111 }
    112 
    113 void ConfirmInstallDialogDelegate::DownloadStarted() {
    114   Cancel();
    115 }
    116 
    117 void ConfirmInstallDialogDelegate::OnlyWeakObserversLeft() {
    118   Cancel();
    119 }
    120 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    121 }  // namespace
    122 
    123 // PluginObserver -------------------------------------------------------------
    124 
    125 #if defined(ENABLE_PLUGIN_INSTALLATION)
    126 class PluginObserver::PluginPlaceholderHost : public PluginInstallerObserver {
    127  public:
    128   PluginPlaceholderHost(PluginObserver* observer,
    129                         int routing_id,
    130                         string16 plugin_name,
    131                         PluginInstaller* installer)
    132       : PluginInstallerObserver(installer),
    133         observer_(observer),
    134         routing_id_(routing_id) {
    135     DCHECK(installer);
    136     switch (installer->state()) {
    137       case PluginInstaller::INSTALLER_STATE_IDLE: {
    138         observer->Send(new ChromeViewMsg_FoundMissingPlugin(routing_id_,
    139                                                             plugin_name));
    140         break;
    141       }
    142       case PluginInstaller::INSTALLER_STATE_DOWNLOADING: {
    143         DownloadStarted();
    144         break;
    145       }
    146     }
    147   }
    148 
    149   // PluginInstallerObserver methods:
    150   virtual void DownloadStarted() OVERRIDE {
    151     observer_->Send(new ChromeViewMsg_StartedDownloadingPlugin(routing_id_));
    152   }
    153 
    154   virtual void DownloadError(const std::string& msg) OVERRIDE {
    155     observer_->Send(new ChromeViewMsg_ErrorDownloadingPlugin(routing_id_, msg));
    156   }
    157 
    158   virtual void DownloadCancelled() OVERRIDE {
    159     observer_->Send(new ChromeViewMsg_CancelledDownloadingPlugin(routing_id_));
    160   }
    161 
    162   virtual void DownloadFinished() OVERRIDE {
    163     observer_->Send(new ChromeViewMsg_FinishedDownloadingPlugin(routing_id_));
    164   }
    165 
    166  private:
    167   // Weak pointer; owns us.
    168   PluginObserver* observer_;
    169 
    170   int routing_id_;
    171 };
    172 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    173 
    174 PluginObserver::PluginObserver(content::WebContents* web_contents)
    175     : content::WebContentsObserver(web_contents),
    176       weak_ptr_factory_(this) {
    177 }
    178 
    179 PluginObserver::~PluginObserver() {
    180 #if defined(ENABLE_PLUGIN_INSTALLATION)
    181   STLDeleteValues(&plugin_placeholders_);
    182 #endif
    183 }
    184 
    185 void PluginObserver::PluginCrashed(const base::FilePath& plugin_path,
    186                                    base::ProcessId plugin_pid) {
    187   DCHECK(!plugin_path.value().empty());
    188 
    189   string16 plugin_name =
    190       PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    191   string16 infobar_text;
    192 #if defined(OS_WIN)
    193   // Find out whether the plugin process is still alive.
    194   // Note: Although the chances are slim, it is possible that after the plugin
    195   // process died, |plugin_pid| has been reused by a new process. The
    196   // consequence is that we will display |IDS_PLUGIN_DISCONNECTED_PROMPT| rather
    197   // than |IDS_PLUGIN_CRASHED_PROMPT| to the user, which seems acceptable.
    198   base::ProcessHandle plugin_handle = base::kNullProcessHandle;
    199   bool open_result = base::OpenProcessHandleWithAccess(
    200       plugin_pid, PROCESS_QUERY_INFORMATION | SYNCHRONIZE, &plugin_handle);
    201   bool is_running = false;
    202   if (open_result) {
    203     is_running = base::GetTerminationStatus(plugin_handle, NULL) ==
    204         base::TERMINATION_STATUS_STILL_RUNNING;
    205     base::CloseProcessHandle(plugin_handle);
    206   }
    207 
    208   if (is_running) {
    209     infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_DISCONNECTED_PROMPT,
    210                                               plugin_name);
    211     UMA_HISTOGRAM_COUNTS("Plugin.ShowDisconnectedInfobar", 1);
    212   } else {
    213     infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
    214                                               plugin_name);
    215     UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
    216   }
    217 #else
    218   // Calling the POSIX version of base::GetTerminationStatus() may affect other
    219   // code which is interested in the process termination status. (Please see the
    220   // comment of the function.) Therefore, a better way is needed to distinguish
    221   // disconnections from crashes.
    222   infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
    223                                             plugin_name);
    224   UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
    225 #endif
    226 
    227   SimpleAlertInfoBarDelegate::Create(
    228       InfoBarService::FromWebContents(web_contents()),
    229       IDR_INFOBAR_PLUGIN_CRASHED, infobar_text, true);
    230 }
    231 
    232 bool PluginObserver::OnMessageReceived(const IPC::Message& message) {
    233   IPC_BEGIN_MESSAGE_MAP(PluginObserver, message)
    234     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedOutdatedPlugin,
    235                         OnBlockedOutdatedPlugin)
    236     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedUnauthorizedPlugin,
    237                         OnBlockedUnauthorizedPlugin)
    238 #if defined(ENABLE_PLUGIN_INSTALLATION)
    239     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_FindMissingPlugin,
    240                         OnFindMissingPlugin)
    241     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RemovePluginPlaceholderHost,
    242                         OnRemovePluginPlaceholderHost)
    243 #endif
    244     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_OpenAboutPlugins,
    245                         OnOpenAboutPlugins)
    246     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CouldNotLoadPlugin,
    247                         OnCouldNotLoadPlugin)
    248     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NPAPINotSupported,
    249                         OnNPAPINotSupported)
    250 
    251     IPC_MESSAGE_UNHANDLED(return false)
    252   IPC_END_MESSAGE_MAP()
    253 
    254   return true;
    255 }
    256 
    257 void PluginObserver::AboutToNavigateRenderView(
    258     content::RenderViewHost* render_view_host) {
    259 #if defined(USE_AURA) && defined(OS_WIN)
    260   // If the window belongs to the Ash desktop, before we navigate we need
    261   // to tell the renderview that NPAPI plugins are not supported so it does
    262   // not try to instantiate them. The final decision is actually done in
    263   // the IO thread by PluginInfoMessageFilter of this proces,s but it's more
    264   // complex to manage a map of Ash views in PluginInfoMessageFilter than
    265   // just telling the renderer via IPC.
    266   if (!web_contents())
    267     return;
    268 
    269   content::WebContentsView* wcv = web_contents()->GetView();
    270   if (!wcv)
    271     return;
    272 
    273   aura::Window* window = wcv->GetNativeView();
    274   if (chrome::GetHostDesktopTypeForNativeView(window) ==
    275       chrome::HOST_DESKTOP_TYPE_ASH) {
    276     int routing_id = render_view_host->GetRoutingID();
    277     render_view_host->Send(new ChromeViewMsg_NPAPINotSupported(routing_id));
    278   }
    279 #endif
    280 }
    281 
    282 void PluginObserver::OnBlockedUnauthorizedPlugin(
    283     const string16& name,
    284     const std::string& identifier) {
    285   UnauthorizedPluginInfoBarDelegate::Create(
    286       InfoBarService::FromWebContents(web_contents()),
    287       Profile::FromBrowserContext(web_contents()->GetBrowserContext())->
    288           GetHostContentSettingsMap(),
    289       name, identifier);
    290 }
    291 
    292 void PluginObserver::OnBlockedOutdatedPlugin(int placeholder_id,
    293                                              const std::string& identifier) {
    294 #if defined(ENABLE_PLUGIN_INSTALLATION)
    295   PluginFinder* finder = PluginFinder::GetInstance();
    296   // Find plugin to update.
    297   PluginInstaller* installer = NULL;
    298   scoped_ptr<PluginMetadata> plugin;
    299   bool ret = finder->FindPluginWithIdentifier(identifier, &installer, &plugin);
    300   DCHECK(ret);
    301 
    302   plugin_placeholders_[placeholder_id] =
    303       new PluginPlaceholderHost(this, placeholder_id,
    304                                 plugin->name(), installer);
    305   OutdatedPluginInfoBarDelegate::Create(
    306       InfoBarService::FromWebContents(web_contents()), installer,
    307       plugin.Pass());
    308 #else
    309   // If we don't support third-party plug-in installation, we shouldn't have
    310   // outdated plug-ins.
    311   NOTREACHED();
    312 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    313 }
    314 
    315 #if defined(ENABLE_PLUGIN_INSTALLATION)
    316 void PluginObserver::OnFindMissingPlugin(int placeholder_id,
    317                                          const std::string& mime_type) {
    318   std::string lang = "en-US";  // Oh yes.
    319   scoped_ptr<PluginMetadata> plugin_metadata;
    320   PluginInstaller* installer = NULL;
    321   bool found_plugin = PluginFinder::GetInstance()->FindPlugin(
    322       mime_type, lang, &installer, &plugin_metadata);
    323   if (!found_plugin) {
    324     Send(new ChromeViewMsg_DidNotFindMissingPlugin(placeholder_id));
    325     return;
    326   }
    327   DCHECK(installer);
    328   DCHECK(plugin_metadata.get());
    329 
    330   plugin_placeholders_[placeholder_id] =
    331       new PluginPlaceholderHost(this, placeholder_id, plugin_metadata->name(),
    332                                 installer);
    333   PluginInstallerInfoBarDelegate::Create(
    334       InfoBarService::FromWebContents(web_contents()), installer,
    335       plugin_metadata.Pass(),
    336       base::Bind(&PluginObserver::InstallMissingPlugin,
    337                  weak_ptr_factory_.GetWeakPtr(), installer));
    338 }
    339 
    340 void PluginObserver::InstallMissingPlugin(
    341     PluginInstaller* installer,
    342     const PluginMetadata* plugin_metadata) {
    343   if (plugin_metadata->url_for_display()) {
    344     installer->OpenDownloadURL(plugin_metadata->plugin_url(), web_contents());
    345   } else {
    346     TabModalConfirmDialog::Create(
    347         new ConfirmInstallDialogDelegate(
    348             web_contents(), installer, plugin_metadata->Clone()),
    349         web_contents());
    350   }
    351 }
    352 
    353 void PluginObserver::OnRemovePluginPlaceholderHost(int placeholder_id) {
    354   std::map<int, PluginPlaceholderHost*>::iterator it =
    355       plugin_placeholders_.find(placeholder_id);
    356   if (it == plugin_placeholders_.end()) {
    357     NOTREACHED();
    358     return;
    359   }
    360   delete it->second;
    361   plugin_placeholders_.erase(it);
    362 }
    363 #endif  // defined(ENABLE_PLUGIN_INSTALLATION)
    364 
    365 void PluginObserver::OnOpenAboutPlugins() {
    366   web_contents()->OpenURL(OpenURLParams(
    367       GURL(chrome::kAboutPluginsURL),
    368       content::Referrer(web_contents()->GetURL(),
    369                         WebKit::WebReferrerPolicyDefault),
    370       NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
    371 }
    372 
    373 void PluginObserver::OnCouldNotLoadPlugin(const base::FilePath& plugin_path) {
    374   g_browser_process->metrics_service()->LogPluginLoadingError(plugin_path);
    375   string16 plugin_name =
    376       PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    377   SimpleAlertInfoBarDelegate::Create(
    378       InfoBarService::FromWebContents(web_contents()),
    379       IDR_INFOBAR_PLUGIN_CRASHED,
    380       l10n_util::GetStringFUTF16(IDS_PLUGIN_INITIALIZATION_ERROR_PROMPT,
    381                                  plugin_name),
    382       true);
    383 }
    384 
    385 void PluginObserver::OnNPAPINotSupported(const std::string& identifier) {
    386 #if defined(OS_WIN) && defined(ENABLE_PLUGIN_INSTALLATION)
    387 #if !defined(USE_AURA)
    388   DCHECK(base::win::IsMetroProcess());
    389 #endif
    390 
    391   Profile* profile =
    392       Profile::FromBrowserContext(web_contents()->GetBrowserContext());
    393   if (profile->IsOffTheRecord())
    394     return;
    395   HostContentSettingsMap* content_settings =
    396       profile->GetHostContentSettingsMap();
    397   if (content_settings->GetContentSetting(
    398       web_contents()->GetURL(),
    399       web_contents()->GetURL(),
    400       CONTENT_SETTINGS_TYPE_METRO_SWITCH_TO_DESKTOP,
    401       std::string()) == CONTENT_SETTING_BLOCK)
    402     return;
    403 
    404   scoped_ptr<PluginMetadata> plugin;
    405   bool ret = PluginFinder::GetInstance()->FindPluginWithIdentifier(
    406       identifier, NULL, &plugin);
    407   DCHECK(ret);
    408 
    409   PluginMetroModeInfoBarDelegate::Create(
    410       InfoBarService::FromWebContents(web_contents()),
    411       PluginMetroModeInfoBarDelegate::DESKTOP_MODE_REQUIRED, plugin->name());
    412 #endif
    413 }
    414