Bug 1474454 - Use GeckoResult in GeckoSession.NavigationDelegate.onLoadRequest() r=jchen,droeh draft
authorJames Willcox <snorp@snorp.net>
Mon, 09 Jul 2018 15:24:55 -0500
changeset 815756 658d4e4197256dfe4fe4f275f4f9999a3c16d471
parent 815755 575f71e6889ce809f8106ed7eed5fbb92700ae57
child 815757 8e600d187d4c1dec68f4c19a8703b6fb4c7e7b32
push id115646
push userbmo:snorp@snorp.net
push dateMon, 09 Jul 2018 22:38:07 +0000
reviewersjchen, droeh
bugs1474454
milestone63.0a1
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
@@ -14,16 +14,17 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
 import org.junit.Assume.assumeThat
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class ContentDelegateTest : BaseSessionTest() {
 
     @Test fun titleChange() {
         sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
 
@@ -38,19 +39,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 GeckoResult.fromValue(false)
             }
 
             @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
@@ -15,16 +15,17 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class NavigationDelegateTest : BaseSessionTest() {
 
     @Setting(key = Setting.Key.USE_TRACKING_PROTECTION, value = "true")
     @Test fun trackingProtectionBasic() {
@@ -96,26 +97,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 GeckoResult.fromValue(false)
             }
 
             @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 +290,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 GeckoResult.fromValue(false)
             }
 
             @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 +339,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 GeckoResult.fromValue(false)
             }
 
             @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 +373,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 GeckoResult.fromValue(false)
             }
 
             @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 +406,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 +440,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 GeckoResult.fromValue(false)
             }
 
             @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 +469,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 GeckoResult.fromValue(false)
             }
 
             @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 +564,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 GeckoResult.fromValue(false)
             }
 
             @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
@@ -13,16 +13,17 @@ import android.support.test.filters.Medi
 import android.support.test.filters.LargeTest
 import android.support.test.runner.AndroidJUnit4
 
 import org.hamcrest.Matchers.*
 import org.junit.Assume.assumeThat
 import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 class ProgressDelegateTest : BaseSessionTest() {
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
@@ -57,25 +58,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 GeckoResult.fromValue(false)
             }
 
             @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
@@ -6,16 +6,17 @@
 package org.mozilla.geckoview.test.util
 
 import org.mozilla.geckoview.GeckoResponse
 import org.mozilla.geckoview.GeckoSession
 
 import android.view.inputmethod.CursorAnchorInfo
 import android.view.inputmethod.ExtractedText
 import android.view.inputmethod.ExtractedTextRequest
+import org.mozilla.geckoview.GeckoResult
 
 class Callbacks private constructor() {
     object Default : All
 
     interface All : ContentDelegate, NavigationDelegate, PermissionDelegate, ProgressDelegate,
                     PromptDelegate, ScrollDelegate, SelectionActionDelegate, TextInputDelegate,
                     TrackingProtectionDelegate
 
@@ -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 GeckoResult.fromValue(false)
         }
 
         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
@@ -201,23 +201,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);
@@ -2138,24 +2152,24 @@ 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. May be null.
          */
-        void onLoadRequest(GeckoSession session, String uri,
-                           @TargetWindow int target,
-                           @LoadRequestFlags int flags,
-                           GeckoResponse<Boolean> response);
+        GeckoResult<Boolean> onLoadRequest(GeckoSession session, 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);