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