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