1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * vim: ts=4 sw=4 expandtab:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 package org.mozilla.geckoview;
8 
9 import android.annotation.TargetApi;
10 import android.content.ContentResolver;
11 import android.content.Context;
12 import android.database.Cursor;
13 import android.graphics.Bitmap;
14 import android.graphics.Matrix;
15 import android.graphics.Rect;
16 import android.graphics.RectF;
17 import android.net.Uri;
18 import android.os.Binder;
19 import android.os.Build;
20 import android.os.IInterface;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.os.SystemClock;
24 import android.text.TextUtils;
25 import android.util.Base64;
26 import android.util.Log;
27 import android.util.SparseArray;
28 import android.view.PointerIcon;
29 import android.view.Surface;
30 import android.view.View;
31 import android.view.ViewStructure;
32 import android.view.inputmethod.CursorAnchorInfo;
33 import android.view.inputmethod.ExtractedText;
34 import android.view.inputmethod.ExtractedTextRequest;
35 import androidx.annotation.AnyThread;
36 import androidx.annotation.IntDef;
37 import androidx.annotation.LongDef;
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.StringDef;
41 import androidx.annotation.UiThread;
42 import java.io.ByteArrayInputStream;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.lang.ref.WeakReference;
46 import java.security.cert.CertificateException;
47 import java.security.cert.CertificateFactory;
48 import java.security.cert.X509Certificate;
49 import java.util.AbstractSequentialList;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collection;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.ListIterator;
57 import java.util.Map;
58 import java.util.NoSuchElementException;
59 import java.util.Objects;
60 import java.util.Set;
61 import java.util.UUID;
62 import org.json.JSONException;
63 import org.json.JSONObject;
64 import org.mozilla.gecko.EventDispatcher;
65 import org.mozilla.gecko.GeckoAppShell;
66 import org.mozilla.gecko.GeckoThread;
67 import org.mozilla.gecko.IGeckoEditableParent;
68 import org.mozilla.gecko.NativeQueue;
69 import org.mozilla.gecko.annotation.WrapForJNI;
70 import org.mozilla.gecko.mozglue.JNIObject;
71 import org.mozilla.gecko.util.BundleEventListener;
72 import org.mozilla.gecko.util.EventCallback;
73 import org.mozilla.gecko.util.GeckoBundle;
74 import org.mozilla.gecko.util.IntentUtils;
75 import org.mozilla.gecko.util.ThreadUtils;
76 
77 public class GeckoSession {
78   private static final String LOGTAG = "GeckoSession";
79   private static final boolean DEBUG = false;
80 
81   // Type of changes given to onWindowChanged.
82   // Window has been cleared due to the session being closed.
83   private static final int WINDOW_CLOSE = 0;
84   // Window has been set due to the session being opened.
85   private static final int WINDOW_OPEN = 1; // Window has been opened.
86   // Window has been cleared due to the session being transferred to another session.
87   private static final int WINDOW_TRANSFER_OUT = 2; // Window has been transfer.
88   // Window has been set due to another session being transferred to this one.
89   private static final int WINDOW_TRANSFER_IN = 3;
90 
91   private static final int DATA_URI_MAX_LENGTH = 2 * 1024 * 1024;
92 
93   // Delay running compositor memory pressure by 10s to avoid interfering with tab switching.
94   private static final int NOTIFY_MEMORY_PRESSURE_DELAY_MS = 10 * 1000;
95 
96   private final Runnable mNotifyMemoryPressure =
97       new Runnable() {
98         @Override
99         public void run() {
100           if (mCompositorReady) {
101             mCompositor.notifyMemoryPressure();
102           }
103         }
104       };
105 
106   private enum State implements NativeQueue.State {
107     INITIAL(0),
108     READY(1);
109 
110     private final int mRank;
111 
State(final int rank)112     private State(final int rank) {
113       mRank = rank;
114     }
115 
116     @Override
is(final NativeQueue.State other)117     public boolean is(final NativeQueue.State other) {
118       return this == other;
119     }
120 
121     @Override
isAtLeast(final NativeQueue.State other)122     public boolean isAtLeast(final NativeQueue.State other) {
123       return (other instanceof State) && mRank >= ((State) other).mRank;
124     }
125   }
126 
127   private final NativeQueue mNativeQueue = new NativeQueue(State.INITIAL, State.READY);
128 
129   private final EventDispatcher mEventDispatcher = new EventDispatcher(mNativeQueue);
130 
131   private final SessionTextInput mTextInput = new SessionTextInput(this, mNativeQueue);
132   private SessionAccessibility mAccessibility;
133   private SessionFinder mFinder;
134 
135   private String mId;
getId()136   /* package */ String getId() {
137     return mId;
138   }
139 
140   private boolean mShouldPinOnScreen;
141 
142   // All fields are accessed on UI thread only.
143   private PanZoomController mPanZoomController = new PanZoomController(this);
144   private OverscrollEdgeEffect mOverscroll;
145   private CompositorController mController;
146   private Autofill.Support mAutofillSupport;
147 
148   private boolean mAttachedCompositor;
149   private boolean mCompositorReady;
150   private Surface mSurface;
151 
152   // All fields of coordinates are in screen units.
153   private int mLeft;
154   private int mTop; // Top of the surface (including toolbar);
155   private int mClientTop; // Top of the client area (i.e. excluding toolbar);
156   private int mOffsetX;
157   private int mOffsetY;
158   private int mWidth;
159   private int mHeight; // Height of the surface (including toolbar);
160   private int mClientHeight; // Height of the client area (i.e. excluding toolbar);
161   private int mFixedBottomOffset =
162       0; // The margin for fixed elements attached to the bottom of the viewport.
163   private int mDynamicToolbarMaxHeight = 0; // The maximum height of the dynamic toolbar
164   private float mViewportLeft;
165   private float mViewportTop;
166   private float mViewportZoom = 1.0f;
167 
168   //
169   // NOTE: These values are also defined in
170   // gfx/layers/ipc/UiCompositorControllerMessageTypes.h and must be kept in sync. Any
171   // new AnimatorMessageType added here must also be added there.
172   //
173   // Sent from compositor after first paint
174   /* package */ static final int FIRST_PAINT = 0;
175   // Sent from compositor when a layer has been updated
176   /* package */ static final int LAYERS_UPDATED = 1;
177   // Special message sent from UiCompositorControllerChild once it is open
178   /* package */ static final int COMPOSITOR_CONTROLLER_OPEN = 2;
179   // Special message sent from controller to query if the compositor controller is open.
180   /* package */ static final int IS_COMPOSITOR_CONTROLLER_OPEN = 3;
181 
182   /* protected */ class Compositor extends JNIObject {
isReady()183     public boolean isReady() {
184       return GeckoSession.this.isCompositorReady();
185     }
186 
187     @WrapForJNI(calledFrom = "ui")
onCompositorAttached()188     private void onCompositorAttached() {
189       GeckoSession.this.onCompositorAttached();
190     }
191 
192     @WrapForJNI(calledFrom = "ui")
onCompositorDetached()193     private void onCompositorDetached() {
194       // Clear out any pending calls on the UI thread.
195       GeckoSession.this.onCompositorDetached();
196     }
197 
198     @WrapForJNI(dispatchTo = "gecko")
199     @Override
disposeNative()200     protected native void disposeNative();
201 
202     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
attachNPZC(PanZoomController.NativeProvider npzc)203     public native void attachNPZC(PanZoomController.NativeProvider npzc);
204 
205     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
onBoundsChanged(int left, int top, int width, int height)206     public native void onBoundsChanged(int left, int top, int width, int height);
207 
208     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
setDynamicToolbarMaxHeight(int height)209     public native void setDynamicToolbarMaxHeight(int height);
210 
211     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
notifyMemoryPressure()212     public native void notifyMemoryPressure();
213 
214     // Gecko thread pauses compositor; blocks UI thread.
215     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
syncPauseCompositor()216     public native void syncPauseCompositor();
217 
218     // UI thread resumes compositor and notifies Gecko thread; does not block UI thread.
219     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
syncResumeResizeCompositor( int x, int y, int width, int height, Object surface)220     public native void syncResumeResizeCompositor(
221         int x, int y, int width, int height, Object surface);
222 
223     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
setMaxToolbarHeight(int height)224     public native void setMaxToolbarHeight(int height);
225 
226     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
setFixedBottomOffset(int offset)227     public native void setFixedBottomOffset(int offset);
228 
229     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
sendToolbarAnimatorMessage(int message)230     public native void sendToolbarAnimatorMessage(int message);
231 
232     @WrapForJNI(calledFrom = "ui")
recvToolbarAnimatorMessage(final int message)233     private void recvToolbarAnimatorMessage(final int message) {
234       GeckoSession.this.handleCompositorMessage(message);
235     }
236 
237     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
setDefaultClearColor(int color)238     public native void setDefaultClearColor(int color);
239 
240     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
requestScreenPixels( final GeckoResult<Bitmap> result, final Bitmap target, final int x, final int y, final int srcWidth, final int srcHeight, final int outWidth, final int outHeight)241     /* package */ native void requestScreenPixels(
242         final GeckoResult<Bitmap> result,
243         final Bitmap target,
244         final int x,
245         final int y,
246         final int srcWidth,
247         final int srcHeight,
248         final int outWidth,
249         final int outHeight);
250 
251     @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
enableLayerUpdateNotifications(boolean enable)252     public native void enableLayerUpdateNotifications(boolean enable);
253 
254     // The compositor invokes this function just before compositing a frame where the
255     // document is different from the document composited on the last frame. In these
256     // cases, the viewport information we have in Java is no longer valid and needs to
257     // be replaced with the new viewport information provided.
258     @WrapForJNI(calledFrom = "ui")
updateRootFrameMetrics( final float scrollX, final float scrollY, final float zoom)259     private void updateRootFrameMetrics(
260         final float scrollX, final float scrollY, final float zoom) {
261       GeckoSession.this.onMetricsChanged(scrollX, scrollY, zoom);
262     }
263 
264     @WrapForJNI(calledFrom = "ui")
updateOverscrollVelocity(final float x, final float y)265     private void updateOverscrollVelocity(final float x, final float y) {
266       GeckoSession.this.updateOverscrollVelocity(x, y);
267     }
268 
269     @WrapForJNI(calledFrom = "ui")
updateOverscrollOffset(final float x, final float y)270     private void updateOverscrollOffset(final float x, final float y) {
271       GeckoSession.this.updateOverscrollOffset(x, y);
272     }
273 
274     @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
onSafeAreaInsetsChanged(int top, int right, int bottom, int left)275     public native void onSafeAreaInsetsChanged(int top, int right, int bottom, int left);
276 
277     @WrapForJNI(calledFrom = "ui")
setPointerIcon( final int defaultCursor, final Bitmap customCursor, final float x, final float y)278     public void setPointerIcon(
279         final int defaultCursor, final Bitmap customCursor, final float x, final float y) {
280       GeckoSession.this.setPointerIcon(defaultCursor, customCursor, x, y);
281     }
282 
283     @Override
finalize()284     protected void finalize() throws Throwable {
285       disposeNative();
286     }
287   }
288 
289   /* package */ final Compositor mCompositor = new Compositor();
290 
291   @WrapForJNI(stubName = "GetCompositor", calledFrom = "ui")
getCompositorFromNative()292   private Object getCompositorFromNative() {
293     // Only used by native code.
294     return mCompositorReady ? mCompositor : null;
295   }
296 
297   private final GeckoSessionHandler<HistoryDelegate> mHistoryHandler =
298       new GeckoSessionHandler<HistoryDelegate>(
299           "GeckoViewHistory",
300           this,
301           new String[] {
302             "GeckoView:OnVisited", "GeckoView:GetVisited", "GeckoView:StateUpdated",
303           }) {
304         @Override
305         public void handleMessage(
306             final HistoryDelegate delegate,
307             final String event,
308             final GeckoBundle message,
309             final EventCallback callback) {
310           if ("GeckoView:OnVisited".equals(event)) {
311             final GeckoResult<Boolean> result =
312                 delegate.onVisited(
313                     GeckoSession.this,
314                     message.getString("url"),
315                     message.getString("lastVisitedURL"),
316                     message.getInt("flags"));
317 
318             if (result == null) {
319               callback.sendSuccess(false);
320               return;
321             }
322 
323             result.accept(
324                 visited -> callback.sendSuccess(visited.booleanValue()),
325                 exception -> callback.sendSuccess(false));
326           } else if ("GeckoView:GetVisited".equals(event)) {
327             final String[] urls = message.getStringArray("urls");
328 
329             final GeckoResult<boolean[]> result = delegate.getVisited(GeckoSession.this, urls);
330 
331             if (result == null) {
332               callback.sendSuccess(null);
333               return;
334             }
335 
336             result.accept(
337                 visited -> callback.sendSuccess(visited),
338                 exception -> callback.sendError("Failed to fetch visited statuses for URIs"));
339           } else if ("GeckoView:StateUpdated".equals(event)) {
340 
341             final GeckoBundle update = message.getBundle("data");
342 
343             if (update == null) {
344               return;
345             }
346             final int previousHistorySize = mStateCache.size();
347             mStateCache.updateSessionState(update);
348 
349             final ProgressDelegate progressDelegate = getProgressDelegate();
350             if (progressDelegate != null) {
351               final SessionState state = new SessionState(mStateCache);
352               if (!state.isEmpty()) {
353                 progressDelegate.onSessionStateChange(GeckoSession.this, state);
354               }
355             }
356 
357             if (update.getBundle("historychange") != null) {
358               final SessionState state = new SessionState(mStateCache);
359 
360               delegate.onHistoryStateChange(GeckoSession.this, state);
361 
362               // If the previous history was larger than one entry and the new size is one, it means
363               // the
364               // History has been purged and the navigation delegate needs to be update.
365               if ((previousHistorySize > 1)
366                   && (state.size() == 1)
367                   && mNavigationHandler.getDelegate() != null) {
368                 mNavigationHandler.getDelegate().onCanGoForward(GeckoSession.this, false);
369                 mNavigationHandler.getDelegate().onCanGoBack(GeckoSession.this, false);
370               }
371             }
372           }
373         }
374       };
375 
376   private final WebExtension.SessionController mWebExtensionController;
377 
378   private final GeckoSessionHandler<ContentDelegate> mContentHandler =
379       new GeckoSessionHandler<ContentDelegate>(
380           "GeckoViewContent",
381           this,
382           new String[] {
383             "GeckoView:ContentCrash",
384             "GeckoView:ContentKill",
385             "GeckoView:ContextMenu",
386             "GeckoView:DOMMetaViewportFit",
387             "GeckoView:PageTitleChanged",
388             "GeckoView:DOMWindowClose",
389             "GeckoView:ExternalResponse",
390             "GeckoView:FocusRequest",
391             "GeckoView:FullScreenEnter",
392             "GeckoView:FullScreenExit",
393             "GeckoView:WebAppManifest",
394             "GeckoView:FirstContentfulPaint",
395             "GeckoView:PaintStatusReset",
396             "GeckoView:PreviewImage",
397           }) {
398         @Override
399         public void handleMessage(
400             final ContentDelegate delegate,
401             final String event,
402             final GeckoBundle message,
403             final EventCallback callback) {
404           if ("GeckoView:ContentCrash".equals(event)) {
405             close();
406             delegate.onCrash(GeckoSession.this);
407           } else if ("GeckoView:ContentKill".equals(event)) {
408             close();
409             delegate.onKill(GeckoSession.this);
410           } else if ("GeckoView:ContextMenu".equals(event)) {
411             final ContentDelegate.ContextElement elem =
412                 new ContentDelegate.ContextElement(
413                     message.getString("baseUri"),
414                     message.getString("uri"),
415                     message.getString("title"),
416                     message.getString("alt"),
417                     message.getString("elementType"),
418                     message.getString("elementSrc"));
419 
420             delegate.onContextMenu(
421                 GeckoSession.this, message.getInt("screenX"), message.getInt("screenY"), elem);
422 
423           } else if ("GeckoView:DOMMetaViewportFit".equals(event)) {
424             delegate.onMetaViewportFitChange(GeckoSession.this, message.getString("viewportfit"));
425           } else if ("GeckoView:PageTitleChanged".equals(event)) {
426             delegate.onTitleChange(GeckoSession.this, message.getString("title"));
427           } else if ("GeckoView:FocusRequest".equals(event)) {
428             delegate.onFocusRequest(GeckoSession.this);
429           } else if ("GeckoView:DOMWindowClose".equals(event)) {
430             delegate.onCloseRequest(GeckoSession.this);
431           } else if ("GeckoView:FullScreenEnter".equals(event)) {
432             delegate.onFullScreen(GeckoSession.this, true);
433           } else if ("GeckoView:FullScreenExit".equals(event)) {
434             delegate.onFullScreen(GeckoSession.this, false);
435           } else if ("GeckoView:WebAppManifest".equals(event)) {
436             final GeckoBundle manifest = message.getBundle("manifest");
437             if (manifest == null) {
438               return;
439             }
440 
441             try {
442               delegate.onWebAppManifest(
443                   GeckoSession.this, fixupWebAppManifest(manifest.toJSONObject()));
444             } catch (final JSONException e) {
445               Log.e(LOGTAG, "Failed to convert web app manifest to JSON", e);
446             }
447           } else if ("GeckoView:FirstContentfulPaint".equals(event)) {
448             delegate.onFirstContentfulPaint(GeckoSession.this);
449           } else if ("GeckoView:PaintStatusReset".equals(event)) {
450             delegate.onPaintStatusReset(GeckoSession.this);
451           } else if ("GeckoView:PreviewImage".equals(event)) {
452             delegate.onPreviewImage(GeckoSession.this, message.getString("previewImageUrl"));
453           }
454         }
455       };
456 
457   private final GeckoSessionHandler<NavigationDelegate> mNavigationHandler =
458       new GeckoSessionHandler<NavigationDelegate>(
459           "GeckoViewNavigation",
460           this,
461           new String[] {"GeckoView:LocationChange", "GeckoView:OnNewSession"},
462           new String[] {
463             "GeckoView:OnLoadError", "GeckoView:OnLoadRequest",
464           }) {
465         // This needs to match nsIBrowserDOMWindow.idl
466         private int convertGeckoTarget(final int geckoTarget) {
467           switch (geckoTarget) {
468             case 0: // OPEN_DEFAULTWINDOW
469             case 1: // OPEN_CURRENTWINDOW
470               return NavigationDelegate.TARGET_WINDOW_CURRENT;
471             default: // OPEN_NEWWINDOW, OPEN_NEWTAB
472               return NavigationDelegate.TARGET_WINDOW_NEW;
473           }
474         }
475 
476         @Override
477         public void handleDefaultMessage(
478             final String event, final GeckoBundle message, final EventCallback callback) {
479 
480           if ("GeckoView:OnLoadRequest".equals(event)) {
481             callback.sendSuccess(false);
482           } else if ("GeckoView:OnLoadError".equals(event)) {
483             callback.sendSuccess(null);
484           } else {
485             super.handleDefaultMessage(event, message, callback);
486           }
487         }
488 
489         @Override
490         public void handleMessage(
491             final NavigationDelegate delegate,
492             final String event,
493             final GeckoBundle message,
494             final EventCallback callback) {
495           Log.d(LOGTAG, "handleMessage " + event + " uri=" + message.getString("uri"));
496           if ("GeckoView:LocationChange".equals(event)) {
497             if (message.getBoolean("isTopLevel")) {
498               final GeckoBundle[] perms = message.getBundleArray("permissions");
499               final List<PermissionDelegate.ContentPermission> permList =
500                   PermissionDelegate.ContentPermission.fromBundleArray(perms);
501               delegate.onLocationChange(GeckoSession.this, message.getString("uri"), permList);
502             }
503             delegate.onCanGoBack(GeckoSession.this, message.getBoolean("canGoBack"));
504             delegate.onCanGoForward(GeckoSession.this, message.getBoolean("canGoForward"));
505           } else if ("GeckoView:OnLoadRequest".equals(event)) {
506             final NavigationDelegate.LoadRequest request =
507                 new NavigationDelegate.LoadRequest(
508                     message.getString("uri"),
509                     message.getString("triggerUri"),
510                     message.getInt("where"),
511                     message.getInt("flags"),
512                     message.getBoolean("hasUserGesture"),
513                     /* isDirectNavigation */ false);
514 
515             if (!IntentUtils.isUriSafeForScheme(request.uri)) {
516               callback.sendError("Blocked unsafe intent URI");
517 
518               delegate.onLoadError(
519                   GeckoSession.this,
520                   request.uri,
521                   new WebRequestError(
522                       WebRequestError.ERROR_MALFORMED_URI,
523                       WebRequestError.ERROR_CATEGORY_URI,
524                       null));
525 
526               return;
527             }
528 
529             final GeckoResult<AllowOrDeny> result =
530                 delegate.onLoadRequest(GeckoSession.this, request);
531 
532             if (result == null) {
533               callback.sendSuccess(null);
534               return;
535             }
536 
537             callback.resolveTo(
538                 result.map(
539                     value -> {
540                       ThreadUtils.assertOnUiThread();
541                       if (value == AllowOrDeny.ALLOW) {
542                         return false;
543                       }
544                       if (value == AllowOrDeny.DENY) {
545                         return true;
546                       }
547                       throw new IllegalArgumentException("Invalid response");
548                     }));
549           } else if ("GeckoView:OnLoadError".equals(event)) {
550             final String uri = message.getString("uri");
551             final long errorCode = message.getLong("error");
552             final int errorModule = message.getInt("errorModule");
553             final int errorClass = message.getInt("errorClass");
554 
555             final WebRequestError err =
556                 WebRequestError.fromGeckoError(errorCode, errorModule, errorClass, null);
557 
558             final GeckoResult<String> result = delegate.onLoadError(GeckoSession.this, uri, err);
559             if (result == null) {
560               callback.sendError("abort");
561               return;
562             }
563 
564             callback.resolveTo(
565                 result.map(
566                     url -> {
567                       if (url == null) {
568                         throw new IllegalArgumentException("abort");
569                       }
570                       return url;
571                     }));
572           } else if ("GeckoView:OnNewSession".equals(event)) {
573             final String uri = message.getString("uri");
574             final GeckoResult<GeckoSession> result = delegate.onNewSession(GeckoSession.this, uri);
575             if (result == null) {
576               callback.sendSuccess(false);
577               return;
578             }
579 
580             final String newSessionId = message.getString("newSessionId");
581             callback.resolveTo(
582                 result.map(
583                     session -> {
584                       ThreadUtils.assertOnUiThread();
585                       if (session == null) {
586                         return false;
587                       }
588 
589                       if (session.isOpen()) {
590                         throw new AssertionError("Must use an unopened GeckoSession instance");
591                       }
592 
593                       if (GeckoSession.this.mWindow == null) {
594                         throw new IllegalArgumentException("Session is not attached to a window");
595                       }
596 
597                       session.open(GeckoSession.this.mWindow.runtime, newSessionId);
598                       return true;
599                     }));
600           }
601         }
602       };
603 
604   private final GeckoSessionHandler<ContentDelegate> mProcessHangHandler =
605       new GeckoSessionHandler<ContentDelegate>(
606           "GeckoViewProcessHangMonitor", this, new String[] {"GeckoView:HangReport"}) {
607 
608         @Override
609         protected void handleMessage(
610             final ContentDelegate delegate,
611             final String event,
612             final GeckoBundle message,
613             final EventCallback eventCallback) {
614           Log.d(LOGTAG, "handleMessage " + event + " uri=" + message.getString("uri"));
615 
616           final GeckoResult<SlowScriptResponse> result =
617               delegate.onSlowScript(GeckoSession.this, message.getString("scriptFileName"));
618           if (result != null) {
619             final int mReportId = message.getInt("hangId");
620             result.accept(
621                 stopOrContinue -> {
622                   if (stopOrContinue != null) {
623                     final GeckoBundle bundle = new GeckoBundle();
624                     bundle.putInt("hangId", mReportId);
625                     switch (stopOrContinue) {
626                       case STOP:
627                         mEventDispatcher.dispatch("GeckoView:HangReportStop", bundle);
628                         break;
629                       case CONTINUE:
630                         mEventDispatcher.dispatch("GeckoView:HangReportWait", bundle);
631                         break;
632                     }
633                   }
634                 });
635           } else {
636             // default to stopping the script
637             final GeckoBundle bundle = new GeckoBundle();
638             bundle.putInt("hangId", message.getInt("hangId"));
639             mEventDispatcher.dispatch("GeckoView:HangReportStop", bundle);
640           }
641         }
642       };
643 
644   private final GeckoSessionHandler<ProgressDelegate> mProgressHandler =
645       new GeckoSessionHandler<ProgressDelegate>(
646           "GeckoViewProgress",
647           this,
648           new String[] {
649             "GeckoView:PageStart",
650             "GeckoView:PageStop",
651             "GeckoView:ProgressChanged",
652             "GeckoView:SecurityChanged",
653             "GeckoView:StateUpdated",
654           }) {
655         @Override
656         public void handleMessage(
657             final ProgressDelegate delegate,
658             final String event,
659             final GeckoBundle message,
660             final EventCallback callback) {
661           Log.d(LOGTAG, "handleMessage " + event + " uri=" + message.getString("uri"));
662           if ("GeckoView:PageStart".equals(event)) {
663             delegate.onPageStart(GeckoSession.this, message.getString("uri"));
664           } else if ("GeckoView:PageStop".equals(event)) {
665             delegate.onPageStop(GeckoSession.this, message.getBoolean("success"));
666           } else if ("GeckoView:ProgressChanged".equals(event)) {
667             delegate.onProgressChange(GeckoSession.this, message.getInt("progress"));
668           } else if ("GeckoView:SecurityChanged".equals(event)) {
669             final GeckoBundle identity = message.getBundle("identity");
670             delegate.onSecurityChange(
671                 GeckoSession.this, new ProgressDelegate.SecurityInformation(identity));
672           } else if ("GeckoView:StateUpdated".equals(event)) {
673             final GeckoBundle update = message.getBundle("data");
674             if (update != null) {
675               if (getHistoryDelegate() == null) {
676                 mStateCache.updateSessionState(update);
677                 final SessionState state = new SessionState(mStateCache);
678                 if (!state.isEmpty()) {
679                   delegate.onSessionStateChange(GeckoSession.this, state);
680                 }
681               }
682             }
683           }
684         }
685       };
686 
687   private final GeckoSessionHandler<ScrollDelegate> mScrollHandler =
688       new GeckoSessionHandler<ScrollDelegate>(
689           "GeckoViewScroll", this, new String[] {"GeckoView:ScrollChanged"}) {
690         @Override
691         public void handleMessage(
692             final ScrollDelegate delegate,
693             final String event,
694             final GeckoBundle message,
695             final EventCallback callback) {
696 
697           if ("GeckoView:ScrollChanged".equals(event)) {
698             delegate.onScrollChanged(
699                 GeckoSession.this, message.getInt("scrollX"), message.getInt("scrollY"));
700           }
701         }
702       };
703 
704   private final GeckoSessionHandler<ContentBlocking.Delegate> mContentBlockingHandler =
705       new GeckoSessionHandler<ContentBlocking.Delegate>(
706           "GeckoViewContentBlocking", this, new String[] {"GeckoView:ContentBlockingEvent"}) {
707         @Override
708         public void handleMessage(
709             final ContentBlocking.Delegate delegate,
710             final String event,
711             final GeckoBundle message,
712             final EventCallback callback) {
713 
714           if ("GeckoView:ContentBlockingEvent".equals(event)) {
715             final ContentBlocking.BlockEvent be = ContentBlocking.BlockEvent.fromBundle(message);
716             if (be.isBlocking()) {
717               delegate.onContentBlocked(GeckoSession.this, be);
718             } else {
719               delegate.onContentLoaded(GeckoSession.this, be);
720             }
721           }
722         }
723       };
724 
725   private final GeckoSessionHandler<PermissionDelegate> mPermissionHandler =
726       new GeckoSessionHandler<PermissionDelegate>(
727           "GeckoViewPermission",
728           this,
729           new String[] {
730             "GeckoView:AndroidPermission",
731             "GeckoView:ContentPermission",
732             "GeckoView:MediaPermission"
733           }) {
734         @Override
735         public void handleMessage(
736             final PermissionDelegate delegate,
737             final String event,
738             final GeckoBundle message,
739             final EventCallback callback) {
740           if (delegate == null) {
741             callback.sendSuccess(/* granted */ false);
742             return;
743           }
744           if ("GeckoView:AndroidPermission".equals(event)) {
745             delegate.onAndroidPermissionsRequest(
746                 GeckoSession.this,
747                 message.getStringArray("perms"),
748                 new PermissionCallback("android", callback));
749           } else if ("GeckoView:ContentPermission".equals(event)) {
750             final GeckoResult<Integer> res =
751                 delegate.onContentPermissionRequest(
752                     GeckoSession.this, new PermissionDelegate.ContentPermission(message));
753             if (res == null) {
754               callback.sendSuccess(PermissionDelegate.ContentPermission.VALUE_PROMPT);
755               return;
756             }
757 
758             callback.resolveTo(res);
759           } else if ("GeckoView:MediaPermission".equals(event)) {
760             final GeckoBundle[] videoBundles = message.getBundleArray("video");
761             final GeckoBundle[] audioBundles = message.getBundleArray("audio");
762             PermissionDelegate.MediaSource[] videos = null;
763             PermissionDelegate.MediaSource[] audios = null;
764 
765             if (videoBundles != null) {
766               videos = new PermissionDelegate.MediaSource[videoBundles.length];
767               for (int i = 0; i < videoBundles.length; i++) {
768                 videos[i] = new PermissionDelegate.MediaSource(videoBundles[i]);
769               }
770             }
771 
772             if (audioBundles != null) {
773               audios = new PermissionDelegate.MediaSource[audioBundles.length];
774               for (int i = 0; i < audioBundles.length; i++) {
775                 audios[i] = new PermissionDelegate.MediaSource(audioBundles[i]);
776               }
777             }
778 
779             delegate.onMediaPermissionRequest(
780                 GeckoSession.this,
781                 message.getString("uri"),
782                 videos,
783                 audios,
784                 new PermissionCallback("media", callback));
785           }
786         }
787       };
788 
789   private final GeckoSessionHandler<SelectionActionDelegate> mSelectionActionDelegate =
790       new GeckoSessionHandler<SelectionActionDelegate>(
791           "GeckoViewSelectionAction",
792           this,
793           new String[] {
794             "GeckoView:HideSelectionAction", "GeckoView:ShowSelectionAction",
795           }) {
796         @Override
797         public void handleMessage(
798             final SelectionActionDelegate delegate,
799             final String event,
800             final GeckoBundle message,
801             final EventCallback callback) {
802           if ("GeckoView:ShowSelectionAction".equals(event)) {
803             final @SelectionActionDelegateAction HashSet<String> actionsSet =
804                 new HashSet<>(Arrays.asList(message.getStringArray("actions")));
805             final SelectionActionDelegate.Selection selection =
806                 new SelectionActionDelegate.Selection(message, actionsSet, callback);
807 
808             delegate.onShowActionRequest(GeckoSession.this, selection);
809 
810           } else if ("GeckoView:HideSelectionAction".equals(event)) {
811             final String reasonString = message.getString("reason");
812             final int reason;
813             if ("invisibleselection".equals(reasonString)) {
814               reason = SelectionActionDelegate.HIDE_REASON_INVISIBLE_SELECTION;
815             } else if ("presscaret".equals(reasonString)) {
816               reason = SelectionActionDelegate.HIDE_REASON_ACTIVE_SELECTION;
817             } else if ("scroll".equals(reasonString)) {
818               reason = SelectionActionDelegate.HIDE_REASON_ACTIVE_SCROLL;
819             } else if ("visibilitychange".equals(reasonString)) {
820               reason = SelectionActionDelegate.HIDE_REASON_NO_SELECTION;
821             } else {
822               throw new IllegalArgumentException();
823             }
824 
825             delegate.onHideAction(GeckoSession.this, reason);
826           }
827         }
828       };
829 
830   private final GeckoSessionHandler<MediaDelegate> mMediaHandler =
831       new GeckoSessionHandler<MediaDelegate>(
832           "GeckoViewMedia",
833           this,
834           new String[] {
835             "GeckoView:MediaRecordingStatusChanged",
836           }) {
837         @Override
838         public void handleMessage(
839             final MediaDelegate delegate,
840             final String event,
841             final GeckoBundle message,
842             final EventCallback callback) {
843           if ("GeckoView:MediaRecordingStatusChanged".equals(event)) {
844             final GeckoBundle[] deviceBundles = message.getBundleArray("devices");
845             final MediaDelegate.RecordingDevice[] devices =
846                 new MediaDelegate.RecordingDevice[deviceBundles.length];
847             for (int i = 0; i < deviceBundles.length; i++) {
848               devices[i] = new MediaDelegate.RecordingDevice(deviceBundles[i]);
849             }
850             delegate.onRecordingStatusChanged(GeckoSession.this, devices);
851             return;
852           }
853         }
854       };
855 
856   private final MediaSession.Handler mMediaSessionHandler = new MediaSession.Handler(this);
857 
858   /* package */ int handlersCount;
859 
860   private final GeckoSessionHandler<?>[] mSessionHandlers =
861       new GeckoSessionHandler<?>[] {
862         mContentHandler, mHistoryHandler, mMediaHandler,
863         mNavigationHandler, mPermissionHandler, mProcessHangHandler,
864         mProgressHandler, mScrollHandler, mSelectionActionDelegate,
865         mContentBlockingHandler, mMediaSessionHandler
866       };
867 
868   private static class PermissionCallback
869       implements PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
870 
871     private final String mType;
872     private EventCallback mCallback;
873 
PermissionCallback(final String type, final EventCallback callback)874     public PermissionCallback(final String type, final EventCallback callback) {
875       mType = type;
876       mCallback = callback;
877     }
878 
submit(final Object response)879     private void submit(final Object response) {
880       if (mCallback != null) {
881         mCallback.sendSuccess(response);
882         mCallback = null;
883       }
884     }
885 
886     @Override // PermissionDelegate.Callback
grant()887     public void grant() {
888       if ("media".equals(mType)) {
889         throw new UnsupportedOperationException();
890       }
891       submit(/* response */ true);
892     }
893 
894     @Override // PermissionDelegate.Callback, PermissionDelegate.MediaCallback
reject()895     public void reject() {
896       submit(/* response */ false);
897     }
898 
899     @Override // PermissionDelegate.MediaCallback
grant(final String video, final String audio)900     public void grant(final String video, final String audio) {
901       if (!"media".equals(mType)) {
902         throw new UnsupportedOperationException();
903       }
904       final GeckoBundle response = new GeckoBundle(2);
905       response.putString("video", video);
906       response.putString("audio", audio);
907       submit(response);
908     }
909 
910     @Override // PermissionDelegate.MediaCallback
grant( final PermissionDelegate.MediaSource video, final PermissionDelegate.MediaSource audio)911     public void grant(
912         final PermissionDelegate.MediaSource video, final PermissionDelegate.MediaSource audio) {
913       grant(video != null ? video.id : null, audio != null ? audio.id : null);
914     }
915   }
916 
917   /**
918    * Get the current user agent string for this GeckoSession.
919    *
920    * @return a {@link GeckoResult} containing the UserAgent string
921    */
922   @AnyThread
getUserAgent()923   public @NonNull GeckoResult<String> getUserAgent() {
924     return mEventDispatcher.queryString("GeckoView:GetUserAgent");
925   }
926 
927   /**
928    * Get the default user agent for this GeckoView build.
929    *
930    * <p>This method does not account for any override that might have been applied to the user agent
931    * string.
932    *
933    * @return the default user agent string
934    */
935   @AnyThread
getDefaultUserAgent()936   public static @NonNull String getDefaultUserAgent() {
937     return BuildConfig.USER_AGENT_GECKOVIEW_MOBILE;
938   }
939 
940   /**
941    * Get the current permission delegate for this GeckoSession.
942    *
943    * @return PermissionDelegate instance or null if using default delegate.
944    */
945   @UiThread
getPermissionDelegate()946   public @Nullable PermissionDelegate getPermissionDelegate() {
947     ThreadUtils.assertOnUiThread();
948     return mPermissionHandler.getDelegate();
949   }
950 
951   /**
952    * Set the current permission delegate for this GeckoSession.
953    *
954    * @param delegate PermissionDelegate instance or null to use the default delegate.
955    */
956   @UiThread
setPermissionDelegate(final @Nullable PermissionDelegate delegate)957   public void setPermissionDelegate(final @Nullable PermissionDelegate delegate) {
958     ThreadUtils.assertOnUiThread();
959     mPermissionHandler.setDelegate(delegate, this);
960   }
961 
962   private PromptDelegate mPromptDelegate;
963 
964   private final Listener mListener = new Listener();
965 
966   /* package */ static final class Window extends JNIObject implements IInterface {
967     public final GeckoRuntime runtime;
968     private WeakReference<GeckoSession> mOwner;
969     private NativeQueue mNativeQueue;
970     private Binder mBinder;
971 
Window( final @NonNull GeckoRuntime runtime, final @NonNull GeckoSession owner, final @NonNull NativeQueue nativeQueue)972     public Window(
973         final @NonNull GeckoRuntime runtime,
974         final @NonNull GeckoSession owner,
975         final @NonNull NativeQueue nativeQueue) {
976       this.runtime = runtime;
977       mOwner = new WeakReference<>(owner);
978       mNativeQueue = nativeQueue;
979     }
980 
981     @Override // IInterface
asBinder()982     public Binder asBinder() {
983       if (mBinder == null) {
984         mBinder = new Binder();
985         mBinder.attachInterface(this, Window.class.getName());
986       }
987       return mBinder;
988     }
989 
990     // Create a new Gecko window and assign an initial set of Java session objects to it.
991     @WrapForJNI(dispatchTo = "proxy")
open( Window instance, NativeQueue queue, Compositor compositor, EventDispatcher dispatcher, SessionAccessibility.NativeProvider sessionAccessibility, GeckoBundle initData, String id, String chromeUri, boolean privateMode)992     public static native void open(
993         Window instance,
994         NativeQueue queue,
995         Compositor compositor,
996         EventDispatcher dispatcher,
997         SessionAccessibility.NativeProvider sessionAccessibility,
998         GeckoBundle initData,
999         String id,
1000         String chromeUri,
1001         boolean privateMode);
1002 
1003     @Override // JNIObject
disposeNative()1004     public void disposeNative() {
1005       if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
1006         nativeDisposeNative();
1007       } else {
1008         GeckoThread.queueNativeCallUntil(
1009             GeckoThread.State.PROFILE_READY, this, "nativeDisposeNative");
1010       }
1011     }
1012 
1013     @WrapForJNI(dispatchTo = "proxy", stubName = "DisposeNative")
nativeDisposeNative()1014     private native void nativeDisposeNative();
1015 
1016     // Force the underlying Gecko window to close and release assigned Java objects.
close()1017     public void close() {
1018       // Reset our queue, so we don't end up with queued calls on a disposed object.
1019       synchronized (this) {
1020         if (mNativeQueue == null) {
1021           // Already closed elsewhere.
1022           return;
1023         }
1024         mNativeQueue.reset(State.INITIAL);
1025         mNativeQueue = null;
1026         mOwner = new WeakReference<>(null);
1027       }
1028 
1029       // Detach ourselves from the binder as well, to prevent this window from being
1030       // read from any parcels.
1031       asBinder().attachInterface(null, Window.class.getName());
1032 
1033       if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
1034         nativeClose();
1035       } else {
1036         GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, this, "nativeClose");
1037       }
1038     }
1039 
1040     @WrapForJNI(dispatchTo = "proxy", stubName = "Close")
nativeClose()1041     private native void nativeClose();
1042 
1043     @WrapForJNI(dispatchTo = "proxy", stubName = "Transfer")
nativeTransfer( NativeQueue queue, Compositor compositor, EventDispatcher dispatcher, SessionAccessibility.NativeProvider sessionAccessibility, GeckoBundle initData)1044     private native void nativeTransfer(
1045         NativeQueue queue,
1046         Compositor compositor,
1047         EventDispatcher dispatcher,
1048         SessionAccessibility.NativeProvider sessionAccessibility,
1049         GeckoBundle initData);
1050 
1051     @WrapForJNI(dispatchTo = "proxy")
attachEditable(IGeckoEditableParent parent)1052     public native void attachEditable(IGeckoEditableParent parent);
1053 
1054     @WrapForJNI(dispatchTo = "proxy")
attachAccessibility( SessionAccessibility.NativeProvider sessionAccessibility)1055     public native void attachAccessibility(
1056         SessionAccessibility.NativeProvider sessionAccessibility);
1057 
1058     @WrapForJNI(calledFrom = "gecko")
onReady(final @Nullable NativeQueue queue)1059     private synchronized void onReady(final @Nullable NativeQueue queue) {
1060       // onReady is called the first time the Gecko window is ready, with a null queue
1061       // argument. In this case, we simply set the current queue to ready state.
1062       //
1063       // After the initial call, onReady is called again every time Window.transfer()
1064       // is called, with a non-null queue argument. In this case, we only set the
1065       // current queue to ready state _if_ the current queue matches the given queue,
1066       // because if the queues don't match, we know there is another onReady call coming.
1067 
1068       if ((queue == null && mNativeQueue == null) || (queue != null && mNativeQueue != queue)) {
1069         return;
1070       }
1071 
1072       if (mNativeQueue.checkAndSetState(State.INITIAL, State.READY) && queue == null) {
1073         Log.i(LOGTAG, "zerdatime " + SystemClock.elapsedRealtime() + " - chrome startup finished");
1074       }
1075     }
1076 
1077     @Override
finalize()1078     protected void finalize() throws Throwable {
1079       close();
1080       disposeNative();
1081     }
1082 
1083     @WrapForJNI(calledFrom = "gecko")
onLoadRequest( final @NonNull String uri, final int windowType, final int flags, final @Nullable String triggeringUri, final boolean hasUserGesture, final boolean isTopLevel)1084     private GeckoResult<Boolean> onLoadRequest(
1085         final @NonNull String uri,
1086         final int windowType,
1087         final int flags,
1088         final @Nullable String triggeringUri,
1089         final boolean hasUserGesture,
1090         final boolean isTopLevel) {
1091       final ProfilerController profilerController = runtime.getProfilerController();
1092       final Double onLoadRequestProfilerStartTime = profilerController.getProfilerTime();
1093       final Runnable addMarker =
1094           () ->
1095               profilerController.addMarker(
1096                   "GeckoSession.onLoadRequest", onLoadRequestProfilerStartTime);
1097 
1098       final GeckoSession session = mOwner.get();
1099       if (session == null) {
1100         // Don't handle any load request if we can't get the session for some reason.
1101         return GeckoResult.fromValue(false);
1102       }
1103       final GeckoResult<Boolean> res = new GeckoResult<>();
1104 
1105       ThreadUtils.postToUiThread(
1106           new Runnable() {
1107             @Override
1108             public void run() {
1109               final NavigationDelegate delegate = session.getNavigationDelegate();
1110 
1111               if (delegate == null) {
1112                 res.complete(false);
1113                 addMarker.run();
1114                 return;
1115               }
1116 
1117               if (!IntentUtils.isUriSafeForScheme(uri)) {
1118                 delegate.onLoadError(
1119                     session,
1120                     uri,
1121                     new WebRequestError(
1122                         WebRequestError.ERROR_MALFORMED_URI,
1123                         WebRequestError.ERROR_CATEGORY_URI,
1124                         null));
1125                 res.complete(true);
1126                 addMarker.run();
1127                 return;
1128               }
1129 
1130               final String trigger = TextUtils.isEmpty(triggeringUri) ? null : triggeringUri;
1131               final NavigationDelegate.LoadRequest req =
1132                   new NavigationDelegate.LoadRequest(
1133                       uri,
1134                       trigger,
1135                       windowType,
1136                       flags,
1137                       hasUserGesture,
1138                       false /* isDirectNavigation */);
1139               final GeckoResult<AllowOrDeny> reqResponse =
1140                   isTopLevel
1141                       ? delegate.onLoadRequest(session, req)
1142                       : delegate.onSubframeLoadRequest(session, req);
1143 
1144               if (reqResponse == null) {
1145                 res.complete(false);
1146                 addMarker.run();
1147                 return;
1148               }
1149 
1150               reqResponse.accept(
1151                   value -> {
1152                     if (value == AllowOrDeny.DENY) {
1153                       res.complete(true);
1154                     } else {
1155                       res.complete(false);
1156                     }
1157                     addMarker.run();
1158                   },
1159                   ex -> {
1160                     // This is incredibly ugly and unreadable because checkstyle sucks.
1161                     res.complete(false);
1162                     addMarker.run();
1163                   });
1164             }
1165           });
1166 
1167       return res;
1168     }
1169 
1170     @WrapForJNI(calledFrom = "ui")
passExternalWebResponse(final WebResponse response)1171     private void passExternalWebResponse(final WebResponse response) {
1172       final GeckoSession session = mOwner.get();
1173       if (session == null) {
1174         return;
1175       }
1176       final ContentDelegate delegate = session.getContentDelegate();
1177       if (delegate != null) {
1178         delegate.onExternalResponse(session, response);
1179       }
1180     }
1181 
1182     @WrapForJNI(calledFrom = "gecko")
onShowDynamicToolbar()1183     private void onShowDynamicToolbar() {
1184       final Window self = this;
1185       ThreadUtils.runOnUiThread(
1186           () -> {
1187             final GeckoSession session = self.mOwner.get();
1188             if (session == null) {
1189               return;
1190             }
1191             final ContentDelegate delegate = session.getContentDelegate();
1192             if (delegate != null) {
1193               delegate.onShowDynamicToolbar(session);
1194             }
1195           });
1196     }
1197   }
1198 
1199   private class Listener implements BundleEventListener {
registerListeners()1200     /* package */ void registerListeners() {
1201       getEventDispatcher()
1202           .registerUiThreadListener(
1203               this, "GeckoView:PinOnScreen", "GeckoView:Prompt", "GeckoView:Prompt:Dismiss", null);
1204     }
1205 
1206     @Override
handleMessage( final String event, final GeckoBundle message, final EventCallback callback)1207     public void handleMessage(
1208         final String event, final GeckoBundle message, final EventCallback callback) {
1209       if (DEBUG) {
1210         Log.d(LOGTAG, "handleMessage: event = " + event);
1211       }
1212 
1213       if ("GeckoView:PinOnScreen".equals(event)) {
1214         GeckoSession.this.setShouldPinOnScreen(message.getBoolean("pinned"));
1215       } else if ("GeckoView:Prompt".equals(event)) {
1216         mPromptController.handleEvent(GeckoSession.this, message, callback);
1217       } else if ("GeckoView:Prompt:Dismiss".equals(event)) {
1218         mPromptController.dismissPrompt(message.getString("id"));
1219       }
1220     }
1221   }
1222 
1223   private final PromptController mPromptController;
1224 
1225   protected @Nullable Window mWindow;
1226   private GeckoSessionSettings mSettings;
1227 
1228   @SuppressWarnings("checkstyle:javadocmethod")
GeckoSession()1229   public GeckoSession() {
1230     this(null);
1231   }
1232 
1233   @SuppressWarnings("checkstyle:javadocmethod")
GeckoSession(final @Nullable GeckoSessionSettings settings)1234   public GeckoSession(final @Nullable GeckoSessionSettings settings) {
1235     mSettings = new GeckoSessionSettings(settings, this);
1236     mListener.registerListeners();
1237 
1238     mWebExtensionController = new WebExtension.SessionController(this);
1239     mPromptController = new PromptController();
1240 
1241     mAutofillSupport = new Autofill.Support(this);
1242     mAutofillSupport.registerListeners();
1243 
1244     if (BuildConfig.DEBUG && handlersCount != mSessionHandlers.length) {
1245       throw new AssertionError("Add new handler to handlers list");
1246     }
1247   }
1248 
1249   /* package */ @Nullable
getRuntime()1250   GeckoRuntime getRuntime() {
1251     if (mWindow == null) {
1252       return null;
1253     }
1254     return mWindow.runtime;
1255   }
1256 
abandonWindow()1257   /* package */ synchronized void abandonWindow() {
1258     if (mWindow == null) {
1259       return;
1260     }
1261 
1262     onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ true);
1263     mWindow = null;
1264     onWindowChanged(WINDOW_TRANSFER_OUT, /* inProgress */ false);
1265   }
1266 
1267   /**
1268    * Return whether this session is open.
1269    *
1270    * @return True if session is open.
1271    * @see #open
1272    * @see #close
1273    */
1274   @AnyThread
isOpen()1275   public boolean isOpen() {
1276     return mWindow != null;
1277   }
1278 
isReady()1279   /* package */ boolean isReady() {
1280     return mNativeQueue.isReady();
1281   }
1282 
createInitData()1283   private GeckoBundle createInitData() {
1284     final GeckoBundle initData = new GeckoBundle(2);
1285     initData.putBundle("settings", mSettings.toBundle());
1286 
1287     final GeckoBundle modules = new GeckoBundle(mSessionHandlers.length);
1288     for (final GeckoSessionHandler<?> handler : mSessionHandlers) {
1289       modules.putBoolean(handler.getName(), handler.isEnabled());
1290     }
1291     initData.putBundle("modules", modules);
1292     return initData;
1293   }
1294 
1295   /**
1296    * Opens the session.
1297    *
1298    * <p>Call this when you are ready to use a GeckoSession instance.
1299    *
1300    * <p>The session is in a 'closed' state when first created. Opening it creates the underlying
1301    * Gecko objects necessary to load a page, etc. Most GeckoSession methods only take affect on an
1302    * open session, and are queued until the session is opened here. Opening a session is an
1303    * asynchronous operation.
1304    *
1305    * @param runtime The Gecko runtime to attach this session to.
1306    * @see #close
1307    * @see #isOpen
1308    */
1309   @UiThread
open(final @NonNull GeckoRuntime runtime)1310   public void open(final @NonNull GeckoRuntime runtime) {
1311     open(runtime, UUID.randomUUID().toString().replace("-", ""));
1312   }
1313 
open(final @NonNull GeckoRuntime runtime, final String id)1314   /* package */ void open(final @NonNull GeckoRuntime runtime, final String id) {
1315     ThreadUtils.assertOnUiThread();
1316 
1317     if (isOpen()) {
1318       // We will leak the existing Window if we open another one.
1319       throw new IllegalStateException("Session is open");
1320     }
1321 
1322     final String chromeUri = mSettings.getChromeUri();
1323     final boolean isPrivate = mSettings.getUsePrivateMode();
1324 
1325     mId = id;
1326     mWindow = new Window(runtime, this, mNativeQueue);
1327     mWebExtensionController.setRuntime(runtime);
1328 
1329     onWindowChanged(WINDOW_OPEN, /* inProgress */ true);
1330 
1331     if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
1332       Window.open(
1333           mWindow,
1334           mNativeQueue,
1335           mCompositor,
1336           mEventDispatcher,
1337           mAccessibility != null ? mAccessibility.nativeProvider : null,
1338           createInitData(),
1339           mId,
1340           chromeUri,
1341           isPrivate);
1342     } else {
1343       GeckoThread.queueNativeCallUntil(
1344           GeckoThread.State.PROFILE_READY,
1345           Window.class,
1346           "open",
1347           Window.class,
1348           mWindow,
1349           NativeQueue.class,
1350           mNativeQueue,
1351           Compositor.class,
1352           mCompositor,
1353           EventDispatcher.class,
1354           mEventDispatcher,
1355           SessionAccessibility.NativeProvider.class,
1356           mAccessibility != null ? mAccessibility.nativeProvider : null,
1357           GeckoBundle.class,
1358           createInitData(),
1359           String.class,
1360           mId,
1361           String.class,
1362           chromeUri,
1363           isPrivate);
1364     }
1365 
1366     onWindowChanged(WINDOW_OPEN, /* inProgress */ false);
1367   }
1368 
1369   /**
1370    * Closes the session.
1371    *
1372    * <p>This frees the underlying Gecko objects and unloads the current page. The session may be
1373    * reopened later, but page state is not restored. Call this when you are finished using a
1374    * GeckoSession instance.
1375    *
1376    * @see #open
1377    * @see #isOpen
1378    */
1379   @UiThread
close()1380   public void close() {
1381     ThreadUtils.assertOnUiThread();
1382 
1383     if (!isOpen()) {
1384       Log.w(LOGTAG, "Attempted to close a GeckoSession that was already closed.");
1385       return;
1386     }
1387 
1388     onWindowChanged(WINDOW_CLOSE, /* inProgress */ true);
1389 
1390     // We need to ensure the compositor releases any Surface it currently holds.
1391     onSurfaceDestroyed();
1392 
1393     mWindow.close();
1394     mWindow.disposeNative();
1395     // Can't access the compositor after we dispose of the window
1396     mCompositorReady = false;
1397     mWindow = null;
1398 
1399     onWindowChanged(WINDOW_CLOSE, /* inProgress */ false);
1400   }
1401 
onWindowChanged(final int change, final boolean inProgress)1402   private void onWindowChanged(final int change, final boolean inProgress) {
1403     if ((change == WINDOW_OPEN || change == WINDOW_TRANSFER_IN) && !inProgress) {
1404       mTextInput.onWindowChanged(mWindow);
1405     }
1406     if ((change == WINDOW_CLOSE || change == WINDOW_TRANSFER_OUT) && !inProgress) {
1407       getAutofillSupport().clear();
1408     }
1409   }
1410 
1411   /**
1412    * Get the SessionTextInput instance for this session. May be called on any thread.
1413    *
1414    * @return SessionTextInput instance.
1415    */
1416   @AnyThread
getTextInput()1417   public @NonNull SessionTextInput getTextInput() {
1418     // May be called on any thread.
1419     return mTextInput;
1420   }
1421 
1422   /**
1423    * Get the SessionAccessibility instance for this session.
1424    *
1425    * @return SessionAccessibility instance.
1426    */
1427   @UiThread
getAccessibility()1428   public @NonNull SessionAccessibility getAccessibility() {
1429     ThreadUtils.assertOnUiThread();
1430     if (mAccessibility != null) {
1431       return mAccessibility;
1432     }
1433 
1434     mAccessibility = new SessionAccessibility(this);
1435     if (mWindow != null) {
1436       if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
1437         mWindow.attachAccessibility(mAccessibility.nativeProvider);
1438       } else {
1439         GeckoThread.queueNativeCallUntil(
1440             GeckoThread.State.PROFILE_READY,
1441             mWindow,
1442             "attachAccessibility",
1443             SessionAccessibility.NativeProvider.class,
1444             mAccessibility.nativeProvider);
1445       }
1446     }
1447     return mAccessibility;
1448   }
1449 
1450   @Retention(RetentionPolicy.SOURCE)
1451   @IntDef(
1452       flag = true,
1453       value = {
1454         LOAD_FLAGS_NONE,
1455         LOAD_FLAGS_BYPASS_CACHE,
1456         LOAD_FLAGS_BYPASS_PROXY,
1457         LOAD_FLAGS_EXTERNAL,
1458         LOAD_FLAGS_ALLOW_POPUPS,
1459         LOAD_FLAGS_FORCE_ALLOW_DATA_URI,
1460         LOAD_FLAGS_REPLACE_HISTORY
1461       })
1462   public @interface LoadFlags {}
1463 
1464   // These flags follow similarly named ones in Gecko's nsIWebNavigation.idl
1465   // https://searchfox.org/mozilla-central/source/docshell/base/nsIWebNavigation.idl
1466   //
1467   // We do not use the same values directly in order to insulate ourselves from
1468   // changes in Gecko. Instead, the flags are converted in GeckoViewNavigation.jsm.
1469 
1470   /** Default load flag, no special considerations. */
1471   public static final int LOAD_FLAGS_NONE = 0;
1472 
1473   /** Bypass the cache. */
1474   public static final int LOAD_FLAGS_BYPASS_CACHE = 1 << 0;
1475 
1476   /** Bypass the proxy, if one has been configured. */
1477   public static final int LOAD_FLAGS_BYPASS_PROXY = 1 << 1;
1478 
1479   /** The load is coming from an external app. Perform additional checks. */
1480   public static final int LOAD_FLAGS_EXTERNAL = 1 << 2;
1481 
1482   /** Popup blocking will be disabled for this load */
1483   public static final int LOAD_FLAGS_ALLOW_POPUPS = 1 << 3;
1484 
1485   /** Bypass the URI classifier (content blocking and Safe Browsing). */
1486   public static final int LOAD_FLAGS_BYPASS_CLASSIFIER = 1 << 4;
1487 
1488   /**
1489    * Allows a top-level data: navigation to occur. E.g. view-image is an explicit user action which
1490    * should be allowed.
1491    */
1492   public static final int LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 1 << 5;
1493 
1494   /** This flag specifies that any existing history entry should be replaced. */
1495   public static final int LOAD_FLAGS_REPLACE_HISTORY = 1 << 6;
1496 
1497   /**
1498    * Filter headers according to the CORS safelisted rules.
1499    *
1500    * <p>See <a
1501    * href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header">
1502    * CORS-safelisted request header </a>.
1503    */
1504   public static final int HEADER_FILTER_CORS_SAFELISTED = 1;
1505   /**
1506    * Allows most headers.
1507    *
1508    * <p>Note: the <code>Host</code> and <code>Connection</code> headers are still ignored.
1509    *
1510    * <p>This should only be used when input is hard-coded from the app or when properly sanitized,
1511    * as some headers could cause unexpected consequences and security issues.
1512    *
1513    * <p>Only use this if you know what you're doing.
1514    */
1515   public static final int HEADER_FILTER_UNRESTRICTED_UNSAFE = 2;
1516 
1517   @Retention(RetentionPolicy.SOURCE)
1518   @IntDef(value = {HEADER_FILTER_CORS_SAFELISTED, HEADER_FILTER_UNRESTRICTED_UNSAFE})
1519   public @interface HeaderFilter {}
1520 
1521   /**
1522    * Main entry point for loading URIs into a {@link GeckoSession}.
1523    *
1524    * <p>The simplest use case is loading a URIs with no extra options, this can be accomplished by
1525    * specifying the URI in {@link #uri} and then calling {@link #load}, e.g.
1526    *
1527    * <pre><code>
1528    *     session.load(new Loader().uri("http://mozilla.org"));
1529    * </code></pre>
1530    *
1531    * This class can also be used to load <code>data:</code> URIs, either from a <code>byte[]</code>
1532    * array or a <code>String</code> using {@link #data}, e.g.
1533    *
1534    * <pre><code>
1535    *     session.load(new Loader().data("the data:1234,5678", "text/plain"));
1536    * </code></pre>
1537    *
1538    * This class also allows you to specify some extra data, e.g. you can set a referrer using {@link
1539    * #referrer} which can either be a {@link GeckoSession} or a plain URL string. You can also
1540    * specify some Load Flags using {@link #flags}.
1541    *
1542    * <p>The class is structured as a Builder, so method calls can be easily chained, e.g.
1543    *
1544    * <pre><code>
1545    *     session.load(new Loader()
1546    *          .url("http://mozilla.org")
1547    *          .referrer("http://my-referrer.com")
1548    *          .flags(...));
1549    * </code></pre>
1550    */
1551   @AnyThread
1552   public static class Loader {
1553     private String mUri;
1554     private GeckoSession mReferrerSession;
1555     private String mReferrerUri;
1556     private GeckoBundle mHeaders;
1557     private @LoadFlags int mLoadFlags = LOAD_FLAGS_NONE;
1558     private boolean mIsDataUri;
1559     private @HeaderFilter int mHeaderFilter = HEADER_FILTER_CORS_SAFELISTED;
1560 
createDataUri( @onNull final byte[] bytes, @Nullable final String mimeType)1561     private static @NonNull String createDataUri(
1562         @NonNull final byte[] bytes, @Nullable final String mimeType) {
1563       return String.format(
1564           "data:%s;base64,%s",
1565           mimeType != null ? mimeType : "", Base64.encodeToString(bytes, Base64.NO_WRAP));
1566     }
1567 
createDataUri( @onNull final String data, @Nullable final String mimeType)1568     private static @NonNull String createDataUri(
1569         @NonNull final String data, @Nullable final String mimeType) {
1570       return String.format("data:%s,%s", mimeType != null ? mimeType : "", data);
1571     }
1572 
1573     @Override
hashCode()1574     public int hashCode() {
1575       // Move to Objects.hashCode once our MIN_SDK >= 19
1576       return Arrays.hashCode(
1577           new Object[] {
1578             mUri, mReferrerSession, mReferrerUri, mHeaders, mLoadFlags, mIsDataUri, mHeaderFilter
1579           });
1580     }
1581 
equals(final Object a, final Object b)1582     private static boolean equals(final Object a, final Object b) {
1583       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
1584         return Objects.equals(a, b);
1585       }
1586 
1587       return (a == b) || (a != null && a.equals(b));
1588     }
1589 
1590     @Override
equals(final @Nullable Object obj)1591     public boolean equals(final @Nullable Object obj) {
1592       if (!(obj instanceof Loader)) {
1593         return false;
1594       }
1595 
1596       final Loader other = (Loader) obj;
1597       return equals(mUri, other.mUri)
1598           && equals(mReferrerSession, other.mReferrerSession)
1599           && equals(mReferrerUri, other.mReferrerUri)
1600           && equals(mHeaders, other.mHeaders)
1601           && equals(mLoadFlags, other.mLoadFlags)
1602           && equals(mIsDataUri, other.mIsDataUri)
1603           && equals(mHeaderFilter, other.mHeaderFilter);
1604     }
1605 
1606     /**
1607      * Set the URI of the resource to load.
1608      *
1609      * @param uri a String containg the URI
1610      * @return this {@link Loader} instance.
1611      */
1612     @NonNull
uri(final @NonNull String uri)1613     public Loader uri(final @NonNull String uri) {
1614       mUri = uri;
1615       mIsDataUri = false;
1616       return this;
1617     }
1618 
1619     /**
1620      * Set the URI of the resource to load.
1621      *
1622      * @param uri a {@link Uri} instance
1623      * @return this {@link Loader} instance.
1624      */
1625     @NonNull
uri(final @NonNull Uri uri)1626     public Loader uri(final @NonNull Uri uri) {
1627       mUri = uri.toString();
1628       mIsDataUri = false;
1629       return this;
1630     }
1631 
1632     /**
1633      * Set the data URI of the resource to load.
1634      *
1635      * @param bytes a <code>byte</code> array containing the data to load.
1636      * @param mimeType a <code>String</code> containing the mime type for this data URI, e.g.
1637      *     "text/plain"
1638      * @return this {@link Loader} instance.
1639      */
1640     @NonNull
data(final @NonNull byte[] bytes, final @Nullable String mimeType)1641     public Loader data(final @NonNull byte[] bytes, final @Nullable String mimeType) {
1642       mUri = createDataUri(bytes, mimeType);
1643       mIsDataUri = true;
1644       return this;
1645     }
1646 
1647     /**
1648      * Set the data URI of the resource to load.
1649      *
1650      * @param data a <code>String</code> array containing the data to load.
1651      * @param mimeType a <code>String</code> containing the mime type for this data URI, e.g.
1652      *     "text/plain"
1653      * @return this {@link Loader} instance.
1654      */
1655     @NonNull
data(final @NonNull String data, final @Nullable String mimeType)1656     public Loader data(final @NonNull String data, final @Nullable String mimeType) {
1657       mUri = createDataUri(data, mimeType);
1658       mIsDataUri = true;
1659       return this;
1660     }
1661 
1662     /**
1663      * Set the referrer for this load.
1664      *
1665      * @param referrer a <code>GeckoSession</code> that will be used as the referrer
1666      * @return this {@link Loader} instance.
1667      */
1668     @NonNull
referrer(final @NonNull GeckoSession referrer)1669     public Loader referrer(final @NonNull GeckoSession referrer) {
1670       mReferrerSession = referrer;
1671       return this;
1672     }
1673 
1674     /**
1675      * Set the referrer for this load.
1676      *
1677      * @param referrerUri a {@link Uri} that will be used as the referrer
1678      * @return this {@link Loader} instance.
1679      */
1680     @NonNull
referrer(final @NonNull Uri referrerUri)1681     public Loader referrer(final @NonNull Uri referrerUri) {
1682       mReferrerUri = referrerUri != null ? referrerUri.toString() : null;
1683       return this;
1684     }
1685 
1686     /**
1687      * Set the referrer for this load.
1688      *
1689      * @param referrerUri a <code>String</code> containing the URI that will be used as the referrer
1690      * @return this {@link Loader} instance.
1691      */
1692     @NonNull
referrer(final @NonNull String referrerUri)1693     public Loader referrer(final @NonNull String referrerUri) {
1694       mReferrerUri = referrerUri;
1695       return this;
1696     }
1697 
1698     /**
1699      * Add headers for this load.
1700      *
1701      * <p>Note: only CORS safelisted headers are allowed by default. To modify this behavior use
1702      * {@link #headerFilter}.
1703      *
1704      * <p>See <a
1705      * href="https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header">
1706      * CORS-safelisted request header </a>.
1707      *
1708      * @param headers a <code>Map</code> containing headers that will be added to this load.
1709      * @return this {@link Loader} instance.
1710      */
1711     @NonNull
additionalHeaders(final @NonNull Map<String, String> headers)1712     public Loader additionalHeaders(final @NonNull Map<String, String> headers) {
1713       final GeckoBundle bundle = new GeckoBundle(headers.size());
1714       for (final Map.Entry<String, String> entry : headers.entrySet()) {
1715         if (entry.getKey() == null) {
1716           // Ignore null keys
1717           continue;
1718         }
1719         bundle.putString(entry.getKey(), entry.getValue());
1720       }
1721       mHeaders = bundle;
1722       return this;
1723     }
1724 
1725     /**
1726      * Modify the header filter behavior. By default only CORS safelisted headers are allowed.
1727      *
1728      * @param filter one of the {@link GeckoSession#HEADER_FILTER_CORS_SAFELISTED HEADER_FILTER_*}
1729      *     constants.
1730      * @return this {@link Loader} instance.
1731      */
1732     @NonNull
headerFilter(final @HeaderFilter int filter)1733     public Loader headerFilter(final @HeaderFilter int filter) {
1734       mHeaderFilter = filter;
1735       return this;
1736     }
1737 
1738     /**
1739      * Set the load flags for this load.
1740      *
1741      * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
1742      *     that will be used as the referrer
1743      * @return this {@link Loader} instance.
1744      */
1745     @NonNull
flags(final @LoadFlags int flags)1746     public Loader flags(final @LoadFlags int flags) {
1747       mLoadFlags = flags;
1748       return this;
1749     }
1750   }
1751 
1752   /**
1753    * Load page using the {@link Loader} specified.
1754    *
1755    * @param request Loader for this request.
1756    * @see Loader
1757    */
1758   @AnyThread
load(final @NonNull Loader request)1759   public void load(final @NonNull Loader request) {
1760     if (request.mUri == null) {
1761       throw new IllegalArgumentException(
1762           "You need to specify at least one between `uri` and `data`.");
1763     }
1764 
1765     if (request.mReferrerUri != null && request.mReferrerSession != null) {
1766       throw new IllegalArgumentException(
1767           "Cannot specify both a referrer session and a referrer URI.");
1768     }
1769 
1770     final NavigationDelegate navDelegate = mNavigationHandler.getDelegate();
1771     final boolean isDataUriTooLong = !maybeCheckDataUriLength(request);
1772     if (navDelegate == null && isDataUriTooLong) {
1773       throw new IllegalArgumentException("data URI is too long");
1774     }
1775 
1776     final int loadFlags =
1777         request.mIsDataUri
1778             // If this is a data: load then we need to force allow it.
1779             ? request.mLoadFlags | LOAD_FLAGS_FORCE_ALLOW_DATA_URI
1780             : request.mLoadFlags;
1781 
1782     // For performance reasons we short-circuit the delegate here
1783     // instead of making Gecko call it for direct load calls.
1784     final NavigationDelegate.LoadRequest loadRequest =
1785         new NavigationDelegate.LoadRequest(
1786             request.mUri,
1787             null, /* triggerUri */
1788             1, /* geckoTarget: OPEN_CURRENTWINDOW */
1789             0, /* flags */
1790             false, /* hasUserGesture */
1791             true /* isDirectNavigation */);
1792 
1793     shouldLoadUri(loadRequest)
1794         .getOrAccept(
1795             allowOrDeny -> {
1796               if (allowOrDeny == AllowOrDeny.DENY) {
1797                 return;
1798               }
1799 
1800               if (isDataUriTooLong) {
1801                 ThreadUtils.runOnUiThread(
1802                     () -> {
1803                       navDelegate.onLoadError(
1804                           this,
1805                           request.mUri,
1806                           new WebRequestError(
1807                               WebRequestError.ERROR_DATA_URI_TOO_LONG,
1808                               WebRequestError.ERROR_CATEGORY_URI,
1809                               null));
1810                     });
1811                 return;
1812               }
1813 
1814               final GeckoBundle msg = new GeckoBundle();
1815               msg.putString("uri", request.mUri);
1816               msg.putInt("flags", loadFlags);
1817               msg.putInt("headerFilter", request.mHeaderFilter);
1818 
1819               if (request.mReferrerUri != null) {
1820                 msg.putString("referrerUri", request.mReferrerUri);
1821               }
1822 
1823               if (request.mReferrerSession != null) {
1824                 msg.putString("referrerSessionId", request.mReferrerSession.mId);
1825               }
1826 
1827               if (request.mHeaders != null) {
1828                 msg.putBundle("headers", request.mHeaders);
1829               }
1830 
1831               mEventDispatcher.dispatch("GeckoView:LoadUri", msg);
1832             });
1833   }
1834 
1835   /**
1836    * Load the given URI.
1837    *
1838    * <p>Convenience method for
1839    *
1840    * <pre><code>
1841    *     session.load(new Loader().uri(uri));
1842    * </code></pre>
1843    *
1844    * @param uri The URI of the resource to load.
1845    */
1846   @AnyThread
loadUri(final @NonNull String uri)1847   public void loadUri(final @NonNull String uri) {
1848     load(new Loader().uri(uri));
1849   }
1850 
shouldLoadUri(final NavigationDelegate.LoadRequest request)1851   private GeckoResult<AllowOrDeny> shouldLoadUri(final NavigationDelegate.LoadRequest request) {
1852     final NavigationDelegate delegate = mNavigationHandler.getDelegate();
1853     if (delegate == null) {
1854       return GeckoResult.allow();
1855     }
1856 
1857     // Always run the callback on the UI thread regardless of what thread we were called in.
1858     final GeckoResult<AllowOrDeny> result = new GeckoResult<>(ThreadUtils.getUiHandler());
1859 
1860     ThreadUtils.runOnUiThread(
1861         () -> {
1862           final GeckoResult<AllowOrDeny> delegateResult = delegate.onLoadRequest(this, request);
1863 
1864           if (delegateResult == null) {
1865             result.complete(AllowOrDeny.ALLOW);
1866           } else {
1867             delegateResult.getOrAccept(
1868                 allowOrDeny -> result.complete(allowOrDeny),
1869                 error -> result.completeExceptionally(error));
1870           }
1871         });
1872 
1873     return result;
1874   }
1875 
1876   /** Reload the current URI. */
1877   @AnyThread
reload()1878   public void reload() {
1879     reload(LOAD_FLAGS_NONE);
1880   }
1881 
1882   /**
1883    * Reload the current URI.
1884    *
1885    * @param flags the load flags to use, an OR-ed value of {@link #LOAD_FLAGS_NONE LOAD_FLAGS_*}
1886    */
1887   @AnyThread
reload(final @LoadFlags int flags)1888   public void reload(final @LoadFlags int flags) {
1889     final GeckoBundle msg = new GeckoBundle();
1890     msg.putInt("flags", flags);
1891     mEventDispatcher.dispatch("GeckoView:Reload", msg);
1892   }
1893 
1894   /** Stop loading. */
1895   @AnyThread
stop()1896   public void stop() {
1897     mEventDispatcher.dispatch("GeckoView:Stop", null);
1898   }
1899 
1900   /**
1901    * Go back in history and assumes the call was based on a user interaction.
1902    *
1903    * @see #goBack(boolean)
1904    */
1905   @AnyThread
goBack()1906   public void goBack() {
1907     goBack(true);
1908   }
1909 
1910   /**
1911    * Go back in history.
1912    *
1913    * @param userInteraction Whether the action was invoked by a user interaction.
1914    */
1915   @AnyThread
goBack(final boolean userInteraction)1916   public void goBack(final boolean userInteraction) {
1917     final GeckoBundle msg = new GeckoBundle(1);
1918     msg.putBoolean("userInteraction", userInteraction);
1919     mEventDispatcher.dispatch("GeckoView:GoBack", msg);
1920   }
1921 
1922   /**
1923    * Go forward in history and assumes the call was based on a user interaction.
1924    *
1925    * @see #goForward(boolean)
1926    */
1927   @AnyThread
goForward()1928   public void goForward() {
1929     goForward(true);
1930   }
1931 
1932   /**
1933    * Go forward in history.
1934    *
1935    * @param userInteraction Whether the action was invoked by a user interaction.
1936    */
1937   @AnyThread
goForward(final boolean userInteraction)1938   public void goForward(final boolean userInteraction) {
1939     final GeckoBundle msg = new GeckoBundle(1);
1940     msg.putBoolean("userInteraction", userInteraction);
1941     mEventDispatcher.dispatch("GeckoView:GoForward", msg);
1942   }
1943 
1944   /**
1945    * Navigate to an index in browser history; the index of the currently viewed page can be
1946    * retrieved from an up-to-date HistoryList by calling {@link
1947    * HistoryDelegate.HistoryList#getCurrentIndex()}.
1948    *
1949    * @param index The index of the location in browser history you want to navigate to.
1950    */
1951   @AnyThread
gotoHistoryIndex(final int index)1952   public void gotoHistoryIndex(final int index) {
1953     final GeckoBundle msg = new GeckoBundle(1);
1954     msg.putInt("index", index);
1955     mEventDispatcher.dispatch("GeckoView:GotoHistoryIndex", msg);
1956   }
1957 
1958   /**
1959    * Returns a WebExtensionController for this GeckoSession. Delegates attached to this controller
1960    * will receive events specific to this session.
1961    *
1962    * @return an instance of {@link WebExtension.SessionController}.
1963    */
1964   @UiThread
getWebExtensionController()1965   public @NonNull WebExtension.SessionController getWebExtensionController() {
1966     return mWebExtensionController;
1967   }
1968 
1969   /**
1970    * Purge history for the session. The session history is used for back and forward history.
1971    * Purging the session history means {@link NavigationDelegate#onCanGoBack(GeckoSession, boolean)}
1972    * and {@link NavigationDelegate#onCanGoForward(GeckoSession, boolean)} will be false.
1973    */
1974   @AnyThread
purgeHistory()1975   public void purgeHistory() {
1976     mEventDispatcher.dispatch("GeckoView:PurgeHistory", null);
1977   }
1978 
1979   @Retention(RetentionPolicy.SOURCE)
1980   @IntDef(
1981       flag = true,
1982       value = {
1983         FINDER_FIND_BACKWARDS,
1984         FINDER_FIND_LINKS_ONLY,
1985         FINDER_FIND_MATCH_CASE,
1986         FINDER_FIND_WHOLE_WORD
1987       })
1988   public @interface FinderFindFlags {}
1989 
1990   /** Go backwards when finding the next match. */
1991   public static final int FINDER_FIND_BACKWARDS = 1;
1992   /** Perform case-sensitive match; default is to perform a case-insensitive match. */
1993   public static final int FINDER_FIND_MATCH_CASE = 1 << 1;
1994   /** Must match entire words; default is to allow matching partial words. */
1995   public static final int FINDER_FIND_WHOLE_WORD = 1 << 2;
1996   /** Limit matches to links on the page. */
1997   public static final int FINDER_FIND_LINKS_ONLY = 1 << 3;
1998 
1999   @Retention(RetentionPolicy.SOURCE)
2000   @IntDef(
2001       flag = true,
2002       value = {
2003         FINDER_DISPLAY_HIGHLIGHT_ALL,
2004         FINDER_DISPLAY_DIM_PAGE,
2005         FINDER_DISPLAY_DRAW_LINK_OUTLINE
2006       })
2007   public @interface FinderDisplayFlags {}
2008 
2009   /** Highlight all find-in-page matches. */
2010   public static final int FINDER_DISPLAY_HIGHLIGHT_ALL = 1;
2011   /** Dim the rest of the page when showing a find-in-page match. */
2012   public static final int FINDER_DISPLAY_DIM_PAGE = 1 << 1;
2013   /** Draw outlines around matching links. */
2014   public static final int FINDER_DISPLAY_DRAW_LINK_OUTLINE = 1 << 2;
2015 
2016   /** Represent the result of a find-in-page operation. */
2017   @AnyThread
2018   public static class FinderResult {
2019     /** Whether a match was found. */
2020     public final boolean found;
2021     /** Whether the search wrapped around the top or bottom of the page. */
2022     public final boolean wrapped;
2023     /** Ordinal number of the current match starting from 1, or 0 if no match. */
2024     public final int current;
2025     /** Total number of matches found so far, or -1 if unknown. */
2026     public final int total;
2027     /** Search string. */
2028     @NonNull public final String searchString;
2029     /**
2030      * Flags used for the search; either 0 or a combination of {@link #FINDER_FIND_BACKWARDS
2031      * FINDER_FIND_*} flags.
2032      */
2033     @FinderFindFlags public final int flags;
2034     /** URI of the link, if the current match is a link, or null otherwise. */
2035     @Nullable public final String linkUri;
2036     /** Bounds of the current match in client coordinates, or null if unknown. */
2037     @Nullable public final RectF clientRect;
2038 
FinderResult(@onNull final GeckoBundle bundle)2039     /* package */ FinderResult(@NonNull final GeckoBundle bundle) {
2040       found = bundle.getBoolean("found");
2041       wrapped = bundle.getBoolean("wrapped");
2042       current = bundle.getInt("current", 0);
2043       total = bundle.getInt("total", -1);
2044       searchString = bundle.getString("searchString");
2045       flags = SessionFinder.getFlagsFromBundle(bundle.getBundle("flags"));
2046       linkUri = bundle.getString("linkURL");
2047 
2048       final GeckoBundle rectBundle = bundle.getBundle("clientRect");
2049       if (rectBundle == null) {
2050         clientRect = null;
2051       } else {
2052         clientRect =
2053             new RectF(
2054                 (float) rectBundle.getDouble("left"),
2055                 (float) rectBundle.getDouble("top"),
2056                 (float) rectBundle.getDouble("right"),
2057                 (float) rectBundle.getDouble("bottom"));
2058       }
2059     }
2060 
2061     /** Empty constructor for tests */
FinderResult()2062     protected FinderResult() {
2063       found = false;
2064       wrapped = false;
2065       current = 0;
2066       total = 0;
2067       flags = 0;
2068       searchString = "";
2069       linkUri = "";
2070       clientRect = null;
2071     }
2072   }
2073 
2074   /**
2075    * Get the SessionFinder instance for this session, to perform find-in-page operations.
2076    *
2077    * @return SessionFinder instance.
2078    */
2079   @AnyThread
getFinder()2080   public @NonNull SessionFinder getFinder() {
2081     if (mFinder == null) {
2082       mFinder = new SessionFinder(getEventDispatcher());
2083     }
2084     return mFinder;
2085   }
2086 
2087   /**
2088    * Set this GeckoSession as active or inactive, which represents if the session is currently
2089    * visible or not. Setting a GeckoSession to inactive will significantly reduce its memory
2090    * footprint, but should only be done if the GeckoSession is not currently visible. Note that a
2091    * session can be active (i.e. visible) but not focused. When a session is set inactive, it will
2092    * flush the session state and trigger a `ProgressDelegate.onSessionStateChange` callback.
2093    *
2094    * @param active A boolean determining whether the GeckoSession is active.
2095    * @see #setFocused
2096    */
2097   @AnyThread
setActive(final boolean active)2098   public void setActive(final boolean active) {
2099     final GeckoBundle msg = new GeckoBundle(1);
2100     msg.putBoolean("active", active);
2101     mEventDispatcher.dispatch("GeckoView:SetActive", msg);
2102 
2103     if (!active) {
2104       mEventDispatcher.dispatch("GeckoView:FlushSessionState", null);
2105       ThreadUtils.postToUiThreadDelayed(mNotifyMemoryPressure, NOTIFY_MEMORY_PRESSURE_DELAY_MS);
2106     } else {
2107       // Delete any pending memory pressure events since we're active again.
2108       ThreadUtils.removeUiThreadCallbacks(mNotifyMemoryPressure);
2109     }
2110 
2111     ThreadUtils.runOnUiThread(() -> getAutofillSupport().onActiveChanged(active));
2112   }
2113 
2114   /**
2115    * Move focus to this session or away from this session. Only one session has focus at a given
2116    * time. Note that a session can be unfocused but still active (i.e. visible).
2117    *
2118    * @param focused True if the session should gain focus or false if the session should lose focus.
2119    * @see #setActive
2120    */
2121   @AnyThread
setFocused(final boolean focused)2122   public void setFocused(final boolean focused) {
2123     final GeckoBundle msg = new GeckoBundle(1);
2124     msg.putBoolean("focused", focused);
2125     mEventDispatcher.dispatch("GeckoView:SetFocused", msg);
2126   }
2127 
2128   /** Class representing a saved session state. */
2129   @AnyThread
2130   public static class SessionState extends AbstractSequentialList<HistoryDelegate.HistoryItem>
2131       implements HistoryDelegate.HistoryList, Parcelable {
2132     private GeckoBundle mState;
2133 
2134     private class SessionStateItem implements HistoryDelegate.HistoryItem {
2135       private final GeckoBundle mItem;
2136 
SessionStateItem(final @NonNull GeckoBundle item)2137       private SessionStateItem(final @NonNull GeckoBundle item) {
2138         mItem = item;
2139       }
2140 
2141       @Override /* HistoryItem */
getUri()2142       public String getUri() {
2143         return mItem.getString("url");
2144       }
2145 
2146       @Override /* HistoryItem */
getTitle()2147       public String getTitle() {
2148         return mItem.getString("title");
2149       }
2150     }
2151 
2152     private class SessionStateIterator implements ListIterator<HistoryDelegate.HistoryItem> {
2153       private final SessionState mState;
2154       private int mIndex;
2155 
SessionStateIterator(final @NonNull SessionState state)2156       private SessionStateIterator(final @NonNull SessionState state) {
2157         this(state, 0);
2158       }
2159 
SessionStateIterator(final @NonNull SessionState state, final int index)2160       private SessionStateIterator(final @NonNull SessionState state, final int index) {
2161         mIndex = index;
2162         mState = state;
2163       }
2164 
2165       @Override /* ListIterator */
add(final HistoryDelegate.HistoryItem item)2166       public void add(final HistoryDelegate.HistoryItem item) {
2167         throw new UnsupportedOperationException();
2168       }
2169 
2170       @Override /* ListIterator */
hasNext()2171       public boolean hasNext() {
2172         final GeckoBundle[] entries = mState.getHistoryEntries();
2173 
2174         if (entries == null) {
2175           Log.w(LOGTAG, "No history entries found.");
2176           return false;
2177         }
2178 
2179         if (mIndex >= mState.getHistoryEntries().length) {
2180           return false;
2181         }
2182         return true;
2183       }
2184 
2185       @Override /* ListIterator */
hasPrevious()2186       public boolean hasPrevious() {
2187         if (mIndex <= 0) {
2188           return false;
2189         }
2190         return true;
2191       }
2192 
2193       @Override /* ListIterator */
next()2194       public HistoryDelegate.HistoryItem next() {
2195         if (hasNext()) {
2196           mIndex++;
2197           return new SessionStateItem(mState.getHistoryEntries()[mIndex - 1]);
2198         } else {
2199           throw new NoSuchElementException();
2200         }
2201       }
2202 
2203       @Override /* ListIterator */
nextIndex()2204       public int nextIndex() {
2205         return mIndex;
2206       }
2207 
2208       @Override /* ListIterator */
previous()2209       public HistoryDelegate.HistoryItem previous() {
2210         if (hasPrevious()) {
2211           mIndex--;
2212           return new SessionStateItem(mState.getHistoryEntries()[mIndex]);
2213         } else {
2214           throw new NoSuchElementException();
2215         }
2216       }
2217 
2218       @Override /* ListIterator */
previousIndex()2219       public int previousIndex() {
2220         return mIndex - 1;
2221       }
2222 
2223       @Override /* ListIterator */
remove()2224       public void remove() {
2225         throw new UnsupportedOperationException();
2226       }
2227 
2228       @Override /* ListIterator */
set(final @NonNull HistoryDelegate.HistoryItem item)2229       public void set(final @NonNull HistoryDelegate.HistoryItem item) {
2230         throw new UnsupportedOperationException();
2231       }
2232     }
2233 
SessionState()2234     private SessionState() {
2235       mState = new GeckoBundle(3);
2236     }
2237 
SessionState(final @NonNull GeckoBundle state)2238     private SessionState(final @NonNull GeckoBundle state) {
2239       mState = new GeckoBundle(state);
2240     }
2241 
2242     @SuppressWarnings("checkstyle:javadocmethod")
SessionState(final @NonNull SessionState state)2243     public SessionState(final @NonNull SessionState state) {
2244       mState = new GeckoBundle(state.mState);
2245     }
2246 
updateSessionState(final @NonNull GeckoBundle updateData)2247     /* package */ void updateSessionState(final @NonNull GeckoBundle updateData) {
2248       if (updateData == null) {
2249         Log.w(LOGTAG, "Session state update has no data field.");
2250         return;
2251       }
2252 
2253       final GeckoBundle history = updateData.getBundle("historychange");
2254       final GeckoBundle scroll = updateData.getBundle("scroll");
2255       final GeckoBundle formdata = updateData.getBundle("formdata");
2256 
2257       if (history != null) {
2258         mState.putBundle("history", history);
2259       }
2260 
2261       if (scroll != null) {
2262         mState.putBundle("scrolldata", scroll);
2263       }
2264 
2265       if (formdata != null) {
2266         mState.putBundle("formdata", formdata);
2267       }
2268 
2269       return;
2270     }
2271 
2272     @Override
hashCode()2273     public int hashCode() {
2274       return mState.hashCode();
2275     }
2276 
2277     @Override
equals(final Object other)2278     public boolean equals(final Object other) {
2279       if (other == null || !(other instanceof SessionState)) {
2280         return false;
2281       }
2282 
2283       final SessionState otherState = (SessionState) other;
2284 
2285       return this.mState.equals(otherState.mState);
2286     }
2287 
2288     /**
2289      * Creates a new SessionState instance from a value previously returned by {@link #toString()}.
2290      *
2291      * @param value The serialized SessionState in String form.
2292      * @return A new SessionState instance if input is valid; otherwise null.
2293      */
fromString(final @Nullable String value)2294     public static @Nullable SessionState fromString(final @Nullable String value) {
2295       final GeckoBundle bundleState;
2296       try {
2297         bundleState = GeckoBundle.fromJSONObject(new JSONObject(value));
2298       } catch (final Exception e) {
2299         Log.e(LOGTAG, "String does not represent valid session state.");
2300         return null;
2301       }
2302 
2303       if (bundleState == null) {
2304         return null;
2305       }
2306 
2307       return new SessionState(bundleState);
2308     }
2309 
2310     @Override
toString()2311     public @Nullable String toString() {
2312       if (mState == null) {
2313         Log.w(LOGTAG, "Can't convert SessionState with null state to string");
2314         return null;
2315       }
2316 
2317       String res;
2318       try {
2319         res = mState.toJSONObject().toString();
2320       } catch (final JSONException e) {
2321         Log.e(LOGTAG, "Could not convert session state to string.");
2322         res = null;
2323       }
2324 
2325       return res;
2326     }
2327 
2328     @Override // Parcelable
describeContents()2329     public int describeContents() {
2330       return 0;
2331     }
2332 
2333     @Override // Parcelable
writeToParcel(final Parcel dest, final int flags)2334     public void writeToParcel(final Parcel dest, final int flags) {
2335       dest.writeString(toString());
2336     }
2337 
2338     // AIDL code may call readFromParcel even though it's not part of Parcelable.
2339     @SuppressWarnings("checkstyle:javadocmethod")
readFromParcel(final @NonNull Parcel source)2340     public void readFromParcel(final @NonNull Parcel source) {
2341       if (source.readString() == null) {
2342         Log.w(LOGTAG, "Can't reproduce session state from Parcel");
2343       }
2344 
2345       try {
2346         mState = GeckoBundle.fromJSONObject(new JSONObject(source.readString()));
2347       } catch (final JSONException e) {
2348         Log.e(LOGTAG, "Could not convert string to session state.");
2349         mState = null;
2350       }
2351     }
2352 
2353     public static final Parcelable.Creator<SessionState> CREATOR =
2354         new Parcelable.Creator<SessionState>() {
2355           @Override
2356           public SessionState createFromParcel(final Parcel source) {
2357             if (source.readString() == null) {
2358               Log.w(LOGTAG, "Can't create session state from Parcel");
2359             }
2360 
2361             GeckoBundle res;
2362             try {
2363               res = GeckoBundle.fromJSONObject(new JSONObject(source.readString()));
2364             } catch (final JSONException e) {
2365               Log.e(LOGTAG, "Could not convert parcel to session state.");
2366               res = null;
2367             }
2368 
2369             return new SessionState(res);
2370           }
2371 
2372           @Override
2373           public SessionState[] newArray(final int size) {
2374             return new SessionState[size];
2375           }
2376         };
2377 
2378     @Override /* AbstractSequentialList */
get(final int index)2379     public @NonNull HistoryDelegate.HistoryItem get(final int index) {
2380       final GeckoBundle[] entries = getHistoryEntries();
2381 
2382       if (entries == null || index < 0 || index >= entries.length) {
2383         throw new NoSuchElementException();
2384       }
2385 
2386       return new SessionStateItem(entries[index]);
2387     }
2388 
2389     @Override /* AbstractSequentialList */
iterator()2390     public @NonNull Iterator<HistoryDelegate.HistoryItem> iterator() {
2391       return listIterator(0);
2392     }
2393 
2394     @Override /* AbstractSequentialList */
listIterator(final int index)2395     public @NonNull ListIterator<HistoryDelegate.HistoryItem> listIterator(final int index) {
2396       return new SessionStateIterator(this, index);
2397     }
2398 
2399     @Override /* AbstractSequentialList */
size()2400     public int size() {
2401       final GeckoBundle[] entries = getHistoryEntries();
2402 
2403       if (entries == null) {
2404         Log.w(LOGTAG, "No history entries found.");
2405         return 0;
2406       }
2407 
2408       return entries.length;
2409     }
2410 
2411     @Override /* HistoryList */
getCurrentIndex()2412     public int getCurrentIndex() {
2413       final GeckoBundle history = getHistory();
2414 
2415       if (history == null) {
2416         throw new IllegalStateException("No history state exists.");
2417       }
2418 
2419       return history.getInt("index") + history.getInt("fromIdx");
2420     }
2421 
2422     // Some helpers for common code.
getHistory()2423     private GeckoBundle getHistory() {
2424       if (mState == null) {
2425         return null;
2426       }
2427 
2428       return mState.getBundle("history");
2429     }
2430 
getHistoryEntries()2431     private GeckoBundle[] getHistoryEntries() {
2432       final GeckoBundle history = getHistory();
2433 
2434       if (history == null) {
2435         return null;
2436       }
2437 
2438       return history.getBundleArray("entries");
2439     }
2440   }
2441 
2442   private SessionState mStateCache = new SessionState();
2443 
2444   /**
2445    * Restore a saved state to this GeckoSession; only data that is saved (history, scroll position,
2446    * zoom, and form data) will be restored. These will overwrite the corresponding state of this
2447    * GeckoSession.
2448    *
2449    * @param state A saved session state; this should originate from onSessionStateChange().
2450    */
2451   @AnyThread
restoreState(final @NonNull SessionState state)2452   public void restoreState(final @NonNull SessionState state) {
2453     mEventDispatcher.dispatch("GeckoView:RestoreState", state.mState);
2454   }
2455 
2456   // This is the GeckoDisplay acquired via acquireDisplay(), if any.
2457   private GeckoDisplay mDisplay;
2458 
2459   /* package */ interface Owner {
onRelease()2460     void onRelease();
2461   }
2462 
2463   private static final WeakReference<Owner> NO_OWNER = new WeakReference<>(null);
2464   private WeakReference<Owner> mOwner = NO_OWNER;
2465 
2466   @UiThread
releaseOwner()2467   /* package */ void releaseOwner() {
2468     ThreadUtils.assertOnUiThread();
2469     mOwner = NO_OWNER;
2470   }
2471 
2472   @UiThread
setOwner(final Owner owner)2473   /* package */ void setOwner(final Owner owner) {
2474     ThreadUtils.assertOnUiThread();
2475     final Owner oldOwner = mOwner.get();
2476     if (oldOwner != null && owner != oldOwner) {
2477       oldOwner.onRelease();
2478     }
2479     mOwner = new WeakReference<>(owner);
2480   }
2481 
getDisplay()2482   /* package */ GeckoDisplay getDisplay() {
2483     return mDisplay;
2484   }
2485 
2486   /**
2487    * Acquire the GeckoDisplay instance for providing the session with a drawing Surface. Be sure to
2488    * call {@link GeckoDisplay#surfaceChanged(Surface, int, int)} on the acquired display if there is
2489    * already a valid Surface.
2490    *
2491    * @return GeckoDisplay instance.
2492    * @see #releaseDisplay(GeckoDisplay)
2493    */
2494   @UiThread
acquireDisplay()2495   public @NonNull GeckoDisplay acquireDisplay() {
2496     ThreadUtils.assertOnUiThread();
2497 
2498     if (mDisplay != null) {
2499       throw new IllegalStateException("Display already acquired");
2500     }
2501 
2502     mDisplay = new GeckoDisplay(this);
2503     return mDisplay;
2504   }
2505 
2506   /**
2507    * Release an acquired GeckoDisplay instance. Be sure to call {@link
2508    * GeckoDisplay#surfaceDestroyed()} before releasing the display if it still has a valid Surface.
2509    *
2510    * @param display Acquired GeckoDisplay instance.
2511    * @see #acquireDisplay()
2512    */
2513   @UiThread
releaseDisplay(final @NonNull GeckoDisplay display)2514   public void releaseDisplay(final @NonNull GeckoDisplay display) {
2515     ThreadUtils.assertOnUiThread();
2516 
2517     if (display != mDisplay) {
2518       throw new IllegalArgumentException("Display not attached");
2519     }
2520 
2521     mDisplay = null;
2522   }
2523 
2524   @AnyThread
2525   @SuppressWarnings("checkstyle:javadocmethod")
getSettings()2526   public @NonNull GeckoSessionSettings getSettings() {
2527     return mSettings;
2528   }
2529 
2530   /** Exits fullscreen mode */
2531   @AnyThread
exitFullScreen()2532   public void exitFullScreen() {
2533     mEventDispatcher.dispatch("GeckoViewContent:ExitFullScreen", null);
2534   }
2535 
2536   /**
2537    * Set the content callback handler. This will replace the current handler.
2538    *
2539    * @param delegate An implementation of ContentDelegate.
2540    */
2541   @UiThread
setContentDelegate(final @Nullable ContentDelegate delegate)2542   public void setContentDelegate(final @Nullable ContentDelegate delegate) {
2543     ThreadUtils.assertOnUiThread();
2544     mContentHandler.setDelegate(delegate, this);
2545     mProcessHangHandler.setDelegate(delegate, this);
2546   }
2547 
2548   /**
2549    * Get the content callback handler.
2550    *
2551    * @return The current content callback handler.
2552    */
2553   @UiThread
getContentDelegate()2554   public @Nullable ContentDelegate getContentDelegate() {
2555     ThreadUtils.assertOnUiThread();
2556     return mContentHandler.getDelegate();
2557   }
2558 
2559   /**
2560    * Set the progress callback handler. This will replace the current handler.
2561    *
2562    * @param delegate An implementation of ProgressDelegate.
2563    */
2564   @UiThread
setProgressDelegate(final @Nullable ProgressDelegate delegate)2565   public void setProgressDelegate(final @Nullable ProgressDelegate delegate) {
2566     ThreadUtils.assertOnUiThread();
2567     mProgressHandler.setDelegate(delegate, this);
2568   }
2569 
2570   /**
2571    * Get the progress callback handler.
2572    *
2573    * @return The current progress callback handler.
2574    */
2575   @UiThread
getProgressDelegate()2576   public @Nullable ProgressDelegate getProgressDelegate() {
2577     ThreadUtils.assertOnUiThread();
2578     return mProgressHandler.getDelegate();
2579   }
2580 
2581   /**
2582    * Set the navigation callback handler. This will replace the current handler.
2583    *
2584    * @param delegate An implementation of NavigationDelegate.
2585    */
2586   @UiThread
setNavigationDelegate(final @Nullable NavigationDelegate delegate)2587   public void setNavigationDelegate(final @Nullable NavigationDelegate delegate) {
2588     ThreadUtils.assertOnUiThread();
2589     mNavigationHandler.setDelegate(delegate, this);
2590   }
2591 
2592   /**
2593    * Get the navigation callback handler.
2594    *
2595    * @return The current navigation callback handler.
2596    */
2597   @UiThread
getNavigationDelegate()2598   public @Nullable NavigationDelegate getNavigationDelegate() {
2599     ThreadUtils.assertOnUiThread();
2600     return mNavigationHandler.getDelegate();
2601   }
2602 
2603   /**
2604    * Set the content scroll callback handler. This will replace the current handler.
2605    *
2606    * @param delegate An implementation of ScrollDelegate.
2607    */
2608   @UiThread
setScrollDelegate(final @Nullable ScrollDelegate delegate)2609   public void setScrollDelegate(final @Nullable ScrollDelegate delegate) {
2610     ThreadUtils.assertOnUiThread();
2611     mScrollHandler.setDelegate(delegate, this);
2612   }
2613 
2614   @UiThread
2615   @SuppressWarnings("checkstyle:javadocmethod")
getScrollDelegate()2616   public @Nullable ScrollDelegate getScrollDelegate() {
2617     ThreadUtils.assertOnUiThread();
2618     return mScrollHandler.getDelegate();
2619   }
2620 
2621   /**
2622    * Set the history tracking delegate for this session, replacing the current delegate if one is
2623    * set.
2624    *
2625    * @param delegate The history tracking delegate, or {@code null} to unset.
2626    */
2627   @AnyThread
setHistoryDelegate(final @Nullable HistoryDelegate delegate)2628   public void setHistoryDelegate(final @Nullable HistoryDelegate delegate) {
2629     mHistoryHandler.setDelegate(delegate, this);
2630   }
2631 
2632   /** @return The history tracking delegate for this session. */
2633   @AnyThread
getHistoryDelegate()2634   public @Nullable HistoryDelegate getHistoryDelegate() {
2635     return mHistoryHandler.getDelegate();
2636   }
2637 
2638   /**
2639    * Set the content blocking callback handler. This will replace the current handler.
2640    *
2641    * @param delegate An implementation of {@link ContentBlocking.Delegate}.
2642    */
2643   @AnyThread
setContentBlockingDelegate(final @Nullable ContentBlocking.Delegate delegate)2644   public void setContentBlockingDelegate(final @Nullable ContentBlocking.Delegate delegate) {
2645     mContentBlockingHandler.setDelegate(delegate, this);
2646   }
2647 
2648   /**
2649    * Get the content blocking callback handler.
2650    *
2651    * @return The current content blocking callback handler.
2652    */
2653   @AnyThread
getContentBlockingDelegate()2654   public @Nullable ContentBlocking.Delegate getContentBlockingDelegate() {
2655     return mContentBlockingHandler.getDelegate();
2656   }
2657 
2658   /**
2659    * Set the current prompt delegate for this GeckoSession.
2660    *
2661    * @param delegate PromptDelegate instance or null to use the built-in delegate.
2662    */
2663   @AnyThread
setPromptDelegate(final @Nullable PromptDelegate delegate)2664   public void setPromptDelegate(final @Nullable PromptDelegate delegate) {
2665     mPromptDelegate = delegate;
2666   }
2667 
2668   /**
2669    * Get the current prompt delegate for this GeckoSession.
2670    *
2671    * @return PromptDelegate instance or null if using built-in delegate.
2672    */
2673   @AnyThread
getPromptDelegate()2674   public @Nullable PromptDelegate getPromptDelegate() {
2675     return mPromptDelegate;
2676   }
2677 
2678   /**
2679    * Set the current selection action delegate for this GeckoSession.
2680    *
2681    * @param delegate SelectionActionDelegate instance or null to unset.
2682    */
2683   @UiThread
setSelectionActionDelegate(final @Nullable SelectionActionDelegate delegate)2684   public void setSelectionActionDelegate(final @Nullable SelectionActionDelegate delegate) {
2685     ThreadUtils.assertOnUiThread();
2686 
2687     if (getSelectionActionDelegate() != null) {
2688       // When the delegate is changed or cleared, make sure onHideAction is called
2689       // one last time to hide any existing selection action UI. Gecko doesn't keep
2690       // track of the old delegate, so we can't rely on Gecko to do that for us.
2691       getSelectionActionDelegate()
2692           .onHideAction(this, GeckoSession.SelectionActionDelegate.HIDE_REASON_NO_SELECTION);
2693     }
2694     mSelectionActionDelegate.setDelegate(delegate, this);
2695   }
2696 
2697   /**
2698    * Set the media callback handler. This will replace the current handler.
2699    *
2700    * @param delegate An implementation of MediaDelegate.
2701    */
2702   @AnyThread
setMediaDelegate(final @Nullable MediaDelegate delegate)2703   public void setMediaDelegate(final @Nullable MediaDelegate delegate) {
2704     mMediaHandler.setDelegate(delegate, this);
2705   }
2706 
2707   /**
2708    * Get the Media callback handler.
2709    *
2710    * @return The current Media callback handler.
2711    */
2712   @AnyThread
getMediaDelegate()2713   public @Nullable MediaDelegate getMediaDelegate() {
2714     return mMediaHandler.getDelegate();
2715   }
2716 
2717   /**
2718    * Set the media session delegate. This will replace the current handler.
2719    *
2720    * @param delegate An implementation of {@link MediaSession.Delegate}.
2721    */
2722   @AnyThread
setMediaSessionDelegate(final @Nullable MediaSession.Delegate delegate)2723   public void setMediaSessionDelegate(final @Nullable MediaSession.Delegate delegate) {
2724     mMediaSessionHandler.setDelegate(delegate, this);
2725   }
2726 
2727   /**
2728    * Get the media session delegate.
2729    *
2730    * @return The current media session delegate.
2731    */
2732   @AnyThread
getMediaSessionDelegate()2733   public @Nullable MediaSession.Delegate getMediaSessionDelegate() {
2734     return mMediaSessionHandler.getDelegate();
2735   }
2736 
2737   /**
2738    * Get the current selection action delegate for this GeckoSession.
2739    *
2740    * @return SelectionActionDelegate instance or null if not set.
2741    */
2742   @AnyThread
getSelectionActionDelegate()2743   public @Nullable SelectionActionDelegate getSelectionActionDelegate() {
2744     return mSelectionActionDelegate.getDelegate();
2745   }
2746 
2747   @UiThread
setShouldPinOnScreen(final boolean pinned)2748   protected void setShouldPinOnScreen(final boolean pinned) {
2749     if (DEBUG) {
2750       ThreadUtils.assertOnUiThread();
2751     }
2752 
2753     mShouldPinOnScreen = pinned;
2754   }
2755 
shouldPinOnScreen()2756   /* package */ boolean shouldPinOnScreen() {
2757     ThreadUtils.assertOnUiThread();
2758     return mShouldPinOnScreen;
2759   }
2760 
2761   @AnyThread
2762   /* package */ @NonNull
getEventDispatcher()2763   EventDispatcher getEventDispatcher() {
2764     return mEventDispatcher;
2765   }
2766 
2767   public interface ProgressDelegate {
2768     /** Class representing security information for a site. */
2769     public class SecurityInformation {
2770       @Retention(RetentionPolicy.SOURCE)
2771       @IntDef({SECURITY_MODE_UNKNOWN, SECURITY_MODE_IDENTIFIED, SECURITY_MODE_VERIFIED})
2772       public @interface SecurityMode {}
2773 
2774       public static final int SECURITY_MODE_UNKNOWN = 0;
2775       public static final int SECURITY_MODE_IDENTIFIED = 1;
2776       public static final int SECURITY_MODE_VERIFIED = 2;
2777 
2778       @Retention(RetentionPolicy.SOURCE)
2779       @IntDef({CONTENT_UNKNOWN, CONTENT_BLOCKED, CONTENT_LOADED})
2780       public @interface ContentType {}
2781 
2782       public static final int CONTENT_UNKNOWN = 0;
2783       public static final int CONTENT_BLOCKED = 1;
2784       public static final int CONTENT_LOADED = 2;
2785       /** Indicates whether or not the site is secure. */
2786       public final boolean isSecure;
2787       /** Indicates whether or not the site is a security exception. */
2788       public final boolean isException;
2789       /** Contains the origin of the certificate. */
2790       public final @Nullable String origin;
2791       /** Contains the host associated with the certificate. */
2792       public final @NonNull String host;
2793 
2794       /** The server certificate in use, if any. */
2795       public final @Nullable X509Certificate certificate;
2796 
2797       /**
2798        * Indicates the security level of the site; possible values are SECURITY_MODE_UNKNOWN,
2799        * SECURITY_MODE_IDENTIFIED, and SECURITY_MODE_VERIFIED. SECURITY_MODE_IDENTIFIED indicates
2800        * domain validation only, while SECURITY_MODE_VERIFIED indicates extended validation.
2801        */
2802       public final @SecurityMode int securityMode;
2803       /**
2804        * Indicates the presence of passive mixed content; possible values are CONTENT_UNKNOWN,
2805        * CONTENT_BLOCKED, and CONTENT_LOADED.
2806        */
2807       public final @ContentType int mixedModePassive;
2808       /**
2809        * Indicates the presence of active mixed content; possible values are CONTENT_UNKNOWN,
2810        * CONTENT_BLOCKED, and CONTENT_LOADED.
2811        */
2812       public final @ContentType int mixedModeActive;
2813 
SecurityInformation(final GeckoBundle identityData)2814       /* package */ SecurityInformation(final GeckoBundle identityData) {
2815         final GeckoBundle mode = identityData.getBundle("mode");
2816 
2817         mixedModePassive = mode.getInt("mixed_display");
2818         mixedModeActive = mode.getInt("mixed_active");
2819 
2820         securityMode = mode.getInt("identity");
2821 
2822         isSecure = identityData.getBoolean("secure");
2823         isException = identityData.getBoolean("securityException");
2824         origin = identityData.getString("origin");
2825         host = identityData.getString("host");
2826 
2827         X509Certificate decodedCert = null;
2828         try {
2829           final CertificateFactory factory = CertificateFactory.getInstance("X.509");
2830           final String certString = identityData.getString("certificate");
2831           if (certString != null) {
2832             final byte[] certBytes = Base64.decode(certString, Base64.NO_WRAP);
2833             decodedCert =
2834                 (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
2835           }
2836         } catch (final CertificateException e) {
2837           Log.e(LOGTAG, "Failed to decode certificate", e);
2838         }
2839 
2840         certificate = decodedCert;
2841       }
2842 
2843       /** Empty constructor for tests */
SecurityInformation()2844       protected SecurityInformation() {
2845         mixedModePassive = CONTENT_UNKNOWN;
2846         mixedModeActive = CONTENT_UNKNOWN;
2847         securityMode = SECURITY_MODE_UNKNOWN;
2848         isSecure = false;
2849         isException = false;
2850         origin = "";
2851         host = "";
2852         certificate = null;
2853       }
2854     }
2855 
2856     /**
2857      * A View has started loading content from the network.
2858      *
2859      * @param session GeckoSession that initiated the callback.
2860      * @param url The resource being loaded.
2861      */
2862     @UiThread
onPageStart(@onNull final GeckoSession session, @NonNull final String url)2863     default void onPageStart(@NonNull final GeckoSession session, @NonNull final String url) {}
2864 
2865     /**
2866      * A View has finished loading content from the network.
2867      *
2868      * @param session GeckoSession that initiated the callback.
2869      * @param success Whether the page loaded successfully or an error occurred.
2870      */
2871     @UiThread
onPageStop(@onNull final GeckoSession session, final boolean success)2872     default void onPageStop(@NonNull final GeckoSession session, final boolean success) {}
2873 
2874     /**
2875      * Page loading has progressed.
2876      *
2877      * @param session GeckoSession that initiated the callback.
2878      * @param progress Current page load progress value [0, 100].
2879      */
2880     @UiThread
onProgressChange(@onNull final GeckoSession session, final int progress)2881     default void onProgressChange(@NonNull final GeckoSession session, final int progress) {}
2882 
2883     /**
2884      * The security status has been updated.
2885      *
2886      * @param session GeckoSession that initiated the callback.
2887      * @param securityInfo The new security information.
2888      */
2889     @UiThread
onSecurityChange( @onNull final GeckoSession session, @NonNull final SecurityInformation securityInfo)2890     default void onSecurityChange(
2891         @NonNull final GeckoSession session, @NonNull final SecurityInformation securityInfo) {}
2892 
2893     /**
2894      * The browser session state has changed. This can happen in response to navigation, scrolling,
2895      * or form data changes; the session state passed includes the most up to date information on
2896      * all of these.
2897      *
2898      * @param session GeckoSession that initiated the callback.
2899      * @param sessionState SessionState representing the latest browser state.
2900      */
2901     @UiThread
onSessionStateChange( @onNull final GeckoSession session, @NonNull final SessionState sessionState)2902     default void onSessionStateChange(
2903         @NonNull final GeckoSession session, @NonNull final SessionState sessionState) {}
2904   }
2905 
2906   /** WebResponseInfo contains information about a single web response. */
2907   @AnyThread
2908   public static class WebResponseInfo {
2909     /** The URI of the response. Cannot be null. */
2910     @NonNull public final String uri;
2911 
2912     /** The content type (mime type) of the response. May be null. */
2913     @Nullable public final String contentType;
2914 
2915     /** The content length of the response. May be 0 if unknokwn. */
2916     @Nullable public final long contentLength;
2917 
2918     /** The filename obtained from the content disposition, if any. May be null. */
2919     @Nullable public final String filename;
2920 
WebResponseInfo(final GeckoBundle message)2921     /* package */ WebResponseInfo(final GeckoBundle message) {
2922       uri = message.getString("uri");
2923       if (uri == null) {
2924         throw new IllegalArgumentException("URI cannot be null");
2925       }
2926 
2927       contentType = message.getString("contentType");
2928       contentLength = message.getLong("contentLength");
2929       filename = message.getString("filename");
2930     }
2931 
2932     /** Empty constructor for tests. */
WebResponseInfo()2933     protected WebResponseInfo() {
2934       uri = "";
2935       contentType = "";
2936       contentLength = 0;
2937       filename = "";
2938     }
2939   }
2940 
2941   public interface ContentDelegate {
2942     /**
2943      * A page title was discovered in the content or updated after the content loaded.
2944      *
2945      * @param session The GeckoSession that initiated the callback.
2946      * @param title The title sent from the content.
2947      */
2948     @UiThread
onTitleChange(@onNull final GeckoSession session, @Nullable final String title)2949     default void onTitleChange(@NonNull final GeckoSession session, @Nullable final String title) {}
2950 
2951     /**
2952      * A preview image was discovered in the content after the content loaded.
2953      *
2954      * @param session The GeckoSession that initiated the callback.
2955      * @param previewImageUrl The preview image URL sent from the content.
2956      */
2957     @UiThread
onPreviewImage( @onNull final GeckoSession session, @NonNull final String previewImageUrl)2958     default void onPreviewImage(
2959         @NonNull final GeckoSession session, @NonNull final String previewImageUrl) {}
2960 
2961     /**
2962      * A page has requested focus. Note that window.focus() in content will not result in this being
2963      * called.
2964      *
2965      * @param session The GeckoSession that initiated the callback.
2966      */
2967     @UiThread
onFocusRequest(@onNull final GeckoSession session)2968     default void onFocusRequest(@NonNull final GeckoSession session) {}
2969 
2970     /**
2971      * A page has requested to close
2972      *
2973      * @param session The GeckoSession that initiated the callback.
2974      */
2975     @UiThread
onCloseRequest(@onNull final GeckoSession session)2976     default void onCloseRequest(@NonNull final GeckoSession session) {}
2977 
2978     /**
2979      * A page has entered or exited full screen mode. Typically, the implementation would set the
2980      * Activity containing the GeckoSession to full screen when the page is in full screen mode.
2981      *
2982      * @param session The GeckoSession that initiated the callback.
2983      * @param fullScreen True if the page is in full screen mode.
2984      */
2985     @UiThread
onFullScreen(@onNull final GeckoSession session, final boolean fullScreen)2986     default void onFullScreen(@NonNull final GeckoSession session, final boolean fullScreen) {}
2987 
2988     /**
2989      * A viewport-fit was discovered in the content or updated after the content.
2990      *
2991      * @param session The GeckoSession that initiated the callback.
2992      * @param viewportFit The value of viewport-fit of meta element in content.
2993      * @see <a href="https://drafts.csswg.org/css-round-display/#viewport-fit-descriptor">4.1. The
2994      *     viewport-fit descriptor</a>
2995      */
2996     @UiThread
onMetaViewportFitChange( @onNull final GeckoSession session, @NonNull final String viewportFit)2997     default void onMetaViewportFitChange(
2998         @NonNull final GeckoSession session, @NonNull final String viewportFit) {}
2999 
3000     /** Element details for onContextMenu callbacks. */
3001     public static class ContextElement {
3002       @Retention(RetentionPolicy.SOURCE)
3003       @IntDef({TYPE_NONE, TYPE_IMAGE, TYPE_VIDEO, TYPE_AUDIO})
3004       public @interface Type {}
3005 
3006       public static final int TYPE_NONE = 0;
3007       public static final int TYPE_IMAGE = 1;
3008       public static final int TYPE_VIDEO = 2;
3009       public static final int TYPE_AUDIO = 3;
3010 
3011       /** The base URI of the element's document. */
3012       public final @Nullable String baseUri;
3013 
3014       /** The absolute link URI (href) of the element. */
3015       public final @Nullable String linkUri;
3016 
3017       /** The title text of the element. */
3018       public final @Nullable String title;
3019 
3020       /** The alternative text (alt) for the element. */
3021       public final @Nullable String altText;
3022 
3023       /** The type of the element. One of the {@link ContextElement#TYPE_NONE} flags. */
3024       public final @Type int type;
3025 
3026       /** The source URI (src) of the element. Set for (nested) media elements. */
3027       public final @Nullable String srcUri;
3028 
3029       // TODO: Bug 1595822 make public
3030       final List<WebExtension.Menu> extensionMenus;
3031 
ContextElement( final @Nullable String baseUri, final @Nullable String linkUri, final @Nullable String title, final @Nullable String altText, final @NonNull String typeStr, final @Nullable String srcUri)3032       protected ContextElement(
3033           final @Nullable String baseUri,
3034           final @Nullable String linkUri,
3035           final @Nullable String title,
3036           final @Nullable String altText,
3037           final @NonNull String typeStr,
3038           final @Nullable String srcUri) {
3039         this.baseUri = baseUri;
3040         this.linkUri = linkUri;
3041         this.title = title;
3042         this.altText = altText;
3043         this.type = getType(typeStr);
3044         this.srcUri = srcUri;
3045         this.extensionMenus = null;
3046       }
3047 
getType(final String name)3048       private static int getType(final String name) {
3049         if ("HTMLImageElement".equals(name)) {
3050           return TYPE_IMAGE;
3051         } else if ("HTMLVideoElement".equals(name)) {
3052           return TYPE_VIDEO;
3053         } else if ("HTMLAudioElement".equals(name)) {
3054           return TYPE_AUDIO;
3055         }
3056         return TYPE_NONE;
3057       }
3058     }
3059 
3060     /**
3061      * A user has initiated the context menu via long-press. This event is fired on links, (nested)
3062      * images and (nested) media elements.
3063      *
3064      * @param session The GeckoSession that initiated the callback.
3065      * @param screenX The screen coordinates of the press.
3066      * @param screenY The screen coordinates of the press.
3067      * @param element The details for the pressed element.
3068      */
3069     @UiThread
onContextMenu( @onNull final GeckoSession session, final int screenX, final int screenY, @NonNull final ContextElement element)3070     default void onContextMenu(
3071         @NonNull final GeckoSession session,
3072         final int screenX,
3073         final int screenY,
3074         @NonNull final ContextElement element) {}
3075 
3076     /**
3077      * This is fired when there is a response that cannot be handled by Gecko (e.g., a download).
3078      *
3079      * @param session the GeckoSession that received the external response.
3080      * @param response the external WebResponse.
3081      */
3082     @UiThread
onExternalResponse( @onNull final GeckoSession session, @NonNull final WebResponse response)3083     default void onExternalResponse(
3084         @NonNull final GeckoSession session, @NonNull final WebResponse response) {}
3085 
3086     /**
3087      * The content process hosting this GeckoSession has crashed. The GeckoSession is now closed and
3088      * unusable. You may call {@link #open(GeckoRuntime)} to recover the session, but no state is
3089      * preserved. Most applications will want to call {@link #load} or {@link
3090      * #restoreState(SessionState)} at this point.
3091      *
3092      * @param session The GeckoSession for which the content process has crashed.
3093      */
3094     @UiThread
onCrash(@onNull final GeckoSession session)3095     default void onCrash(@NonNull final GeckoSession session) {}
3096 
3097     /**
3098      * The content process hosting this GeckoSession has been killed. The GeckoSession is now closed
3099      * and unusable. You may call {@link #open(GeckoRuntime)} to recover the session, but no state
3100      * is preserved. Most applications will want to call {@link #load} or {@link
3101      * #restoreState(SessionState)} at this point.
3102      *
3103      * @param session The GeckoSession for which the content process has been killed.
3104      */
3105     @UiThread
onKill(@onNull final GeckoSession session)3106     default void onKill(@NonNull final GeckoSession session) {}
3107 
3108     /**
3109      * Notification that the first content composition has occurred. This callback is invoked for
3110      * the first content composite after either a start or a restart of the compositor.
3111      *
3112      * @param session The GeckoSession that had a first paint event.
3113      */
3114     @UiThread
onFirstComposite(@onNull final GeckoSession session)3115     default void onFirstComposite(@NonNull final GeckoSession session) {}
3116 
3117     /**
3118      * Notification that the first content paint has occurred. This callback is invoked for the
3119      * first content paint after a page has been loaded, or after a {@link
3120      * #onPaintStatusReset(GeckoSession)} event. The function {@link
3121      * #onFirstComposite(GeckoSession)} will be called once the compositor has started rendering.
3122      * However, it is possible for the compositor to start rendering before there is any content to
3123      * render. onFirstContentfulPaint() is called once some content has been rendered. It may be
3124      * nothing more than the page background color. It is not an indication that the whole page has
3125      * been rendered.
3126      *
3127      * @param session The GeckoSession that had a first paint event.
3128      */
3129     @UiThread
onFirstContentfulPaint(@onNull final GeckoSession session)3130     default void onFirstContentfulPaint(@NonNull final GeckoSession session) {}
3131 
3132     /**
3133      * Notification that the paint status has been reset.
3134      *
3135      * <p>This callback is invoked whenever the painted content is no longer being displayed. This
3136      * can occur in response to the session being paused. After this has fired the compositor may
3137      * continue rendering, but may not render the page content. This callback can therefore be used
3138      * in conjunction with {@link #onFirstContentfulPaint(GeckoSession)} to determine when there is
3139      * valid content being rendered.
3140      *
3141      * @param session The GeckoSession that had the paint status reset event.
3142      */
3143     @UiThread
onPaintStatusReset(@onNull final GeckoSession session)3144     default void onPaintStatusReset(@NonNull final GeckoSession session) {}
3145 
3146     /**
3147      * A page has requested to change pointer icon.
3148      *
3149      * <p>If the application wants to control pointer icon, it should override this, then handle it.
3150      *
3151      * @param session The GeckoSession that initiated the callback.
3152      * @param icon The pointer icon sent from the content.
3153      */
3154     @TargetApi(Build.VERSION_CODES.N)
3155     @UiThread
onPointerIconChange( @onNull final GeckoSession session, @NonNull final PointerIcon icon)3156     default void onPointerIconChange(
3157         @NonNull final GeckoSession session, @NonNull final PointerIcon icon) {
3158       final View view = session.getTextInput().getView();
3159       if (view != null) {
3160         view.setPointerIcon(icon);
3161       }
3162     }
3163 
3164     /**
3165      * This is fired when the loaded document has a valid Web App Manifest present.
3166      *
3167      * <p>The various colors (theme_color, background_color, etc.) present in the manifest have been
3168      * transformed into #AARRGGBB format.
3169      *
3170      * @param session The GeckoSession that contains the Web App Manifest
3171      * @param manifest A parsed and validated {@link JSONObject} containing the manifest contents.
3172      * @see <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest specification</a>
3173      */
3174     @UiThread
onWebAppManifest( @onNull final GeckoSession session, @NonNull final JSONObject manifest)3175     default void onWebAppManifest(
3176         @NonNull final GeckoSession session, @NonNull final JSONObject manifest) {}
3177 
3178     /**
3179      * A script has exceeded its execution timeout value
3180      *
3181      * @param geckoSession GeckoSession that initiated the callback.
3182      * @param scriptFileName Filename of the slow script
3183      * @return A {@link GeckoResult} with a SlowScriptResponse value which indicates whether to
3184      *     allow the Slow Script to continue processing. Stop will halt the slow script. Continue
3185      *     will pause notifications for a period of time before resuming.
3186      */
3187     @UiThread
onSlowScript( @onNull final GeckoSession geckoSession, @NonNull final String scriptFileName)3188     default @Nullable GeckoResult<SlowScriptResponse> onSlowScript(
3189         @NonNull final GeckoSession geckoSession, @NonNull final String scriptFileName) {
3190       return null;
3191     }
3192 
3193     /**
3194      * The app should display its dynamic toolbar, fully expanded to the height that was previously
3195      * specified via {@link GeckoView#setDynamicToolbarMaxHeight}.
3196      *
3197      * @param geckoSession GeckoSession that initiated the callback.
3198      */
3199     @UiThread
onShowDynamicToolbar(@onNull final GeckoSession geckoSession)3200     default void onShowDynamicToolbar(@NonNull final GeckoSession geckoSession) {}
3201   }
3202 
3203   public interface SelectionActionDelegate {
3204     /** The selection is collapsed at a single position. */
3205     final int FLAG_IS_COLLAPSED = 1;
3206     /**
3207      * The selection is inside editable content such as an input element or contentEditable node.
3208      */
3209     final int FLAG_IS_EDITABLE = 2;
3210     /** The selection is inside a password field. */
3211     final int FLAG_IS_PASSWORD = 4;
3212 
3213     /** Hide selection actions and cause {@link #onHideAction} to be called. */
3214     final String ACTION_HIDE = "org.mozilla.geckoview.HIDE";
3215     /** Copy onto the clipboard then delete the selected content. Selection must be editable. */
3216     final String ACTION_CUT = "org.mozilla.geckoview.CUT";
3217     /** Copy the selected content onto the clipboard. */
3218     final String ACTION_COPY = "org.mozilla.geckoview.COPY";
3219     /** Delete the selected content. Selection must be editable. */
3220     final String ACTION_DELETE = "org.mozilla.geckoview.DELETE";
3221     /** Replace the selected content with the clipboard content. Selection must be editable. */
3222     final String ACTION_PASTE = "org.mozilla.geckoview.PASTE";
3223     /**
3224      * Replace the selected content with the clipboard content as plain text. Selection must be
3225      * editable.
3226      */
3227     final String ACTION_PASTE_AS_PLAIN_TEXT = "org.mozilla.geckoview.PASTE_AS_PLAIN_TEXT";
3228     /** Select the entire content of the document or editor. */
3229     final String ACTION_SELECT_ALL = "org.mozilla.geckoview.SELECT_ALL";
3230     /** Clear the current selection. Selection must not be editable. */
3231     final String ACTION_UNSELECT = "org.mozilla.geckoview.UNSELECT";
3232     /** Collapse the current selection to its start position. Selection must be editable. */
3233     final String ACTION_COLLAPSE_TO_START = "org.mozilla.geckoview.COLLAPSE_TO_START";
3234     /** Collapse the current selection to its end position. Selection must be editable. */
3235     final String ACTION_COLLAPSE_TO_END = "org.mozilla.geckoview.COLLAPSE_TO_END";
3236 
3237     /** Represents attributes of a selection. */
3238     class Selection {
3239       /**
3240        * Flags describing the current selection, as a bitwise combination of the {@link
3241        * #FLAG_IS_COLLAPSED FLAG_*} constants.
3242        */
3243       public final @SelectionActionDelegateFlag int flags;
3244 
3245       /**
3246        * Text content of the current selection. An empty string indicates the selection is collapsed
3247        * or the selection cannot be represented as plain text.
3248        */
3249       public final @NonNull String text;
3250 
3251       /**
3252        * The bounds of the current selection in client coordinates. Use {@link
3253        * GeckoSession#getClientToScreenMatrix} to perform transformation to screen coordinates.
3254        */
3255       public final @Nullable RectF clientRect;
3256 
3257       /** Set of valid actions available through {@link Selection#execute(String)} */
3258       public final @NonNull @SelectionActionDelegateAction Collection<String> availableActions;
3259 
3260       private final int mSeqNo;
3261 
3262       private final EventCallback mEventCallback;
3263 
Selection( final GeckoBundle bundle, final @NonNull @SelectionActionDelegateAction Set<String> actions, final EventCallback callback)3264       /* package */ Selection(
3265           final GeckoBundle bundle,
3266           final @NonNull @SelectionActionDelegateAction Set<String> actions,
3267           final EventCallback callback) {
3268         flags =
3269             (bundle.getBoolean("collapsed") ? SelectionActionDelegate.FLAG_IS_COLLAPSED : 0)
3270                 | (bundle.getBoolean("editable") ? SelectionActionDelegate.FLAG_IS_EDITABLE : 0)
3271                 | (bundle.getBoolean("password") ? SelectionActionDelegate.FLAG_IS_PASSWORD : 0);
3272         text = bundle.getString("selection");
3273 
3274         final GeckoBundle rectBundle = bundle.getBundle("clientRect");
3275         if (rectBundle == null) {
3276           clientRect = null;
3277         } else {
3278           clientRect =
3279               new RectF(
3280                   (float) rectBundle.getDouble("left"),
3281                   (float) rectBundle.getDouble("top"),
3282                   (float) rectBundle.getDouble("right"),
3283                   (float) rectBundle.getDouble("bottom"));
3284         }
3285 
3286         availableActions = actions;
3287         mSeqNo = bundle.getInt("seqNo");
3288         mEventCallback = callback;
3289       }
3290 
3291       /** Empty constructor for tests. */
Selection()3292       protected Selection() {
3293         flags = 0;
3294         text = "";
3295         clientRect = null;
3296         availableActions = new HashSet<>();
3297         mSeqNo = 0;
3298         mEventCallback = null;
3299       }
3300 
3301       /**
3302        * Checks if the passed action is available
3303        *
3304        * @param action An {@link SelectionActionDelegate} to perform
3305        * @return True if the action is available.
3306        */
3307       @AnyThread
isActionAvailable( @onNull @electionActionDelegateAction final String action)3308       public boolean isActionAvailable(
3309           @NonNull @SelectionActionDelegateAction final String action) {
3310         return availableActions.contains(action);
3311       }
3312 
3313       /**
3314        * Execute an {@link SelectionActionDelegate} action.
3315        *
3316        * @throws IllegalStateException If the action was not available.
3317        * @param action A {@link SelectionActionDelegate} action.
3318        */
3319       @AnyThread
execute(@onNull @electionActionDelegateAction final String action)3320       public void execute(@NonNull @SelectionActionDelegateAction final String action) {
3321         if (!isActionAvailable(action)) {
3322           throw new IllegalStateException("Action not available");
3323         }
3324         final GeckoBundle response = new GeckoBundle(2);
3325         response.putString("id", action);
3326         response.putInt("seqNo", mSeqNo);
3327         mEventCallback.sendSuccess(response);
3328       }
3329 
3330       /**
3331        * Hide selection actions and cause {@link #onHideAction} to be called.
3332        *
3333        * @throws IllegalStateException If the action was not available.
3334        */
3335       @AnyThread
hide()3336       public void hide() {
3337         execute(ACTION_HIDE);
3338       }
3339 
3340       /**
3341        * Copy onto the clipboard then delete the selected content.
3342        *
3343        * @throws IllegalStateException If the action was not available.
3344        */
3345       @AnyThread
cut()3346       public void cut() {
3347         execute(ACTION_CUT);
3348       }
3349 
3350       /**
3351        * Copy the selected content onto the clipboard.
3352        *
3353        * @throws IllegalStateException If the action was not available.
3354        */
3355       @AnyThread
copy()3356       public void copy() {
3357         execute(ACTION_COPY);
3358       }
3359 
3360       /**
3361        * Delete the selected content.
3362        *
3363        * @throws IllegalStateException If the action was not available.
3364        */
3365       @AnyThread
delete()3366       public void delete() {
3367         execute(ACTION_DELETE);
3368       }
3369 
3370       /**
3371        * Replace the selected content with the clipboard content.
3372        *
3373        * @throws IllegalStateException If the action was not available.
3374        */
3375       @AnyThread
paste()3376       public void paste() {
3377         execute(ACTION_PASTE);
3378       }
3379 
3380       /**
3381        * Replace the selected content with the clipboard content as plain text.
3382        *
3383        * @throws IllegalStateException If the action was not available.
3384        */
3385       @AnyThread
pasteAsPlainText()3386       public void pasteAsPlainText() {
3387         execute(ACTION_PASTE_AS_PLAIN_TEXT);
3388       }
3389 
3390       /**
3391        * Select the entire content of the document or editor.
3392        *
3393        * @throws IllegalStateException If the action was not available.
3394        */
3395       @AnyThread
selectAll()3396       public void selectAll() {
3397         execute(ACTION_SELECT_ALL);
3398       }
3399 
3400       /**
3401        * Clear the current selection.
3402        *
3403        * @throws IllegalStateException If the action was not available.
3404        */
3405       @AnyThread
unselect()3406       public void unselect() {
3407         execute(ACTION_UNSELECT);
3408       }
3409 
3410       /**
3411        * Collapse the current selection to its start position.
3412        *
3413        * @throws IllegalStateException If the action was not available.
3414        */
3415       @AnyThread
collapseToStart()3416       public void collapseToStart() {
3417         execute(ACTION_COLLAPSE_TO_START);
3418       }
3419 
3420       /**
3421        * Collapse the current selection to its end position.
3422        *
3423        * @throws IllegalStateException If the action was not available.
3424        */
3425       @AnyThread
collapseToEnd()3426       public void collapseToEnd() {
3427         execute(ACTION_COLLAPSE_TO_END);
3428       }
3429     }
3430 
3431     /**
3432      * Selection actions are available. Selection actions become available when the user selects
3433      * some content in the document or editor. Inside an editor, selection actions can also become
3434      * available when the user explicitly requests editor action UI, for example by tapping on the
3435      * caret handle.
3436      *
3437      * <p>In response to this callback, applications typically display a toolbar containing the
3438      * selection actions. To perform a certain action, check if the action is available with {@link
3439      * Selection#isActionAvailable} then either use the relevant helper method or {@link
3440      * Selection#execute}
3441      *
3442      * <p>Once an {@link #onHideAction} call (with particular reasons) or another {@link
3443      * #onShowActionRequest} call is received, the previous Selection object is no longer usable.
3444      *
3445      * @param session The GeckoSession that initiated the callback.
3446      * @param selection Current selection attributes and Callback object for performing built-in
3447      *     actions. May be used multiple times to perform multiple actions at once.
3448      */
3449     @UiThread
onShowActionRequest( @onNull final GeckoSession session, @NonNull final Selection selection)3450     default void onShowActionRequest(
3451         @NonNull final GeckoSession session, @NonNull final Selection selection) {}
3452 
3453     /** Actions are no longer available due to the user clearing the selection. */
3454     final int HIDE_REASON_NO_SELECTION = 0;
3455     /**
3456      * Actions are no longer available due to the user moving the selection out of view. Previous
3457      * actions are still available after a callback with this reason.
3458      */
3459     final int HIDE_REASON_INVISIBLE_SELECTION = 1;
3460     /**
3461      * Actions are no longer available due to the user actively changing the selection. {@link
3462      * #onShowActionRequest} may be called again once the user has set a selection, if the new
3463      * selection has available actions.
3464      */
3465     final int HIDE_REASON_ACTIVE_SELECTION = 2;
3466     /**
3467      * Actions are no longer available due to the user actively scrolling the page. {@link
3468      * #onShowActionRequest} may be called again once the user has stopped scrolling the page, if
3469      * the selection is still visible. Until then, previous actions are still available after a
3470      * callback with this reason.
3471      */
3472     final int HIDE_REASON_ACTIVE_SCROLL = 3;
3473 
3474     /**
3475      * Previous actions are no longer available due to the user interacting with the page.
3476      * Applications typically hide the action toolbar in response.
3477      *
3478      * @param session The GeckoSession that initiated the callback.
3479      * @param reason The reason that actions are no longer available, as one of the {@link
3480      *     #HIDE_REASON_NO_SELECTION HIDE_REASON_*} constants.
3481      */
3482     @UiThread
onHideAction( @onNull final GeckoSession session, @SelectionActionDelegateHideReason final int reason)3483     default void onHideAction(
3484         @NonNull final GeckoSession session, @SelectionActionDelegateHideReason final int reason) {}
3485   }
3486 
3487   @Retention(RetentionPolicy.SOURCE)
3488   @StringDef({
3489     SelectionActionDelegate.ACTION_HIDE,
3490     SelectionActionDelegate.ACTION_CUT,
3491     SelectionActionDelegate.ACTION_COPY,
3492     SelectionActionDelegate.ACTION_DELETE,
3493     SelectionActionDelegate.ACTION_PASTE,
3494     SelectionActionDelegate.ACTION_PASTE_AS_PLAIN_TEXT,
3495     SelectionActionDelegate.ACTION_SELECT_ALL,
3496     SelectionActionDelegate.ACTION_UNSELECT,
3497     SelectionActionDelegate.ACTION_COLLAPSE_TO_START,
3498     SelectionActionDelegate.ACTION_COLLAPSE_TO_END
3499   })
3500   public @interface SelectionActionDelegateAction {}
3501 
3502   @Retention(RetentionPolicy.SOURCE)
3503   @IntDef(
3504       flag = true,
3505       value = {
3506         SelectionActionDelegate.FLAG_IS_COLLAPSED,
3507         SelectionActionDelegate.FLAG_IS_EDITABLE,
3508         SelectionActionDelegate.FLAG_IS_PASSWORD
3509       })
3510   public @interface SelectionActionDelegateFlag {}
3511 
3512   @Retention(RetentionPolicy.SOURCE)
3513   @IntDef({
3514     SelectionActionDelegate.HIDE_REASON_NO_SELECTION,
3515     SelectionActionDelegate.HIDE_REASON_INVISIBLE_SELECTION,
3516     SelectionActionDelegate.HIDE_REASON_ACTIVE_SELECTION,
3517     SelectionActionDelegate.HIDE_REASON_ACTIVE_SCROLL
3518   })
3519   public @interface SelectionActionDelegateHideReason {}
3520 
3521   public interface NavigationDelegate {
3522     /**
3523      * A view has started loading content from the network.
3524      *
3525      * @param session The GeckoSession that initiated the callback.
3526      * @param url The resource being loaded.
3527      */
3528     @UiThread
3529     @DeprecationSchedule(id = "location-permissions", version = 92)
onLocationChange( @onNull final GeckoSession session, @Nullable final String url)3530     default void onLocationChange(
3531         @NonNull final GeckoSession session, @Nullable final String url) {}
3532 
3533     /**
3534      * A view has started loading content from the network.
3535      *
3536      * @param session The GeckoSession that initiated the callback.
3537      * @param url The resource being loaded.
3538      * @param perms The permissions currently associated with this url.
3539      */
3540     @UiThread
onLocationChange( @onNull GeckoSession session, @Nullable String url, final @NonNull List<PermissionDelegate.ContentPermission> perms)3541     default void onLocationChange(
3542         @NonNull GeckoSession session,
3543         @Nullable String url,
3544         final @NonNull List<PermissionDelegate.ContentPermission> perms) {
3545       session.getNavigationDelegate().onLocationChange(session, url);
3546     }
3547 
3548     /**
3549      * The view's ability to go back has changed.
3550      *
3551      * @param session The GeckoSession that initiated the callback.
3552      * @param canGoBack The new value for the ability.
3553      */
3554     @UiThread
onCanGoBack(@onNull final GeckoSession session, final boolean canGoBack)3555     default void onCanGoBack(@NonNull final GeckoSession session, final boolean canGoBack) {}
3556 
3557     /**
3558      * The view's ability to go forward has changed.
3559      *
3560      * @param session The GeckoSession that initiated the callback.
3561      * @param canGoForward The new value for the ability.
3562      */
3563     @UiThread
onCanGoForward(@onNull final GeckoSession session, final boolean canGoForward)3564     default void onCanGoForward(@NonNull final GeckoSession session, final boolean canGoForward) {}
3565 
3566     public static final int TARGET_WINDOW_NONE = 0;
3567     public static final int TARGET_WINDOW_CURRENT = 1;
3568     public static final int TARGET_WINDOW_NEW = 2;
3569 
3570     // Match with nsIWebNavigation.idl.
3571     /** The load request was triggered by an HTTP redirect. */
3572     static final int LOAD_REQUEST_IS_REDIRECT = 0x800000;
3573 
3574     /** Load request details. */
3575     public static class LoadRequest {
LoadRequest( @onNull final String uri, @Nullable final String triggerUri, final int geckoTarget, final int flags, final boolean hasUserGesture, final boolean isDirectNavigation)3576       /* package */ LoadRequest(
3577           @NonNull final String uri,
3578           @Nullable final String triggerUri,
3579           final int geckoTarget,
3580           final int flags,
3581           final boolean hasUserGesture,
3582           final boolean isDirectNavigation) {
3583         this.uri = uri;
3584         this.triggerUri = triggerUri;
3585         this.target = convertGeckoTarget(geckoTarget);
3586         this.isRedirect = (flags & LOAD_REQUEST_IS_REDIRECT) != 0;
3587         this.hasUserGesture = hasUserGesture;
3588         this.isDirectNavigation = isDirectNavigation;
3589       }
3590 
3591       /** Empty constructor for tests. */
LoadRequest()3592       protected LoadRequest() {
3593         uri = "";
3594         triggerUri = null;
3595         target = TARGET_WINDOW_NONE;
3596         isRedirect = false;
3597         hasUserGesture = false;
3598         isDirectNavigation = false;
3599       }
3600 
3601       // This needs to match nsIBrowserDOMWindow.idl
convertGeckoTarget(final int geckoTarget)3602       private @TargetWindow int convertGeckoTarget(final int geckoTarget) {
3603         switch (geckoTarget) {
3604           case 0: // OPEN_DEFAULTWINDOW
3605           case 1: // OPEN_CURRENTWINDOW
3606             return TARGET_WINDOW_CURRENT;
3607           default: // OPEN_NEWWINDOW, OPEN_NEWTAB
3608             return TARGET_WINDOW_NEW;
3609         }
3610       }
3611 
3612       /** The URI to be loaded. */
3613       public final @NonNull String uri;
3614 
3615       /**
3616        * The URI of the origin page that triggered the load request. null for initial loads and
3617        * loads originating from data: URIs.
3618        */
3619       public final @Nullable String triggerUri;
3620 
3621       /**
3622        * The target where the window has requested to open. One of {@link #TARGET_WINDOW_NONE
3623        * TARGET_WINDOW_*}.
3624        */
3625       public final @TargetWindow int target;
3626 
3627       /**
3628        * True if and only if the request was triggered by an HTTP redirect.
3629        *
3630        * <p>If the user loads URI "a", which redirects to URI "b", then <code>onLoadRequest</code>
3631        * will be called twice, first with uri "a" and <code>isRedirect = false</code>, then with uri
3632        * "b" and <code>isRedirect = true</code>.
3633        */
3634       public final boolean isRedirect;
3635 
3636       /** True if there was an active user gesture when the load was requested. */
3637       public final boolean hasUserGesture;
3638 
3639       /**
3640        * This load request was initiated by a direct navigation from the application. E.g. when
3641        * calling {@link GeckoSession#load}.
3642        */
3643       public final boolean isDirectNavigation;
3644 
3645       @Override
toString()3646       public String toString() {
3647         final StringBuilder out = new StringBuilder("LoadRequest { ");
3648         out.append("uri: " + uri)
3649             .append(", triggerUri: " + triggerUri)
3650             .append(", target: " + target)
3651             .append(", isRedirect: " + isRedirect)
3652             .append(", hasUserGesture: " + hasUserGesture)
3653             .append(", fromLoadUri: " + hasUserGesture)
3654             .append(" }");
3655         return out.toString();
3656       }
3657     }
3658 
3659     /**
3660      * A request to open an URI. This is called before each top-level page load to allow custom
3661      * behavior. For example, this can be used to override the behavior of TAGET_WINDOW_NEW
3662      * requests, which defaults to requesting a new GeckoSession via onNewSession.
3663      *
3664      * @param session The GeckoSession that initiated the callback.
3665      * @param request The {@link LoadRequest} containing the request details.
3666      * @return A {@link GeckoResult} with a {@link AllowOrDeny} value which indicates whether or not
3667      *     the load was handled. If unhandled, Gecko will continue the load as normal. If handled (a
3668      *     {@link AllowOrDeny#DENY DENY} value), Gecko will abandon the load. A null return value is
3669      *     interpreted as {@link AllowOrDeny#ALLOW ALLOW} (unhandled).
3670      */
3671     @UiThread
onLoadRequest( @onNull final GeckoSession session, @NonNull final LoadRequest request)3672     default @Nullable GeckoResult<AllowOrDeny> onLoadRequest(
3673         @NonNull final GeckoSession session, @NonNull final LoadRequest request) {
3674       return null;
3675     }
3676 
3677     /**
3678      * A request to load a URI in a non-top-level context.
3679      *
3680      * @param session The GeckoSession that initiated the callback.
3681      * @param request The {@link LoadRequest} containing the request details.
3682      * @return A {@link GeckoResult} with a {@link AllowOrDeny} value which indicates whether or not
3683      *     the load was handled. If unhandled, Gecko will continue the load as normal. If handled (a
3684      *     {@link AllowOrDeny#DENY DENY} value), Gecko will abandon the load. A null return value is
3685      *     interpreted as {@link AllowOrDeny#ALLOW ALLOW} (unhandled).
3686      */
3687     @UiThread
onSubframeLoadRequest( @onNull final GeckoSession session, @NonNull final LoadRequest request)3688     default @Nullable GeckoResult<AllowOrDeny> onSubframeLoadRequest(
3689         @NonNull final GeckoSession session, @NonNull final LoadRequest request) {
3690       return null;
3691     }
3692 
3693     /**
3694      * A request has been made to open a new session. The URI is provided only for informational
3695      * purposes. Do not call GeckoSession.load here. Additionally, the returned GeckoSession must be
3696      * a newly-created one.
3697      *
3698      * @param session The GeckoSession that initiated the callback.
3699      * @param uri The URI to be loaded.
3700      * @return A {@link GeckoResult} which holds the returned GeckoSession. May be null, in which
3701      *     case the request for a new window by web content will fail. e.g., <code>window.open()
3702      *     </code> will return null. The implementation of onNewSession is responsible for
3703      *     maintaining a reference to the returned object, to prevent it from being garbage
3704      *     collected.
3705      */
3706     @UiThread
onNewSession( @onNull final GeckoSession session, @NonNull final String uri)3707     default @Nullable GeckoResult<GeckoSession> onNewSession(
3708         @NonNull final GeckoSession session, @NonNull final String uri) {
3709       return null;
3710     }
3711 
3712     /**
3713      * @param session The GeckoSession that initiated the callback.
3714      * @param uri The URI that failed to load.
3715      * @param error A WebRequestError containing details about the error
3716      * @return A URI to display as an error. Returning null will halt the load entirely. The
3717      *     following special methods are made available to the URI: -
3718      *     document.addCertException(isTemporary), returns Promise -
3719      *     document.getFailedCertSecurityInfo(), returns FailedCertSecurityInfo -
3720      *     document.getNetErrorInfo(), returns NetErrorInfo - document.allowDeprecatedTls, a
3721      *     property indicating whether or not TLS 1.0/1.1 is allowed -
3722      *     document.reloadWithHttpsOnlyException()
3723      * @see <a
3724      *     href="https://searchfox.org/mozilla-central/source/dom/webidl/FailedCertSecurityInfo.webidl">FailedCertSecurityInfo
3725      *     IDL</a>
3726      * @see <a
3727      *     href="https://searchfox.org/mozilla-central/source/dom/webidl/NetErrorInfo.webidl">NetErrorInfo
3728      *     IDL</a>
3729      */
3730     @UiThread
onLoadError( @onNull final GeckoSession session, @Nullable final String uri, @NonNull final WebRequestError error)3731     default @Nullable GeckoResult<String> onLoadError(
3732         @NonNull final GeckoSession session,
3733         @Nullable final String uri,
3734         @NonNull final WebRequestError error) {
3735       return null;
3736     }
3737   }
3738 
3739   @Retention(RetentionPolicy.SOURCE)
3740   @IntDef({
3741     NavigationDelegate.TARGET_WINDOW_NONE,
3742     NavigationDelegate.TARGET_WINDOW_CURRENT,
3743     NavigationDelegate.TARGET_WINDOW_NEW
3744   })
3745   public @interface TargetWindow {}
3746 
3747   /**
3748    * GeckoSession applications implement this interface to handle prompts triggered by content in
3749    * the GeckoSession, such as alerts, authentication dialogs, and select list pickers.
3750    */
3751   public interface PromptDelegate {
3752     /** PromptResponse is an opaque class created upon confirming or dismissing a prompt. */
3753     public class PromptResponse {
3754       private final BasePrompt mPrompt;
3755 
PromptResponse(@onNull final BasePrompt prompt)3756       /* package */ PromptResponse(@NonNull final BasePrompt prompt) {
3757         mPrompt = prompt;
3758       }
3759 
dispatch(@onNull final EventCallback callback)3760       /* package */ void dispatch(@NonNull final EventCallback callback) {
3761         if (mPrompt == null) {
3762           throw new RuntimeException("Trying to confirm/dismiss a null prompt.");
3763         }
3764         mPrompt.dispatch(callback);
3765       }
3766     }
3767 
3768     interface PromptInstanceDelegate {
3769       /**
3770        * Called when this prompt has been dismissed by the system.
3771        *
3772        * <p>This can happen e.g. when the page navigates away and the content of the prompt is not
3773        * relevant anymore.
3774        *
3775        * <p>When this method is called, you should hide the prompt UI elements.
3776        *
3777        * @param prompt the prompt that should be dismissed.
3778        */
3779       @UiThread
onPromptDismiss(final @NonNull BasePrompt prompt)3780       default void onPromptDismiss(final @NonNull BasePrompt prompt) {}
3781     }
3782 
3783     // Prompt classes.
3784     public class BasePrompt {
3785       private boolean mIsCompleted;
3786       private boolean mIsConfirmed;
3787       private GeckoBundle mResult;
3788       private final WeakReference<Observer> mObserver;
3789       private PromptInstanceDelegate mDelegate;
3790 
3791       protected interface Observer {
3792         @AnyThread
onPromptCompleted(@onNull BasePrompt prompt)3793         default void onPromptCompleted(@NonNull BasePrompt prompt) {}
3794       }
3795 
complete()3796       private void complete() {
3797         mIsCompleted = true;
3798         final Observer observer = mObserver.get();
3799         if (observer != null) {
3800           observer.onPromptCompleted(this);
3801         }
3802       }
3803 
3804       /** The title of this prompt; may be null. */
3805       public final @Nullable String title;
3806       /* package */ String id;
3807 
BasePrompt( @onNull final String id, @Nullable final String title, final Observer observer)3808       private BasePrompt(
3809           @NonNull final String id, @Nullable final String title, final Observer observer) {
3810         this.title = title;
3811         this.id = id;
3812         mIsConfirmed = false;
3813         mIsCompleted = false;
3814         mObserver = new WeakReference<>(observer);
3815       }
3816 
3817       @UiThread
confirm()3818       protected @NonNull PromptResponse confirm() {
3819         if (mIsCompleted) {
3820           throw new RuntimeException("Cannot confirm/dismiss a Prompt twice.");
3821         }
3822 
3823         mIsConfirmed = true;
3824         complete();
3825         return new PromptResponse(this);
3826       }
3827 
3828       /**
3829        * This dismisses the prompt without sending any meaningful information back to content.
3830        *
3831        * @return A {@link PromptResponse} with which you can complete the {@link GeckoResult} that
3832        *     corresponds to this prompt.
3833        */
3834       @UiThread
dismiss()3835       public @NonNull PromptResponse dismiss() {
3836         if (mIsCompleted) {
3837           throw new RuntimeException("Cannot confirm/dismiss a Prompt twice.");
3838         }
3839 
3840         complete();
3841         return new PromptResponse(this);
3842       }
3843 
3844       /**
3845        * Set the delegate for this prompt.
3846        *
3847        * @param delegate the {@link PromptInstanceDelegate} instance.
3848        */
3849       @UiThread
setDelegate(final @Nullable PromptInstanceDelegate delegate)3850       public void setDelegate(final @Nullable PromptInstanceDelegate delegate) {
3851         mDelegate = delegate;
3852       }
3853 
3854       /**
3855        * Get the delegate for this prompt.
3856        *
3857        * @return the {@link PromptInstanceDelegate} instance.
3858        */
3859       @UiThread
3860       @Nullable
getDelegate()3861       public PromptInstanceDelegate getDelegate() {
3862         return mDelegate;
3863       }
3864 
ensureResult()3865       /* package */ GeckoBundle ensureResult() {
3866         if (mResult == null) {
3867           // Usually result object contains two items.
3868           mResult = new GeckoBundle(2);
3869         }
3870         return mResult;
3871       }
3872 
3873       /**
3874        * This returns true if the prompt has already been confirmed or dismissed.
3875        *
3876        * @return A boolean which is true if the prompt has been confirmed or dismissed, and false
3877        *     otherwise.
3878        */
3879       @UiThread
isComplete()3880       public boolean isComplete() {
3881         return mIsCompleted;
3882       }
3883 
dispatch(@onNull final EventCallback callback)3884       /* package */ void dispatch(@NonNull final EventCallback callback) {
3885         if (!mIsCompleted) {
3886           throw new RuntimeException("Trying to dispatch an incomplete prompt.");
3887         }
3888 
3889         if (!mIsConfirmed) {
3890           callback.sendSuccess(null);
3891         } else {
3892           callback.sendSuccess(mResult);
3893         }
3894       }
3895     }
3896 
3897     /**
3898      * BeforeUnloadPrompt represents the onbeforeunload prompt. See
3899      * https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
3900      */
3901     class BeforeUnloadPrompt extends BasePrompt {
BeforeUnloadPrompt(@onNull final String id, @NonNull final Observer observer)3902       protected BeforeUnloadPrompt(@NonNull final String id, @NonNull final Observer observer) {
3903         super(id, null, observer);
3904       }
3905 
3906       /**
3907        * Confirms the prompt.
3908        *
3909        * @param allowOrDeny whether the navigation should be allowed to continue or not.
3910        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
3911        *     associated with this prompt.
3912        */
3913       @UiThread
confirm(final @Nullable AllowOrDeny allowOrDeny)3914       public @NonNull PromptResponse confirm(final @Nullable AllowOrDeny allowOrDeny) {
3915         ensureResult().putBoolean("allow", allowOrDeny != AllowOrDeny.DENY);
3916         return super.confirm();
3917       }
3918     }
3919 
3920     /**
3921      * RepostConfirmPrompt represents a prompt shown whenever the browser needs to resubmit POST
3922      * data (e.g. due to page refresh).
3923      */
3924     class RepostConfirmPrompt extends BasePrompt {
RepostConfirmPrompt(@onNull final String id, @NonNull final Observer observer)3925       protected RepostConfirmPrompt(@NonNull final String id, @NonNull final Observer observer) {
3926         super(id, null, observer);
3927       }
3928 
3929       /**
3930        * Confirms the prompt.
3931        *
3932        * @param allowOrDeny whether the browser should allow resubmitting data.
3933        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
3934        *     associated with this prompt.
3935        */
3936       @UiThread
confirm(final @Nullable AllowOrDeny allowOrDeny)3937       public @NonNull PromptResponse confirm(final @Nullable AllowOrDeny allowOrDeny) {
3938         ensureResult().putBoolean("allow", allowOrDeny != AllowOrDeny.DENY);
3939         return super.confirm();
3940       }
3941     }
3942 
3943     /**
3944      * AlertPrompt contains the information necessary to represent a JavaScript alert() call from
3945      * content; it can only be dismissed, not confirmed.
3946      */
3947     public class AlertPrompt extends BasePrompt {
3948       /** The message to be displayed with this alert; may be null. */
3949       public final @Nullable String message;
3950 
AlertPrompt( @onNull final String id, @Nullable final String title, @Nullable final String message, @NonNull final Observer observer)3951       protected AlertPrompt(
3952           @NonNull final String id,
3953           @Nullable final String title,
3954           @Nullable final String message,
3955           @NonNull final Observer observer) {
3956         super(id, title, observer);
3957         this.message = message;
3958       }
3959     }
3960 
3961     /**
3962      * ButtonPrompt contains the information necessary to represent a JavaScript confirm() call from
3963      * content.
3964      */
3965     public class ButtonPrompt extends BasePrompt {
3966       @Retention(RetentionPolicy.SOURCE)
3967       @IntDef({Type.POSITIVE, Type.NEGATIVE})
3968       public @interface ButtonType {}
3969 
3970       public static class Type {
3971         /** Index of positive response button (eg, "Yes", "OK") */
3972         public static final int POSITIVE = 0;
3973 
3974         /** Index of negative response button (eg, "No", "Cancel") */
3975         public static final int NEGATIVE = 2;
3976 
Type()3977         protected Type() {}
3978       }
3979 
3980       /** The message to be displayed with this prompt; may be null. */
3981       public final @Nullable String message;
3982 
ButtonPrompt( @onNull final String id, @Nullable final String title, @Nullable final String message, @NonNull final Observer observer)3983       protected ButtonPrompt(
3984           @NonNull final String id,
3985           @Nullable final String title,
3986           @Nullable final String message,
3987           @NonNull final Observer observer) {
3988         super(id, title, observer);
3989         this.message = message;
3990       }
3991 
3992       /**
3993        * Confirms this prompt, returning the selected button to content.
3994        *
3995        * @param selection An int representing the selected button, must be one of {@link Type}.
3996        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
3997        *     associated with this prompt.
3998        */
3999       @UiThread
confirm(@uttonType final int selection)4000       public @NonNull PromptResponse confirm(@ButtonType final int selection) {
4001         ensureResult().putInt("button", selection);
4002         return super.confirm();
4003       }
4004     }
4005 
4006     /**
4007      * TextPrompt contains the information necessary to represent a Javascript prompt() call from
4008      * content.
4009      */
4010     public class TextPrompt extends BasePrompt {
4011       /** The message to be displayed with this prompt; may be null. */
4012       public final @Nullable String message;
4013 
4014       /** The default value for the text field; may be null. */
4015       public final @Nullable String defaultValue;
4016 
TextPrompt( @onNull final String id, @Nullable final String title, @Nullable final String message, @Nullable final String defaultValue, @NonNull final Observer observer)4017       protected TextPrompt(
4018           @NonNull final String id,
4019           @Nullable final String title,
4020           @Nullable final String message,
4021           @Nullable final String defaultValue,
4022           @NonNull final Observer observer) {
4023         super(id, title, observer);
4024         this.message = message;
4025         this.defaultValue = defaultValue;
4026       }
4027 
4028       /**
4029        * Confirms this prompt, returning the input text to content.
4030        *
4031        * @param text A String containing the text input given by the user.
4032        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4033        *     associated with this prompt.
4034        */
4035       @UiThread
confirm(@onNull final String text)4036       public @NonNull PromptResponse confirm(@NonNull final String text) {
4037         ensureResult().putString("text", text);
4038         return super.confirm();
4039       }
4040     }
4041 
4042     /**
4043      * AuthPrompt contains the information necessary to represent an HTML authorization prompt
4044      * generated by content.
4045      */
4046     public class AuthPrompt extends BasePrompt {
4047       public static class AuthOptions {
4048         @Retention(RetentionPolicy.SOURCE)
4049         @IntDef(
4050             flag = true,
4051             value = {
4052               Flags.HOST,
4053               Flags.PROXY,
4054               Flags.ONLY_PASSWORD,
4055               Flags.PREVIOUS_FAILED,
4056               Flags.CROSS_ORIGIN_SUB_RESOURCE
4057             })
4058         public @interface AuthFlag {}
4059 
4060         /** Auth prompt flags. */
4061         public static class Flags {
4062           /** The auth prompt is for a network host. */
4063           public static final int HOST = 1;
4064           /** The auth prompt is for a proxy. */
4065           public static final int PROXY = 2;
4066           /** The auth prompt should only request a password. */
4067           public static final int ONLY_PASSWORD = 8;
4068           /** The auth prompt is the result of a previous failed login. */
4069           public static final int PREVIOUS_FAILED = 16;
4070           /** The auth prompt is for a cross-origin sub-resource. */
4071           public static final int CROSS_ORIGIN_SUB_RESOURCE = 32;
4072 
Flags()4073           protected Flags() {}
4074         }
4075 
4076         @Retention(RetentionPolicy.SOURCE)
4077         @IntDef({Level.NONE, Level.PW_ENCRYPTED, Level.SECURE})
4078         public @interface AuthLevel {}
4079 
4080         /** Auth prompt levels. */
4081         public static class Level {
4082           /** The auth request is unencrypted or the encryption status is unknown. */
4083           public static final int NONE = 0;
4084           /** The auth request only encrypts password but not data. */
4085           public static final int PW_ENCRYPTED = 1;
4086           /** The auth request encrypts both password and data. */
4087           public static final int SECURE = 2;
4088 
Level()4089           protected Level() {}
4090         }
4091 
4092         /** An int bit-field of {@link Flags}. */
4093         public @AuthFlag final int flags;
4094 
4095         /** A string containing the URI for the auth request or null if unknown. */
4096         public @Nullable final String uri;
4097 
4098         /** An int, one of {@link Level}, indicating level of encryption. */
4099         public @AuthLevel final int level;
4100 
4101         /** A string containing the initial username or null if password-only. */
4102         public @Nullable final String username;
4103 
4104         /** A string containing the initial password. */
4105         public @Nullable final String password;
4106 
AuthOptions(final GeckoBundle options)4107         /* package */ AuthOptions(final GeckoBundle options) {
4108           flags = options.getInt("flags");
4109           uri = options.getString("uri");
4110           level = options.getInt("level");
4111           username = options.getString("username");
4112           password = options.getString("password");
4113         }
4114 
4115         /** Empty constructor for tests */
AuthOptions()4116         protected AuthOptions() {
4117           flags = 0;
4118           uri = "";
4119           level = Level.NONE;
4120           username = "";
4121           password = "";
4122         }
4123       }
4124 
4125       /** The message to be displayed with this prompt; may be null. */
4126       public final @Nullable String message;
4127 
4128       /** The {@link AuthOptions} that describe the type of authorization prompt. */
4129       public final @NonNull AuthOptions authOptions;
4130 
AuthPrompt( @onNull final String id, @Nullable final String title, @Nullable final String message, @NonNull final AuthOptions authOptions, @NonNull final Observer observer)4131       protected AuthPrompt(
4132           @NonNull final String id,
4133           @Nullable final String title,
4134           @Nullable final String message,
4135           @NonNull final AuthOptions authOptions,
4136           @NonNull final Observer observer) {
4137         super(id, title, observer);
4138         this.message = message;
4139         this.authOptions = authOptions;
4140       }
4141 
4142       /**
4143        * Confirms this prompt with just a password, returning the password to content.
4144        *
4145        * @param password A String containing the password input by the user.
4146        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4147        *     associated with this prompt.
4148        */
4149       @UiThread
confirm(@onNull final String password)4150       public @NonNull PromptResponse confirm(@NonNull final String password) {
4151         ensureResult().putString("password", password);
4152         return super.confirm();
4153       }
4154 
4155       /**
4156        * Confirms this prompt with a username and password, returning both to content.
4157        *
4158        * @param username A String containing the username input by the user.
4159        * @param password A String containing the password input by the user.
4160        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4161        *     associated with this prompt.
4162        */
4163       @UiThread
confirm( @onNull final String username, @NonNull final String password)4164       public @NonNull PromptResponse confirm(
4165           @NonNull final String username, @NonNull final String password) {
4166         ensureResult().putString("username", username);
4167         ensureResult().putString("password", password);
4168         return super.confirm();
4169       }
4170     }
4171 
4172     /**
4173      * ChoicePrompt contains the information necessary to display a menu or list prompt generated by
4174      * content.
4175      */
4176     public class ChoicePrompt extends BasePrompt {
4177       public static class Choice {
4178         /**
4179          * A boolean indicating if the item is disabled. Item should not be selectable if this is
4180          * true.
4181          */
4182         public final boolean disabled;
4183 
4184         /**
4185          * A String giving the URI of the item icon, or null if none exists (only valid for menus)
4186          */
4187         public final @Nullable String icon;
4188 
4189         /** A String giving the ID of the item or group */
4190         public final @NonNull String id;
4191 
4192         /** A Choice array of sub-items in a group, or null if not a group */
4193         public final @Nullable Choice[] items;
4194 
4195         /** A string giving the label for displaying the item or group */
4196         public final @NonNull String label;
4197 
4198         /** A boolean indicating if the item should be pre-selected (pre-checked for menu items) */
4199         public final boolean selected;
4200 
4201         /** A boolean indicating if the item should be a menu separator (only valid for menus) */
4202         public final boolean separator;
4203 
Choice(final GeckoBundle choice)4204         /* package */ Choice(final GeckoBundle choice) {
4205           disabled = choice.getBoolean("disabled");
4206           icon = choice.getString("icon");
4207           id = choice.getString("id");
4208           label = choice.getString("label");
4209           selected = choice.getBoolean("selected");
4210           separator = choice.getBoolean("separator");
4211 
4212           final GeckoBundle[] choices = choice.getBundleArray("items");
4213           if (choices == null) {
4214             items = null;
4215           } else {
4216             items = new Choice[choices.length];
4217             for (int i = 0; i < choices.length; i++) {
4218               items[i] = new Choice(choices[i]);
4219             }
4220           }
4221         }
4222 
4223         /** Empty constructor for tests. */
Choice()4224         protected Choice() {
4225           disabled = false;
4226           icon = "";
4227           id = "";
4228           label = "";
4229           selected = false;
4230           separator = false;
4231           items = null;
4232         }
4233       }
4234 
4235       @Retention(RetentionPolicy.SOURCE)
4236       @IntDef({Type.MENU, Type.SINGLE, Type.MULTIPLE})
4237       public @interface ChoiceType {}
4238 
4239       public static class Type {
4240         /** Display choices in a menu that dismisses as soon as an item is chosen. */
4241         public static final int MENU = 1;
4242 
4243         /** Display choices in a list that allows a single selection. */
4244         public static final int SINGLE = 2;
4245 
4246         /** Display choices in a list that allows multiple selections. */
4247         public static final int MULTIPLE = 3;
4248 
Type()4249         protected Type() {}
4250       }
4251 
4252       /** The message to be displayed with this prompt; may be null. */
4253       public final @Nullable String message;
4254 
4255       /** One of {@link Type}. */
4256       public final @ChoiceType int type;
4257 
4258       /** An array of {@link Choice} representing possible choices. */
4259       public final @NonNull Choice[] choices;
4260 
ChoicePrompt( @onNull final String id, @Nullable final String title, @Nullable final String message, @ChoiceType final int type, @NonNull final Choice[] choices, @NonNull final Observer observer)4261       protected ChoicePrompt(
4262           @NonNull final String id,
4263           @Nullable final String title,
4264           @Nullable final String message,
4265           @ChoiceType final int type,
4266           @NonNull final Choice[] choices,
4267           @NonNull final Observer observer) {
4268         super(id, title, observer);
4269         this.message = message;
4270         this.type = type;
4271         this.choices = choices;
4272       }
4273 
4274       /**
4275        * Confirms this prompt with the string id of a single choice.
4276        *
4277        * @param selectedId The string ID of the selected choice.
4278        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4279        *     associated with this prompt.
4280        */
4281       @UiThread
confirm(@onNull final String selectedId)4282       public @NonNull PromptResponse confirm(@NonNull final String selectedId) {
4283         return confirm(new String[] {selectedId});
4284       }
4285 
4286       /**
4287        * Confirms this prompt with the string ids of multiple choices
4288        *
4289        * @param selectedIds The string IDs of the selected choices.
4290        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4291        *     associated with this prompt.
4292        */
4293       @UiThread
confirm(@onNull final String[] selectedIds)4294       public @NonNull PromptResponse confirm(@NonNull final String[] selectedIds) {
4295         if ((Type.MENU == type || Type.SINGLE == type)
4296             && (selectedIds == null || selectedIds.length != 1)) {
4297           throw new IllegalArgumentException();
4298         }
4299         ensureResult().putStringArray("choices", selectedIds);
4300         return super.confirm();
4301       }
4302 
4303       /**
4304        * Confirms this prompt with a single choice.
4305        *
4306        * @param selectedChoice The selected choice.
4307        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4308        *     associated with this prompt.
4309        */
4310       @UiThread
confirm(@onNull final Choice selectedChoice)4311       public @NonNull PromptResponse confirm(@NonNull final Choice selectedChoice) {
4312         return confirm(selectedChoice == null ? null : selectedChoice.id);
4313       }
4314 
4315       /**
4316        * Confirms this prompt with multiple choices.
4317        *
4318        * @param selectedChoices The selected choices.
4319        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4320        *     associated with this prompt.
4321        */
4322       @UiThread
confirm(@onNull final Choice[] selectedChoices)4323       public @NonNull PromptResponse confirm(@NonNull final Choice[] selectedChoices) {
4324         if ((Type.MENU == type || Type.SINGLE == type)
4325             && (selectedChoices == null || selectedChoices.length != 1)) {
4326           throw new IllegalArgumentException();
4327         }
4328 
4329         if (selectedChoices == null) {
4330           return confirm((String[]) null);
4331         }
4332 
4333         final String[] ids = new String[selectedChoices.length];
4334         for (int i = 0; i < ids.length; i++) {
4335           ids[i] = (selectedChoices[i] == null) ? null : selectedChoices[i].id;
4336         }
4337 
4338         return confirm(ids);
4339       }
4340     }
4341 
4342     /**
4343      * ColorPrompt contains the information necessary to represent a prompt for color input
4344      * generated by content.
4345      */
4346     public class ColorPrompt extends BasePrompt {
4347       /** The default value supplied by content. */
4348       public final @Nullable String defaultValue;
4349 
ColorPrompt( @onNull final String id, @Nullable final String title, @Nullable final String defaultValue, @NonNull final Observer observer)4350       protected ColorPrompt(
4351           @NonNull final String id,
4352           @Nullable final String title,
4353           @Nullable final String defaultValue,
4354           @NonNull final Observer observer) {
4355         super(id, title, observer);
4356         this.defaultValue = defaultValue;
4357       }
4358 
4359       /**
4360        * Confirms the prompt and passes the color value back to content.
4361        *
4362        * @param color A String representing the color to be returned to content.
4363        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4364        *     associated with this prompt.
4365        */
4366       @UiThread
confirm(@onNull final String color)4367       public @NonNull PromptResponse confirm(@NonNull final String color) {
4368         ensureResult().putString("color", color);
4369         return super.confirm();
4370       }
4371     }
4372 
4373     /**
4374      * DateTimePrompt contains the information necessary to represent a prompt for date and/or time
4375      * input generated by content.
4376      */
4377     public class DateTimePrompt extends BasePrompt {
4378       @Retention(RetentionPolicy.SOURCE)
4379       @IntDef({Type.DATE, Type.MONTH, Type.WEEK, Type.TIME, Type.DATETIME_LOCAL})
4380       public @interface DatetimeType {}
4381 
4382       public static class Type {
4383         /** Prompt for year, month, and day. */
4384         public static final int DATE = 1;
4385 
4386         /** Prompt for year and month. */
4387         public static final int MONTH = 2;
4388 
4389         /** Prompt for year and week. */
4390         public static final int WEEK = 3;
4391 
4392         /** Prompt for hour and minute. */
4393         public static final int TIME = 4;
4394 
4395         /** Prompt for year, month, day, hour, and minute, without timezone. */
4396         public static final int DATETIME_LOCAL = 5;
4397 
Type()4398         protected Type() {}
4399       }
4400 
4401       /** One of {@link Type} indicating the type of prompt. */
4402       public final @DatetimeType int type;
4403 
4404       /** A String representing the default value supplied by content. */
4405       public final @Nullable String defaultValue;
4406 
4407       /** A String representing the minimum value allowed by content. */
4408       public final @Nullable String minValue;
4409 
4410       /** A String representing the maximum value allowed by content. */
4411       public final @Nullable String maxValue;
4412 
DateTimePrompt( @onNull final String id, @Nullable final String title, @DatetimeType final int type, @Nullable final String defaultValue, @Nullable final String minValue, @Nullable final String maxValue, @NonNull final Observer observer)4413       protected DateTimePrompt(
4414           @NonNull final String id,
4415           @Nullable final String title,
4416           @DatetimeType final int type,
4417           @Nullable final String defaultValue,
4418           @Nullable final String minValue,
4419           @Nullable final String maxValue,
4420           @NonNull final Observer observer) {
4421         super(id, title, observer);
4422         this.type = type;
4423         this.defaultValue = defaultValue;
4424         this.minValue = minValue;
4425         this.maxValue = maxValue;
4426       }
4427 
4428       /**
4429        * Confirms the prompt and passes the date and/or time value back to content.
4430        *
4431        * @param datetime A String representing the date and time to be returned to content.
4432        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4433        *     associated with this prompt.
4434        */
4435       @UiThread
confirm(@onNull final String datetime)4436       public @NonNull PromptResponse confirm(@NonNull final String datetime) {
4437         ensureResult().putString("datetime", datetime);
4438         return super.confirm();
4439       }
4440     }
4441 
4442     /**
4443      * FilePrompt contains the information necessary to represent a prompt for a file or files
4444      * generated by content.
4445      */
4446     public class FilePrompt extends BasePrompt {
4447       @Retention(RetentionPolicy.SOURCE)
4448       @IntDef({Type.SINGLE, Type.MULTIPLE})
4449       public @interface FileType {}
4450 
4451       /** Types of file prompts. */
4452       public static class Type {
4453         /** Prompt for a single file. */
4454         public static final int SINGLE = 1;
4455 
4456         /** Prompt for multiple files. */
4457         public static final int MULTIPLE = 2;
4458 
Type()4459         protected Type() {}
4460       }
4461 
4462       @Retention(RetentionPolicy.SOURCE)
4463       @IntDef({Capture.NONE, Capture.ANY, Capture.USER, Capture.ENVIRONMENT})
4464       public @interface CaptureType {}
4465 
4466       /** Possible capture attribute values. */
4467       public static class Capture {
4468         // These values should match the corresponding values in nsIFilePicker.idl
4469         /** No capture attribute has been supplied by content. */
4470         public static final int NONE = 0;
4471 
4472         /** The capture attribute was supplied with a missing or invalid value. */
4473         public static final int ANY = 1;
4474 
4475         /** The "user" capture attribute has been supplied by content. */
4476         public static final int USER = 2;
4477 
4478         /** The "environment" capture attribute has been supplied by content. */
4479         public static final int ENVIRONMENT = 3;
4480 
Capture()4481         protected Capture() {}
4482       }
4483 
4484       /** One of {@link Type} indicating the prompt type. */
4485       public final @FileType int type;
4486 
4487       /**
4488        * An array of Strings giving the MIME types specified by the "accept" attribute, if any are
4489        * specified.
4490        */
4491       public final @Nullable String[] mimeTypes;
4492 
4493       /** One of {@link Capture} indicating the capture attribute supplied by content. */
4494       public final @CaptureType int capture;
4495 
FilePrompt( @onNull final String id, @Nullable final String title, @FileType final int type, @CaptureType final int capture, @Nullable final String[] mimeTypes, @NonNull final Observer observer)4496       protected FilePrompt(
4497           @NonNull final String id,
4498           @Nullable final String title,
4499           @FileType final int type,
4500           @CaptureType final int capture,
4501           @Nullable final String[] mimeTypes,
4502           @NonNull final Observer observer) {
4503         super(id, title, observer);
4504         this.type = type;
4505         this.capture = capture;
4506         this.mimeTypes = mimeTypes;
4507       }
4508 
4509       /**
4510        * Confirms the prompt and passes the file URI back to content.
4511        *
4512        * @param context An Application context for parsing URIs.
4513        * @param uri The URI of the file chosen by the user.
4514        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4515        *     associated with this prompt.
4516        */
4517       @UiThread
confirm( @onNull final Context context, @NonNull final Uri uri)4518       public @NonNull PromptResponse confirm(
4519           @NonNull final Context context, @NonNull final Uri uri) {
4520         return confirm(context, new Uri[] {uri});
4521       }
4522 
4523       /**
4524        * Confirms the prompt and passes the file URIs back to content.
4525        *
4526        * @param context An Application context for parsing URIs.
4527        * @param uris The URIs of the files chosen by the user.
4528        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4529        *     associated with this prompt.
4530        */
4531       @UiThread
confirm( @onNull final Context context, @NonNull final Uri[] uris)4532       public @NonNull PromptResponse confirm(
4533           @NonNull final Context context, @NonNull final Uri[] uris) {
4534         if (Type.SINGLE == type && (uris == null || uris.length != 1)) {
4535           throw new IllegalArgumentException();
4536         }
4537 
4538         final String[] paths = new String[uris != null ? uris.length : 0];
4539         for (int i = 0; i < paths.length; i++) {
4540           paths[i] = getFile(context, uris[i]);
4541           if (paths[i] == null) {
4542             Log.e(LOGTAG, "Only file URIs are supported: " + uris[i]);
4543           }
4544         }
4545         ensureResult().putStringArray("files", paths);
4546 
4547         return super.confirm();
4548       }
4549 
getFile(final @NonNull Context context, final @NonNull Uri uri)4550       private static String getFile(final @NonNull Context context, final @NonNull Uri uri) {
4551         if (uri == null) {
4552           return null;
4553         }
4554         if ("file".equals(uri.getScheme())) {
4555           return uri.getPath();
4556         }
4557         final ContentResolver cr = context.getContentResolver();
4558         final Cursor cur =
4559             cr.query(
4560                 uri,
4561                 new String[] {"_data"}, /* selection */
4562                 null,
4563                 /* args */ null, /* sort */
4564                 null);
4565         if (cur == null) {
4566           return null;
4567         }
4568         try {
4569           final int idx = cur.getColumnIndex("_data");
4570           if (idx < 0 || !cur.moveToFirst()) {
4571             return null;
4572           }
4573           do {
4574             try {
4575               final String path = cur.getString(idx);
4576               if (path != null && !path.isEmpty()) {
4577                 return path;
4578               }
4579             } catch (final Exception e) {
4580             }
4581           } while (cur.moveToNext());
4582         } finally {
4583           cur.close();
4584         }
4585         return null;
4586       }
4587     }
4588 
4589     /** PopupPrompt contains the information necessary to represent a popup blocking request. */
4590     public class PopupPrompt extends BasePrompt {
4591       /** The target URI for the popup; may be null. */
4592       public final @Nullable String targetUri;
4593 
PopupPrompt( @onNull final String id, @Nullable final String targetUri, @NonNull final Observer observer)4594       protected PopupPrompt(
4595           @NonNull final String id,
4596           @Nullable final String targetUri,
4597           @NonNull final Observer observer) {
4598         super(id, null, observer);
4599         this.targetUri = targetUri;
4600       }
4601 
4602       /**
4603        * Confirms the prompt and either allows or blocks the popup.
4604        *
4605        * @param response An {@link AllowOrDeny} specifying whether to allow or deny the popup.
4606        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4607        *     associated with this prompt.
4608        */
4609       @UiThread
confirm(@onNull final AllowOrDeny response)4610       public @NonNull PromptResponse confirm(@NonNull final AllowOrDeny response) {
4611         boolean res = false;
4612         if (AllowOrDeny.ALLOW == response) {
4613           res = true;
4614         }
4615         ensureResult().putBoolean("response", res);
4616         return super.confirm();
4617       }
4618     }
4619 
4620     /** SharePrompt contains the information necessary to represent a (v1) WebShare request. */
4621     public class SharePrompt extends BasePrompt {
4622       @Retention(RetentionPolicy.SOURCE)
4623       @IntDef({Result.SUCCESS, Result.FAILURE, Result.ABORT})
4624       public @interface ShareResult {}
4625 
4626       /** Possible results to a {@link SharePrompt}. */
4627       public static class Result {
4628         /** The user shared with another app successfully. */
4629         public static final int SUCCESS = 0;
4630 
4631         /** The user attempted to share with another app, but it failed. */
4632         public static final int FAILURE = 1;
4633 
4634         /** The user aborted the share. */
4635         public static final int ABORT = 2;
4636 
Result()4637         protected Result() {}
4638       }
4639 
4640       /** The text for the share request. */
4641       public final @Nullable String text;
4642 
4643       /** The uri for the share request. */
4644       public final @Nullable String uri;
4645 
SharePrompt( @onNull final String id, @Nullable final String title, @Nullable final String text, @Nullable final String uri, @NonNull final Observer observer)4646       protected SharePrompt(
4647           @NonNull final String id,
4648           @Nullable final String title,
4649           @Nullable final String text,
4650           @Nullable final String uri,
4651           @NonNull final Observer observer) {
4652         super(id, title, observer);
4653         this.text = text;
4654         this.uri = uri;
4655       }
4656 
4657       /**
4658        * Confirms the prompt and either blocks or allows the share request.
4659        *
4660        * @param response One of {@link Result} specifying the outcome of the share attempt.
4661        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4662        *     associated with this prompt.
4663        */
4664       @UiThread
confirm(@hareResult final int response)4665       public @NonNull PromptResponse confirm(@ShareResult final int response) {
4666         ensureResult().putInt("response", response);
4667         return super.confirm();
4668       }
4669 
4670       /**
4671        * Dismisses the prompt and returns {@link Result#ABORT} to web content.
4672        *
4673        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4674        *     associated with this prompt.
4675        */
4676       @UiThread
dismiss()4677       public @NonNull PromptResponse dismiss() {
4678         ensureResult().putInt("response", Result.ABORT);
4679         return super.dismiss();
4680       }
4681     }
4682 
4683     /** Request containing information required to resolve Autocomplete prompt requests. */
4684     public class AutocompleteRequest<T extends Autocomplete.Option<?>> extends BasePrompt {
4685       /**
4686        * The Autocomplete options for this request. This can contain a single or multiple entries.
4687        */
4688       public final @NonNull T[] options;
4689 
AutocompleteRequest( final @NonNull String id, final @NonNull T[] options, final Observer observer)4690       protected AutocompleteRequest(
4691           final @NonNull String id, final @NonNull T[] options, final Observer observer) {
4692         super(id, null, observer);
4693         this.options = options;
4694       }
4695 
4696       /**
4697        * Confirm the request by responding with a selection. See the PromptDelegate callbacks for
4698        * specifics.
4699        *
4700        * @param selection The {@link Autocomplete.Option} used to confirm the request.
4701        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4702        *     associated with this prompt.
4703        */
4704       @UiThread
confirm(final @NonNull Autocomplete.Option<?> selection)4705       public @NonNull PromptResponse confirm(final @NonNull Autocomplete.Option<?> selection) {
4706         ensureResult().putBundle("selection", selection.toBundle());
4707         return super.confirm();
4708       }
4709 
4710       /**
4711        * Dismiss the request. See the PromptDelegate callbacks for specifics.
4712        *
4713        * @return A {@link PromptResponse} which can be used to complete the {@link GeckoResult}
4714        *     associated with this prompt.
4715        */
4716       @UiThread
dismiss()4717       public @NonNull PromptResponse dismiss() {
4718         return super.dismiss();
4719       }
4720     }
4721 
4722     // Delegate functions.
4723     /**
4724      * Display an alert prompt.
4725      *
4726      * @param session GeckoSession that triggered the prompt.
4727      * @param prompt The {@link AlertPrompt} that describes the prompt.
4728      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4729      *     necessary information to resolve the prompt.
4730      */
4731     @UiThread
onAlertPrompt( @onNull final GeckoSession session, @NonNull final AlertPrompt prompt)4732     default @Nullable GeckoResult<PromptResponse> onAlertPrompt(
4733         @NonNull final GeckoSession session, @NonNull final AlertPrompt prompt) {
4734       return null;
4735     }
4736 
4737     /**
4738      * Display a onbeforeunload prompt.
4739      *
4740      * <p>See https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
4741      * See {@link BeforeUnloadPrompt}
4742      *
4743      * @param session GeckoSession that triggered the prompt
4744      * @param prompt the {@link BeforeUnloadPrompt} that describes the prompt.
4745      * @return A {@link GeckoResult} resolving to {@link AllowOrDeny#ALLOW} if the page is allowed
4746      *     to continue with the navigation or {@link AllowOrDeny#DENY} otherwise.
4747      */
4748     @UiThread
onBeforeUnloadPrompt( @onNull final GeckoSession session, @NonNull final BeforeUnloadPrompt prompt)4749     default @Nullable GeckoResult<PromptResponse> onBeforeUnloadPrompt(
4750         @NonNull final GeckoSession session, @NonNull final BeforeUnloadPrompt prompt) {
4751       return null;
4752     }
4753 
4754     /**
4755      * Display a POST resubmission confirmation prompt.
4756      *
4757      * <p>This prompt will trigger whenever refreshing or navigating to a page needs resubmitting
4758      * POST data that has been submitted already.
4759      *
4760      * @param session GeckoSession that triggered the prompt
4761      * @param prompt the {@link RepostConfirmPrompt} that describes the prompt.
4762      * @return A {@link GeckoResult} resolving to {@link AllowOrDeny#ALLOW} if the page is allowed
4763      *     to continue with the navigation and resubmit the POST data or {@link AllowOrDeny#DENY}
4764      *     otherwise.
4765      */
4766     @UiThread
onRepostConfirmPrompt( @onNull final GeckoSession session, @NonNull final RepostConfirmPrompt prompt)4767     default @Nullable GeckoResult<PromptResponse> onRepostConfirmPrompt(
4768         @NonNull final GeckoSession session, @NonNull final RepostConfirmPrompt prompt) {
4769       return null;
4770     }
4771 
4772     /**
4773      * Display a button prompt.
4774      *
4775      * @param session GeckoSession that triggered the prompt.
4776      * @param prompt The {@link ButtonPrompt} that describes the prompt.
4777      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4778      *     necessary information to resolve the prompt.
4779      */
4780     @UiThread
onButtonPrompt( @onNull final GeckoSession session, @NonNull final ButtonPrompt prompt)4781     default @Nullable GeckoResult<PromptResponse> onButtonPrompt(
4782         @NonNull final GeckoSession session, @NonNull final ButtonPrompt prompt) {
4783       return null;
4784     }
4785 
4786     /**
4787      * Display a text prompt.
4788      *
4789      * @param session GeckoSession that triggered the prompt.
4790      * @param prompt The {@link TextPrompt} that describes the prompt.
4791      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4792      *     necessary information to resolve the prompt.
4793      */
4794     @UiThread
onTextPrompt( @onNull final GeckoSession session, @NonNull final TextPrompt prompt)4795     default @Nullable GeckoResult<PromptResponse> onTextPrompt(
4796         @NonNull final GeckoSession session, @NonNull final TextPrompt prompt) {
4797       return null;
4798     }
4799 
4800     /**
4801      * Display an authorization prompt.
4802      *
4803      * @param session GeckoSession that triggered the prompt.
4804      * @param prompt The {@link AuthPrompt} that describes the prompt.
4805      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4806      *     necessary information to resolve the prompt.
4807      */
4808     @UiThread
onAuthPrompt( @onNull final GeckoSession session, @NonNull final AuthPrompt prompt)4809     default @Nullable GeckoResult<PromptResponse> onAuthPrompt(
4810         @NonNull final GeckoSession session, @NonNull final AuthPrompt prompt) {
4811       return null;
4812     }
4813 
4814     /**
4815      * Display a list/menu prompt.
4816      *
4817      * @param session GeckoSession that triggered the prompt.
4818      * @param prompt The {@link ChoicePrompt} that describes the prompt.
4819      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4820      *     necessary information to resolve the prompt.
4821      */
4822     @UiThread
onChoicePrompt( @onNull final GeckoSession session, @NonNull final ChoicePrompt prompt)4823     default @Nullable GeckoResult<PromptResponse> onChoicePrompt(
4824         @NonNull final GeckoSession session, @NonNull final ChoicePrompt prompt) {
4825       return null;
4826     }
4827 
4828     /**
4829      * Display a color prompt.
4830      *
4831      * @param session GeckoSession that triggered the prompt.
4832      * @param prompt The {@link ColorPrompt} that describes the prompt.
4833      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4834      *     necessary information to resolve the prompt.
4835      */
4836     @UiThread
onColorPrompt( @onNull final GeckoSession session, @NonNull final ColorPrompt prompt)4837     default @Nullable GeckoResult<PromptResponse> onColorPrompt(
4838         @NonNull final GeckoSession session, @NonNull final ColorPrompt prompt) {
4839       return null;
4840     }
4841 
4842     /**
4843      * Display a date/time prompt.
4844      *
4845      * @param session GeckoSession that triggered the prompt.
4846      * @param prompt The {@link DateTimePrompt} that describes the prompt.
4847      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4848      *     necessary information to resolve the prompt.
4849      */
4850     @UiThread
onDateTimePrompt( @onNull final GeckoSession session, @NonNull final DateTimePrompt prompt)4851     default @Nullable GeckoResult<PromptResponse> onDateTimePrompt(
4852         @NonNull final GeckoSession session, @NonNull final DateTimePrompt prompt) {
4853       return null;
4854     }
4855 
4856     /**
4857      * Display a file prompt.
4858      *
4859      * @param session GeckoSession that triggered the prompt.
4860      * @param prompt The {@link FilePrompt} that describes the prompt.
4861      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4862      *     necessary information to resolve the prompt.
4863      */
4864     @UiThread
onFilePrompt( @onNull final GeckoSession session, @NonNull final FilePrompt prompt)4865     default @Nullable GeckoResult<PromptResponse> onFilePrompt(
4866         @NonNull final GeckoSession session, @NonNull final FilePrompt prompt) {
4867       return null;
4868     }
4869 
4870     /**
4871      * Display a popup request prompt; this occurs when content attempts to open a new window in a
4872      * way that doesn't appear to be the result of user input.
4873      *
4874      * @param session GeckoSession that triggered the prompt.
4875      * @param prompt The {@link PopupPrompt} that describes the prompt.
4876      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4877      *     necessary information to resolve the prompt.
4878      */
4879     @UiThread
onPopupPrompt( @onNull final GeckoSession session, @NonNull final PopupPrompt prompt)4880     default @Nullable GeckoResult<PromptResponse> onPopupPrompt(
4881         @NonNull final GeckoSession session, @NonNull final PopupPrompt prompt) {
4882       return null;
4883     }
4884 
4885     /**
4886      * Display a share request prompt; this occurs when content attempts to use the WebShare API.
4887      * See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share
4888      *
4889      * @param session GeckoSession that triggered the prompt.
4890      * @param prompt The {@link SharePrompt} that describes the prompt.
4891      * @return A {@link GeckoResult} resolving to a {@link PromptResponse} which includes all
4892      *     necessary information to resolve the prompt.
4893      */
4894     @UiThread
onSharePrompt( @onNull final GeckoSession session, @NonNull final SharePrompt prompt)4895     default @Nullable GeckoResult<PromptResponse> onSharePrompt(
4896         @NonNull final GeckoSession session, @NonNull final SharePrompt prompt) {
4897       return null;
4898     }
4899 
4900     /**
4901      * Handle a login save prompt request. This is triggered by the user entering new or modified
4902      * login credentials into a login form.
4903      *
4904      * @param session The {@link GeckoSession} that triggered the request.
4905      * @param request The {@link AutocompleteRequest} containing the request details.
4906      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}.
4907      *     <p>Confirm the request with an {@link Autocomplete.Option} to trigger a {@link
4908      *     Autocomplete.StorageDelegate#onLoginSave} request to save the given selection. The
4909      *     confirmed selection may be an entry out of the request's options, a modified option, or a
4910      *     freshly created login entry.
4911      *     <p>Dismiss the request to deny the saving request.
4912      */
4913     @UiThread
onLoginSave( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.LoginSaveOption> request)4914     default @Nullable GeckoResult<PromptResponse> onLoginSave(
4915         @NonNull final GeckoSession session,
4916         @NonNull final AutocompleteRequest<Autocomplete.LoginSaveOption> request) {
4917       return null;
4918     }
4919 
4920     /**
4921      * Handle a address save prompt request. This is triggered by the user entering new or modified
4922      * address credentials into a address form.
4923      *
4924      * @param session The {@link GeckoSession} that triggered the request.
4925      * @param request The {@link AutocompleteRequest} containing the request details.
4926      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}.
4927      *     <p>Confirm the request with an {@link Autocomplete.Option} to trigger a {@link
4928      *     Autocomplete.StorageDelegate#onAddressSave} request to save the given selection. The
4929      *     confirmed selection may be an entry out of the request's options, a modified option, or a
4930      *     freshly created address entry.
4931      *     <p>Dismiss the request to deny the saving request.
4932      */
4933     @UiThread
onAddressSave( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.AddressSaveOption> request)4934     default @Nullable GeckoResult<PromptResponse> onAddressSave(
4935         @NonNull final GeckoSession session,
4936         @NonNull final AutocompleteRequest<Autocomplete.AddressSaveOption> request) {
4937       return null;
4938     }
4939 
4940     /**
4941      * Handle a credit card save prompt request. This is triggered by the user entering new or
4942      * modified credit card credentials into a form.
4943      *
4944      * @param session The {@link GeckoSession} that triggered the request.
4945      * @param request The {@link AutocompleteRequest} containing the request details.
4946      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}.
4947      *     <p>Confirm the request with an {@link Autocomplete.Option} to trigger a {@link
4948      *     Autocomplete.StorageDelegate#onCreditCardSave} request to save the given selection. The
4949      *     confirmed selection may be an entry out of the request's options, a modified option, or a
4950      *     freshly created credit card entry.
4951      *     <p>Dismiss the request to deny the saving request.
4952      */
4953     @UiThread
onCreditCardSave( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.CreditCardSaveOption> request)4954     default @Nullable GeckoResult<PromptResponse> onCreditCardSave(
4955         @NonNull final GeckoSession session,
4956         @NonNull final AutocompleteRequest<Autocomplete.CreditCardSaveOption> request) {
4957       return null;
4958     }
4959 
4960     /**
4961      * Handle a login selection prompt request. This is triggered by the user focusing on a login
4962      * username field.
4963      *
4964      * @param session The {@link GeckoSession} that triggered the request.
4965      * @param request The {@link AutocompleteRequest} containing the request details.
4966      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}
4967      *     <p>Confirm the request with an {@link Autocomplete.Option} to let GeckoView fill out the
4968      *     login forms with the given selection details. The confirmed selection may be an entry out
4969      *     of the request's options, a modified option, or a freshly created login entry.
4970      *     <p>Dismiss the request to deny autocompletion for the detected form.
4971      */
4972     @UiThread
onLoginSelect( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.LoginSelectOption> request)4973     default @Nullable GeckoResult<PromptResponse> onLoginSelect(
4974         @NonNull final GeckoSession session,
4975         @NonNull final AutocompleteRequest<Autocomplete.LoginSelectOption> request) {
4976       return null;
4977     }
4978 
4979     /**
4980      * Handle a credit card selection prompt request. This is triggered by the user focusing on a
4981      * credit card input field.
4982      *
4983      * @param session The {@link GeckoSession} that triggered the request.
4984      * @param request The {@link AutocompleteRequest} containing the request details.
4985      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}
4986      *     <p>Confirm the request with an {@link Autocomplete.Option} to let GeckoView fill out the
4987      *     credit card forms with the given selection details. The confirmed selection may be an
4988      *     entry out of the request's options, a modified option, or a freshly created credit card
4989      *     entry.
4990      *     <p>Dismiss the request to deny autocompletion for the detected form.
4991      */
4992     @UiThread
onCreditCardSelect( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.CreditCardSelectOption> request)4993     default @Nullable GeckoResult<PromptResponse> onCreditCardSelect(
4994         @NonNull final GeckoSession session,
4995         @NonNull final AutocompleteRequest<Autocomplete.CreditCardSelectOption> request) {
4996       return null;
4997     }
4998 
4999     /**
5000      * Handle a address selection prompt request. This is triggered by the user focusing on a
5001      * address field.
5002      *
5003      * @param session The {@link GeckoSession} that triggered the request.
5004      * @param request The {@link AutocompleteRequest} containing the request details.
5005      * @return A {@link GeckoResult} resolving to a {@link PromptResponse}
5006      *     <p>Confirm the request with an {@link Autocomplete.Option} to let GeckoView fill out the
5007      *     address forms with the given selection details. The confirmed selection may be an entry
5008      *     out of the request's options, a modified option, or a freshly created address entry.
5009      *     <p>Dismiss the request to deny autocompletion for the detected form.
5010      */
5011     @UiThread
onAddressSelect( @onNull final GeckoSession session, @NonNull final AutocompleteRequest<Autocomplete.AddressSelectOption> request)5012     default @Nullable GeckoResult<PromptResponse> onAddressSelect(
5013         @NonNull final GeckoSession session,
5014         @NonNull final AutocompleteRequest<Autocomplete.AddressSelectOption> request) {
5015       return null;
5016     }
5017   }
5018 
5019   /** GeckoSession applications implement this interface to handle content scroll events. */
5020   public interface ScrollDelegate {
5021     /**
5022      * The scroll position of the content has changed.
5023      *
5024      * @param session GeckoSession that initiated the callback.
5025      * @param scrollX The new horizontal scroll position in pixels.
5026      * @param scrollY The new vertical scroll position in pixels.
5027      */
5028     @UiThread
onScrollChanged( @onNull final GeckoSession session, final int scrollX, final int scrollY)5029     default void onScrollChanged(
5030         @NonNull final GeckoSession session, final int scrollX, final int scrollY) {}
5031   }
5032 
5033   /**
5034    * Get the PanZoomController instance for this session.
5035    *
5036    * @return PanZoomController instance.
5037    */
5038   @UiThread
getPanZoomController()5039   public @NonNull PanZoomController getPanZoomController() {
5040     ThreadUtils.assertOnUiThread();
5041 
5042     return mPanZoomController;
5043   }
5044 
5045   /**
5046    * Get the OverscrollEdgeEffect instance for this session.
5047    *
5048    * @return OverscrollEdgeEffect instance.
5049    */
5050   @UiThread
getOverscrollEdgeEffect()5051   public @NonNull OverscrollEdgeEffect getOverscrollEdgeEffect() {
5052     ThreadUtils.assertOnUiThread();
5053 
5054     if (mOverscroll == null) {
5055       mOverscroll = new OverscrollEdgeEffect(this);
5056     }
5057     return mOverscroll;
5058   }
5059 
5060   /**
5061    * Get the CompositorController instance for this session.
5062    *
5063    * @return CompositorController instance.
5064    */
5065   @UiThread
getCompositorController()5066   public @NonNull CompositorController getCompositorController() {
5067     ThreadUtils.assertOnUiThread();
5068 
5069     if (mController == null) {
5070       mController = new CompositorController(this);
5071       if (mCompositorReady) {
5072         mController.onCompositorReady();
5073       }
5074     }
5075     return mController;
5076   }
5077 
5078   /**
5079    * Get a matrix for transforming from client coordinates to surface coordinates.
5080    *
5081    * @param matrix Matrix to be replaced by the transformation matrix.
5082    * @see #getClientToScreenMatrix(Matrix)
5083    * @see #getPageToSurfaceMatrix(Matrix)
5084    */
5085   @UiThread
getClientToSurfaceMatrix(@onNull final Matrix matrix)5086   public void getClientToSurfaceMatrix(@NonNull final Matrix matrix) {
5087     ThreadUtils.assertOnUiThread();
5088 
5089     matrix.setScale(mViewportZoom, mViewportZoom);
5090     if (mClientTop != mTop) {
5091       matrix.postTranslate(0, mClientTop - mTop);
5092     }
5093   }
5094 
5095   /**
5096    * Get a matrix for transforming from client coordinates to screen coordinates. The client
5097    * coordinates are in CSS pixels and are relative to the viewport origin; their relation to screen
5098    * coordinates does not depend on the current scroll position.
5099    *
5100    * @param matrix Matrix to be replaced by the transformation matrix.
5101    * @see #getClientToSurfaceMatrix(Matrix)
5102    * @see #getPageToScreenMatrix(Matrix)
5103    */
5104   @UiThread
getClientToScreenMatrix(@onNull final Matrix matrix)5105   public void getClientToScreenMatrix(@NonNull final Matrix matrix) {
5106     ThreadUtils.assertOnUiThread();
5107 
5108     getClientToSurfaceMatrix(matrix);
5109     matrix.postTranslate(mLeft, mTop);
5110   }
5111 
5112   /**
5113    * Get a matrix for transforming from page coordinates to screen coordinates. The page coordinates
5114    * are in CSS pixels and are relative to the page origin; their relation to screen coordinates
5115    * depends on the current scroll position of the outermost frame.
5116    *
5117    * @param matrix Matrix to be replaced by the transformation matrix.
5118    * @see #getPageToSurfaceMatrix(Matrix)
5119    * @see #getClientToScreenMatrix(Matrix)
5120    */
5121   @UiThread
getPageToScreenMatrix(@onNull final Matrix matrix)5122   public void getPageToScreenMatrix(@NonNull final Matrix matrix) {
5123     ThreadUtils.assertOnUiThread();
5124 
5125     getPageToSurfaceMatrix(matrix);
5126     matrix.postTranslate(mLeft, mTop);
5127   }
5128 
5129   /**
5130    * Get a matrix for transforming from page coordinates to surface coordinates.
5131    *
5132    * @param matrix Matrix to be replaced by the transformation matrix.
5133    * @see #getPageToScreenMatrix(Matrix)
5134    * @see #getClientToSurfaceMatrix(Matrix)
5135    */
5136   @UiThread
getPageToSurfaceMatrix(@onNull final Matrix matrix)5137   public void getPageToSurfaceMatrix(@NonNull final Matrix matrix) {
5138     ThreadUtils.assertOnUiThread();
5139 
5140     getClientToSurfaceMatrix(matrix);
5141     matrix.postTranslate(-mViewportLeft, -mViewportTop);
5142   }
5143 
5144   /**
5145    * Get a matrix for transforming from layout device client coordinates to screen coordinates.
5146    *
5147    * @param matrix Matrix to be replaced by the transformation matrix.
5148    * @see #getClientToScreenMatrix(Matrix)
5149    * @see #getPageToSurfaceMatrix(Matrix)
5150    */
5151   @UiThread
getClientToScreenOffsetMatrix(@onNull final Matrix matrix)5152   /* package */ void getClientToScreenOffsetMatrix(@NonNull final Matrix matrix) {
5153     ThreadUtils.assertOnUiThread();
5154 
5155     matrix.postTranslate(mLeft, mTop);
5156   }
5157 
5158   /**
5159    * Get the bounds of the client area in client coordinates. The returned top-left coordinates are
5160    * always (0, 0). Use the matrix from {@link #getClientToSurfaceMatrix(Matrix)} or {@link
5161    * #getClientToScreenMatrix(Matrix)} to map these bounds to surface or screen coordinates,
5162    * respectively.
5163    *
5164    * @param rect RectF to be replaced by the client bounds in client coordinates.
5165    * @see #getSurfaceBounds(Rect)
5166    */
5167   @UiThread
getClientBounds(@onNull final RectF rect)5168   public void getClientBounds(@NonNull final RectF rect) {
5169     ThreadUtils.assertOnUiThread();
5170 
5171     rect.set(0.0f, 0.0f, (float) mWidth / mViewportZoom, (float) mClientHeight / mViewportZoom);
5172   }
5173 
5174   /**
5175    * Get the bounds of the client area in surface coordinates. This is equivalent to mapping the
5176    * bounds returned by #getClientBounds(RectF) with the matrix returned by
5177    * #getClientToSurfaceMatrix(Matrix).
5178    *
5179    * @param rect Rect to be replaced by the client bounds in surface coordinates.
5180    */
5181   @UiThread
getSurfaceBounds(@onNull final Rect rect)5182   public void getSurfaceBounds(@NonNull final Rect rect) {
5183     ThreadUtils.assertOnUiThread();
5184 
5185     rect.set(0, mClientTop - mTop, mWidth, mHeight);
5186   }
5187 
5188   /**
5189    * GeckoSession applications implement this interface to handle requests for permissions from
5190    * content, such as geolocation and notifications. For each permission, usually two requests are
5191    * generated: one request for the Android app permission through requestAppPermissions, which is
5192    * typically handled by a system permission dialog; and another request for the content permission
5193    * (e.g. through requestContentPermission), which is typically handled by an app-specific
5194    * permission dialog.
5195    *
5196    * <p>When denying an Android app permission, the response is not stored by GeckoView. It is the
5197    * responsibility of the consumer to store the response state and therefore prevent further
5198    * requests from being presented to the user.
5199    */
5200   public interface PermissionDelegate {
5201     /**
5202      * Permission for using the geolocation API. See:
5203      * https://developer.mozilla.org/en-US/docs/Web/API/Geolocation
5204      */
5205     int PERMISSION_GEOLOCATION = 0;
5206 
5207     /**
5208      * Permission for using the notifications API. See:
5209      * https://developer.mozilla.org/en-US/docs/Web/API/notification
5210      */
5211     int PERMISSION_DESKTOP_NOTIFICATION = 1;
5212 
5213     /**
5214      * Permission for using the storage API. See:
5215      * https://developer.mozilla.org/en-US/docs/Web/API/Storage_API
5216      */
5217     int PERMISSION_PERSISTENT_STORAGE = 2;
5218 
5219     /** Permission for using the WebXR API. See: https://www.w3.org/TR/webxr */
5220     int PERMISSION_XR = 3;
5221 
5222     /** Permission for allowing autoplay of inaudible (silent) video. */
5223     int PERMISSION_AUTOPLAY_INAUDIBLE = 4;
5224 
5225     /** Permission for allowing autoplay of audible video. */
5226     int PERMISSION_AUTOPLAY_AUDIBLE = 5;
5227 
5228     /** Permission for accessing system media keys used to decode DRM media. */
5229     int PERMISSION_MEDIA_KEY_SYSTEM_ACCESS = 6;
5230 
5231     /**
5232      * Permission for trackers to operate on the page -- disables all tracking protection features
5233      * for a given site.
5234      */
5235     int PERMISSION_TRACKING = 7;
5236 
5237     /**
5238      * Permission for third party frames to access first party cookies and storage. May be granted
5239      * heuristically in some cases.
5240      */
5241     int PERMISSION_STORAGE_ACCESS = 8;
5242 
5243     /**
5244      * Represents a content permission -- including the type of permission, the present value of the
5245      * permission, the URL the permission pertains to, and other information.
5246      */
5247     class ContentPermission {
5248       @Retention(RetentionPolicy.SOURCE)
5249       @IntDef({VALUE_PROMPT, VALUE_DENY, VALUE_ALLOW})
5250       public @interface Value {}
5251 
5252       /** The corresponding permission is currently set to default/prompt behavior. */
5253       public static final int VALUE_PROMPT = 3;
5254 
5255       /** The corresponding permission is currently set to deny. */
5256       public static final int VALUE_DENY = 2;
5257 
5258       /** The corresponding permission is currently set to allow. */
5259       public static final int VALUE_ALLOW = 1;
5260 
5261       /** The URI associated with this content permission. */
5262       public final @NonNull String uri;
5263 
5264       /**
5265        * The third party origin associated with the request; currently only used for storage access
5266        * permission.
5267        */
5268       public final @Nullable String thirdPartyOrigin;
5269 
5270       /**
5271        * A boolean indicating whether this content permission is associated with private browsing.
5272        */
5273       public final boolean privateMode;
5274 
5275       /** The type of this permission; one of {@link #PERMISSION_GEOLOCATION PERMISSION_*}. */
5276       public final int permission;
5277 
5278       /** The value of the permission; one of {@link #VALUE_PROMPT VALUE_}. */
5279       public final @Value int value;
5280 
5281       /**
5282        * The context ID associated with the permission if any.
5283        *
5284        * @see GeckoSessionSettings.Builder#contextId
5285        */
5286       public final @Nullable String contextId;
5287 
5288       private final String mPrincipal;
5289 
ContentPermission()5290       protected ContentPermission() {
5291         this.uri = "";
5292         this.thirdPartyOrigin = null;
5293         this.privateMode = false;
5294         this.permission = PERMISSION_GEOLOCATION;
5295         this.value = VALUE_ALLOW;
5296         this.mPrincipal = "";
5297         this.contextId = null;
5298       }
5299 
ContentPermission(final @NonNull GeckoBundle bundle)5300       private ContentPermission(final @NonNull GeckoBundle bundle) {
5301         this.uri = bundle.getString("uri");
5302         this.mPrincipal = bundle.getString("principal");
5303         this.privateMode = bundle.getBoolean("privateMode");
5304 
5305         final String permission = bundle.getString("perm");
5306         this.permission = convertType(permission);
5307         if (permission.startsWith("3rdPartyStorage^")) {
5308           // Storage access permissions are stored with the key "3rdPartyStorage^https://foo.com"
5309           // where the third party origin is "https://foo.com".
5310           this.thirdPartyOrigin = permission.substring(16);
5311         } else {
5312           this.thirdPartyOrigin = bundle.getString("thirdPartyOrigin");
5313         }
5314 
5315         this.value = bundle.getInt("value");
5316         this.contextId =
5317             StorageController.retrieveUnsafeSessionContextId(bundle.getString("contextId"));
5318       }
5319 
5320       /**
5321        * Converts a JSONObject to a ContentPermission -- should only be used on the output of {@link
5322        * #toJson()}.
5323        *
5324        * @param perm A JSONObject representing a ContentPermission, output by {@link #toJson()}.
5325        * @return The corresponding ContentPermission.
5326        */
5327       @AnyThread
fromJson(final @NonNull JSONObject perm)5328       public static @Nullable ContentPermission fromJson(final @NonNull JSONObject perm) {
5329         ContentPermission res = null;
5330         try {
5331           res = new ContentPermission(GeckoBundle.fromJSONObject(perm));
5332         } catch (final JSONException e) {
5333           Log.w(LOGTAG, "Failed to create ContentPermission; invalid JSONObject.", e);
5334         }
5335         return res;
5336       }
5337 
5338       /**
5339        * Converts a ContentPermission to a JSONObject that can be converted back to a
5340        * ContentPermission by {@link #fromJson(JSONObject)}.
5341        *
5342        * @return A JSONObject representing this ContentPermission. Modifying any of the fields may
5343        *     result in undefined behavior when converted back to a ContentPermission and used.
5344        * @throws JSONException if the conversion fails for any reason.
5345        */
5346       @AnyThread
toJson()5347       public @NonNull JSONObject toJson() throws JSONException {
5348         return toGeckoBundle().toJSONObject();
5349       }
5350 
convertType(final @NonNull String type)5351       private static int convertType(final @NonNull String type) {
5352         if ("geolocation".equals(type)) {
5353           return PERMISSION_GEOLOCATION;
5354         } else if ("desktop-notification".equals(type)) {
5355           return PERMISSION_DESKTOP_NOTIFICATION;
5356         } else if ("persistent-storage".equals(type)) {
5357           return PERMISSION_PERSISTENT_STORAGE;
5358         } else if ("xr".equals(type)) {
5359           return PERMISSION_XR;
5360         } else if ("autoplay-media-inaudible".equals(type)) {
5361           return PERMISSION_AUTOPLAY_INAUDIBLE;
5362         } else if ("autoplay-media-audible".equals(type)) {
5363           return PERMISSION_AUTOPLAY_AUDIBLE;
5364         } else if ("media-key-system-access".equals(type)) {
5365           return PERMISSION_MEDIA_KEY_SYSTEM_ACCESS;
5366         } else if ("trackingprotection".equals(type) || "trackingprotection-pb".equals(type)) {
5367           return PERMISSION_TRACKING;
5368         } else if ("storage-access".equals(type) || type.startsWith("3rdPartyStorage^")) {
5369           return PERMISSION_STORAGE_ACCESS;
5370         } else {
5371           return -1;
5372         }
5373       }
5374 
5375       // This also gets used in StorageController, so it's package rather than private.
convertType(final int type, final boolean privateMode)5376       /* package */ static String convertType(final int type, final boolean privateMode) {
5377         switch (type) {
5378           case PERMISSION_GEOLOCATION:
5379             return "geolocation";
5380           case PERMISSION_DESKTOP_NOTIFICATION:
5381             return "desktop-notification";
5382           case PERMISSION_PERSISTENT_STORAGE:
5383             return "persistent-storage";
5384           case PERMISSION_XR:
5385             return "xr";
5386           case PERMISSION_AUTOPLAY_INAUDIBLE:
5387             return "autoplay-media-inaudible";
5388           case PERMISSION_AUTOPLAY_AUDIBLE:
5389             return "autoplay-media-audible";
5390           case PERMISSION_MEDIA_KEY_SYSTEM_ACCESS:
5391             return "media-key-system-access";
5392           case PERMISSION_TRACKING:
5393             return privateMode ? "trackingprotection-pb" : "trackingprotection";
5394           case PERMISSION_STORAGE_ACCESS:
5395             return "storage-access";
5396           default:
5397             return "";
5398         }
5399       }
5400 
fromBundleArray( final @NonNull GeckoBundle[] bundleArray)5401       /* package */ static @NonNull ArrayList<ContentPermission> fromBundleArray(
5402           final @NonNull GeckoBundle[] bundleArray) {
5403         final ArrayList<ContentPermission> res = new ArrayList<ContentPermission>();
5404         if (bundleArray == null) {
5405           return res;
5406         }
5407 
5408         for (final GeckoBundle bundle : bundleArray) {
5409           final ContentPermission temp = new ContentPermission(bundle);
5410           if (temp.permission == -1 || temp.value < 1 || temp.value > 3) {
5411             continue;
5412           }
5413           res.add(temp);
5414         }
5415         return res;
5416       }
5417 
5418       /* package */ @NonNull
toGeckoBundle()5419       GeckoBundle toGeckoBundle() {
5420         final GeckoBundle res = new GeckoBundle(7);
5421         res.putString("uri", uri);
5422         res.putString("thirdPartyOrigin", thirdPartyOrigin);
5423         res.putString("principal", mPrincipal);
5424         res.putBoolean("privateMode", privateMode);
5425         res.putString("perm", convertType(permission, privateMode));
5426         res.putInt("value", value);
5427         res.putString("contextId", contextId);
5428         return res;
5429       }
5430     }
5431 
5432     /** Callback interface for notifying the result of a permission request. */
5433     interface Callback {
5434       /**
5435        * Called by the implementation after permissions are granted; the implementation must call
5436        * either grant() or reject() for every request.
5437        */
5438       @UiThread
grant()5439       default void grant() {}
5440 
5441       /**
5442        * Called by the implementation when permissions are not granted; the implementation must call
5443        * either grant() or reject() for every request.
5444        */
5445       @UiThread
reject()5446       default void reject() {}
5447     }
5448 
5449     /**
5450      * Request Android app permissions.
5451      *
5452      * @param session GeckoSession instance requesting the permissions.
5453      * @param permissions List of permissions to request; possible values are,
5454      *     android.Manifest.permission.ACCESS_COARSE_LOCATION
5455      *     android.Manifest.permission.ACCESS_FINE_LOCATION android.Manifest.permission.CAMERA
5456      *     android.Manifest.permission.RECORD_AUDIO
5457      * @param callback Callback interface.
5458      */
5459     @UiThread
onAndroidPermissionsRequest( @onNull final GeckoSession session, @Nullable final String[] permissions, @NonNull final Callback callback)5460     default void onAndroidPermissionsRequest(
5461         @NonNull final GeckoSession session,
5462         @Nullable final String[] permissions,
5463         @NonNull final Callback callback) {
5464       callback.reject();
5465     }
5466 
5467     /**
5468      * Request content permission.
5469      *
5470      * <p>Note, that in the case of PERMISSION_PERSISTENT_STORAGE, once permission has been granted
5471      * for a site, it cannot be revoked. If the permission has previously been granted, it is the
5472      * responsibility of the consuming app to remember the permission and prevent the prompt from
5473      * being redisplayed to the user.
5474      *
5475      * @param session GeckoSession instance requesting the permission.
5476      * @param perm An {@link ContentPermission} describing the permission being requested and its
5477      *     current status.
5478      * @return A {@link GeckoResult} resolving to one of {@link ContentPermission#VALUE_PROMPT
5479      *     VALUE_*}, determining the response to the permission request and updating the permissions
5480      *     for this site.
5481      */
5482     @UiThread
onContentPermissionRequest( @onNull final GeckoSession session, @NonNull ContentPermission perm)5483     default @Nullable GeckoResult<Integer> onContentPermissionRequest(
5484         @NonNull final GeckoSession session, @NonNull ContentPermission perm) {
5485       return GeckoResult.fromValue(ContentPermission.VALUE_PROMPT);
5486     }
5487 
5488     class MediaSource {
5489       @Retention(RetentionPolicy.SOURCE)
5490       @IntDef({
5491         SOURCE_CAMERA, SOURCE_SCREEN,
5492         SOURCE_MICROPHONE, SOURCE_AUDIOCAPTURE,
5493         SOURCE_OTHER
5494       })
5495       public @interface Source {}
5496 
5497       /** Constant to indicate that camera will be recorded. */
5498       public static final int SOURCE_CAMERA = 0;
5499 
5500       /** Constant to indicate that screen will be recorded. */
5501       public static final int SOURCE_SCREEN = 1;
5502 
5503       /** Constant to indicate that microphone will be recorded. */
5504       public static final int SOURCE_MICROPHONE = 2;
5505 
5506       /** Constant to indicate that device audio playback will be recorded. */
5507       public static final int SOURCE_AUDIOCAPTURE = 3;
5508 
5509       /** Constant to indicate a media source that does not fall under the other categories. */
5510       public static final int SOURCE_OTHER = 4;
5511 
5512       @Retention(RetentionPolicy.SOURCE)
5513       @IntDef({TYPE_VIDEO, TYPE_AUDIO})
5514       public @interface Type {}
5515 
5516       /** The media type is video. */
5517       public static final int TYPE_VIDEO = 0;
5518 
5519       /** The media type is audio. */
5520       public static final int TYPE_AUDIO = 1;
5521 
5522       /** A string giving a unique source identifier. */
5523       public final @NonNull String id;
5524 
5525       /**
5526        * A string giving a unique source identifier.
5527        *
5528        * @deprecated Instead use {@link #id}, which is the same string.
5529        */
5530       @Deprecated
5531       @DeprecationSchedule(id = "media-source-rawId", version = 100)
5532       public final @NonNull String rawId;
5533 
5534       /**
5535        * A string giving the name of the video source from the system (for example, "Camera 0,
5536        * Facing back, Orientation 90"). May be empty.
5537        */
5538       public final @Nullable String name;
5539 
5540       /**
5541        * An int indicating the media source type. Possible values for a video source are:
5542        * SOURCE_CAMERA, SOURCE_SCREEN, and SOURCE_OTHER. Possible values for an audio source are:
5543        * SOURCE_MICROPHONE, SOURCE_AUDIOCAPTURE, and SOURCE_OTHER.
5544        */
5545       public final @Source int source;
5546 
5547       /** An int giving the type of media, must be either TYPE_VIDEO or TYPE_AUDIO. */
5548       public final @Type int type;
5549 
getSourceFromString(final String src)5550       private static @Source int getSourceFromString(final String src) {
5551         // The strings here should match those in MediaSourceEnum in MediaStreamTrack.webidl
5552         if ("camera".equals(src)) {
5553           return SOURCE_CAMERA;
5554         } else if ("screen".equals(src) || "window".equals(src) || "browser".equals(src)) {
5555           return SOURCE_SCREEN;
5556         } else if ("microphone".equals(src)) {
5557           return SOURCE_MICROPHONE;
5558         } else if ("audioCapture".equals(src)) {
5559           return SOURCE_AUDIOCAPTURE;
5560         } else if ("other".equals(src) || "application".equals(src)) {
5561           return SOURCE_OTHER;
5562         } else {
5563           throw new IllegalArgumentException(
5564               "String: " + src + " is not a valid media source string");
5565         }
5566       }
5567 
getTypeFromString(final String type)5568       private static @Type int getTypeFromString(final String type) {
5569         // The strings here should match the possible types in MediaDevice::MediaDevice in
5570         // MediaManager.cpp
5571         if ("videoinput".equals(type)) {
5572           return TYPE_VIDEO;
5573         } else if ("audioinput".equals(type)) {
5574           return TYPE_AUDIO;
5575         } else {
5576           throw new IllegalArgumentException(
5577               "String: " + type + " is not a valid media type string");
5578         }
5579       }
5580 
MediaSource(final GeckoBundle media)5581       /* package */ MediaSource(final GeckoBundle media) {
5582         id = media.getString("id");
5583         rawId = id;
5584         name = media.getString("name");
5585         source = getSourceFromString(media.getString("mediaSource"));
5586         type = getTypeFromString(media.getString("type"));
5587       }
5588 
5589       /** Empty constructor for tests. */
MediaSource()5590       protected MediaSource() {
5591         id = null;
5592         rawId = null;
5593         name = null;
5594         source = SOURCE_CAMERA;
5595         type = TYPE_VIDEO;
5596       }
5597     }
5598 
5599     /**
5600      * Callback interface for notifying the result of a media permission request, including which
5601      * media source(s) to use.
5602      */
5603     interface MediaCallback {
5604       /**
5605        * Called by the implementation after permissions are granted; the implementation must call
5606        * one of grant() or reject() for every request.
5607        *
5608        * @param video "id" value from the bundle for the video source to use, or null when video is
5609        *     not requested.
5610        * @param audio "id" value from the bundle for the audio source to use, or null when audio is
5611        *     not requested.
5612        */
5613       @UiThread
grant(final @Nullable String video, final @Nullable String audio)5614       default void grant(final @Nullable String video, final @Nullable String audio) {}
5615 
5616       /**
5617        * Called by the implementation after permissions are granted; the implementation must call
5618        * one of grant() or reject() for every request.
5619        *
5620        * @param video MediaSource for the video source to use (must be an original MediaSource
5621        *     object that was passed to the implementation); or null when video is not requested.
5622        * @param audio MediaSource for the audio source to use (must be an original MediaSource
5623        *     object that was passed to the implementation); or null when audio is not requested.
5624        */
5625       @UiThread
grant(final @Nullable MediaSource video, final @Nullable MediaSource audio)5626       default void grant(final @Nullable MediaSource video, final @Nullable MediaSource audio) {}
5627 
5628       /**
5629        * Called by the implementation when permissions are not granted; the implementation must call
5630        * one of grant() or reject() for every request.
5631        */
5632       @UiThread
reject()5633       default void reject() {}
5634     }
5635 
5636     /**
5637      * Request content media permissions, including request for which video and/or audio source to
5638      * use.
5639      *
5640      * <p>Media permissions will still be requested if the associated device permissions have been
5641      * denied if there are video or audio sources in that category that can still be accessed. It is
5642      * the responsibility of consumers to ensure that media permission requests are not displayed in
5643      * this case.
5644      *
5645      * @param session GeckoSession instance requesting the permission.
5646      * @param uri The URI of the content requesting the permission.
5647      * @param video List of video sources, or null if not requesting video.
5648      * @param audio List of audio sources, or null if not requesting audio.
5649      * @param callback Callback interface.
5650      */
5651     @UiThread
onMediaPermissionRequest( @onNull final GeckoSession session, @NonNull final String uri, @Nullable final MediaSource[] video, @Nullable final MediaSource[] audio, @NonNull final MediaCallback callback)5652     default void onMediaPermissionRequest(
5653         @NonNull final GeckoSession session,
5654         @NonNull final String uri,
5655         @Nullable final MediaSource[] video,
5656         @Nullable final MediaSource[] audio,
5657         @NonNull final MediaCallback callback) {
5658       callback.reject();
5659     }
5660   }
5661 
5662   @Retention(RetentionPolicy.SOURCE)
5663   @IntDef({
5664     PermissionDelegate.PERMISSION_GEOLOCATION,
5665     PermissionDelegate.PERMISSION_DESKTOP_NOTIFICATION,
5666     PermissionDelegate.PERMISSION_PERSISTENT_STORAGE,
5667     PermissionDelegate.PERMISSION_XR,
5668     PermissionDelegate.PERMISSION_AUTOPLAY_INAUDIBLE,
5669     PermissionDelegate.PERMISSION_AUTOPLAY_AUDIBLE,
5670     PermissionDelegate.PERMISSION_MEDIA_KEY_SYSTEM_ACCESS,
5671     PermissionDelegate.PERMISSION_TRACKING,
5672     PermissionDelegate.PERMISSION_STORAGE_ACCESS
5673   })
5674   public @interface Permission {}
5675 
5676   /**
5677    * Interface that SessionTextInput uses for performing operations such as opening and closing the
5678    * software keyboard. If the delegate is not set, these operations are forwarded to the system
5679    * {@link android.view.inputmethod.InputMethodManager} automatically.
5680    */
5681   public interface TextInputDelegate {
5682     /** Restarting input due to an input field gaining focus. */
5683     int RESTART_REASON_FOCUS = 0;
5684     /** Restarting input due to an input field losing focus. */
5685     int RESTART_REASON_BLUR = 1;
5686     /**
5687      * Restarting input due to the content of the input field changing. For example, the input field
5688      * type may have changed, or the current composition may have been committed outside of the
5689      * input method.
5690      */
5691     int RESTART_REASON_CONTENT_CHANGE = 2;
5692 
5693     /**
5694      * Reset the input method, and discard any existing states such as the current composition or
5695      * current autocompletion. Because the current focused editor may have changed, as part of the
5696      * reset, a custom input method would normally call {@link
5697      * SessionTextInput#onCreateInputConnection} to update its knowledge of the focused editor. Note
5698      * that {@code restartInput} should be used to detect changes in focus, rather than {@link
5699      * #showSoftInput} or {@link #hideSoftInput}, because focus changes are not always accompanied
5700      * by requests to show or hide the soft input. This method is always called, even in viewless
5701      * mode.
5702      *
5703      * @param session Session instance.
5704      * @param reason Reason for the reset.
5705      */
5706     @UiThread
restartInput( @onNull final GeckoSession session, @RestartReason final int reason)5707     default void restartInput(
5708         @NonNull final GeckoSession session, @RestartReason final int reason) {}
5709 
5710     /**
5711      * Display the soft input. May be called consecutively, even if the soft input is already shown.
5712      * This method is always called, even in viewless mode.
5713      *
5714      * @param session Session instance.
5715      * @see #hideSoftInput
5716      */
5717     @UiThread
showSoftInput(@onNull final GeckoSession session)5718     default void showSoftInput(@NonNull final GeckoSession session) {}
5719 
5720     /**
5721      * Hide the soft input. May be called consecutively, even if the soft input is already hidden.
5722      * This method is always called, even in viewless mode.
5723      *
5724      * @param session Session instance.
5725      * @see #showSoftInput
5726      */
5727     @UiThread
hideSoftInput(@onNull final GeckoSession session)5728     default void hideSoftInput(@NonNull final GeckoSession session) {}
5729 
5730     /**
5731      * Update the soft input on the current selection. This method is <i>not</i> called in viewless
5732      * mode.
5733      *
5734      * @param session Session instance.
5735      * @param selStart Start offset of the selection.
5736      * @param selEnd End offset of the selection.
5737      * @param compositionStart Composition start offset, or -1 if there is no composition.
5738      * @param compositionEnd Composition end offset, or -1 if there is no composition.
5739      */
5740     @UiThread
updateSelection( @onNull final GeckoSession session, final int selStart, final int selEnd, final int compositionStart, final int compositionEnd)5741     default void updateSelection(
5742         @NonNull final GeckoSession session,
5743         final int selStart,
5744         final int selEnd,
5745         final int compositionStart,
5746         final int compositionEnd) {}
5747 
5748     /**
5749      * Update the soft input on the current extracted text, as requested through {@link
5750      * android.view.inputmethod.InputConnection#getExtractedText}. Consequently, this method is
5751      * <i>not</i> called in viewless mode.
5752      *
5753      * @param session Session instance.
5754      * @param request The extract text request.
5755      * @param text The extracted text.
5756      */
5757     @UiThread
updateExtractedText( @onNull final GeckoSession session, @NonNull final ExtractedTextRequest request, @NonNull final ExtractedText text)5758     default void updateExtractedText(
5759         @NonNull final GeckoSession session,
5760         @NonNull final ExtractedTextRequest request,
5761         @NonNull final ExtractedText text) {}
5762 
5763     /**
5764      * Update the cursor-anchor information as requested through {@link
5765      * android.view.inputmethod.InputConnection#requestCursorUpdates}. Consequently, this method is
5766      * <i>not</i> called in viewless mode.
5767      *
5768      * @param session Session instance.
5769      * @param info Cursor-anchor information.
5770      */
5771     @UiThread
updateCursorAnchorInfo( @onNull final GeckoSession session, @NonNull final CursorAnchorInfo info)5772     default void updateCursorAnchorInfo(
5773         @NonNull final GeckoSession session, @NonNull final CursorAnchorInfo info) {}
5774   }
5775 
5776   @Retention(RetentionPolicy.SOURCE)
5777   @IntDef({
5778     TextInputDelegate.RESTART_REASON_FOCUS,
5779     TextInputDelegate.RESTART_REASON_BLUR,
5780     TextInputDelegate.RESTART_REASON_CONTENT_CHANGE
5781   })
5782   public @interface RestartReason {}
5783 
onSurfaceChanged( final Surface surface, final int x, final int y, final int width, final int height)5784   /* package */ void onSurfaceChanged(
5785       final Surface surface, final int x, final int y, final int width, final int height) {
5786     ThreadUtils.assertOnUiThread();
5787 
5788     mOffsetX = x;
5789     mOffsetY = y;
5790     mWidth = width;
5791     mHeight = height;
5792 
5793     if (mCompositorReady) {
5794       mCompositor.syncResumeResizeCompositor(x, y, width, height, surface);
5795       onWindowBoundsChanged();
5796       return;
5797     }
5798 
5799     // We have a valid surface but we're not attached or the compositor
5800     // is not ready; save the surface for later when we're ready.
5801     mSurface = surface;
5802 
5803     // Adjust bounds as the last step.
5804     onWindowBoundsChanged();
5805   }
5806 
onSurfaceDestroyed()5807   /* package */ void onSurfaceDestroyed() {
5808     ThreadUtils.assertOnUiThread();
5809 
5810     if (mCompositorReady) {
5811       mCompositor.syncPauseCompositor();
5812       return;
5813     }
5814 
5815     // While the surface was valid, we never became attached or the
5816     // compositor never became ready; clear the saved surface.
5817     mSurface = null;
5818   }
5819 
onScreenOriginChanged(final int left, final int top)5820   /* package */ void onScreenOriginChanged(final int left, final int top) {
5821     ThreadUtils.assertOnUiThread();
5822 
5823     if (mLeft == left && mTop == top) {
5824       return;
5825     }
5826 
5827     mLeft = left;
5828     mTop = top;
5829     onWindowBoundsChanged();
5830   }
5831 
setDynamicToolbarMaxHeight(final int height)5832   /* package */ void setDynamicToolbarMaxHeight(final int height) {
5833     if (mDynamicToolbarMaxHeight == height) {
5834       return;
5835     }
5836 
5837     if (mHeight != 0 && height != 0 && mHeight < height) {
5838       Log.w(
5839           LOGTAG,
5840           new AssertionError(
5841               "The maximum height of the dynamic toolbar ("
5842                   + height
5843                   + ") should be smaller than GeckoView height ("
5844                   + mHeight
5845                   + ")"));
5846     }
5847 
5848     mDynamicToolbarMaxHeight = height;
5849 
5850     if (mAttachedCompositor) {
5851       mCompositor.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
5852     }
5853   }
5854 
setFixedBottomOffset(final int offset)5855   /* package */ void setFixedBottomOffset(final int offset) {
5856     if (mFixedBottomOffset == offset) {
5857       return;
5858     }
5859 
5860     mFixedBottomOffset = offset;
5861 
5862     if (mCompositorReady) {
5863       mCompositor.setFixedBottomOffset(mFixedBottomOffset);
5864     }
5865   }
5866 
onCompositorAttached()5867   /* package */ void onCompositorAttached() {
5868     if (DEBUG) {
5869       ThreadUtils.assertOnUiThread();
5870     }
5871 
5872     mAttachedCompositor = true;
5873     mCompositor.attachNPZC(mPanZoomController.mNative);
5874 
5875     if (mSurface != null) {
5876       // If we have a valid surface, create the compositor now that we're attached.
5877       // Leave mSurface alone because we'll need it later for onCompositorReady.
5878       onSurfaceChanged(mSurface, mOffsetX, mOffsetY, mWidth, mHeight);
5879     }
5880 
5881     mCompositor.sendToolbarAnimatorMessage(IS_COMPOSITOR_CONTROLLER_OPEN);
5882     mCompositor.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
5883   }
5884 
onCompositorDetached()5885   /* package */ void onCompositorDetached() {
5886     if (DEBUG) {
5887       ThreadUtils.assertOnUiThread();
5888     }
5889 
5890     if (mController != null) {
5891       mController.onCompositorDetached();
5892     }
5893 
5894     mAttachedCompositor = false;
5895     mCompositorReady = false;
5896   }
5897 
handleCompositorMessage(final int message)5898   /* package */ void handleCompositorMessage(final int message) {
5899     if (DEBUG) {
5900       ThreadUtils.assertOnUiThread();
5901     }
5902 
5903     switch (message) {
5904       case COMPOSITOR_CONTROLLER_OPEN:
5905         {
5906           if (isCompositorReady()) {
5907             return;
5908           }
5909 
5910           // Delay calling onCompositorReady to avoid deadlock due
5911           // to synchronous call to the compositor.
5912           ThreadUtils.postToUiThread(this::onCompositorReady);
5913           break;
5914         }
5915 
5916       case FIRST_PAINT:
5917         {
5918           if (mController != null) {
5919             mController.onFirstPaint();
5920           }
5921           final ContentDelegate delegate = mContentHandler.getDelegate();
5922           if (delegate != null) {
5923             delegate.onFirstComposite(this);
5924           }
5925           break;
5926         }
5927 
5928       case LAYERS_UPDATED:
5929         {
5930           if (mController != null) {
5931             mController.notifyDrawCallbacks();
5932           }
5933           break;
5934         }
5935 
5936       default:
5937         {
5938           Log.w(LOGTAG, "Unexpected message: " + message);
5939           break;
5940         }
5941     }
5942   }
5943 
isCompositorReady()5944   /* package */ boolean isCompositorReady() {
5945     return mCompositorReady;
5946   }
5947 
onCompositorReady()5948   /* package */ void onCompositorReady() {
5949     if (DEBUG) {
5950       ThreadUtils.assertOnUiThread();
5951     }
5952 
5953     if (!mAttachedCompositor) {
5954       return;
5955     }
5956 
5957     mCompositorReady = true;
5958 
5959     if (mController != null) {
5960       mController.onCompositorReady();
5961     }
5962 
5963     if (mSurface != null) {
5964       // If we have a valid surface, resume the
5965       // compositor now that the compositor is ready.
5966       onSurfaceChanged(mSurface, mOffsetX, mOffsetY, mWidth, mHeight);
5967       mSurface = null;
5968     }
5969 
5970     if (mFixedBottomOffset != 0) {
5971       mCompositor.setFixedBottomOffset(mFixedBottomOffset);
5972     }
5973   }
5974 
updateOverscrollVelocity(final float x, final float y)5975   /* package */ void updateOverscrollVelocity(final float x, final float y) {
5976     if (DEBUG) {
5977       ThreadUtils.assertOnUiThread();
5978     }
5979 
5980     if (mOverscroll == null) {
5981       return;
5982     }
5983 
5984     // Multiply the velocity by 1000 to match what was done in JPZ.
5985     mOverscroll.setVelocity(x * 1000.0f, OverscrollEdgeEffect.AXIS_X);
5986     mOverscroll.setVelocity(y * 1000.0f, OverscrollEdgeEffect.AXIS_Y);
5987   }
5988 
updateOverscrollOffset(final float x, final float y)5989   /* package */ void updateOverscrollOffset(final float x, final float y) {
5990     if (DEBUG) {
5991       ThreadUtils.assertOnUiThread();
5992     }
5993 
5994     if (mOverscroll == null) {
5995       return;
5996     }
5997 
5998     mOverscroll.setDistance(x, OverscrollEdgeEffect.AXIS_X);
5999     mOverscroll.setDistance(y, OverscrollEdgeEffect.AXIS_Y);
6000   }
6001 
onMetricsChanged(final float scrollX, final float scrollY, final float zoom)6002   /* package */ void onMetricsChanged(final float scrollX, final float scrollY, final float zoom) {
6003     if (DEBUG) {
6004       ThreadUtils.assertOnUiThread();
6005     }
6006 
6007     mViewportLeft = scrollX;
6008     mViewportTop = scrollY;
6009     mViewportZoom = zoom;
6010   }
6011 
onWindowBoundsChanged()6012   /* protected */ void onWindowBoundsChanged() {
6013     if (DEBUG) {
6014       ThreadUtils.assertOnUiThread();
6015     }
6016 
6017     if (mHeight != 0 && mDynamicToolbarMaxHeight != 0 && mHeight < mDynamicToolbarMaxHeight) {
6018       Log.w(
6019           LOGTAG,
6020           new AssertionError(
6021               "The maximum height of the dynamic toolbar ("
6022                   + mDynamicToolbarMaxHeight
6023                   + ") should be smaller than GeckoView height ("
6024                   + mHeight
6025                   + ")"));
6026     }
6027 
6028     final int toolbarHeight = 0;
6029 
6030     mClientTop = mTop + toolbarHeight;
6031     // If the view is not tall enough to even fix the toolbar we just
6032     // default the client height to 0
6033     mClientHeight = Math.max(mHeight - toolbarHeight, 0);
6034 
6035     if (mAttachedCompositor) {
6036       mCompositor.onBoundsChanged(mLeft, mClientTop, mWidth, mClientHeight);
6037     }
6038 
6039     if (mOverscroll != null) {
6040       mOverscroll.setSize(mWidth, mClientHeight);
6041     }
6042   }
6043 
onSafeAreaInsetsChanged( final int top, final int right, final int bottom, final int left)6044   /* pacakge */ void onSafeAreaInsetsChanged(
6045       final int top, final int right, final int bottom, final int left) {
6046     ThreadUtils.assertOnUiThread();
6047 
6048     if (mAttachedCompositor) {
6049       mCompositor.onSafeAreaInsetsChanged(top, right, bottom, left);
6050     }
6051   }
6052 
setPointerIcon( final int defaultCursor, final @Nullable Bitmap customCursor, final float x, final float y)6053   /* package */ void setPointerIcon(
6054       final int defaultCursor, final @Nullable Bitmap customCursor, final float x, final float y) {
6055     ThreadUtils.assertOnUiThread();
6056 
6057     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
6058       return;
6059     }
6060 
6061     final PointerIcon icon;
6062     if (customCursor != null) {
6063       try {
6064         icon = PointerIcon.create(customCursor, x, y);
6065       } catch (final IllegalArgumentException e) {
6066         // x/y hotspot might be invalid
6067         return;
6068       }
6069     } else {
6070       final Context context = GeckoAppShell.getApplicationContext();
6071       icon = PointerIcon.getSystemIcon(context, defaultCursor);
6072     }
6073 
6074     final ContentDelegate delegate = getContentDelegate();
6075     delegate.onPointerIconChange(this, icon);
6076   }
6077 
6078   /** GeckoSession applications implement this interface to handle media events. */
6079   public interface MediaDelegate {
6080 
6081     class RecordingDevice {
6082 
6083       /*
6084        * Default status flags for this RecordingDevice.
6085        */
6086       public static class Status {
6087         public static final long RECORDING = 0;
6088         public static final long INACTIVE = 1 << 0;
6089 
6090         // Do not instantiate this class.
Status()6091         protected Status() {}
6092       }
6093 
6094       /*
6095        * Default device types for this RecordingDevice.
6096        */
6097       public static class Type {
6098         public static final long CAMERA = 0;
6099         public static final long MICROPHONE = 1 << 0;
6100 
6101         // Do not instantiate this class.
Type()6102         protected Type() {}
6103       }
6104 
6105       @Retention(RetentionPolicy.SOURCE)
6106       @LongDef(
6107           flag = true,
6108           value = {Status.RECORDING, Status.INACTIVE})
6109       public @interface RecordingStatus {}
6110 
6111       @Retention(RetentionPolicy.SOURCE)
6112       @LongDef(
6113           flag = true,
6114           value = {Type.CAMERA, Type.MICROPHONE})
6115       public @interface DeviceType {}
6116 
6117       /**
6118        * A long giving the current recording status, must be either Status.RECORDING, Status.PAUSED
6119        * or Status.INACTIVE.
6120        */
6121       public final @RecordingStatus long status;
6122 
6123       /**
6124        * A long giving the type of the recording device, must be either Type.CAMERA or
6125        * Type.MICROPHONE.
6126        */
6127       public final @DeviceType long type;
6128 
getTypeFromString(final String type)6129       private static @DeviceType long getTypeFromString(final String type) {
6130         if ("microphone".equals(type)) {
6131           return Type.MICROPHONE;
6132         } else if ("camera".equals(type)) {
6133           return Type.CAMERA;
6134         } else {
6135           throw new IllegalArgumentException(
6136               "String: " + type + " is not a valid recording device string");
6137         }
6138       }
6139 
getStatusFromString(final String type)6140       private static @RecordingStatus long getStatusFromString(final String type) {
6141         if ("recording".equals(type)) {
6142           return Status.RECORDING;
6143         } else {
6144           return Status.INACTIVE;
6145         }
6146       }
6147 
RecordingDevice(final GeckoBundle media)6148       /* package */ RecordingDevice(final GeckoBundle media) {
6149         status = getStatusFromString(media.getString("status"));
6150         type = getTypeFromString(media.getString("type"));
6151       }
6152 
6153       /** Empty constructor for tests. */
RecordingDevice()6154       protected RecordingDevice() {
6155         status = Status.INACTIVE;
6156         type = Type.CAMERA;
6157       }
6158     }
6159 
6160     /**
6161      * A recording device has changed state. Any change to the recording state of the devices
6162      * microphone or camera will call this delegate method. The argument provides details of the
6163      * active recording devices.
6164      *
6165      * @param session The session that the event has originated from.
6166      * @param devices The list of active devices and their recording state.
6167      */
6168     @UiThread
onRecordingStatusChanged( @onNull final GeckoSession session, @NonNull final RecordingDevice[] devices)6169     default void onRecordingStatusChanged(
6170         @NonNull final GeckoSession session, @NonNull final RecordingDevice[] devices) {}
6171   }
6172 
6173   /** An interface for recording new history visits and fetching the visited status for links. */
6174   public interface HistoryDelegate {
6175     /** A representation of an entry in browser history. */
6176     public interface HistoryItem {
6177       /**
6178        * Get the URI of this history element.
6179        *
6180        * @return A String representing the URI of this history element.
6181        */
6182       @AnyThread
getUri()6183       default @NonNull String getUri() {
6184         throw new UnsupportedOperationException("HistoryItem.getUri() called on invalid object.");
6185       }
6186 
6187       /**
6188        * Get the title of this history element.
6189        *
6190        * @return A String representing the title of this history element.
6191        */
6192       @AnyThread
getTitle()6193       default @NonNull String getTitle() {
6194         throw new UnsupportedOperationException(
6195             "HistoryItem.getString() called on invalid object.");
6196       }
6197     }
6198 
6199     /**
6200      * A representation of browser history, accessible as a `List`. The list itself and its entries
6201      * are immutable; any attempt to mutate will result in an `UnsupportedOperationException`.
6202      */
6203     public interface HistoryList extends List<HistoryItem> {
6204       /**
6205        * Get the current index in browser history.
6206        *
6207        * @return An int representing the current index in browser history.
6208        */
6209       @AnyThread
getCurrentIndex()6210       default int getCurrentIndex() {
6211         throw new UnsupportedOperationException(
6212             "HistoryList.getCurrentIndex() called on invalid object.");
6213       }
6214     }
6215 
6216     // These flags are similar to those in `IHistory::LoadFlags`, but we use
6217     // different values to decouple GeckoView from Gecko changes. These
6218     // should be kept in sync with `GeckoViewHistory::GeckoViewVisitFlags`.
6219 
6220     /** The URL was visited a top-level window. */
6221     final int VISIT_TOP_LEVEL = 1 << 0;
6222     /** The URL is the target of a temporary redirect. */
6223     final int VISIT_REDIRECT_TEMPORARY = 1 << 1;
6224     /** The URL is the target of a permanent redirect. */
6225     final int VISIT_REDIRECT_PERMANENT = 1 << 2;
6226     /** The URL is temporarily redirected to another URL. */
6227     final int VISIT_REDIRECT_SOURCE = 1 << 3;
6228     /** The URL is permanently redirected to another URL. */
6229     final int VISIT_REDIRECT_SOURCE_PERMANENT = 1 << 4;
6230     /** The URL failed to load due to a client or server error. */
6231     final int VISIT_UNRECOVERABLE_ERROR = 1 << 5;
6232 
6233     /**
6234      * Records a visit to a page.
6235      *
6236      * @param session The session where the URL was visited.
6237      * @param url The visited URL.
6238      * @param lastVisitedURL The last visited URL in this session, to detect redirects and reloads.
6239      * @param flags Additional flags for this visit, including redirect and error statuses. This is
6240      *     a bitmask of one or more {@link #VISIT_TOP_LEVEL VISIT_*} flags, OR-ed together.
6241      * @return A {@link GeckoResult} completed with a boolean indicating whether to highlight links
6242      *     for the new URL as visited ({@code true}) or unvisited ({@code false}).
6243      */
6244     @UiThread
onVisited( @onNull final GeckoSession session, @NonNull final String url, @Nullable final String lastVisitedURL, @VisitFlags final int flags)6245     default @Nullable GeckoResult<Boolean> onVisited(
6246         @NonNull final GeckoSession session,
6247         @NonNull final String url,
6248         @Nullable final String lastVisitedURL,
6249         @VisitFlags final int flags) {
6250       return null;
6251     }
6252 
6253     /**
6254      * Returns the visited statuses for links on a page. This is used to highlight links as visited
6255      * or unvisited, for example.
6256      *
6257      * @param session The session requesting the visited statuses.
6258      * @param urls A list of URLs to check.
6259      * @return A {@link GeckoResult} completed with a list of booleans corresponding to the URLs in
6260      *     {@code urls}, and indicating whether to highlight links for each URL as visited ({@code
6261      *     true}) or unvisited ({@code false}).
6262      */
6263     @UiThread
getVisited( @onNull final GeckoSession session, @NonNull final String[] urls)6264     default @Nullable GeckoResult<boolean[]> getVisited(
6265         @NonNull final GeckoSession session, @NonNull final String[] urls) {
6266       return null;
6267     }
6268 
6269     @UiThread
6270     @SuppressWarnings("checkstyle:javadocmethod")
onHistoryStateChange( @onNull final GeckoSession session, @NonNull final HistoryList historyList)6271     default void onHistoryStateChange(
6272         @NonNull final GeckoSession session, @NonNull final HistoryList historyList) {}
6273   }
6274 
6275   @Retention(RetentionPolicy.SOURCE)
6276   @IntDef(
6277       flag = true,
6278       value = {
6279         HistoryDelegate.VISIT_TOP_LEVEL,
6280         HistoryDelegate.VISIT_REDIRECT_TEMPORARY,
6281         HistoryDelegate.VISIT_REDIRECT_PERMANENT,
6282         HistoryDelegate.VISIT_REDIRECT_SOURCE,
6283         HistoryDelegate.VISIT_REDIRECT_SOURCE_PERMANENT,
6284         HistoryDelegate.VISIT_UNRECOVERABLE_ERROR
6285       })
6286   public @interface VisitFlags {}
6287 
getAutofillSupport()6288   private Autofill.Support getAutofillSupport() {
6289     return mAutofillSupport;
6290   }
6291 
6292   /**
6293    * Sets the autofill delegate for this session.
6294    *
6295    * @param delegate An instance of {@link Autofill.Delegate}.
6296    */
6297   @UiThread
setAutofillDelegate(final @Nullable Autofill.Delegate delegate)6298   public void setAutofillDelegate(final @Nullable Autofill.Delegate delegate) {
6299     getAutofillSupport().setDelegate(delegate);
6300   }
6301 
6302   /** @return The current {@link Autofill.Delegate} for this session, if any. */
6303   @UiThread
getAutofillDelegate()6304   public @Nullable Autofill.Delegate getAutofillDelegate() {
6305     return getAutofillSupport().getDelegate();
6306   }
6307 
6308   /**
6309    * Perform autofill using the specified values.
6310    *
6311    * @param values Map of autofill IDs to values.
6312    */
6313   @UiThread
autofill(final @NonNull SparseArray<CharSequence> values)6314   public void autofill(final @NonNull SparseArray<CharSequence> values) {
6315     getAutofillSupport().autofill(values);
6316   }
6317 
6318   /**
6319    * Provides an autofill structure similar to {@link
6320    * View#onProvideAutofillVirtualStructure(ViewStructure, int)} , but does not rely on {@link
6321    * ViewStructure} to build the tree. This is useful for apps that want to provide autofill
6322    * functionality without using the Android autofill system or requiring API 26.
6323    *
6324    * @return The elements available for autofill.
6325    */
6326   @UiThread
getAutofillSession()6327   public @NonNull Autofill.Session getAutofillSession() {
6328     return getAutofillSupport().getAutofillSession();
6329   }
6330 
rgbaToArgb(final String color)6331   private static String rgbaToArgb(final String color) {
6332     // We expect #rrggbbaa
6333     if (color.length() != 9 || !color.startsWith("#")) {
6334       throw new IllegalArgumentException("Invalid color format");
6335     }
6336 
6337     return "#" + color.substring(7) + color.substring(1, 7);
6338   }
6339 
fixupManifestColor(final JSONObject manifest, final String name)6340   private static void fixupManifestColor(final JSONObject manifest, final String name)
6341       throws JSONException {
6342     if (manifest.isNull(name)) {
6343       return;
6344     }
6345 
6346     manifest.put(name, rgbaToArgb(manifest.getString(name)));
6347   }
6348 
fixupWebAppManifest(final JSONObject manifest)6349   private static JSONObject fixupWebAppManifest(final JSONObject manifest) {
6350     // Colors are #rrggbbaa, but we want them to be #aarrggbb, since that's what
6351     // android.graphics.Color expects.
6352     try {
6353       fixupManifestColor(manifest, "theme_color");
6354       fixupManifestColor(manifest, "background_color");
6355     } catch (final JSONException e) {
6356       Log.w(LOGTAG, "Failed to fixup web app manifest", e);
6357     }
6358 
6359     return manifest;
6360   }
6361 
maybeCheckDataUriLength(final @NonNull Loader request)6362   private static boolean maybeCheckDataUriLength(final @NonNull Loader request) {
6363     if (!request.mIsDataUri) {
6364       return true;
6365     }
6366 
6367     return request.mUri.length() <= DATA_URI_MAX_LENGTH;
6368   }
6369 }
6370