Bug 1474454 - Use GeckoResult in GeckoSession.NavigationDelegate.onLoadRequest() r=jchen,droeh
authorJames Willcox <snorp@snorp.net>
Mon, 09 Jul 2018 15:24:55 -0500
changeset 426654 3ebb68824d935128e81ae6f71c966c8f90bfe124
parent 426653 b1d67e1c64627e9457b542e309eb14f0d3dfb011
child 426655 714b1d874ec9f70de9a1c5560ddf823cde96cee3
push id34278
push useraciure@mozilla.com
push dateSun, 15 Jul 2018 09:53:15 +0000
treeherdermozilla-central@2a8f94a45fd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen, droeh
bugs1474454
milestone63.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 1474454 - Use GeckoResult in GeckoSession.NavigationDelegate.onLoadRequest() r=jchen,droeh MozReview-Commit-ID: CmdjYhqpZcZ
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -54,16 +54,17 @@ import org.mozilla.gecko.util.ActivityUt
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.PackageUtil;
 import org.mozilla.gecko.webapps.WebApps;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.geckoview.GeckoResponse;
+import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import java.util.List;
 
 public class CustomTabsActivity extends AppCompatActivity
@@ -595,31 +596,28 @@ public class CustomTabsActivity extends 
 
     @Override
     public void onCanGoForward(GeckoSession session, boolean canGoForward) {
         mCanGoForward = canGoForward;
         updateMenuItemForward();
     }
 
     @Override
-    public void onLoadRequest(final GeckoSession session, final String urlStr,
-                              final int target,
-                              final int flags,
-                              final GeckoResponse<Boolean> response) {
+    public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
+                                              final int target,
+                                              final int flags) {
         if (target != GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW) {
-            response.respond(false);
-            return;
+            return GeckoResult.fromValue(false);
         }
 
         final Uri uri = Uri.parse(urlStr);
         if (uri == null) {
             // We can't handle this, so deny it.
             Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
-            response.respond(true);
-            return;
+            return GeckoResult.fromValue(true);
         }
 
         // Always use Fennec for these schemes.
         if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()) ||
             "data".equals(uri.getScheme()) || "blob".equals(uri.getScheme())) {
             final Intent intent = new Intent(this, BrowserApp.class);
             intent.setAction(Intent.ACTION_VIEW);
             intent.setData(uri);
@@ -634,17 +632,17 @@ public class CustomTabsActivity extends 
             intent.setData(uri);
             try {
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
                 Log.w(LOGTAG, "No activity handler found for: " + urlStr);
             }
         }
 
-        response.respond(true);
+        return GeckoResult.fromValue(true);
     }
 
     @Override
     public void onNewSession(final GeckoSession session, final String uri,
                              final GeckoResponse<GeckoSession> response) {
         // We should never get here because we abort loads that need a new session in onLoadRequest()
         throw new IllegalStateException("Unexpected new session");
     }
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -34,16 +34,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.text.TextSelection;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.geckoview.GeckoResponse;
+import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 public class WebAppActivity extends AppCompatActivity
                             implements ActionModePresenter,
                                        GeckoSession.ContentDelegate,
@@ -378,39 +379,35 @@ public class WebAppActivity extends AppC
     }
 
     @Override // GeckoSession.ContentDelegate
     public void onFullScreen(GeckoSession session, boolean fullScreen) {
         updateFullScreenContent(fullScreen);
     }
 
     @Override
-    public void onLoadRequest(final GeckoSession session, final String urlStr,
-                              final int target,
-                              final int flags,
-                              final GeckoResponse<Boolean> response) {
+    public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
+                                              final int target,
+                                              final int flags) {
         final Uri uri = Uri.parse(urlStr);
         if (uri == null) {
             // We can't really handle this, so deny it?
             Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
-            response.respond(true);
-            return;
+            return GeckoResult.fromValue(true);
         }
 
         if (mManifest.isInScope(uri) && target != TARGET_WINDOW_NEW) {
             // This is in scope and wants to load in the same frame, so
             // let Gecko handle it.
-            response.respond(false);
-            return;
+            return GeckoResult.fromValue(false);
         }
 
         if ("javascript".equals(uri.getScheme())) {
             // These URIs will fail the scope check but should still be loaded in the PWA.
-            response.respond(false);
-            return;
+            return GeckoResult.fromValue(false);
         }
 
         if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()) ||
             "data".equals(uri.getScheme()) || "blob".equals(uri.getScheme())) {
             final CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder()
                 .addDefaultShareMenuItem()
                 .setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left)
                 .setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right);
