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 | } |