Bug 1432485 - Add GeckoSession.NavigationListener.onNewSession r=jchen,esawin draft
authorJames Willcox <snorp@snorp.net>
Wed, 31 Jan 2018 17:08:48 -0600
changeset 757611 0a6bf7c89204d357fabe096bcbabd12bf792c3a4
parent 757244 2c000486eac466da6623e4d7f7f1fd4e318f60e8
child 757612 a6675ad4af752216b85c2b957d7460f346623969
push id99798
push userbmo:snorp@snorp.net
push dateTue, 20 Feb 2018 22:20:17 +0000
reviewersjchen, esawin
bugs1432485
milestone60.0a1
Bug 1432485 - Add GeckoSession.NavigationListener.onNewSession r=jchen,esawin This allows apps to decide which GeckoSession should handle a load that will be in a new window (e.g., window.open()). MozReview-Commit-ID: BkJM93489Ga
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
mobile/android/chrome/geckoview/geckoview.js
mobile/android/chrome/geckoview/geckoview.xul
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
mobile/android/modules/geckoview/GeckoViewNavigation.jsm
widget/android/GeneratedJNIWrappers.h
widget/android/nsWindow.cpp
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -644,16 +644,23 @@ public class CustomTabsActivity extends 
             } catch (ActivityNotFoundException e) {
                 Log.w(LOGTAG, "No activity handler found for: " + urlStr);
             }
         }
 
         return true;
     }
 
+    @Override
+    public void onNewSession(final GeckoSession session, final String uri,
+                             final GeckoSession.Response<GeckoSession> response) {
+        // We should never get here because we abort loads that need a new session in onLoadUri()
+        throw new IllegalStateException("Unexpected new session");
+    }
+
     /* GeckoSession.ProgressListener */
     @Override
     public void onPageStart(GeckoSession session, String url) {
         mCurrentUrl = url;
         mCanStop = true;
         updateActionBar();
         updateCanStop();
         updateProgress(20);
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -417,16 +417,23 @@ public class WebAppActivity extends AppC
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
                 Log.w(LOGTAG, "No activity handler found for: " + urlStr);
             }
         }
         return true;
     }
 
+    @Override
+    public void onNewSession(final GeckoSession session, final String uri,
+                             final GeckoSession.Response<GeckoSession> response) {
+        // We should never get here because we abort loads that need a new session in onLoadUri()
+        throw new IllegalStateException("Unexpected new session");
+    }
+
     private void updateFullScreen() {
         boolean fullScreen = mIsFullScreenContent || mIsFullScreenMode;
         if (ActivityUtils.isFullScreen(this) == fullScreen) {
             return;
         }
 
         ActivityUtils.setFullScreen(this, fullScreen);
     }
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -4,32 +4,34 @@
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "EventDispatcher",
   "resource://gre/modules/Messaging.jsm");
