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