Bug 1472491: Part 5i - Add PluginChild actor. r=felipe
authorKris Maglione <maglione.k@gmail.com>
Sun, 29 Jul 2018 20:37:42 -0700
changeset 486561 0fbc93e84af5cfc0ce57f3c0bf3189bfcf20af74
parent 486560 923c346f02128f4498a7ca102ec75b4ee1aef2fa
child 486562 17440776f078aa0219f72e97ce88f0b7a1217b30
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs1472491
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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,73 +1,71 @@
 /* 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");
 
+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");
 
-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;
-
         switch (msg.data.command) {
           case "play":
-            this._showClickToPlayNotification(contextMenu.getTarget(msg, "plugin"), true);
+            this._showClickToPlayNotification(ContextMenuChild.getTarget(this.mm, msg, "plugin"), true);
             break;
           case "hide":
-            this.hideClickToPlayOverlay(contextMenu.getTarget(msg, "plugin"));
+            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,
@@ -80,61 +78,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 +171,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 +220,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 +331,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 +361,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 +388,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 +531,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 +557,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 +595,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 +629,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 +703,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 +761,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 +844,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 +903,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 +947,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 +987,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 +1046,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 +1086,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:///actors/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',