Bug 558184 - Part 8 - Load js plugins in a separate process. r=billm.
authorPeter Van der Beken <peterv@propagandism.org>
Mon, 29 May 2017 12:38:46 +0200
changeset 412275 b31942dbee848f90041776eb0da2867b8ae9f59e
parent 412274 e69bf3dbf73c58fb3e85ab95d8cb18a70aad6914
child 412276 d04876d5e3c6859b0224f36b8f7e1090cba3d56e
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs558184
milestone55.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 558184 - Part 8 - Load js plugins in a separate process. r=billm. Every JS plugin is assigned a unique ID. When an instance of a JS plugin is created the frame loader that we use to load the plugin's handler URI will create a special TabContext. This TabContext causes the ContentParent to use the process for this specific JS plugin (creating one if it hasn't already) when it creates the PBrowser actors. This causes the iframes for all the instances of a specific JS plugin to be grouped in the same process.
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/base/nsObjectLoadingContent.cpp
dom/base/nsObjectLoadingContent.h
dom/events/EventStateManager.cpp
dom/ipc/ContentBridgeParent.cpp
dom/ipc/ContentBridgeParent.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/ipc/PTabContext.ipdlh
dom/ipc/TabContext.cpp
dom/ipc/TabContext.h
dom/ipc/nsIContentParent.h
dom/plugins/base/nsIPluginTag.idl
dom/plugins/base/nsPluginTags.cpp
dom/plugins/base/nsPluginTags.h
layout/ipc/RenderFrameParent.cpp
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -151,22 +151,24 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameL
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable)
 NS_INTERFACE_MAP_END
 
-nsFrameLoader::nsFrameLoader(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated)
+nsFrameLoader::nsFrameLoader(Element* aOwner, nsPIDOMWindowOuter* aOpener,
+                             bool aNetworkCreated, int32_t aJSPluginID)
   : mOwnerContent(aOwner)
   , mDetachedSubdocFrame(nullptr)
   , mOpener(aOpener)
   , mRemoteBrowser(nullptr)
   , mChildID(0)
+  , mJSPluginID(aJSPluginID)
   , mEventMode(EVENT_MODE_NORMAL_DISPATCH)
   , mBrowserChangingProcessBlockers(nullptr)
   , mIsPrerendered(false)
   , mDepthTooGreat(false)
   , mIsTopLevelContent(false)
   , mDestroyCalled(false)
   , mNeedsAsyncDestroy(false)
   , mInSwap(false)
@@ -188,17 +190,18 @@ nsFrameLoader::~nsFrameLoader()
 {
   if (mMessageManager) {
     mMessageManager->Disconnect();
   }
   MOZ_RELEASE_ASSERT(mDestroyCalled);
 }
 
 nsFrameLoader*
-nsFrameLoader::Create(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated)
+nsFrameLoader::Create(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated,
+                      int32_t aJSPluginId)
 {
   NS_ENSURE_TRUE(aOwner, nullptr);
   nsIDocument* doc = aOwner->OwnerDoc();
 
   // We never create nsFrameLoaders for elements in resource documents.
   //
   // We never create nsFrameLoaders for elements in data documents, unless the
   // document is a static document.
@@ -218,17 +221,17 @@ nsFrameLoader::Create(Element* aOwner, n
   // since for a static document we know aOwner will end up in a document and
   // the nsFrameLoader will be used for its docShell.)
   //
   NS_ENSURE_TRUE(!doc->IsResourceDoc() &&
                  ((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
                   doc->IsStaticDocument()),
                  nullptr);
 
-  return new nsFrameLoader(aOwner, aOpener, aNetworkCreated);
+  return new nsFrameLoader(aOwner, aOpener, aNetworkCreated, aJSPluginId);
 }
 
 NS_IMETHODIMP
 nsFrameLoader::LoadFrame()
 {
   NS_ENSURE_TRUE(mOwnerContent, NS_ERROR_NOT_INITIALIZED);
 
   nsAutoString src;
@@ -309,18 +312,25 @@ NS_IMETHODIMP
 nsFrameLoader::LoadURI(nsIURI* aURI)
 {
   if (!aURI)
     return NS_ERROR_INVALID_POINTER;
   NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent);
 
   nsCOMPtr<nsIDocument> doc = mOwnerContent->OwnerDoc();
 
-  nsresult rv = CheckURILoad(aURI);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsresult rv;
+  // If IsForJSPlugin() returns true then we want to allow the load. We're just
+  // loading the source for the implementation of the JS plugin from a URI
+  // that's under our control. We will already have done the security checks for
+  // loading the plugin content itself in the object/embed loading code.
+  if (!IsForJSPlugin()) {
+    rv = CheckURILoad(aURI);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   mURIToLoad = aURI;
   rv = doc->InitializeFrameLoader(this);
   if (NS_FAILED(rv)) {
     mURIToLoad = nullptr;
   }
   return rv;
 }
@@ -2241,16 +2251,20 @@ nsFrameLoader::OwnerIsIsolatedMozBrowser
   }
 
   return false;
 }
 
 bool
 nsFrameLoader::ShouldUseRemoteProcess()
 {
+  if (IsForJSPlugin()) {
+    return true;
+  }
+
   if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
       Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
     return false;
   }
 
   // Don't try to launch nested children if we don't have OMTC.
   // They won't render!
   if (XRE_IsContentProcess() &&
@@ -2905,17 +2919,19 @@ nsFrameLoader::TryRemoteBrowser()
 
   if (openingTab &&
       openingTab->Manager() &&
       openingTab->Manager()->IsContentParent()) {
     openerContentParent = openingTab->Manager()->AsContentParent();
   }
 
   // <iframe mozbrowser> gets to skip these checks.
-  if (!OwnerIsMozBrowserFrame()) {
+  // iframes for JS plugins also get to skip these checks. We control the URL that gets
+  // loaded, but the load is triggered from the document containing the plugin.
+  if (!OwnerIsMozBrowserFrame() && !IsForJSPlugin()) {
     if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
       // Allow about:addon an exception to this rule so it can load remote
       // extension options pages.
       //
       // Note that the new frame's message manager will not be a child of the
       // chrome window message manager, and, the values of window.top and
       // window.parent will be different than they would be for a non-remote
       // frame.
@@ -3619,16 +3635,21 @@ nsFrameLoader::MaybeUpdatePrimaryTabPare
     }
   }
 }
 
 nsresult
 nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
                                 nsIURI* aURI)
 {
+  if (IsForJSPlugin()) {
+    return aTabContext->SetTabContextForJSPluginFrame(mJSPluginID) ? NS_OK :
+           NS_ERROR_FAILURE;
+  }
+
   OriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
   nsresult rv;
 
   attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
 
   // set the userContextId on the attrs before we pass them into
   // the tab context
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -21,16 +21,17 @@
 #include "nsFrameMessageManager.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/Attributes.h"
 #include "nsStubMutationObserver.h"
 #include "Units.h"
 #include "nsIWebBrowserPersistable.h"
 #include "nsIFrame.h"
 #include "nsIGroupedSHistory.h"
+#include "nsPluginTags.h"
 
 class nsIURI;
 class nsSubDocumentFrame;
 class nsView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 class AutoResetInFrameSwap;
 class nsITabParent;
@@ -74,17 +75,18 @@ class nsFrameLoader final : public nsIFr
   friend class RequestGroupedHistoryNavigationHelper;
   typedef mozilla::dom::PBrowserParent PBrowserParent;
   typedef mozilla::dom::TabParent TabParent;
   typedef mozilla::layout::RenderFrameParent RenderFrameParent;
 
 public:
   static nsFrameLoader* Create(mozilla::dom::Element* aOwner,
                                nsPIDOMWindowOuter* aOpener,
-                               bool aNetworkCreated);
+                               bool aNetworkCreated,
+                               int32_t aJSPluginID = nsFakePluginTag::NOT_JSPLUGIN);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsFrameLoader, nsIFrameLoader)
   NS_DECL_NSIFRAMELOADER
   NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
   NS_DECL_NSIWEBBROWSERPERSISTABLE
   nsresult CheckForRecursiveLoad(nsIURI* aURI);
   nsresult ReallyStartLoading();
