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