| 1 | // Copyright 2012 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | package org.chromium.android_webview; |
| 6 | |
| 7 | import android.content.pm.PackageManager; |
| 8 | import android.content.res.Configuration; |
| 9 | import android.graphics.Bitmap; |
| 10 | import android.graphics.Canvas; |
| 11 | import android.graphics.Color; |
| 12 | import android.graphics.Picture; |
| 13 | import android.graphics.Rect; |
| 14 | import android.graphics.drawable.Drawable; |
| 15 | import android.net.http.SslCertificate; |
| 16 | import android.os.AsyncTask; |
| 17 | import android.os.Build; |
| 18 | import android.os.Bundle; |
| 19 | import android.os.Message; |
| 20 | import android.os.Process; |
| 21 | import android.text.TextUtils; |
| 22 | import android.util.Log; |
| 23 | import android.view.KeyEvent; |
| 24 | import android.view.MotionEvent; |
| 25 | import android.view.View; |
| 26 | import android.view.ViewGroup; |
| 27 | import android.view.ViewTreeObserver; |
| 28 | import android.view.accessibility.AccessibilityEvent; |
| 29 | import android.view.accessibility.AccessibilityNodeInfo; |
| 30 | import android.view.accessibility.AccessibilityNodeProvider; |
| 31 | import android.view.inputmethod.EditorInfo; |
| 32 | import android.view.inputmethod.InputConnection; |
| 33 | import android.webkit.GeolocationPermissions; |
| 34 | import android.webkit.ValueCallback; |
| 35 | import android.widget.OverScroller; |
| 36 | |
| 37 | import com.google.common.annotations.VisibleForTesting; |
| 38 | |
| 39 | import org.chromium.base.CalledByNative; |
| 40 | import org.chromium.base.JNINamespace; |
| 41 | import org.chromium.base.ThreadUtils; |
| 42 | import org.chromium.content.browser.ContentSettings; |
| 43 | import org.chromium.content.browser.ContentVideoView; |
| 44 | import org.chromium.content.browser.ContentViewClient; |
| 45 | import org.chromium.content.browser.ContentViewCore; |
| 46 | import org.chromium.content.browser.ContentViewStatics; |
| 47 | import org.chromium.content.browser.LoadUrlParams; |
| 48 | import org.chromium.content.browser.NavigationHistory; |
| 49 | import org.chromium.content.browser.PageTransitionTypes; |
| 50 | import org.chromium.content.common.CleanupReference; |
| 51 | import org.chromium.components.navigation_interception.InterceptNavigationDelegate; |
| 52 | import org.chromium.components.navigation_interception.NavigationParams; |
| 53 | import org.chromium.net.GURLUtils; |
| 54 | import org.chromium.ui.gfx.DeviceDisplayInfo; |
| 55 | |
| 56 | import java.io.File; |
| 57 | import java.lang.annotation.Annotation; |
| 58 | import java.net.MalformedURLException; |
| 59 | import java.net.URL; |
| 60 | import java.util.concurrent.Callable; |
| 61 | import java.util.ArrayList; |
| 62 | import java.util.List; |
| 63 | |
| 64 | /** |
| 65 | * Exposes the native AwContents class, and together these classes wrap the ContentViewCore |
| 66 | * and Browser components that are required to implement Android WebView API. This is the |
| 67 | * primary entry point for the WebViewProvider implementation; it holds a 1:1 object |
| 68 | * relationship with application WebView instances. |
| 69 | * (We define this class independent of the hidden WebViewProvider interfaces, to allow |
| 70 | * continuous build & test in the open source SDK-based tree). |
| 71 | */ |
| 72 | @JNINamespace("android_webview") |
| 73 | public class AwContents { |
| 74 | private static final String TAG = "AwContents"; |
| 75 | |
| 76 | private static final String WEB_ARCHIVE_EXTENSION = ".mht"; |
| 77 | |
| 78 | /** |
| 79 | * WebKit hit test related data strcutre. These are used to implement |
| 80 | * getHitTestResult, requestFocusNodeHref, requestImageRef methods in WebView. |
| 81 | * All values should be updated together. The native counterpart is |
| 82 | * AwHitTestData. |
| 83 | */ |
| 84 | public static class HitTestData { |
| 85 | // Used in getHitTestResult. |
| 86 | public int hitTestResultType; |
| 87 | public String hitTestResultExtraData; |
| 88 | |
| 89 | // Used in requestFocusNodeHref (all three) and requestImageRef (only imgSrc). |
| 90 | public String href; |
| 91 | public String anchorText; |
| 92 | public String imgSrc; |
| 93 | } |
| 94 | |
| 95 | /** |
| 96 | * Interface that consumers of {@link AwContents} must implement to allow the proper |
| 97 | * dispatching of view methods through the containing view. |
| 98 | */ |
| 99 | public interface InternalAccessDelegate extends ContentViewCore.InternalAccessDelegate { |
| 100 | /** |
| 101 | * @see View#onScrollChanged(int, int, int, int) |
| 102 | * |
| 103 | * TODO(mkosiba): WebViewClassic calls this, AwContents doesn't. Check if there |
| 104 | * are any cases we're missing, if not - remove. |
| 105 | */ |
| 106 | void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); |
| 107 | |
| 108 | /** |
| 109 | * @see View#overScrollBy(int, int, int, int, int, int, int, int, boolean); |
| 110 | */ |
| 111 | void overScrollBy(int deltaX, int deltaY, |
| 112 | int scrollX, int scrollY, |
| 113 | int scrollRangeX, int scrollRangeY, |
| 114 | int maxOverScrollX, int maxOverScrollY, |
| 115 | boolean isTouchEvent); |
| 116 | |
| 117 | /** |
| 118 | * @see View#scrollTo(int, int) |
| 119 | */ |
| 120 | void super_scrollTo(int scrollX, int scrollY); |
| 121 | |
| 122 | /** |
| 123 | * @see View#setMeasuredDimension(int, int) |
| 124 | */ |
| 125 | void setMeasuredDimension(int measuredWidth, int measuredHeight); |
| 126 | |
| 127 | /** |
| 128 | * @see View#getScrollBarStyle() |
| 129 | */ |
| 130 | int super_getScrollBarStyle(); |
| 131 | |
| 132 | /** |
| 133 | * Requests a callback on the native DrawGL method (see getAwDrawGLFunction) |
| 134 | * if called from within onDraw, |canvas| will be non-null and hardware accelerated. |
| 135 | * otherwise, |canvas| will be null, and the container view itself will be hardware |
| 136 | * accelerated. |
| 137 | * |
| 138 | * @return false indicates the GL draw request was not accepted, and the caller |
| 139 | * should fallback to the SW path. |
| 140 | */ |
| 141 | boolean requestDrawGL(Canvas canvas); |
| 142 | } |
| 143 | |
| 144 | private int mNativeAwContents; |
| 145 | private final AwBrowserContext mBrowserContext; |
| 146 | private final ViewGroup mContainerView; |
| 147 | private ContentViewCore mContentViewCore; |
| 148 | private final AwContentsClient mContentsClient; |
| 149 | private final AwContentsClientBridge mContentsClientBridge; |
| 150 | private final AwWebContentsDelegate mWebContentsDelegate; |
| 151 | private final AwContentsIoThreadClient mIoThreadClient; |
| 152 | private final InterceptNavigationDelegateImpl mInterceptNavigationDelegate; |
| 153 | private final InternalAccessDelegate mInternalAccessAdapter; |
| 154 | private final AwLayoutSizer mLayoutSizer; |
| 155 | private final AwZoomControls mZoomControls; |
| 156 | private final AwScrollOffsetManager mScrollOffsetManager; |
| 157 | private OverScrollGlow mOverScrollGlow; |
| 158 | // This can be accessed on any thread after construction. See AwContentsIoThreadClient. |
| 159 | private final AwSettings mSettings; |
| 160 | |
| 161 | private boolean mIsPaused; |
| 162 | private boolean mIsViewVisible; |
| 163 | private boolean mIsWindowVisible; |
| 164 | private boolean mIsAttachedToWindow; |
| 165 | private Bitmap mFavicon; |
| 166 | private boolean mHasRequestedVisitedHistoryFromClient; |
| 167 | // TODO(boliu): This should be in a global context, not per webview. |
| 168 | private final double mDIPScale; |
| 169 | |
| 170 | // The base background color, i.e. not accounting for any CSS body from the current page. |
| 171 | private int mBaseBackgroundColor = Color.WHITE; |
| 172 | |
| 173 | // Must call nativeUpdateLastHitTestData first to update this before use. |
| 174 | private final HitTestData mPossiblyStaleHitTestData = new HitTestData(); |
| 175 | |
| 176 | private DefaultVideoPosterRequestHandler mDefaultVideoPosterRequestHandler; |
| 177 | |
| 178 | // Bound method for suppling Picture instances to the AwContentsClient. Will be null if the |
| 179 | // picture listener API has not yet been enabled, or if it is using invalidation-only mode. |
| 180 | private Callable<Picture> mPictureListenerContentProvider; |
| 181 | |
| 182 | private boolean mContainerViewFocused; |
| 183 | private boolean mWindowFocused; |
| 184 | |
| 185 | private AwAutofillManagerDelegate mAwAutofillManagerDelegate; |
| 186 | |
| 187 | private static final class DestroyRunnable implements Runnable { |
| 188 | private int mNativeAwContents; |
| 189 | private DestroyRunnable(int nativeAwContents) { |
| 190 | mNativeAwContents = nativeAwContents; |
| 191 | } |
| 192 | @Override |
| 193 | public void run() { |
| 194 | // This is a no-op if not currently attached. |
| 195 | nativeOnDetachedFromWindow(mNativeAwContents); |
| 196 | nativeDestroy(mNativeAwContents); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | // Reference to the active mNativeAwContents pointer while it is active use |
| 201 | // (ie before it is destroyed). |
| 202 | private CleanupReference mCleanupReference; |
| 203 | |
| 204 | // A list of references to native pointers where the Java counterpart has been |
| 205 | // destroyed, but are held here because they are waiting for onDetachFromWindow |
| 206 | // to release GL resources. This is cleared inside onDetachFromWindow. |
| 207 | private List<CleanupReference> mPendingDetachCleanupReferences; |
| 208 | |
| 209 | //-------------------------------------------------------------------------------------------- |
| 210 | private class IoThreadClientImpl implements AwContentsIoThreadClient { |
| 211 | // All methods are called on the IO thread. |
| 212 | |
| 213 | @Override |
| 214 | public int getCacheMode() { |
| 215 | return mSettings.getCacheMode(); |
| 216 | } |
| 217 | |
| 218 | @Override |
| 219 | public InterceptedRequestData shouldInterceptRequest(final String url, |
| 220 | boolean isMainFrame) { |
| 221 | InterceptedRequestData interceptedRequestData; |
| 222 | // Return the response directly if the url is default video poster url. |
| 223 | interceptedRequestData = mDefaultVideoPosterRequestHandler.shouldInterceptRequest(url); |
| 224 | if (interceptedRequestData != null) return interceptedRequestData; |
| 225 | |
| 226 | interceptedRequestData = mContentsClient.shouldInterceptRequest(url); |
| 227 | |
| 228 | if (interceptedRequestData == null) { |
| 229 | mContentsClient.getCallbackHelper().postOnLoadResource(url); |
| 230 | } |
| 231 | |
| 232 | if (isMainFrame && interceptedRequestData != null && |
| 233 | interceptedRequestData.getData() == null) { |
| 234 | // In this case the intercepted URLRequest job will simulate an empty response |
| 235 | // which doesn't trigger the onReceivedError callback. For WebViewClassic |
| 236 | // compatibility we synthesize that callback. http://crbug.com/180950 |
| 237 | mContentsClient.getCallbackHelper().postOnReceivedError( |
| 238 | ErrorCodeConversionHelper.ERROR_UNKNOWN, |
| 239 | null /* filled in by the glue layer */, url); |
| 240 | } |
| 241 | return interceptedRequestData; |
| 242 | } |
| 243 | |
| 244 | @Override |
| 245 | public boolean shouldBlockContentUrls() { |
| 246 | return !mSettings.getAllowContentAccess(); |
| 247 | } |
| 248 | |
| 249 | @Override |
| 250 | public boolean shouldBlockFileUrls() { |
| 251 | return !mSettings.getAllowFileAccess(); |
| 252 | } |
| 253 | |
| 254 | @Override |
| 255 | public boolean shouldBlockNetworkLoads() { |
| 256 | return mSettings.getBlockNetworkLoads(); |
| 257 | } |
| 258 | |
| 259 | @Override |
| 260 | public void onDownloadStart(String url, |
| 261 | String userAgent, |
| 262 | String contentDisposition, |
| 263 | String mimeType, |
| 264 | long contentLength) { |
| 265 | mContentsClient.getCallbackHelper().postOnDownloadStart(url, userAgent, |
| 266 | contentDisposition, mimeType, contentLength); |
| 267 | } |
| 268 | |
| 269 | @Override |
| 270 | public void newLoginRequest(String realm, String account, String args) { |
| 271 | mContentsClient.getCallbackHelper().postOnReceivedLoginRequest(realm, account, args); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | //-------------------------------------------------------------------------------------------- |
| 276 | private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate { |
| 277 | private String mLastLoadUrlAddress; |
| 278 | |
| 279 | public void onUrlLoadRequested(String url) { |
| 280 | mLastLoadUrlAddress = url; |
| 281 | } |
| 282 | |
| 283 | @Override |
| 284 | public boolean shouldIgnoreNavigation(NavigationParams navigationParams) { |
| 285 | final String url = navigationParams.url; |
| 286 | final int transitionType = navigationParams.pageTransitionType; |
| 287 | final boolean isLoadUrl = |
| 288 | (transitionType & PageTransitionTypes.PAGE_TRANSITION_FROM_API) != 0; |
| 289 | final boolean isBackForward = |
| 290 | (transitionType & PageTransitionTypes.PAGE_TRANSITION_FORWARD_BACK) != 0; |
| 291 | final boolean isReload = |
| 292 | (transitionType & PageTransitionTypes.PAGE_TRANSITION_CORE_MASK) == |
| 293 | PageTransitionTypes.PAGE_TRANSITION_RELOAD; |
| 294 | final boolean isRedirect = navigationParams.isRedirect; |
| 295 | |
| 296 | boolean ignoreNavigation = false; |
| 297 | |
| 298 | // Any navigation from loadUrl, goBack/Forward, or reload, are considered application |
| 299 | // initiated and hence will not yield a shouldOverrideUrlLoading() callback. |
| 300 | // TODO(joth): Using PageTransitionTypes should be sufficient to determine all app |
| 301 | // initiated navigations, and so mLastLoadUrlAddress should be removed. |
| 302 | if ((isLoadUrl && !isRedirect) || isBackForward || isReload || |
| 303 | mLastLoadUrlAddress != null && mLastLoadUrlAddress.equals(url)) { |
| 304 | // Support the case where the user clicks on a link that takes them back to the |
| 305 | // same page. |
| 306 | mLastLoadUrlAddress = null; |
| 307 | |
| 308 | // If the embedder requested the load of a certain URL via the loadUrl API, then we |
| 309 | // do not offer it to AwContentsClient.shouldOverrideUrlLoading. |
| 310 | // The embedder is also not allowed to intercept POST requests because of |
| 311 | // crbug.com/155250. |
| 312 | } else if (!navigationParams.isPost) { |
| 313 | ignoreNavigation = mContentsClient.shouldOverrideUrlLoading(url); |
| 314 | } |
| 315 | |
| 316 | // The existing contract is that shouldOverrideUrlLoading callbacks are delivered before |
| 317 | // onPageStarted callbacks; third party apps depend on this behavior. |
| 318 | // Using a ResouceThrottle to implement the navigation interception feature results in |
| 319 | // the WebContentsObserver.didStartLoading callback happening before the |
| 320 | // ResourceThrottle has a chance to run. |
| 321 | // To preserve the ordering the onPageStarted callback is synthesized from the |
| 322 | // shouldOverrideUrlLoading, and only if the navigation was not ignored (this |
| 323 | // balances out with the onPageFinished callback, which is suppressed in the |
| 324 | // AwContentsClient if the navigation was ignored). |
| 325 | if (!ignoreNavigation) { |
| 326 | // The shouldOverrideUrlLoading call might have resulted in posting messages to the |
| 327 | // UI thread. Using sendMessage here (instead of calling onPageStarted directly) |
| 328 | // will allow those to run in order. |
| 329 | mContentsClient.getCallbackHelper().postOnPageStarted(url); |
| 330 | } |
| 331 | |
| 332 | return ignoreNavigation; |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | //-------------------------------------------------------------------------------------------- |
| 337 | private class AwLayoutSizerDelegate implements AwLayoutSizer.Delegate { |
| 338 | @Override |
| 339 | public void requestLayout() { |
| 340 | mContainerView.requestLayout(); |
| 341 | } |
| 342 | |
| 343 | @Override |
| 344 | public void setMeasuredDimension(int measuredWidth, int measuredHeight) { |
| 345 | mInternalAccessAdapter.setMeasuredDimension(measuredWidth, measuredHeight); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | //-------------------------------------------------------------------------------------------- |
| 350 | // NOTE: This content size change notification comes from the compositor and reflects the size |
| 351 | // of the content on screen (but not neccessarily in the renderer main thread). |
| 352 | private class AwContentUpdateFrameInfoListener |
| 353 | implements ContentViewCore.UpdateFrameInfoListener { |
| 354 | @Override |
| 355 | public void onFrameInfoUpdated(float widthCss, float heightCss, float pageScaleFactor) { |
| 356 | if (mNativeAwContents == 0) return; |
| 357 | int widthPix = (int) Math.floor(widthCss * mDIPScale * pageScaleFactor); |
| 358 | int heightPix = (int) Math.floor(heightCss * mDIPScale * pageScaleFactor); |
| 359 | mScrollOffsetManager.setContentSize(widthPix, heightPix); |
| 360 | |
| 361 | nativeSetDisplayedPageScaleFactor(mNativeAwContents, pageScaleFactor); |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | //-------------------------------------------------------------------------------------------- |
| 366 | private class AwScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate { |
| 367 | @Override |
| 368 | public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, |
| 369 | int scrollRangeX, int scrollRangeY, boolean isTouchEvent) { |
| 370 | mInternalAccessAdapter.overScrollBy(deltaX, deltaY, scrollX, scrollY, |
| 371 | scrollRangeX, scrollRangeY, 0, 0, isTouchEvent); |
| 372 | } |
| 373 | |
| 374 | @Override |
| 375 | public void scrollContainerViewTo(int x, int y) { |
| 376 | mInternalAccessAdapter.super_scrollTo(x, y); |
| 377 | } |
| 378 | |
| 379 | @Override |
| 380 | public void scrollNativeTo(int x, int y) { |
| 381 | if (mNativeAwContents == 0) return; |
| 382 | nativeScrollTo(mNativeAwContents, x, y); |
| 383 | } |
| 384 | |
| 385 | @Override |
| 386 | public int getContainerViewScrollX() { |
| 387 | return mContainerView.getScrollX(); |
| 388 | } |
| 389 | |
| 390 | @Override |
| 391 | public int getContainerViewScrollY() { |
| 392 | return mContainerView.getScrollY(); |
| 393 | } |
| 394 | |
| 395 | @Override |
| 396 | public void invalidate() { |
| 397 | mContainerView.invalidate(); |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | //-------------------------------------------------------------------------------------------- |
| 402 | private class AwGestureStateListener implements ContentViewCore.GestureStateListener { |
| 403 | @Override |
| 404 | public void onPinchGestureStart() { |
| 405 | // While it's possible to re-layout the view during a pinch gesture, the effect is very |
| 406 | // janky (especially that the page scale update notification comes from the renderer |
| 407 | // main thread, not from the impl thread, so it's usually out of sync with what's on |
| 408 | // screen). It's also quite expensive to do a re-layout, so we simply postpone |
| 409 | // re-layout for the duration of the gesture. This is compatible with what |
| 410 | // WebViewClassic does. |
| 411 | mLayoutSizer.freezeLayoutRequests(); |
| 412 | } |
| 413 | |
| 414 | @Override |
| 415 | public void onPinchGestureEnd() { |
| 416 | mLayoutSizer.unfreezeLayoutRequests(); |
| 417 | } |
| 418 | |
| 419 | @Override |
| 420 | public void onFlingStartGesture(int velocityX, int velocityY) { |
| 421 | mScrollOffsetManager.onFlingStartGesture(velocityX, velocityY); |
| 422 | } |
| 423 | |
| 424 | |
| 425 | @Override |
| 426 | public void onFlingCancelGesture() { |
| 427 | mScrollOffsetManager.onFlingCancelGesture(); |
| 428 | } |
| 429 | |
| 430 | @Override |
| 431 | public void onUnhandledFlingStartEvent() { |
| 432 | mScrollOffsetManager.onUnhandledFlingStartEvent(); |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | /** |
| 437 | * @param browserContext the browsing context to associate this view contents with. |
| 438 | * @param containerView the view-hierarchy item this object will be bound to. |
| 439 | * @param internalAccessAdapter to access private methods on containerView. |
| 440 | * @param contentsClient will receive API callbacks from this WebView Contents |
| 441 | * @param isAccessFromFileURLsGrantedByDefault passed to AwSettings. |
| 442 | * |
| 443 | * This constructor uses the default view sizing policy. |
| 444 | */ |
| 445 | public AwContents(AwBrowserContext browserContext, ViewGroup containerView, |
| 446 | InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient, |
| 447 | boolean isAccessFromFileURLsGrantedByDefault) { |
| 448 | this(browserContext, containerView, internalAccessAdapter, contentsClient, |
| 449 | isAccessFromFileURLsGrantedByDefault, new AwLayoutSizer()); |
| 450 | } |
| 451 | |
| 452 | public AwContents(AwBrowserContext browserContext, ViewGroup containerView, |
| 453 | InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient, |
| 454 | boolean isAccessFromFileURLsGrantedByDefault, AwLayoutSizer layoutSizer) { |
| 455 | this(browserContext, containerView, internalAccessAdapter, contentsClient, |
| 456 | isAccessFromFileURLsGrantedByDefault, layoutSizer, false); |
| 457 | } |
| 458 | |
| 459 | private static ContentViewCore createAndInitializeContentViewCore(ViewGroup containerView, |
| 460 | InternalAccessDelegate internalDispatcher, int nativeWebContents, |
| 461 | ContentViewCore.GestureStateListener pinchGestureStateListener, |
| 462 | ContentViewClient contentViewClient, |
| 463 | ContentViewCore.ZoomControlsDelegate zoomControlsDelegate) { |
| 464 | ContentViewCore contentViewCore = new ContentViewCore(containerView.getContext()); |
| 465 | // Note INPUT_EVENTS_DELIVERED_IMMEDIATELY is passed to avoid triggering vsync in the |
| 466 | // compositor, not because input events are delivered immediately. |
| 467 | contentViewCore.initialize(containerView, internalDispatcher, nativeWebContents, null, |
| 468 | ContentViewCore.INPUT_EVENTS_DELIVERED_IMMEDIATELY); |
| 469 | contentViewCore.setGestureStateListener(pinchGestureStateListener); |
| 470 | contentViewCore.setContentViewClient(contentViewClient); |
| 471 | contentViewCore.setZoomControlsDelegate(zoomControlsDelegate); |
| 472 | return contentViewCore; |
| 473 | } |
| 474 | |
| 475 | /** |
| 476 | * @param layoutSizer the AwLayoutSizer instance implementing the sizing policy for the view. |
| 477 | * |
| 478 | * This version of the constructor is used in test code to inject test versions of the above |
| 479 | * documented classes |
| 480 | */ |
| 481 | public AwContents(AwBrowserContext browserContext, ViewGroup containerView, |
| 482 | InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient, |
| 483 | boolean isAccessFromFileURLsGrantedByDefault, AwLayoutSizer layoutSizer, |
| 484 | boolean supportsLegacyQuirks) { |
| 485 | mBrowserContext = browserContext; |
| 486 | mContainerView = containerView; |
| 487 | mInternalAccessAdapter = internalAccessAdapter; |
| 488 | mContentsClient = contentsClient; |
| 489 | mLayoutSizer = layoutSizer; |
| 490 | mDIPScale = DeviceDisplayInfo.create(containerView.getContext()).getDIPScale(); |
| 491 | mLayoutSizer.setDelegate(new AwLayoutSizerDelegate()); |
| 492 | mLayoutSizer.setDIPScale(mDIPScale); |
| 493 | mWebContentsDelegate = new AwWebContentsDelegateAdapter(contentsClient, |
| 494 | mLayoutSizer.getPreferredSizeChangedListener()); |
| 495 | mContentsClientBridge = new AwContentsClientBridge(contentsClient); |
| 496 | mZoomControls = new AwZoomControls(this); |
| 497 | mIoThreadClient = new IoThreadClientImpl(); |
| 498 | mInterceptNavigationDelegate = new InterceptNavigationDelegateImpl(); |
| 499 | |
| 500 | boolean hasInternetPermission = containerView.getContext().checkPermission( |
| 501 | android.Manifest.permission.INTERNET, |
| 502 | Process.myPid(), |
| 503 | Process.myUid()) == PackageManager.PERMISSION_GRANTED; |
| 504 | AwSettings.ZoomSupportChangeListener zoomListener = |
| 505 | new AwSettings.ZoomSupportChangeListener() { |
| 506 | @Override |
| 507 | public void onGestureZoomSupportChanged(boolean supportsGestureZoom) { |
| 508 | mContentViewCore.updateMultiTouchZoomSupport(supportsGestureZoom); |
| 509 | mContentViewCore.updateDoubleTapDragSupport(supportsGestureZoom); |
| 510 | } |
| 511 | |
| 512 | }; |
| 513 | mSettings = new AwSettings(mContainerView.getContext(), hasInternetPermission, zoomListener, |
| 514 | isAccessFromFileURLsGrantedByDefault, mDIPScale, supportsLegacyQuirks); |
| 515 | mDefaultVideoPosterRequestHandler = new DefaultVideoPosterRequestHandler(mContentsClient); |
| 516 | mSettings.setDefaultVideoPosterURL( |
| 517 | mDefaultVideoPosterRequestHandler.getDefaultVideoPosterURL()); |
| 518 | mContentsClient.setDIPScale(mDIPScale); |
| 519 | mScrollOffsetManager = new AwScrollOffsetManager(new AwScrollOffsetManagerDelegate(), |
| 520 | new OverScroller(mContainerView.getContext())); |
| 521 | |
| 522 | setOverScrollMode(mContainerView.getOverScrollMode()); |
| 523 | setScrollBarStyle(mInternalAccessAdapter.super_getScrollBarStyle()); |
| 524 | |
| 525 | setNewAwContents(nativeInit(browserContext)); |
| 526 | } |
| 527 | |
| 528 | /** |
| 529 | * Common initialization routine for adopting a native AwContents instance into this |
| 530 | * java instance. |
| 531 | * |
| 532 | * TAKE CARE! This method can get called multiple times per java instance. Code accordingly. |
| 533 | * ^^^^^^^^^ See the native class declaration for more details on relative object lifetimes. |
| 534 | */ |
| 535 | private void setNewAwContents(int newAwContentsPtr) { |
| 536 | if (mNativeAwContents != 0) { |
| 537 | destroy(); |
| 538 | mContentViewCore = null; |
| 539 | } |
| 540 | |
| 541 | assert mNativeAwContents == 0 && mCleanupReference == null && mContentViewCore == null; |
| 542 | |
| 543 | mNativeAwContents = newAwContentsPtr; |
| 544 | // TODO(joth): when the native and java counterparts of AwBrowserContext are hooked up to |
| 545 | // each other, we should update |mBrowserContext| according to the newly received native |
| 546 | // WebContent's browser context. |
| 547 | |
| 548 | // The native side object has been bound to this java instance, so now is the time to |
| 549 | // bind all the native->java relationships. |
| 550 | mCleanupReference = new CleanupReference(this, new DestroyRunnable(mNativeAwContents)); |
| 551 | |
| 552 | int nativeWebContents = nativeGetWebContents(mNativeAwContents); |
| 553 | mContentViewCore = createAndInitializeContentViewCore( |
| 554 | mContainerView, mInternalAccessAdapter, nativeWebContents, |
| 555 | new AwGestureStateListener(), mContentsClient.getContentViewClient(), |
| 556 | mZoomControls); |
| 557 | nativeSetJavaPeers(mNativeAwContents, this, mWebContentsDelegate, mContentsClientBridge, |
| 558 | mIoThreadClient, mInterceptNavigationDelegate); |
| 559 | mContentsClient.installWebContentsObserver(mContentViewCore); |
| 560 | mContentViewCore.setUpdateFrameInfoListener(new AwContentUpdateFrameInfoListener()); |
| 561 | mSettings.setWebContents(nativeWebContents); |
| 562 | nativeSetDipScale(mNativeAwContents, (float) mDIPScale); |
| 563 | updateGlobalVisibleRect(); |
| 564 | |
| 565 | // The only call to onShow. onHide should never be called. |
| 566 | mContentViewCore.onShow(); |
| 567 | } |
| 568 | |
| 569 | /** |
| 570 | * Called on the "source" AwContents that is opening the popup window to |
| 571 | * provide the AwContents to host the pop up content. |
| 572 | */ |
| 573 | public void supplyContentsForPopup(AwContents newContents) { |
| 574 | int popupNativeAwContents = nativeReleasePopupAwContents(mNativeAwContents); |
| 575 | if (popupNativeAwContents == 0) { |
| 576 | Log.w(TAG, "Popup WebView bind failed: no pending content."); |
| 577 | if (newContents != null) newContents.destroy(); |
| 578 | return; |
| 579 | } |
| 580 | if (newContents == null) { |
| 581 | nativeDestroy(popupNativeAwContents); |
| 582 | return; |
| 583 | } |
| 584 | |
| 585 | newContents.receivePopupContents(popupNativeAwContents); |
| 586 | } |
| 587 | |
| 588 | // Recap: supplyContentsForPopup() is called on the parent window's content, this method is |
| 589 | // called on the popup window's content. |
| 590 | private void receivePopupContents(int popupNativeAwContents) { |
| 591 | // Save existing view state. |
| 592 | final boolean wasAttached = mIsAttachedToWindow; |
| 593 | final boolean wasViewVisible = mIsViewVisible; |
| 594 | final boolean wasWindowVisible = mIsWindowVisible; |
| 595 | final boolean wasPaused = mIsPaused; |
| 596 | final boolean wasFocused = mWindowFocused; |
| 597 | |
| 598 | // Properly clean up existing mContentViewCore and mNativeAwContents. |
| 599 | if (wasFocused) onWindowFocusChanged(false); |
| 600 | if (wasViewVisible) setViewVisibilityInternal(false); |
| 601 | if (wasWindowVisible) setWindowVisibilityInternal(false); |
| 602 | if (!wasPaused) onPause(); |
| 603 | // Not calling onDetachedFromWindow here because native code requires GL context to release |
| 604 | // GL resources. This case is properly handled when destroy is called while still attached |
| 605 | // to window. |
| 606 | |
| 607 | setNewAwContents(popupNativeAwContents); |
| 608 | |
| 609 | // Finally refresh all view state for mContentViewCore and mNativeAwContents. |
| 610 | if (!wasPaused) onResume(); |
| 611 | if (wasAttached) onAttachedToWindow(); |
| 612 | onSizeChanged(mContainerView.getWidth(), mContainerView.getHeight(), 0, 0); |
| 613 | if (wasWindowVisible) setWindowVisibilityInternal(true); |
| 614 | if (wasViewVisible) setViewVisibilityInternal(true); |
| 615 | if (wasFocused) onWindowFocusChanged(true); |
| 616 | } |
| 617 | |
| 618 | /** |
| 619 | * Deletes the native counterpart of this object. Normally happens immediately, |
| 620 | * but maybe deferred until the appropriate time for GL resource cleanup. Either way |
| 621 | * this is transparent to the caller: after this function returns the object is |
| 622 | * effectively dead and methods are no-ops. |
| 623 | */ |
| 624 | public void destroy() { |
| 625 | if (mCleanupReference != null) { |
| 626 | // We explicitly do not null out the mContentViewCore reference here |
| 627 | // because ContentViewCore already has code to deal with the case |
| 628 | // methods are called on it after it's been destroyed, and other |
| 629 | // code relies on AwContents.mContentViewCore to be non-null. |
| 630 | mContentViewCore.destroy(); |
| 631 | mNativeAwContents = 0; |
| 632 | |
| 633 | // We cannot destroy immediately if we are still attached to the window. |
| 634 | // Instead if we make sure to null out the native pointer so there is no more native |
| 635 | // calls, and delay the actual destroy until onDetachedFromWindow. |
| 636 | if (mIsAttachedToWindow) { |
| 637 | if (mPendingDetachCleanupReferences == null) { |
| 638 | mPendingDetachCleanupReferences = new ArrayList<CleanupReference>(); |
| 639 | } |
| 640 | mPendingDetachCleanupReferences.add(mCleanupReference); |
| 641 | } else { |
| 642 | mCleanupReference.cleanupNow(); |
| 643 | } |
| 644 | mCleanupReference = null; |
| 645 | } |
| 646 | |
| 647 | assert !mContentViewCore.isAlive(); |
| 648 | assert mNativeAwContents == 0; |
| 649 | } |
| 650 | |
| 651 | @VisibleForTesting |
| 652 | public ContentViewCore getContentViewCore() { |
| 653 | return mContentViewCore; |
| 654 | } |
| 655 | |
| 656 | // Can be called from any thread. |
| 657 | public AwSettings getSettings() { |
| 658 | return mSettings; |
| 659 | } |
| 660 | |
| 661 | public static void setAwDrawSWFunctionTable(int functionTablePointer) { |
| 662 | nativeSetAwDrawSWFunctionTable(functionTablePointer); |
| 663 | } |
| 664 | |
| 665 | public static void setAwDrawGLFunctionTable(int functionTablePointer) { |
| 666 | nativeSetAwDrawGLFunctionTable(functionTablePointer); |
| 667 | } |
| 668 | |
| 669 | public static int getAwDrawGLFunction() { |
| 670 | return nativeGetAwDrawGLFunction(); |
| 671 | } |
| 672 | |
| 673 | /** |
| 674 | * Intended for test code. |
| 675 | * @return the number of native instances of this class. |
| 676 | */ |
| 677 | @VisibleForTesting |
| 678 | public static int getNativeInstanceCount() { |
| 679 | return nativeGetNativeInstanceCount(); |
| 680 | } |
| 681 | |
| 682 | public int getAwDrawGLViewContext() { |
| 683 | // Only called during early construction, so client should not have had a chance to |
| 684 | // call destroy yet. |
| 685 | assert mNativeAwContents != 0; |
| 686 | |
| 687 | // Using the native pointer as the returned viewContext. This is matched by the |
| 688 | // reinterpret_cast back to BrowserViewRenderer pointer in the native DrawGLFunction. |
| 689 | return nativeGetAwDrawGLViewContext(mNativeAwContents); |
| 690 | } |
| 691 | |
| 692 | // This is only to avoid heap allocations inside updateGLobalVisibleRect. It should treated |
| 693 | // as a local variable in the function and not used anywhere else. |
| 694 | private static final Rect sLocalGlobalVisibleRect = new Rect(); |
| 695 | |
| 696 | @CalledByNative |
| 697 | private void updateGlobalVisibleRect() { |
| 698 | if (mNativeAwContents == 0) return; |
| 699 | if (!mContainerView.getGlobalVisibleRect(sLocalGlobalVisibleRect)) { |
| 700 | sLocalGlobalVisibleRect.setEmpty(); |
| 701 | } |
| 702 | |
| 703 | nativeSetGlobalVisibleRect(mNativeAwContents, sLocalGlobalVisibleRect.left, |
| 704 | sLocalGlobalVisibleRect.top, sLocalGlobalVisibleRect.right, |
| 705 | sLocalGlobalVisibleRect.bottom); |
| 706 | } |
| 707 | |
| 708 | //-------------------------------------------------------------------------------------------- |
| 709 | // WebView[Provider] method implementations (where not provided by ContentViewCore) |
| 710 | //-------------------------------------------------------------------------------------------- |
| 711 | |
| 712 | // Only valid within onDraw(). |
| 713 | private final Rect mClipBoundsTemporary = new Rect(); |
| 714 | |
| 715 | public void onDraw(Canvas canvas) { |
| 716 | if (mNativeAwContents == 0) { |
| 717 | canvas.drawColor(getEffectiveBackgroundColor()); |
| 718 | return; |
| 719 | } |
| 720 | |
| 721 | mScrollOffsetManager.syncScrollOffsetFromOnDraw(); |
| 722 | |
| 723 | canvas.getClipBounds(mClipBoundsTemporary); |
| 724 | if (!nativeOnDraw(mNativeAwContents, canvas, canvas.isHardwareAccelerated(), |
| 725 | mContainerView.getScrollX(), mContainerView.getScrollY(), |
| 726 | mClipBoundsTemporary.left, mClipBoundsTemporary.top, |
| 727 | mClipBoundsTemporary.right, mClipBoundsTemporary.bottom)) { |
| 728 | Log.w(TAG, "nativeOnDraw failed; clearing to background color."); |
| 729 | canvas.drawColor(getEffectiveBackgroundColor()); |
| 730 | } |
| 731 | |
| 732 | if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas, |
| 733 | mScrollOffsetManager.computeMaximumHorizontalScrollOffset(), |
| 734 | mScrollOffsetManager.computeMaximumVerticalScrollOffset())) { |
| 735 | mContainerView.invalidate(); |
| 736 | } |
| 737 | } |
| 738 | |
| 739 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| 740 | mLayoutSizer.onMeasure(widthMeasureSpec, heightMeasureSpec); |
| 741 | } |
| 742 | |
| 743 | public int getContentHeightCss() { |
| 744 | return (int) Math.ceil(mContentViewCore.getContentHeightCss()); |
| 745 | } |
| 746 | |
| 747 | public int getContentWidthCss() { |
| 748 | return (int) Math.ceil(mContentViewCore.getContentWidthCss()); |
| 749 | } |
| 750 | |
| 751 | public Picture capturePicture() { |
| 752 | if (mNativeAwContents == 0) return null; |
| 753 | return new AwPicture(nativeCapturePicture(mNativeAwContents, |
| 754 | mScrollOffsetManager.computeHorizontalScrollRange(), |
| 755 | mScrollOffsetManager.computeVerticalScrollRange())); |
| 756 | } |
| 757 | |
| 758 | /** |
| 759 | * Enable the onNewPicture callback. |
| 760 | * @param enabled Flag to enable the callback. |
| 761 | * @param invalidationOnly Flag to call back only on invalidation without providing a picture. |
| 762 | */ |
| 763 | public void enableOnNewPicture(boolean enabled, boolean invalidationOnly) { |
| 764 | if (mNativeAwContents == 0) return; |
| 765 | if (invalidationOnly) { |
| 766 | mPictureListenerContentProvider = null; |
| 767 | } else if (enabled && mPictureListenerContentProvider == null) { |
| 768 | mPictureListenerContentProvider = new Callable<Picture>() { |
| 769 | @Override |
| 770 | public Picture call() { |
| 771 | return capturePicture(); |
| 772 | } |
| 773 | }; |
| 774 | } |
| 775 | nativeEnableOnNewPicture(mNativeAwContents, enabled); |
| 776 | } |
| 777 | |
| 778 | public void findAllAsync(String searchString) { |
| 779 | if (mNativeAwContents == 0) return; |
| 780 | nativeFindAllAsync(mNativeAwContents, searchString); |
| 781 | } |
| 782 | |
| 783 | public void findNext(boolean forward) { |
| 784 | if (mNativeAwContents == 0) return; |
| 785 | nativeFindNext(mNativeAwContents, forward); |
| 786 | } |
| 787 | |
| 788 | public void clearMatches() { |
| 789 | if (mNativeAwContents == 0) return; |
| 790 | nativeClearMatches(mNativeAwContents); |
| 791 | } |
| 792 | |
| 793 | /** |
| 794 | * @return load progress of the WebContents. |
| 795 | */ |
| 796 | public int getMostRecentProgress() { |
| 797 | // WebContentsDelegateAndroid conveniently caches the most recent notified value for us. |
| 798 | return mWebContentsDelegate.getMostRecentProgress(); |
| 799 | } |
| 800 | |
| 801 | public Bitmap getFavicon() { |
| 802 | return mFavicon; |
| 803 | } |
| 804 | |
| 805 | private void requestVisitedHistoryFromClient() { |
| 806 | ValueCallback<String[]> callback = new ValueCallback<String[]>() { |
| 807 | @Override |
| 808 | public void onReceiveValue(final String[] value) { |
| 809 | ThreadUtils.runOnUiThread(new Runnable() { |
| 810 | @Override |
| 811 | public void run() { |
| 812 | if (mNativeAwContents == 0) return; |
| 813 | nativeAddVisitedLinks(mNativeAwContents, value); |
| 814 | } |
| 815 | }); |
| 816 | } |
| 817 | }; |
| 818 | mContentsClient.getVisitedHistory(callback); |
| 819 | } |
| 820 | |
| 821 | /** |
| 822 | * Load url without fixing up the url string. Consumers of ContentView are responsible for |
| 823 | * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left |
| 824 | * off during user input). |
| 825 | * |
| 826 | * @param pararms Parameters for this load. |
| 827 | */ |
| 828 | public void loadUrl(LoadUrlParams params) { |
| 829 | if (params.getLoadUrlType() == LoadUrlParams.LOAD_TYPE_DATA && |
| 830 | !params.isBaseUrlDataScheme()) { |
| 831 | // This allows data URLs with a non-data base URL access to file:///android_asset/ and |
| 832 | // file:///android_res/ URLs. If AwSettings.getAllowFileAccess permits, it will also |
| 833 | // allow access to file:// URLs (subject to OS level permission checks). |
| 834 | params.setCanLoadLocalResources(true); |
| 835 | } |
| 836 | |
| 837 | // If we are reloading the same url, then set transition type as reload. |
| 838 | if (params.getUrl() != null && |
| 839 | params.getUrl().equals(mContentViewCore.getUrl()) && |
| 840 | params.getTransitionType() == PageTransitionTypes.PAGE_TRANSITION_LINK) { |
| 841 | params.setTransitionType(PageTransitionTypes.PAGE_TRANSITION_RELOAD); |
| 842 | } |
| 843 | params.setTransitionType( |
| 844 | params.getTransitionType() | PageTransitionTypes.PAGE_TRANSITION_FROM_API); |
| 845 | |
| 846 | // For WebView, always use the user agent override, which is set |
| 847 | // every time the user agent in AwSettings is modified. |
| 848 | params.setOverrideUserAgent(LoadUrlParams.UA_OVERRIDE_TRUE); |
| 849 | |
| 850 | mContentViewCore.loadUrl(params); |
| 851 | |
| 852 | suppressInterceptionForThisNavigation(); |
| 853 | |
| 854 | // The behavior of WebViewClassic uses the populateVisitedLinks callback in WebKit. |
| 855 | // Chromium does not use this use code path and the best emulation of this behavior to call |
| 856 | // request visited links once on the first URL load of the WebView. |
| 857 | if (!mHasRequestedVisitedHistoryFromClient) { |
| 858 | mHasRequestedVisitedHistoryFromClient = true; |
| 859 | requestVisitedHistoryFromClient(); |
| 860 | } |
| 861 | } |
| 862 | |
| 863 | private void suppressInterceptionForThisNavigation() { |
| 864 | if (mInterceptNavigationDelegate != null) { |
| 865 | // getUrl returns a sanitized address in the same format that will be used for |
| 866 | // callbacks, so it's safe to use string comparison as an equality check later on. |
| 867 | mInterceptNavigationDelegate.onUrlLoadRequested(mContentViewCore.getUrl()); |
| 868 | } |
| 869 | } |
| 870 | |
| 871 | /** |
| 872 | * Get the URL of the current page. |
| 873 | * |
| 874 | * @return The URL of the current page or null if it's empty. |
| 875 | */ |
| 876 | public String getUrl() { |
| 877 | String url = mContentViewCore.getUrl(); |
| 878 | if (url == null || url.trim().isEmpty()) return null; |
| 879 | return url; |
| 880 | } |
| 881 | |
| 882 | public void requestFocus() { |
| 883 | if (mNativeAwContents == 0) return; |
| 884 | if (!mContainerView.isInTouchMode() && mSettings.shouldFocusFirstNode()) { |
| 885 | nativeFocusFirstNode(mNativeAwContents); |
| 886 | } |
| 887 | } |
| 888 | |
| 889 | public void setBackgroundColor(int color) { |
| 890 | mBaseBackgroundColor = color; |
| 891 | if (mNativeAwContents != 0) nativeSetBackgroundColor(mNativeAwContents, color); |
| 892 | } |
| 893 | |
| 894 | private int getEffectiveBackgroundColor() { |
| 895 | // Do not ask the ContentViewCore for the background color, as it will always |
| 896 | // report white prior to initial navigation or post destruction, whereas we want |
| 897 | // to use the client supplied base value in those cases. |
| 898 | if (mNativeAwContents == 0 || !mContentsClient.isCachedRendererBackgroundColorValid()) { |
| 899 | return mBaseBackgroundColor; |
| 900 | } |
| 901 | return mContentsClient.getCachedRendererBackgroundColor(); |
| 902 | } |
| 903 | |
| 904 | public boolean isMultiTouchZoomSupported() { |
| 905 | return mSettings.supportsGestureZoom(); |
| 906 | } |
| 907 | |
| 908 | public View getZoomControlsForTest() { |
| 909 | return mZoomControls.getZoomControlsViewForTest(); |
| 910 | } |
| 911 | |
| 912 | /** |
| 913 | * @see ContentViewCore#getContentSettings() |
| 914 | */ |
| 915 | public ContentSettings getContentSettings() { |
| 916 | return mContentViewCore.getContentSettings(); |
| 917 | } |
| 918 | |
| 919 | /** |
| 920 | * @see View#setOverScrollMode(int) |
| 921 | */ |
| 922 | public void setOverScrollMode(int mode) { |
| 923 | if (mode != View.OVER_SCROLL_NEVER) { |
| 924 | mOverScrollGlow = new OverScrollGlow(mContainerView); |
| 925 | } else { |
| 926 | mOverScrollGlow = null; |
| 927 | } |
| 928 | } |
| 929 | |
| 930 | // TODO(mkosiba): In WebViewClassic these appear in some of the scroll extent calculation |
| 931 | // methods but toggling them has no visiual effect on the content (in other words the scrolling |
| 932 | // code behaves as if the scrollbar-related padding is in place but the onDraw code doesn't |
| 933 | // take that into consideration). |
| 934 | // http://crbug.com/269032 |
| 935 | private boolean mOverlayHorizontalScrollbar = true; |
| 936 | private boolean mOverlayVerticalScrollbar = false; |
| 937 | |
| 938 | /** |
| 939 | * @see View#setScrollBarStyle(int) |
| 940 | */ |
| 941 | public void setScrollBarStyle(int style) { |
| 942 | if (style == View.SCROLLBARS_INSIDE_OVERLAY |
| 943 | || style == View.SCROLLBARS_OUTSIDE_OVERLAY) { |
| 944 | mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; |
| 945 | } else { |
| 946 | mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; |
| 947 | } |
| 948 | } |
| 949 | |
| 950 | /** |
| 951 | * @see View#setHorizontalScrollbarOverlay(boolean) |
| 952 | */ |
| 953 | public void setHorizontalScrollbarOverlay(boolean overlay) { |
| 954 | mOverlayHorizontalScrollbar = overlay; |
| 955 | } |
| 956 | |
| 957 | /** |
| 958 | * @see View#setVerticalScrollbarOverlay(boolean) |
| 959 | */ |
| 960 | public void setVerticalScrollbarOverlay(boolean overlay) { |
| 961 | mOverlayVerticalScrollbar = overlay; |
| 962 | } |
| 963 | |
| 964 | /** |
| 965 | * @see View#overlayHorizontalScrollbar() |
| 966 | */ |
| 967 | public boolean overlayHorizontalScrollbar() { |
| 968 | return mOverlayHorizontalScrollbar; |
| 969 | } |
| 970 | |
| 971 | /** |
| 972 | * @see View#overlayVerticalScrollbar() |
| 973 | */ |
| 974 | public boolean overlayVerticalScrollbar() { |
| 975 | return mOverlayVerticalScrollbar; |
| 976 | } |
| 977 | |
| 978 | /** |
| 979 | * Called by the embedder when the scroll offset of the containing view has changed. |
| 980 | * @see View#onScrollChanged(int,int) |
| 981 | */ |
| 982 | public void onContainerViewScrollChanged(int l, int t, int oldl, int oldt) { |
| 983 | mScrollOffsetManager.onContainerViewScrollChanged(l, t); |
| 984 | } |
| 985 | |
| 986 | /** |
| 987 | * Called by the embedder when the containing view is to be scrolled or overscrolled. |
| 988 | * @see View#onOverScrolled(int,int,int,int) |
| 989 | */ |
| 990 | public void onContainerViewOverScrolled(int scrollX, int scrollY, boolean clampedX, |
| 991 | boolean clampedY) { |
| 992 | int oldX = mContainerView.getScrollX(); |
| 993 | int oldY = mContainerView.getScrollY(); |
| 994 | |
| 995 | mScrollOffsetManager.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY); |
| 996 | |
| 997 | if (mOverScrollGlow != null) { |
| 998 | mOverScrollGlow.pullGlow(mContainerView.getScrollX(), mContainerView.getScrollY(), |
| 999 | oldX, oldY, |
| 1000 | mScrollOffsetManager.computeMaximumHorizontalScrollOffset(), |
| 1001 | mScrollOffsetManager.computeMaximumVerticalScrollOffset()); |
| 1002 | } |
| 1003 | } |
| 1004 | |
| 1005 | /** |
| 1006 | * @see WebView#requestChildRectangleOnScreen(View, Rect, boolean) |
| 1007 | */ |
| 1008 | public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate) { |
| 1009 | return mScrollOffsetManager.requestChildRectangleOnScreen( |
| 1010 | child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY(), |
| 1011 | rect, immediate); |
| 1012 | } |
| 1013 | |
| 1014 | /** |
| 1015 | * @see View.computeScroll() |
| 1016 | */ |
| 1017 | public void computeScroll() { |
| 1018 | mScrollOffsetManager.computeScrollAndAbsorbGlow(mOverScrollGlow); |
| 1019 | } |
| 1020 | |
| 1021 | /** |
| 1022 | * @see View#computeHorizontalScrollRange() |
| 1023 | */ |
| 1024 | public int computeHorizontalScrollRange() { |
| 1025 | return mScrollOffsetManager.computeHorizontalScrollRange(); |
| 1026 | } |
| 1027 | |
| 1028 | /** |
| 1029 | * @see View#computeHorizontalScrollOffset() |
| 1030 | */ |
| 1031 | public int computeHorizontalScrollOffset() { |
| 1032 | return mScrollOffsetManager.computeHorizontalScrollOffset(); |
| 1033 | } |
| 1034 | |
| 1035 | /** |
| 1036 | * @see View#computeVerticalScrollRange() |
| 1037 | */ |
| 1038 | public int computeVerticalScrollRange() { |
| 1039 | return mScrollOffsetManager.computeVerticalScrollRange(); |
| 1040 | } |
| 1041 | |
| 1042 | /** |
| 1043 | * @see View#computeVerticalScrollOffset() |
| 1044 | */ |
| 1045 | public int computeVerticalScrollOffset() { |
| 1046 | return mScrollOffsetManager.computeVerticalScrollOffset(); |
| 1047 | } |
| 1048 | |
| 1049 | /** |
| 1050 | * @see View#computeVerticalScrollExtent() |
| 1051 | */ |
| 1052 | public int computeVerticalScrollExtent() { |
| 1053 | return mScrollOffsetManager.computeVerticalScrollExtent(); |
| 1054 | } |
| 1055 | |
| 1056 | /** |
| 1057 | * @see android.webkit.WebView#stopLoading() |
| 1058 | */ |
| 1059 | public void stopLoading() { |
| 1060 | mContentViewCore.stopLoading(); |
| 1061 | } |
| 1062 | |
| 1063 | /** |
| 1064 | * @see android.webkit.WebView#reload() |
| 1065 | */ |
| 1066 | public void reload() { |
| 1067 | mContentViewCore.reload(); |
| 1068 | } |
| 1069 | |
| 1070 | /** |
| 1071 | * @see android.webkit.WebView#canGoBack() |
| 1072 | */ |
| 1073 | public boolean canGoBack() { |
| 1074 | return mContentViewCore.canGoBack(); |
| 1075 | } |
| 1076 | |
| 1077 | /** |
| 1078 | * @see android.webkit.WebView#goBack() |
| 1079 | */ |
| 1080 | public void goBack() { |
| 1081 | mContentViewCore.goBack(); |
| 1082 | |
| 1083 | suppressInterceptionForThisNavigation(); |
| 1084 | } |
| 1085 | |
| 1086 | /** |
| 1087 | * @see android.webkit.WebView#canGoForward() |
| 1088 | */ |
| 1089 | public boolean canGoForward() { |
| 1090 | return mContentViewCore.canGoForward(); |
| 1091 | } |
| 1092 | |
| 1093 | /** |
| 1094 | * @see android.webkit.WebView#goForward() |
| 1095 | */ |
| 1096 | public void goForward() { |
| 1097 | mContentViewCore.goForward(); |
| 1098 | |
| 1099 | suppressInterceptionForThisNavigation(); |
| 1100 | } |
| 1101 | |
| 1102 | /** |
| 1103 | * @see android.webkit.WebView#canGoBackOrForward(int) |
| 1104 | */ |
| 1105 | public boolean canGoBackOrForward(int steps) { |
| 1106 | return mContentViewCore.canGoToOffset(steps); |
| 1107 | } |
| 1108 | |
| 1109 | /** |
| 1110 | * @see android.webkit.WebView#goBackOrForward(int) |
| 1111 | */ |
| 1112 | public void goBackOrForward(int steps) { |
| 1113 | mContentViewCore.goToOffset(steps); |
| 1114 | |
| 1115 | suppressInterceptionForThisNavigation(); |
| 1116 | } |
| 1117 | |
| 1118 | /** |
| 1119 | * @see android.webkit.WebView#pauseTimers() |
| 1120 | */ |
| 1121 | public void pauseTimers() { |
| 1122 | ContentViewStatics.setWebKitSharedTimersSuspended(true); |
| 1123 | } |
| 1124 | |
| 1125 | /** |
| 1126 | * @see android.webkit.WebView#resumeTimers() |
| 1127 | */ |
| 1128 | public void resumeTimers() { |
| 1129 | ContentViewStatics.setWebKitSharedTimersSuspended(false); |
| 1130 | } |
| 1131 | |
| 1132 | /** |
| 1133 | * @see android.webkit.WebView#onPause() |
| 1134 | */ |
| 1135 | public void onPause() { |
| 1136 | if (mIsPaused || mNativeAwContents == 0) return; |
| 1137 | mIsPaused = true; |
| 1138 | nativeSetIsPaused(mNativeAwContents, mIsPaused); |
| 1139 | } |
| 1140 | |
| 1141 | /** |
| 1142 | * @see android.webkit.WebView#onResume() |
| 1143 | */ |
| 1144 | public void onResume() { |
| 1145 | if (!mIsPaused || mNativeAwContents == 0) return; |
| 1146 | mIsPaused = false; |
| 1147 | nativeSetIsPaused(mNativeAwContents, mIsPaused); |
| 1148 | } |
| 1149 | |
| 1150 | /** |
| 1151 | * @see android.webkit.WebView#isPaused() |
| 1152 | */ |
| 1153 | public boolean isPaused() { |
| 1154 | return mIsPaused; |
| 1155 | } |
| 1156 | |
| 1157 | /** |
| 1158 | * @see android.webkit.WebView#onCreateInputConnection(EditorInfo) |
| 1159 | */ |
| 1160 | public InputConnection onCreateInputConnection(EditorInfo outAttrs) { |
| 1161 | return mContentViewCore.onCreateInputConnection(outAttrs); |
| 1162 | } |
| 1163 | |
| 1164 | /** |
| 1165 | * @see android.webkit.WebView#onKeyUp(int, KeyEvent) |
| 1166 | */ |
| 1167 | public boolean onKeyUp(int keyCode, KeyEvent event) { |
| 1168 | return mContentViewCore.onKeyUp(keyCode, event); |
| 1169 | } |
| 1170 | |
| 1171 | /** |
| 1172 | * @see android.webkit.WebView#dispatchKeyEvent(KeyEvent) |
| 1173 | */ |
| 1174 | public boolean dispatchKeyEvent(KeyEvent event) { |
| 1175 | return mContentViewCore.dispatchKeyEvent(event); |
| 1176 | } |
| 1177 | |
| 1178 | /** |
| 1179 | * Clears the resource cache. Note that the cache is per-application, so this will clear the |
| 1180 | * cache for all WebViews used. |
| 1181 | * |
| 1182 | * @param includeDiskFiles if false, only the RAM cache is cleared |
| 1183 | */ |
| 1184 | public void clearCache(boolean includeDiskFiles) { |
| 1185 | if (mNativeAwContents == 0) return; |
| 1186 | nativeClearCache(mNativeAwContents, includeDiskFiles); |
| 1187 | } |
| 1188 | |
| 1189 | public void documentHasImages(Message message) { |
| 1190 | if (mNativeAwContents == 0) return; |
| 1191 | nativeDocumentHasImages(mNativeAwContents, message); |
| 1192 | } |
| 1193 | |
| 1194 | public void saveWebArchive( |
| 1195 | final String basename, boolean autoname, final ValueCallback<String> callback) { |
| 1196 | if (!autoname) { |
| 1197 | saveWebArchiveInternal(basename, callback); |
| 1198 | return; |
| 1199 | } |
| 1200 | // If auto-generating the file name, handle the name generation on a background thread |
| 1201 | // as it will require I/O access for checking whether previous files existed. |
| 1202 | new AsyncTask<Void, Void, String>() { |
| 1203 | @Override |
| 1204 | protected String doInBackground(Void... params) { |
| 1205 | return generateArchiveAutoNamePath(getOriginalUrl(), basename); |
| 1206 | } |
| 1207 | |
| 1208 | @Override |
| 1209 | protected void onPostExecute(String result) { |
| 1210 | saveWebArchiveInternal(result, callback); |
| 1211 | } |
| 1212 | }.execute(); |
| 1213 | } |
| 1214 | |
| 1215 | public String getOriginalUrl() { |
| 1216 | NavigationHistory history = mContentViewCore.getNavigationHistory(); |
| 1217 | int currentIndex = history.getCurrentEntryIndex(); |
| 1218 | if (currentIndex >= 0 && currentIndex < history.getEntryCount()) { |
| 1219 | return history.getEntryAtIndex(currentIndex).getOriginalUrl(); |
| 1220 | } |
| 1221 | return null; |
| 1222 | } |
| 1223 | |
| 1224 | /** |
| 1225 | * @see ContentViewCore#getNavigationHistory() |
| 1226 | */ |
| 1227 | public NavigationHistory getNavigationHistory() { |
| 1228 | return mContentViewCore.getNavigationHistory(); |
| 1229 | } |
| 1230 | |
| 1231 | /** |
| 1232 | * @see android.webkit.WebView#getTitle() |
| 1233 | */ |
| 1234 | public String getTitle() { |
| 1235 | return mContentViewCore.getTitle(); |
| 1236 | } |
| 1237 | |
| 1238 | /** |
| 1239 | * @see android.webkit.WebView#clearHistory() |
| 1240 | */ |
| 1241 | public void clearHistory() { |
| 1242 | mContentViewCore.clearHistory(); |
| 1243 | } |
| 1244 | |
| 1245 | public String[] getHttpAuthUsernamePassword(String host, String realm) { |
| 1246 | return mBrowserContext.getHttpAuthDatabase(mContentViewCore.getContext()) |
| 1247 | .getHttpAuthUsernamePassword(host, realm); |
| 1248 | } |
| 1249 | |
| 1250 | public void setHttpAuthUsernamePassword(String host, String realm, String username, |
| 1251 | String password) { |
| 1252 | mBrowserContext.getHttpAuthDatabase(mContentViewCore.getContext()) |
| 1253 | .setHttpAuthUsernamePassword(host, realm, username, password); |
| 1254 | } |
| 1255 | |
| 1256 | /** |
| 1257 | * @see android.webkit.WebView#getCertificate() |
| 1258 | */ |
| 1259 | public SslCertificate getCertificate() { |
| 1260 | if (mNativeAwContents == 0) return null; |
| 1261 | return SslUtil.getCertificateFromDerBytes(nativeGetCertificate(mNativeAwContents)); |
| 1262 | } |
| 1263 | |
| 1264 | /** |
| 1265 | * @see android.webkit.WebView#clearSslPreferences() |
| 1266 | */ |
| 1267 | public void clearSslPreferences() { |
| 1268 | mContentViewCore.clearSslPreferences(); |
| 1269 | } |
| 1270 | |
| 1271 | /** |
| 1272 | * Method to return all hit test values relevant to public WebView API. |
| 1273 | * Note that this expose more data than needed for WebView.getHitTestResult. |
| 1274 | * Unsafely returning reference to mutable internal object to avoid excessive |
| 1275 | * garbage allocation on repeated calls. |
| 1276 | */ |
| 1277 | public HitTestData getLastHitTestResult() { |
| 1278 | if (mNativeAwContents == 0) return null; |
| 1279 | nativeUpdateLastHitTestData(mNativeAwContents); |
| 1280 | return mPossiblyStaleHitTestData; |
| 1281 | } |
| 1282 | |
| 1283 | /** |
| 1284 | * @see android.webkit.WebView#requestFocusNodeHref() |
| 1285 | */ |
| 1286 | public void requestFocusNodeHref(Message msg) { |
| 1287 | if (msg == null || mNativeAwContents == 0) return; |
| 1288 | |
| 1289 | nativeUpdateLastHitTestData(mNativeAwContents); |
| 1290 | Bundle data = msg.getData(); |
| 1291 | data.putString("url", mPossiblyStaleHitTestData.href); |
| 1292 | data.putString("title", mPossiblyStaleHitTestData.anchorText); |
| 1293 | data.putString("src", mPossiblyStaleHitTestData.imgSrc); |
| 1294 | msg.setData(data); |
| 1295 | msg.sendToTarget(); |
| 1296 | } |
| 1297 | |
| 1298 | /** |
| 1299 | * @see android.webkit.WebView#requestImageRef() |
| 1300 | */ |
| 1301 | public void requestImageRef(Message msg) { |
| 1302 | if (msg == null || mNativeAwContents == 0) return; |
| 1303 | |
| 1304 | nativeUpdateLastHitTestData(mNativeAwContents); |
| 1305 | Bundle data = msg.getData(); |
| 1306 | data.putString("url", mPossiblyStaleHitTestData.imgSrc); |
| 1307 | msg.setData(data); |
| 1308 | msg.sendToTarget(); |
| 1309 | } |
| 1310 | |
| 1311 | /** |
| 1312 | * @see android.webkit.WebView#getScale() |
| 1313 | * |
| 1314 | * Please note that the scale returned is the page scale multiplied by |
| 1315 | * the screen density factor. See CTS WebViewTest.testSetInitialScale. |
| 1316 | */ |
| 1317 | public float getScale() { |
| 1318 | return (float)(mContentViewCore.getScale() * mDIPScale); |
| 1319 | } |
| 1320 | |
| 1321 | /** |
| 1322 | * @see android.webkit.WebView#flingScroll(int, int) |
| 1323 | */ |
| 1324 | public void flingScroll(int velocityX, int velocityY) { |
| 1325 | mScrollOffsetManager.flingScroll(velocityX, velocityY); |
| 1326 | } |
| 1327 | |
| 1328 | /** |
| 1329 | * @see android.webkit.WebView#pageUp(boolean) |
| 1330 | */ |
| 1331 | public boolean pageUp(boolean top) { |
| 1332 | return mScrollOffsetManager.pageUp(top); |
| 1333 | } |
| 1334 | |
| 1335 | /** |
| 1336 | * @see android.webkit.WebView#pageDown(boolean) |
| 1337 | */ |
| 1338 | public boolean pageDown(boolean bottom) { |
| 1339 | return mScrollOffsetManager.pageDown(bottom); |
| 1340 | } |
| 1341 | |
| 1342 | /** |
| 1343 | * @see android.webkit.WebView#canZoomIn() |
| 1344 | */ |
| 1345 | public boolean canZoomIn() { |
| 1346 | return mContentViewCore.canZoomIn(); |
| 1347 | } |
| 1348 | |
| 1349 | /** |
| 1350 | * @see android.webkit.WebView#canZoomOut() |
| 1351 | */ |
| 1352 | public boolean canZoomOut() { |
| 1353 | return mContentViewCore.canZoomOut(); |
| 1354 | } |
| 1355 | |
| 1356 | /** |
| 1357 | * @see android.webkit.WebView#zoomIn() |
| 1358 | */ |
| 1359 | public boolean zoomIn() { |
| 1360 | return mContentViewCore.zoomIn(); |
| 1361 | } |
| 1362 | |
| 1363 | /** |
| 1364 | * @see android.webkit.WebView#zoomOut() |
| 1365 | */ |
| 1366 | public boolean zoomOut() { |
| 1367 | return mContentViewCore.zoomOut(); |
| 1368 | } |
| 1369 | |
| 1370 | /** |
| 1371 | * @see android.webkit.WebView#invokeZoomPicker() |
| 1372 | */ |
| 1373 | public void invokeZoomPicker() { |
| 1374 | mContentViewCore.invokeZoomPicker(); |
| 1375 | } |
| 1376 | |
| 1377 | /** |
| 1378 | * @see ContentViewCore.evaluateJavaScript(String, ContentViewCore.JavaScriptCallback) |
| 1379 | */ |
| 1380 | public void evaluateJavaScript(String script, final ValueCallback<String> callback) { |
| 1381 | ContentViewCore.JavaScriptCallback jsCallback = null; |
| 1382 | if (callback != null) { |
| 1383 | jsCallback = new ContentViewCore.JavaScriptCallback() { |
| 1384 | @Override |
| 1385 | public void handleJavaScriptResult(String jsonResult) { |
| 1386 | callback.onReceiveValue(jsonResult); |
| 1387 | } |
| 1388 | }; |
| 1389 | } |
| 1390 | |
| 1391 | mContentViewCore.evaluateJavaScript(script, jsCallback); |
| 1392 | } |
| 1393 | |
| 1394 | /** |
| 1395 | * @see ContentViewCore.evaluateJavaScriptEvenIfNotYetNavigated(String) |
| 1396 | */ |
| 1397 | public void evaluateJavaScriptEvenIfNotYetNavigated(String script) { |
| 1398 | mContentViewCore.evaluateJavaScriptEvenIfNotYetNavigated(script); |
| 1399 | } |
| 1400 | |
| 1401 | //-------------------------------------------------------------------------------------------- |
| 1402 | // View and ViewGroup method implementations |
| 1403 | //-------------------------------------------------------------------------------------------- |
| 1404 | |
| 1405 | /** |
| 1406 | * @see android.webkit.View#onTouchEvent() |
| 1407 | */ |
| 1408 | public boolean onTouchEvent(MotionEvent event) { |
| 1409 | if (mNativeAwContents == 0) return false; |
| 1410 | |
| 1411 | mScrollOffsetManager.setProcessingTouchEvent(true); |
| 1412 | boolean rv = mContentViewCore.onTouchEvent(event); |
| 1413 | mScrollOffsetManager.setProcessingTouchEvent(false); |
| 1414 | |
| 1415 | if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { |
| 1416 | int actionIndex = event.getActionIndex(); |
| 1417 | |
| 1418 | // Note this will trigger IPC back to browser even if nothing is hit. |
| 1419 | nativeRequestNewHitTestDataAt(mNativeAwContents, |
| 1420 | (int)Math.round(event.getX(actionIndex) / mDIPScale), |
| 1421 | (int)Math.round(event.getY(actionIndex) / mDIPScale)); |
| 1422 | } |
| 1423 | |
| 1424 | if (mOverScrollGlow != null && event.getActionMasked() == MotionEvent.ACTION_UP) { |
| 1425 | mOverScrollGlow.releaseAll(); |
| 1426 | } |
| 1427 | |
| 1428 | return rv; |
| 1429 | } |
| 1430 | |
| 1431 | /** |
| 1432 | * @see android.view.View#onHoverEvent() |
| 1433 | */ |
| 1434 | public boolean onHoverEvent(MotionEvent event) { |
| 1435 | return mContentViewCore.onHoverEvent(event); |
| 1436 | } |
| 1437 | |
| 1438 | /** |
| 1439 | * @see android.view.View#onGenericMotionEvent() |
| 1440 | */ |
| 1441 | public boolean onGenericMotionEvent(MotionEvent event) { |
| 1442 | return mContentViewCore.onGenericMotionEvent(event); |
| 1443 | } |
| 1444 | |
| 1445 | /** |
| 1446 | * @see android.view.View#onConfigurationChanged() |
| 1447 | */ |
| 1448 | public void onConfigurationChanged(Configuration newConfig) { |
| 1449 | mContentViewCore.onConfigurationChanged(newConfig); |
| 1450 | } |
| 1451 | |
| 1452 | /** |
| 1453 | * @see android.view.View#onAttachedToWindow() |
| 1454 | * |
| 1455 | * Note that this is also called from receivePopupContents. |
| 1456 | */ |
| 1457 | public void onAttachedToWindow() { |
| 1458 | if (mNativeAwContents == 0) return; |
| 1459 | mIsAttachedToWindow = true; |
| 1460 | |
| 1461 | mContentViewCore.onAttachedToWindow(); |
| 1462 | nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(), |
| 1463 | mContainerView.getHeight()); |
| 1464 | } |
| 1465 | |
| 1466 | /** |
| 1467 | * @see android.view.View#onDetachedFromWindow() |
| 1468 | */ |
| 1469 | public void onDetachedFromWindow() { |
| 1470 | mIsAttachedToWindow = false; |
| 1471 | hideAutofillPopup(); |
| 1472 | if (mNativeAwContents != 0) { |
| 1473 | nativeOnDetachedFromWindow(mNativeAwContents); |
| 1474 | } |
| 1475 | |
| 1476 | mContentViewCore.onDetachedFromWindow(); |
| 1477 | |
| 1478 | if (mPendingDetachCleanupReferences != null) { |
| 1479 | for (int i = 0; i < mPendingDetachCleanupReferences.size(); ++i) { |
| 1480 | mPendingDetachCleanupReferences.get(i).cleanupNow(); |
| 1481 | } |
| 1482 | mPendingDetachCleanupReferences = null; |
| 1483 | } |
| 1484 | } |
| 1485 | |
| 1486 | /** |
| 1487 | * @see android.view.View#onWindowFocusChanged() |
| 1488 | */ |
| 1489 | public void onWindowFocusChanged(boolean hasWindowFocus) { |
| 1490 | mWindowFocused = hasWindowFocus; |
| 1491 | mContentViewCore.onFocusChanged(mContainerViewFocused && mWindowFocused); |
| 1492 | } |
| 1493 | |
| 1494 | /** |
| 1495 | * @see android.view.View#onFocusChanged() |
| 1496 | */ |
| 1497 | public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { |
| 1498 | mContainerViewFocused = focused; |
| 1499 | mContentViewCore.onFocusChanged(mContainerViewFocused && mWindowFocused); |
| 1500 | } |
| 1501 | |
| 1502 | /** |
| 1503 | * @see android.view.View#onSizeChanged() |
| 1504 | */ |
| 1505 | public void onSizeChanged(int w, int h, int ow, int oh) { |
| 1506 | if (mNativeAwContents == 0) return; |
| 1507 | mScrollOffsetManager.setContainerViewSize(w, h); |
| 1508 | mContentViewCore.onPhysicalBackingSizeChanged(w, h); |
| 1509 | mContentViewCore.onSizeChanged(w, h, ow, oh); |
| 1510 | nativeOnSizeChanged(mNativeAwContents, w, h, ow, oh); |
| 1511 | } |
| 1512 | |
| 1513 | /** |
| 1514 | * @see android.view.View#onVisibilityChanged() |
| 1515 | */ |
| 1516 | public void onVisibilityChanged(View changedView, int visibility) { |
| 1517 | boolean viewVisible = mContainerView.getVisibility() == View.VISIBLE; |
| 1518 | if (mIsViewVisible == viewVisible) return; |
| 1519 | setViewVisibilityInternal(viewVisible); |
| 1520 | } |
| 1521 | |
| 1522 | /** |
| 1523 | * @see android.view.View#onWindowVisibilityChanged() |
| 1524 | */ |
| 1525 | public void onWindowVisibilityChanged(int visibility) { |
| 1526 | boolean windowVisible = visibility == View.VISIBLE; |
| 1527 | if (mIsWindowVisible == windowVisible) return; |
| 1528 | setWindowVisibilityInternal(windowVisible); |
| 1529 | } |
| 1530 | |
| 1531 | private void setViewVisibilityInternal(boolean visible) { |
| 1532 | mIsViewVisible = visible; |
| 1533 | if (mNativeAwContents == 0) return; |
| 1534 | nativeSetViewVisibility(mNativeAwContents, mIsViewVisible); |
| 1535 | } |
| 1536 | |
| 1537 | private void setWindowVisibilityInternal(boolean visible) { |
| 1538 | mIsWindowVisible = visible; |
| 1539 | if (mNativeAwContents == 0) return; |
| 1540 | nativeSetWindowVisibility(mNativeAwContents, mIsWindowVisible); |
| 1541 | } |
| 1542 | |
| 1543 | /** |
| 1544 | * Key for opaque state in bundle. Note this is only public for tests. |
| 1545 | */ |
| 1546 | public static final String SAVE_RESTORE_STATE_KEY = "WEBVIEW_CHROMIUM_STATE"; |
| 1547 | |
| 1548 | /** |
| 1549 | * Save the state of this AwContents into provided Bundle. |
| 1550 | * @return False if saving state failed. |
| 1551 | */ |
| 1552 | public boolean saveState(Bundle outState) { |
| 1553 | if (mNativeAwContents == 0 || outState == null) return false; |
| 1554 | |
| 1555 | byte[] state = nativeGetOpaqueState(mNativeAwContents); |
| 1556 | if (state == null) return false; |
| 1557 | |
| 1558 | outState.putByteArray(SAVE_RESTORE_STATE_KEY, state); |
| 1559 | return true; |
| 1560 | } |
| 1561 | |
| 1562 | /** |
| 1563 | * Restore the state of this AwContents into provided Bundle. |
| 1564 | * @param inState Must be a bundle returned by saveState. |
| 1565 | * @return False if restoring state failed. |
| 1566 | */ |
| 1567 | public boolean restoreState(Bundle inState) { |
| 1568 | if (mNativeAwContents == 0 || inState == null) return false; |
| 1569 | |
| 1570 | byte[] state = inState.getByteArray(SAVE_RESTORE_STATE_KEY); |
| 1571 | if (state == null) return false; |
| 1572 | |
| 1573 | boolean result = nativeRestoreFromOpaqueState(mNativeAwContents, state); |
| 1574 | |
| 1575 | // The onUpdateTitle callback normally happens when a page is loaded, |
| 1576 | // but is optimized out in the restoreState case because the title is |
| 1577 | // already restored. See WebContentsImpl::UpdateTitleForEntry. So we |
| 1578 | // call the callback explicitly here. |
| 1579 | if (result) mContentsClient.onReceivedTitle(mContentViewCore.getTitle()); |
| 1580 | |
| 1581 | return result; |
| 1582 | } |
| 1583 | |
| 1584 | /** |
| 1585 | * @see ContentViewCore#addPossiblyUnsafeJavascriptInterface(Object, String, Class) |
| 1586 | */ |
| 1587 | public void addPossiblyUnsafeJavascriptInterface(Object object, String name, |
| 1588 | Class<? extends Annotation> requiredAnnotation) { |
| 1589 | mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation); |
| 1590 | } |
| 1591 | |
| 1592 | /** |
| 1593 | * @see android.webkit.WebView#removeJavascriptInterface(String) |
| 1594 | */ |
| 1595 | public void removeJavascriptInterface(String interfaceName) { |
| 1596 | mContentViewCore.removeJavascriptInterface(interfaceName); |
| 1597 | } |
| 1598 | |
| 1599 | /** |
| 1600 | * If native accessibility (not script injection) is enabled, and if this is |
| 1601 | * running on JellyBean or later, returns an AccessibilityNodeProvider that |
| 1602 | * implements native accessibility for this view. Returns null otherwise. |
| 1603 | * @return The AccessibilityNodeProvider, if available, or null otherwise. |
| 1604 | */ |
| 1605 | public AccessibilityNodeProvider getAccessibilityNodeProvider() { |
| 1606 | return mContentViewCore.getAccessibilityNodeProvider(); |
| 1607 | } |
| 1608 | |
| 1609 | /** |
| 1610 | * @see android.webkit.WebView#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) |
| 1611 | */ |
| 1612 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { |
| 1613 | mContentViewCore.onInitializeAccessibilityNodeInfo(info); |
| 1614 | } |
| 1615 | |
| 1616 | /** |
| 1617 | * @see android.webkit.WebView#onInitializeAccessibilityEvent(AccessibilityEvent) |
| 1618 | */ |
| 1619 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { |
| 1620 | mContentViewCore.onInitializeAccessibilityEvent(event); |
| 1621 | } |
| 1622 | |
| 1623 | public boolean supportsAccessibilityAction(int action) { |
| 1624 | return mContentViewCore.supportsAccessibilityAction(action); |
| 1625 | } |
| 1626 | |
| 1627 | /** |
| 1628 | * @see android.webkit.WebView#performAccessibilityAction(int, Bundle) |
| 1629 | */ |
| 1630 | public boolean performAccessibilityAction(int action, Bundle arguments) { |
| 1631 | return mContentViewCore.performAccessibilityAction(action, arguments); |
| 1632 | } |
| 1633 | |
| 1634 | /** |
| 1635 | * @see android.webkit.WebView#clearFormData() |
| 1636 | */ |
| 1637 | public void hideAutofillPopup() { |
| 1638 | if (mAwAutofillManagerDelegate != null) |
| 1639 | mAwAutofillManagerDelegate.hideAutofillPopup(); |
| 1640 | } |
| 1641 | |
| 1642 | //-------------------------------------------------------------------------------------------- |
| 1643 | // Methods called from native via JNI |
| 1644 | //-------------------------------------------------------------------------------------------- |
| 1645 | |
| 1646 | @CalledByNative |
| 1647 | private static void onDocumentHasImagesResponse(boolean result, Message message) { |
| 1648 | message.arg1 = result ? 1 : 0; |
| 1649 | message.sendToTarget(); |
| 1650 | } |
| 1651 | |
| 1652 | @CalledByNative |
| 1653 | private void onReceivedTouchIconUrl(String url, boolean precomposed) { |
| 1654 | mContentsClient.onReceivedTouchIconUrl(url, precomposed); |
| 1655 | } |
| 1656 | |
| 1657 | @CalledByNative |
| 1658 | private void onReceivedIcon(Bitmap bitmap) { |
| 1659 | mContentsClient.onReceivedIcon(bitmap); |
| 1660 | mFavicon = bitmap; |
| 1661 | } |
| 1662 | |
| 1663 | /** Callback for generateMHTML. */ |
| 1664 | @CalledByNative |
| 1665 | private static void generateMHTMLCallback( |
| 1666 | String path, long size, ValueCallback<String> callback) { |
| 1667 | if (callback == null) return; |
| 1668 | callback.onReceiveValue(size < 0 ? null : path); |
| 1669 | } |
| 1670 | |
| 1671 | @CalledByNative |
| 1672 | private void onReceivedHttpAuthRequest(AwHttpAuthHandler handler, String host, String realm) { |
| 1673 | mContentsClient.onReceivedHttpAuthRequest(handler, host, realm); |
| 1674 | } |
| 1675 | |
| 1676 | private class AwGeolocationCallback implements GeolocationPermissions.Callback { |
| 1677 | |
| 1678 | @Override |
| 1679 | public void invoke(final String origin, final boolean allow, final boolean retain) { |
| 1680 | ThreadUtils.runOnUiThread(new Runnable() { |
| 1681 | @Override |
| 1682 | public void run() { |
| 1683 | if (retain) { |
| 1684 | if (allow) { |
| 1685 | mBrowserContext.getGeolocationPermissions().allow(origin); |
| 1686 | } else { |
| 1687 | mBrowserContext.getGeolocationPermissions().deny(origin); |
| 1688 | } |
| 1689 | } |
| 1690 | if (mNativeAwContents == 0) return; |
| 1691 | nativeInvokeGeolocationCallback(mNativeAwContents, allow, origin); |
| 1692 | } |
| 1693 | }); |
| 1694 | } |
| 1695 | } |
| 1696 | |
| 1697 | @CalledByNative |
| 1698 | private void onGeolocationPermissionsShowPrompt(String origin) { |
| 1699 | if (mNativeAwContents == 0) return; |
| 1700 | AwGeolocationPermissions permissions = mBrowserContext.getGeolocationPermissions(); |
| 1701 | // Reject if geoloaction is disabled, or the origin has a retained deny |
| 1702 | if (!mSettings.getGeolocationEnabled()) { |
| 1703 | nativeInvokeGeolocationCallback(mNativeAwContents, false, origin); |
| 1704 | return; |
| 1705 | } |
| 1706 | // Allow if the origin has a retained allow |
| 1707 | if (permissions.hasOrigin(origin)) { |
| 1708 | nativeInvokeGeolocationCallback(mNativeAwContents, permissions.isOriginAllowed(origin), |
| 1709 | origin); |
| 1710 | return; |
| 1711 | } |
| 1712 | mContentsClient.onGeolocationPermissionsShowPrompt( |
| 1713 | origin, new AwGeolocationCallback()); |
| 1714 | } |
| 1715 | |
| 1716 | @CalledByNative |
| 1717 | private void onGeolocationPermissionsHidePrompt() { |
| 1718 | mContentsClient.onGeolocationPermissionsHidePrompt(); |
| 1719 | } |
| 1720 | |
| 1721 | @CalledByNative |
| 1722 | public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, |
| 1723 | boolean isDoneCounting) { |
| 1724 | mContentsClient.onFindResultReceived(activeMatchOrdinal, numberOfMatches, isDoneCounting); |
| 1725 | } |
| 1726 | |
| 1727 | @CalledByNative |
| 1728 | public void onNewPicture() { |
| 1729 | // Don't call capturePicture() here but instead defer it until the posted task runs within |
| 1730 | // the callback helper, to avoid doubling back into the renderer compositor in the middle |
| 1731 | // of the notification it is sending up to here. |
| 1732 | mContentsClient.getCallbackHelper().postOnNewPicture(mPictureListenerContentProvider); |
| 1733 | } |
| 1734 | |
| 1735 | // Called as a result of nativeUpdateLastHitTestData. |
| 1736 | @CalledByNative |
| 1737 | private void updateHitTestData( |
| 1738 | int type, String extra, String href, String anchorText, String imgSrc) { |
| 1739 | mPossiblyStaleHitTestData.hitTestResultType = type; |
| 1740 | mPossiblyStaleHitTestData.hitTestResultExtraData = extra; |
| 1741 | mPossiblyStaleHitTestData.href = href; |
| 1742 | mPossiblyStaleHitTestData.anchorText = anchorText; |
| 1743 | mPossiblyStaleHitTestData.imgSrc = imgSrc; |
| 1744 | } |
| 1745 | |
| 1746 | @CalledByNative |
| 1747 | private boolean requestDrawGL(Canvas canvas) { |
| 1748 | return mInternalAccessAdapter.requestDrawGL(canvas); |
| 1749 | } |
| 1750 | |
| 1751 | private static final boolean SUPPORTS_ON_ANIMATION = |
| 1752 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; |
| 1753 | |
| 1754 | @CalledByNative |
| 1755 | private void postInvalidateOnAnimation() { |
| 1756 | if (SUPPORTS_ON_ANIMATION) { |
| 1757 | mContainerView.postInvalidateOnAnimation(); |
| 1758 | } else { |
| 1759 | mContainerView.postInvalidate(); |
| 1760 | } |
| 1761 | } |
| 1762 | |
| 1763 | @CalledByNative |
| 1764 | private boolean performLongClick() { |
| 1765 | return mContainerView.performLongClick(); |
| 1766 | } |
| 1767 | |
| 1768 | @CalledByNative |
| 1769 | private int[] getLocationOnScreen() { |
| 1770 | int[] result = new int[2]; |
| 1771 | mContainerView.getLocationOnScreen(result); |
| 1772 | return result; |
| 1773 | } |
| 1774 | |
| 1775 | @CalledByNative |
| 1776 | private void onWebLayoutPageScaleFactorChanged(float webLayoutPageScaleFactor) { |
| 1777 | // This change notification comes from the renderer thread, not from the cc/ impl thread. |
| 1778 | mLayoutSizer.onPageScaleChanged(webLayoutPageScaleFactor); |
| 1779 | } |
| 1780 | |
| 1781 | @CalledByNative |
| 1782 | private void scrollContainerViewTo(int x, int y) { |
| 1783 | mScrollOffsetManager.scrollContainerViewTo(x, y); |
| 1784 | } |
| 1785 | |
| 1786 | @CalledByNative |
| 1787 | private void setAwAutofillManagerDelegate(AwAutofillManagerDelegate delegate) { |
| 1788 | mAwAutofillManagerDelegate = delegate; |
| 1789 | delegate.init(mContentViewCore); |
| 1790 | } |
| 1791 | |
| 1792 | @CalledByNative |
| 1793 | private void didOverscroll(int deltaX, int deltaY) { |
| 1794 | if (mOverScrollGlow != null) { |
| 1795 | mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY); |
| 1796 | } |
| 1797 | |
| 1798 | mScrollOffsetManager.overScrollBy(deltaX, deltaY); |
| 1799 | |
| 1800 | if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) { |
| 1801 | mContainerView.invalidate(); |
| 1802 | } |
| 1803 | } |
| 1804 | |
| 1805 | // ------------------------------------------------------------------------------------------- |
| 1806 | // Helper methods |
| 1807 | // ------------------------------------------------------------------------------------------- |
| 1808 | |
| 1809 | private void saveWebArchiveInternal(String path, final ValueCallback<String> callback) { |
| 1810 | if (path == null || mNativeAwContents == 0) { |
| 1811 | ThreadUtils.runOnUiThread(new Runnable() { |
| 1812 | @Override |
| 1813 | public void run() { |
| 1814 | callback.onReceiveValue(null); |
| 1815 | } |
| 1816 | }); |
| 1817 | } else { |
| 1818 | nativeGenerateMHTML(mNativeAwContents, path, callback); |
| 1819 | } |
| 1820 | } |
| 1821 | |
| 1822 | /** |
| 1823 | * Try to generate a pathname for saving an MHTML archive. This roughly follows WebView's |
| 1824 | * autoname logic. |
| 1825 | */ |
| 1826 | private static String generateArchiveAutoNamePath(String originalUrl, String baseName) { |
| 1827 | String name = null; |
| 1828 | if (originalUrl != null && !originalUrl.isEmpty()) { |
| 1829 | try { |
| 1830 | String path = new URL(originalUrl).getPath(); |
| 1831 | int lastSlash = path.lastIndexOf('/'); |
| 1832 | if (lastSlash > 0) { |
| 1833 | name = path.substring(lastSlash + 1); |
| 1834 | } else { |
| 1835 | name = path; |
| 1836 | } |
| 1837 | } catch (MalformedURLException e) { |
| 1838 | // If it fails parsing the URL, we'll just rely on the default name below. |
| 1839 | } |
| 1840 | } |
| 1841 | |
| 1842 | if (TextUtils.isEmpty(name)) name = "index"; |
| 1843 | |
| 1844 | String testName = baseName + name + WEB_ARCHIVE_EXTENSION; |
| 1845 | if (!new File(testName).exists()) return testName; |
| 1846 | |
| 1847 | for (int i = 1; i < 100; i++) { |
| 1848 | testName = baseName + name + "-" + i + WEB_ARCHIVE_EXTENSION; |
| 1849 | if (!new File(testName).exists()) return testName; |
| 1850 | } |
| 1851 | |
| 1852 | Log.e(TAG, "Unable to auto generate archive name for path: " + baseName); |
| 1853 | return null; |
| 1854 | } |
| 1855 | |
| 1856 | //-------------------------------------------------------------------------------------------- |
| 1857 | // Native methods |
| 1858 | //-------------------------------------------------------------------------------------------- |
| 1859 | |
| 1860 | private static native int nativeInit(AwBrowserContext browserContext); |
| 1861 | private static native void nativeDestroy(int nativeAwContents); |
| 1862 | private static native void nativeSetAwDrawSWFunctionTable(int functionTablePointer); |
| 1863 | private static native void nativeSetAwDrawGLFunctionTable(int functionTablePointer); |
| 1864 | private static native int nativeGetAwDrawGLFunction(); |
| 1865 | private static native int nativeGetNativeInstanceCount(); |
| 1866 | private native void nativeSetJavaPeers(int nativeAwContents, AwContents awContents, |
| 1867 | AwWebContentsDelegate webViewWebContentsDelegate, |
| 1868 | AwContentsClientBridge contentsClientBridge, |
| 1869 | AwContentsIoThreadClient ioThreadClient, |
| 1870 | InterceptNavigationDelegate navigationInterceptionDelegate); |
| 1871 | private native int nativeGetWebContents(int nativeAwContents); |
| 1872 | |
| 1873 | private native void nativeDocumentHasImages(int nativeAwContents, Message message); |
| 1874 | private native void nativeGenerateMHTML( |
| 1875 | int nativeAwContents, String path, ValueCallback<String> callback); |
| 1876 | |
| 1877 | private native void nativeAddVisitedLinks(int nativeAwContents, String[] visitedLinks); |
| 1878 | private native boolean nativeOnDraw(int nativeAwContents, Canvas canvas, |
| 1879 | boolean isHardwareAccelerated, int scrollX, int ScrollY, |
| 1880 | int clipLeft, int clipTop, int clipRight, int clipBottom); |
| 1881 | private native void nativeSetGlobalVisibleRect(int nativeAwContents, int visibleLeft, |
| 1882 | int visibleTop, int visibleRight, int visibleBottom); |
| 1883 | private native void nativeFindAllAsync(int nativeAwContents, String searchString); |
| 1884 | private native void nativeFindNext(int nativeAwContents, boolean forward); |
| 1885 | private native void nativeClearMatches(int nativeAwContents); |
| 1886 | private native void nativeClearCache(int nativeAwContents, boolean includeDiskFiles); |
| 1887 | private native byte[] nativeGetCertificate(int nativeAwContents); |
| 1888 | |
| 1889 | // Coordinates in desity independent pixels. |
| 1890 | private native void nativeRequestNewHitTestDataAt(int nativeAwContents, int x, int y); |
| 1891 | private native void nativeUpdateLastHitTestData(int nativeAwContents); |
| 1892 | |
| 1893 | private native void nativeOnSizeChanged(int nativeAwContents, int w, int h, int ow, int oh); |
| 1894 | private native void nativeScrollTo(int nativeAwContents, int x, int y); |
| 1895 | private native void nativeSetViewVisibility(int nativeAwContents, boolean visible); |
| 1896 | private native void nativeSetWindowVisibility(int nativeAwContents, boolean visible); |
| 1897 | private native void nativeSetIsPaused(int nativeAwContents, boolean paused); |
| 1898 | private native void nativeOnAttachedToWindow(int nativeAwContents, int w, int h); |
| 1899 | private static native void nativeOnDetachedFromWindow(int nativeAwContents); |
| 1900 | private native void nativeSetDipScale(int nativeAwContents, float dipScale); |
| 1901 | private native void nativeSetDisplayedPageScaleFactor(int nativeAwContents, |
| 1902 | float pageScaleFactor); |
| 1903 | |
| 1904 | // Returns null if save state fails. |
| 1905 | private native byte[] nativeGetOpaqueState(int nativeAwContents); |
| 1906 | |
| 1907 | // Returns false if restore state fails. |
| 1908 | private native boolean nativeRestoreFromOpaqueState(int nativeAwContents, byte[] state); |
| 1909 | |
| 1910 | private native int nativeReleasePopupAwContents(int nativeAwContents); |
| 1911 | private native void nativeFocusFirstNode(int nativeAwContents); |
| 1912 | private native void nativeSetBackgroundColor(int nativeAwContents, int color); |
| 1913 | |
| 1914 | private native int nativeGetAwDrawGLViewContext(int nativeAwContents); |
| 1915 | private native int nativeCapturePicture(int nativeAwContents, int width, int height); |
| 1916 | private native void nativeEnableOnNewPicture(int nativeAwContents, boolean enabled); |
| 1917 | |
| 1918 | private native void nativeInvokeGeolocationCallback( |
| 1919 | int nativeAwContents, boolean value, String requestingFrame); |
| 1920 | } |