diff options
author | Thomas Groman <tgroman@nuegia.net> | 2019-12-16 19:48:42 -0800 |
---|---|---|
committer | Thomas Groman <tgroman@nuegia.net> | 2019-12-16 19:48:42 -0800 |
commit | 4492b5f8e774bf3b4f21e4e468fc052cbcbb468a (patch) | |
tree | 37970571a7dcbeb6b58c991ce718ce7001ac97d6 /base/content/browser-plugins.js | |
download | webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.gz webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.lz webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.xz webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.zip |
initial commit
Diffstat (limited to 'base/content/browser-plugins.js')
-rw-r--r-- | base/content/browser-plugins.js | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/base/content/browser-plugins.js b/base/content/browser-plugins.js new file mode 100644 index 0000000..8382682 --- /dev/null +++ b/base/content/browser-plugins.js @@ -0,0 +1,781 @@ +# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +const kPrefSessionPersistMinutes = "plugin.sessionPermissionNow.intervalInMinutes"; +const kPrefPersistentDays = "plugin.persistentPermissionAlways.intervalInDays"; + +var gPluginHandler = { + PLUGIN_SCRIPTED_STATE_NONE: 0, + PLUGIN_SCRIPTED_STATE_FIRED: 1, + PLUGIN_SCRIPTED_STATE_DONE: 2, + + getPluginUI: function (plugin, anonid) { + return plugin.ownerDocument. + getAnonymousElementByAttribute(plugin, "anonid", anonid); + }, + + _getPluginInfo: function (pluginElement) { + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); + + let tagMimetype; + let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin"); + let pluginTag = null; + let permissionString = null; + let fallbackType = null; + let blocklistState = null; + + if (pluginElement instanceof HTMLAppletElement) { + tagMimetype = "application/x-java-vm"; + } else { + tagMimetype = pluginElement.actualType; + + if (tagMimetype == "") { + tagMimetype = pluginElement.type; + } + } + + if (gPluginHandler.isKnownPlugin(pluginElement)) { + pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); + pluginName = gPluginHandler.makeNicePluginName(pluginTag.name); + + permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType); + fallbackType = pluginElement.defaultFallbackType; + blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType); + // Make state-softblocked == state-notblocked for our purposes, + // they have the same UI. STATE_OUTDATED should not exist for plugin + // items, but let's alias it anyway, just in case. + if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED || + blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { + blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED; + } + } + + return { mimetype: tagMimetype, + pluginName: pluginName, + pluginTag: pluginTag, + permissionString: permissionString, + fallbackType: fallbackType, + blocklistState: blocklistState, + }; + }, + + // Map the plugin's name to a filtered version more suitable for user UI. + makeNicePluginName : function (aName) { + if (aName == "Shockwave Flash") + return "Adobe Flash"; + + // Clean up the plugin name by stripping off any trailing version numbers + // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar" + // Do this by first stripping the numbers, etc. off the end, and then + // removing "Plugin" (and then trimming to get rid of any whitespace). + // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled) + let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim(); + return newName; + }, + + isTooSmall : function (plugin, overlay) { + // Is the <object>'s size too small to hold what we want to show? + let pluginRect = plugin.getBoundingClientRect(); + // XXX bug 446693. The text-shadow on the submitted-report text at + // the bottom causes scrollHeight to be larger than it should be. + // Clamp width/height to properly show CTP overlay on different + // zoom levels when embedded in iframes (rounding bug). (Bug 972237) + let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) || + (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height)); + return overflows; + }, + + addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) { + // XXX just doing (callback)(arg) was giving a same-origin error. bug? + let self = this; + let callbackArgs = Array.prototype.slice.call(arguments).slice(2); + linkNode.addEventListener("click", + function(evt) { + if (!evt.isTrusted) + return; + evt.preventDefault(); + if (callbackArgs.length == 0) + callbackArgs = [ evt ]; + (self[callbackName]).apply(self, callbackArgs); + }, + true); + + linkNode.addEventListener("keydown", + function(evt) { + if (!evt.isTrusted) + return; + if (evt.keyCode == evt.DOM_VK_RETURN) { + evt.preventDefault(); + if (callbackArgs.length == 0) + callbackArgs = [ evt ]; + evt.preventDefault(); + (self[callbackName]).apply(self, callbackArgs); + } + }, + true); + }, + + // Helper to get the binding handler type from a plugin object + _getBindingType : function(plugin) { + if (!(plugin instanceof Ci.nsIObjectLoadingContent)) + return null; + + switch (plugin.pluginFallbackType) { + case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: + return "PluginNotFound"; + case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED: + return "PluginDisabled"; + case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED: + return "PluginBlocklisted"; + case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED: + return "PluginOutdated"; + case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: + return "PluginClickToPlay"; + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: + return "PluginVulnerableUpdatable"; + case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: + return "PluginVulnerableNoUpdate"; + case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: + return "PluginPlayPreview"; + default: + // Not all states map to a handler + return null; + } + }, + + handleEvent : function(event) { + let plugin; + let doc; + + let eventType = event.type; + if (eventType === "PluginRemoved") { + doc = event.target; + } + else { + plugin = event.target; + doc = plugin.ownerDocument; + + if (!(plugin instanceof Ci.nsIObjectLoadingContent)) + return; + } + + if (eventType == "PluginBindingAttached") { + // The plugin binding fires this event when it is created. + // As an untrusted event, ensure that this object actually has a binding + // and make sure we don't handle it twice + let overlay = this.getPluginUI(plugin, "main"); + if (!overlay || overlay._bindingHandled) { + return; + } + overlay._bindingHandled = true; + + // Lookup the handler for this binding + eventType = this._getBindingType(plugin); + if (!eventType) { + // Not all bindings have handlers + return; + } + } + + let shouldShowNotification = false; + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + if (!browser) + return; + + switch (eventType) { + case "PluginCrashed": + this.pluginInstanceCrashed(plugin, event); + break; + + case "PluginNotFound": + /* No action (plugin finder obsolete) */ + break; + + case "PluginBlocklisted": + case "PluginOutdated": + shouldShowNotification = true; + break; + + case "PluginVulnerableUpdatable": + let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); + this.addLinkClickCallback(updateLink, "openPluginUpdatePage"); + /* FALLTHRU */ + + case "PluginVulnerableNoUpdate": + case "PluginClickToPlay": + this._handleClickToPlayEvent(plugin); + let overlay = this.getPluginUI(plugin, "main"); + let pluginName = this._getPluginInfo(plugin).pluginName; + let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]); + let overlayText = this.getPluginUI(plugin, "clickToPlay"); + overlayText.textContent = messageString; + if (eventType == "PluginVulnerableUpdatable" || + eventType == "PluginVulnerableNoUpdate") { + let vulnerabilityString = gNavigatorBundle.getString(eventType); + let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus"); + vulnerabilityText.textContent = vulnerabilityString; + } + shouldShowNotification = true; + break; + + case "PluginPlayPreview": + this._handlePlayPreviewEvent(plugin); + break; + + case "PluginDisabled": + let manageLink = this.getPluginUI(plugin, "managePluginsLink"); + this.addLinkClickCallback(manageLink, "managePlugins"); + shouldShowNotification = true; + break; + + case "PluginInstantiated": + //Pale Moon: don't show the indicator when plugins are enabled/allowed + if (gPrefService.getBoolPref("plugins.always_show_indicator")) { + shouldShowNotification = true; + } + break; + case "PluginRemoved": + shouldShowNotification = true; + break; + } + + // Show the in-content UI if it's not too big. The crashed plugin handler already did this. + if (eventType != "PluginCrashed" && eventType != "PluginRemoved") { + let overlay = this.getPluginUI(plugin, "main"); + if (overlay != null) { + if (!this.isTooSmall(plugin, overlay)) { + overlay.style.visibility = "visible"; + } + plugin.addEventListener("overflow", function(event) { + overlay.style.visibility = "hidden"; + }); + plugin.addEventListener("underflow", function(event) { + // this is triggered if only one dimension underflows, + // the other dimension might still overflow + if (!gPluginHandler.isTooSmall(plugin, overlay)) { + overlay.style.visibility = "visible"; + } + }); + } + } + + // Only show the notification after we've done the isTooSmall check, so + // that the notification can decide whether to show the "alert" icon + if (shouldShowNotification) { + this._showClickToPlayNotification(browser); + } + }, + + isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) { + return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == + Ci.nsIObjectLoadingContent.TYPE_PLUGIN); + }, + + canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) { + // if this isn't a known plugin, we can't activate it + // (this also guards pluginHost.getPermissionStringForType against + // unexpected input) + if (!gPluginHandler.isKnownPlugin(objLoadingContent)) + return false; + + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); + let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal; + let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); + + let isFallbackTypeValid = + objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && + objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; + + if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) { + // checking if play preview is subject to CTP rules + let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType); + isFallbackTypeValid = !playPreviewInfo.ignoreCTP; + } + + return !objLoadingContent.activated && + pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && + isFallbackTypeValid; + }, + + hideClickToPlayOverlay: function(aPlugin) { + let overlay = this.getPluginUI(aPlugin, "main"); + if (overlay) + overlay.style.visibility = "hidden"; + }, + + stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) { + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (objLoadingContent.activated) + return; + + if (aPlayPlugin) + objLoadingContent.playPlugin(); + else + objLoadingContent.cancelPlayPreview(); + }, + + // Callback for user clicking on a disabled plugin + managePlugins: function (aEvent) { + BrowserOpenAddonsMgr("addons://list/plugin"); + }, + + // Callback for user clicking on the link in a click-to-play plugin + // (where the plugin has an update) + openPluginUpdatePage: function (aEvent) { + openURL(Services.urlFormatter.formatURLPref("plugins.update.url")); + }, + + // Callback for user clicking a "reload page" link + reloadPage: function (browser) { + browser.reload(); + }, + + // Callback for user clicking the help icon + openHelpPage: function () { + openHelpLink("plugin-crashed", false); + }, + + // Event listener for click-to-play plugins. + _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) { + let doc = aPlugin.ownerDocument; + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); + // guard against giving pluginHost.getPermissionStringForType a type + // not associated with any known plugin + if (!gPluginHandler.isKnownPlugin(objLoadingContent)) + return; + let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); + let principal = doc.defaultView.top.document.nodePrincipal; + let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); + + let overlay = this.getPluginUI(aPlugin, "main"); + + if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { + if (overlay) + overlay.style.visibility = "hidden"; + return; + } + + if (overlay) { + overlay.addEventListener("click", gPluginHandler._overlayClickListener, true); + let closeIcon = gPluginHandler.getPluginUI(aPlugin, "closeIcon"); + closeIcon.addEventListener("click", function(aEvent) { + if (aEvent.button == 0 && aEvent.isTrusted) + gPluginHandler.hideClickToPlayOverlay(aPlugin); + }, true); + } + }, + + _overlayClickListener: { + handleEvent: function PH_handleOverlayClick(aEvent) { + let plugin = document.getBindingParent(aEvent.target); + let contentWindow = plugin.ownerDocument.defaultView.top; + // gBrowser.getBrowserForDocument does not exist in the case where we + // drag-and-dropped a tab from a window containing only that tab. In + // that case, the window gets destroyed. + let browser = gBrowser.getBrowserForDocument ? + gBrowser.getBrowserForDocument(contentWindow.document) : + null; + // If browser is null here, we've been drag-and-dropped from another + // window, and this is the wrong click handler. + if (!browser) { + aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true); + return; + } + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + // Have to check that the target is not the link to update the plugin + if (!(aEvent.originalTarget instanceof HTMLAnchorElement) && + (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') && + aEvent.button == 0 && aEvent.isTrusted) { + gPluginHandler._showClickToPlayNotification(browser, plugin); + aEvent.stopPropagation(); + aEvent.preventDefault(); + } + } + }, + + _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) { + let doc = aPlugin.ownerDocument; + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let pluginInfo = this._getPluginInfo(aPlugin); + let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype); + + let previewContent = this.getPluginUI(aPlugin, "previewPluginContent"); + let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; + if (!iframe) { + // lazy initialization of the iframe + iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); + iframe.className = "previewPluginContentFrame"; + previewContent.appendChild(iframe); + + // Force a style flush, so that we ensure our binding is attached. + aPlugin.clientTop; + } + iframe.src = playPreviewInfo.redirectURL; + + // MozPlayPlugin event can be dispatched from the extension chrome + // code to replace the preview content with the native plugin + previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) { + if (!aEvent.isTrusted) + return; + + previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); + + let playPlugin = !aEvent.detail; + gPluginHandler.stopPlayPreview(aPlugin, playPlugin); + + // cleaning up: removes overlay iframe from the DOM + let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; + if (iframe) + previewContent.removeChild(iframe); + }, true); + + if (!playPreviewInfo.ignoreCTP) { + gPluginHandler._showClickToPlayNotification(browser); + } + }, + + reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { + let browser = gBrowser.selectedBrowser; + let contentWindow = browser.contentWindow; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let doc = contentWindow.document; + let plugins = cwu.plugins; + for (let plugin of plugins) { + let overlay = doc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + if (overlay) + overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); + let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (gPluginHandler.canActivatePlugin(objLoadingContent)) + gPluginHandler._handleClickToPlayEvent(plugin); + } + gPluginHandler._showClickToPlayNotification(browser); + }, + + _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) { + if (event == "showing") { + gPluginHandler._makeCenterActions(this); + } + else if (event == "dismissed") { + // Once the popup is dismissed, clicking the icon should show the full + // list again + this.options.primaryPlugin = null; + } + }, + + _makeCenterActions: function PH_makeCenterActions(notification) { + let contentWindow = notification.browser.contentWindow; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let principal = contentWindow.document.nodePrincipal; + + let centerActions = []; + let pluginsFound = new Set(); + for (let plugin of cwu.plugins) { + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + if (plugin.getContentTypeForMIMEType(plugin.actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN) { + continue; + } + + let pluginInfo = this._getPluginInfo(plugin); + if (pluginInfo.permissionString === null) { + Components.utils.reportError("No permission string for active plugin."); + continue; + } + if (pluginsFound.has(pluginInfo.permissionString)) { + continue; + } + pluginsFound.add(pluginInfo.permissionString); + + // Add the per-site permissions and details URLs to pluginInfo here + // because they are more expensive to compute and so we avoid it in + // the tighter loop above. + let permissionObj = Services.perms. + getPermissionObject(principal, pluginInfo.permissionString, false); + if (permissionObj) { + pluginInfo.pluginPermissionPrePath = permissionObj.principal.originNoSuffix; + pluginInfo.pluginPermissionType = permissionObj.expireType; + } + else { + pluginInfo.pluginPermissionPrePath = principal.originNoSuffix; + pluginInfo.pluginPermissionType = undefined; + } + + let url; + // TODO: allow the blocklist to specify a better link, bug 873093 + if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { + url = Services.urlFormatter.formatURLPref("plugins.update.url"); + } + else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { + url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag); + } + pluginInfo.detailsLink = url; + + centerActions.push(pluginInfo); + } + centerActions.sort(function(a, b) { + return a.pluginName.localeCompare(b.pluginName); + }); + + notification.options.centerActions = centerActions; + }, + + /** + * Called from the plugin doorhanger to set the new permissions for a plugin + * and activate plugins if necessary. + * aNewState should be either "allownow" "allowalways" or "block" + */ + _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) { + let permission; + let expireType; + let expireTime; + + switch (aNewState) { + case "allownow": + permission = Ci.nsIPermissionManager.ALLOW_ACTION; + expireType = Ci.nsIPermissionManager.EXPIRE_SESSION; + expireTime = Date.now() + Services.prefs.getIntPref(kPrefSessionPersistMinutes) * 60 * 1000; + break; + + case "allowalways": + permission = Ci.nsIPermissionManager.ALLOW_ACTION; + expireType = Ci.nsIPermissionManager.EXPIRE_TIME; + expireTime = Date.now() + + Services.prefs.getIntPref(kPrefPersistentDays) * 24 * 60 * 60 * 1000; + break; + + case "block": + permission = Ci.nsIPermissionManager.PROMPT_ACTION; + expireType = Ci.nsIPermissionManager.EXPIRE_NEVER; + expireTime = 0; + break; + + // In case a plugin has already been allowed in another tab, the "continue allowing" button + // shouldn't change any permissions but should run the plugin-enablement code below. + case "continue": + break; + + default: + Cu.reportError(Error("Unexpected plugin state: " + aNewState)); + return; + } + + let browser = aNotification.browser; + let contentWindow = browser.contentWindow; + if (aNewState != "continue") { + let principal = contentWindow.document.nodePrincipal; + Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString, + permission, expireType, expireTime); + + if (aNewState == "block") { + return; + } + } + + // Manually activate the plugins that would have been automatically + // activated. + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + let plugins = cwu.plugins; + let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + + for (let plugin of plugins) { + plugin.QueryInterface(Ci.nsIObjectLoadingContent); + // canActivatePlugin will return false if this isn't a known plugin type, + // so the pluginHost.getPermissionStringForType call is protected + if (gPluginHandler.canActivatePlugin(plugin) && + aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { + plugin.playPlugin(); + } + } + }, + + _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPrimaryPlugin) { + let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); + + let contentWindow = aBrowser.contentWindow; + let contentDoc = aBrowser.contentDocument; + let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + // Pale Moon: cwu.plugins may contain non-plugin <object>s, filter them out + let plugins = cwu.plugins.filter(function(plugin) { + return (plugin.getContentTypeForMIMEType(plugin.actualType) == + Ci.nsIObjectLoadingContent.TYPE_PLUGIN); + }); + if (plugins.length == 0) { + if (notification) { + PopupNotifications.remove(notification); + } + return; + } + + let icon = 'plugins-notification-icon'; + for (let plugin of plugins) { + let fallbackType = plugin.pluginFallbackType; + if (fallbackType == plugin.PLUGIN_VULNERABLE_UPDATABLE || + fallbackType == plugin.PLUGIN_VULNERABLE_NO_UPDATE || + fallbackType == plugin.PLUGIN_BLOCKLISTED) { + icon = 'blocked-plugins-notification-icon'; + break; + } + if (fallbackType == plugin.PLUGIN_CLICK_TO_PLAY) { + let overlay = contentDoc.getAnonymousElementByAttribute(plugin, "anonid", "main"); + if (!overlay || overlay.style.visibility == 'hidden') { + icon = 'alert-plugins-notification-icon'; + } + } + } + + let dismissed = notification ? notification.dismissed : true; + if (aPrimaryPlugin) + dismissed = false; + + let primaryPluginPermission = null; + if (aPrimaryPlugin) { + primaryPluginPermission = this._getPluginInfo(aPrimaryPlugin).permissionString; + } + + let options = { + dismissed: dismissed, + eventCallback: this._clickToPlayNotificationEventCallback, + primaryPlugin: primaryPluginPermission + }; + PopupNotifications.show(aBrowser, "click-to-play-plugins", + "", icon, + null, null, options); + }, + + // Crashed-plugin observer. Notified once per plugin crash, before events + // are dispatched to individual plugin instances. + pluginCrashed : function(subject, topic, data) { + let propertyBag = subject; + if (!(propertyBag instanceof Ci.nsIPropertyBag2) || + !(propertyBag instanceof Ci.nsIWritablePropertyBag2)) + return; + }, + + // Crashed-plugin event listener. Called for every instance of a + // plugin in content. + pluginInstanceCrashed: function (plugin, aEvent) { + // Ensure the plugin and event are of the right type. + if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent)) + return; + + let submittedReport = aEvent.getData("submittedCrashReport"); + let doPrompt = true; // XXX followup for .getData("doPrompt"); + let submitReports = true; // XXX followup for .getData("submitReports"); + let pluginName = aEvent.getData("pluginName"); + let pluginDumpID = aEvent.getData("pluginDumpID"); + let browserDumpID = aEvent.getData("browserDumpID"); + + // Remap the plugin name to a more user-presentable form. + pluginName = this.makeNicePluginName(pluginName); + + let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); + + // + // Configure the crashed-plugin placeholder. + // + + // Force a layout flush so the binding is attached. + plugin.clientTop; + let doc = plugin.ownerDocument; + let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); + let statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus"); + + let crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msgCrashedText"); + crashText.textContent = messageString; + + let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); + + let link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink"); + this.addLinkClickCallback(link, "reloadPage", browser); + + let notificationBox = gBrowser.getNotificationBox(browser); + + let isShowing = true; + + // Is the <object>'s size too small to hold what we want to show? + if (this.isTooSmall(plugin, overlay)) { + // First try hiding the crash report submission UI. + statusDiv.removeAttribute("status"); + + if (this.isTooSmall(plugin, overlay)) { + // Hide the overlay's contents. Use visibility style, so that it doesn't + // collapse down to 0x0. + overlay.style.visibility = "hidden"; + isShowing = false; + } + } + + if (isShowing) { + // If a previous plugin on the page was too small and resulted in adding a + // notification bar, then remove it because this plugin instance it big + // enough to serve as in-content notification. + hideNotificationBar(); + doc.mozNoPluginCrashedNotification = true; + } else { + // If another plugin on the page was large enough to show our UI, we don't + // want to show a notification bar. + if (!doc.mozNoPluginCrashedNotification) + showNotificationBar(pluginDumpID, browserDumpID); + } + + function hideNotificationBar() { + let notification = notificationBox.getNotificationWithValue("plugin-crashed"); + if (notification) + notificationBox.removeNotification(notification, true); + } + + function showNotificationBar(pluginDumpID, browserDumpID) { + // If there's already an existing notification bar, don't do anything. + let notification = notificationBox.getNotificationWithValue("plugin-crashed"); + if (notification) + return; + + // Configure the notification bar + let priority = notificationBox.PRIORITY_WARNING_MEDIUM; + let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png"; + let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label"); + let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey"); + let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label"); + let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey"); + + let buttons = [{ + label: reloadLabel, + accessKey: reloadKey, + popup: null, + callback: function() { browser.reload(); }, + }]; + + notification = notificationBox.appendNotification(messageString, "plugin-crashed", + iconURL, priority, buttons); + + // Add the "learn more" link. + let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + let link = notification.ownerDocument.createElementNS(XULNS, "label"); + link.className = "text-link"; + link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore")); + let crashurl = formatURL("app.support.baseURL", true); + crashurl += "plugin-crashed-notificationbar"; + link.href = crashurl; + + let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); + description.appendChild(link); + + // Remove the notfication when the page is reloaded. + doc.defaultView.top.addEventListener("unload", function() { + notificationBox.removeNotification(notification); + }, false); + } + + } +}; |