@@ -428,17 +425,18 @@ public class WebAppActivity extends AppC
             intent.setAction(Intent.ACTION_VIEW);
             intent.setData(uri);
             try {
                 startActivity(intent);
             } catch (ActivityNotFoundException e) {
                 Log.w(LOGTAG, "No activity handler found for: " + urlStr);
             }
         }
-        response.respond(true);
+
+        return GeckoResult.fromValue(true);
     }
 
     @Override
     public void onNewSession(final GeckoSession session, final String uri,
                              final GeckoResponse<GeckoSession> response) {
         // We should never get here because we abort loads that need a new session in onLoadRequest()
         throw new IllegalStateException("Unexpected new session");
     }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -1,15 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
-import org.mozilla.geckoview.GeckoResponse
+import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 import org.mozilla.geckoview.test.util.Callbacks
 
@@ -38,19 +38,18 @@ class ContentDelegateTest : BaseSessionT
 
     @Test fun download() {
         sessionRule.session.loadTestPath(DOWNLOAD_HTML_PATH)
 
         sessionRule.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
 
             @AssertCalled(count = 2)
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int, flags: Int,
-                                       response: GeckoResponse<Boolean>) {
-                response.respond(false)
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
+                return null
             }
 
             @AssertCalled(false)
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
             }
 
             @AssertCalled(count = 1)
             override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) {
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -1,15 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
-import org.mozilla.geckoview.GeckoResponse
+import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
@@ -96,26 +96,24 @@ class NavigationDelegateTest : BaseSessi
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URI should not be null", uri, notNullValue())
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should not be null", where, notNullValue())
                 assertThat("Where should match", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
-                response.respond(false)
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("Session should not be null", session, notNullValue())
                 assertThat("URL should not be null", url, notNullValue())
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
@@ -291,23 +289,21 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.waitForPageStop()
 
         sessionRule.session.reload()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should match", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
-                response.respond(false)
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = [2])
@@ -342,23 +338,21 @@ class NavigationDelegateTest : BaseSessi
         })
 
         sessionRule.session.goBack()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
                 assertThat("Where should match", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
-                response.respond(false)
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = [2])
@@ -378,23 +372,21 @@ class NavigationDelegateTest : BaseSessi
         })
 
         sessionRule.session.goForward()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI should match", uri, endsWith(HELLO2_HTML_PATH))
                 assertThat("Where should match", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
-                response.respond(false)
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
 
             @AssertCalled(count = 1, order = [2])
@@ -413,20 +405,18 @@ class NavigationDelegateTest : BaseSessi
             }
         })
     }
 
     @Test fun onLoadUri_returnTrueCancelsLoad() {
         sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
-                response.respond(uri.endsWith(HELLO_HTML_PATH))
+                                       where: Int, flags: Int): GeckoResult<Boolean> {
+                return GeckoResult.fromValue(uri.endsWith(HELLO_HTML_PATH))
             }
         })
 
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.session.loadTestPath(HELLO2_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
@@ -449,20 +439,22 @@ class NavigationDelegateTest : BaseSessi
 
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.evaluateJS("window.open('newSession_child.html', '_blank')")
 
         sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
+            override fun onLoadRequest(session: GeckoSession, uri: String,
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
                 assertThat("Where should be correct", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
             }
         })
     }
@@ -476,20 +468,22 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
             // We get two onLoadRequest calls for the link click,
             // one when loading the URL and one when opening a new window.
             @AssertCalled(count = 2, order = [1])
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
+            override fun onLoadRequest(session: GeckoSession, uri: String,
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
                 assertThat("Where should be correct", where,
                            equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
+                return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
             }
         })
     }