+ChromeUtils.defineModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
   () => EventDispatcher.for(window));
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "View"));
 
 // Creates and manages GeckoView modules.
 // A module must extend GeckoViewModule.
 // Instantiate a module by calling
 //   add(<resource path>, <type name>)
 // and remove by calling
 //   remove(<type name>)
 var ModuleManager = {
-  init: function() {
-    this.browser = document.getElementById("content");
+  init: function(aBrowser) {
+    this.browser = aBrowser;
     this.modules = {};
   },
 
   add: function(aResource, aType, ...aArgs) {
     this.remove(aType);
     let scope = {};
     ChromeUtils.import(aResource, scope);
     this.modules[aType] = new scope[aType](
@@ -40,18 +42,34 @@ var ModuleManager = {
   remove: function(aType) {
     if (!(aType in this.modules)) {
       return;
     }
     delete this.modules[aType];
   }
 };
 
+function createBrowser() {
+  const browser = window.browser = document.createElement("browser");
+  browser.setAttribute("type", "content");
+  browser.setAttribute("primary", "true");
+  browser.setAttribute("flex", "1");
+
+  // There may be a GeckoViewNavigation module in another window waiting for us to
+  // create a browser so it can call presetOpenerWindow(), so allow them to do that now.
+  Services.obs.notifyObservers(window, "geckoview-window-created");
+  window.document.getElementById("main-window").appendChild(browser);
+
+  browser.stop();
+  return browser;
+}
+
 function startup() {
-  ModuleManager.init();
+  const browser = createBrowser();
+  ModuleManager.init(browser);
 
   // GeckoViewNavigation needs to go first because nsIDOMBrowserWindow must set up
   // before the first remote browser. Bug 1365364.
   ModuleManager.add("resource://gre/modules/GeckoViewNavigation.jsm",
                     "GeckoViewNavigation");
   ModuleManager.add("resource://gre/modules/GeckoViewSettings.jsm",
                     "GeckoViewSettings");
   ModuleManager.add("resource://gre/modules/GeckoViewContent.jsm",
@@ -64,10 +82,10 @@ function startup() {
                     "GeckoViewTab");
   ModuleManager.add("resource://gre/modules/GeckoViewRemoteDebugger.jsm",
                     "GeckoViewRemoteDebugger");
   ModuleManager.add("resource://gre/modules/GeckoViewTrackingProtection.jsm",
                     "GeckoViewTrackingProtection");
 
   // Move focus to the content window at the end of startup,
   // so things like text selection can work properly.
-  document.getElementById("content").focus();
+  browser.focus();
 }
--- a/mobile/android/chrome/geckoview/geckoview.xul
+++ b/mobile/android/chrome/geckoview/geckoview.xul
@@ -1,16 +1,11 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
-<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
-
 <window id="main-window"
         onload="startup();"
         windowtype="navigator:geckoview"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <browser id="content" type="content" primary="true" src="about:blank" flex="1"/>
-
   <script type="application/javascript" src="chrome://geckoview/content/geckoview.js"/>
 </window>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
@@ -4,16 +4,17 @@
  * 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/. */
 
 package org.mozilla.gecko;
 
 import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.UUID;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.gfx.LayerSession;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -64,16 +65,19 @@ public class GeckoSession extends LayerS
     private final NativeQueue mNativeQueue =
         new NativeQueue(State.INITIAL, State.READY);
 
     private final EventDispatcher mEventDispatcher =
         new EventDispatcher(mNativeQueue);
 
     private final TextInputController mTextInput = new TextInputController(this, mNativeQueue);
 
+    private String mId = UUID.randomUUID().toString().replace("-", "");
+    /* package */ String getId() { return mId; }
+
     private final GeckoSessionHandler<ContentListener> mContentHandler =
         new GeckoSessionHandler<ContentListener>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContextMenu",
                 "GeckoView:DOMTitleChanged",
                 "GeckoView:DOMWindowFocus",
                 "GeckoView:FullScreenEnter",
@@ -105,17 +109,18 @@ public class GeckoSession extends LayerS
             }
         };
 
     private final GeckoSessionHandler<NavigationListener> mNavigationHandler =
         new GeckoSessionHandler<NavigationListener>(
             "GeckoViewNavigation", this,
             new String[]{
                 "GeckoView:LocationChange",
-                "GeckoView:OnLoadUri"
+                "GeckoView:OnLoadUri",
+                "GeckoView:OnNewSession"
             }
         ) {
             @Override
             public void handleMessage(final NavigationListener listener,
                                       final String event,
                                       final GeckoBundle message,
                                       final EventCallback callback) {
                 if ("GeckoView:LocationChange".equals(event)) {
@@ -128,16 +133,29 @@ public class GeckoSession extends LayerS
                 } else if ("GeckoView:OnLoadUri".equals(event)) {
                     final String uri = message.getString("uri");
                     final NavigationListener.TargetWindow where =
                         NavigationListener.TargetWindow.forGeckoValue(
                             message.getInt("where"));
                     final boolean result =
                         listener.onLoadUri(GeckoSession.this, uri, where);
                     callback.sendSuccess(result);
+                } else if ("GeckoView:OnNewSession".equals(event)) {
+                    final String uri = message.getString("uri");
+                    listener.onNewSession(GeckoSession.this, uri,
+                        new Response<GeckoSession>() {
+                            @Override
+                            public void respond(GeckoSession session) {
+                                if (session.isOpen() && session.isReady()) {
+                                    throw new IllegalArgumentException("Must use a new GeckoSession instance");
+                                }
+
+                                callback.sendSuccess(session != null ? session.getId() : null);
+                            }
+                        });
                 }
             }
         };
 
     private final GeckoSessionHandler<ProgressListener> mProgressHandler =
         new GeckoSessionHandler<ProgressListener>(
             "GeckoViewProgress", this,
             new String[]{
@@ -344,17 +362,17 @@ public class GeckoSession extends LayerS
             }
             return mBinder;
         }
 
         @WrapForJNI(dispatchTo = "proxy")
         public static native void open(Window instance, Compositor compositor,
                                        EventDispatcher dispatcher,
                                        GeckoBundle settings, String chromeUri,
-                                       int screenId, boolean privateMode);
+                                       int screenId, boolean privateMode, String id);
 
         @Override // JNIObject
         protected void disposeNative() {
             // Detach ourselves from the binder as well, to prevent this window from being
             // read from any parcels.
             asBinder().attachInterface(null, Window.class.getName());
 
             // Reset our queue, so we don't end up with queued calls on a disposed object.
@@ -428,33 +446,39 @@ public class GeckoSession extends LayerS
 
     protected Window mWindow;
     private GeckoSessionSettings mSettings;
 
     public GeckoSession() {
         this(/* settings */ null);
     }
 
+    public GeckoSession(GeckoSession otherSession) {
+        this(otherSession.getSettings());
+    }
+
     public GeckoSession(final GeckoSessionSettings settings) {
         if (settings == null) {
             mSettings = new GeckoSessionSettings(this);
         } else {
             mSettings = new GeckoSessionSettings(settings, this);
         }
 
         mListener.registerListeners();
     }
 
-    private void transferFrom(final Window window, final GeckoSessionSettings settings) {
+    private void transferFrom(final Window window, final GeckoSessionSettings settings,
+                              final String id) {
         if (isOpen()) {
             throw new IllegalStateException("Session is open");
         }
 
         mWindow = window;
         mSettings = new GeckoSessionSettings(settings, this);
+        mId = id;
 
         if (mWindow != null) {
             if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
                 mWindow.transfer(mCompositor, mEventDispatcher, mSettings.asBundle());
             } else {
                 GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY,
                         mWindow, "transfer",
                         Compositor.class, mCompositor,
@@ -462,41 +486,43 @@ public class GeckoSession extends LayerS
                         GeckoBundle.class, mSettings.asBundle());
             }
         }
 
         onWindowChanged();
     }
 
     /* package */ void transferFrom(final GeckoSession session) {
-        transferFrom(session.mWindow, session.mSettings);
+        transferFrom(session.mWindow, session.mSettings, session.mId);
         session.mWindow = null;
         session.onWindowChanged();
     }
 
     @Override // Parcelable
     public int describeContents() {
         return 0;
     }
 
     @Override // Parcelable
     public void writeToParcel(Parcel out, int flags) {
         out.writeStrongInterface(mWindow);
         out.writeParcelable(mSettings, flags);
+        out.writeString(mId);
     }
 
     // AIDL code may call readFromParcel even though it's not part of Parcelable.
     public void readFromParcel(final Parcel source) {
         final IBinder binder = source.readStrongBinder();
         final IInterface ifce = (binder != null) ?
                 binder.queryLocalInterface(Window.class.getName()) : null;
         final Window window = (ifce instanceof Window) ? (Window) ifce : null;
         final GeckoSessionSettings settings =
                 source.readParcelable(getClass().getClassLoader());
-        transferFrom(window, settings);
+        final String id = source.readString();
+        transferFrom(window, settings, id);
     }
 
     public static final Creator<GeckoSession> CREATOR = new Creator<GeckoSession>() {
         @Override
         public GeckoSession createFromParcel(final Parcel in) {
             final GeckoSession session = new GeckoSession();
             session.readFromParcel(in);
             return session;
@@ -537,16 +563,20 @@ public class GeckoSession extends LayerS
             GeckoThread.launch();
         }
     }
 
     public boolean isOpen() {
         return mWindow != null;
     }
 
+    /* package */ boolean isReady() {
+        return mNativeQueue.isReady();
+    }
+
     public void openWindow(final Context appContext) {
         ThreadUtils.assertOnUiThread();
 
         if (isOpen()) {
             throw new IllegalStateException("Session is open");
         }
 
         if (!GeckoThread.isLaunched()) {
@@ -558,27 +588,27 @@ public class GeckoSession extends LayerS
         final String chromeUri = mSettings.getString(GeckoSessionSettings.CHROME_URI);
         final int screenId = mSettings.getInt(GeckoSessionSettings.SCREEN_ID);
         final boolean isPrivate = mSettings.getBoolean(GeckoSessionSettings.USE_PRIVATE_MODE);
 
         mWindow = new Window(mNativeQueue);
 
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             Window.open(mWindow, mCompositor, mEventDispatcher,
-                        mSettings.asBundle(), chromeUri, screenId, isPrivate);
+                        mSettings.asBundle(), chromeUri, screenId, isPrivate, mId);
         } else {
             GeckoThread.queueNativeCallUntil(
                 GeckoThread.State.PROFILE_READY,
                 Window.class, "open",
                 Window.class, mWindow,
                 Compositor.class, mCompositor,
                 EventDispatcher.class, mEventDispatcher,
                 GeckoBundle.class, mSettings.asBundle(),
                 String.class, chromeUri,
-                screenId, isPrivate);
+                screenId, isPrivate, mId);
         }
 
         onWindowChanged();
     }
 
     public void closeWindow() {
         ThreadUtils.assertOnUiThread();
 
@@ -1297,16 +1327,26 @@ public class GeckoSession extends LayerS
          *            image-links.
          * @param elementSrc The source URI of the pressed element, set for
          *                   (nested) images and media elements.
          */
         void onContextMenu(GeckoSession session, int screenX, int screenY,
                            String uri, String elementSrc);
     }
 
