text: // 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/browser/plugins/plugin_observer.h"
  
  #include <utility>
  
  #include "base/auto_reset.h"
  #include "base/bind.h"
  #include "base/debug/crash_logging.h"
  #include "base/memory/ptr_util.h"
  #include "base/metrics/histogram_macros.h"
  #include "base/strings/utf_string_conversions.h"
  #include "build/build_config.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/infobars/infobar_service.h"
  #include "chrome/browser/lifetime/application_lifetime.h"
  #include "chrome/browser/plugins/flash_download_interception.h"
  #include "chrome/browser/plugins/plugin_finder.h"
  #include "chrome/browser/plugins/plugin_infobar_delegates.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
  #include "chrome/common/features.h"
  #include "chrome/common/render_messages.h"
  #include "chrome/grit/generated_resources.h"
  #include "chrome/grit/theme_resources.h"
  #include "components/component_updater/component_updater_service.h"
  #include "components/content_settings/core/browser/host_content_settings_map.h"
  #include "components/infobars/core/confirm_infobar_delegate.h"
  #include "components/infobars/core/infobar.h"
  #include "components/infobars/core/infobar_delegate.h"
  #include "components/infobars/core/simple_alert_infobar_delegate.h"
  #include "components/metrics_services_manager/metrics_services_manager.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/plugin_service.h"
  #include "content/public/browser/render_frame_host.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_delegate.h"
  #include "content/public/common/webplugininfo.h"
  #include "ui/base/l10n/l10n_util.h"
  #include "ui/gfx/vector_icons_public.h"
  
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  #include "chrome/browser/plugins/plugin_installer.h"
  #include "chrome/browser/plugins/plugin_installer_observer.h"
  #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
  #endif  // BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  
  using content::PluginService;
  
  DEFINE_WEB_CONTENTS_USER_DATA_KEY(PluginObserver);
  
  namespace {
  
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  
  // ConfirmInstallDialogDelegate ------------------------------------------------
  
  class ConfirmInstallDialogDelegate : public TabModalConfirmDialogDelegate,
                                       public WeakPluginInstallerObserver {
   public:
    ConfirmInstallDialogDelegate(content::WebContents* web_contents,
                                 PluginInstaller* installer,
                                 std::unique_ptr<PluginMetadata> plugin_metadata);
  
    // TabModalConfirmDialogDelegate methods:
    base::string16 GetTitle() override;
    base::string16 GetDialogMessage() override;
    base::string16 GetAcceptButtonTitle() override;
    void OnAccepted() override;
    void OnCanceled() override;
  
    // WeakPluginInstallerObserver methods:
    void DownloadStarted() override;
    void OnlyWeakObserversLeft() override;
  
   private:
    content::WebContents* web_contents_;
    std::unique_ptr<PluginMetadata> plugin_metadata_;
  };
  
  ConfirmInstallDialogDelegate::ConfirmInstallDialogDelegate(
      content::WebContents* web_contents,
      PluginInstaller* installer,
      std::unique_ptr<PluginMetadata> plugin_metadata)
      : TabModalConfirmDialogDelegate(web_contents),
        WeakPluginInstallerObserver(installer),
        web_contents_(web_contents),
        plugin_metadata_(std::move(plugin_metadata)) {}
  
  base::string16 ConfirmInstallDialogDelegate::GetTitle() {
    return l10n_util::GetStringFUTF16(
        IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_TITLE, plugin_metadata_->name());
  }
  
  base::string16 ConfirmInstallDialogDelegate::GetDialogMessage() {
    return l10n_util::GetStringFUTF16(IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_MSG,
                                      plugin_metadata_->name());
  }
  
  base::string16 ConfirmInstallDialogDelegate::GetAcceptButtonTitle() {
    return l10n_util::GetStringUTF16(
        IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_ACCEPT_BUTTON);
  }
  
  void ConfirmInstallDialogDelegate::OnAccepted() {
    installer()->StartInstalling(plugin_metadata_->plugin_url(), web_contents_);
  }
  
  void ConfirmInstallDialogDelegate::OnCanceled() {
  }
  
  void ConfirmInstallDialogDelegate::DownloadStarted() {
    Cancel();
  }
  
  void ConfirmInstallDialogDelegate::OnlyWeakObserversLeft() {
    Cancel();
  }
  #endif  // BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  
  // ReloadPluginInfoBarDelegate -------------------------------------------------
  
  class ReloadPluginInfoBarDelegate : public ConfirmInfoBarDelegate {
   public:
    static void Create(InfoBarService* infobar_service,
                       content::NavigationController* controller,
                       const base::string16& message);
  
   private:
    ReloadPluginInfoBarDelegate(content::NavigationController* controller,
                                const base::string16& message);
    ~ReloadPluginInfoBarDelegate() override;
  
    // ConfirmInfobarDelegate:
    infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
    gfx::VectorIconId GetVectorIconId() const override;
    base::string16 GetMessageText() const override;
    int GetButtons() const override;
    base::string16 GetButtonLabel(InfoBarButton button) const override;
    bool Accept() override;
  
    content::NavigationController* controller_;
    base::string16 message_;
  };
  
  // static
  void ReloadPluginInfoBarDelegate::Create(
      InfoBarService* infobar_service,
      content::NavigationController* controller,
      const base::string16& message) {
    infobar_service->AddInfoBar(infobar_service->CreateConfirmInfoBar(
        std::unique_ptr<ConfirmInfoBarDelegate>(
            new ReloadPluginInfoBarDelegate(controller, message))));
  }
  
  ReloadPluginInfoBarDelegate::ReloadPluginInfoBarDelegate(
      content::NavigationController* controller,
      const base::string16& message)
      : controller_(controller),
        message_(message) {}
  
  ReloadPluginInfoBarDelegate::~ReloadPluginInfoBarDelegate() {}
  
  infobars::InfoBarDelegate::InfoBarIdentifier
  ReloadPluginInfoBarDelegate::GetIdentifier() const {
    return RELOAD_PLUGIN_INFOBAR_DELEGATE;
  }
  
  gfx::VectorIconId ReloadPluginInfoBarDelegate::GetVectorIconId() const {
    return gfx::VectorIconId::EXTENSION_CRASHED;
  }
  
  base::string16 ReloadPluginInfoBarDelegate::GetMessageText() const {
    return message_;
  }
  
  int ReloadPluginInfoBarDelegate::GetButtons() const {
    return BUTTON_OK;
  }
  
  base::string16 ReloadPluginInfoBarDelegate::GetButtonLabel(
      InfoBarButton button) const {
    DCHECK_EQ(BUTTON_OK, button);
    return l10n_util::GetStringUTF16(IDS_RELOAD_PAGE_WITH_PLUGIN);
  }
  
  bool ReloadPluginInfoBarDelegate::Accept() {
    controller_->Reload(content::ReloadType::NORMAL, true);
    return true;
  }
  
  }  // namespace
  
  // PluginObserver -------------------------------------------------------------
  
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  class PluginObserver::PluginPlaceholderHost : public PluginInstallerObserver {
   public:
    PluginPlaceholderHost(PluginObserver* observer,
                          int routing_id,
                          base::string16 plugin_name,
                          PluginInstaller* installer)
        : PluginInstallerObserver(installer),
          observer_(observer),
          routing_id_(routing_id) {
      DCHECK(installer);
      switch (installer->state()) {
        case PluginInstaller::INSTALLER_STATE_IDLE: {
          observer->Send(new ChromeViewMsg_FoundMissingPlugin(routing_id_,
                                                              plugin_name));
          break;
        }
        case PluginInstaller::INSTALLER_STATE_DOWNLOADING: {
          DownloadStarted();
          break;
        }
      }
    }
  
    // PluginInstallerObserver methods:
    void DownloadStarted() override {
      observer_->Send(new ChromeViewMsg_StartedDownloadingPlugin(routing_id_));
    }
  
    void DownloadError(const std::string& msg) override {
      observer_->Send(new ChromeViewMsg_ErrorDownloadingPlugin(routing_id_, msg));
    }
  
    void DownloadCancelled() override {
      observer_->Send(new ChromeViewMsg_CancelledDownloadingPlugin(routing_id_));
    }
  
    void DownloadFinished() override {
      observer_->Send(new ChromeViewMsg_FinishedDownloadingPlugin(routing_id_));
    }
  
   private:
    // Weak pointer; owns us.
    PluginObserver* observer_;
  
    int routing_id_;
  };
  #endif  // BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  
  class PluginObserver::ComponentObserver
      : public update_client::UpdateClient::Observer {
   public:
    using Events = update_client::UpdateClient::Observer::Events;
    ComponentObserver(PluginObserver* observer,
                      int routing_id,
                      const std::string& component_id)
        : observer_(observer),
          routing_id_(routing_id),
          component_id_(component_id) {
      g_browser_process->component_updater()->AddObserver(this);
    }
  
    ~ComponentObserver() override {
      g_browser_process->component_updater()->RemoveObserver(this);
    }
  
    void OnEvent(Events event, const std::string& id) override {
      if (id != component_id_)
        return;
      switch (event) {
        case Events::COMPONENT_UPDATED:
          observer_->Send(
              new ChromeViewMsg_PluginComponentUpdateSuccess(routing_id_));
          observer_->RemoveComponentObserver(routing_id_);
          break;
        case Events::COMPONENT_UPDATE_FOUND:
          observer_->Send(
              new ChromeViewMsg_PluginComponentUpdateDownloading(routing_id_));
          break;
        case Events::COMPONENT_NOT_UPDATED:
          observer_->Send(
              new ChromeViewMsg_PluginComponentUpdateFailure(routing_id_));
          observer_->RemoveComponentObserver(routing_id_);
          break;
        default:
          // No message to send.
          break;
      }
    }
  
   private:
    PluginObserver* observer_;
    int routing_id_;
    std::string component_id_;
    DISALLOW_COPY_AND_ASSIGN(ComponentObserver);
  };
  
  PluginObserver::PluginObserver(content::WebContents* web_contents)
      : content::WebContentsObserver(web_contents),
        weak_ptr_factory_(this) {
  }
  
  PluginObserver::~PluginObserver() {
  }
  
  void PluginObserver::PluginCrashed(const base::FilePath& plugin_path,
                                     base::ProcessId plugin_pid) {
    DCHECK(!plugin_path.value().empty());
  
    base::string16 plugin_name =
        PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    base::string16 infobar_text;
  #if defined(OS_WIN)
    // Find out whether the plugin process is still alive.
    // Note: Although the chances are slim, it is possible that after the plugin
    // process died, |plugin_pid| has been reused by a new process. The
    // consequence is that we will display |IDS_PLUGIN_DISCONNECTED_PROMPT| rather
    // than |IDS_PLUGIN_CRASHED_PROMPT| to the user, which seems acceptable.
    base::Process plugin_process =
        base::Process::OpenWithAccess(plugin_pid,
                                      PROCESS_QUERY_INFORMATION | SYNCHRONIZE);
    bool is_running = false;
    if (plugin_process.IsValid()) {
      is_running =
          base::GetTerminationStatus(plugin_process.Handle(), NULL) ==
              base::TERMINATION_STATUS_STILL_RUNNING;
      plugin_process.Close();
    }
  
    if (is_running) {
      infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_DISCONNECTED_PROMPT,
                                                plugin_name);
      UMA_HISTOGRAM_COUNTS("Plugin.ShowDisconnectedInfobar", 1);
    } else {
      infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
                                                plugin_name);
      UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
    }
  #else
    // Calling the POSIX version of base::GetTerminationStatus() may affect other
    // code which is interested in the process termination status. (Please see the
    // comment of the function.) Therefore, a better way is needed to distinguish
    // disconnections from crashes.
    infobar_text = l10n_util::GetStringFUTF16(IDS_PLUGIN_CRASHED_PROMPT,
                                              plugin_name);
    UMA_HISTOGRAM_COUNTS("Plugin.ShowCrashedInfobar", 1);
  #endif
  
    ReloadPluginInfoBarDelegate::Create(
        InfoBarService::FromWebContents(web_contents()),
        &web_contents()->GetController(),
        infobar_text);
  }
  
  bool PluginObserver::OnMessageReceived(
        const IPC::Message& message,
        content::RenderFrameHost* render_frame_host) {
    IPC_BEGIN_MESSAGE_MAP(PluginObserver, message)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedOutdatedPlugin,
                          OnBlockedOutdatedPlugin)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_BlockedComponentUpdatedPlugin,
                          OnBlockedComponentUpdatedPlugin)
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RemovePluginPlaceholderHost,
                          OnRemovePluginPlaceholderHost)
  #endif
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_ShowFlashPermissionBubble,
                          OnShowFlashPermissionBubble)
      IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CouldNotLoadPlugin,
                          OnCouldNotLoadPlugin)
  
      IPC_MESSAGE_UNHANDLED(return false)
    IPC_END_MESSAGE_MAP()
  
    return true;
  }
  
  void PluginObserver::OnBlockedOutdatedPlugin(int placeholder_id,
                                               const std::string& identifier) {
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
    PluginFinder* finder = PluginFinder::GetInstance();
    // Find plugin to update.
    PluginInstaller* installer = NULL;
    std::unique_ptr<PluginMetadata> plugin;
    if (finder->FindPluginWithIdentifier(identifier, &installer, &plugin)) {
      plugin_placeholders_[placeholder_id] =
          base::MakeUnique<PluginPlaceholderHost>(this, placeholder_id,
                                                  plugin->name(), installer);
      OutdatedPluginInfoBarDelegate::Create(
          InfoBarService::FromWebContents(web_contents()), installer,
          std::move(plugin));
    } else {
      NOTREACHED();
    }
  #else
    // If we don't support third-party plugin installation, we shouldn't have
    // outdated plugins.
    NOTREACHED();
  #endif  // BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  }
  
  void PluginObserver::OnBlockedComponentUpdatedPlugin(
      int placeholder_id,
      const std::string& identifier) {
    component_observers_[placeholder_id] =
        base::MakeUnique<ComponentObserver>(this, placeholder_id, identifier);
    g_browser_process->component_updater()->GetOnDemandUpdater().OnDemandUpdate(
        identifier, component_updater::Callback());
  }
  
  void PluginObserver::RemoveComponentObserver(int placeholder_id) {
    auto it = component_observers_.find(placeholder_id);
    DCHECK(it != component_observers_.end());
    component_observers_.erase(it);
  }
  
  #if BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  void PluginObserver::OnRemovePluginPlaceholderHost(int placeholder_id) {
    auto it = plugin_placeholders_.find(placeholder_id);
    if (it == plugin_placeholders_.end()) {
      NOTREACHED();
      return;
    }
    plugin_placeholders_.erase(it);
  }
  #endif  // BUILDFLAG(ENABLE_PLUGIN_INSTALLATION)
  
  void PluginObserver::OnShowFlashPermissionBubble() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  
    FlashDownloadInterception::InterceptFlashDownloadNavigation(
        web_contents(), web_contents()->GetLastCommittedURL());
  }
  
  void PluginObserver::OnCouldNotLoadPlugin(const base::FilePath& plugin_path) {
    g_browser_process->GetMetricsServicesManager()->OnPluginLoadingError(
        plugin_path);
    base::string16 plugin_name =
        PluginService::GetInstance()->GetPluginDisplayNameByPath(plugin_path);
    SimpleAlertInfoBarDelegate::Create(
        InfoBarService::FromWebContents(web_contents()),
        infobars::InfoBarDelegate::PLUGIN_OBSERVER, 0,
        gfx::VectorIconId::EXTENSION_CRASHED,
        l10n_util::GetStringFUTF16(IDS_PLUGIN_INITIALIZATION_ERROR_PROMPT,
                                   plugin_name),
        true);
  }
  