@@ -569,33 +563,36 @@ class NavigationDelegateTest : BaseSessi
     @Test fun onNewSession_notCalledForHandledLoads() {
         // Disable popup blocker.
         sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
 
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
+            override fun onLoadRequest(session: GeckoSession, uri: String,
+                                       where: Int, flags: Int): GeckoResult<Boolean> {
                 // Pretend we handled the target="_blank" link click.
-                response.respond(uri.endsWith(NEW_SESSION_CHILD_HTML_PATH))
+                return GeckoResult.fromValue(uri.endsWith(NEW_SESSION_CHILD_HTML_PATH))
             }
         })
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
 
         // Assert that onNewSession was not called for the link click.
         sessionRule.session.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
-            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
+            override fun onLoadRequest(session: GeckoSession, uri: String,
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 assertThat("URI must match", uri,
                            endsWith(forEachCall(NEW_SESSION_CHILD_HTML_PATH, NEW_SESSION_HTML_PATH)))
+                return null
             }
 
             @AssertCalled(count = 0)
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
             }
         })
     }
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ProgressDelegateTest.kt
@@ -1,15 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
-import org.mozilla.geckoview.GeckoResponse
+import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.filters.LargeTest
 import android.support.test.runner.AndroidJUnit4
 
@@ -57,25 +57,23 @@ class ProgressDelegateTest : BaseSession
 
     fun loadExpectNetError(testUri: String) {
         sessionRule.session.loadUri(testUri);
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate {
             @AssertCalled(count = 2)
             override fun onLoadRequest(session: GeckoSession, uri: String,
-                                       where: Int,
-                                       flags: Int,
-                                       response: GeckoResponse<Boolean>) {
+                                       where: Int, flags: Int): GeckoResult<Boolean>? {
                 if (sessionRule.currentCall.counter == 1) {
                     assertThat("URI should be " + testUri, uri, equalTo(testUri));
                 } else {
                     assertThat("URI should be about:neterror", uri, startsWith("about:neterror"));
                 }
-                response.respond(false)
+                return null
             }
 
             @AssertCalled(count = 1)
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should fail", success, equalTo(false))
             }
         })
     }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -2,16 +2,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/. */
 
 package org.mozilla.geckoview.test;
 
 import org.mozilla.gecko.gfx.GeckoDisplay;
 import org.mozilla.geckoview.GeckoResponse;
+import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoRuntimeSettings;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -45,21 +46,20 @@ public class TestRunnerActivity extends 
         }
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean canGoForward) {
 
         }
 
         @Override
-        public void onLoadRequest(GeckoSession session, String uri, int target,
-                                  int flags,
-                                  GeckoResponse<Boolean> response) {
+        public GeckoResult<Boolean> onLoadRequest(GeckoSession session, String uri, int target,
+                                                  int flags) {
             // Allow Gecko to load all URIs
-            response.respond(false);
+            return GeckoResult.fromValue(false);
         }
 
         @Override
         public void onNewSession(GeckoSession session, String uri, GeckoResponse<GeckoSession> response) {
             response.respond(createBackgroundSession(session.getSettings()));
         }
     };
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.geckoview.test.util
 
 import org.mozilla.geckoview.GeckoResponse
