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 "chrome/grit/generated_resources.h"
26#include "components/infobars/core/confirm_infobar_delegate.h"
27#include "components/infobars/core/infobar.h"
28#include "content/public/browser/plugin_service.h"
29#include "content/public/browser/render_frame_host.h"
30#include "content/public/browser/render_view_host.h"
31#include "content/public/browser/web_contents.h"
32#include "content/public/browser/web_contents_delegate.h"
33#include "content/public/common/webplugininfo.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
46using content::OpenURLParams;
47using content::PluginService;
48using content::Referrer;
49using content::WebContents;
50
51DEFINE_WEB_CONTENTS_USER_DATA_KEY(PluginObserver);
52
53namespace {
54
55#if defined(ENABLE_PLUGIN_INSTALLATION)
56
57// ConfirmInstallDialogDelegate ------------------------------------------------
58
59class 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
82ConfirmInstallDialogDelegate::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
92base::string16 ConfirmInstallDialogDelegate::GetTitle() {
93  return l10n_util::GetStringFUTF16(
94      IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_TITLE, plugin_metadata_->name());
95}
96
97base::string16 ConfirmInstallDialogDelegate::GetDialogMessage() {
98  return l10n_util::GetStringFUTF16(IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_MSG,
99                                    plugin_metadata_->name());
100}
101
102base::string16 ConfirmInstallDialogDelegate::GetAcceptButtonTitle() {
103  return l10n_util::GetStringUTF16(
104      IDS_PLUGIN_CONFIRM_INSTALL_DIALOG_ACCEPT_BUTTON);
105}
106
107void ConfirmInstallDialogDelegate::OnAccepted() {
108  installer()->StartInstalling(plugin_metadata_->plugin_url(), web_contents_);
109}
110
111void ConfirmInstallDialogDelegate::OnCanceled() {
112}
113
114void ConfirmInstallDialogDelegate::DownloadStarted() {
115  Cancel();
116}
117
118void ConfirmInstallDialogDelegate::OnlyWeakObserversLeft() {
119  Cancel();
120}
121#endif  // defined(ENABLE_PLUGIN_INSTALLATION)
122
123// ReloadPluginInfoBarDelegate -------------------------------------------------
124
125class 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
148void 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
157ReloadPluginInfoBarDelegate::ReloadPluginInfoBarDelegate(
158    content::NavigationController* controller,
159    const base::string16& message)
160    : controller_(controller),
161      message_(message) {}
162
163ReloadPluginInfoBarDelegate::~ReloadPluginInfoBarDelegate(){ }
164
165int ReloadPluginInfoBarDelegate::GetIconID() const {
166  return IDR_INFOBAR_PLUGIN_CRASHED;
167}
168
169base::string16 ReloadPluginInfoBarDelegate::GetMessageText() const {
170  return message_;
171}
172
173int ReloadPluginInfoBarDelegate::GetButtons() const {
174  return BUTTON_OK;
175}
176
177base::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
183bool ReloadPluginInfoBarDelegate::Accept() {
184  controller_->Reload(true);
185  return true;
186}
187
188}  // namespace
189
190// PluginObserver -------------------------------------------------------------
191
192#if defined(ENABLE_PLUGIN_INSTALLATION)
193class 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
241PluginObserver::PluginObserver(content::WebContents* web_contents)
242    : content::WebContentsObserver(web_contents),
243      weak_ptr_factory_(this) {
244}
245
246PluginObserver::~PluginObserver() {
247#if defined(ENABLE_PLUGIN_INSTALLATION)
248  STLDeleteValues(&plugin_placeholders_);
249#endif
250}
251
252void 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
284void 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
332bool 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
353bool 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
370void 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
380void 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)
403void 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
427void 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
440void 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
452void PluginObserver::OnOpenAboutPlugins() {
453  web_contents()->OpenURL(OpenURLParams(
454      GURL(chrome::kChromeUIPluginsURL),
455      content::Referrer(web_contents()->GetURL(),
456                        blink::WebReferrerPolicyDefault),
457      NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_AUTO_BOOKMARK, false));
458}
459
460void 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
473void 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