diff options
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java new file mode 100644 index 0000000000..1546451056 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java @@ -0,0 +1,189 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: ts=4 sw=4 expandtab: + * 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; + +import androidx.annotation.AnyThread; +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Locale; +import org.mozilla.gecko.GeckoThread; +import org.mozilla.gecko.annotation.WrapForJNI; + +/** + * GeckoWebExecutor is responsible for fetching a {@link WebRequest} and delivering a {@link + * WebResponse} to the caller via {@link #fetch(WebRequest)}. Example: + * + * <pre> + * final GeckoWebExecutor executor = new GeckoWebExecutor(); + * + * final GeckoResult<WebResponse> result = executor.fetch( + * new WebRequest.Builder("https://example.org/json") + * .header("Accept", "application/json") + * .build()); + * + * result.then(response -> { + * // Do something with response + * }); + * </pre> + */ +@AnyThread +public class GeckoWebExecutor { + // We don't use this right now because we access GeckoThread directly, but + // it's future-proofing for a world where we allow multiple GeckoRuntimes. + private final GeckoRuntime mRuntime; + + @WrapForJNI(dispatchTo = "gecko", stubName = "Fetch") + private static native void nativeFetch( + WebRequest request, int flags, GeckoResult<WebResponse> result); + + @WrapForJNI(dispatchTo = "gecko", stubName = "Resolve") + private static native void nativeResolve(String host, GeckoResult<InetAddress[]> result); + + @WrapForJNI(calledFrom = "gecko", exceptionMode = "nsresult") + private static ByteBuffer createByteBuffer(final int capacity) { + return ByteBuffer.allocateDirect(capacity); + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FETCH_FLAGS_NONE, + FETCH_FLAGS_ANONYMOUS, + FETCH_FLAGS_NO_REDIRECTS, + FETCH_FLAGS_PRIVATE, + FETCH_FLAGS_STREAM_FAILURE_TEST, + }) + public @interface FetchFlags {} + + /** No special treatment. */ + public static final int FETCH_FLAGS_NONE = 0; + + /** Don't send cookies or other user data along with the request. */ + @WrapForJNI public static final int FETCH_FLAGS_ANONYMOUS = 1; + + /** Don't automatically follow redirects. */ + @WrapForJNI public static final int FETCH_FLAGS_NO_REDIRECTS = 1 << 1; + + // There was supposed to be another flag, which we then decided not to implement. + // That's the reason there's no value 1 << 2, and it can absolutely be used :) + + /** Associates this download with the current private browsing session */ + @WrapForJNI public static final int FETCH_FLAGS_PRIVATE = 1 << 3; + + /** This flag causes a read error in the {@link WebResponse} body. Useful for testing. */ + @WrapForJNI public static final int FETCH_FLAGS_STREAM_FAILURE_TEST = 1 << 10; + + /** + * Create a new GeckoWebExecutor instance. + * + * @param runtime A GeckoRuntime instance + */ + public GeckoWebExecutor(final @NonNull GeckoRuntime runtime) { + mRuntime = runtime; + } + + /** + * Send the given {@link WebRequest}. + * + * @param request A {@link WebRequest} instance + * @return A {@link GeckoResult} which will be completed with a {@link WebResponse}. If the + * request fails to complete, the {@link GeckoResult} will be completed exceptionally with a + * {@link WebRequestError}. + * @throws IllegalArgumentException if request is null or otherwise unusable. + */ + public @NonNull GeckoResult<WebResponse> fetch(final @NonNull WebRequest request) { + return fetch(request, FETCH_FLAGS_NONE); + } + + /** + * Send the given {@link WebRequest} with specified flags. + * + * @param request A {@link WebRequest} instance + * @param flags The specified flags. One or more of the {@link #FETCH_FLAGS_NONE FETCH_*} flags. + * @return A {@link GeckoResult} which will be completed with a {@link WebResponse}. If the + * request fails to complete, the {@link GeckoResult} will be completed exceptionally with a + * {@link WebRequestError}. + * @throws IllegalArgumentException if request is null or otherwise unusable. + */ + public @NonNull GeckoResult<WebResponse> fetch( + final @NonNull WebRequest request, final @FetchFlags int flags) { + if (request.body != null && !request.body.isDirect()) { + throw new IllegalArgumentException("Request body must be a direct ByteBuffer"); + } + + if (request.cacheMode < WebRequest.CACHE_MODE_FIRST + || request.cacheMode > WebRequest.CACHE_MODE_LAST) { + throw new IllegalArgumentException("Unknown cache mode"); + } + + final String uri = request.uri.toLowerCase(Locale.ROOT); + // We don't need to fully validate the URI here, just a sanity check + if (!uri.startsWith("http") && !uri.startsWith("blob")) { + throw new IllegalArgumentException( + "Unsupported URI scheme: " + (uri.length() > 10 ? uri.substring(0, 10) : uri)); + } + + final GeckoResult<WebResponse> result = new GeckoResult<>(); + + if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { + nativeFetch(request, flags, result); + } else { + GeckoThread.queueNativeCallUntil( + GeckoThread.State.PROFILE_READY, + this, + "nativeFetch", + WebRequest.class, + request, + flags, + GeckoResult.class, + result); + } + + return result; + } + + /** + * Resolves the specified host name. + * + * @param host An Internet host name, e.g. mozilla.org. + * @return A {@link GeckoResult} which will be fulfilled with a {@link List} of {@link + * InetAddress}. In case of failure, the {@link GeckoResult} will be completed exceptionally + * with a {@link java.net.UnknownHostException}. + */ + public @NonNull GeckoResult<InetAddress[]> resolve(final @NonNull String host) { + final GeckoResult<InetAddress[]> result = new GeckoResult<>(); + + if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) { + nativeResolve(host, result); + } else { + GeckoThread.queueNativeCallUntil( + GeckoThread.State.PROFILE_READY, + this, + "nativeResolve", + String.class, + host, + GeckoResult.class, + result); + } + return result; + } + + /** + * This causes a speculative connection to be made to the host in the specified URI. This is + * useful if an app thinks it may be making a request to that host in the near future. If no + * request is made, the connection will be cleaned up after an unspecified amount of time. + * + * @param uri A URI String. + */ + public void speculativeConnect(final @NonNull String uri) { + GeckoThread.speculativeConnect(uri); + } +} |