forked from mirrors/gecko-dev
		
	 0c6cd02ae1
			
		
	
	
		0c6cd02ae1
		
	
	
	
	
		
			
			Having to add pagehide/pageshow listeners to the chrome event target is a serious inconvience for the use cases of this bug. Dispatching to system group listeners has approximately the same effect as the old code, but is much easier for window-bound code to handle. --HG-- extra : rebase_source : d67c14e9ba91772d8a9dd82481120b34bdb551e0
		
			
				
	
	
		
			1102 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1102 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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 EXPORTED_SYMBOLS = ["PluginChild"];
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/Timer.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/BrowserUtils.jsm");
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "ContextMenuChild",
 | |
|                                "resource:///actors/ContextMenuChild.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
 | |
|   const url = "chrome://browser/locale/browser.properties";
 | |
|   return Services.strings.createBundle(url);
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "AppConstants",
 | |
|   "resource://gre/modules/AppConstants.jsm");
 | |
| 
 | |
| const OVERLAY_DISPLAY = {
 | |
|   HIDDEN: 0, // The overlay will be transparent
 | |
|   BLANK: 1, // The overlay will be just a grey box
 | |
|   TINY: 2, // The overlay with a 16x16 plugin icon
 | |
|   REDUCED: 3, // The overlay with a 32x32 plugin icon
 | |
|   NOTEXT: 4, // The overlay with a 48x48 plugin icon and the close button
 | |
|   FULL: 5, // The full overlay: 48x48 plugin icon, close button and label
 | |
| };
 | |
| 
 | |