+    /**
+     * This is used to send responses in delegate methods that have asynchronous responses.
+     */
+    public interface Response<T> {
+        /**
+         * @param val The value contained in the response
+         */
+        void respond(T val);
+    }
+
     public interface NavigationListener {
         /**
         * A view has started loading content from the network.
         * @param session The GeckoSession that initiated the callback.
         * @param url The resource being loaded.
         */
         void onLocationChange(GeckoSession session, String url);
 
@@ -1369,20 +1409,32 @@ public class GeckoSession extends LayerS
         }
 
         /**
         * A request to open an URI.
         * @param session The GeckoSession that initiated the callback.
         * @param uri The URI to be loaded.
         * @param where The target window.
         *
-        * @return True if the URI loading has been handled, false if Gecko
-        *         should handle the loading.
+        * @return Whether or not the load was handled. Returning false will allow Gecko
+        *         to continue the load as normal.
         */
         boolean onLoadUri(GeckoSession session, String uri, TargetWindow where);
+
+        /**
+        * A request has been made to open a new session. The URI is provided only for
+        * informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the
+        * returned GeckoSession must be a newly-created one.
+        *
+        * @param session The GeckoSession that initiated the callback.
+        * @param uri The URI to be loaded.
+        *
+        * @param response A Response which will hold the returned GeckoSession
+        */
+        void onNewSession(GeckoSession session, String uri, Response<GeckoSession> response);
     }
 
     /**
      * GeckoSession applications implement this interface to handle prompts triggered by
      * content in the GeckoSession, such as alerts, authentication dialogs, and select list
      * pickers.
      **/
     public interface PromptDelegate {
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -14,16 +14,17 @@ import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 
 import java.util.Locale;
 
 import org.mozilla.gecko.GeckoSession;
+import org.mozilla.gecko.GeckoSession.Response;
 import org.mozilla.gecko.GeckoSessionSettings;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.GeckoSession.PermissionDelegate.MediaSource;
 import org.mozilla.gecko.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.gecko.util.GeckoBundle;
 
 public class GeckoViewActivity extends Activity {
@@ -48,16 +49,17 @@ public class GeckoViewActivity extends A
               " - application start");
 
         String geckoArgs = null;
         final String intentArgs = getIntent().getStringExtra("args");
 
         if (BuildConfig.DEBUG) {
             // In debug builds, we want to load JavaScript resources fresh with each build.
             geckoArgs = "-purgecaches";
+
         }
 
         if (!TextUtils.isEmpty(intentArgs)) {
             if (geckoArgs == null) {
                 geckoArgs = intentArgs;
             } else {
                 geckoArgs += " " + intentArgs;
             }
@@ -82,18 +84,18 @@ public class GeckoViewActivity extends A
         final BasicGeckoViewPrompt prompt = new BasicGeckoViewPrompt(this);
         prompt.filePickerRequestCode = REQUEST_FILE_PICKER;
         mGeckoSession.setPromptDelegate(prompt);
 
         final MyGeckoViewPermission permission = new MyGeckoViewPermission();
         permission.androidPermissionRequestCode = REQUEST_PERMISSIONS;
         mGeckoSession.setPermissionDelegate(permission);
 
-        mGeckoView.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
-                                            useMultiprocess);
+        mGeckoSession.getSettings().setBoolean(GeckoSessionSettings.USE_MULTIPROCESS,
+                                               useMultiprocess);
 
         mGeckoSession.enableTrackingProtection(
               TrackingProtectionDelegate.CATEGORY_AD |
               TrackingProtectionDelegate.CATEGORY_ANALYTIC |
               TrackingProtectionDelegate.CATEGORY_SOCIAL
         );
 
         loadSettings(getIntent());
@@ -341,23 +343,22 @@ public class GeckoViewActivity extends A
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean value) {
         }
 
         @Override
         public boolean onLoadUri(final GeckoSession session, final String uri,
                                  final TargetWindow where) {
-            Log.d(LOGTAG, "onLoadUri=" + uri +
-                          " where=" + where);
-            if (where != TargetWindow.NEW) {
-                return false;
-            }
-            session.loadUri(uri);
-            return true;
+            return false;
+        }
+
+        @Override
+        public void onNewSession(final GeckoSession session, final String uri, Response<GeckoSession> response) {
+            response.respond(null);
         }
     }
 
     private class MyTrackingProtection implements GeckoSession.TrackingProtectionDelegate {
         private int mBlockedAds = 0;
         private int mBlockedAnalytics = 0;
         private int mBlockedSocial = 0;
 
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -93,80 +93,122 @@ class GeckoViewNavigation extends GeckoV
     this.eventDispatcher.sendRequestForResult(message).then(response => {
       handled = response;
     });
     Services.tm.spinEventLoopUntil(() => handled !== undefined);
 
     return handled;
   }
 
