Bug 1268688 - Start browser API for frames swapping to HTML. r=bz draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 28 Apr 2016 17:04:52 -0500
changeset 357454 70f9b9468423b1db8eea089cf81acb2853705dfe
parent 356965 86730d0a82093d705e44f33a34973d28b269f1ea
child 519660 b257e92f03794407f9ae6e99c6cd844a0778513f
push id16797
push userbmo:jryans@gmail.com
push dateThu, 28 Apr 2016 22:10:23 +0000
reviewersbz
bugs1268688
milestone49.0a1
Bug 1268688 - Start browser API for frames swapping to HTML. r=bz MozReview-Commit-ID: 56lMg0b86Bp
dom/base/nsFrameLoader.cpp
dom/base/test/chrome/test_swapFrameLoaders.xul
dom/base/test/chrome/window_swapFrameLoaders.xul
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabContext.cpp
dom/ipc/TabContext.h
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -998,20 +998,38 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   ourFrameFrame->EndSwapDocShells(otherFrame);
 
   ourShell->BackingScaleFactorChanged();
   otherShell->BackingScaleFactorChanged();
 
   ourDoc->FlushPendingNotifications(Flush_Layout);
   otherDoc->FlushPendingNotifications(Flush_Layout);
 
+  // Initialize browser API if needed now that owner content has changed.
+  InitializeBrowserAPI();
+  aOther->InitializeBrowserAPI();
+
   mInSwap = aOther->mInSwap = false;
 
-  Unused << mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
-  Unused << aOther->mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
+  // Send an updated tab context since owner content type may have changed.
+  MutableTabContext ourContext;
+  rv = GetNewTabContext(&ourContext);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  MutableTabContext otherContext;
+  rv = aOther->GetNewTabContext(&otherContext);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  Unused << mRemoteBrowser->SendSwappedWithOtherRemoteLoader(
+    ourContext.AsIPCTabContext());
+  Unused << aOther->mRemoteBrowser->SendSwappedWithOtherRemoteLoader(
+    otherContext.AsIPCTabContext());
   return NS_OK;
 }
 
 class MOZ_RAII AutoResetInFrameSwap final
 {
 public:
   AutoResetInFrameSwap(nsFrameLoader* aThisFrameLoader,
                        nsFrameLoader* aOtherFrameLoader,
@@ -1390,16 +1408,20 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their
   // backing scale factor may have changed. (Bug 822266)
   ourShell->BackingScaleFactorChanged();
   otherShell->BackingScaleFactorChanged();
 
   ourParentDocument->FlushPendingNotifications(Flush_Layout);
   otherParentDocument->FlushPendingNotifications(Flush_Layout);
 
+  // Initialize browser API if needed now that owner content has changed
+  InitializeBrowserAPI();
+  aOther->InitializeBrowserAPI();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::Destroy()
 {
   StartDestroy();
   return NS_OK;
--- a/dom/base/test/chrome/test_swapFrameLoaders.xul
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xul
@@ -13,14 +13,13 @@ Test swapFrameLoaders with different fra
   <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>
--- a/dom/base/test/chrome/window_swapFrameLoaders.xul
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -31,16 +31,30 @@ Test swapFrameLoaders with different fra
     ["html", "xul"],
     ["html", "html"],
     ["xul", "xul", "remote"],
     ["xul", "html", "remote"],
     ["html", "xul", "remote"],
     ["html", "html", "remote"],
   ];
 
+  const HEIGHTS = [
+    200,
+    400
+  ];
+
+  function frameScript() {
+    addEventListener("load", function onLoad() {
+      sendAsyncMessage("test:load");
+    }, true);
+  }
+
+  // Watch for loads in new frames
+  window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true);
+
   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"],
       ]) {
@@ -51,51 +65,57 @@ Test swapFrameLoaders with different fra
             resolve(aArgs);
           }, useCapture);
           break;
         }
       }
     });
   }
 