+import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.GeckoSession
 
 import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.ExtractedTextRequest
 
 class Callbacks private constructor() {
     object Default : All
@@ -48,19 +49,18 @@ class Callbacks private constructor() {
 
         override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
         }
 
         override fun onCanGoForward(session: GeckoSession, canGoForward: Boolean) {
         }
 
         override fun onLoadRequest(session: GeckoSession, uri: String, where: Int,
-                                   flags: Int,
-                                   response: GeckoResponse<Boolean>) {
-            response.respond(false)
+                                   flags: Int): GeckoResult<Boolean>? {
+            return null
         }
 
         override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
             response.respond(null)
         }
     }
 
     interface PermissionDelegate : GeckoSession.PermissionDelegate {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -197,23 +197,37 @@ public class GeckoSession extends LayerS
                                          message.getBoolean("canGoBack"));
                     delegate.onCanGoForward(GeckoSession.this,
                                             message.getBoolean("canGoForward"));
                 } else if ("GeckoView:OnLoadRequest".equals(event)) {
                     final String uri = message.getString("uri");
                     final int where = convertGeckoTarget(message.getInt("where"));
                     final int flags = filterFlags(message.getInt("flags"));
 
-                    delegate.onLoadRequest(GeckoSession.this, uri, where, flags,
-                        new GeckoResponse<Boolean>() {
-                            @Override
-                            public void respond(Boolean handled) {
-                                callback.sendSuccess(handled);
-                            }
-                        });
+                    final GeckoResult<Boolean> result = delegate.onLoadRequest(GeckoSession.this,
+                            uri, where, flags);
+
+                    if (result == null) {
+                        callback.sendSuccess(null);
+                        return;
+                    }
+
+                    result.then(new GeckoResult.OnValueListener<Boolean, Void>() {
+                        @Override
+                        public GeckoResult<Void> onValue(Boolean value) throws Throwable {
+                            callback.sendSuccess(value);
+                            return null;
+                        }
+                    }, new GeckoResult.OnExceptionListener<Void>() {
+                        @Override
+                        public GeckoResult<Void> onException(Throwable exception) throws Throwable {
+                            callback.sendError(exception.getMessage());
+                            return null;
+                        }
+                    });
                 } else if ("GeckoView:OnNewSession".equals(event)) {
                     final String uri = message.getString("uri");
                     delegate.onNewSession(GeckoSession.this, uri,
                         new GeckoResponse<GeckoSession>() {
                             @Override
                             public void respond(GeckoSession session) {
                                 if (session == null) {
                                     callback.sendSuccess(null);
@@ -2218,24 +2232,26 @@ public class GeckoSession extends LayerS
          *
          * @param session The GeckoSession that initiated the callback.
          * @param uri The URI to be loaded.
          * @param target The target where the window has requested to open.
          *               One of {@link #TARGET_WINDOW_NONE TARGET_WINDOW_*}.
          * @param flags The load request flags.
          *              One or more of {@link #LOAD_REQUEST_IS_USER_TRIGGERED
          *              LOAD_REQUEST_*}.
-         * @param response A response which will state whether or not the load
-         *                 was handled. If unhandled, Gecko will continue the
-         *                 load as normal.
+         *
+         * @return A {@link GeckoResult} with a boolean value which indicates whether or
+         *         not the load was handled. If unhandled, Gecko will continue the
+         *         load as normal. If handled (true value), Gecko will abandon the load.
+         *         A null return value is interpreted as false (unhandled).
          */
-        void onLoadRequest(GeckoSession session, String uri,
-                           @TargetWindow int target,
-                           @LoadRequestFlags int flags,
-                           GeckoResponse<Boolean> response);
+        @Nullable GeckoResult<Boolean> onLoadRequest(@NonNull GeckoSession session,
+                                                     @NonNull String uri,
+                                                     @TargetWindow int target,
+                                                     @LoadRequestFlags int flags);
 
         /**
         * 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.
--- 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
@@ -2,16 +2,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/. */
 
 package org.mozilla.geckoview_example;
 
 import org.mozilla.geckoview.BasicSelectionActionDelegate;
 import org.mozilla.geckoview.GeckoResponse;
+import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoRuntimeSettings;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import android.Manifest;
@@ -556,22 +557,21 @@ public class GeckoViewActivity extends A
         }
 
         @Override
         public void onCanGoForward(GeckoSession session, boolean canGoForward) {
             mCanGoForward = canGoForward;
         }
 
         @Override
-        public void onLoadRequest(final GeckoSession session, final String uri,
-                                  final int target, final int flags,
-                                  GeckoResponse<Boolean> response) {
+        public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String uri,
+                                                  final int target, final int flags) {
             Log.d(LOGTAG, "onLoadRequest=" + uri + " where=" + target +
                   " flags=" + flags);
-            response.respond(false);
+            return GeckoResult.fromValue(false);
         }
 
         @Override
         public void onNewSession(final GeckoSession session, final String uri, GeckoResponse<GeckoSession> response) {
             GeckoSession newSession = new GeckoSession(session.getSettings());
             response.respond(newSession);
 
             Intent intent = new Intent(GeckoViewActivity.this, SessionActivity.class);