1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/browser-plugins.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1332 @@ 1.4 +# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +var gPluginHandler = { 1.10 + PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash", 1.11 + PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification", 1.12 + PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes", 1.13 + PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays", 1.14 + 1.15 + getPluginUI: function (plugin, anonid) { 1.16 + return plugin.ownerDocument. 1.17 + getAnonymousElementByAttribute(plugin, "anonid", anonid); 1.18 + }, 1.19 + 1.20 +#ifdef MOZ_CRASHREPORTER 1.21 + get CrashSubmit() { 1.22 + delete this.CrashSubmit; 1.23 + Cu.import("resource://gre/modules/CrashSubmit.jsm", this); 1.24 + return this.CrashSubmit; 1.25 + }, 1.26 +#endif 1.27 + 1.28 + _getPluginInfo: function (pluginElement) { 1.29 + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.30 + pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); 1.31 + 1.32 + let tagMimetype; 1.33 + let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin"); 1.34 + let pluginTag = null; 1.35 + let permissionString = null; 1.36 + let fallbackType = null; 1.37 + let blocklistState = null; 1.38 + 1.39 + tagMimetype = pluginElement.actualType; 1.40 + if (tagMimetype == "") { 1.41 + tagMimetype = pluginElement.type; 1.42 + } 1.43 + 1.44 + if (gPluginHandler.isKnownPlugin(pluginElement)) { 1.45 + pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); 1.46 + pluginName = gPluginHandler.makeNicePluginName(pluginTag.name); 1.47 + 1.48 + permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType); 1.49 + fallbackType = pluginElement.defaultFallbackType; 1.50 + blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType); 1.51 + // Make state-softblocked == state-notblocked for our purposes, 1.52 + // they have the same UI. STATE_OUTDATED should not exist for plugin 1.53 + // items, but let's alias it anyway, just in case. 1.54 + if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED || 1.55 + blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { 1.56 + blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED; 1.57 + } 1.58 + } 1.59 + 1.60 + return { mimetype: tagMimetype, 1.61 + pluginName: pluginName, 1.62 + pluginTag: pluginTag, 1.63 + permissionString: permissionString, 1.64 + fallbackType: fallbackType, 1.65 + blocklistState: blocklistState, 1.66 + }; 1.67 + }, 1.68 + 1.69 + // Map the plugin's name to a filtered version more suitable for user UI. 1.70 + makeNicePluginName : function (aName) { 1.71 + if (aName == "Shockwave Flash") 1.72 + return "Adobe Flash"; 1.73 + 1.74 + // Clean up the plugin name by stripping off parenthetical clauses, 1.75 + // trailing version numbers or "plugin". 1.76 + // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar" 1.77 + // Do this by first stripping the numbers, etc. off the end, and then 1.78 + // removing "Plugin" (and then trimming to get rid of any whitespace). 1.79 + // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled) 1.80 + let newName = aName.replace(/\(.*?\)/g, ""). 1.81 + replace(/[\s\d\.\-\_\(\)]+$/, ""). 1.82 + replace(/\bplug-?in\b/i, "").trim(); 1.83 + return newName; 1.84 + }, 1.85 + 1.86 + /** 1.87 + * Update the visibility of the plugin overlay. 1.88 + */ 1.89 + setVisibility : function (plugin, overlay, shouldShow) { 1.90 + overlay.classList.toggle("visible", shouldShow); 1.91 + }, 1.92 + 1.93 + /** 1.94 + * Check whether the plugin should be visible on the page. A plugin should 1.95 + * not be visible if the overlay is too big, or if any other page content 1.96 + * overlays it. 1.97 + * 1.98 + * This function will handle showing or hiding the overlay. 1.99 + * @returns true if the plugin is invisible. 1.100 + */ 1.101 + shouldShowOverlay : function (plugin, overlay) { 1.102 + // If the overlay size is 0, we haven't done layout yet. Presume that 1.103 + // plugins are visible until we know otherwise. 1.104 + if (overlay.scrollWidth == 0) { 1.105 + return true; 1.106 + } 1.107 + 1.108 + // Is the <object>'s size too small to hold what we want to show? 1.109 + let pluginRect = plugin.getBoundingClientRect(); 1.110 + // XXX bug 446693. The text-shadow on the submitted-report text at 1.111 + // the bottom causes scrollHeight to be larger than it should be. 1.112 + let overflows = (overlay.scrollWidth > pluginRect.width) || 1.113 + (overlay.scrollHeight - 5 > pluginRect.height); 1.114 + if (overflows) { 1.115 + return false; 1.116 + } 1.117 + 1.118 + // Is the plugin covered up by other content so that it is not clickable? 1.119 + // Floating point can confuse .elementFromPoint, so inset just a bit 1.120 + let left = pluginRect.left + 2; 1.121 + let right = pluginRect.right - 2; 1.122 + let top = pluginRect.top + 2; 1.123 + let bottom = pluginRect.bottom - 2; 1.124 + let centerX = left + (right - left) / 2; 1.125 + let centerY = top + (bottom - top) / 2; 1.126 + let points = [[left, top], 1.127 + [left, bottom], 1.128 + [right, top], 1.129 + [right, bottom], 1.130 + [centerX, centerY]]; 1.131 + 1.132 + if (right <= 0 || top <= 0) { 1.133 + return false; 1.134 + } 1.135 + 1.136 + let contentWindow = plugin.ownerDocument.defaultView; 1.137 + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.138 + .getInterface(Ci.nsIDOMWindowUtils); 1.139 + 1.140 + for (let [x, y] of points) { 1.141 + let el = cwu.elementFromPoint(x, y, true, true); 1.142 + if (el !== plugin) { 1.143 + return false; 1.144 + } 1.145 + } 1.146 + 1.147 + return true; 1.148 + }, 1.149 + 1.150 + addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) { 1.151 + // XXX just doing (callback)(arg) was giving a same-origin error. bug? 1.152 + let self = this; 1.153 + let callbackArgs = Array.prototype.slice.call(arguments).slice(2); 1.154 + linkNode.addEventListener("click", 1.155 + function(evt) { 1.156 + if (!evt.isTrusted) 1.157 + return; 1.158 + evt.preventDefault(); 1.159 + if (callbackArgs.length == 0) 1.160 + callbackArgs = [ evt ]; 1.161 + (self[callbackName]).apply(self, callbackArgs); 1.162 + }, 1.163 + true); 1.164 + 1.165 + linkNode.addEventListener("keydown", 1.166 + function(evt) { 1.167 + if (!evt.isTrusted) 1.168 + return; 1.169 + if (evt.keyCode == evt.DOM_VK_RETURN) { 1.170 + evt.preventDefault(); 1.171 + if (callbackArgs.length == 0) 1.172 + callbackArgs = [ evt ]; 1.173 + evt.preventDefault(); 1.174 + (self[callbackName]).apply(self, callbackArgs); 1.175 + } 1.176 + }, 1.177 + true); 1.178 + }, 1.179 + 1.180 + // Helper to get the binding handler type from a plugin object 1.181 + _getBindingType : function(plugin) { 1.182 + if (!(plugin instanceof Ci.nsIObjectLoadingContent)) 1.183 + return null; 1.184 + 1.185 + switch (plugin.pluginFallbackType) { 1.186 + case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: 1.187 + return "PluginNotFound"; 1.188 + case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED: 1.189 + return "PluginDisabled"; 1.190 + case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED: 1.191 + return "PluginBlocklisted"; 1.192 + case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED: 1.193 + return "PluginOutdated"; 1.194 + case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: 1.195 + return "PluginClickToPlay"; 1.196 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: 1.197 + return "PluginVulnerableUpdatable"; 1.198 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: 1.199 + return "PluginVulnerableNoUpdate"; 1.200 + case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: 1.201 + return "PluginPlayPreview"; 1.202 + default: 1.203 + // Not all states map to a handler 1.204 + return null; 1.205 + } 1.206 + }, 1.207 + 1.208 + supportedPlugins: { 1.209 + "mimetypes": { 1.210 + "application/x-shockwave-flash": "flash", 1.211 + "application/futuresplash": "flash", 1.212 + "application/x-java-.*": "java", 1.213 + "application/x-director": "shockwave", 1.214 + "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime", 1.215 + "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime", 1.216 + "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime", 1.217 + "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime", 1.218 + "application/x-unknown": "test", 1.219 + }, 1.220 + 1.221 + "plugins": { 1.222 + "flash": { 1.223 + "displayName": "Flash", 1.224 + "installWINNT": true, 1.225 + "installDarwin": true, 1.226 + "installLinux": true, 1.227 + }, 1.228 + "java": { 1.229 + "displayName": "Java", 1.230 + "installWINNT": true, 1.231 + "installDarwin": true, 1.232 + "installLinux": true, 1.233 + }, 1.234 + "shockwave": { 1.235 + "displayName": "Shockwave", 1.236 + "installWINNT": true, 1.237 + "installDarwin": true, 1.238 + }, 1.239 + "quicktime": { 1.240 + "displayName": "QuickTime", 1.241 + "installWINNT": true, 1.242 + }, 1.243 + "test": { 1.244 + "displayName": "Test plugin", 1.245 + "installWINNT": true, 1.246 + "installLinux": true, 1.247 + "installDarwin": true, 1.248 + } 1.249 + } 1.250 + }, 1.251 + 1.252 + nameForSupportedPlugin: function (aMimeType) { 1.253 + for (let type in this.supportedPlugins.mimetypes) { 1.254 + let re = new RegExp(type); 1.255 + if (re.test(aMimeType)) { 1.256 + return this.supportedPlugins.mimetypes[type]; 1.257 + } 1.258 + } 1.259 + return null; 1.260 + }, 1.261 + 1.262 + canInstallThisMimeType: function (aMimeType) { 1.263 + let os = Services.appinfo.OS; 1.264 + let pluginName = this.nameForSupportedPlugin(aMimeType); 1.265 + if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) { 1.266 + return true; 1.267 + } 1.268 + return false; 1.269 + }, 1.270 + 1.271 + handleEvent : function(event) { 1.272 + let eventType = event.type; 1.273 + 1.274 + if (eventType == "PluginRemoved") { 1.275 + let doc = event.target; 1.276 + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); 1.277 + if (browser) 1.278 + this._setPluginNotificationIcon(browser); 1.279 + return; 1.280 + } 1.281 + 1.282 + let plugin = event.target; 1.283 + let doc = plugin.ownerDocument; 1.284 + 1.285 + if (!(plugin instanceof Ci.nsIObjectLoadingContent)) 1.286 + return; 1.287 + 1.288 + if (eventType == "PluginBindingAttached") { 1.289 + // The plugin binding fires this event when it is created. 1.290 + // As an untrusted event, ensure that this object actually has a binding 1.291 + // and make sure we don't handle it twice 1.292 + let overlay = this.getPluginUI(plugin, "main"); 1.293 + if (!overlay || overlay._bindingHandled) { 1.294 + return; 1.295 + } 1.296 + overlay._bindingHandled = true; 1.297 + 1.298 + // Lookup the handler for this binding 1.299 + eventType = this._getBindingType(plugin); 1.300 + if (!eventType) { 1.301 + // Not all bindings have handlers 1.302 + return; 1.303 + } 1.304 + } 1.305 + 1.306 + let shouldShowNotification = false; 1.307 + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); 1.308 + if (!browser) 1.309 + return; 1.310 + 1.311 + switch (eventType) { 1.312 + case "PluginCrashed": 1.313 + this.pluginInstanceCrashed(plugin, event); 1.314 + break; 1.315 + 1.316 + case "PluginNotFound": 1.317 + let installable = this.showInstallNotification(plugin, eventType); 1.318 + // For non-object plugin tags, register a click handler to install the 1.319 + // plugin. Object tags can, and often do, deal with that themselves, 1.320 + // so don't stomp on the page developers toes. 1.321 + if (installable && !(plugin instanceof HTMLObjectElement)) { 1.322 + let installStatus = this.getPluginUI(plugin, "installStatus"); 1.323 + installStatus.setAttribute("installable", "true"); 1.324 + let iconStatus = this.getPluginUI(plugin, "icon"); 1.325 + iconStatus.setAttribute("installable", "true"); 1.326 + 1.327 + let installLink = this.getPluginUI(plugin, "installPluginLink"); 1.328 + this.addLinkClickCallback(installLink, "installSinglePlugin", plugin); 1.329 + } 1.330 + break; 1.331 + 1.332 + case "PluginBlocklisted": 1.333 + case "PluginOutdated": 1.334 + shouldShowNotification = true; 1.335 + break; 1.336 + 1.337 + case "PluginVulnerableUpdatable": 1.338 + let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); 1.339 + this.addLinkClickCallback(updateLink, "openPluginUpdatePage"); 1.340 + /* FALLTHRU */ 1.341 + 1.342 + case "PluginVulnerableNoUpdate": 1.343 + case "PluginClickToPlay": 1.344 + this._handleClickToPlayEvent(plugin); 1.345 + let overlay = this.getPluginUI(plugin, "main"); 1.346 + let pluginName = this._getPluginInfo(plugin).pluginName; 1.347 + let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]); 1.348 + let overlayText = this.getPluginUI(plugin, "clickToPlay"); 1.349 + overlayText.textContent = messageString; 1.350 + if (eventType == "PluginVulnerableUpdatable" || 1.351 + eventType == "PluginVulnerableNoUpdate") { 1.352 + let vulnerabilityString = gNavigatorBundle.getString(eventType); 1.353 + let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus"); 1.354 + vulnerabilityText.textContent = vulnerabilityString; 1.355 + } 1.356 + shouldShowNotification = true; 1.357 + break; 1.358 + 1.359 + case "PluginPlayPreview": 1.360 + this._handlePlayPreviewEvent(plugin); 1.361 + break; 1.362 + 1.363 + case "PluginDisabled": 1.364 + // Screw the disabled message. It messes with HTML5 fallback on YouTube 1.365 + let plugin_overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); 1.366 + if (plugin_overlay != null) 1.367 + plugin_overlay.style.visibility = "hidden"; 1.368 + break; 1.369 + 1.370 + case "PluginInstantiated": 1.371 + shouldShowNotification = true; 1.372 + break; 1.373 + } 1.374 + 1.375 + // Show the in-content UI if it's not too big. The crashed plugin handler already did this. 1.376 + if (eventType != "PluginCrashed") { 1.377 + let overlay = this.getPluginUI(plugin, "main"); 1.378 + if (overlay != null) { 1.379 + this.setVisibility(plugin, overlay, 1.380 + this.shouldShowOverlay(plugin, overlay)); 1.381 + let resizeListener = (event) => { 1.382 + this.setVisibility(plugin, overlay, 1.383 + this.shouldShowOverlay(plugin, overlay)); 1.384 + this._setPluginNotificationIcon(browser); 1.385 + }; 1.386 + plugin.addEventListener("overflow", resizeListener); 1.387 + plugin.addEventListener("underflow", resizeListener); 1.388 + } 1.389 + } 1.390 + 1.391 + let closeIcon = this.getPluginUI(plugin, "closeIcon"); 1.392 + if (closeIcon) { 1.393 + closeIcon.addEventListener("click", function(aEvent) { 1.394 + if (aEvent.button == 0 && aEvent.isTrusted) 1.395 + gPluginHandler.hideClickToPlayOverlay(plugin); 1.396 + }, true); 1.397 + } 1.398 + 1.399 + if (shouldShowNotification) { 1.400 + this._showClickToPlayNotification(browser, plugin, false); 1.401 + } 1.402 + }, 1.403 + 1.404 + isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) { 1.405 + return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == 1.406 + Ci.nsIObjectLoadingContent.TYPE_PLUGIN); 1.407 + }, 1.408 + 1.409 + canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) { 1.410 + // if this isn't a known plugin, we can't activate it 1.411 + // (this also guards pluginHost.getPermissionStringForType against 1.412 + // unexpected input) 1.413 + if (!gPluginHandler.isKnownPlugin(objLoadingContent)) 1.414 + return false; 1.415 + 1.416 + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.417 + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); 1.418 + let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal; 1.419 + let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); 1.420 + 1.421 + let isFallbackTypeValid = 1.422 + objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && 1.423 + objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; 1.424 + 1.425 + if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) { 1.426 + // checking if play preview is subject to CTP rules 1.427 + let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType); 1.428 + isFallbackTypeValid = !playPreviewInfo.ignoreCTP; 1.429 + } 1.430 + 1.431 + return !objLoadingContent.activated && 1.432 + pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && 1.433 + isFallbackTypeValid; 1.434 + }, 1.435 + 1.436 + hideClickToPlayOverlay: function(aPlugin) { 1.437 + let overlay = this.getPluginUI(aPlugin, "main"); 1.438 + if (overlay) { 1.439 + overlay.classList.remove("visible"); 1.440 + } 1.441 + }, 1.442 + 1.443 + stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) { 1.444 + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); 1.445 + if (objLoadingContent.activated) 1.446 + return; 1.447 + 1.448 + if (aPlayPlugin) 1.449 + objLoadingContent.playPlugin(); 1.450 + else 1.451 + objLoadingContent.cancelPlayPreview(); 1.452 + }, 1.453 + 1.454 + newPluginInstalled : function(event) { 1.455 + // browser elements are anonymous so we can't just use target. 1.456 + var browser = event.originalTarget; 1.457 + // clear the plugin list, now that at least one plugin has been installed 1.458 + browser.missingPlugins = null; 1.459 + 1.460 + var notificationBox = gBrowser.getNotificationBox(browser); 1.461 + var notification = notificationBox.getNotificationWithValue("missing-plugins"); 1.462 + if (notification) 1.463 + notificationBox.removeNotification(notification); 1.464 + 1.465 + // reload the browser to make the new plugin show. 1.466 + browser.reload(); 1.467 + }, 1.468 + 1.469 + // Callback for user clicking on a missing (unsupported) plugin. 1.470 + installSinglePlugin: function (plugin) { 1.471 + var missingPlugins = new Map(); 1.472 + 1.473 + var pluginInfo = this._getPluginInfo(plugin); 1.474 + missingPlugins.set(pluginInfo.mimetype, pluginInfo); 1.475 + 1.476 + openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", 1.477 + "PFSWindow", "chrome,centerscreen,resizable=yes", 1.478 + {plugins: missingPlugins, browser: gBrowser.selectedBrowser}); 1.479 + }, 1.480 + 1.481 + // Callback for user clicking on a disabled plugin 1.482 + managePlugins: function (aEvent) { 1.483 + BrowserOpenAddonsMgr("addons://list/plugin"); 1.484 + }, 1.485 + 1.486 + // Callback for user clicking on the link in a click-to-play plugin 1.487 + // (where the plugin has an update) 1.488 + openPluginUpdatePage: function (aEvent) { 1.489 + openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab"); 1.490 + }, 1.491 + 1.492 +#ifdef MOZ_CRASHREPORTER 1.493 + submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) { 1.494 + let keyVals = {}; 1.495 + if (plugin) { 1.496 + let userComment = this.getPluginUI(plugin, "submitComment").value.trim(); 1.497 + if (userComment) 1.498 + keyVals.PluginUserComment = userComment; 1.499 + if (this.getPluginUI(plugin, "submitURLOptIn").checked) 1.500 + keyVals.PluginContentURL = plugin.ownerDocument.URL; 1.501 + } 1.502 + this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals }); 1.503 + if (browserDumpID) 1.504 + this.CrashSubmit.submit(browserDumpID); 1.505 + }, 1.506 +#endif 1.507 + 1.508 + // Callback for user clicking a "reload page" link 1.509 + reloadPage: function (browser) { 1.510 + browser.reload(); 1.511 + }, 1.512 + 1.513 + // Callback for user clicking the help icon 1.514 + openHelpPage: function () { 1.515 + openHelpLink("plugin-crashed", false); 1.516 + }, 1.517 + 1.518 + showInstallNotification: function (aPlugin) { 1.519 + let hideMissingPluginsNotification = 1.520 + Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION); 1.521 + if (hideMissingPluginsNotification) { 1.522 + return false; 1.523 + } 1.524 + 1.525 + let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument 1.526 + .defaultView.top.document); 1.527 + if (!browser.missingPlugins) 1.528 + browser.missingPlugins = new Map(); 1.529 + 1.530 + let pluginInfo = this._getPluginInfo(aPlugin); 1.531 + browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo); 1.532 + 1.533 + // only show notification for small subset of plugins 1.534 + let mimetype = pluginInfo.mimetype.split(";")[0]; 1.535 + if (!this.canInstallThisMimeType(mimetype)) 1.536 + return false; 1.537 + 1.538 + let pluginIdentifier = this.nameForSupportedPlugin(mimetype); 1.539 + if (!pluginIdentifier) 1.540 + return false; 1.541 + 1.542 + let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName; 1.543 + 1.544 + // don't show several notifications 1.545 + let notification = PopupNotifications.getNotification("plugins-not-found", browser); 1.546 + if (notification) 1.547 + return true; 1.548 + 1.549 + let messageString = gNavigatorBundle.getString("installPlugin.message"); 1.550 + let mainAction = { 1.551 + label: gNavigatorBundle.getFormattedString("installPlugin.button.label", 1.552 + [displayName]), 1.553 + accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"), 1.554 + callback: function () { 1.555 + openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", 1.556 + "PFSWindow", "chrome,centerscreen,resizable=yes", 1.557 + {plugins: browser.missingPlugins, browser: browser}); 1.558 + } 1.559 + }; 1.560 + let secondaryActions = null; 1.561 + let options = { dismissed: true }; 1.562 + 1.563 + let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH); 1.564 + if (pluginIdentifier == "flash" && showForFlash) { 1.565 + let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH; 1.566 + secondaryActions = [{ 1.567 + label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"), 1.568 + accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"), 1.569 + callback: function () { 1.570 + Services.prefs.setBoolPref(prefNotifyMissingFlash, false); 1.571 + } 1.572 + }]; 1.573 + options.dismissed = false; 1.574 + } 1.575 + PopupNotifications.show(browser, "plugins-not-found", 1.576 + messageString, "plugin-install-notification-icon", 1.577 + mainAction, secondaryActions, options); 1.578 + return true; 1.579 + }, 1.580 + // Event listener for click-to-play plugins. 1.581 + _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) { 1.582 + let doc = aPlugin.ownerDocument; 1.583 + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); 1.584 + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.585 + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); 1.586 + // guard against giving pluginHost.getPermissionStringForType a type 1.587 + // not associated with any known plugin 1.588 + if (!gPluginHandler.isKnownPlugin(objLoadingContent)) 1.589 + return; 1.590 + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); 1.591 + let principal = doc.defaultView.top.document.nodePrincipal; 1.592 + let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); 1.593 + 1.594 + let overlay = this.getPluginUI(aPlugin, "main"); 1.595 + 1.596 + if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { 1.597 + if (overlay) { 1.598 + overlay.classList.remove("visible"); 1.599 + } 1.600 + return; 1.601 + } 1.602 + 1.603 + if (overlay) { 1.604 + overlay.addEventListener("click", gPluginHandler._overlayClickListener, true); 1.605 + } 1.606 + }, 1.607 + 1.608 + _overlayClickListener: { 1.609 + handleEvent: function PH_handleOverlayClick(aEvent) { 1.610 + let plugin = document.getBindingParent(aEvent.target); 1.611 + let contentWindow = plugin.ownerDocument.defaultView.top; 1.612 + // gBrowser.getBrowserForDocument does not exist in the case where we 1.613 + // drag-and-dropped a tab from a window containing only that tab. In 1.614 + // that case, the window gets destroyed. 1.615 + let browser = gBrowser.getBrowserForDocument ? 1.616 + gBrowser.getBrowserForDocument(contentWindow.document) : 1.617 + null; 1.618 + // If browser is null here, we've been drag-and-dropped from another 1.619 + // window, and this is the wrong click handler. 1.620 + if (!browser) { 1.621 + aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true); 1.622 + return; 1.623 + } 1.624 + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); 1.625 + // Have to check that the target is not the link to update the plugin 1.626 + if (!(aEvent.originalTarget instanceof HTMLAnchorElement) && 1.627 + (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') && 1.628 + aEvent.button == 0 && aEvent.isTrusted) { 1.629 + gPluginHandler._showClickToPlayNotification(browser, plugin, true); 1.630 + aEvent.stopPropagation(); 1.631 + aEvent.preventDefault(); 1.632 + } 1.633 + } 1.634 + }, 1.635 + 1.636 + _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) { 1.637 + let doc = aPlugin.ownerDocument; 1.638 + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); 1.639 + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.640 + let pluginInfo = this._getPluginInfo(aPlugin); 1.641 + let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype); 1.642 + 1.643 + let previewContent = this.getPluginUI(aPlugin, "previewPluginContent"); 1.644 + let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; 1.645 + if (!iframe) { 1.646 + // lazy initialization of the iframe 1.647 + iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); 1.648 + iframe.className = "previewPluginContentFrame"; 1.649 + previewContent.appendChild(iframe); 1.650 + 1.651 + // Force a style flush, so that we ensure our binding is attached. 1.652 + aPlugin.clientTop; 1.653 + } 1.654 + iframe.src = playPreviewInfo.redirectURL; 1.655 + 1.656 + // MozPlayPlugin event can be dispatched from the extension chrome 1.657 + // code to replace the preview content with the native plugin 1.658 + previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) { 1.659 + if (!aEvent.isTrusted) 1.660 + return; 1.661 + 1.662 + previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); 1.663 + 1.664 + let playPlugin = !aEvent.detail; 1.665 + gPluginHandler.stopPlayPreview(aPlugin, playPlugin); 1.666 + 1.667 + // cleaning up: removes overlay iframe from the DOM 1.668 + let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; 1.669 + if (iframe) 1.670 + previewContent.removeChild(iframe); 1.671 + }, true); 1.672 + 1.673 + if (!playPreviewInfo.ignoreCTP) { 1.674 + gPluginHandler._showClickToPlayNotification(browser, aPlugin, false); 1.675 + } 1.676 + }, 1.677 + 1.678 + reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { 1.679 + let browser = gBrowser.selectedBrowser; 1.680 + let contentWindow = browser.contentWindow; 1.681 + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.682 + .getInterface(Ci.nsIDOMWindowUtils); 1.683 + let plugins = cwu.plugins; 1.684 + for (let plugin of plugins) { 1.685 + let overlay = this.getPluginUI(plugin, "main"); 1.686 + if (overlay) 1.687 + overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); 1.688 + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); 1.689 + if (gPluginHandler.canActivatePlugin(objLoadingContent)) 1.690 + gPluginHandler._handleClickToPlayEvent(plugin); 1.691 + } 1.692 + gPluginHandler._showClickToPlayNotification(browser, null, false); 1.693 + }, 1.694 + 1.695 + _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) { 1.696 + if (event == "showing") { 1.697 + Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN") 1.698 + .add(!this.options.primaryPlugin); 1.699 + // Histograms always start at 0, even though our data starts at 1 1.700 + let histogramCount = this.options.pluginData.size - 1; 1.701 + if (histogramCount > 4) { 1.702 + histogramCount = 4; 1.703 + } 1.704 + Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT") 1.705 + .add(histogramCount); 1.706 + } 1.707 + else if (event == "dismissed") { 1.708 + // Once the popup is dismissed, clicking the icon should show the full 1.709 + // list again 1.710 + this.options.primaryPlugin = null; 1.711 + } 1.712 + }, 1.713 + 1.714 + // Match the behaviour of nsPermissionManager 1.715 + _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) { 1.716 + if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) { 1.717 + return "(null)"; 1.718 + } 1.719 + 1.720 + try { 1.721 + if (principal.URI.host) 1.722 + return principal.URI.host; 1.723 + } catch (e) {} 1.724 + 1.725 + return principal.origin; 1.726 + }, 1.727 + 1.728 + /** 1.729 + * Called from the plugin doorhanger to set the new permissions for a plugin 1.730 + * and activate plugins if necessary. 1.731 + * aNewState should be either "allownow" "allowalways" or "block" 1.732 + */ 1.733 + _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) { 1.734 + let permission; 1.735 + let expireType; 1.736 + let expireTime; 1.737 + let histogram = 1.738 + Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION"); 1.739 + 1.740 + // Update the permission manager. 1.741 + // Also update the current state of pluginInfo.fallbackType so that 1.742 + // subsequent opening of the notification shows the current state. 1.743 + switch (aNewState) { 1.744 + case "allownow": 1.745 + permission = Ci.nsIPermissionManager.ALLOW_ACTION; 1.746 + expireType = Ci.nsIPermissionManager.EXPIRE_SESSION; 1.747 + expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000; 1.748 + histogram.add(0); 1.749 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; 1.750 + break; 1.751 + 1.752 + case "allowalways": 1.753 + permission = Ci.nsIPermissionManager.ALLOW_ACTION; 1.754 + expireType = Ci.nsIPermissionManager.EXPIRE_TIME; 1.755 + expireTime = Date.now() + 1.756 + Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000; 1.757 + histogram.add(1); 1.758 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; 1.759 + break; 1.760 + 1.761 + case "block": 1.762 + permission = Ci.nsIPermissionManager.PROMPT_ACTION; 1.763 + expireType = Ci.nsIPermissionManager.EXPIRE_NEVER; 1.764 + expireTime = 0; 1.765 + histogram.add(2); 1.766 + switch (aPluginInfo.blocklistState) { 1.767 + case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE: 1.768 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE; 1.769 + break; 1.770 + case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE: 1.771 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; 1.772 + break; 1.773 + default: 1.774 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY; 1.775 + } 1.776 + break; 1.777 + 1.778 + // In case a plugin has already been allowed in another tab, the "continue allowing" button 1.779 + // shouldn't change any permissions but should run the plugin-enablement code below. 1.780 + case "continue": 1.781 + aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; 1.782 + break; 1.783 + default: 1.784 + Cu.reportError(Error("Unexpected plugin state: " + aNewState)); 1.785 + return; 1.786 + } 1.787 + 1.788 + let browser = aNotification.browser; 1.789 + let contentWindow = browser.contentWindow; 1.790 + if (aNewState != "continue") { 1.791 + let principal = contentWindow.document.nodePrincipal; 1.792 + Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString, 1.793 + permission, expireType, expireTime); 1.794 + aPluginInfo.pluginPermissionType = expireType; 1.795 + } 1.796 + 1.797 + // Manually activate the plugins that would have been automatically 1.798 + // activated. 1.799 + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.800 + .getInterface(Ci.nsIDOMWindowUtils); 1.801 + let plugins = cwu.plugins; 1.802 + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.803 + 1.804 + let pluginFound = false; 1.805 + for (let plugin of plugins) { 1.806 + plugin.QueryInterface(Ci.nsIObjectLoadingContent); 1.807 + if (!gPluginHandler.isKnownPlugin(plugin)) { 1.808 + continue; 1.809 + } 1.810 + if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { 1.811 + pluginFound = true; 1.812 + if (aNewState == "block") { 1.813 + plugin.reload(true); 1.814 + } else { 1.815 + if (gPluginHandler.canActivatePlugin(plugin)) { 1.816 + let overlay = this.getPluginUI(plugin, "main"); 1.817 + if (overlay) { 1.818 + overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); 1.819 + } 1.820 + plugin.playPlugin(); 1.821 + } 1.822 + } 1.823 + } 1.824 + } 1.825 + 1.826 + // If there are no instances of the plugin on the page any more, what the 1.827 + // user probably needs is for us to allow and then refresh. 1.828 + if (aNewState != "block" && !pluginFound) { 1.829 + browser.reload(); 1.830 + } 1.831 + 1.832 + this._setPluginNotificationIcon(browser); 1.833 + }, 1.834 + 1.835 + _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) { 1.836 + let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); 1.837 + let plugins = []; 1.838 + 1.839 + // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need 1.840 + // to collect all the plugins 1.841 + if (aPlugin === null) { 1.842 + let contentWindow = aBrowser.contentWindow; 1.843 + let contentDoc = aBrowser.contentDocument; 1.844 + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.845 + .getInterface(Ci.nsIDOMWindowUtils); 1.846 + // cwu.plugins may contain non-plugin <object>s, filter them out 1.847 + plugins = cwu.plugins.filter((plugin) => 1.848 + plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); 1.849 + 1.850 + if (plugins.length == 0) { 1.851 + if (notification) { 1.852 + PopupNotifications.remove(notification); 1.853 + } 1.854 + return; 1.855 + } 1.856 + } else { 1.857 + plugins = [aPlugin]; 1.858 + } 1.859 + 1.860 + // If this is a new notification, create a pluginData map, otherwise append 1.861 + let pluginData; 1.862 + if (notification) { 1.863 + pluginData = notification.options.pluginData; 1.864 + } else { 1.865 + pluginData = new Map(); 1.866 + } 1.867 + 1.868 + let principal = aBrowser.contentDocument.nodePrincipal; 1.869 + let principalHost = this._getHostFromPrincipal(principal); 1.870 + 1.871 + for (var plugin of plugins) { 1.872 + let pluginInfo = this._getPluginInfo(plugin); 1.873 + if (pluginInfo.permissionString === null) { 1.874 + Cu.reportError("No permission string for active plugin."); 1.875 + continue; 1.876 + } 1.877 + if (pluginData.has(pluginInfo.permissionString)) { 1.878 + continue; 1.879 + } 1.880 + 1.881 + let permissionObj = Services.perms. 1.882 + getPermissionObject(principal, pluginInfo.permissionString, false); 1.883 + if (permissionObj) { 1.884 + pluginInfo.pluginPermissionHost = permissionObj.host; 1.885 + pluginInfo.pluginPermissionType = permissionObj.expireType; 1.886 + } 1.887 + else { 1.888 + pluginInfo.pluginPermissionHost = principalHost; 1.889 + pluginInfo.pluginPermissionType = undefined; 1.890 + } 1.891 + 1.892 + let url; 1.893 + // TODO: allow the blocklist to specify a better link, bug 873093 1.894 + if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { 1.895 + url = Services.urlFormatter.formatURLPref("plugins.update.url"); 1.896 + } 1.897 + else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { 1.898 + url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag); 1.899 + } 1.900 + else { 1.901 + url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay"; 1.902 + } 1.903 + pluginInfo.detailsLink = url; 1.904 + 1.905 + pluginData.set(pluginInfo.permissionString, pluginInfo); 1.906 + } 1.907 + 1.908 + let primaryPluginPermission = null; 1.909 + if (aShowNow) { 1.910 + primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString; 1.911 + } 1.912 + 1.913 + if (notification) { 1.914 + // Don't modify the notification UI while it's on the screen, that would be 1.915 + // jumpy and might allow clickjacking. 1.916 + if (aShowNow) { 1.917 + notification.options.primaryPlugin = primaryPluginPermission; 1.918 + notification.reshow(); 1.919 + setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); 1.920 + } 1.921 + return; 1.922 + } 1.923 + 1.924 + let options = { 1.925 + dismissed: !aShowNow, 1.926 + eventCallback: this._clickToPlayNotificationEventCallback, 1.927 + primaryPlugin: primaryPluginPermission, 1.928 + pluginData: pluginData 1.929 + }; 1.930 + PopupNotifications.show(aBrowser, "click-to-play-plugins", 1.931 + "", "plugins-notification-icon", 1.932 + null, null, options); 1.933 + setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); 1.934 + }, 1.935 + 1.936 + _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) { 1.937 + // Because this is called on a timeout, sanity-check before continuing 1.938 + if (!aBrowser.docShell || !aBrowser.contentWindow) { 1.939 + return; 1.940 + } 1.941 + 1.942 + let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); 1.943 + if (!notification) 1.944 + return; 1.945 + 1.946 + // Make a copy of the actions, removing active plugins and checking for 1.947 + // outdated plugins. 1.948 + let haveInsecure = false; 1.949 + let actions = new Map(); 1.950 + for (let action of notification.options.pluginData.values()) { 1.951 + switch (action.fallbackType) { 1.952 + // haveInsecure will trigger the red flashing icon and the infobar 1.953 + // styling below 1.954 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: 1.955 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: 1.956 + haveInsecure = true; 1.957 + // fall through 1.958 + 1.959 + case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: 1.960 + actions.set(action.permissionString, action); 1.961 + continue; 1.962 + } 1.963 + } 1.964 + 1.965 + // check for hidden plugins 1.966 + let contentWindow = aBrowser.contentWindow; 1.967 + let contentDoc = aBrowser.contentDocument; 1.968 + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.969 + .getInterface(Ci.nsIDOMWindowUtils); 1.970 + for (let plugin of cwu.plugins) { 1.971 + let info = this._getPluginInfo(plugin); 1.972 + if (!actions.has(info.permissionString)) { 1.973 + continue; 1.974 + } 1.975 + let fallbackType = info.fallbackType; 1.976 + if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { 1.977 + actions.delete(info.permissionString); 1.978 + if (actions.size == 0) { 1.979 + break; 1.980 + } 1.981 + continue; 1.982 + } 1.983 + if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && 1.984 + fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && 1.985 + fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { 1.986 + continue; 1.987 + } 1.988 + let overlay = this.getPluginUI(plugin, "main"); 1.989 + if (!overlay) { 1.990 + continue; 1.991 + } 1.992 + let shouldShow = this.shouldShowOverlay(plugin, overlay); 1.993 + this.setVisibility(plugin, overlay, shouldShow); 1.994 + if (shouldShow) { 1.995 + actions.delete(info.permissionString); 1.996 + if (actions.size == 0) { 1.997 + break; 1.998 + } 1.999 + } 1.1000 + } 1.1001 + 1.1002 + // Set up the icon 1.1003 + document.getElementById("plugins-notification-icon").classList. 1.1004 + toggle("plugin-blocked", haveInsecure); 1.1005 + 1.1006 + // Now configure the notification bar 1.1007 + 1.1008 + let notificationBox = gBrowser.getNotificationBox(aBrowser); 1.1009 + 1.1010 + function hideNotification() { 1.1011 + let n = notificationBox.getNotificationWithValue("plugin-hidden"); 1.1012 + if (n) { 1.1013 + notificationBox.removeNotification(n, true); 1.1014 + } 1.1015 + } 1.1016 + 1.1017 + // There are three different cases when showing an infobar: 1.1018 + // 1. A single type of plugin is hidden on the page. Show the UI for that 1.1019 + // plugin. 1.1020 + // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI 1.1021 + // with the vulnerable styling. 1.1022 + // 2b. Multiple types of plugins are hidden on the page, but none are 1.1023 + // vulnerable. Show the nonvulnerable multi-UI. 1.1024 + function showNotification() { 1.1025 + let n = notificationBox.getNotificationWithValue("plugin-hidden"); 1.1026 + if (n) { 1.1027 + // If something is already shown, just keep it 1.1028 + return; 1.1029 + } 1.1030 + 1.1031 + Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN"). 1.1032 + add(true); 1.1033 + 1.1034 + let message; 1.1035 + // Icons set directly cannot be manipulated using moz-image-region, so 1.1036 + // we use CSS classes instead. 1.1037 + let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal); 1.1038 + let brand = document.getElementById("bundle_brand").getString("brandShortName"); 1.1039 + 1.1040 + if (actions.size == 1) { 1.1041 + let pluginInfo = [...actions.values()][0]; 1.1042 + let pluginName = pluginInfo.pluginName; 1.1043 + 1.1044 + switch (pluginInfo.fallbackType) { 1.1045 + case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: 1.1046 + message = gNavigatorBundle.getFormattedString( 1.1047 + "pluginActivateNew.message", 1.1048 + [pluginName, host]); 1.1049 + break; 1.1050 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: 1.1051 + message = gNavigatorBundle.getFormattedString( 1.1052 + "pluginActivateOutdated.message", 1.1053 + [pluginName, host, brand]); 1.1054 + break; 1.1055 + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: 1.1056 + message = gNavigatorBundle.getFormattedString( 1.1057 + "pluginActivateVulnerable.message", 1.1058 + [pluginName, host, brand]); 1.1059 + } 1.1060 + } else { 1.1061 + // Multi-plugin 1.1062 + message = gNavigatorBundle.getFormattedString( 1.1063 + "pluginActivateMultiple.message", [host]); 1.1064 + 1.1065 + for (let action of actions.values()) { 1.1066 + if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) { 1.1067 + break; 1.1068 + } 1.1069 + } 1.1070 + } 1.1071 + 1.1072 + let buttons = [ 1.1073 + { 1.1074 + label: gNavigatorBundle.getString("pluginContinueBlocking.label"), 1.1075 + accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"), 1.1076 + callback: function() { 1.1077 + Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK"). 1.1078 + add(true); 1.1079 + 1.1080 + Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal, 1.1081 + "plugin-hidden-notification", 1.1082 + Services.perms.DENY_ACTION); 1.1083 + } 1.1084 + }, 1.1085 + { 1.1086 + label: gNavigatorBundle.getString("pluginActivateTrigger.label"), 1.1087 + accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"), 1.1088 + callback: function() { 1.1089 + Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW"). 1.1090 + add(true); 1.1091 + 1.1092 + let curNotification = 1.1093 + PopupNotifications.getNotification("click-to-play-plugins", 1.1094 + aBrowser); 1.1095 + if (curNotification) { 1.1096 + curNotification.reshow(); 1.1097 + } 1.1098 + } 1.1099 + } 1.1100 + ]; 1.1101 + n = notificationBox. 1.1102 + appendNotification(message, "plugin-hidden", null, 1.1103 + notificationBox.PRIORITY_INFO_HIGH, buttons); 1.1104 + if (haveInsecure) { 1.1105 + n.classList.add('pluginVulnerable'); 1.1106 + } 1.1107 + } 1.1108 + 1.1109 + if (actions.size == 0) { 1.1110 + hideNotification(); 1.1111 + } else { 1.1112 + let notificationPermission = Services.perms.testPermissionFromPrincipal( 1.1113 + aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification"); 1.1114 + if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) { 1.1115 + hideNotification(); 1.1116 + } else { 1.1117 + showNotification(); 1.1118 + } 1.1119 + } 1.1120 + }, 1.1121 + 1.1122 + // Crashed-plugin observer. Notified once per plugin crash, before events 1.1123 + // are dispatched to individual plugin instances. 1.1124 + pluginCrashed : function(subject, topic, data) { 1.1125 + let propertyBag = subject; 1.1126 + if (!(propertyBag instanceof Ci.nsIPropertyBag2) || 1.1127 + !(propertyBag instanceof Ci.nsIWritablePropertyBag2)) 1.1128 + return; 1.1129 + 1.1130 +#ifdef MOZ_CRASHREPORTER 1.1131 + let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID"); 1.1132 + let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID"); 1.1133 + let shouldSubmit = gCrashReporter.submitReports; 1.1134 + let doPrompt = true; // XXX followup to get via gCrashReporter 1.1135 + 1.1136 + // Submit automatically when appropriate. 1.1137 + if (pluginDumpID && shouldSubmit && !doPrompt) { 1.1138 + this.submitReport(pluginDumpID, browserDumpID); 1.1139 + // Submission is async, so we can't easily show failure UI. 1.1140 + propertyBag.setPropertyAsBool("submittedCrashReport", true); 1.1141 + } 1.1142 +#endif 1.1143 + }, 1.1144 + 1.1145 + // Crashed-plugin event listener. Called for every instance of a 1.1146 + // plugin in content. 1.1147 + pluginInstanceCrashed: function (plugin, aEvent) { 1.1148 + // Ensure the plugin and event are of the right type. 1.1149 + if (!(aEvent instanceof Ci.nsIDOMCustomEvent)) 1.1150 + return; 1.1151 + 1.1152 + let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); 1.1153 + let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); 1.1154 + let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); 1.1155 + let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); 1.1156 + let pluginName = propBag.getPropertyAsAString("pluginName"); 1.1157 + let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); 1.1158 + let browserDumpID = propBag.getPropertyAsAString("browserDumpID"); 1.1159 + 1.1160 + // Remap the plugin name to a more user-presentable form. 1.1161 + pluginName = this.makeNicePluginName(pluginName); 1.1162 + 1.1163 + let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); 1.1164 + 1.1165 + // 1.1166 + // Configure the crashed-plugin placeholder. 1.1167 + // 1.1168 + 1.1169 + // Force a layout flush so the binding is attached. 1.1170 + plugin.clientTop; 1.1171 + let overlay = this.getPluginUI(plugin, "main"); 1.1172 + let statusDiv = this.getPluginUI(plugin, "submitStatus"); 1.1173 + let doc = plugin.ownerDocument; 1.1174 +#ifdef MOZ_CRASHREPORTER 1.1175 + let status; 1.1176 + 1.1177 + // Determine which message to show regarding crash reports. 1.1178 + if (submittedReport) { // submitReports && !doPrompt, handled in observer 1.1179 + status = "submitted"; 1.1180 + } 1.1181 + else if (!submitReports && !doPrompt) { 1.1182 + status = "noSubmit"; 1.1183 + } 1.1184 + else { // doPrompt 1.1185 + status = "please"; 1.1186 + this.getPluginUI(plugin, "submitButton").addEventListener("click", 1.1187 + function (event) { 1.1188 + if (event.button != 0 || !event.isTrusted) 1.1189 + return; 1.1190 + this.submitReport(pluginDumpID, browserDumpID, plugin); 1.1191 + pref.setBoolPref("", optInCB.checked); 1.1192 + }.bind(this)); 1.1193 + let optInCB = this.getPluginUI(plugin, "submitURLOptIn"); 1.1194 + let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL"); 1.1195 + optInCB.checked = pref.getBoolPref(""); 1.1196 + } 1.1197 + 1.1198 + // If we don't have a minidumpID, we can't (or didn't) submit anything. 1.1199 + // This can happen if the plugin is killed from the task manager. 1.1200 + if (!pluginDumpID) { 1.1201 + status = "noReport"; 1.1202 + } 1.1203 + 1.1204 + statusDiv.setAttribute("status", status); 1.1205 + 1.1206 + let helpIcon = this.getPluginUI(plugin, "helpIcon"); 1.1207 + this.addLinkClickCallback(helpIcon, "openHelpPage"); 1.1208 + 1.1209 + // If we're showing the link to manually trigger report submission, we'll 1.1210 + // want to be able to update all the instances of the UI for this crash to 1.1211 + // show an updated message when a report is submitted. 1.1212 + if (doPrompt) { 1.1213 + let observer = { 1.1214 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, 1.1215 + Ci.nsISupportsWeakReference]), 1.1216 + observe : function(subject, topic, data) { 1.1217 + let propertyBag = subject; 1.1218 + if (!(propertyBag instanceof Ci.nsIPropertyBag2)) 1.1219 + return; 1.1220 + // Ignore notifications for other crashes. 1.1221 + if (propertyBag.get("minidumpID") != pluginDumpID) 1.1222 + return; 1.1223 + statusDiv.setAttribute("status", data); 1.1224 + }, 1.1225 + 1.1226 + handleEvent : function(event) { 1.1227 + // Not expected to be called, just here for the closure. 1.1228 + } 1.1229 + } 1.1230 + 1.1231 + // Use a weak reference, so we don't have to remove it... 1.1232 + Services.obs.addObserver(observer, "crash-report-status", true); 1.1233 + // ...alas, now we need something to hold a strong reference to prevent 1.1234 + // it from being GC. But I don't want to manually manage the reference's 1.1235 + // lifetime (which should be no greater than the page). 1.1236 + // Clever solution? Use a closue with an event listener on the document. 1.1237 + // When the doc goes away, so do the listener references and the closure. 1.1238 + doc.addEventListener("mozCleverClosureHack", observer, false); 1.1239 + } 1.1240 +#endif 1.1241 + 1.1242 + let crashText = this.getPluginUI(plugin, "crashedText"); 1.1243 + crashText.textContent = messageString; 1.1244 + 1.1245 + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); 1.1246 + 1.1247 + let link = this.getPluginUI(plugin, "reloadLink"); 1.1248 + this.addLinkClickCallback(link, "reloadPage", browser); 1.1249 + 1.1250 + let notificationBox = gBrowser.getNotificationBox(browser); 1.1251 + 1.1252 + let isShowing = this.shouldShowOverlay(plugin, overlay); 1.1253 + 1.1254 + // Is the <object>'s size too small to hold what we want to show? 1.1255 + if (!isShowing) { 1.1256 + // First try hiding the crash report submission UI. 1.1257 + statusDiv.removeAttribute("status"); 1.1258 + 1.1259 + isShowing = this.shouldShowOverlay(plugin, overlay); 1.1260 + } 1.1261 + this.setVisibility(plugin, overlay, isShowing); 1.1262 + 1.1263 + if (isShowing) { 1.1264 + // If a previous plugin on the page was too small and resulted in adding a 1.1265 + // notification bar, then remove it because this plugin instance it big 1.1266 + // enough to serve as in-content notification. 1.1267 + hideNotificationBar(); 1.1268 + doc.mozNoPluginCrashedNotification = true; 1.1269 + } else { 1.1270 + // If another plugin on the page was large enough to show our UI, we don't 1.1271 + // want to show a notification bar. 1.1272 + if (!doc.mozNoPluginCrashedNotification) 1.1273 + showNotificationBar(pluginDumpID, browserDumpID); 1.1274 + } 1.1275 + 1.1276 + function hideNotificationBar() { 1.1277 + let notification = notificationBox.getNotificationWithValue("plugin-crashed"); 1.1278 + if (notification) 1.1279 + notificationBox.removeNotification(notification, true); 1.1280 + } 1.1281 + 1.1282 + function showNotificationBar(pluginDumpID, browserDumpID) { 1.1283 + // If there's already an existing notification bar, don't do anything. 1.1284 + let notification = notificationBox.getNotificationWithValue("plugin-crashed"); 1.1285 + if (notification) 1.1286 + return; 1.1287 + 1.1288 + // Configure the notification bar 1.1289 + let priority = notificationBox.PRIORITY_WARNING_MEDIUM; 1.1290 + let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png"; 1.1291 + let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label"); 1.1292 + let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey"); 1.1293 + let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label"); 1.1294 + let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey"); 1.1295 + 1.1296 + let buttons = [{ 1.1297 + label: reloadLabel, 1.1298 + accessKey: reloadKey, 1.1299 + popup: null, 1.1300 + callback: function() { browser.reload(); }, 1.1301 + }]; 1.1302 +#ifdef MOZ_CRASHREPORTER 1.1303 + let submitButton = { 1.1304 + label: submitLabel, 1.1305 + accessKey: submitKey, 1.1306 + popup: null, 1.1307 + callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); }, 1.1308 + }; 1.1309 + if (pluginDumpID) 1.1310 + buttons.push(submitButton); 1.1311 +#endif 1.1312 + 1.1313 + let notification = notificationBox.appendNotification(messageString, "plugin-crashed", 1.1314 + iconURL, priority, buttons); 1.1315 + 1.1316 + // Add the "learn more" link. 1.1317 + let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 1.1318 + let link = notification.ownerDocument.createElementNS(XULNS, "label"); 1.1319 + link.className = "text-link"; 1.1320 + link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore")); 1.1321 + let crashurl = formatURL("app.support.baseURL", true); 1.1322 + crashurl += "plugin-crashed-notificationbar"; 1.1323 + link.href = crashurl; 1.1324 + 1.1325 + let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); 1.1326 + description.appendChild(link); 1.1327 + 1.1328 + // Remove the notfication when the page is reloaded. 1.1329 + doc.defaultView.top.addEventListener("unload", function() { 1.1330 + notificationBox.removeNotification(notification); 1.1331 + }, false); 1.1332 + } 1.1333 + 1.1334 + } 1.1335 +};