michael@0: # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: var gPluginHandler = { michael@0: PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash", michael@0: PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification", michael@0: PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes", michael@0: PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays", michael@0: michael@0: getPluginUI: function (plugin, anonid) { michael@0: return plugin.ownerDocument. michael@0: getAnonymousElementByAttribute(plugin, "anonid", anonid); michael@0: }, michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: get CrashSubmit() { michael@0: delete this.CrashSubmit; michael@0: Cu.import("resource://gre/modules/CrashSubmit.jsm", this); michael@0: return this.CrashSubmit; michael@0: }, michael@0: #endif michael@0: michael@0: _getPluginInfo: function (pluginElement) { michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: michael@0: let tagMimetype; michael@0: let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin"); michael@0: let pluginTag = null; michael@0: let permissionString = null; michael@0: let fallbackType = null; michael@0: let blocklistState = null; michael@0: michael@0: tagMimetype = pluginElement.actualType; michael@0: if (tagMimetype == "") { michael@0: tagMimetype = pluginElement.type; michael@0: } michael@0: michael@0: if (gPluginHandler.isKnownPlugin(pluginElement)) { michael@0: pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); michael@0: pluginName = gPluginHandler.makeNicePluginName(pluginTag.name); michael@0: michael@0: permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType); michael@0: fallbackType = pluginElement.defaultFallbackType; michael@0: blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType); michael@0: // Make state-softblocked == state-notblocked for our purposes, michael@0: // they have the same UI. STATE_OUTDATED should not exist for plugin michael@0: // items, but let's alias it anyway, just in case. michael@0: if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED || michael@0: blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { michael@0: blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: } michael@0: } michael@0: michael@0: return { mimetype: tagMimetype, michael@0: pluginName: pluginName, michael@0: pluginTag: pluginTag, michael@0: permissionString: permissionString, michael@0: fallbackType: fallbackType, michael@0: blocklistState: blocklistState, michael@0: }; michael@0: }, michael@0: michael@0: // Map the plugin's name to a filtered version more suitable for user UI. michael@0: makeNicePluginName : function (aName) { michael@0: if (aName == "Shockwave Flash") michael@0: return "Adobe Flash"; michael@0: michael@0: // Clean up the plugin name by stripping off parenthetical clauses, michael@0: // trailing version numbers or "plugin". michael@0: // EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar" michael@0: // Do this by first stripping the numbers, etc. off the end, and then michael@0: // removing "Plugin" (and then trimming to get rid of any whitespace). michael@0: // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled) michael@0: let newName = aName.replace(/\(.*?\)/g, ""). michael@0: replace(/[\s\d\.\-\_\(\)]+$/, ""). michael@0: replace(/\bplug-?in\b/i, "").trim(); michael@0: return newName; michael@0: }, michael@0: michael@0: /** michael@0: * Update the visibility of the plugin overlay. michael@0: */ michael@0: setVisibility : function (plugin, overlay, shouldShow) { michael@0: overlay.classList.toggle("visible", shouldShow); michael@0: }, michael@0: michael@0: /** michael@0: * Check whether the plugin should be visible on the page. A plugin should michael@0: * not be visible if the overlay is too big, or if any other page content michael@0: * overlays it. michael@0: * michael@0: * This function will handle showing or hiding the overlay. michael@0: * @returns true if the plugin is invisible. michael@0: */ michael@0: shouldShowOverlay : function (plugin, overlay) { michael@0: // If the overlay size is 0, we haven't done layout yet. Presume that michael@0: // plugins are visible until we know otherwise. michael@0: if (overlay.scrollWidth == 0) { michael@0: return true; michael@0: } michael@0: michael@0: // Is the 's size too small to hold what we want to show? michael@0: let pluginRect = plugin.getBoundingClientRect(); michael@0: // XXX bug 446693. The text-shadow on the submitted-report text at michael@0: // the bottom causes scrollHeight to be larger than it should be. michael@0: let overflows = (overlay.scrollWidth > pluginRect.width) || michael@0: (overlay.scrollHeight - 5 > pluginRect.height); michael@0: if (overflows) { michael@0: return false; michael@0: } michael@0: michael@0: // Is the plugin covered up by other content so that it is not clickable? michael@0: // Floating point can confuse .elementFromPoint, so inset just a bit michael@0: let left = pluginRect.left + 2; michael@0: let right = pluginRect.right - 2; michael@0: let top = pluginRect.top + 2; michael@0: let bottom = pluginRect.bottom - 2; michael@0: let centerX = left + (right - left) / 2; michael@0: let centerY = top + (bottom - top) / 2; michael@0: let points = [[left, top], michael@0: [left, bottom], michael@0: [right, top], michael@0: [right, bottom], michael@0: [centerX, centerY]]; michael@0: michael@0: if (right <= 0 || top <= 0) { michael@0: return false; michael@0: } michael@0: michael@0: let contentWindow = plugin.ownerDocument.defaultView; michael@0: let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: michael@0: for (let [x, y] of points) { michael@0: let el = cwu.elementFromPoint(x, y, true, true); michael@0: if (el !== plugin) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) { michael@0: // XXX just doing (callback)(arg) was giving a same-origin error. bug? michael@0: let self = this; michael@0: let callbackArgs = Array.prototype.slice.call(arguments).slice(2); michael@0: linkNode.addEventListener("click", michael@0: function(evt) { michael@0: if (!evt.isTrusted) michael@0: return; michael@0: evt.preventDefault(); michael@0: if (callbackArgs.length == 0) michael@0: callbackArgs = [ evt ]; michael@0: (self[callbackName]).apply(self, callbackArgs); michael@0: }, michael@0: true); michael@0: michael@0: linkNode.addEventListener("keydown", michael@0: function(evt) { michael@0: if (!evt.isTrusted) michael@0: return; michael@0: if (evt.keyCode == evt.DOM_VK_RETURN) { michael@0: evt.preventDefault(); michael@0: if (callbackArgs.length == 0) michael@0: callbackArgs = [ evt ]; michael@0: evt.preventDefault(); michael@0: (self[callbackName]).apply(self, callbackArgs); michael@0: } michael@0: }, michael@0: true); michael@0: }, michael@0: michael@0: // Helper to get the binding handler type from a plugin object michael@0: _getBindingType : function(plugin) { michael@0: if (!(plugin instanceof Ci.nsIObjectLoadingContent)) michael@0: return null; michael@0: michael@0: switch (plugin.pluginFallbackType) { michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED: michael@0: return "PluginNotFound"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED: michael@0: return "PluginDisabled"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED: michael@0: return "PluginBlocklisted"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED: michael@0: return "PluginOutdated"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: michael@0: return "PluginClickToPlay"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: michael@0: return "PluginVulnerableUpdatable"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: michael@0: return "PluginVulnerableNoUpdate"; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW: michael@0: return "PluginPlayPreview"; michael@0: default: michael@0: // Not all states map to a handler michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: supportedPlugins: { michael@0: "mimetypes": { michael@0: "application/x-shockwave-flash": "flash", michael@0: "application/futuresplash": "flash", michael@0: "application/x-java-.*": "java", michael@0: "application/x-director": "shockwave", michael@0: "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime", michael@0: "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime", michael@0: "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime", michael@0: "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime", michael@0: "application/x-unknown": "test", michael@0: }, michael@0: michael@0: "plugins": { michael@0: "flash": { michael@0: "displayName": "Flash", michael@0: "installWINNT": true, michael@0: "installDarwin": true, michael@0: "installLinux": true, michael@0: }, michael@0: "java": { michael@0: "displayName": "Java", michael@0: "installWINNT": true, michael@0: "installDarwin": true, michael@0: "installLinux": true, michael@0: }, michael@0: "shockwave": { michael@0: "displayName": "Shockwave", michael@0: "installWINNT": true, michael@0: "installDarwin": true, michael@0: }, michael@0: "quicktime": { michael@0: "displayName": "QuickTime", michael@0: "installWINNT": true, michael@0: }, michael@0: "test": { michael@0: "displayName": "Test plugin", michael@0: "installWINNT": true, michael@0: "installLinux": true, michael@0: "installDarwin": true, michael@0: } michael@0: } michael@0: }, michael@0: michael@0: nameForSupportedPlugin: function (aMimeType) { michael@0: for (let type in this.supportedPlugins.mimetypes) { michael@0: let re = new RegExp(type); michael@0: if (re.test(aMimeType)) { michael@0: return this.supportedPlugins.mimetypes[type]; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: canInstallThisMimeType: function (aMimeType) { michael@0: let os = Services.appinfo.OS; michael@0: let pluginName = this.nameForSupportedPlugin(aMimeType); michael@0: if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) { michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: handleEvent : function(event) { michael@0: let eventType = event.type; michael@0: michael@0: if (eventType == "PluginRemoved") { michael@0: let doc = event.target; michael@0: let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); michael@0: if (browser) michael@0: this._setPluginNotificationIcon(browser); michael@0: return; michael@0: } michael@0: michael@0: let plugin = event.target; michael@0: let doc = plugin.ownerDocument; michael@0: michael@0: if (!(plugin instanceof Ci.nsIObjectLoadingContent)) michael@0: return; michael@0: michael@0: if (eventType == "PluginBindingAttached") { michael@0: // The plugin binding fires this event when it is created. michael@0: // As an untrusted event, ensure that this object actually has a binding michael@0: // and make sure we don't handle it twice michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: if (!overlay || overlay._bindingHandled) { michael@0: return; michael@0: } michael@0: overlay._bindingHandled = true; michael@0: michael@0: // Lookup the handler for this binding michael@0: eventType = this._getBindingType(plugin); michael@0: if (!eventType) { michael@0: // Not all bindings have handlers michael@0: return; michael@0: } michael@0: } michael@0: michael@0: let shouldShowNotification = false; michael@0: let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); michael@0: if (!browser) michael@0: return; michael@0: michael@0: switch (eventType) { michael@0: case "PluginCrashed": michael@0: this.pluginInstanceCrashed(plugin, event); michael@0: break; michael@0: michael@0: case "PluginNotFound": michael@0: let installable = this.showInstallNotification(plugin, eventType); michael@0: // For non-object plugin tags, register a click handler to install the michael@0: // plugin. Object tags can, and often do, deal with that themselves, michael@0: // so don't stomp on the page developers toes. michael@0: if (installable && !(plugin instanceof HTMLObjectElement)) { michael@0: let installStatus = this.getPluginUI(plugin, "installStatus"); michael@0: installStatus.setAttribute("installable", "true"); michael@0: let iconStatus = this.getPluginUI(plugin, "icon"); michael@0: iconStatus.setAttribute("installable", "true"); michael@0: michael@0: let installLink = this.getPluginUI(plugin, "installPluginLink"); michael@0: this.addLinkClickCallback(installLink, "installSinglePlugin", plugin); michael@0: } michael@0: break; michael@0: michael@0: case "PluginBlocklisted": michael@0: case "PluginOutdated": michael@0: shouldShowNotification = true; michael@0: break; michael@0: michael@0: case "PluginVulnerableUpdatable": michael@0: let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); michael@0: this.addLinkClickCallback(updateLink, "openPluginUpdatePage"); michael@0: /* FALLTHRU */ michael@0: michael@0: case "PluginVulnerableNoUpdate": michael@0: case "PluginClickToPlay": michael@0: this._handleClickToPlayEvent(plugin); michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: let pluginName = this._getPluginInfo(plugin).pluginName; michael@0: let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]); michael@0: let overlayText = this.getPluginUI(plugin, "clickToPlay"); michael@0: overlayText.textContent = messageString; michael@0: if (eventType == "PluginVulnerableUpdatable" || michael@0: eventType == "PluginVulnerableNoUpdate") { michael@0: let vulnerabilityString = gNavigatorBundle.getString(eventType); michael@0: let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus"); michael@0: vulnerabilityText.textContent = vulnerabilityString; michael@0: } michael@0: shouldShowNotification = true; michael@0: break; michael@0: michael@0: case "PluginPlayPreview": michael@0: this._handlePlayPreviewEvent(plugin); michael@0: break; michael@0: michael@0: case "PluginDisabled": michael@0: // Screw the disabled message. It messes with HTML5 fallback on YouTube michael@0: let plugin_overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox"); michael@0: if (plugin_overlay != null) michael@0: plugin_overlay.style.visibility = "hidden"; michael@0: break; michael@0: michael@0: case "PluginInstantiated": michael@0: shouldShowNotification = true; michael@0: break; michael@0: } michael@0: michael@0: // Show the in-content UI if it's not too big. The crashed plugin handler already did this. michael@0: if (eventType != "PluginCrashed") { michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: if (overlay != null) { michael@0: this.setVisibility(plugin, overlay, michael@0: this.shouldShowOverlay(plugin, overlay)); michael@0: let resizeListener = (event) => { michael@0: this.setVisibility(plugin, overlay, michael@0: this.shouldShowOverlay(plugin, overlay)); michael@0: this._setPluginNotificationIcon(browser); michael@0: }; michael@0: plugin.addEventListener("overflow", resizeListener); michael@0: plugin.addEventListener("underflow", resizeListener); michael@0: } michael@0: } michael@0: michael@0: let closeIcon = this.getPluginUI(plugin, "closeIcon"); michael@0: if (closeIcon) { michael@0: closeIcon.addEventListener("click", function(aEvent) { michael@0: if (aEvent.button == 0 && aEvent.isTrusted) michael@0: gPluginHandler.hideClickToPlayOverlay(plugin); michael@0: }, true); michael@0: } michael@0: michael@0: if (shouldShowNotification) { michael@0: this._showClickToPlayNotification(browser, plugin, false); michael@0: } michael@0: }, michael@0: michael@0: isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) { michael@0: return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) == michael@0: Ci.nsIObjectLoadingContent.TYPE_PLUGIN); michael@0: }, michael@0: michael@0: canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) { michael@0: // if this isn't a known plugin, we can't activate it michael@0: // (this also guards pluginHost.getPermissionStringForType against michael@0: // unexpected input) michael@0: if (!gPluginHandler.isKnownPlugin(objLoadingContent)) michael@0: return false; michael@0: michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); michael@0: let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal; michael@0: let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); michael@0: michael@0: let isFallbackTypeValid = michael@0: objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && michael@0: objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; michael@0: michael@0: if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) { michael@0: // checking if play preview is subject to CTP rules michael@0: let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType); michael@0: isFallbackTypeValid = !playPreviewInfo.ignoreCTP; michael@0: } michael@0: michael@0: return !objLoadingContent.activated && michael@0: pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && michael@0: isFallbackTypeValid; michael@0: }, michael@0: michael@0: hideClickToPlayOverlay: function(aPlugin) { michael@0: let overlay = this.getPluginUI(aPlugin, "main"); michael@0: if (overlay) { michael@0: overlay.classList.remove("visible"); michael@0: } michael@0: }, michael@0: michael@0: stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) { michael@0: let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: if (objLoadingContent.activated) michael@0: return; michael@0: michael@0: if (aPlayPlugin) michael@0: objLoadingContent.playPlugin(); michael@0: else michael@0: objLoadingContent.cancelPlayPreview(); michael@0: }, michael@0: michael@0: newPluginInstalled : function(event) { michael@0: // browser elements are anonymous so we can't just use target. michael@0: var browser = event.originalTarget; michael@0: // clear the plugin list, now that at least one plugin has been installed michael@0: browser.missingPlugins = null; michael@0: michael@0: var notificationBox = gBrowser.getNotificationBox(browser); michael@0: var notification = notificationBox.getNotificationWithValue("missing-plugins"); michael@0: if (notification) michael@0: notificationBox.removeNotification(notification); michael@0: michael@0: // reload the browser to make the new plugin show. michael@0: browser.reload(); michael@0: }, michael@0: michael@0: // Callback for user clicking on a missing (unsupported) plugin. michael@0: installSinglePlugin: function (plugin) { michael@0: var missingPlugins = new Map(); michael@0: michael@0: var pluginInfo = this._getPluginInfo(plugin); michael@0: missingPlugins.set(pluginInfo.mimetype, pluginInfo); michael@0: michael@0: openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", michael@0: "PFSWindow", "chrome,centerscreen,resizable=yes", michael@0: {plugins: missingPlugins, browser: gBrowser.selectedBrowser}); michael@0: }, michael@0: michael@0: // Callback for user clicking on a disabled plugin michael@0: managePlugins: function (aEvent) { michael@0: BrowserOpenAddonsMgr("addons://list/plugin"); michael@0: }, michael@0: michael@0: // Callback for user clicking on the link in a click-to-play plugin michael@0: // (where the plugin has an update) michael@0: openPluginUpdatePage: function (aEvent) { michael@0: openUILinkIn(Services.urlFormatter.formatURLPref("plugins.update.url"), "tab"); michael@0: }, michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) { michael@0: let keyVals = {}; michael@0: if (plugin) { michael@0: let userComment = this.getPluginUI(plugin, "submitComment").value.trim(); michael@0: if (userComment) michael@0: keyVals.PluginUserComment = userComment; michael@0: if (this.getPluginUI(plugin, "submitURLOptIn").checked) michael@0: keyVals.PluginContentURL = plugin.ownerDocument.URL; michael@0: } michael@0: this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals }); michael@0: if (browserDumpID) michael@0: this.CrashSubmit.submit(browserDumpID); michael@0: }, michael@0: #endif michael@0: michael@0: // Callback for user clicking a "reload page" link michael@0: reloadPage: function (browser) { michael@0: browser.reload(); michael@0: }, michael@0: michael@0: // Callback for user clicking the help icon michael@0: openHelpPage: function () { michael@0: openHelpLink("plugin-crashed", false); michael@0: }, michael@0: michael@0: showInstallNotification: function (aPlugin) { michael@0: let hideMissingPluginsNotification = michael@0: Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION); michael@0: if (hideMissingPluginsNotification) { michael@0: return false; michael@0: } michael@0: michael@0: let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument michael@0: .defaultView.top.document); michael@0: if (!browser.missingPlugins) michael@0: browser.missingPlugins = new Map(); michael@0: michael@0: let pluginInfo = this._getPluginInfo(aPlugin); michael@0: browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo); michael@0: michael@0: // only show notification for small subset of plugins michael@0: let mimetype = pluginInfo.mimetype.split(";")[0]; michael@0: if (!this.canInstallThisMimeType(mimetype)) michael@0: return false; michael@0: michael@0: let pluginIdentifier = this.nameForSupportedPlugin(mimetype); michael@0: if (!pluginIdentifier) michael@0: return false; michael@0: michael@0: let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName; michael@0: michael@0: // don't show several notifications michael@0: let notification = PopupNotifications.getNotification("plugins-not-found", browser); michael@0: if (notification) michael@0: return true; michael@0: michael@0: let messageString = gNavigatorBundle.getString("installPlugin.message"); michael@0: let mainAction = { michael@0: label: gNavigatorBundle.getFormattedString("installPlugin.button.label", michael@0: [displayName]), michael@0: accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"), michael@0: callback: function () { michael@0: openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul", michael@0: "PFSWindow", "chrome,centerscreen,resizable=yes", michael@0: {plugins: browser.missingPlugins, browser: browser}); michael@0: } michael@0: }; michael@0: let secondaryActions = null; michael@0: let options = { dismissed: true }; michael@0: michael@0: let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH); michael@0: if (pluginIdentifier == "flash" && showForFlash) { michael@0: let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH; michael@0: secondaryActions = [{ michael@0: label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"), michael@0: accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"), michael@0: callback: function () { michael@0: Services.prefs.setBoolPref(prefNotifyMissingFlash, false); michael@0: } michael@0: }]; michael@0: options.dismissed = false; michael@0: } michael@0: PopupNotifications.show(browser, "plugins-not-found", michael@0: messageString, "plugin-install-notification-icon", michael@0: mainAction, secondaryActions, options); michael@0: return true; michael@0: }, michael@0: // Event listener for click-to-play plugins. michael@0: _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) { michael@0: let doc = aPlugin.ownerDocument; michael@0: let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: // guard against giving pluginHost.getPermissionStringForType a type michael@0: // not associated with any known plugin michael@0: if (!gPluginHandler.isKnownPlugin(objLoadingContent)) michael@0: return; michael@0: let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); michael@0: let principal = doc.defaultView.top.document.nodePrincipal; michael@0: let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); michael@0: michael@0: let overlay = this.getPluginUI(aPlugin, "main"); michael@0: michael@0: if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { michael@0: if (overlay) { michael@0: overlay.classList.remove("visible"); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (overlay) { michael@0: overlay.addEventListener("click", gPluginHandler._overlayClickListener, true); michael@0: } michael@0: }, michael@0: michael@0: _overlayClickListener: { michael@0: handleEvent: function PH_handleOverlayClick(aEvent) { michael@0: let plugin = document.getBindingParent(aEvent.target); michael@0: let contentWindow = plugin.ownerDocument.defaultView.top; michael@0: // gBrowser.getBrowserForDocument does not exist in the case where we michael@0: // drag-and-dropped a tab from a window containing only that tab. In michael@0: // that case, the window gets destroyed. michael@0: let browser = gBrowser.getBrowserForDocument ? michael@0: gBrowser.getBrowserForDocument(contentWindow.document) : michael@0: null; michael@0: // If browser is null here, we've been drag-and-dropped from another michael@0: // window, and this is the wrong click handler. michael@0: if (!browser) { michael@0: aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true); michael@0: return; michael@0: } michael@0: let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: // Have to check that the target is not the link to update the plugin michael@0: if (!(aEvent.originalTarget instanceof HTMLAnchorElement) && michael@0: (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') && michael@0: aEvent.button == 0 && aEvent.isTrusted) { michael@0: gPluginHandler._showClickToPlayNotification(browser, plugin, true); michael@0: aEvent.stopPropagation(); michael@0: aEvent.preventDefault(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) { michael@0: let doc = aPlugin.ownerDocument; michael@0: let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: let pluginInfo = this._getPluginInfo(aPlugin); michael@0: let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype); michael@0: michael@0: let previewContent = this.getPluginUI(aPlugin, "previewPluginContent"); michael@0: let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; michael@0: if (!iframe) { michael@0: // lazy initialization of the iframe michael@0: iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); michael@0: iframe.className = "previewPluginContentFrame"; michael@0: previewContent.appendChild(iframe); michael@0: michael@0: // Force a style flush, so that we ensure our binding is attached. michael@0: aPlugin.clientTop; michael@0: } michael@0: iframe.src = playPreviewInfo.redirectURL; michael@0: michael@0: // MozPlayPlugin event can be dispatched from the extension chrome michael@0: // code to replace the preview content with the native plugin michael@0: previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) { michael@0: if (!aEvent.isTrusted) michael@0: return; michael@0: michael@0: previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true); michael@0: michael@0: let playPlugin = !aEvent.detail; michael@0: gPluginHandler.stopPlayPreview(aPlugin, playPlugin); michael@0: michael@0: // cleaning up: removes overlay iframe from the DOM michael@0: let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0]; michael@0: if (iframe) michael@0: previewContent.removeChild(iframe); michael@0: }, true); michael@0: michael@0: if (!playPreviewInfo.ignoreCTP) { michael@0: gPluginHandler._showClickToPlayNotification(browser, aPlugin, false); michael@0: } michael@0: }, michael@0: michael@0: reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() { michael@0: let browser = gBrowser.selectedBrowser; michael@0: let contentWindow = browser.contentWindow; michael@0: let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: let plugins = cwu.plugins; michael@0: for (let plugin of plugins) { michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: if (overlay) michael@0: overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); michael@0: let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: if (gPluginHandler.canActivatePlugin(objLoadingContent)) michael@0: gPluginHandler._handleClickToPlayEvent(plugin); michael@0: } michael@0: gPluginHandler._showClickToPlayNotification(browser, null, false); michael@0: }, michael@0: michael@0: _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) { michael@0: if (event == "showing") { michael@0: Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN") michael@0: .add(!this.options.primaryPlugin); michael@0: // Histograms always start at 0, even though our data starts at 1 michael@0: let histogramCount = this.options.pluginData.size - 1; michael@0: if (histogramCount > 4) { michael@0: histogramCount = 4; michael@0: } michael@0: Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_PLUGIN_COUNT") michael@0: .add(histogramCount); michael@0: } michael@0: else if (event == "dismissed") { michael@0: // Once the popup is dismissed, clicking the icon should show the full michael@0: // list again michael@0: this.options.primaryPlugin = null; michael@0: } michael@0: }, michael@0: michael@0: // Match the behaviour of nsPermissionManager michael@0: _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) { michael@0: if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) { michael@0: return "(null)"; michael@0: } michael@0: michael@0: try { michael@0: if (principal.URI.host) michael@0: return principal.URI.host; michael@0: } catch (e) {} michael@0: michael@0: return principal.origin; michael@0: }, michael@0: michael@0: /** michael@0: * Called from the plugin doorhanger to set the new permissions for a plugin michael@0: * and activate plugins if necessary. michael@0: * aNewState should be either "allownow" "allowalways" or "block" michael@0: */ michael@0: _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) { michael@0: let permission; michael@0: let expireType; michael@0: let expireTime; michael@0: let histogram = michael@0: Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_USER_ACTION"); michael@0: michael@0: // Update the permission manager. michael@0: // Also update the current state of pluginInfo.fallbackType so that michael@0: // subsequent opening of the notification shows the current state. michael@0: switch (aNewState) { michael@0: case "allownow": michael@0: permission = Ci.nsIPermissionManager.ALLOW_ACTION; michael@0: expireType = Ci.nsIPermissionManager.EXPIRE_SESSION; michael@0: expireTime = Date.now() + Services.prefs.getIntPref(this.PREF_SESSION_PERSIST_MINUTES) * 60 * 1000; michael@0: histogram.add(0); michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; michael@0: break; michael@0: michael@0: case "allowalways": michael@0: permission = Ci.nsIPermissionManager.ALLOW_ACTION; michael@0: expireType = Ci.nsIPermissionManager.EXPIRE_TIME; michael@0: expireTime = Date.now() + michael@0: Services.prefs.getIntPref(this.PREF_PERSISTENT_DAYS) * 24 * 60 * 60 * 1000; michael@0: histogram.add(1); michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; michael@0: break; michael@0: michael@0: case "block": michael@0: permission = Ci.nsIPermissionManager.PROMPT_ACTION; michael@0: expireType = Ci.nsIPermissionManager.EXPIRE_NEVER; michael@0: expireTime = 0; michael@0: histogram.add(2); michael@0: switch (aPluginInfo.blocklistState) { michael@0: case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE: michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE; michael@0: break; michael@0: case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE: michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE; michael@0: break; michael@0: default: michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY; michael@0: } michael@0: break; michael@0: michael@0: // In case a plugin has already been allowed in another tab, the "continue allowing" button michael@0: // shouldn't change any permissions but should run the plugin-enablement code below. michael@0: case "continue": michael@0: aPluginInfo.fallbackType = Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE; michael@0: break; michael@0: default: michael@0: Cu.reportError(Error("Unexpected plugin state: " + aNewState)); michael@0: return; michael@0: } michael@0: michael@0: let browser = aNotification.browser; michael@0: let contentWindow = browser.contentWindow; michael@0: if (aNewState != "continue") { michael@0: let principal = contentWindow.document.nodePrincipal; michael@0: Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString, michael@0: permission, expireType, expireTime); michael@0: aPluginInfo.pluginPermissionType = expireType; michael@0: } michael@0: michael@0: // Manually activate the plugins that would have been automatically michael@0: // activated. michael@0: let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: let plugins = cwu.plugins; michael@0: let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); michael@0: michael@0: let pluginFound = false; michael@0: for (let plugin of plugins) { michael@0: plugin.QueryInterface(Ci.nsIObjectLoadingContent); michael@0: if (!gPluginHandler.isKnownPlugin(plugin)) { michael@0: continue; michael@0: } michael@0: if (aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { michael@0: pluginFound = true; michael@0: if (aNewState == "block") { michael@0: plugin.reload(true); michael@0: } else { michael@0: if (gPluginHandler.canActivatePlugin(plugin)) { michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: if (overlay) { michael@0: overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true); michael@0: } michael@0: plugin.playPlugin(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If there are no instances of the plugin on the page any more, what the michael@0: // user probably needs is for us to allow and then refresh. michael@0: if (aNewState != "block" && !pluginFound) { michael@0: browser.reload(); michael@0: } michael@0: michael@0: this._setPluginNotificationIcon(browser); michael@0: }, michael@0: michael@0: _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPlugin, aShowNow) { michael@0: let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); michael@0: let plugins = []; michael@0: michael@0: // if aPlugin is null, that means the user has navigated back to a page with plugins, and we need michael@0: // to collect all the plugins michael@0: if (aPlugin === null) { michael@0: let contentWindow = aBrowser.contentWindow; michael@0: let contentDoc = aBrowser.contentDocument; michael@0: let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: // cwu.plugins may contain non-plugin s, filter them out michael@0: plugins = cwu.plugins.filter((plugin) => michael@0: plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); michael@0: michael@0: if (plugins.length == 0) { michael@0: if (notification) { michael@0: PopupNotifications.remove(notification); michael@0: } michael@0: return; michael@0: } michael@0: } else { michael@0: plugins = [aPlugin]; michael@0: } michael@0: michael@0: // If this is a new notification, create a pluginData map, otherwise append michael@0: let pluginData; michael@0: if (notification) { michael@0: pluginData = notification.options.pluginData; michael@0: } else { michael@0: pluginData = new Map(); michael@0: } michael@0: michael@0: let principal = aBrowser.contentDocument.nodePrincipal; michael@0: let principalHost = this._getHostFromPrincipal(principal); michael@0: michael@0: for (var plugin of plugins) { michael@0: let pluginInfo = this._getPluginInfo(plugin); michael@0: if (pluginInfo.permissionString === null) { michael@0: Cu.reportError("No permission string for active plugin."); michael@0: continue; michael@0: } michael@0: if (pluginData.has(pluginInfo.permissionString)) { michael@0: continue; michael@0: } michael@0: michael@0: let permissionObj = Services.perms. michael@0: getPermissionObject(principal, pluginInfo.permissionString, false); michael@0: if (permissionObj) { michael@0: pluginInfo.pluginPermissionHost = permissionObj.host; michael@0: pluginInfo.pluginPermissionType = permissionObj.expireType; michael@0: } michael@0: else { michael@0: pluginInfo.pluginPermissionHost = principalHost; michael@0: pluginInfo.pluginPermissionType = undefined; michael@0: } michael@0: michael@0: let url; michael@0: // TODO: allow the blocklist to specify a better link, bug 873093 michael@0: if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { michael@0: url = Services.urlFormatter.formatURLPref("plugins.update.url"); michael@0: } michael@0: else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { michael@0: url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag); michael@0: } michael@0: else { michael@0: url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay"; michael@0: } michael@0: pluginInfo.detailsLink = url; michael@0: michael@0: pluginData.set(pluginInfo.permissionString, pluginInfo); michael@0: } michael@0: michael@0: let primaryPluginPermission = null; michael@0: if (aShowNow) { michael@0: primaryPluginPermission = this._getPluginInfo(aPlugin).permissionString; michael@0: } michael@0: michael@0: if (notification) { michael@0: // Don't modify the notification UI while it's on the screen, that would be michael@0: // jumpy and might allow clickjacking. michael@0: if (aShowNow) { michael@0: notification.options.primaryPlugin = primaryPluginPermission; michael@0: notification.reshow(); michael@0: setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let options = { michael@0: dismissed: !aShowNow, michael@0: eventCallback: this._clickToPlayNotificationEventCallback, michael@0: primaryPlugin: primaryPluginPermission, michael@0: pluginData: pluginData michael@0: }; michael@0: PopupNotifications.show(aBrowser, "click-to-play-plugins", michael@0: "", "plugins-notification-icon", michael@0: null, null, options); michael@0: setTimeout(() => { this._setPluginNotificationIcon(aBrowser); }, 0); michael@0: }, michael@0: michael@0: _setPluginNotificationIcon : function PH_setPluginNotificationIcon(aBrowser) { michael@0: // Because this is called on a timeout, sanity-check before continuing michael@0: if (!aBrowser.docShell || !aBrowser.contentWindow) { michael@0: return; michael@0: } michael@0: michael@0: let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser); michael@0: if (!notification) michael@0: return; michael@0: michael@0: // Make a copy of the actions, removing active plugins and checking for michael@0: // outdated plugins. michael@0: let haveInsecure = false; michael@0: let actions = new Map(); michael@0: for (let action of notification.options.pluginData.values()) { michael@0: switch (action.fallbackType) { michael@0: // haveInsecure will trigger the red flashing icon and the infobar michael@0: // styling below michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: michael@0: haveInsecure = true; michael@0: // fall through michael@0: michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: michael@0: actions.set(action.permissionString, action); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // check for hidden plugins michael@0: let contentWindow = aBrowser.contentWindow; michael@0: let contentDoc = aBrowser.contentDocument; michael@0: let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils); michael@0: for (let plugin of cwu.plugins) { michael@0: let info = this._getPluginInfo(plugin); michael@0: if (!actions.has(info.permissionString)) { michael@0: continue; michael@0: } michael@0: let fallbackType = info.fallbackType; michael@0: if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { michael@0: actions.delete(info.permissionString); michael@0: if (actions.size == 0) { michael@0: break; michael@0: } michael@0: continue; michael@0: } michael@0: if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && michael@0: fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && michael@0: fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { michael@0: continue; michael@0: } michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: if (!overlay) { michael@0: continue; michael@0: } michael@0: let shouldShow = this.shouldShowOverlay(plugin, overlay); michael@0: this.setVisibility(plugin, overlay, shouldShow); michael@0: if (shouldShow) { michael@0: actions.delete(info.permissionString); michael@0: if (actions.size == 0) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Set up the icon michael@0: document.getElementById("plugins-notification-icon").classList. michael@0: toggle("plugin-blocked", haveInsecure); michael@0: michael@0: // Now configure the notification bar michael@0: michael@0: let notificationBox = gBrowser.getNotificationBox(aBrowser); michael@0: michael@0: function hideNotification() { michael@0: let n = notificationBox.getNotificationWithValue("plugin-hidden"); michael@0: if (n) { michael@0: notificationBox.removeNotification(n, true); michael@0: } michael@0: } michael@0: michael@0: // There are three different cases when showing an infobar: michael@0: // 1. A single type of plugin is hidden on the page. Show the UI for that michael@0: // plugin. michael@0: // 2a. Multiple types of plugins are hidden on the page. Show the multi-UI michael@0: // with the vulnerable styling. michael@0: // 2b. Multiple types of plugins are hidden on the page, but none are michael@0: // vulnerable. Show the nonvulnerable multi-UI. michael@0: function showNotification() { michael@0: let n = notificationBox.getNotificationWithValue("plugin-hidden"); michael@0: if (n) { michael@0: // If something is already shown, just keep it michael@0: return; michael@0: } michael@0: michael@0: Services.telemetry.getHistogramById("PLUGINS_INFOBAR_SHOWN"). michael@0: add(true); michael@0: michael@0: let message; michael@0: // Icons set directly cannot be manipulated using moz-image-region, so michael@0: // we use CSS classes instead. michael@0: let host = gPluginHandler._getHostFromPrincipal(aBrowser.contentDocument.nodePrincipal); michael@0: let brand = document.getElementById("bundle_brand").getString("brandShortName"); michael@0: michael@0: if (actions.size == 1) { michael@0: let pluginInfo = [...actions.values()][0]; michael@0: let pluginName = pluginInfo.pluginName; michael@0: michael@0: switch (pluginInfo.fallbackType) { michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: michael@0: message = gNavigatorBundle.getFormattedString( michael@0: "pluginActivateNew.message", michael@0: [pluginName, host]); michael@0: break; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: michael@0: message = gNavigatorBundle.getFormattedString( michael@0: "pluginActivateOutdated.message", michael@0: [pluginName, host, brand]); michael@0: break; michael@0: case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: michael@0: message = gNavigatorBundle.getFormattedString( michael@0: "pluginActivateVulnerable.message", michael@0: [pluginName, host, brand]); michael@0: } michael@0: } else { michael@0: // Multi-plugin michael@0: message = gNavigatorBundle.getFormattedString( michael@0: "pluginActivateMultiple.message", [host]); michael@0: michael@0: for (let action of actions.values()) { michael@0: if (action.fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: let buttons = [ michael@0: { michael@0: label: gNavigatorBundle.getString("pluginContinueBlocking.label"), michael@0: accessKey: gNavigatorBundle.getString("pluginContinueBlocking.accesskey"), michael@0: callback: function() { michael@0: Services.telemetry.getHistogramById("PLUGINS_INFOBAR_BLOCK"). michael@0: add(true); michael@0: michael@0: Services.perms.addFromPrincipal(aBrowser.contentDocument.nodePrincipal, michael@0: "plugin-hidden-notification", michael@0: Services.perms.DENY_ACTION); michael@0: } michael@0: }, michael@0: { michael@0: label: gNavigatorBundle.getString("pluginActivateTrigger.label"), michael@0: accessKey: gNavigatorBundle.getString("pluginActivateTrigger.accesskey"), michael@0: callback: function() { michael@0: Services.telemetry.getHistogramById("PLUGINS_INFOBAR_ALLOW"). michael@0: add(true); michael@0: michael@0: let curNotification = michael@0: PopupNotifications.getNotification("click-to-play-plugins", michael@0: aBrowser); michael@0: if (curNotification) { michael@0: curNotification.reshow(); michael@0: } michael@0: } michael@0: } michael@0: ]; michael@0: n = notificationBox. michael@0: appendNotification(message, "plugin-hidden", null, michael@0: notificationBox.PRIORITY_INFO_HIGH, buttons); michael@0: if (haveInsecure) { michael@0: n.classList.add('pluginVulnerable'); michael@0: } michael@0: } michael@0: michael@0: if (actions.size == 0) { michael@0: hideNotification(); michael@0: } else { michael@0: let notificationPermission = Services.perms.testPermissionFromPrincipal( michael@0: aBrowser.contentDocument.nodePrincipal, "plugin-hidden-notification"); michael@0: if (notificationPermission == Ci.nsIPermissionManager.DENY_ACTION) { michael@0: hideNotification(); michael@0: } else { michael@0: showNotification(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: // Crashed-plugin observer. Notified once per plugin crash, before events michael@0: // are dispatched to individual plugin instances. michael@0: pluginCrashed : function(subject, topic, data) { michael@0: let propertyBag = subject; michael@0: if (!(propertyBag instanceof Ci.nsIPropertyBag2) || michael@0: !(propertyBag instanceof Ci.nsIWritablePropertyBag2)) michael@0: return; michael@0: michael@0: #ifdef MOZ_CRASHREPORTER michael@0: let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID"); michael@0: let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID"); michael@0: let shouldSubmit = gCrashReporter.submitReports; michael@0: let doPrompt = true; // XXX followup to get via gCrashReporter michael@0: michael@0: // Submit automatically when appropriate. michael@0: if (pluginDumpID && shouldSubmit && !doPrompt) { michael@0: this.submitReport(pluginDumpID, browserDumpID); michael@0: // Submission is async, so we can't easily show failure UI. michael@0: propertyBag.setPropertyAsBool("submittedCrashReport", true); michael@0: } michael@0: #endif michael@0: }, michael@0: michael@0: // Crashed-plugin event listener. Called for every instance of a michael@0: // plugin in content. michael@0: pluginInstanceCrashed: function (plugin, aEvent) { michael@0: // Ensure the plugin and event are of the right type. michael@0: if (!(aEvent instanceof Ci.nsIDOMCustomEvent)) michael@0: return; michael@0: michael@0: let propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); michael@0: let submittedReport = propBag.getPropertyAsBool("submittedCrashReport"); michael@0: let doPrompt = true; // XXX followup for .getPropertyAsBool("doPrompt"); michael@0: let submitReports = true; // XXX followup for .getPropertyAsBool("submitReports"); michael@0: let pluginName = propBag.getPropertyAsAString("pluginName"); michael@0: let pluginDumpID = propBag.getPropertyAsAString("pluginDumpID"); michael@0: let browserDumpID = propBag.getPropertyAsAString("browserDumpID"); michael@0: michael@0: // Remap the plugin name to a more user-presentable form. michael@0: pluginName = this.makeNicePluginName(pluginName); michael@0: michael@0: let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]); michael@0: michael@0: // michael@0: // Configure the crashed-plugin placeholder. michael@0: // michael@0: michael@0: // Force a layout flush so the binding is attached. michael@0: plugin.clientTop; michael@0: let overlay = this.getPluginUI(plugin, "main"); michael@0: let statusDiv = this.getPluginUI(plugin, "submitStatus"); michael@0: let doc = plugin.ownerDocument; michael@0: #ifdef MOZ_CRASHREPORTER michael@0: let status; michael@0: michael@0: // Determine which message to show regarding crash reports. michael@0: if (submittedReport) { // submitReports && !doPrompt, handled in observer michael@0: status = "submitted"; michael@0: } michael@0: else if (!submitReports && !doPrompt) { michael@0: status = "noSubmit"; michael@0: } michael@0: else { // doPrompt michael@0: status = "please"; michael@0: this.getPluginUI(plugin, "submitButton").addEventListener("click", michael@0: function (event) { michael@0: if (event.button != 0 || !event.isTrusted) michael@0: return; michael@0: this.submitReport(pluginDumpID, browserDumpID, plugin); michael@0: pref.setBoolPref("", optInCB.checked); michael@0: }.bind(this)); michael@0: let optInCB = this.getPluginUI(plugin, "submitURLOptIn"); michael@0: let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL"); michael@0: optInCB.checked = pref.getBoolPref(""); michael@0: } michael@0: michael@0: // If we don't have a minidumpID, we can't (or didn't) submit anything. michael@0: // This can happen if the plugin is killed from the task manager. michael@0: if (!pluginDumpID) { michael@0: status = "noReport"; michael@0: } michael@0: michael@0: statusDiv.setAttribute("status", status); michael@0: michael@0: let helpIcon = this.getPluginUI(plugin, "helpIcon"); michael@0: this.addLinkClickCallback(helpIcon, "openHelpPage"); michael@0: michael@0: // If we're showing the link to manually trigger report submission, we'll michael@0: // want to be able to update all the instances of the UI for this crash to michael@0: // show an updated message when a report is submitted. michael@0: if (doPrompt) { michael@0: let observer = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]), michael@0: observe : function(subject, topic, data) { michael@0: let propertyBag = subject; michael@0: if (!(propertyBag instanceof Ci.nsIPropertyBag2)) michael@0: return; michael@0: // Ignore notifications for other crashes. michael@0: if (propertyBag.get("minidumpID") != pluginDumpID) michael@0: return; michael@0: statusDiv.setAttribute("status", data); michael@0: }, michael@0: michael@0: handleEvent : function(event) { michael@0: // Not expected to be called, just here for the closure. michael@0: } michael@0: } michael@0: michael@0: // Use a weak reference, so we don't have to remove it... michael@0: Services.obs.addObserver(observer, "crash-report-status", true); michael@0: // ...alas, now we need something to hold a strong reference to prevent michael@0: // it from being GC. But I don't want to manually manage the reference's michael@0: // lifetime (which should be no greater than the page). michael@0: // Clever solution? Use a closue with an event listener on the document. michael@0: // When the doc goes away, so do the listener references and the closure. michael@0: doc.addEventListener("mozCleverClosureHack", observer, false); michael@0: } michael@0: #endif michael@0: michael@0: let crashText = this.getPluginUI(plugin, "crashedText"); michael@0: crashText.textContent = messageString; michael@0: michael@0: let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document); michael@0: michael@0: let link = this.getPluginUI(plugin, "reloadLink"); michael@0: this.addLinkClickCallback(link, "reloadPage", browser); michael@0: michael@0: let notificationBox = gBrowser.getNotificationBox(browser); michael@0: michael@0: let isShowing = this.shouldShowOverlay(plugin, overlay); michael@0: michael@0: // Is the 's size too small to hold what we want to show? michael@0: if (!isShowing) { michael@0: // First try hiding the crash report submission UI. michael@0: statusDiv.removeAttribute("status"); michael@0: michael@0: isShowing = this.shouldShowOverlay(plugin, overlay); michael@0: } michael@0: this.setVisibility(plugin, overlay, isShowing); michael@0: michael@0: if (isShowing) { michael@0: // If a previous plugin on the page was too small and resulted in adding a michael@0: // notification bar, then remove it because this plugin instance it big michael@0: // enough to serve as in-content notification. michael@0: hideNotificationBar(); michael@0: doc.mozNoPluginCrashedNotification = true; michael@0: } else { michael@0: // If another plugin on the page was large enough to show our UI, we don't michael@0: // want to show a notification bar. michael@0: if (!doc.mozNoPluginCrashedNotification) michael@0: showNotificationBar(pluginDumpID, browserDumpID); michael@0: } michael@0: michael@0: function hideNotificationBar() { michael@0: let notification = notificationBox.getNotificationWithValue("plugin-crashed"); michael@0: if (notification) michael@0: notificationBox.removeNotification(notification, true); michael@0: } michael@0: michael@0: function showNotificationBar(pluginDumpID, browserDumpID) { michael@0: // If there's already an existing notification bar, don't do anything. michael@0: let notification = notificationBox.getNotificationWithValue("plugin-crashed"); michael@0: if (notification) michael@0: return; michael@0: michael@0: // Configure the notification bar michael@0: let priority = notificationBox.PRIORITY_WARNING_MEDIUM; michael@0: let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png"; michael@0: let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label"); michael@0: let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey"); michael@0: let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label"); michael@0: let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey"); michael@0: michael@0: let buttons = [{ michael@0: label: reloadLabel, michael@0: accessKey: reloadKey, michael@0: popup: null, michael@0: callback: function() { browser.reload(); }, michael@0: }]; michael@0: #ifdef MOZ_CRASHREPORTER michael@0: let submitButton = { michael@0: label: submitLabel, michael@0: accessKey: submitKey, michael@0: popup: null, michael@0: callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); }, michael@0: }; michael@0: if (pluginDumpID) michael@0: buttons.push(submitButton); michael@0: #endif michael@0: michael@0: let notification = notificationBox.appendNotification(messageString, "plugin-crashed", michael@0: iconURL, priority, buttons); michael@0: michael@0: // Add the "learn more" link. michael@0: let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: let link = notification.ownerDocument.createElementNS(XULNS, "label"); michael@0: link.className = "text-link"; michael@0: link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore")); michael@0: let crashurl = formatURL("app.support.baseURL", true); michael@0: crashurl += "plugin-crashed-notificationbar"; michael@0: link.href = crashurl; michael@0: michael@0: let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText"); michael@0: description.appendChild(link); michael@0: michael@0: // Remove the notfication when the page is reloaded. michael@0: doc.defaultView.top.addEventListener("unload", function() { michael@0: notificationBox.removeNotification(notification); michael@0: }, false); michael@0: } michael@0: michael@0: } michael@0: };