+  waitAndSetOpener(sessionId, opener) {
+    if (!sessionId) {
+      return Promise.resolve(null);
+    }
+
+    return new Promise(resolve => {
+      const handler = {
+        observe(aSubject, aTopic, aData) {
+          if (aTopic === "geckoview-window-created" && aSubject.name === sessionId) {
+            aSubject.browser.presetOpenerWindow(opener);
+            Services.obs.removeObserver(handler, "geckoview-window-created");
+            resolve(aSubject);
+          }
+        }
+      };
+
+      // This event is emitted from createBrowser() in geckoview.js
+      Services.obs.addObserver(handler, "geckoview-window-created");
+    });
+  }
+
+  handleNewSession(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
+    debug("handleNewSession: aUri=" + (aUri && aUri.spec) +
+          " aWhere=" + aWhere +
+          " aFlags=" + aFlags);
+
+    if (!this.isRegistered) {
+      return null;
+    }
+
+    const message = {
+      type: "GeckoView:OnNewSession",
+      uri: aUri ? aUri.displaySpec : ""
+    };
+
+    let browser = undefined;
+    this.eventDispatcher.sendRequestForResult(message).then(sessionId => {
+      return this.waitAndSetOpener(sessionId, aOpener);
+    }).then(window => {
+      browser = (window && window.browser);
+    });
+
+    // Wait indefinitely for app to respond with a browser or null
+    Services.tm.spinEventLoopUntil(() => browser !== undefined);
+    return browser;
+  }
+
   // nsILoadURIDelegate.
   loadURI(aUri, aWhere, aFlags, aTriggeringPrincipal) {
     debug("loadURI " + aUri + " " + aWhere + " " + aFlags + " " +
           aTriggeringPrincipal);
 
-    let handled = this.handleLoadUri(aUri, null, aWhere, aFlags,
-                                     aTriggeringPrincipal);
+    const handled = this.handleLoadUri(aUri, null, aWhere, aFlags,
+                                       aTriggeringPrincipal);
     if (!handled) {
       throw Cr.NS_ERROR_ABORT;
     }
   }
 
   // nsIBrowserDOMWindow.
   createContentWindow(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     debug("createContentWindow: aUri=" + (aUri && aUri.spec) +
           " aWhere=" + aWhere +
           " aFlags=" + aFlags);
 
-    let handled = this.handleLoadUri(aUri, aOpener, aWhere, aFlags,
-                                     aTriggeringPrincipal);
-    if (!handled &&
-        (aWhere === Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW ||
-         aWhere === Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW)) {
-      return this.browser.contentWindow;
+    const browser = this.handleNewSession(aUri, aOpener, aWhere, aFlags,
+                                          aTriggeringPrincipal);
+    if (!browser) {
+      return null;
     }
 
-    throw Cr.NS_ERROR_ABORT;
+    return browser.contentWindow;
   }
 
   // nsIBrowserDOMWindow.
   createContentWindowInFrame(aUri, aParams, aWhere, aFlags, aNextTabParentId,
                              aName) {
     debug("createContentWindowInFrame: aUri=" + (aUri && aUri.spec) +
           " aParams=" + aParams +
           " aWhere=" + aWhere +
           " aFlags=" + aFlags +
           " aNextTabParentId=" + aNextTabParentId +
           " aName=" + aName);
 
-    let handled = this.handleLoadUri(aUri, null, aWhere, aFlags, null);
-    if (!handled &&
-        (aWhere === Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW ||
-         aWhere === Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW)) {
-      return this.browser;
+    const browser = this.handleNewSession(aUri, null, aWhere, aFlags, null);
+    if (browser) {
+      browser.setAttribute("nextTabParentId", aNextTabParentId);
     }
 
-    throw Cr.NS_ERROR_ABORT;
+    return browser;
   }
 
   // nsIBrowserDOMWindow.
   openURI(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     return this.createContentWindow(aUri, aOpener, aWhere, aFlags,
                                     aTriggeringPrincipal);
   }
 
   // nsIBrowserDOMWindow.
   openURIInFrame(aUri, aParams, aWhere, aFlags, aNextTabParentId, aName) {
     return this.createContentWindowInFrame(aUri, aParams, aWhere, aFlags,
                                            aNextTabParentId, aName);
   }
 
   // nsIBrowserDOMWindow.
   isTabContentWindow(aWindow) {
-    debug("isTabContentWindow " + this.browser.contentWindow === aWindow);
     return this.browser.contentWindow === aWindow;
   }
 
   // nsIBrowserDOMWindow.
   canClose() {
     debug("canClose");
     return false;
   }
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -2349,20 +2349,21 @@ public:
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 Window::Param,
                 mozilla::jni::Object::Param,
                 mozilla::jni::Object::Param,
                 mozilla::jni::Object::Param,
                 mozilla::jni::String::Param,
                 int32_t,
-                bool> Args;
+                bool,
+                mozilla::jni::String::Param> Args;
         static constexpr char name[] = "open";
         static constexpr char signature[] =
-                "(Lorg/mozilla/gecko/GeckoSession$Window;Lorg/mozilla/gecko/gfx/LayerSession$Compositor;Lorg/mozilla/gecko/EventDispatcher;Lorg/mozilla/gecko/util/GeckoBundle;Ljava/lang/String;IZ)V";
+                "(Lorg/mozilla/gecko/GeckoSession$Window;Lorg/mozilla/gecko/gfx/LayerSession$Compositor;Lorg/mozilla/gecko/EventDispatcher;Lorg/mozilla/gecko/util/GeckoBundle;Ljava/lang/String;IZLjava/lang/String;)V";
         static const bool isStatic = true;
         static const mozilla::jni::ExceptionMode exceptionMode =
                 mozilla::jni::ExceptionMode::ABORT;
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::PROXY;
     };
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -277,17 +277,18 @@ public:
     // Create and attach a window.
     static void Open(const jni::Class::LocalRef& aCls,
                      GeckoSession::Window::Param aWindow,
                      jni::Object::Param aCompositor,
                      jni::Object::Param aDispatcher,
                      jni::Object::Param aSettings,
                      jni::String::Param aChromeURI,
                      int32_t aScreenId,
-                     bool aPrivateMode);
+                     bool aPrivateMode,
+                     jni::String::Param aId);
 
     // Close and destroy the nsWindow.
     void Close();
 
     // Transfer this nsWindow to new GeckoSession objects.
     void Transfer(const GeckoSession::Window::LocalRef& inst,
                   jni::Object::Param aCompositor,
                   jni::Object::Param aDispatcher,
@@ -1243,17 +1244,18 @@ nsWindow::GeckoViewSupport::~GeckoViewSu
 /* static */ void
 nsWindow::GeckoViewSupport::Open(const jni::Class::LocalRef& aCls,
                                  GeckoSession::Window::Param aWindow,
                                  jni::Object::Param aCompositor,
                                  jni::Object::Param aDispatcher,
                                  jni::Object::Param aSettings,
                                  jni::String::Param aChromeURI,
                                  int32_t aScreenId,
-                                 bool aPrivateMode)
+                                 bool aPrivateMode,
+                                 jni::String::Param aId)
 {
     MOZ_ASSERT(NS_IsMainThread());
 
     AUTO_PROFILER_LABEL("nsWindow::GeckoViewSupport::Open", OTHER);
 
     nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
     MOZ_RELEASE_ASSERT(ww);
 
@@ -1273,17 +1275,17 @@ nsWindow::GeckoViewSupport::Open(const j
             java::EventDispatcher::Ref::From(aDispatcher), nullptr);
     androidView->mSettings = java::GeckoBundle::Ref::From(aSettings);
 
     nsAutoCString chromeFlags("chrome,dialog=0,resizable,scrollbars");
     if (aPrivateMode) {
         chromeFlags += ",private";
     }
     nsCOMPtr<mozIDOMWindowProxy> domWindow;
-    ww->OpenWindow(nullptr, url.get(), nullptr, chromeFlags.get(),
+    ww->OpenWindow(nullptr, url.get(), aId->ToCString().get(), chromeFlags.get(),
                    androidView, getter_AddRefs(domWindow));
     MOZ_RELEASE_ASSERT(domWindow);
 
     nsCOMPtr<nsPIDOMWindowOuter> pdomWindow =
             nsPIDOMWindowOuter::From(domWindow);
     nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(pdomWindow);
     MOZ_ASSERT(widget);