# -*- 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 '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(objLoadingContent) { return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); }, canActivatePlugin: function(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(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 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(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(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(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() { 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(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(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; 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(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(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 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 '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); } } };