Bug 1472491: Part 5i - Add PluginChild actor. r=felipe draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 29 Jul 2018 20:37:42 -0700
changeset 828444 bcb167ee9177781b9696ac7659931d769672c437
parent 828443 16b170068a38c81f558923d05844a63926bee375
child 828445 a528a0ff331cc3b0de3fe672643cd4ac173036d5
push id118680
push usermaglione.k@gmail.com
push dateFri, 10 Aug 2018 23:04:22 +0000
reviewersfelipe
bugs1472491
milestone63.0a1
Bug 1472491: Part 5i - Add PluginChild actor. r=felipe MozReview-Commit-ID: 63iOMa9OsWu
browser/actors/PluginChild.jsm
browser/actors/moz.build
browser/base/content/content.js
browser/components/nsBrowserGlue.js
browser/modules/PluginContent.jsm
browser/modules/moz.build
rename from browser/modules/PluginContent.jsm
rename to browser/actors/PluginChild.jsm
--- a/browser/modules/PluginContent.jsm
+++ b/browser/actors/PluginChild.jsm
@@ -1,66 +1,63 @@
 /* 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 = [ "PluginContent" ];
+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");
 
 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");
 
-var PluginContent = function(global) {
-  this.init(global);
-};
-
 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
 };
 
-PluginContent.prototype = {
-  init(global) {
-    this.global = global;
-    // Need to hold onto the content window or else it'll get destroyed
-    this.content = this.global.content;
+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();
 
-    global.addEventListener("pagehide", this, true);
-    global.addEventListener("pageshow", this, true);
-  },
+    this.mm.addEventListener("pagehide", this, true);
+    this.mm.addEventListener("pageshow", this, 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":
-        let contextMenu = this.global.contextMenu;
+        let contextMenu = this.mm.contextMenu;
 
         switch (msg.data.command) {
           case "play":
             this._showClickToPlayNotification(contextMenu.getTarget(msg, "plugin"), true);
             break;
           case "hide":
             this.hideClickToPlayOverlay(contextMenu.getTarget(msg, "plugin"));
             break;
@@ -80,61 +77,61 @@ PluginContent.prototype = {
         });
         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) {
+  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.global.content.pluginRequiresReload = true;
+          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;
@@ -173,17 +170,17 @@ PluginContent.prototype = {
 
     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) {
@@ -222,27 +219,27 @@ PluginContent.prototype = {
              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
@@ -333,17 +330,17 @@ PluginContent.prototype = {
       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)
@@ -363,17 +360,17 @@ PluginContent.prototype = {
                                   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:
@@ -390,17 +387,17 @@ PluginContent.prototype = {
       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;
     }
@@ -533,22 +530,22 @@ PluginContent.prototype = {
           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;
 
@@ -559,32 +556,32 @@ PluginContent.prototype = {
 
     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.global.sendAsyncMessage("PluginContent:LinkClickCallback",
+    this.mm.sendAsyncMessage("PluginContent:LinkClickCallback",
       { name, pluginTag });
-  },
+  }
 
-  submitReport: function submitReport(plugin) {
+  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)) {
@@ -597,23 +594,23 @@ PluginContent.prototype = {
     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.global.sendAsyncMessage("PluginContent:SubmitReport",
+    this.mm.sendAsyncMessage("PluginContent:SubmitReport",
                                  { runID, keyVals, submitURLOptIn });
-  },
+  }
 
   reloadPage() {
-    this.global.content.location.reload();
-  },
+    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
@@ -631,54 +628,54 @@ PluginContent.prototype = {
         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.global.content;
+    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.global.content;
+    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)) {
@@ -705,17 +702,17 @@ PluginContent.prototype = {
     // 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;
@@ -763,22 +760,22 @@ PluginContent.prototype = {
         pluginInfo.pluginPermissionType = undefined;
       }
 
       this.pluginData.set(pluginInfo.permissionString, pluginInfo);
     }
 
     this.haveShownNotification = true;
 
-    this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
+    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
@@ -846,35 +843,35 @@ PluginContent.prototype = {
         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", {
+    this.mm.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
       haveInsecure,
       actions: [...actions.values()],
       location,
     }, null, principal);
-  },
+  }
 
   removeNotification(name) {
-    this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name });
-  },
+    this.mm.sendAsyncMessage("PluginContent:RemoveNotification", { name });
+  }
 
   clearPluginCaches() {
     this.pluginData.clear();
     this.pluginCrashData.clear();
-  },
+  }
 
   hideNotificationBar(name) {
-    this.global.sendAsyncMessage("PluginContent: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
@@ -905,17 +902,17 @@ PluginContent.prototype = {
     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))
@@ -949,24 +946,24 @@ PluginContent.prototype = {
       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.global.content;
+    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,
@@ -989,17 +986,17 @@ PluginContent.prototype = {
               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");
 
@@ -1048,39 +1045,39 @@ PluginContent.prototype = {
       // 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.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+      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.global.content;
+    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;
 
@@ -1088,17 +1085,17 @@ PluginContent.prototype = {
       // TODO: Throw exception? How did we get here?
       return;
     }
 
     let messageString =
       gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
                                             [pluginName], 1);
 
-    this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+    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");
     });
-  },
-};
+  }
+}
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -8,18 +8,22 @@ with Files("LightWeightThemeInstallChild
     BUG_COMPONENT = ("Firefox", "Theme")
 
 with Files("PageInfoChild.jsm"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
 with Files("PageStyleChild.jsm"):
     BUG_COMPONENT = ("Firefox", "Menus")
 
+with Files("PluginChild.jsm"):
+    BUG_COMPONENT = ("Core", "Plug-ins")
+
 FINAL_TARGET_FILES.actors += [
     'AboutReaderChild.jsm',
     'BrowserTabChild.jsm',
     'ClickHandlerChild.jsm',
     'ContentSearchChild.jsm',
     'ContextMenuChild.jsm',
     'LightWeightThemeInstallChild.jsm',
     'PageInfoChild.jsm',
     'PageStyleChild.jsm',
+    'PluginChild.jsm',
 ]
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -17,17 +17,16 @@ var global = this;
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BlockedSiteContent: "resource:///modules/BlockedSiteContent.jsm",
   ContentLinkHandler: "resource:///modules/ContentLinkHandler.jsm",
   ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
   ContentWebRTC: "resource:///modules/ContentWebRTC.jsm",
   LoginFormFactory: "resource://gre/modules/LoginManagerContent.jsm",
   InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
-  PluginContent: "resource:///modules/PluginContent.jsm",
   FormSubmitObserver: "resource:///modules/FormSubmitObserver.jsm",
   NetErrorContent: "resource:///modules/NetErrorContent.jsm",
   PageMetadata: "resource://gre/modules/PageMetadata.jsm",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
   ContextMenuChild: "resource:///modules/ContextMenuChild.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
@@ -153,79 +152,16 @@ this.AboutNetAndCertErrorListener = {
     NetErrorContent.handleEvent(global, aEvent);
   },
 };
 AboutNetAndCertErrorListener.init(this);
 
 new ContentLinkHandler(this);
 ContentMetaHandler.init(this);
 
-var PluginContentStub = {
-  EVENTS: [
-    "PluginCrashed",
-    "PluginOutdated",
-    "PluginInstantiated",
-    "PluginRemoved",
-    "HiddenPlugin",
-  ],
-
-  MESSAGES: [
-    "BrowserPlugins:ActivatePlugins",
-    "BrowserPlugins:NotificationShown",
-    "BrowserPlugins:ContextMenuCommand",
-    "BrowserPlugins:NPAPIPluginProcessCrashed",
-    "BrowserPlugins:CrashReportSubmitted",
-    "BrowserPlugins:Test:ClearCrashData",
-  ],
-
-  _pluginContent: null,
-  get pluginContent() {
-    if (!this._pluginContent) {
-      this._pluginContent = new PluginContent(global);
-    }
-    return this._pluginContent;
-  },
-
-  init() {
-    addEventListener("unload", this);
-
-    addEventListener("PluginBindingAttached", this, true, true);
-
-    for (let event of this.EVENTS) {
-      addEventListener(event, this, true);
-    }
-    for (let msg of this.MESSAGES) {
-      addMessageListener(msg, this);
-    }
-    Services.obs.addObserver(this, "decoder-doctor-notification");
-    this.init = null;
-  },
-
-  uninit() {
-    Services.obs.removeObserver(this, "decoder-doctor-notification");
-  },
-
-  observe(subject, topic, data) {
-    return this.pluginContent.observe(subject, topic, data);
-  },
-
-  handleEvent(event) {
-    if (event.type === "unload") {
-      return this.uninit();
-    }
-    return this.pluginContent.handleEvent(event);
-  },
-
-  receiveMessage(msg) {
-    return this.pluginContent.receiveMessage(msg);
-  },
-};
-
-PluginContentStub.init();
-
 // This is a temporary hack to prevent regressions (bug 1471327).
 void content;
 
 addEventListener("DOMWindowFocus", function(event) {
   sendAsyncMessage("DOMWindowFocus", {});
 }, false);
 
 // We use this shim so that ContentWebRTC.jsm will not be loaded until
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -113,16 +113,43 @@ let ACTORS = {
         "pageshow": {},
       },
       messages: [
         "PageStyle:Switch",
         "PageStyle:Disable",
       ]
     },
   },
+
+  Plugin: {
+    child: {
+      module: "resource:///actors/PluginChild.jsm",
+      events: {
+        "PluginBindingAttached": {capture: true, wantUntrusted: true},
+        "PluginCrashed": {capture: true},
+        "PluginOutdated": {capture: true},
+        "PluginInstantiated": {capture: true},
+        "PluginRemoved": {capture: true},
+        "HiddenPlugin": {capture: true},
+      },
+
+      messages: [
+        "BrowserPlugins:ActivatePlugins",
+        "BrowserPlugins:NotificationShown",
+        "BrowserPlugins:ContextMenuCommand",
+        "BrowserPlugins:NPAPIPluginProcessCrashed",
+        "BrowserPlugins:CrashReportSubmitted",
+        "BrowserPlugins:Test:ClearCrashData",
+      ],
+
+      observers: [
+        "decoder-doctor-notification",
+      ],
+    },
+  },
 };
 
 (function earlyBlankFirstPaint() {
   if (!Services.prefs.getBoolPref("browser.startup.blankWindow", false))
     return;
 
   let store = Services.xulStore;
   let getValue = attr =>
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -68,19 +68,16 @@ with Files("LightweightThemeChildHelper.
     BUG_COMPONENT = ("WebExtensions", "Themes")
 
 with Files("OpenInTabsUtils.jsm"):
     BUG_COMPONENT = ("Firefox", "Tabbed Browser")
 
 with Files("PermissionUI.jsm"):
    BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
-with Files("PluginContent.jsm"):
-    BUG_COMPONENT = ("Core", "Plug-ins")
-
 with Files("ProcessHangMonitor.jsm"):
     BUG_COMPONENT = ("Core", "DOM: Content Processes")
 
 with Files("ReaderParent.jsm"):
     BUG_COMPONENT = ("Toolkit", "Reader Mode")
 
 with Files("Sanitizer.jsm"):
     BUG_COMPONENT = ("Firefox", "Preferences")
@@ -143,17 +140,16 @@ EXTRA_JS_MODULES += [
     'HomePage.jsm',
     'LaterRun.jsm',
     'LightweightThemeChildHelper.jsm',
     'NetErrorContent.jsm',
     'OpenInTabsUtils.jsm',
     'PageActions.jsm',
     'PermissionUI.jsm',
     'PingCentre.jsm',
-    'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RemotePrompt.jsm',
     'Sanitizer.jsm',
     'SavantShieldStudy.jsm',
     'SchedulePressure.jsm',
     'SiteDataManager.jsm',
     'SitePermissions.jsm',