Bug 1170894 - Implement nsIFrameLoader::SwitchProcessAndLoadURI. r=smaug
☠☠ backed out by dffea8ce8b60 ☠ ☠
authorKan-Ru Chen <kanru@kanru.info>
Mon, 31 Aug 2015 18:21:40 +0800
changeset 260208 e4e12583c280bff2a12fc168dd2933e09d02adb3
parent 260207 d5c3de760613cdf7c94a01cd4532fdfc39afa06b
child 260209 cd30c9b6ee534953f001f5a51d5fdc7c96b923e1
push id64440
push userkchen@mozilla.com
push dateTue, 01 Sep 2015 04:55:10 +0000
treeherdermozilla-inbound@e4e12583c280 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1170894
milestone43.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 1170894 - Implement nsIFrameLoader::SwitchProcessAndLoadURI. r=smaug
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/base/nsIFrameLoader.idl
dom/base/test/mochitest.ini
dom/base/test/test_frameLoader_switchProcess.html
dom/browser-element/BrowserElementParent.js
dom/html/nsBrowserElement.cpp
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -272,16 +272,52 @@ nsFrameLoader::LoadURI(nsIURI* aURI)
   rv = doc->InitializeFrameLoader(this);
   if (NS_FAILED(rv)) {
     mURIToLoad = nullptr;
   }
   return rv;
 }
 
 NS_IMETHODIMP