@@ -225,28 +227,34 @@ public:
 
   // public because a callback needs these.
   RefPtr<nsFrameMessageManager> mMessageManager;
   nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager;
 
 private:
   nsFrameLoader(mozilla::dom::Element* aOwner,
                 nsPIDOMWindowOuter* aOpener,
-                bool aNetworkCreated);
+                bool aNetworkCreated,
+                int32_t aJSPluginID);
   ~nsFrameLoader();
 
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
   bool ShouldUseRemoteProcess();
 
   /**
    * Return true if the frame is a remote frame. Return false otherwise
    */
   bool IsRemoteFrame();
 
+  bool IsForJSPlugin()
+  {
+    return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN;
+  }
+
   /**
    * Is this a frame loader for a bona fide <iframe mozbrowser>?
    * <xul:browser> is not a mozbrowser, so this is false for that case.
    */
   bool OwnerIsMozBrowserFrame();
 
   /**
    * Is this a frame loader for an isolated <iframe mozbrowser>?
@@ -338,16 +346,18 @@ private:
   nsCOMPtr<nsIDocument> mContainerDocWhileDetached;
 
   // An opener window which should be used when the docshell is created.
   nsCOMPtr<nsPIDOMWindowOuter> mOpener;
 
   TabParent* mRemoteBrowser;
   uint64_t mChildID;
 
+  int32_t mJSPluginID;
+
   // See nsIFrameLoader.idl. EVENT_MODE_NORMAL_DISPATCH automatically
   // forwards some input events to out-of-process content.
   uint32_t mEventMode;
 
   // Holds the last known size of the frame.
   mozilla::ScreenIntSize mLazySize;
 
   nsCOMPtr<nsIPartialSHistory> mPartialSHistory;
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -551,29 +551,38 @@ nsObjectLoadingContent::MakePluginListen
   NS_ENSURE_SUCCESS(rv, false);
   rv = pluginHost->NewPluginStreamListener(mURI, inst,
                                            getter_AddRefs(finalListener));
   NS_ENSURE_SUCCESS(rv, false);
   mFinalListener = finalListener;
   return true;
 }
 
-// Helper to spawn the frameloader and return a pointer to its docshell
-already_AddRefed<nsIDocShell>
-nsObjectLoadingContent::SetupFrameLoader(nsIURI *aRecursionCheckURI)
+// Helper to spawn the frameloader.
+void
+nsObjectLoadingContent::SetupFrameLoader(int32_t aJSPluginId)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
 
   mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(),
                                        /* aOpener = */ nullptr,
