diff options
Diffstat (limited to 'browser/modules/PluginContent.jsm')
-rw-r--r-- | browser/modules/PluginContent.jsm | 1132 |
1 files changed, 0 insertions, 1132 deletions
diff --git a/browser/modules/PluginContent.jsm b/browser/modules/PluginContent.jsm deleted file mode 100644 index 622d608bc..000000000 --- a/browser/modules/PluginContent.jsm +++ /dev/null @@ -1,1132 +0,0 @@ -/* 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/. */ - -"use strict"; - -var Cc = Components.classes; -var Ci = Components.interfaces; -var Cu = Components.utils; - -this.EXPORTED_SYMBOLS = [ "PluginContent" ]; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Timer.jsm"); -Cu.import("resource://gre/modules/BrowserUtils.jsm"); - -XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() { - const url = "chrome://browser/locale/browser.properties"; - return Services.strings.createBundle(url); -}); - -XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", - "resource://gre/modules/AppConstants.jsm"); - -this.PluginContent = function (global) { - this.init(global); -} - -const FLASH_MIME_TYPE = "application/x-shockwave-flash"; -const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css", null, null); - -PluginContent.prototype = { - init: function (global) { - this.global = global; - // Need to hold onto the content window or else it'll get destroyed - this.content = this.global.content; - // Cache of plugin actions for the current page. - this.pluginData = new Map(); - // Cache of plugin crash information sent from the parent - this.pluginCrashData = new Map(); - - // Note that the XBL binding is untrusted - global.addEventListener("PluginBindingAttached", this, true, true); - global.addEventListener("PluginPlaceholderReplaced", this, true, true); - global.addEventListener("PluginCrashed", this, true); - global.addEventListener("PluginOutdated", this, true); - global.addEventListener("PluginInstantiated", this, true); - global.addEventListener("PluginRemoved", this, true); - global.addEventListener("pagehide", this, true); - global.addEventListener("pageshow", this, true); - global.addEventListener("unload", this); - global.addEventListener("HiddenPlugin", this, true); - - global.addMessageListener("BrowserPlugins:ActivatePlugins", this); - global.addMessageListener("BrowserPlugins:NotificationShown", this); - global.addMessageListener("BrowserPlugins:ContextMenuCommand", this); - global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this); - global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this); - global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this); - - Services.obs.addObserver(this, "decoder-doctor-notification", false); - }, - - uninit: function() { - let global = this.global; - - global.removeEventListener("PluginBindingAttached", this, true); - global.removeEventListener("PluginPlaceholderReplaced", this, true, true); - global.removeEventListener("PluginCrashed", this, true); - global.removeEventListener("PluginOutdated", this, true); - global.removeEventListener("PluginInstantiated", this, true); - global.removeEventListener("PluginRemoved", this, true); - global.removeEventListener("pagehide", this, true); - global.removeEventListener("pageshow", this, true); - global.removeEventListener("unload", this); - global.removeEventListener("HiddenPlugin", this, true); - - global.removeMessageListener("BrowserPlugins:ActivatePlugins", this); - global.removeMessageListener("BrowserPlugins:NotificationShown", this); - global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this); - global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this); - global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this); - global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this); - - Services.obs.removeObserver(this, "decoder-doctor-notification"); - - delete this.global; - delete this.content; - }, - - receiveMessage: function (msg) { - switch (msg.name) { - case "BrowserPlugins:ActivatePlugins": - this.activatePlugins(msg.data.pluginInfo, msg.data.newState); - break; - case "BrowserPlugins:NotificationShown": - setTimeout(() => this.updateNotificationUI(), 0); - break; - case "BrowserPlugins:ContextMenuCommand": - switch (msg.data.command) { - case "play": - this._showClickToPlayNotification(msg.objects.plugin, true); - break; - case "hide": - this.hideClickToPlayOverlay(msg.objects.plugin); - break; - } - break; - case "BrowserPlugins:NPAPIPluginProcessCrashed": - this.NPAPIPluginProcessCrashed({ - pluginName: msg.data.pluginName, - runID: msg.data.runID, - state: msg.data.state, - }); - break; - case "BrowserPlugins:CrashReportSubmitted": - this.NPAPIPluginCrashReportSubmitted({ - runID: msg.data.runID, - state: msg.data.state, - }) - break; - case "BrowserPlugins:Test:ClearCrashData": - // This message should ONLY ever be sent by automated tests. - if (Services.prefs.getBoolPref("plugins.testmode")) { - this.pluginCrashData.clear(); - } - } - }, - - observe: function observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "decoder-doctor-notification": - let data = JSON.parse(aData); - if (this.haveShownNotification && - aSubject.top.document == this.content.document && - data.formats.toLowerCase().includes("application/x-mpegurl", 0)) { - let principal = this.content.document.nodePrincipal; - let location = this.content.document.location.href; - this.global.content.pluginRequiresReload = true; - this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", - { plugins: [... this.pluginData.values()], - showNow: true, - location: location, - }, null, principal); - } - } - }, - - onPageShow: function (event) { - // Ignore events that aren't from the main document. - if (!this.content || event.target != this.content.document) { - return; - } - - // The PluginClickToPlay events are not fired when navigating using the - // BF cache. |persisted| is true when the page is loaded from the - // BF cache, so this code reshows the notification if necessary. - if (event.persisted) { - this.reshowClickToPlayNotification(); - } - }, - - onPageHide: function (event) { - // Ignore events that aren't from the main document. - if (!this.content || event.target != this.content.document) { - return; - } - - this._finishRecordingFlashPluginTelemetry(); - this.clearPluginCaches(); - this.haveShownNotification = false; - }, - - getPluginUI: function (plugin, anonid) { - return plugin.ownerDocument. - getAnonymousElementByAttribute(plugin, "anonid", anonid); - }, - - _getPluginInfo: function (pluginElement) { - if (pluginElement instanceof Ci.nsIDOMHTMLAnchorElement) { - // Anchor elements are our place holders, and we only have them for Flash - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - return { - pluginName: "Shockwave Flash", - mimetype: FLASH_MIME_TYPE, - permissionString: pluginHost.getPermissionStringForType(FLASH_MIME_TYPE) - }; - } - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - pluginElement.QueryInterface(Ci.nsIObjectLoadingContent); - - let tagMimetype; - let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin"); - let pluginTag = null; - let permissionString = null; - let fallbackType = null; - let blocklistState = null; - - tagMimetype = pluginElement.actualType; - if (tagMimetype == "") { - tagMimetype = pluginElement.type; - } - - if (this.isKnownPlugin(pluginElement)) { - pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType); - pluginName = BrowserUtils.makeNicePluginName(pluginTag.name); - - // Convert this from nsIPluginTag so it can be serialized. - let properties = ["name", "description", "filename", "version", "enabledState", "niceName"]; - let pluginTagCopy = {}; - for (let prop of properties) { - pluginTagCopy[prop] = pluginTag[prop]; - } - pluginTag = pluginTagCopy; - - 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, - }; - }, - - _getPluginInfoForTag: function (pluginTag, tagMimetype) { - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - - let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin"); - let permissionString = null; - let blocklistState = null; - - if (pluginTag) { - pluginName = BrowserUtils.makeNicePluginName(pluginTag.name); - - permissionString = pluginHost.getPermissionStringForTag(pluginTag); - blocklistState = pluginTag.blocklistState; - - // Convert this from nsIPluginTag so it can be serialized. - let properties = ["name", "description", "filename", "version", "enabledState", "niceName"]; - let pluginTagCopy = {}; - for (let prop of properties) { - pluginTagCopy[prop] = pluginTag[prop]; - } - pluginTag = pluginTagCopy; - - // 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: null, - blocklistState: blocklistState, - }; - }, - - /** - * Update the visibility of the plugin overlay. - */ - setVisibility : function (plugin, overlay, shouldShow) { - overlay.classList.toggle("visible", shouldShow); - if (shouldShow) { - overlay.removeAttribute("dismissed"); - } - }, - - /** - * Check whether the plugin should be visible on the page. A plugin should - * not be visible if the overlay is too big, or if any other page content - * overlays it. - * - * This function will handle showing or hiding the overlay. - * @returns true if the plugin is invisible. - */ - shouldShowOverlay : function (plugin, overlay) { - // If the overlay size is 0, we haven't done layout yet. Presume that - // plugins are visible until we know otherwise. - if (overlay.scrollWidth == 0) { - return true; - } - - // 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. - let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) || - (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height)); - if (overflows) { - return false; - } - - // Is the plugin covered up by other content so that it is not clickable? - // Floating point can confuse .elementFromPoint, so inset just a bit - let left = pluginRect.left + 2; - let right = pluginRect.right - 2; - let top = pluginRect.top + 2; - let bottom = pluginRect.bottom - 2; - let centerX = left + (right - left) / 2; - let centerY = top + (bottom - top) / 2; - let points = [[left, top], - [left, bottom], - [right, top], - [right, bottom], - [centerX, centerY]]; - - if (right <= 0 || top <= 0) { - return false; - } - - let contentWindow = plugin.ownerGlobal; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - - for (let [x, y] of points) { - let el = cwu.elementFromPoint(x, y, true, true); - if (el !== plugin) { - return false; - } - } - - return true; - }, - - 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"; - default: - // Not all states map to a handler - return null; - } - }, - - handleEvent: function (event) { - let eventType = event.type; - - if (eventType == "unload") { - this.uninit(); - return; - } - - if (eventType == "pagehide") { - this.onPageHide(event); - return; - } - - if (eventType == "pageshow") { - this.onPageShow(event); - return; - } - - if (eventType == "PluginRemoved") { - this.updateNotificationUI(event.target); - return; - } - - if (eventType == "click") { - this.onOverlayClick(event); - return; - } - - if (eventType == "PluginCrashed" && - !(event.target instanceof Ci.nsIObjectLoadingContent)) { - // If the event target is not a plugin object (i.e., an <object> or - // <embed> element), this call is for a window-global plugin. - this.onPluginCrashed(event.target, event); - return; - } - - if (eventType == "HiddenPlugin") { - let win = event.target.defaultView; - if (!win.mozHiddenPluginTouched) { - let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag); - if (win.top.document != this.content.document) { - return; - } - this._showClickToPlayNotification(pluginTag, false); - let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - try { - winUtils.loadSheet(REPLACEMENT_STYLE_SHEET, win.AGENT_SHEET); - win.mozHiddenPluginTouched = true; - } catch (e) { - Cu.reportError("Error adding plugin replacement style sheet: " + e); - } - } - } - - let plugin = event.target; - - if (eventType == "PluginPlaceholderReplaced") { - plugin.removeAttribute("href"); - let overlay = this.getPluginUI(plugin, "main"); - this.setVisibility(plugin, overlay, true); - let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"] - .getService(Ci.inIDOMUtils); - // Add psuedo class so our styling will take effect - inIDOMUtils.addPseudoClassLock(plugin, "-moz-handler-clicktoplay"); - overlay.addEventListener("click", this, true); - return; - } - - 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; - switch (eventType) { - case "PluginCrashed": - this.onPluginCrashed(plugin, event); - break; - - case "PluginNotFound": { - /* NOP */ - break; - } - - case "PluginBlocklisted": - case "PluginOutdated": - shouldShowNotification = true; - break; - - case "PluginVulnerableUpdatable": - let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink"); - let { pluginTag } = this._getPluginInfo(plugin); - this.addLinkClickCallback(updateLink, "forwardCallback", - "openPluginUpdatePage", pluginTag); - /* FALLTHRU */ - - case "PluginVulnerableNoUpdate": - case "PluginClickToPlay": - this._handleClickToPlayEvent(plugin); - let pluginName = this._getPluginInfo(plugin).pluginName; - let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1); - let overlayText = this.getPluginUI(plugin, "clickToPlay"); - overlayText.textContent = messageString; - if (eventType == "PluginVulnerableUpdatable" || - eventType == "PluginVulnerableNoUpdate") { - let vulnerabilityString = gNavigatorBundle.GetStringFromName(eventType); - let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus"); - vulnerabilityText.textContent = vulnerabilityString; - } - shouldShowNotification = true; - break; - - case "PluginDisabled": - let manageLink = this.getPluginUI(plugin, "managePluginsLink"); - this.addLinkClickCallback(manageLink, "forwardCallback", "managePlugins"); - shouldShowNotification = true; - break; - - case "PluginInstantiated": - let key = this._getPluginInfo(plugin).pluginTag.niceName; - Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(key); - shouldShowNotification = true; - let pluginRect = plugin.getBoundingClientRect(); - if (pluginRect.width <= 5 && pluginRect.height <= 5) { - Services.telemetry.getHistogramById('PLUGIN_TINY_CONTENT').add(1); - } - break; - } - - if (this._getPluginInfo(plugin).mimetype === FLASH_MIME_TYPE) { - this._recordFlashPluginTelemetry(eventType, plugin); - } - - // Show the in-content UI if it's not too big. The crashed plugin handler already did this. - let overlay = this.getPluginUI(plugin, "main"); - if (eventType != "PluginCrashed") { - if (overlay != null) { - this.setVisibility(plugin, overlay, - this.shouldShowOverlay(plugin, overlay)); - let resizeListener = (event) => { - this.setVisibility(plugin, overlay, - this.shouldShowOverlay(plugin, overlay)); - this.updateNotificationUI(); - }; - plugin.addEventListener("overflow", resizeListener); - plugin.addEventListener("underflow", resizeListener); - } - } - - let closeIcon = this.getPluginUI(plugin, "closeIcon"); - if (closeIcon) { - closeIcon.addEventListener("click", event => { - if (event.button == 0 && event.isTrusted) { - this.hideClickToPlayOverlay(plugin); - overlay.setAttribute("dismissed", "true"); - } - }, true); - } - - if (shouldShowNotification) { - this._showClickToPlayNotification(plugin, false); - } - }, - - _recordFlashPluginTelemetry: function (eventType, plugin) { - if (!Services.telemetry.canRecordExtended) { - return; - } - - if (!this.flashPluginStats) { - this.flashPluginStats = { - instancesCount: 0, - plugins: new WeakSet() - }; - } - - if (!this.flashPluginStats.plugins.has(plugin)) { - // Reporting plugin instance and its dimensions only once. - this.flashPluginStats.plugins.add(plugin); - - this.flashPluginStats.instancesCount++; - - let pluginRect = plugin.getBoundingClientRect(); - Services.telemetry.getHistogramById('FLASH_PLUGIN_WIDTH') - .add(pluginRect.width); - Services.telemetry.getHistogramById('FLASH_PLUGIN_HEIGHT') - .add(pluginRect.height); - Services.telemetry.getHistogramById('FLASH_PLUGIN_AREA') - .add(pluginRect.width * pluginRect.height); - - let state = this._getPluginInfo(plugin).fallbackType; - if (state === null) { - state = Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED; - } - Services.telemetry.getHistogramById('FLASH_PLUGIN_STATES') - .add(state); - } - }, - - _finishRecordingFlashPluginTelemetry: function () { - if (this.flashPluginStats) { - Services.telemetry.getHistogramById('FLASH_PLUGIN_INSTANCES_ON_PAGE') - .add(this.flashPluginStats.instancesCount); - delete this.flashPluginStats; - } - }, - - 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 (!this.isKnownPlugin(objLoadingContent)) - return false; - - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); - let principal = objLoadingContent.ownerGlobal.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; - - return !objLoadingContent.activated && - pluginPermission != Ci.nsIPermissionManager.DENY_ACTION && - isFallbackTypeValid; - }, - - hideClickToPlayOverlay: function (plugin) { - let overlay = this.getPluginUI(plugin, "main"); - if (overlay) { - overlay.classList.remove("visible"); - } - }, - - // Forward a link click callback to the chrome process. - forwardCallback: function (name, pluginTag) { - this.global.sendAsyncMessage("PluginContent:LinkClickCallback", - { name, pluginTag }); - }, - - submitReport: function submitReport(plugin) { - /*** STUB ***/ - return; - }, - - reloadPage: function () { - this.global.content.location.reload(); - }, - - // Event listener for click-to-play plugins. - _handleClickToPlayEvent: function (plugin) { - let doc = plugin.ownerDocument; - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - let permissionString; - if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) { - // We only have replacement content for Flash installs - permissionString = pluginHost.getPermissionStringForType(FLASH_MIME_TYPE); - } else { - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - // guard against giving pluginHost.getPermissionStringForType a type - // not associated with any known plugin - if (!this.isKnownPlugin(objLoadingContent)) - return; - permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType); - } - - let principal = doc.defaultView.top.document.nodePrincipal; - let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString); - - let overlay = this.getPluginUI(plugin, "main"); - - if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) { - if (overlay) { - overlay.classList.remove("visible"); - } - return; - } - - if (overlay) { - overlay.addEventListener("click", this, true); - } - }, - - onOverlayClick: function (event) { - let document = event.target.ownerDocument; - let plugin = document.getBindingParent(event.target); - let contentWindow = plugin.ownerGlobal.top; - let overlay = this.getPluginUI(plugin, "main"); - // Have to check that the target is not the link to update the plugin - if (!(event.originalTarget instanceof contentWindow.HTMLAnchorElement) && - (event.originalTarget.getAttribute('anonid') != 'closeIcon') && - !overlay.hasAttribute('dismissed') && - event.button == 0 && - event.isTrusted) { - this._showClickToPlayNotification(plugin, true); - event.stopPropagation(); - event.preventDefault(); - } - }, - - reshowClickToPlayNotification: function () { - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let plugins = cwu.plugins; - for (let plugin of plugins) { - let overlay = this.getPluginUI(plugin, "main"); - if (overlay) - overlay.removeEventListener("click", this, true); - let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (this.canActivatePlugin(objLoadingContent)) - this._handleClickToPlayEvent(plugin); - } - this._showClickToPlayNotification(null, false); - }, - - /** - * Activate the plugins that the user has specified. - */ - activatePlugins: function (pluginInfo, newState) { - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let plugins = cwu.plugins; - let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); - - let pluginFound = false; - let placeHolderFound = false; - for (let plugin of plugins) { - plugin.QueryInterface(Ci.nsIObjectLoadingContent); - if (!this.isKnownPlugin(plugin)) { - continue; - } - if (pluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) { - let overlay = this.getPluginUI(plugin, "main"); - if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) { - placeHolderFound = true; - } else { - pluginFound = true; - } - if (newState == "block") { - if (overlay) { - overlay.addEventListener("click", this, true); - } - plugin.reload(true); - } else if (this.canActivatePlugin(plugin)) { - if (overlay) { - overlay.removeEventListener("click", this, true); - } - plugin.playPlugin(); - } - } - } - - // If there are no instances of the plugin on the page any more, what the - // user probably needs is for us to allow and then refresh. Additionally, if - // this is content that requires HLS or we replaced the placeholder the page - // needs to be refreshed for it to insert its plugins - if (newState != "block" && - (!pluginFound || placeHolderFound || contentWindow.pluginRequiresReload)) { - this.reloadPage(); - } - this.updateNotificationUI(); - }, - - _showClickToPlayNotification: function (plugin, showNow) { - let plugins = []; - - // If plugin is null, that means the user has navigated back to a page with - // plugins, and we need to collect all the plugins. - if (plugin === null) { - let contentWindow = this.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - // cwu.plugins may contain non-plugin <object>s, filter them out - plugins = cwu.plugins.filter((plugin) => - plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN); - - if (plugins.length == 0) { - this.removeNotification("click-to-play-plugins"); - return; - } - } else { - plugins = [plugin]; - } - - let pluginData = this.pluginData; - - let principal = this.content.document.nodePrincipal; - let location = this.content.document.location.href; - - for (let p of plugins) { - let pluginInfo; - if (p instanceof Ci.nsIPluginTag) { - let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null; - pluginInfo = this._getPluginInfoForTag(p, mimeType); - } else { - pluginInfo = this._getPluginInfo(p); - } - if (pluginInfo.permissionString === null) { - Cu.reportError("No permission string for active plugin."); - continue; - } - if (pluginData.has(pluginInfo.permissionString)) { - continue; - } - - 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; - } - - this.pluginData.set(pluginInfo.permissionString, pluginInfo); - } - - this.haveShownNotification = true; - - this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", { - plugins: [... this.pluginData.values()], - showNow: showNow, - location: location, - }, null, principal); - }, - - /** - * Updates the "hidden plugin" notification bar UI. - * - * @param document (optional) - * Specify the document that is causing the update. - * This is useful when the document is possibly no longer - * the current loaded document (for example, if we're - * responding to a PluginRemoved event for an unloading - * document). If this parameter is omitted, it defaults - * to the current top-level document. - */ - updateNotificationUI: function (document) { - document = document || this.content.document; - - // We're only interested in the top-level document, since that's - // the one that provides the Principal that we send back to the - // parent. - let principal = document.defaultView.top.document.nodePrincipal; - let location = document.location.href; - - // Make a copy of the actions from the last popup notification. - let haveInsecure = false; - let actions = new Map(); - for (let action of this.pluginData.values()) { - switch (action.fallbackType) { - // haveInsecure will trigger the red flashing icon and the infobar - // styling below - case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE: - case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE: - haveInsecure = true; - // fall through - - case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY: - actions.set(action.permissionString, action); - continue; - } - } - - // Remove plugins that are already active, or large enough to show an overlay. - let cwu = this.content.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - for (let plugin of cwu.plugins) { - let info = this._getPluginInfo(plugin); - if (!actions.has(info.permissionString)) { - continue; - } - let fallbackType = info.fallbackType; - if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) { - actions.delete(info.permissionString); - if (actions.size == 0) { - break; - } - continue; - } - if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY && - fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE && - fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) { - continue; - } - let overlay = this.getPluginUI(plugin, "main"); - if (!overlay) { - continue; - } - let shouldShow = this.shouldShowOverlay(plugin, overlay); - this.setVisibility(plugin, overlay, shouldShow); - if (shouldShow) { - actions.delete(info.permissionString); - if (actions.size == 0) { - break; - } - } - } - - // If there are any items remaining in `actions` now, they are hidden - // plugins that need a notification bar. - this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", { - haveInsecure: haveInsecure, - actions: [... actions.values()], - location: location, - }, null, principal); - }, - - removeNotification: function (name) { - this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name }); - }, - - clearPluginCaches: function () { - this.pluginData.clear(); - this.pluginCrashData.clear(); - }, - - hideNotificationBar: function (name) { - this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name }); - }, - - /** - * The PluginCrashed event handler. Note that the PluginCrashed event is - * fired for both NPAPI and Gecko Media plugins. In the latter case, the - * target of the event is the document that the GMP is being used in. - */ - onPluginCrashed: function (target, aEvent) { - if (!(aEvent instanceof this.content.PluginCrashedEvent)) - return; - - if (aEvent.gmpPlugin) { - this.GMPCrashed(aEvent); - return; - } - - if (!(target instanceof Ci.nsIObjectLoadingContent)) - return; - - let crashData = this.pluginCrashData.get(target.runID); - if (!crashData) { - // We haven't received information from the parent yet about - // this crash, so we should hold off showing the crash report - // UI. - return; - } - - crashData.instances.delete(target); - if (crashData.instances.length == 0) { - this.pluginCrashData.delete(target.runID); - } - - this.setCrashedNPAPIPluginState({ - plugin: target, - state: crashData.state, - message: crashData.message, - }); - }, - - NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) { - let message = - gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", - [pluginName], 1); - - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let plugins = cwu.plugins; - - for (let plugin of plugins) { - if (plugin instanceof Ci.nsIObjectLoadingContent && - plugin.runID == runID) { - // The parent has told us that the plugin process has died. - // It's possible that this content process hasn't yet noticed, - // in which case we need to stash this data around until the - // PluginCrashed events get sent up. - if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) { - // This plugin has already been put into the crashed state by the - // content process, so we can tweak its crash UI without delay. - this.setCrashedNPAPIPluginState({plugin, state, message}); - } else { - // The content process hasn't yet determined that the plugin has crashed. - // Stash the data in our map, and throw the plugin into a WeakSet. When - // the PluginCrashed event fires on the <object>/<embed>, we'll retrieve - // the information we need from the Map and remove the instance from the - // WeakSet. Once the WeakSet is empty, we can clear the map. - if (!this.pluginCrashData.has(runID)) { - this.pluginCrashData.set(runID, { - state: state, - message: message, - instances: new WeakSet(), - }); - } - let crashData = this.pluginCrashData.get(runID); - crashData.instances.add(plugin); - } - } - } - }, - - setCrashedNPAPIPluginState: function ({plugin, state, message}) { - // Force a layout flush so the binding is attached. - plugin.clientTop; - let overlay = this.getPluginUI(plugin, "main"); - let statusDiv = this.getPluginUI(plugin, "submitStatus"); - let optInCB = this.getPluginUI(plugin, "submitURLOptIn"); - - this.getPluginUI(plugin, "submitButton") - .addEventListener("click", (event) => { - if (event.button != 0 || !event.isTrusted) - return; - this.submitReport(plugin); - }); - - let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL"); - optInCB.checked = pref.getBoolPref(""); - - statusDiv.setAttribute("status", state); - - let helpIcon = this.getPluginUI(plugin, "helpIcon"); - this.addLinkClickCallback(helpIcon, "openHelpPage"); - - let crashText = this.getPluginUI(plugin, "crashedText"); - crashText.textContent = message; - - let link = this.getPluginUI(plugin, "reloadLink"); - this.addLinkClickCallback(link, "reloadPage"); - - let isShowing = this.shouldShowOverlay(plugin, overlay); - - // Is the <object>'s size too small to hold what we want to show? - if (!isShowing) { - // First try hiding the crash report submission UI. - statusDiv.removeAttribute("status"); - - isShowing = this.shouldShowOverlay(plugin, overlay); - } - this.setVisibility(plugin, overlay, isShowing); - - let doc = plugin.ownerDocument; - let runID = plugin.runID; - - 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. - this.hideNotificationBar("plugin-crashed"); - doc.mozNoPluginCrashedNotification = true; - - // Notify others that the crash reporter UI is now ready. - // Currently, this event is only used by tests. - let winUtils = this.content.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let event = new this.content.CustomEvent("PluginCrashReporterDisplayed", {bubbles: true}); - winUtils.dispatchEventToChromeOnly(plugin, event); - } else if (!doc.mozNoPluginCrashedNotification) { - // If another plugin on the page was large enough to show our UI, we don't - // want to show a notification bar. - this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", - { messageString: message, pluginID: runID }); - // Remove the notification when the page is reloaded. - doc.defaultView.top.addEventListener("unload", event => { - this.hideNotificationBar("plugin-crashed"); - }, false); - } - }, - - NPAPIPluginCrashReportSubmitted: function({ runID, state }) { - this.pluginCrashData.delete(runID); - let contentWindow = this.global.content; - let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - let plugins = cwu.plugins; - - for (let plugin of plugins) { - if (plugin instanceof Ci.nsIObjectLoadingContent && - plugin.runID == runID) { - let statusDiv = this.getPluginUI(plugin, "submitStatus"); - statusDiv.setAttribute("status", state); - } - } - }, - - GMPCrashed: function(aEvent) { - let target = aEvent.target; - let pluginName = aEvent.pluginName; - let gmpPlugin = aEvent.gmpPlugin; - let pluginID = aEvent.pluginID; - let doc = target.document; - - if (!gmpPlugin || !doc) { - // TODO: Throw exception? How did we get here? - return; - } - - let messageString = - gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title", - [pluginName], 1); - - this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification", - { messageString, pluginID }); - - // Remove the notification when the page is reloaded. - doc.defaultView.top.addEventListener("unload", event => { - this.hideNotificationBar("plugin-crashed"); - }, false); - }, -}; |