Bug 1242644 - HTML swapFrameLoaders. r=bz
authorJ. Ryan Stinnett <jryans@gmail.com>
Fri, 08 Jan 2016 18:11:58 -0600
changeset 291165 0d7b5a4fbffa4df34440e19c04e5628060315576
parent 291164 18963f8a2c46048c8c45f330799af71b118e34c6
child 291166 ba007e9d5ef1195d196c171859ce911e8cf8604f
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1242644
milestone48.0a1
Bug 1242644 - HTML swapFrameLoaders. r=bz Adds swapFrameLoaders for HTML frames. It is also possible to swap frame loaders between XUL and HTML frames. MozReview-Commit-ID: 43JeiBuMcOL
dom/base/nsFrameLoader.cpp
dom/base/nsIFrameLoader.idl
dom/base/nsObjectLoadingContent.cpp
dom/base/nsObjectLoadingContent.h
dom/base/test/chrome/chrome.ini
dom/base/test/chrome/test_swapFrameLoaders.xul
dom/base/test/chrome/window_swapFrameLoaders.xul
dom/html/nsGenericHTMLFrameElement.cpp
dom/html/nsGenericHTMLFrameElement.h
dom/webidl/XULElement.webidl
dom/xul/nsXULElement.cpp
dom/xul/nsXULElement.h
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -894,16 +894,39 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   }
 
   nsIPresShell* ourShell = ourDoc->GetShell();
   nsIPresShell* otherShell = otherDoc->GetShell();
   if (!ourShell || !otherShell) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  if (mRemoteBrowser->IsIsolatedMozBrowserElement() !=
+      aOther->mRemoteBrowser->IsIsolatedMozBrowserElement() ||
+      mRemoteBrowser->HasOwnApp() != aOther->mRemoteBrowser->HasOwnApp()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  if (mRemoteBrowser->OriginAttributesRef() !=
+      aOther->mRemoteBrowser->OriginAttributesRef()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  bool ourHasHistory =
+    mIsTopLevelContent &&
+    ourContent->IsXULElement(nsGkAtoms::browser) &&
+    !ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
+  bool otherHasHistory =
+    aOther->mIsTopLevelContent &&
+    otherContent->IsXULElement(nsGkAtoms::browser) &&
+    !otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
+  if (ourHasHistory != otherHasHistory) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   if (mInSwap || aOther->mInSwap) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   mInSwap = aOther->mInSwap = true;
 
   nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
   nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
   if (!ourFrame || !otherFrame) {
@@ -1046,33 +1069,72 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
                                    RefPtr<nsFrameLoader>& aFirstToSwap,
                                    RefPtr<nsFrameLoader>& aSecondToSwap)
 {
   NS_PRECONDITION((aFirstToSwap == this && aSecondToSwap == aOther) ||
                   (aFirstToSwap == aOther && aSecondToSwap == this),
                   "Swapping some sort of random loaders?");
   NS_ENSURE_STATE(!mInShow && !aOther->mInShow);
 
-  if (IsRemoteFrame() && aOther->IsRemoteFrame()) {
-    return SwapWithOtherRemoteLoader(aOther, aFirstToSwap, aSecondToSwap);
-  }
-
-  if (IsRemoteFrame() || aOther->IsRemoteFrame()) {
+  if (IsRemoteFrame() != aOther->IsRemoteFrame()) {
     NS_WARNING("Swapping remote and non-remote frames is not currently supported");
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   Element* ourContent = mOwnerContent;
   Element* otherContent = aOther->mOwnerContent;
 
   if (!ourContent || !otherContent) {
     // Can't handle this
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  bool ourHasSrcdoc = ourContent->IsHTMLElement(nsGkAtoms::iframe) &&
+                      ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+  bool otherHasSrcdoc = otherContent->IsHTMLElement(nsGkAtoms::iframe) &&
+                        otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
+  if (ourHasSrcdoc || otherHasSrcdoc) {
+    // Ignore this case entirely for now, since we support XUL <-> HTML swapping
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  bool ourPassPointerEvents =
+    ourContent->AttrValueIs(kNameSpaceID_None,
+                            nsGkAtoms::mozpasspointerevents,
+                            nsGkAtoms::_true,
+                            eCaseMatters);
+  bool otherPassPointerEvents =
+    otherContent->AttrValueIs(kNameSpaceID_None,
+                              nsGkAtoms::mozpasspointerevents,
+                              nsGkAtoms::_true,
+                              eCaseMatters);
+  if (ourPassPointerEvents != otherPassPointerEvents) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  bool ourFullscreenAllowed =
+    ourContent->IsXULElement() ||
+    (OwnerIsMozBrowserOrAppFrame() &&
+      (ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
+       ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
+  bool otherFullscreenAllowed =
+    otherContent->IsXULElement() ||
+    (aOther->OwnerIsMozBrowserOrAppFrame() &&
+      (otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
+       otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
+  if (ourFullscreenAllowed != otherFullscreenAllowed) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  // Divert to a separate path for the remaining steps in the remote case
+  if (IsRemoteFrame()) {
+    MOZ_ASSERT(aOther->IsRemoteFrame());
+    return SwapWithOtherRemoteLoader(aOther, aFirstToSwap, aSecondToSwap);
+  }
+
   // Make sure there are no same-origin issues
   bool equal;
   nsresult rv =
     ourContent->NodePrincipal()->Equals(otherContent->NodePrincipal(), &equal);
   if (NS_FAILED(rv) || !equal) {
     // Security problems loom.  Just bail on it all
     return NS_ERROR_DOM_SECURITY_ERR;
   }
@@ -1198,17 +1260,22 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   nsIPresShell* otherShell = otherDoc->GetShell();
   if (!ourShell || !otherShell) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   if (ourDocshell->GetIsIsolatedMozBrowserElement() !=
       otherDocshell->GetIsIsolatedMozBrowserElement() ||
       ourDocshell->GetIsApp() != otherDocshell->GetIsApp()) {
-      return NS_ERROR_NOT_IMPLEMENTED;
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  if (ourDocshell->GetOriginAttributes() !=
+      otherDocshell->GetOriginAttributes()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   if (mInSwap || aOther->mInSwap) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
   AutoResetInFrameSwap autoFrameSwap(this, aOther, ourDocshell, otherDocshell,
                                      ourEventTarget, otherEventTarget);
 
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -230,21 +230,9 @@ interface nsIFrameLoaderOwner : nsISuppo
    * iframes.
    */
   readonly attribute mozIApplication parentApplication;
 
   /**
    * Puts the FrameLoaderOwner in prerendering mode.
    */
   void setIsPrerendered();
-
-  /**
-   * Swap frame loaders with the given nsIFrameLoaderOwner.  This may
-   * only be posible in a very limited range of circumstances, or
-   * never, depending on the object implementing this interface.
-   *
-   * @throws NS_ERROR_NOT_IMPLEMENTED if the swapping logic is not
-   *   implemented for the two given frame loader owners.
-   * @throws NS_ERROR_DOM_SECURITY_ERR if the swap is not allowed on
-   *   security grounds.
-   */
-  void swapFrameLoaders(in nsIFrameLoaderOwner aOtherOwner);
 };
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1244,22 +1244,16 @@ nsObjectLoadingContent::GetParentApplica
 
 NS_IMETHODIMP
 nsObjectLoadingContent::SetIsPrerendered()
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-nsObjectLoadingContent::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoader)
-{
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
 nsObjectLoadingContent::GetActualType(nsACString& aType)
 {
   aType = mContentType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::GetDisplayedType(uint32_t* aType)
@@ -1661,17 +1655,17 @@ nsObjectLoadingContent::CheckProcessPoli
     return false;
   }
 
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "Must be an instance of content");
 
   nsIDocument* doc = thisContent->OwnerDoc();
-  
+
   int32_t objectType;
   switch (mType) {
     case eType_Image:
       objectType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
       break;
     case eType_Document:
       objectType = nsIContentPolicy::TYPE_DOCUMENT;
       break;
@@ -2928,17 +2922,17 @@ nsObjectLoadingContent::PluginCrashed(ns
 
   PluginDestroyed();
 
   // Switch to fallback/crashed state, notify
   LoadFallback(eFallbackCrashed, true);
 
   // send nsPluginCrashedEvent
 
-  // Note that aPluginTag in invalidated after we're called, so copy 
+  // Note that aPluginTag in invalidated after we're called, so copy
   // out any data we need now.
   nsAutoCString pluginName;
   aPluginTag->GetName(pluginName);
   nsAutoCString pluginFilename;
   aPluginTag->GetFilename(pluginFilename);
 
   nsCOMPtr<nsIRunnable> ev =
     new nsPluginCrashedEvent(thisContent,
@@ -3840,9 +3834,8 @@ nsObjectLoadingContent::SetupProtoChainR
   }
   nsObjectLoadingContent* objectLoadingContent =
     static_cast<nsObjectLoadingContent*>(mContent.get());
   objectLoadingContent->SetupProtoChain(cx, obj);
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(nsObjectLoadingContent::SetupProtoChainRunner, nsIRunnable)
-
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -22,25 +22,26 @@
 #include "nsIRunnable.h"
 #include "nsIThreadInternal.h"
 #include "nsIFrame.h"
 #include "nsIFrameLoader.h"
 
 class nsAsyncInstantiateEvent;
 class nsStopPluginRunnable;
 class AutoSetInstantiatingToFalse;
+class nsFrameLoader;
 class nsPluginFrame;
-class nsFrameLoader;
 class nsXULElement;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
 namespace dom {
 template<typename T> class Sequence;
 struct MozPluginParameter;
+class HTMLIFrameElement;
 } // namespace dom
 } // namespace mozilla
 
 class nsObjectLoadingContent : public nsImageLoadingContent
                              , public nsIStreamListener
                              , public nsIFrameLoaderOwner
                              , public nsIObjectLoadingContent
                              , public nsIChannelEventSink
@@ -202,32 +203,38 @@ class nsObjectLoadingContent : public ns
     bool Activated() const
     {
       return mActivated;
     }
     nsIURI* GetSrcURI() const
     {
       return mURI;
     }
-  
+
     /**
      * The default state that this plugin would be without manual activation.
      * @returns PLUGIN_ACTIVE if the default state would be active.
      */
     uint32_t DefaultFallbackType();
 
     uint32_t PluginFallbackType() const
     {
       return mFallbackType;
     }
     bool HasRunningPlugin() const
     {
       return !!mInstanceOwner;
     }
-    void SwapFrameLoaders(nsXULElement& aOtherOwner, mozilla::ErrorResult& aRv)
+    void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+                          mozilla::ErrorResult& aRv)
+    {
+      aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    }
+    void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+                          mozilla::ErrorResult& aRv)
     {
       aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     }
     void LegacyCall(JSContext* aCx, JS::Handle<JS::Value> aThisVal,
                     const mozilla::dom::Sequence<JS::Value>& aArguments,
                     JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aRv);
 
@@ -395,21 +402,21 @@ class nsObjectLoadingContent : public ns
      * - mURI                 : The final URI, considering mChannel if
      *                          mChannelLoaded is set
      * - mContentType         : The final content type, considering mChannel if
      *                          mChannelLoaded is set
      * - mBaseURI             : The object's base URI, which may be set by the
      *                          object (codebase attribute)
      * - mType                : The type the object is determined to be based
      *                          on the above
-     * 
+     *
      * NOTE The class assumes that mType is the currently loaded type at various
      *      points, so the caller of this function must take the appropriate
      *      actions to ensure this
-     * 
+     *
      * NOTE This function does not perform security checks, only determining the
      *      requested type and parameters of the object.
      *
      * @param aJavaURI Specify that the URI will be consumed by java, which
      *                 changes codebase parsing and URI construction. Used
      *                 internally.
      *
      * @return Returns a bitmask of ParameterUpdateFlags values
@@ -501,17 +508,17 @@ class nsObjectLoadingContent : public ns
     void NotifyStateChanged(ObjectType aOldType,
                             mozilla::EventStates aOldState,
                             bool aSync, bool aNotify);
 
     /**
      * Returns a ObjectType value corresponding to the type of content we would
      * support the given MIME type as, taking capabilities and plugin state
      * into account
-     * 
+     *
      * NOTE this does not consider whether the content would be suppressed by
      *      click-to-play or other content policy checks
      */
     ObjectType GetTypeOfContent(const nsCString& aMIMEType);
 
     /**
      * Gets the frame that's associated with this content node.
      * Does not flush.
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -20,16 +20,17 @@ support-files =
   file_bug1209621.xul
   fileconstructor_file.png
   frame_bug814638.xul
   frame_registerElement_content.html
   registerElement_ep.js
   host_bug814638.xul
   window_nsITextInputProcessor.xul
   title_window.xul
+  window_swapFrameLoaders.xul
 
 [test_bug120684.xul]
 [test_bug206691.xul]
 [test_bug339494.xul]
 [test_bug357450.xul]
 support-files = ../file_bug357450.js
 [test_bug380418.html]
 [test_bug380418.html^headers^]
@@ -66,8 +67,9 @@ skip-if = buildapp == 'mulet'
 [test_registerElement_content.xul]
 [test_registerElement_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_fileconstructor_tempfile.xul]
 [test_nsITextInputProcessor.xul]
 [test_title.xul]
 [test_windowroot.xul]
+[test_swapFrameLoaders.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
+Test swapFrameLoaders with different frame types and remoteness
+-->
+<window title="Mozilla Bug 1242644"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644"
+     target="_blank">Mozilla Bug 1242644</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript"><![CDATA[
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestLongerTimeout(100);
+
+  window.open("window_swapFrameLoaders.xul", "bug1242644",
+              "chrome,width=600,height=600");
+  ]]></script>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -0,0 +1,177 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1242644
+Test swapFrameLoaders with different frame types and remoteness
+-->
+<window title="Mozilla Bug 1242644"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+
+  <script type="application/javascript"><![CDATA[
+  ["SimpleTest", "SpecialPowers", "info", "is"].forEach(key => {
+    window[key] = window.opener[key];
+  })
+  const { interfaces: Ci } = Components;
+
+  const NS = {
+    xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+    html: "http://www.w3.org/1999/xhtml",
+  }
+
+  const TAG = {
+    xul: "browser",
+    html: "iframe", // mozbrowser
+  }
+
+  const SCENARIOS = [
+    ["xul", "xul"],
+    ["xul", "html"],
+    ["html", "xul"],
+    ["html", "html"],
+    ["xul", "xul", "remote"],
+    ["xul", "html", "remote"],
+    ["html", "xul", "remote"],
+    ["html", "html", "remote"],
+  ];
+
+  function once(target, eventName, useCapture = false) {
+    info("Waiting for event: '" + eventName + "' on " + target + ".");
+
+    return new Promise(resolve => {
+      for (let [add, remove] of [
+        ["addEventListener", "removeEventListener"],
+        ["addMessageListener", "removeMessageListener"],
+      ]) {
+        if ((add in target) && (remove in target)) {
+          target[add](eventName, function onEvent(...aArgs) {
+            info("Got event: '" + eventName + "' on " + target + ".");
+            target[remove](eventName, onEvent, useCapture);
+            resolve(aArgs);
+          }, useCapture);
+          break;
+        }
+      }
+    });
+  }
+
+  function* addFrame(type, remote) {
+    let frame = document.createElementNS(NS[type], TAG[type]);
+    frame.setAttribute("remote", remote);
+    if (remote && type == "xul") {
+      frame.setAttribute("style", "-moz-binding: none;");
+    }
+    if (type == "html") {
+      frame.setAttribute("mozbrowser", "true");
+      frame.setAttribute("noisolation", "true");
+      frame.setAttribute("allowfullscreen", "true");
+    } else if (type == "xul") {
+      frame.setAttribute("type", "content");
+    }
+    frame.setAttribute("src", "about:blank");
+    document.documentElement.appendChild(frame);
+    return frame;
+  }
+
+  add_task(function*() {
+    yield new Promise(resolve => {
+      SpecialPowers.pushPrefEnv(
+        { "set": [["dom.mozBrowserFramesEnabled", true]] },
+        resolve);
+    });
+  });
+
+  add_task(function*() {
+    for (let scenario of SCENARIOS) {
+      let [ typeA, typeB, remote ] = scenario;
+      remote = !!remote;
+      info(`Adding frame A, type ${typeA}, remote ${remote}`);
+      let frameA = yield addFrame(typeA, remote);
+
+      info(`Adding frame B, type ${typeB}, remote ${remote}`);
+      let frameB = yield addFrame(typeB, remote);
+
+      let frameScriptFactory = function(name) {
+        return `function() {
+          addMessageListener("ping", function() {
+            sendAsyncMessage("pong", "${name}");
+          });
+        }`;
+      }
+
+      // Load frame script into each frame
+      {
+        let mmA = frameA.frameLoader.messageManager;
+        let mmB = frameB.frameLoader.messageManager;
+
+        mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false);
+        mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false);
+      }
+
+      // Ping before swap
+      {
+        let mmA = frameA.frameLoader.messageManager;
+        let mmB = frameB.frameLoader.messageManager;
+
+        let inflightA = once(mmA, "pong");
+        let inflightB = once(mmB, "pong");
+
+        info("Ping message manager for frame A");
+        mmA.sendAsyncMessage("ping");
+        let [ { data: pongA } ] = yield inflightA;
+        is(pongA, "A", "Frame A message manager gets reply A before swap");
+
+        info("Ping message manager for frame B");
+        mmB.sendAsyncMessage("ping");
+        let [ { data: pongB } ] = yield inflightB;
+        is(pongB, "B", "Frame B message manager gets reply B before swap");
+      }
+
+      // Ping after swap using message managers acquired before
+      {
+        let mmA = frameA.frameLoader.messageManager;
+        let mmB = frameB.frameLoader.messageManager;
+
+        info("swapFrameLoaders");
+        frameA.swapFrameLoaders(frameB);
+
+        let inflightA = once(mmA, "pong");
+        let inflightB = once(mmB, "pong");
+
+        info("Ping message manager for frame A");
+        mmA.sendAsyncMessage("ping");
+        let [ { data: pongA } ] = yield inflightA;
+        is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap");
+
+        info("Ping message manager for frame B");
+        mmB.sendAsyncMessage("ping");
+        let [ { data: pongB } ] = yield inflightB;
+        is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap");
+      }
+
+      // Ping after swap using message managers acquired after
+      {
+        let mmA = frameA.frameLoader.messageManager;
+        let mmB = frameB.frameLoader.messageManager;
+
+        let inflightA = once(mmA, "pong");
+        let inflightB = once(mmB, "pong");
+
+        info("Ping message manager for frame A");
+        mmA.sendAsyncMessage("ping");
+        let [ { data: pongA } ] = yield inflightA;
+        is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap");
+
+        info("Ping message manager for frame B");
+        mmB.sendAsyncMessage("ping");
+        let [ { data: pongB } ] = yield inflightB;
+        is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
+      }
+
+      frameA.remove();
+      frameB.remove();
+    }
+  });
+  ]]></script>
+</window>
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #include "nsGenericHTMLFrameElement.h"
 
 #include "mozilla/dom/BrowserElementAudioChannel.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ErrorResult.h"
 #include "GeckoProfiler.h"
 #include "mozIApplication.h"
 #include "nsAttrValueInlines.h"
 #include "nsContentUtils.h"
 #include "nsIAppsService.h"
 #include "nsIDocShell.h"
@@ -20,16 +21,17 @@
 #include "nsIFrame.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermissionManager.h"
 #include "nsIPresShell.h"
 #include "nsIScrollable.h"
 #include "nsPresContext.h"
 #include "nsServiceManagerUtils.h"
 #include "nsSubDocumentFrame.h"
+#include "nsXULElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
                                                   nsGenericHTMLElement)
@@ -206,21 +208,47 @@ nsGenericHTMLFrameElement::GetParentAppl
   rv = appsService->GetAppByLocalId(appId, aApplication);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsGenericHTMLFrameElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner)
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+                                            ErrorResult& rv)
+{
+  if (&aOtherLoaderOwner == this) {
+    // nothing to do
+    return;
+  }
+
+  SwapFrameLoaders(aOtherLoaderOwner.mFrameLoader, rv);
+}
+
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+                                            ErrorResult& rv)
 {
-  // We don't support this yet
-  return NS_ERROR_NOT_IMPLEMENTED;
+  aOtherLoaderOwner.SwapFrameLoaders(mFrameLoader, rv);
+}
+
+void
+nsGenericHTMLFrameElement::SwapFrameLoaders(RefPtr<nsFrameLoader>& aOtherLoader,
+                                            mozilla::ErrorResult& rv)
+{
+  if (!mFrameLoader || !aOtherLoader) {
+    rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    return;
+  }
+
+  rv = mFrameLoader->SwapWithOtherLoader(aOtherLoader,
+                                         mFrameLoader,
+                                         aOtherLoader);
 }
 
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::SetIsPrerendered()
 {
   MOZ_ASSERT(!mFrameLoader, "Please call SetIsPrerendered before frameLoader is created");
   mIsPrerendered = true;
   return NS_OK;
@@ -707,15 +735,8 @@ nsGenericHTMLFrameElement::AllowCreateFr
 
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::InitializeBrowserAPI()
 {
   MOZ_ASSERT(mFrameLoader);
   InitBrowserElementAPI();
   return NS_OK;
 }
-
-void
-nsGenericHTMLFrameElement::SwapFrameLoaders(nsXULElement& aOtherOwner,
-                                            ErrorResult& aError)
-{
-  aError.Throw(NS_ERROR_NOT_IMPLEMENTED);
-}
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -69,17 +69,24 @@ public:
 
   nsresult CopyInnerTo(mozilla::dom::Element* aDest);
 
   virtual int32_t TabIndexDefault() override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
                                            nsGenericHTMLElement)
 
-  void SwapFrameLoaders(nsXULElement& aOtherOwner, mozilla::ErrorResult& aError);
+  void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+                        mozilla::ErrorResult& aError);
+
+  void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+                        mozilla::ErrorResult& aError);
+
+  void SwapFrameLoaders(RefPtr<nsFrameLoader>& aOtherLoader,
+                        mozilla::ErrorResult& rv);
 
   static bool BrowserFramesEnabled();
 
   /**
    * Helper method to map a HTML 'scrolling' attribute value to a nsIScrollable
    * enum value.  scrolling="no" (and its synonyms) maps to
    * nsIScrollable::Scrollbar_Never, and anything else (including nullptr) maps
    * to nsIScrollable::Scrollbar_Auto.
--- a/dom/webidl/XULElement.webidl
+++ b/dom/webidl/XULElement.webidl
@@ -121,15 +121,18 @@ interface XULElement : Element {
 interface MozFrameLoaderOwner {
   [ChromeOnly]
   readonly attribute MozFrameLoader? frameLoader;
 
   [ChromeOnly]
   void setIsPrerendered();
 
   [ChromeOnly, Throws]
-  void swapFrameLoaders(XULElement aOtherOwner);
+  void swapFrameLoaders(XULElement aOtherLoaderOwner);
+
+  [ChromeOnly, Throws]
+  void swapFrameLoaders(HTMLIFrameElement aOtherLoaderOwner);
 };
 
 XULElement implements GlobalEventHandlers;
 XULElement implements TouchEventHandlers;
 XULElement implements MozFrameLoaderOwner;
 XULElement implements OnErrorEventHandlerForNodes;
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -106,16 +106,17 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIDOMXULCommandEvent.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsICSSDeclaration.h"
 
 #include "mozilla/dom/XULElementBinding.h"
 #include "mozilla/dom/BoxObject.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
 uint32_t             nsXULPrototypeAttribute::gNumElements;
 uint32_t             nsXULPrototypeAttribute::gNumAttributes;
 uint32_t             nsXULPrototypeAttribute::gNumCacheTests;
@@ -1631,51 +1632,61 @@ nsXULElement::GetParentApplication(mozIA
 
 nsresult
 nsXULElement::SetIsPrerendered()
 {
   return SetAttr(kNameSpaceID_None, nsGkAtoms::prerendered, nullptr,
                  NS_LITERAL_STRING("true"), true);
 }
 
-nsresult
-nsXULElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner)
+void
+nsXULElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+                               ErrorResult& rv)
 {
-    nsCOMPtr<nsIContent> otherContent(do_QueryInterface(aOtherOwner));
-    NS_ENSURE_TRUE(otherContent, NS_ERROR_NOT_IMPLEMENTED);
-
-    nsXULElement* otherEl = FromContent(otherContent);
-    NS_ENSURE_TRUE(otherEl, NS_ERROR_NOT_IMPLEMENTED);
-
-    ErrorResult rv;
-    SwapFrameLoaders(*otherEl, rv);
-    return rv.StealNSResult();
+    nsXULSlots *ourSlots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
+    if (!ourSlots) {
+        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+        return;
+    }
+
+    aOtherLoaderOwner.SwapFrameLoaders(ourSlots->mFrameLoader, rv);
 }
 
 void
-nsXULElement::SwapFrameLoaders(nsXULElement& aOtherElement, ErrorResult& rv)
+nsXULElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+                               ErrorResult& rv)
 {
-    if (&aOtherElement == this) {
+    if (&aOtherLoaderOwner == this) {
         // nothing to do
         return;
     }
 
-    nsXULSlots *ourSlots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
     nsXULSlots *otherSlots =
-        static_cast<nsXULSlots*>(aOtherElement.GetExistingDOMSlots());
-    if (!ourSlots || !ourSlots->mFrameLoader ||
-        !otherSlots || !otherSlots->mFrameLoader) {
-        // Can't handle swapping when there is nothing to swap... yet.
+        static_cast<nsXULSlots*>(aOtherLoaderOwner.GetExistingDOMSlots());
+    if (!otherSlots) {
         rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
         return;
     }
 
-    rv = ourSlots->mFrameLoader->SwapWithOtherLoader(otherSlots->mFrameLoader,
+    SwapFrameLoaders(otherSlots->mFrameLoader, rv);
+}
+
+void
+nsXULElement::SwapFrameLoaders(RefPtr<nsFrameLoader>& aOtherLoader,
+                               mozilla::ErrorResult& rv)
+{
+    nsXULSlots *ourSlots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
+    if (!ourSlots || !ourSlots->mFrameLoader || !aOtherLoader) {
+        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+        return;
+    }
+
+    rv = ourSlots->mFrameLoader->SwapWithOtherLoader(aOtherLoader,
                                                      ourSlots->mFrameLoader,
-                                                     otherSlots->mFrameLoader);
+                                                     aOtherLoader);
 }
 
 NS_IMETHODIMP
 nsXULElement::GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement)
 {
     for (nsIContent* current = GetParent(); current;
          current = current->GetParent()) {
         if (current->NodeInfo()->Equals(nsGkAtoms::listbox,
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -49,16 +49,17 @@ typedef nsTArray<RefPtr<nsXULPrototypeNo
 namespace mozilla {
 class EventChainPreVisitor;
 class EventListenerManager;
 namespace css {
 class StyleRule;
 } // namespace css
 namespace dom {
 class BoxObject;
+class HTMLIFrameElement;
 } // namespace dom
 } // namespace mozilla
 
 namespace JS {
 class SourceBufferHolder;
 } // namespace JS
 
 ////////////////////////////////////////////////////////////////////////
@@ -407,17 +408,16 @@ public:
     NS_DECL_NSIDOMXULELEMENT
 
     virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
     virtual mozilla::EventStates IntrinsicState() const override;
 
     nsresult GetFrameLoader(nsIFrameLoader** aFrameLoader);
     nsresult GetParentApplication(mozIApplication** aApplication);
     nsresult SetIsPrerendered();
-    nsresult SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner);
 
     virtual void RecompileScriptEventListeners() override;
 
     // This function should ONLY be used by BindToTree implementations.
     // The function exists solely because XUL elements store the binding
     // parent as a member instead of in the slots, as Element does.
     void SetXULBindingParent(nsIContent* aBindingParent)
     {
@@ -571,17 +571,22 @@ public:
                              const nsAString& aValue);
     already_AddRefed<nsINodeList>
       GetElementsByAttributeNS(const nsAString& aNamespaceURI,
                                const nsAString& aAttribute,
                                const nsAString& aValue,
                                mozilla::ErrorResult& rv);
     // Style() inherited from nsStyledElement
     already_AddRefed<nsFrameLoader> GetFrameLoader();
-    void SwapFrameLoaders(nsXULElement& aOtherOwner, mozilla::ErrorResult& rv);
+    void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+                          mozilla::ErrorResult& rv);
+    void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+                          mozilla::ErrorResult& rv);
+    void SwapFrameLoaders(RefPtr<nsFrameLoader>& aOtherLoader,
+                          mozilla::ErrorResult& rv);
 
     nsINode* GetScopeChainParent() const override
     {
         // For XUL, the parent is the parent element, if any
         Element* parent = GetParentElement();
         return parent ? parent : nsStyledElement::GetScopeChainParent();
     }
 
@@ -641,17 +646,17 @@ protected:
     virtual bool ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult) override;
 
     virtual mozilla::EventListenerManager*
       GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                      bool* aDefer) override;
-  
+
     /**
      * Add a listener for the specified attribute, if appropriate.
      */
     void AddListenerFor(const nsAttrName& aName,
                         bool aCompileEventHandlers);
     void MaybeAddPopupListener(nsIAtom* aLocalName);
 
     nsIWidget* GetWindowWidget();
@@ -668,17 +673,17 @@ protected:
 
     void RemoveBroadcaster(const nsAString & broadcasterId);
 
 protected:
     // Internal accessor. This shadows the 'Slots', and returns
     // appropriate value.
     nsIControllers *Controllers() {
       nsDOMSlots* slots = GetExistingDOMSlots();
-      return slots ? slots->mControllers : nullptr; 
+      return slots ? slots->mControllers : nullptr;
     }
 
     void UnregisterAccessKey(const nsAString& aOldValue);
     bool BoolAttrIsTrue(nsIAtom* aName) const;
 
     friend nsresult
     NS_NewXULElement(mozilla::dom::Element** aResult, mozilla::dom::NodeInfo *aNodeInfo);
     friend void