-                                       mNetworkCreated);
+                                       mNetworkCreated, aJSPluginId);
   if (!mFrameLoader) {
     NS_NOTREACHED("nsFrameLoader::Create failed");
+  }
+}
+
+// Helper to spawn the frameloader and return a pointer to its docshell.
+already_AddRefed<nsIDocShell>
+nsObjectLoadingContent::SetupDocShell(nsIURI* aRecursionCheckURI)
+{
+  SetupFrameLoader(nsFakePluginTag::NOT_JSPLUGIN);
+  if (!mFrameLoader) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShell> docShell;
 
   if (aRecursionCheckURI) {
     nsresult rv = mFrameLoader->CheckForRecursiveLoad(aRecursionCheckURI);
     if (NS_SUCCEEDED(rv)) {
@@ -2373,56 +2382,49 @@ nsObjectLoadingContent::LoadObject(bool 
         CloseChannel();
       }
 
       /// XXX(johns) Bug FIXME - We need to cleanup the various plugintag
       ///            classes to be more sane and avoid this dance
       nsCOMPtr<nsIPluginTag> basetag =
         nsContentUtils::PluginTagForType(mContentType, false);
       nsCOMPtr<nsIFakePluginTag> tag = do_QueryInterface(basetag);
+
+      uint32_t id;
+      if (NS_FAILED(tag->GetId(&id))) {
+        rv = NS_ERROR_FAILURE;
+        break;
+      }
+
+      MOZ_ASSERT(id <= PR_INT32_MAX,
+                 "Something went wrong, nsPluginHost::RegisterFakePlugin shouldn't have "
+                 "given out this id.");
+
+      SetupFrameLoader(int32_t(id));
+      if (!mFrameLoader) {
+        rv = NS_ERROR_FAILURE;
+        break;
+      }
+
       nsCOMPtr<nsIURI> handlerURI;
       if (tag) {
         tag->GetHandlerURI(getter_AddRefs(handlerURI));
       }
 
       if (!handlerURI) {
         NS_NOTREACHED("Selected type is not a proper fake plugin handler");
         rv = NS_ERROR_FAILURE;
         break;
       }
 
-      nsCOMPtr<nsIDocShell> docShell = SetupFrameLoader(handlerURI);
-      if (!docShell) {
-        rv = NS_ERROR_FAILURE;
-        break;
-      }
-
       nsCString spec;
       handlerURI->GetSpec(spec);
       LOG(("OBJLC [%p]: Loading fake plugin handler (%s)", this, spec.get()));
 
-      // XXX(johns): This and moreso the document case below are
-      //             sidestepping/duplicating nsFrameLoader's LoadURI code,
-      //             which is not great.
-
-      nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
-      docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
-      if (loadInfo) {
-        loadInfo->SetTriggeringPrincipal(thisContent->NodePrincipal());
-        nsCOMPtr<nsIURI> referrer;
-        thisContent->NodePrincipal()->GetURI(getter_AddRefs(referrer));
-        loadInfo->SetReferrer(referrer);
-
-        rv = docShell->LoadURI(handlerURI, loadInfo,
-                               nsIWebNavigation::LOAD_FLAGS_NONE, false);
-      } else {
-        NS_NOTREACHED("CreateLoadInfo failed");
-        rv = NS_ERROR_FAILURE;
-      }
-
+      rv = mFrameLoader->LoadURI(handlerURI);
       if (NS_FAILED(rv)) {
         LOG(("OBJLC [%p]: LoadURI() failed for fake handler", this));
         mFrameLoader->Destroy();
         mFrameLoader = nullptr;
       }
     }
     break;
     case eType_Document:
@@ -2430,17 +2432,17 @@ nsObjectLoadingContent::LoadObject(bool 
       if (!mChannel) {
         // We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters
         // requires documents have a channel, so this is not a valid state.
         NS_NOTREACHED("Attempting to load a document without a channel");
         rv = NS_ERROR_FAILURE;
         break;
       }
 
-      nsCOMPtr<nsIDocShell> docShell = SetupFrameLoader(mURI);
+      nsCOMPtr<nsIDocShell> docShell = SetupDocShell(mURI);
       if (!docShell) {
         rv = NS_ERROR_FAILURE;
         break;
       }
 
       // We're loading a document, so we have to set LOAD_DOCUMENT_URI
       // (especially important for firing onload)
       nsLoadFlags flags = 0;
@@ -3117,26 +3119,33 @@ nsObjectLoadingContent::DoStopPlugin(nsP
   // instance we are about to destroy. We prevent that with the mIsStopping
   // flag.
   if (mIsStopping) {
     return;
   }
   mIsStopping = true;
 
   RefPtr<nsPluginInstanceOwner> kungFuDeathGrip(aInstanceOwner);
-  RefPtr<nsNPAPIPluginInstance> inst;
-  aInstanceOwner->GetInstance(getter_AddRefs(inst));
-  if (inst) {
+  if (mType == eType_FakePlugin) {
+    if (mFrameLoader) {
+      mFrameLoader->Destroy();
+      mFrameLoader = nullptr;
+    }
+  } else {
+    RefPtr<nsNPAPIPluginInstance> inst;
+    aInstanceOwner->GetInstance(getter_AddRefs(inst));
+    if (inst) {
 #if defined(XP_MACOSX)
-    aInstanceOwner->HidePluginWindow();
+      aInstanceOwner->HidePluginWindow();
 #endif
 
-    RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
-    NS_ASSERTION(pluginHost, "No plugin host?");
-    pluginHost->StopPluginInstance(inst);
+      RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+      NS_ASSERTION(pluginHost, "No plugin host?");
+      pluginHost->StopPluginInstance(inst);
+    }
   }
 
   aInstanceOwner->Destroy();
 
   // If we re-enter in plugin teardown UnloadObject will tear down the
   // protochain -- the current protochain could be from a new, unrelated, load.
   if (!mIsStopping) {
     LOG(("OBJLC [%p]: Re-entered in plugin teardown", this));
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -526,23 +526,25 @@ class nsObjectLoadingContent : public ns
     bool CheckProcessPolicy(int16_t *aContentPolicy);
 
     /**
      * Gets the plugin instance and creates a plugin stream listener, assigning
      * it to mFinalListener
      */
     bool MakePluginListener();
 
+    void SetupFrameLoader(int32_t aJSPluginId);
+
     /**
      * Helper to spawn mFrameLoader and return a pointer to its docshell
      *
      * @param aURI URI we intend to load for the recursive load check (does not
      *             actually load anything)
      */
-    already_AddRefed<nsIDocShell> SetupFrameLoader(nsIURI *aRecursionCheckURI);
+    already_AddRefed<nsIDocShell> SetupDocShell(nsIURI* aRecursionCheckURI);
 
     /**
      * Unloads all content and resets the object to a completely unloaded state
      *
      * NOTE Calls StopPluginInstance() and may spin the event loop
      *
      * @param aResetState Reset the object type to 'loading' and destroy channel
      *                    as well
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -90,16 +90,17 @@
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/HTMLLabelElement.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "GeckoProfiler.h"
 #include "Units.h"
 #include "mozilla/layers/APZCTreeManager.h"
+#include "nsIObjectLoadingContent.h"
 
 #ifdef XP_MACOSX
 #import <ApplicationServices/ApplicationServices.h>
 #endif
 
 namespace mozilla {
 
 using namespace dom;
@@ -1277,35 +1278,19 @@ EventStateManager::DispatchCrossProcessE
   }
   default: {
     MOZ_CRASH("Attempt to send non-whitelisted event?");
   }
   }
 }
 
 bool
-EventStateManager::IsRemoteTarget(nsIContent* target) {
-  if (!target) {
-    return false;
-  }
-
-  // <browser/iframe remote=true> from XUL
-  if (target->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::iframe) &&
-      target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
-                          nsGkAtoms::_true, eIgnoreCase)) {
-    return true;
-  }
-
-  // <frame/iframe mozbrowser>
-  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target);
-  if (browserFrame && browserFrame->GetReallyIsBrowser()) {
-    return !!TabParent::GetFrom(target);
-  }
-
-  return false;
+EventStateManager::IsRemoteTarget(nsIContent* target)
+{
+  return !!TabParent::GetFrom(target);
 }
 
 bool
 EventStateManager::HandleCrossProcessEvent(WidgetEvent* aEvent,
                                            nsEventStatus *aStatus) {
   if (*aStatus == nsEventStatus_eConsumeNoDefault ||
       !aEvent->CanBeSentToRemoteProcess()) {
     return false;
--- a/dom/ipc/ContentBridgeParent.cpp
+++ b/dom/ipc/ContentBridgeParent.cpp
@@ -17,16 +17,17 @@ using namespace mozilla::jsipc;
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS(ContentBridgeParent,
                   nsIContentParent,
                   nsIObserver)
 
 ContentBridgeParent::ContentBridgeParent()
+  : mIsForJSPlugin(false)
 {}
 
 ContentBridgeParent::~ContentBridgeParent()
 {
 }
 
 void
 ContentBridgeParent::ActorDestroy(ActorDestroyReason aWhy)
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -57,16 +57,21 @@ public:
   {
     return mIsForBrowser;
   }
   virtual int32_t Pid() const override
   {
     // XXX: do we need this for ContentBridgeParent?
     return -1;
   }
+  virtual bool IsForJSPlugin() const override
+  {
+    return mIsForJSPlugin;
+  }
+
 
   virtual mozilla::ipc::PParentToChildStreamParent*
   SendPParentToChildStreamConstructor(mozilla::ipc::PParentToChildStreamParent*) override;
 
   virtual bool SendActivate(PBrowserParent* aTab) override
   {
     return PContentBridgeParent::SendActivate(aTab);
   }
@@ -89,16 +94,20 @@ protected:
   {
     mChildID = aId;
   }
 
   void SetIsForBrowser(bool aIsForBrowser)
   {
     mIsForBrowser = aIsForBrowser;
   }
+  void SetIsForJSPlugin(bool aIsForJSPlugin)
+  {
+    mIsForJSPlugin = aIsForJSPlugin;
+  }
 
   void Close()
   {
     // Trick NewRunnableMethod
     PContentBridgeParent::Close();
   }
 
 protected:
@@ -159,16 +168,17 @@ protected:
   DeallocPFileDescriptorSetParent(PFileDescriptorSetParent*) override;
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentBridgeParent);
 
 protected: // members
   RefPtr<ContentBridgeParent> mSelfRef;
   ContentParentId mChildID;
   bool mIsForBrowser;
+  bool mIsForJSPlugin;
 
 private:
   friend class ContentParent;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -545,16 +545,17 @@ GetTelemetryProcessID(const nsAString& r
   // For Telemetry though we want to break out collected data from the WebExtensions process into
   // a separate bucket, to make sure we can analyze it separately and avoid skewing normal content
   // process metrics.
   return remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE) ? ProcessID::Extension : ProcessID::Content;
 }
 
 } // anonymous namespace
 
+nsDataHashtable<nsUint32HashKey, ContentParent*>* ContentParent::sJSPluginContentParents;
 nsTArray<ContentParent*>* ContentParent::sPrivateContent;
 StaticAutoPtr<LinkedList<ContentParent> > ContentParent::sContentParents;
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 UniquePtr<SandboxBrokerPolicyFactory> ContentParent::sSandboxBrokerPolicyFactory;
 #endif
 uint64_t ContentParent::sNextTabParentId = 0;
 nsDataHashtable<nsUint64HashKey, TabParent*> ContentParent::sNextTabParents;
 
@@ -875,16 +876,45 @@ ContentParent::GetNewOrUsedBrowserProces
   }
 
   p->Init();
 
   contentParents.AppendElement(p);
   return p.forget();
 }
 
+/*static*/ already_AddRefed<ContentParent>
+ContentParent::GetNewOrUsedJSPluginProcess(uint32_t aPluginID,
+                                           const hal::ProcessPriority& aPriority)
+{
+  RefPtr<ContentParent> p;
+  if (sJSPluginContentParents) {
+    p = sJSPluginContentParents->Get(aPluginID);
+  } else {
+    sJSPluginContentParents =
+      new nsDataHashtable<nsUint32HashKey, ContentParent*>();
+  }
+
+  if (p) {
+    return p.forget();
+  }
+
+  p = new ContentParent(aPluginID);
+
+  if (!p->LaunchSubprocess(aPriority)) {
+    return nullptr;
+  }
+
+  p->Init();
+
+  sJSPluginContentParents->Put(aPluginID, p);
+
+  return p.forget();
+}
+
 /*static*/ ProcessPriority
 ContentParent::GetInitialProcessPriority(Element* aFrameElement)
 {
   // Frames with mozapptype == critical which are expecting a system message
   // get FOREGROUND_HIGH priority.
 
   if (!aFrameElement) {
     return PROCESS_PRIORITY_FOREGROUND;
@@ -935,29 +965,45 @@ ContentParent::RecvCreateChildProcess(co
   MaybeInvalidTabContext tc(aContext);
   if (!tc.IsValid()) {
     NS_ERROR(nsPrintfCString("Received an invalid TabContext from "
                              "the child process. (%s)",
                              tc.GetInvalidReason()).get());
     return IPC_FAIL_NO_REASON(this);
   }
 
-  cp = GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
-                                  aPriority, this);
+  if (tc.GetTabContext().IsJSPlugin()) {
+    cp = GetNewOrUsedJSPluginProcess(tc.GetTabContext().JSPluginId(),
+                                     aPriority);
+  }
+  else {
+    cp = GetNewOrUsedBrowserProcess(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE),
+                                    aPriority, this);
+  }
 
   if (!cp) {
     *aCpId = 0;
     *aIsForBrowser = false;
     return IPC_OK();
   }
 
   *aCpId = cp->ChildID();
   *aIsForBrowser = cp->IsForBrowser();
 
   ContentProcessManager *cpm = ContentProcessManager::GetSingleton();
+  if (cp->IsForJSPlugin()) {
+    // We group all the iframes for a specific JS plugin into one process, regardless of
+    // origin. As a consequence that process can't be a child of the content process that
+    // contains the document with the element loading the plugin. All content processes
+    // need to be able to communicate with the process for the JS plugin.
+    cpm->RegisterRemoteFrame(aTabId, ChildID(), aOpenerTabId, aContext, cp->ChildID());
+    return IPC_OK();
+  }
+
+  // cp was already added to the ContentProcessManager, this just sets the parent ID.
   cpm->AddContentProcess(cp, this->ChildID());
 
   if (cpm->AddGrandchildProcess(this->ChildID(), cp->ChildID()) &&
       cpm->RegisterRemoteFrame(aTabId, ChildID(), aOpenerTabId, aContext, cp->ChildID())) {
     return IPC_OK();
   }
 
   return IPC_FAIL_NO_REASON(this);
@@ -965,37 +1011,32 @@ ContentParent::RecvCreateChildProcess(co
 
 mozilla::ipc::IPCResult
 ContentParent::RecvBridgeToChildProcess(const ContentParentId& aCpId,
                                         Endpoint<PContentBridgeParent>* aEndpoint)
 {
   ContentProcessManager *cpm = ContentProcessManager::GetSingleton();
   ContentParent* cp = cpm->GetContentProcessById(aCpId);
 
-  if (cp) {
-    ContentParentId parentId;
-    if (cpm->GetParentProcessId(cp->ChildID(), &parentId) &&
-      parentId == this->ChildID()) {
-
-      Endpoint<PContentBridgeParent> parent;
-      Endpoint<PContentBridgeChild> child;
-
-      if (NS_FAILED(PContentBridge::CreateEndpoints(OtherPid(), cp->OtherPid(),
-                                                    &parent, &child))) {
-        return IPC_FAIL(this, "CreateEndpoints failed");
-      }
-
-      *aEndpoint = Move(parent);
-
-      if (!cp->SendInitContentBridgeChild(Move(child))) {
-        return IPC_FAIL(this, "SendInitContentBridgeChild failed");
-      }
-
-      return IPC_OK();
+  if (cp && cp->CanCommunicateWith(ChildID())) {
+    Endpoint<PContentBridgeParent> parent;
+    Endpoint<PContentBridgeChild> child;
+
+    if (NS_FAILED(PContentBridge::CreateEndpoints(OtherPid(), cp->OtherPid(),
+                                                  &parent, &child))) {
+      return IPC_FAIL(this, "CreateEndpoints failed");
     }
+
+    *aEndpoint = Move(parent);
+
+    if (!cp->SendInitContentBridgeChild(Move(child))) {
+      return IPC_FAIL(this, "SendInitContentBridgeChild failed");
+    }
+
+    return IPC_OK();
   }
 
   // You can't bridge to a process you didn't open!
   KillHard("BridgeToChildProcess");
   return IPC_FAIL_NO_REASON(this);
 }
 
 static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement)
@@ -1168,25 +1209,31 @@ ContentParent::CreateBrowser(const TabCo
   nsAutoString remoteType;
   if (!aFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType,
                               remoteType)) {
     remoteType.AssignLiteral(DEFAULT_REMOTE_TYPE);
   }
 
   RefPtr<nsIContentParent> constructorSender;
   if (isInContentProcess) {
-    MOZ_ASSERT(aContext.IsMozBrowserElement());
+    MOZ_ASSERT(aContext.IsMozBrowserElement() || aContext.IsJSPlugin());
     constructorSender = CreateContentBridgeParent(aContext, initialPriority,
                                                   openerTabId, tabId);
   } else {
     if (aOpenerContentParent) {
       constructorSender = aOpenerContentParent;
     } else {
-      constructorSender =
-        GetNewOrUsedBrowserProcess(remoteType, initialPriority, nullptr);
+      if (aContext.IsJSPlugin()) {
+        constructorSender =
+          GetNewOrUsedJSPluginProcess(aContext.JSPluginId(),
+                                      initialPriority);
+      } else {
+        constructorSender =
+          GetNewOrUsedBrowserProcess(remoteType, initialPriority, nullptr);
+      }
       if (!constructorSender) {
         return nullptr;
       }
     }
     ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
     cpm->RegisterRemoteFrame(tabId,
                              ContentParentId(0),
                              openerTabId,
@@ -1271,16 +1318,17 @@ ContentParent::CreateContentBridgeParent
   }
   Endpoint<PContentBridgeParent> endpoint;
   if (!child->SendBridgeToChildProcess(cpId, &endpoint)) {
     return nullptr;
   }
   ContentBridgeParent* parent = ContentBridgeParent::Create(Move(endpoint));
   parent->SetChildID(cpId);
   parent->SetIsForBrowser(isForBrowser);
+  parent->SetIsForJSPlugin(aContext.IsJSPlugin());
   return parent;
 }
 
 void
 ContentParent::GetAll(nsTArray<ContentParent*>& aArray)
 {
   aArray.Clear();
 
@@ -1473,17 +1521,25 @@ ContentParent::ShutDownMessageManager()
 
   mMessageManager->Disconnect();
   mMessageManager = nullptr;
 }
 
 void
 ContentParent::MarkAsTroubled()
 {
-  if (sBrowserContentParents) {
+  if (IsForJSPlugin()) {
+    if (sJSPluginContentParents) {
+      sJSPluginContentParents->Remove(mJSPluginID);
+      if (!sJSPluginContentParents->Count()) {
+        delete sJSPluginContentParents;
+        sJSPluginContentParents = nullptr;
+      }
+    }
+  } else if (sBrowserContentParents) {
     nsTArray<ContentParent*>* contentParents =
       sBrowserContentParents->Get(mRemoteType);
     if (contentParents) {
       contentParents->RemoveElement(this);
       if (contentParents->IsEmpty()) {
         sBrowserContentParents->Remove(mRemoteType);
         if (sBrowserContentParents->IsEmpty()) {
           delete sBrowserContentParents;
@@ -1588,48 +1644,50 @@ mozilla::ipc::IPCResult
 ContentParent::RecvAllocateLayerTreeId(const ContentParentId& aCpId,
                                        const TabId& aTabId, uint64_t* aId)
 {
   // Protect against spoofing by a compromised child. aCpId must either
   // correspond to the process that this ContentParent represents or be a
   // child of it.
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
   RefPtr<ContentParent> contentParent = cpm->GetContentProcessById(aCpId);
-  if (ChildID() != aCpId) {
-    ContentParentId parent;
-    if (!cpm->GetParentProcessId(contentParent->ChildID(), &parent) ||
-        ChildID() != parent) {
-      return IPC_FAIL_NO_REASON(this);
-    }
+  if (ChildID() != aCpId && !contentParent->CanCommunicateWith(ChildID())) {
+    return IPC_FAIL_NO_REASON(this);
   }
 
   // GetTopLevelTabParentByProcessAndTabId will make sure that aTabId
   // lives in the process for aCpId.
   RefPtr<TabParent> browserParent =
     cpm->GetTopLevelTabParentByProcessAndTabId(aCpId, aTabId);
   MOZ_ASSERT(contentParent && browserParent);
 
   if (!AllocateLayerTreeId(contentParent, browserParent, aTabId, aId)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvDeallocateLayerTreeId(const uint64_t& aId)
+ContentParent::RecvDeallocateLayerTreeId(const ContentParentId& aCpId,
+                                         const uint64_t& aId)
 {
   GPUProcessManager* gpu = GPUProcessManager::Get();
 
-  if (!gpu->IsLayerTreeIdMapped(aId, OtherPid()))
-  {
+  ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+  RefPtr<ContentParent> contentParent = cpm->GetContentProcessById(aCpId);
+  if (!contentParent->CanCommunicateWith(ChildID())) {
+    return IPC_FAIL(this, "Spoofed DeallocateLayerTreeId call");
+  }
+
+  if (!gpu->IsLayerTreeIdMapped(aId, contentParent->OtherPid())) {
     // You can't deallocate layer tree ids that you didn't allocate
     KillHard("DeallocateLayerTreeId");
   }
 
-  gpu->UnmapLayerTreeId(aId, OtherPid());
+  gpu->UnmapLayerTreeId(aId, contentParent->OtherPid());
 
   return IPC_OK();
 }
 
 namespace {
 
 void
 DelayedDeleteSubprocess(GeckoChildProcessHost* aSubprocess)
@@ -1796,16 +1854,20 @@ ContentParent::ActorDestroy(ActorDestroy
 #if defined(XP_WIN32) && defined(ACCESSIBILITY)
   a11y::AccessibleWrap::ReleaseContentProcessIdFor(ChildID());
 #endif
 }
 
 bool
 ContentParent::ShouldKeepProcessAlive() const
 {
+  if (IsForJSPlugin()) {
+    return true;
+  }
+
   if (!sBrowserContentParents) {
     return false;
   }
 
   // If we have already been marked as troubled/dead, don't prevent shutdown.
   if (!IsAvailable()) {
     return false;
   }
@@ -2028,24 +2090,26 @@ ContentParent::LaunchSubprocess(ProcessP
     cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
     obs->NotifyObservers(static_cast<nsIObserver*>(this), "ipc:content-initializing", cpId.get());
   }
 
   return true;
 }
 
 ContentParent::ContentParent(ContentParent* aOpener,
-                             const nsAString& aRemoteType)
+                             const nsAString& aRemoteType,
+                             int32_t aJSPluginID)
   : nsIContentParent()
   , mSubprocess(nullptr)
   , mLaunchTS(TimeStamp::Now())
   , mOpener(aOpener)
   , mRemoteType(aRemoteType)
   , mChildID(gContentChildID++)
   , mGeolocationWatchID(-1)
+  , mJSPluginID(aJSPluginID)
   , mNumDestroyingTabs(0)
   , mIsAvailable(true)
   , mIsAlive(true)
   , mIsForBrowser(!mRemoteType.IsEmpty())
   , mCalledClose(false)
   , mCalledKillHard(false)
   , mCreatedPairedMinidumps(false)
   , mShutdownPending(false)
@@ -2081,19 +2145,24 @@ ContentParent::~ContentParent()
   if (mForceKillTimer) {
     mForceKillTimer->Cancel();
   }
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // We should be removed from all these lists in ActorDestroy.
   MOZ_ASSERT(!sPrivateContent || !sPrivateContent->Contains(this));
-  MOZ_ASSERT(!sBrowserContentParents ||
-             !sBrowserContentParents->Contains(mRemoteType) ||
-             !sBrowserContentParents->Get(mRemoteType)->Contains(this));
+  if (IsForJSPlugin()) {
+    MOZ_ASSERT(!sJSPluginContentParents ||
+               !sJSPluginContentParents->Get(mJSPluginID));
+  } else {
+    MOZ_ASSERT(!sBrowserContentParents ||
+               !sBrowserContentParents->Contains(mRemoteType) ||
+               !sBrowserContentParents->Get(mRemoteType)->Contains(this));
+  }
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority,
                             bool aSetupOffMainThreadCompositing,
                             bool aSendRegisteredChrome)
 {
   Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
@@ -5207,8 +5276,24 @@ ContentParent::RecvFileCreationRequest(c
   }
 
   if (!SendFileCreationResponse(aID, FileCreationSuccessResult(ipcBlob))) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   return IPC_OK();
 }
+
+bool
+ContentParent::CanCommunicateWith(ContentParentId aOtherProcess)
+{
+  // Normally a process can only communicate with its parent, but a JS plugin process can
+  // communicate with any process.
+  ContentProcessManager *cpm = ContentProcessManager::GetSingleton();
+  ContentParentId parentId;
+  if (!cpm->GetParentProcessId(ChildID(), &parentId)) {
+    return false;
+  }
+  if (IsForJSPlugin()) {
+    return parentId == ContentParentId(0);
+  }
+  return parentId == aOtherProcess;
+}
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -17,16 +17,17 @@
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "nsDataHashtable.h"
+#include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
@@ -173,16 +174,24 @@ public:
    */
   static already_AddRefed<ContentParent>
   GetNewOrUsedBrowserProcess(const nsAString& aRemoteType = NS_LITERAL_STRING(NO_REMOTE_TYPE),
                              hal::ProcessPriority aPriority =
                              hal::ProcessPriority::PROCESS_PRIORITY_FOREGROUND,
                              ContentParent* aOpener = nullptr);
 
   /**
+   * Get or create a content process for a JS plugin. aPluginID is the id of the JS plugin
+   * (@see nsFakePlugin::mId). There is a maximum of one process per JS plugin.
+   */
+  static already_AddRefed<ContentParent>
+  GetNewOrUsedJSPluginProcess(uint32_t aPluginID,
+                              const hal::ProcessPriority& aPriority);
+
+  /**
    * Get or create a content process for the given TabContext.  aFrameElement
    * should be the frame/iframe element with which this process will
    * associated.
    */
   static TabParent*
   CreateBrowser(const TabContext& aContext,
                 Element* aFrameElement,
                 ContentParent* aOpenerContentParent,
@@ -357,16 +366,20 @@ public:
     return mIsAvailable;
   }
   bool IsAlive() const override;
 
   virtual bool IsForBrowser() const override
   {
     return mIsForBrowser;
   }
+  virtual bool IsForJSPlugin() const override
+  {
+    return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN;
+  }
 
   GeckoChildProcessHost* Process() const
   {
     return mSubprocess;
   }
 
   ContentParent* Opener() const
   {
@@ -663,16 +676,17 @@ private:
    * currently available to host *new* tabs/frames of that type.
    *
    * If a content process is identified as troubled or dead, it will be
    * removed from this list, but will still be in the sContentParents list for
    * the GetAll/GetAllEvenIfDead APIs.
    */
   static nsClassHashtable<nsStringHashKey, nsTArray<ContentParent*>>* sBrowserContentParents;
   static nsTArray<ContentParent*>* sPrivateContent;
+  static nsDataHashtable<nsUint32HashKey, ContentParent*> *sJSPluginContentParents;
   static StaticAutoPtr<LinkedList<ContentParent> > sContentParents;
 
   static void JoinProcessesIOThread(const nsTArray<ContentParent*>* aProcesses,
                                     Monitor* aMonitor, bool* aDone);
 
   static hal::ProcessPriority GetInitialProcessPriority(Element* aFrameElement);
 
   static ContentBridgeParent* CreateContentBridgeParent(const TabContext& aContext,
@@ -706,18 +720,27 @@ private:
                      const float& aFullZoom,
                      uint64_t aNextTabParentId,
                      nsresult& aResult,
                      nsCOMPtr<nsITabParent>& aNewTabParent,
                      bool* aWindowIsNew);
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentParent)
 
+  explicit ContentParent(int32_t aPluginID)
+    : ContentParent(nullptr, EmptyString(), aPluginID)
+  {}
   ContentParent(ContentParent* aOpener,
-                const nsAString& aRemoteType);
+                const nsAString& aRemoteType)
+    : ContentParent(aOpener, aRemoteType, nsFakePluginTag::NOT_JSPLUGIN)
+  {}
+
+  ContentParent(ContentParent* aOpener,
+                const nsAString& aRemoteType,
+                int32_t aPluginID);
 
   // Launch the subprocess and associated initialization.
   // Returns false if the process fails to start.
   bool LaunchSubprocess(hal::ProcessPriority aInitialPriority = hal::PROCESS_PRIORITY_FOREGROUND);
 
   // Common initialization after sub process launch or adoption.
   void InitInternal(ProcessPriority aPriority,
                     bool aSetupOffMainThreadCompositing,
@@ -1045,17 +1068,18 @@ private:
                                                   const bool& aInPrivateBrowsing) override;
 
   virtual void ProcessingError(Result aCode, const char* aMsgName) override;
 
   virtual mozilla::ipc::IPCResult RecvAllocateLayerTreeId(const ContentParentId& aCpId,
                                                           const TabId& aTabId,
                                                           uint64_t* aId) override;
 
-  virtual mozilla::ipc::IPCResult RecvDeallocateLayerTreeId(const uint64_t& aId) override;
+  virtual mozilla::ipc::IPCResult RecvDeallocateLayerTreeId(const ContentParentId& aCpId,
+                                                            const uint64_t& aId) override;
 
   virtual mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override;
 
   virtual mozilla::ipc::IPCResult
   RecvBeginDriverCrashGuard(const uint32_t& aGuardType,
                             bool* aOutCrashed) override;
 
   virtual mozilla::ipc::IPCResult RecvEndDriverCrashGuard(const uint32_t& aGuardType) override;
@@ -1148,31 +1172,39 @@ public:
   void SendGetFilesResponseAndForget(const nsID& aID,
                                      const GetFilesResponseResult& aResult);
 
   bool SendRequestMemoryReport(const uint32_t& aGeneration,
                                const bool& aAnonymize,
                                const bool& aMinimizeMemoryUsage,
                                const MaybeFileDesc& aDMDFile) override;
 
+  bool CanCommunicateWith(ContentParentId aOtherProcess);
+
 private:
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
   // details.
 
   GeckoChildProcessHost* mSubprocess;
   const TimeStamp mLaunchTS; // used to calculate time to start content process
   ContentParent* mOpener;
 
   nsString mRemoteType;
 
   ContentParentId mChildID;
   int32_t mGeolocationWatchID;
 
+  // This contains the id for the JS plugin (@see nsFakePluginTag) if this is the
+  // ContentParent for a process containing iframes for that JS plugin.
+  // If this is not a ContentParent for a JS plugin then it contains the value
+  // nsFakePluginTag::NOT_JSPLUGIN.
+  int32_t mJSPluginID;
+
   nsCString mKillHardAnnotation;
 
   // After we initiate shutdown, we also start a timer to ensure
   // that even content processes that are 100% blocked (say from
   // SIGSTOP), are still killed eventually.  This task enforces that
   // timer.
   nsCOMPtr<nsITimer> mForceKillTimer;
   // How many tabs we're waiting to finish their destruction
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -841,17 +841,17 @@ parent:
 
     sync NotifyKeywordSearchLoading(nsString providerName, nsString keyword);
 
     async CopyFavicon(URIParams oldURI, URIParams newURI, Principal aLoadingPrincipal, bool isPrivate);
 
     // Tell the compositor to allocate a layer tree id for nested remote mozbrowsers.
     sync AllocateLayerTreeId(ContentParentId cpId, TabId tabId)
         returns (uint64_t id);
-    async DeallocateLayerTreeId(uint64_t id);
+    async DeallocateLayerTreeId(ContentParentId cpId, uint64_t id);
 
     /**
      * Notifies the parent about a recording device is starting or shutdown.
      * @param recordingStatus starting or shutdown
      * @param pageURL URL that request that changing the recording status
      * @param isAudio recording start with microphone
      * @param isVideo recording start with camera
      */
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -51,16 +51,21 @@ struct FrameIPCTabContext
   // presented content.
   nsString presentationURL;
 
   // Keyboard indicator state inherited from the parent.
   UIStateChangeType showAccelerators;
   UIStateChangeType showFocusRings;
 };
 
+struct JSPluginFrameIPCTabContext
+{
+  uint32_t jsPluginId;
+};
+
 // XXXcatalinb: This is only used by ServiceWorkerClients::OpenWindow.
 // Because service workers don't have an associated TabChild
 // we can't satisfy the security constraints on b2g. As such, the parent
 // process will accept this tab context only on desktop.
 struct UnsafeIPCTabContext
 { };
 
 // IPCTabContext is an analog to mozilla::dom::TabContext.  Both specify an
@@ -69,13 +74,14 @@ struct UnsafeIPCTabContext
 // travel over IPC.
 //
 // We need IPCTabContext (specifically, PopupIPCTabContext) to prevent a
 // privilege escalation attack by a compromised child process.
 union IPCTabContext
 {
   PopupIPCTabContext;
   FrameIPCTabContext;
+  JSPluginFrameIPCTabContext;
   UnsafeIPCTabContext;
 };
 
 }
 }
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -18,17 +18,17 @@ using namespace mozilla::layout;
 
 namespace mozilla {
 namespace dom {
 
 TabContext::TabContext()
   : mIsPrerendered(false)
   , mInitialized(false)
   , mIsMozBrowserElement(false)
-  , mOriginAttributes()
+  , mJSPluginID(-1)
   , mShowAccelerators(UIStateChangeType_NoChange)
   , mShowFocusRings(UIStateChangeType_NoChange)
 {
 }
 
 bool
 TabContext::IsMozBrowserElement() const
 {
@@ -43,16 +43,28 @@ TabContext::IsIsolatedMozBrowserElement(
 
 bool
 TabContext::IsMozBrowser() const
 {
   return IsMozBrowserElement();
 }
 
 bool
+TabContext::IsJSPlugin() const
+{
+  return mJSPluginID >= 0;
+}
+
+int32_t
+TabContext::JSPluginId() const
+{
+  return mJSPluginID;
+}
+
+bool
 TabContext::SetTabContext(const TabContext& aContext)
 {
   NS_ENSURE_FALSE(mInitialized, false);
 
   *this = aContext;
   mInitialized = true;
 
   return true;
@@ -122,32 +134,47 @@ TabContext::SetTabContext(bool aIsMozBro
   mIsPrerendered = aIsPrerendered;
   mOriginAttributes = aOriginAttributes;
   mPresentationURL = aPresentationURL;
   mShowAccelerators = aShowAccelerators;
   mShowFocusRings = aShowFocusRings;
   return true;
 }
 
+bool
+TabContext::SetTabContextForJSPluginFrame(int32_t aJSPluginID)
+{
+  NS_ENSURE_FALSE(mInitialized, false);
+
+  mInitialized = true;
+  mJSPluginID = aJSPluginID;
+  return true;
+}
+
 IPCTabContext
 TabContext::AsIPCTabContext() const
 {
+  if (IsJSPlugin()) {
+    return IPCTabContext(JSPluginFrameIPCTabContext(mJSPluginID));
+  }
+
   return IPCTabContext(FrameIPCTabContext(mOriginAttributes,
                                           mIsMozBrowserElement,
                                           mIsPrerendered,
                                           mPresentationURL,
                                           mShowAccelerators,
                                           mShowFocusRings));
 }
 
 MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
   : mInvalidReason(nullptr)
 {
   bool isMozBrowserElement = false;
   bool isPrerendered = false;
+  int32_t jsPluginId = -1;
   OriginAttributes originAttributes;
   nsAutoString presentationURL;
   UIStateChangeType showAccelerators = UIStateChangeType_NoChange;
   UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
 
   switch(aParams.type()) {
     case IPCTabContext::TPopupIPCTabContext: {
       const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
@@ -190,16 +217,23 @@ MaybeInvalidTabContext::MaybeInvalidTabC
       // (if any).
       //
       // Otherwise, we're a new app window and we inherit from our
       // opener app.
       isMozBrowserElement = ipcContext.isMozBrowserElement();
       originAttributes = context->mOriginAttributes;
       break;
     }
+    case IPCTabContext::TJSPluginFrameIPCTabContext: {
+      const JSPluginFrameIPCTabContext &ipcContext =
+        aParams.get_JSPluginFrameIPCTabContext();
+
+      jsPluginId = ipcContext.jsPluginId();
+      break;
+    }
     case IPCTabContext::TFrameIPCTabContext: {
       const FrameIPCTabContext &ipcContext =
         aParams.get_FrameIPCTabContext();
 
       isMozBrowserElement = ipcContext.isMozBrowserElement();
       isPrerendered = ipcContext.isPrerendered();
       presentationURL = ipcContext.presentationURL();
       showAccelerators = ipcContext.showAccelerators();
@@ -224,22 +258,26 @@ MaybeInvalidTabContext::MaybeInvalidTabC
       break;
     }
     default: {
       MOZ_CRASH();
     }
   }
 
   bool rv;
-  rv = mTabContext.SetTabContext(isMozBrowserElement,
-                                 isPrerendered,
-                                 showAccelerators,
-                                 showFocusRings,
-                                 originAttributes,
-                                 presentationURL);
+  if (jsPluginId >= 0) {
+    rv = mTabContext.SetTabContextForJSPluginFrame(jsPluginId);
+  } else {
+    rv = mTabContext.SetTabContext(isMozBrowserElement,
+                                   isPrerendered,
+                                   showAccelerators,
+                                   showFocusRings,
+                                   originAttributes,
+                                   presentationURL);
+  }
   if (!rv) {
     mInvalidReason = "Couldn't initialize TabContext.";
   }
 }
 
 bool
 MaybeInvalidTabContext::IsValid()
 {
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -58,16 +58,19 @@ public:
 
   /**
    * Does this TabContext correspond to a mozbrowser?  This is equivalent to
    * IsMozBrowserElement().  Returns false for <xul:browser>, which isn't a
    * mozbrowser.
    */
   bool IsMozBrowser() const;
 
+  bool IsJSPlugin() const;
+  int32_t JSPluginId() const;
+
   /**
    * OriginAttributesRef() returns the OriginAttributes of this frame to
    * the caller. This is used to store any attribute associated with the frame's
    * docshell.
    */
   const OriginAttributes& OriginAttributesRef() const;
 
   /**
@@ -119,30 +122,40 @@ protected:
    */
   bool UpdateTabContextAfterSwap(const TabContext& aContext);
 
   /**
    * Whether this TabContext is in prerender mode.
    */
   bool mIsPrerendered;
 
+  /**
+   * Set this TabContext to be for a JS plugin. aPluginID is the id of the JS plugin
+   * (@see nsFakePlugin::mId).
+   * As with the other protected mutator methods, this lets you modify a TabContext once.
+   * (@see TabContext::SetTabContext above for more details).
+   */
+  bool SetTabContextForJSPluginFrame(int32_t aJSPluginID);
+
 private:
   /**
    * Has this TabContext been initialized?  If so, mutator methods will fail.
    */
   bool mInitialized;
 
   /**
    * Whether this TabContext corresponds to a mozbrowser.
    *
    * <iframe mozbrowser> and <xul:browser> are not considered to be
    * mozbrowser elements.
    */
   bool mIsMozBrowserElement;
 
+  int32_t mJSPluginID;
+
   /**
    * OriginAttributes of the top level tab docShell
    */
   OriginAttributes mOriginAttributes;
 
   /**
    * The requested presentation URL.
    */
@@ -178,16 +191,22 @@ public:
   {
     return TabContext::SetTabContext(aIsMozBrowserElement,
                                      aIsPrerendered,
                                      aShowAccelerators,
                                      aShowFocusRings,
                                      aOriginAttributes,
                                      aPresentationURL);
   }
+
+  bool SetTabContextForJSPluginFrame(uint32_t aJSPluginID)
+  {
+    return TabContext::SetTabContextForJSPluginFrame(aJSPluginID);
+  }
+
 };
 
 /**
  * MaybeInvalidTabContext is a simple class that lets you transform an
  * IPCTabContext into a TabContext.
  *
  * The issue is that an IPCTabContext is not necessarily valid.  So to convert
  * an IPCTabContext into a TabContext, you construct a MaybeInvalidTabContext,
--- a/dom/ipc/nsIContentParent.h
+++ b/dom/ipc/nsIContentParent.h
@@ -56,16 +56,17 @@ class nsIContentParent : public nsISuppo
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTPARENT_IID)
 
   nsIContentParent();
 
   virtual ContentParentId ChildID() const = 0;
   virtual bool IsForBrowser() const = 0;
+  virtual bool IsForJSPlugin() const = 0;
 
   virtual mozilla::ipc::PIPCBlobInputStreamParent*
   SendPIPCBlobInputStreamConstructor(mozilla::ipc::PIPCBlobInputStreamParent* aActor,
                                      const nsID& aID,
                                      const uint64_t& aSize) = 0;
 
   MOZ_MUST_USE virtual PBrowserParent*
   SendPBrowserConstructor(PBrowserParent* actor,
--- a/dom/plugins/base/nsIPluginTag.idl
+++ b/dom/plugins/base/nsIPluginTag.idl
@@ -68,9 +68,14 @@ interface nsIPluginTag : nsISupports
  */
 [scriptable, uuid(6d22c968-226d-4156-b230-da6ad6bbf6e8)]
 interface nsIFakePluginTag : nsIPluginTag
 {
   // The URI that should be loaded into the tag (as a frame) to handle the
   // plugin. Note that the original data/src value for the plugin is not loaded
   // and will need to be requested by the handler via XHR or similar if desired.
   readonly attribute nsIURI handlerURI;
+
+  /**
+   * A unique id for this JS-implemented plugin. 0 is a valid id.
+   */
+  readonly attribute unsigned long id;
 };
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -859,16 +859,17 @@ NS_INTERFACE_TABLE_HEAD(nsFakePluginTag)
   NS_INTERFACE_TABLE_END
 NS_INTERFACE_TABLE_TAIL
 
 /* static */
 nsresult
 nsFakePluginTag::Create(const FakePluginTagInit& aInitDictionary,
                         nsFakePluginTag** aPluginTag)
 {
+  NS_ENSURE_TRUE(sNextId <= PR_INT32_MAX, NS_ERROR_OUT_OF_MEMORY);
   NS_ENSURE_TRUE(!aInitDictionary.mMimeEntries.IsEmpty(), NS_ERROR_INVALID_ARG);
 
   RefPtr<nsFakePluginTag> tag = new nsFakePluginTag();
   nsresult rv = NS_NewURI(getter_AddRefs(tag->mHandlerURI),
                           aInitDictionary.mHandlerURI);
   NS_ENSURE_SUCCESS(rv, rv);
 
   CopyUTF16toUTF8(aInitDictionary.mNiceName, tag->mNiceName);
@@ -1054,8 +1055,15 @@ nsFakePluginTag::GetLastModifiedTime(PRT
 
 // We don't load fake plugins out of a library, so they should always be there.
 NS_IMETHODIMP
 nsFakePluginTag::GetLoaded(bool* ret)
 {
   *ret = true;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsFakePluginTag::GetId(uint32_t* aId)
+{
+  *aId = mId;
+  return NS_OK;
+}
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -224,24 +224,27 @@ public:
   const nsCString& GetNiceFileName() override;
 
   bool HandlerURIMatches(nsIURI* aURI);
 
   nsIURI* HandlerURI() const { return mHandlerURI; }
 
   uint32_t Id() const { return mId; }
 
+  static const int32_t NOT_JSPLUGIN = -1;
+
 private:
   nsFakePluginTag();
   virtual ~nsFakePluginTag();
 
   // A unique id for this JS-implemented plugin. Registering a plugin through
   // nsPluginHost::RegisterFakePlugin assigns a new id. The id is transferred
   // through IPC when getting the list of JS-implemented plugins from child
   // processes, so it should be consistent across processes.
+  // 0 is a valid id.
   uint32_t      mId;
 
   // The URI of the handler for our fake plugin.
   // FIXME-jsplugins do we need to sanity check these?
   nsCOMPtr<nsIURI>    mHandlerURI;
 
   nsCString     mFullPath;
   nsCString     mNiceName;
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -240,17 +240,18 @@ RenderFrameParent::OwnerContentChanged(n
 
 void
 RenderFrameParent::ActorDestroy(ActorDestroyReason why)
 {
   if (mLayersId != 0) {
     if (XRE_IsParentProcess()) {
       GPUProcessManager::Get()->UnmapLayerTreeId(mLayersId, OtherPid());
     } else if (XRE_IsContentProcess()) {
-      ContentChild::GetSingleton()->SendDeallocateLayerTreeId(mLayersId);
+      TabParent* browser = TabParent::GetFrom(mFrameLoader);
+      ContentChild::GetSingleton()->SendDeallocateLayerTreeId(browser->Manager()->ChildID(), mLayersId);
     }
   }
 
   mFrameLoader = nullptr;
   mLayerManager = nullptr;
 }
 
 mozilla::ipc::IPCResult