| class PluginChild extends ActorChild {
 | |
|   constructor(mm) {
 | |
|     super(mm);
 | |
| 
 | |
|     // 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();
 | |
| 
 | |
|     this.mm.addEventListener("pagehide", this, {capture: true, mozSystemGroup: true});
 | |
|     this.mm.addEventListener("pageshow", this, {capture: true, mozSystemGroup: true});
 | |
|   }
 | |
| 
 | |
|   receiveMessage(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(ContextMenuChild.getTarget(this.mm, msg, "plugin"), true);
 | |
|             break;
 | |
|           case "hide":
 | |
|             this.hideClickToPlayOverlay(ContextMenuChild.getTarget(this.mm, msg, "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(aSubject, aTopic, aData) {
 | |
|     switch (aTopic) {
 | |
|       case "decoder-doctor-notification":
 | |
|         let data = JSON.parse(aData);
 | |
|         let type = data.type.toLowerCase();
 | |
|         if (type == "cannot-play" &&
 | |
|             this.haveShownNotification &&
 | |
|             aSubject.top.document == this.content.document &&
 | |
|             data.formats.toLowerCase().includes("application/x-mpegurl", 0)) {
 | |
|           this.content.pluginRequiresReload = true;
 | |
|           this.updateNotificationUI(this.content.document);
 | |
|         }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   onPageShow(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(event) {
 | |
|     // Ignore events that aren't from the main document.
 | |
|     if (!this.content || event.target != this.content.document) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.clearPluginCaches();
 | |
|     this.haveShownNotification = false;
 | |
|   }
 | |
| 
 | |
|   getPluginUI(plugin, anonid) {
 | |
|     return plugin.ownerDocument.
 | |
|            getAnonymousElementByAttribute(plugin, "anonid", anonid);
 | |
|   }
 | |
| 
 | |
|   _getPluginInfo(pluginElement) {
 | |
|     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,
 | |
|              pluginTag,
 | |
|              permissionString,
 | |
|              fallbackType,
 | |
|              blocklistState,
 | |
|            };
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * _getPluginInfoForTag is called when iterating the plugins for a document,
 | |
|    * and what we get from nsIDOMWindowUtils is an nsIPluginTag, and not an
 | |
|    * nsIObjectLoadingContent. This only should happen if the plugin is
 | |
|    * click-to-play (see bug 1186948).
 | |
|    */
 | |
|   _getPluginInfoForTag(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,
 | |
|              pluginTag,
 | |
|              permissionString,
 | |
|              // Since we should only have entered _getPluginInfoForTag when
 | |
|              // examining a click-to-play plugin, we can safely hard-code
 | |
|              // this fallback type, since we don't actually have an
 | |
|              // nsIObjectLoadingContent to check.
 | |
|              fallbackType: Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
 | |
|              blocklistState,
 | |
|            };
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Update the visibility of the plugin overlay.
 | |
|    */
 | |
|   setVisibility(plugin, overlay, overlayDisplayState) {
 | |
|     overlay.classList.toggle("visible", overlayDisplayState != OVERLAY_DISPLAY.HIDDEN);
 | |
|     if (overlayDisplayState != OVERLAY_DISPLAY.HIDDEN) {
 | |
|       overlay.removeAttribute("dismissed");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Adjust the style in which the overlay will be displayed. It might be adjusted
 | |
|    * based on its size, or if there's some other element covering all corners of
 | |
|    * the overlay.
 | |
|    *
 | |
|    * This function will handle adjusting the style of the overlay, but will
 | |
|    * not handle hiding it. That is done by setVisibility with the return value
 | |
|    * from this function.
 | |
|    *
 | |
|    * @returns A value from OVERLAY_DISPLAY.
 | |
|    */
 | |
|   computeAndAdjustOverlayDisplay(plugin, overlay) {
 | |
|     let fallbackType = plugin.pluginFallbackType;
 | |
|     if (plugin.pluginFallbackTypeOverride !== undefined) {
 | |
|       fallbackType = plugin.pluginFallbackTypeOverride;
 | |
|     }
 | |
|     if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET) {
 | |
|       return OVERLAY_DISPLAY.HIDDEN;
 | |
|     }
 | |
| 
 | |
|     // 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 OVERLAY_DISPLAY.FULL;
 | |
|     }
 | |
| 
 | |
|     let overlayDisplay = OVERLAY_DISPLAY.FULL;
 | |
| 
 | |
|     // Is the <object>'s size too small to hold what we want to show?
 | |
|     let pluginRect = plugin.getBoundingClientRect();
 | |
|     let pluginWidth = Math.ceil(pluginRect.width);
 | |
|     let pluginHeight = Math.ceil(pluginRect.height);
 | |
| 
 | |
|     // We must set the attributes while here inside this function in order
 | |
|     // for a possible re-style to occur, which will make the scrollWidth/Height
 | |
|     // checks below correct. Otherwise, we would be requesting e.g. a TINY
 | |
|     // overlay here, but the default styling would be used, and that would make
 | |
|     // it overflow, causing it to change to BLANK instead of remaining as TINY.
 | |
| 
 | |
|     if (pluginWidth <= 32 || pluginHeight <= 32) {
 | |
|       overlay.setAttribute("sizing", "blank");
 | |
|       overlayDisplay = OVERLAY_DISPLAY.BLANK;
 | |
|     } else if (pluginWidth <= 80 || pluginHeight <= 60) {
 | |
|       overlayDisplay = OVERLAY_DISPLAY.TINY;
 | |
|       overlay.setAttribute("sizing", "tiny");
 | |
|       overlay.setAttribute("notext", "notext");
 | |
|     } else if (pluginWidth <= 120 || pluginHeight <= 80) {
 | |
|       overlayDisplay = OVERLAY_DISPLAY.REDUCED;
 | |
|       overlay.setAttribute("sizing", "reduced");
 | |
|       overlay.setAttribute("notext", "notext");
 | |
|     } else if (pluginWidth <= 240 || pluginHeight <= 160) {
 | |
|       overlayDisplay = OVERLAY_DISPLAY.NOTEXT;
 | |
|       overlay.removeAttribute("sizing");
 | |
|       overlay.setAttribute("notext", "notext");
 | |
|     } else {
 | |
|       overlayDisplay = OVERLAY_DISPLAY.FULL;
 | |
|       overlay.removeAttribute("sizing");
 | |
|       overlay.removeAttribute("notext");
 | |
|     }
 | |
| 
 | |
| 
 | |
|     // 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 > pluginWidth) ||
 | |
|                     (overlay.scrollHeight - 5 > pluginHeight);
 | |
|     if (overflows) {
 | |
|       overlay.setAttribute("sizing", "blank");
 | |
|       return OVERLAY_DISPLAY.BLANK;
 | |
|     }
 | |
| 
 | |
|     // 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]];
 | |
| 
 | |
|     let contentWindow = plugin.ownerGlobal;
 | |
|     let cwu = contentWindow.windowUtils;
 | |
| 
 | |
|     for (let [x, y] of points) {
 | |
|       if (x < 0 || y < 0) {
 | |
|         continue;
 | |
|       }
 | |
|       let el = cwu.elementFromPoint(x, y, true, true);
 | |
|       if (el === plugin) {
 | |
|         return overlayDisplay;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     overlay.setAttribute("sizing", "blank");
 | |
|     return OVERLAY_DISPLAY.BLANK;
 | |
|   }
 | |
| 
 | |
|   addLinkClickCallback(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(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:
 | |
|       case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY_QUIET:
 | |
|         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(event) {
 | |
|     let eventType = event.type;
 | |
| 
 | |
|     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 pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
 | |
|       if (event.target.defaultView.top.document != this.content.document) {
 | |
|         return;
 | |
|       }
 | |
|       this._showClickToPlayNotification(pluginTag, false);
 | |
|     }
 | |
| 
 | |
|     let plugin = event.target;
 | |
| 
 | |
|     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("PluginClickToActivate2", [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":
 | |
|         shouldShowNotification = true;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     // 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.computeAndAdjustOverlayDisplay(plugin, overlay));
 | |
|         let resizeListener = () => {
 | |
|           this.setVisibility(plugin, overlay,
 | |
|             this.computeAndAdjustOverlayDisplay(plugin, overlay));
 | |
|           this.updateNotificationUI();
 | |
|         };
 | |
|         plugin.addEventListener("overflow", resizeListener);
 | |
|         plugin.addEventListener("underflow", resizeListener);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let closeIcon = this.getPluginUI(plugin, "closeIcon");
 | |
|     if (closeIcon) {
 | |
|       closeIcon.addEventListener("click", clickEvent => {
 | |
|         if (clickEvent.button == 0 && clickEvent.isTrusted) {
 | |
|           this.hideClickToPlayOverlay(plugin);
 | |
|           overlay.setAttribute("dismissed", "true");
 | |
|         }
 | |
|       }, true);
 | |
|     }
 | |
| 
 | |
|     if (shouldShowNotification) {
 | |
|       this._showClickToPlayNotification(plugin, false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   isKnownPlugin(objLoadingContent) {
 | |
|     return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
 | |
|             Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
 | |
|   }
 | |
| 
 | |
|   canActivatePlugin(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_CLICK_TO_PLAY_QUIET;
 | |
| 
 | |
|     return !objLoadingContent.activated &&
 | |
|            pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
 | |
|            isFallbackTypeValid;
 | |
|   }
 | |
| 
 | |
|   hideClickToPlayOverlay(plugin) {
 | |
|     let overlay = this.getPluginUI(plugin, "main");
 | |
|     if (overlay) {
 | |
|       overlay.classList.remove("visible");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Forward a link click callback to the chrome process.
 | |
|   forwardCallback(name, pluginTag) {
 | |
|     this.mm.sendAsyncMessage("PluginContent:LinkClickCallback",
 | |
|       { name, pluginTag });
 | |
|   }
 | |
| 
 | |
|   submitReport(plugin) {
 | |
|     if (!AppConstants.MOZ_CRASHREPORTER) {
 | |
|       return;
 | |
|     }
 | |
|     if (!plugin) {
 | |
|       Cu.reportError("Attempted to submit crash report without an associated plugin.");
 | |
|       return;
 | |
|     }
 | |
|     if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
 | |
|       Cu.reportError("Attempted to submit crash report on plugin that does not" +
 | |
|                      "implement nsIObjectLoadingContent.");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let runID = plugin.runID;
 | |
|     let submitURLOptIn = this.getPluginUI(plugin, "submitURLOptIn").checked;
 | |
|     let keyVals = {};
 | |
|     let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
 | |
|     if (userComment)
 | |
|       keyVals.PluginUserComment = userComment;
 | |
|     if (submitURLOptIn)
 | |
|       keyVals.PluginContentURL = plugin.ownerDocument.URL;
 | |
| 
 | |
|     this.mm.sendAsyncMessage("PluginContent:SubmitReport",
 | |
|                                  { runID, keyVals, submitURLOptIn });
 | |
|   }
 | |
| 
 | |
|   reloadPage() {
 | |
|     this.content.location.reload();
 | |
|   }
 | |
| 
 | |
|   // Event listener for click-to-play plugins.
 | |
|   _handleClickToPlayEvent(plugin) {
 | |
|     let doc = plugin.ownerDocument;
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
|     let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
 | |
|     // guard against giving pluginHost.getPermissionStringForType a type
 | |
|     // not associated with any known plugin
 | |
|     if (!this.isKnownPlugin(objLoadingContent))
 | |
|       return;
 | |
|     let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
 | |
|     let principal = doc.defaultView.top.document.nodePrincipal;
 | |
|     let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
 | |
| 
 | |
|     let overlay = this.getPluginUI(plugin, "main");
 | |
| 
 | |
|     if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION ||
 | |
|         pluginPermission == Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET) {
 | |
|       if (overlay) {
 | |
|         overlay.classList.remove("visible");
 | |
|       }
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (overlay) {
 | |
|       overlay.addEventListener("click", this, true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   onOverlayClick(event) {
 | |
|     let document = event.target.ownerDocument;
 | |
|     let plugin = document.getBindingParent(event.target);
 | |
|     let overlay = this.getPluginUI(plugin, "main");
 | |
|     // Have to check that the target is not the link to update the plugin
 | |
|     if (!(ChromeUtils.getClassName(event.originalTarget) === "HTMLAnchorElement") &&
 | |
|         (event.originalTarget.getAttribute("anonid") != "closeIcon") &&
 | |
|         !overlay.hasAttribute("dismissed") &&
 | |
|         event.button == 0 &&
 | |
|         event.isTrusted) {
 | |
|       this._showClickToPlayNotification(plugin, true);
 | |
|     event.stopPropagation();
 | |
|     event.preventDefault();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   reshowClickToPlayNotification() {
 | |
|     let contentWindow = this.content;
 | |
|     let cwu = contentWindow.windowUtils;
 | |
|     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(pluginInfo, newState) {
 | |
|     let contentWindow = this.content;
 | |
|     let cwu = contentWindow.windowUtils;
 | |
|     let plugins = cwu.plugins;
 | |
|     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 | |
| 
 | |
|     let pluginFound = 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");
 | |
|         pluginFound = true;
 | |
|         if (newState == "block" || newState == "blockalways" || newState == "continueblocking") {
 | |
|           if (overlay) {
 | |
|             overlay.addEventListener("click", this, true);
 | |
|           }
 | |
|           plugin.pluginFallbackTypeOverride = pluginInfo.fallbackType;
 | |
|           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 or if we've
 | |
|     // noted that the content needs to be reloaded due to replacing HLS, what the
 | |
|     // user probably needs is for us to allow and then refresh.
 | |
|     if (newState != "block" && newState != "blockalways" && newState != "continueblocking" &&
 | |
|        (!pluginFound || contentWindow.pluginRequiresReload)) {
 | |
|       this.reloadPage();
 | |
|     }
 | |
|     this.updateNotificationUI();
 | |
|   }
 | |
| 
 | |
|   _showClickToPlayNotification(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.windowUtils;
 | |
|       // cwu.plugins may contain non-plugin <object>s, filter them out
 | |
|       plugins = cwu.plugins.filter((p) =>
 | |
|         p.getContentTypeForMIMEType(p.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.mm.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
 | |
|       plugins: [...this.pluginData.values()],
 | |
|       showNow,
 | |
|       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(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_QUIET:
 | |
|         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.windowUtils;
 | |
|     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_CLICK_TO_PLAY_QUIET &&
 | |
|           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
 | |
|           fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
 | |
|         continue;
 | |
|       }
 | |
|       let overlay = this.getPluginUI(plugin, "main");
 | |
|       if (!overlay) {
 | |
|         continue;
 | |
|       }
 | |
|       let overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
 | |
|       this.setVisibility(plugin, overlay, overlayDisplayState);
 | |
|       if (overlayDisplayState > OVERLAY_DISPLAY.BLANK) {
 | |
|         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.mm.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
 | |
|       haveInsecure,
 | |
|       actions: [...actions.values()],
 | |
|       location,
 | |
|     }, null, principal);
 | |
|   }
 | |
| 
 | |
|   removeNotification(name) {
 | |
|     this.mm.sendAsyncMessage("PluginContent:RemoveNotification", { name });
 | |
|   }
 | |
| 
 | |
|   clearPluginCaches() {
 | |
|     this.pluginData.clear();
 | |
|     this.pluginCrashData.clear();
 | |
|   }
 | |
| 
 | |
|   hideNotificationBar(name) {
 | |
|     this.mm.sendAsyncMessage("PluginContent:HideNotificationBar", { name });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Determines whether or not the crashed plugin is contained within current
 | |
|    * full screen DOM element.
 | |
|    * @param fullScreenElement (DOM element)
 | |
|    *   The DOM element that is currently full screen, or null.
 | |
|    * @param domElement
 | |
|    *   The DOM element which contains the crashed plugin, or the crashed plugin
 | |
|    *   itself.
 | |
|    * @returns bool
 | |
|    *   True if the plugin is a descendant of the full screen DOM element, false otherwise.
 | |
|    **/
 | |
|   isWithinFullScreenElement(fullScreenElement, domElement) {
 | |
| 
 | |
|     /**
 | |
|      * Traverses down iframes until it find a non-iframe full screen DOM element.
 | |
|      * @param fullScreenIframe
 | |
|      *  Target iframe to begin searching from.
 | |
|      * @returns DOM element
 | |
|      *  The full screen DOM element contained within the iframe (could be inner iframe), or the original iframe if no inner DOM element is found.
 | |
|      **/
 | |
|     let getTrueFullScreenElement = fullScreenIframe => {
 | |
|       if (typeof fullScreenIframe.contentDocument !== "undefined" && fullScreenIframe.contentDocument.mozFullScreenElement) {
 | |
|         return getTrueFullScreenElement(fullScreenIframe.contentDocument.mozFullScreenElement);
 | |
|       }
 | |
|       return fullScreenIframe;
 | |
|     };
 | |
| 
 | |
|     if (fullScreenElement.tagName === "IFRAME") {
 | |
|       fullScreenElement = getTrueFullScreenElement(fullScreenElement);
 | |
|     }
 | |
| 
 | |
|     if (fullScreenElement.contains(domElement)) {
 | |
|       return true;
 | |
|     }
 | |
|     let parentIframe = domElement.ownerGlobal.frameElement;
 | |
|     if (parentIframe) {
 | |
|       return this.isWithinFullScreenElement(fullScreenElement, parentIframe);
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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(target, aEvent) {
 | |
|     if (!(aEvent instanceof this.content.PluginCrashedEvent))
 | |
|       return;
 | |
| 
 | |
|     let fullScreenElement = this.content.document.mozFullScreenElement;
 | |
|     if (fullScreenElement) {
 | |
|       if (this.isWithinFullScreenElement(fullScreenElement, target)) {
 | |
|         this.content.document.mozCancelFullScreen();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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({pluginName, runID, state}) {
 | |
|     let message =
 | |
|       gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
 | |
|                                             [pluginName], 1);
 | |
| 
 | |
|     let contentWindow = this.content;
 | |
|     let cwu = contentWindow.windowUtils;
 | |
|     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,
 | |
|               message,
 | |
|               instances: new WeakSet(),
 | |
|             });
 | |
|           }
 | |
|           let crashData = this.pluginCrashData.get(runID);
 | |
|           crashData.instances.add(plugin);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   setCrashedNPAPIPluginState({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 overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
 | |
| 
 | |
|     // Is the <object>'s size too small to hold what we want to show?
 | |
|     if (overlayDisplayState != OVERLAY_DISPLAY.FULL) {
 | |
|       // First try hiding the crash report submission UI.
 | |
|       statusDiv.removeAttribute("status");
 | |
| 
 | |
|       overlayDisplayState = this.computeAndAdjustOverlayDisplay(plugin, overlay);
 | |
|     }
 | |
|     this.setVisibility(plugin, overlay, overlayDisplayState);
 | |
| 
 | |
|     let doc = plugin.ownerDocument;
 | |
|     let runID = plugin.runID;
 | |
| 
 | |
|     if (overlayDisplayState == OVERLAY_DISPLAY.FULL) {
 | |
|       // 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.windowUtils;
 | |
|       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.mm.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");
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   NPAPIPluginCrashReportSubmitted({ runID, state }) {
 | |
|     this.pluginCrashData.delete(runID);
 | |
|     let contentWindow = this.content;
 | |
|     let cwu = contentWindow.windowUtils;
 | |
|     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(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.mm.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
 | |
|                                  { messageString, pluginID });
 | |
| 
 | |
|     // Remove the notification when the page is reloaded.
 | |
|     doc.defaultView.top.addEventListener("unload", event => {
 | |
|       this.hideNotificationBar("plugin-crashed");
 | |
|     });
 | |
|   }
 | |
| }
 |