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