+nsFrameLoader::SwitchProcessAndLoadURI(nsIURI* aURI)
+{
+  nsCOMPtr<nsIURI> URIToLoad = aURI;
+  nsRefPtr<TabParent> tp = nullptr;
+
+  MutableTabContext context;
+  nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+  nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
+
+  bool tabContextUpdated = true;
+  if (ownApp) {
+    tabContextUpdated = context.SetTabContextForAppFrame(ownApp, containingApp);
+  } else if (OwnerIsBrowserFrame()) {
+    // The |else| above is unnecessary; OwnerIsBrowserFrame() implies !ownApp.
+    tabContextUpdated = context.SetTabContextForBrowserFrame(containingApp);
+  } else {
+    tabContextUpdated = context.SetTabContextForNormalFrame();
+  }
+  NS_ENSURE_STATE(tabContextUpdated);
+
+  nsCOMPtr<Element> ownerElement = mOwnerContent;
+  tp = ContentParent::CreateBrowserOrApp(context, ownerElement, nullptr);
+  if (!tp) {
+    return NS_ERROR_FAILURE;
+  }
+  mRemoteBrowserShown = false;
+
+  nsresult rv = SwapRemoteBrowser(tp);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  LoadURI(URIToLoad);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsFrameLoader::SetIsPrerendered()
 {
   MOZ_ASSERT(!mDocShell, "Please call SetIsPrerendered before docShell is created");
   mIsPrerendered = true;
 
   return NS_OK;
 }
 
@@ -2591,19 +2627,68 @@ nsFrameLoader::GetChildID(uint64_t* aChi
 
 void
 nsFrameLoader::SetRemoteBrowser(nsITabParent* aTabParent)
 {
   MOZ_ASSERT(!mRemoteBrowser);
   mRemoteFrame = true;
   mRemoteBrowser = TabParent::GetFrom(aTabParent);
   mChildID = mRemoteBrowser ? mRemoteBrowser->Manager()->ChildID() : 0;
+  ShowRemoteFrame(ScreenIntSize(0, 0));
+}
+
+nsresult
+nsFrameLoader::SwapRemoteBrowser(nsITabParent* aTabParent)
+{
+  nsRefPtr<TabParent> newParent = TabParent::GetFrom(aTabParent);
+  if (!newParent || !mRemoteBrowser) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+  if (!IsRemoteFrame()) {
+    NS_WARNING("Switching from in-process to out-of-process is not supported.");
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  if (!OwnerIsBrowserOrAppFrame()) {
+    NS_WARNING("Switching process for non-mozbrowser/app frame is not supported.");
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  if (newParent == mRemoteBrowser) {
+    return NS_OK;
+  }
+  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+  if (os) {
+    os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+                        "frameloader-message-manager-will-change", nullptr);
+  }
+
+  mRemoteBrowser->CacheFrameLoader(nullptr);
+  mRemoteBrowser->SetOwnerElement(nullptr);
+  mRemoteBrowser->Detach();
+  mRemoteBrowser->Destroy();
+
+  if (mMessageManager) {
+    mMessageManager->Disconnect();
+    mMessageManager = nullptr;
+  }
+
+  mRemoteBrowser = newParent;
+  mRemoteBrowser->Attach(this);
+  mChildID = mRemoteBrowser->Manager()->ChildID();
   ReallyLoadFrameScripts();
   InitializeBrowserAPI();
-  ShowRemoteFrame(ScreenIntSize(0, 0));
+
+  if (os) {
+    os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
+                        "frameloader-message-manager-changed", nullptr);
+  }
+  if (!mRemoteBrowserShown) {
+    ShowRemoteFrame(ScreenIntSize(0, 0));
+  }
+
+  return NS_OK;
 }
 
 void
 nsFrameLoader::SetDetachedSubdocView(nsView* aDetachedViews,
                                      nsIDocument* aContainerDoc)
 {
   mDetachedSubdocViews = aDetachedViews;
   mContainerDocWhileDetached = aContainerDoc;
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -176,16 +176,18 @@ public:
    *
    * This will assert if mRemoteBrowser is non-null.  In practice,
    * this means you can't have successfully run TryRemoteBrowser() on
    * this object, which means you can't have called ShowRemoteFrame()
    * or ReallyStartLoading().
    */
   void SetRemoteBrowser(nsITabParent* aTabParent);
 
+  nsresult SwapRemoteBrowser(nsITabParent* aTabParent);
+
   /**
    * Stashes a detached view on the frame loader. We do this when we're
    * destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is
    * being reframed we'll restore the detached view when it's recreated,
    * otherwise we'll discard the old presentation and set the detached
    * subdoc view to null. aContainerDoc is the document containing the
    * the subdoc frame. This enables us to detect when the containing
    * document has changed during reframe, so we can discard the presentation 
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -11,17 +11,17 @@ interface nsIURI;
 interface nsIFrame;
 interface nsSubDocumentFrame;
 interface nsIMessageSender;
 interface nsIVariant;
 interface nsIDOMElement;
 interface nsITabParent;
 interface nsILoadContext;
 
-[scriptable, builtinclass, uuid(d24f9330-ae4e-11e4-ab27-0800200c9a66)]
+[scriptable, builtinclass, uuid(c6e00815-b7a1-4544-b309-a85b86cb1747)]
 interface nsIFrameLoader : nsISupports
 {
   /**
    * Get the docshell from the frame loader.
    */
   readonly attribute nsIDocShell docShell;
 
   /**
@@ -45,16 +45,24 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * Loads the specified URI in this frame. Behaves identically to loadFrame,
    * except that this method allows specifying the URI to load.
    */
   void loadURI(in nsIURI aURI);
 
   /**
+   * Loads the specified URI in this frame but using a different process.
+   * Behaves identically to loadURI, except that this method only works
+   * with remote frame.
+   * Throws an exception with non-remote frames.
+   */
+  void switchProcessAndLoadURI(in nsIURI aURI);
+
+  /**
    * Puts the frameloader in prerendering mode.
    */
   void setIsPrerendered();
 
   /**
    * Destroy the frame loader and everything inside it. This will
    * clear the weak owner content reference.
    */
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -835,8 +835,10 @@ skip-if = buildapp == 'mulet' || buildap
 [test_script_loader_crossorigin_data_url.html]
 [test_file_negative_date.html]
 [test_nonascii_blob_url.html]
 [test_window_element_enumeration.html]
 [test_referrer_redirect.html]
 [test_postMessages.html]
 support-files = worker_postMessages.js
 [test_window_proto.html]
+[test_frameLoader_switchProcess.html]
+skip-if = e10s || os != 'linux' || buildapp != 'browser'
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_frameLoader_switchProcess.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test frameLoader SwitchProcessAndLoadURI</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="application/javascript;version=1.7">
+  SimpleTest.waitForExplicitFinish();
+
+  var Ci = SpecialPowers.Ci;
+  var Cc = SpecialPowers.Cc;
+
+  function expectProcessCreated() {
+    return new Promise((resolve, reject) => {
+      var topic = "process-priority-manager:TEST-ONLY:process-created";
+      function observer() {
+        SpecialPowers.removeObserver(observer, topic);
+        ok(true, "Expect process created");
+        resolve();
+      }
+      SpecialPowers.addObserver(observer, topic, /* weak = */ false);
+    });
+  }
+
+  function switchProcessAndLoadURI(iframe, url) {
+    var fl = SpecialPowers.wrap(iframe)
+                          .QueryInterface(Ci.nsIFrameLoaderOwner)
+                          .frameLoader;
+    var uri = SpecialPowers.Services.io.newURI(url, null, null);
+    fl.switchProcessAndLoadURI(uri);
+  }
+
+  var messageManager;
+
+  function runTest() {
+    ok(true, "Run Test");
+    var iframe = document.createElement("iframe");
+    iframe.setAttribute("mozbrowser", "true");
+    iframe.setAttribute("remote", "true");
+    iframe.setAttribute("src", "http://example.org");
+
+    expectProcessCreated()
+    .then(() => new Promise(next => {
+      iframe.addEventListener("mozbrowserloadend", function loadend(e) {
+        iframe.removeEventListener("mozbrowserloadend", loadend);
+        ok(true, "Got mozbrowserloadend");
+        mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+        expectProcessCreated().then(next);
+        switchProcessAndLoadURI(iframe, "data:text/html,%3Cscript%3Ealert(true)%3C/script%3E");
+      });
+    }))
+    .then(() => new Promise(next => {
+      var newMessageManager = SpecialPowers.getBrowserFrameMessageManager(iframe);
+      isnot(messageManager, newMessageManager, "Got a new message manager");
+      messageManager = newMessageManager;
+      iframe.addEventListener("mozbrowsershowmodalprompt", function prompt(e) {
+        iframe.removeEventListener("mozbrowsershowmodalprompt", prompt);
+        ok(true, "Browser API still works after process switch");
+        next();
+      });
+    }))
+    .then(SimpleTest.finish);
+
+    document.body.appendChild(iframe);
+  }
+
+  SpecialPowers.pushPrefEnv(
+    { "set": [["dom.ipc.processPriorityManager.enabled", true],
+              ["dom.ipc.processPriorityManager.testMode", true],
+              ["dom.ipc.tabs.disabled", false],
+              ["dom.ipc.processCount", 3],
+              ["dom.mozBrowserFramesEnabled", true]] },
+    () => SpecialPowers.pushPermissions([
+      { "type": "browser", "allow": 1, "context": document }
+    ], runTest));
+</script>
+</body>
+</html>
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -72,16 +72,18 @@ function BrowserElementParent() {
   this._pendingDOMRequests = {};
   this._pendingSetInputMethodActive = [];
   this._nextPaintListeners = [];
   this._pendingDOMFullscreen = false;
 
   Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'ask-children-to-execute-copypaste-command', /* ownsWeak = */ true);
+  Services.obs.addObserver(this, 'frameloader-message-manager-will-change', /* ownsWeak = */ true);
+  Services.obs.addObserver(this, 'frameloader-message-manager-changed', /* ownsWeak = */ true);
 }
 
 BrowserElementParent.prototype = {
 
   classDescription: "BrowserElementAPI implementation",
   classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
   contractID: "@mozilla.org/dom/browser-element-api;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
@@ -156,20 +158,27 @@ BrowserElementParent.prototype = {
         this._mm.sendAsyncMessage("Webapps:RegisterBEP",
                                   { manifestURL: appManifestURL });
       }
     }
   },
 
   _setupMessageListener: function() {
     this._mm = this._frameLoader.messageManager;
-    let self = this;
-    let isWidget = this._frameLoader
-                       .QueryInterface(Ci.nsIFrameLoader)
-                       .ownerIsWidget;
+    this._isWidget = this._frameLoader
+                         .QueryInterface(Ci.nsIFrameLoader)
+                         .ownerIsWidget;
+    this._mm.addMessageListener('browser-element-api:call', this);
+    this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
+  },
+
+  receiveMessage: function(aMsg) {
+    if (!this._isAlive()) {
+      return;
+    }
 
     // Messages we receive are handed to functions which take a (data) argument,
     // where |data| is the message manager's data object.
     // We use a single message and dispatch to various function based
     // on data.msg_name
     let mmCalls = {
       "hello": this._recvHello,
       "loadstart": this._fireProfiledEventFromMsg,
@@ -217,30 +226,25 @@ BrowserElementParent.prototype = {
       "manifestchange": this._fireEventFromMsg,
       "metachange": this._fireEventFromMsg,
       "resize": this._fireEventFromMsg,
       "activitydone": this._fireEventFromMsg,
       "scroll": this._fireEventFromMsg,
       "opentab": this._fireEventFromMsg
     };
 
-    this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
-      if (!self._isAlive()) {
-        return;
-      }
+    if (aMsg.data.msg_name in mmCalls) {
+      mmCalls[aMsg.data.msg_name].apply(this, arguments);
+    } else if (!this._isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
+      mmSecuritySensitiveCalls[aMsg.data.msg_name].apply(this, arguments);
+    }
+  },
 
-      if (aMsg.data.msg_name in mmCalls) {
-        return mmCalls[aMsg.data.msg_name].apply(self, arguments);
-      } else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
-        return mmSecuritySensitiveCalls[aMsg.data.msg_name]
-                 .apply(self, arguments);
-      }
-    });
-
-    this._mm.loadFrameScript("chrome://global/content/extensions.js", true);
+  _removeMessageListener: function() {
+    this._mm.removeMessageListener('browser-element-api:call', this);
   },
 
   /**
    * You shouldn't touch this._frameElement or this._window if _isAlive is
    * false.  (You'll likely get an exception if you do.)
    */
   _isAlive: function() {
     return !Cu.isDeadWrapper(this._frameElement) &&
@@ -1100,16 +1104,26 @@ BrowserElementParent.prototype = {
         this._sendAsyncMsg('do-command', { command: data });
       }
       break;
     case 'ask-children-to-execute-copypaste-command':
       if (this._isAlive() && this._frameElement == subject.wrappedJSObject) {
         this._sendAsyncMsg('copypaste-do-command', { command: data });
       }
       break;
+    case 'frameloader-message-manager-will-change':
+      if (this._isAlive() && subject == this._frameLoader) {
+        this._removeMessageListener();
+      }
+      break;
+    case 'frameloader-message-manager-changed':
+      if (this._isAlive() && subject == this._frameLoader) {
+        this._setupMessageListener();
+      }
+      break;
     default:
       debug('Unknown topic: ' + topic);
       break;
     };
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -49,16 +49,20 @@ nsBrowserElement::IsNotWidgetOrThrow(Err
   }
   aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
   return false;
 }
 
 void
 nsBrowserElement::InitBrowserElementAPI()
 {
+  if (mBrowserElementAPI) {
+    return;
+  }
+
   bool isBrowserOrApp;
   nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
   NS_ENSURE_TRUE_VOID(frameLoader);
   nsresult rv = frameLoader->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
   NS_ENSURE_SUCCESS_VOID(rv);
   rv = frameLoader->GetOwnerIsWidget(&mOwnerIsWidget);
   NS_ENSURE_SUCCESS_VOID(rv);
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -273,16 +273,17 @@ TabParent::TabParent(nsIContentParent* a
   , mDimensions(0, 0)
   , mOrientation(0)
   , mDPI(0)
   , mDefaultScale(0)
   , mUpdatedDimensions(false)
   , mManager(aManager)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
+  , mIsDetached(true)
   , mAppPackageFileDescriptorSent(false)
   , mSendOfflineStatus(true)
   , mChromeFlags(aChromeFlags)
   , mDragAreaX(0)
   , mDragAreaY(0)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
@@ -467,16 +468,45 @@ TabParent::Destroy()
   const nsTArray<PPluginWidgetParent*>& kids = ManagedPPluginWidgetParent();
   for (uint32_t idx = 0; idx < kids.Length(); ++idx) {
       static_cast<mozilla::plugins::PluginWidgetParent*>(kids[idx])->ParentDestroy();
   }
 
   mMarkedDestroying = true;
 }
 
+void
+TabParent::Detach()
+{
+  if (mIsDetached) {
+    return;
+  }
+  RemoveWindowListeners();
+  if (RenderFrameParent* frame = GetRenderFrame()) {
+    RemoveTabParentFromTable(frame->GetLayersId());
+  }
+  mIsDetached = true;
+}
+
+void
+TabParent::Attach(nsFrameLoader* aFrameLoader)
+{
+  MOZ_ASSERT(mIsDetached);
+  if (!mIsDetached) {
+    return;
+  }
+  Element* ownerElement = aFrameLoader->GetOwnerContent();
+  SetOwnerElement(ownerElement);
+  if (RenderFrameParent* frame = GetRenderFrame()) {
+    AddTabParentToTable(frame->GetLayersId(), this);
+    frame->OwnerContentChanged(ownerElement);
+  }
+  mIsDetached = false;
+}
+
 bool
 TabParent::Recv__delete__()
 {
   if (XRE_IsParentProcess()) {
     Manager()->AsContentParent()->NotifyTabDestroyed(this, mMarkedDestroying);
     ContentParent::DeallocateTabId(mTabId,
                                    Manager()->AsContentParent()->ChildID());
   }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -117,16 +117,18 @@ public:
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
 
     already_AddRefed<nsILoadContext> GetLoadContext();
     already_AddRefed<nsIWidget> GetTopLevelWidget();
     nsIXULBrowserWindow* GetXULBrowserWindow();
 
     void Destroy();
+    void Detach();
+    void Attach(nsFrameLoader* aFrameLoader);
 
     void RemoveWindowListeners();
     void AddWindowListeners();
     void DidRefresh() override;
 
     virtual bool RecvMoveFocus(const bool& aForward,
                                const bool& aForDocumentNavigation) override;
     virtual bool RecvEvent(const RemoteDOMEvent& aEvent) override;
@@ -510,16 +512,18 @@ private:
     // the input block; this is used for generating appropriate pointercancel events.
     void ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid,
                                      uint64_t* aOutInputBlockId,
                                      nsEventStatus* aOutApzResponse);
     // When true, we've initiated normal shutdown and notified our managing PContent.
     bool mMarkedDestroying;
     // When true, the TabParent is invalid and we should not send IPC messages anymore.
     bool mIsDestroyed;
+    // When true, the TabParent is detached from the frame loader.
+    bool mIsDetached;
     // Whether we have already sent a FileDescriptor for the app package.
     bool mAppPackageFileDescriptorSent;
 
     // Whether we need to send the offline status to the TabChild
     // This is true, until the first call of LoadURL
     bool mSendOfflineStatus;
 
     uint32_t mChromeFlags;