-  function* addFrame(type, remote) {
+  function* addFrame(type, remote, height) {
     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");
+    let src = `data:text/html,<!doctype html>` +
+              `<body style="height:${height}px"/>`;
+    frame.setAttribute("src", src);
     document.documentElement.appendChild(frame);
+    let mm = frame.frameLoader.messageManager;
+    yield once(mm, "test:load");
     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);
+      let heightA = HEIGHTS[0];
+      info(`Adding frame A, type ${typeA}, remote ${remote}, height ${heightA}`);
+      let frameA = yield addFrame(typeA, remote, heightA);
 
-      info(`Adding frame B, type ${typeB}, remote ${remote}`);
-      let frameB = yield addFrame(typeB, remote);
+      let heightB = HEIGHTS[1];
+      info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
+      let frameB = yield addFrame(typeB, remote, heightB);
 
       let frameScriptFactory = function(name) {
         return `function() {
           addMessageListener("ping", function() {
             sendAsyncMessage("pong", "${name}");
           });
         }`;
       }
@@ -123,16 +143,28 @@ Test swapFrameLoaders with different fra
         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");
       }
 
+      // Check height before swap
+      {
+        if (frameA.getContentDimensions) {
+          let { height } = yield frameA.getContentDimensions();
+          is(height, heightA, "Frame A's content height is 200px before swap");
+        }
+        if (frameB.getContentDimensions) {
+          let { height } = yield frameB.getContentDimensions();
+          is(height, heightB, "Frame B's content height is 400px 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);
 
@@ -145,16 +177,28 @@ Test swapFrameLoaders with different fra
         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");
       }
 
+      // Check height after swap
+      {
+        if (frameA.getContentDimensions) {
+          let { height } = yield frameA.getContentDimensions();
+          is(height, heightB, "Frame A's content height is 400px after swap");
+        }
+        if (frameB.getContentDimensions) {
+          let { height } = yield frameB.getContentDimensions();
+          is(height, heightA, "Frame B's content height is 200px 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");
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -14,16 +14,17 @@ include protocol PDocumentRenderer;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
 include DOMTypes;
 include JavaScriptTypes;
 include URIParams;
 include BrowserConfiguration;
+include PTabContext;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
 using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntRect from "Units.h";
@@ -719,17 +720,17 @@ child:
      * Tell the child of an app's offline status
      */
     async AppOfflineStatus(uint32_t id, bool offline);
 
     /**
      * Tell the browser that its frame loader has been swapped
      * with another.
      */
-    async SwappedWithOtherRemoteLoader();
+    async SwappedWithOtherRemoteLoader(IPCTabContext context);
 
     /**
      * A potential accesskey was just pressed. Look for accesskey targets
      * using the list of provided charCodes.
      *
      * @param charCode array of potential character codes
      * @param isTrusted true if triggered by a trusted key event
      * @param modifierMask indicates which accesskey modifiers are pressed
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2282,17 +2282,17 @@ TabChild::RecvAppOfflineStatus(const uin
   if (gIOService && ioService) {
     gIOService->SetAppOfflineInternal(aId, aOffline ?
       nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
   }
   return true;
 }
 
 bool
-TabChild::RecvSwappedWithOtherRemoteLoader()
+TabChild::RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext)
 {
   nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation());
   if (NS_WARN_IF(!ourDocShell)) {
     return true;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> ourWindow = ourDocShell->GetWindow();
   if (NS_WARN_IF(!ourWindow)) {
@@ -2302,16 +2302,39 @@ TabChild::RecvSwappedWithOtherRemoteLoad
   RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(ourDocShell.get());
 
   nsCOMPtr<EventTarget> ourEventTarget = ourWindow->GetParentTarget();
 
   docShell->SetInFrameSwap(true);
 
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, false);
   nsContentUtils::FirePageHideEvent(ourDocShell, ourEventTarget);
+
+  // Owner content type may have changed, so store the possibly updated context
+  // and notify others.
+  MaybeInvalidTabContext maybeContext(aContext);
+  if (!maybeContext.IsValid()) {
+    NS_ERROR(nsPrintfCString("Received an invalid TabContext from "
+                             "the parent process. (%s)",
+                             maybeContext.GetInvalidReason()).get());
+    MOZ_CRASH("Invalid TabContext received from the parent process.");
+  }
+
+  if (!UpdateTabContextAfterSwap(maybeContext.GetTabContext())) {
+    MOZ_CRASH("Update to TabContext after swap was denied.");
+  }
+  NotifyTabContextUpdated();
+
+  // Ignore previous value of mTriedBrowserInit since owner content has changed.
+  mTriedBrowserInit = true;
+  // Initialize the child side of the browser element machinery, if appropriate.
+  if (IsMozBrowserOrApp()) {
+    RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT, true);
+  }
+
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, true);
 
   docShell->SetInFrameSwap(false);
 
   return true;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -412,17 +412,18 @@ public:
   virtual bool RecvAsyncMessage(const nsString& aMessage,
                                 InfallibleTArray<CpowEntry>&& aCpows,
                                 const IPC::Principal& aPrincipal,
                                 const ClonedMessageData& aData) override;
 
   virtual bool RecvAppOfflineStatus(const uint32_t& aId,
                                     const bool& aOffline) override;
 
-  virtual bool RecvSwappedWithOtherRemoteLoader() override;
+  virtual bool
+  RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext) override;
 
   virtual PDocAccessibleChild*
   AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&) override;
 
   virtual bool DeallocPDocAccessibleChild(PDocAccessibleChild*) override;
 
   virtual PDocumentRendererChild*
   AllocPDocumentRendererChild(const nsRect& aDocumentRect,
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -154,16 +154,35 @@ TabContext::SetTabContext(const TabConte
   NS_ENSURE_FALSE(mInitialized, false);
 
   *this = aContext;
   mInitialized = true;
 
   return true;
 }
 
+bool
+TabContext::UpdateTabContextAfterSwap(const TabContext& aContext)
+{
+  // This is only used after already initialized.
+  MOZ_ASSERT(mInitialized);
+
+  // The only permissable change is to `mIsMozBrowserElement`.  All other fields
+  // must match for the change to be accepted.
+  if (aContext.OwnAppId() != OwnAppId() ||
+      aContext.mContainingAppId != mContainingAppId ||
+      aContext.mOriginAttributes != mOriginAttributes ||
+      aContext.mSignedPkgOriginNoSuffix != mSignedPkgOriginNoSuffix) {
+    return false;
+  }
+
+  mIsMozBrowserElement = aContext.mIsMozBrowserElement;
+  return true;
+}
+
 const DocShellOriginAttributes&
 TabContext::OriginAttributesRef() const
 {
   return mOriginAttributes;
 }
 
 const nsACString&
 TabContext::SignedPkgOriginNoSuffix() const
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -155,16 +155,27 @@ protected:
    *  - a non-browser, non-app frame. Both own app and owner app should be null.
    */
   bool SetTabContext(bool aIsMozBrowserElement,
                      mozIApplication* aOwnApp,
                      mozIApplication* aAppFrameOwnerApp,
                      const DocShellOriginAttributes& aOriginAttributes,
                      const nsACString& aSignedPkgOriginNoSuffix);
 
+  /**
+   * Modify this TabContext to match the given TabContext.  This is a special
+   * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have
+   * caused the owner content to change.
+   *
+   * This special case only allows the field `mIsMozBrowserElement` to be
+   * changed.  If any other fields have changed, the update is ignored and
+   * returns false.
+   */
+  bool UpdateTabContextAfterSwap(const TabContext& aContext);
+
 private:
   /**
    * Has this TabContext been initialized?  If so, mutator methods will fail.
    */
   bool mInitialized;
 
   /**
    * Whether this TabContext corresponds to a mozbrowser.