| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.android_webview; |
| |
| import android.graphics.Picture; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.SystemClock; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.VisibleForTesting; |
| |
| import java.util.concurrent.Callable; |
| |
| /** |
| * This class is responsible for calling certain client callbacks on the UI thread. |
| * |
| * Most callbacks do no go through here, but get forwarded to AwContentsClient directly. The |
| * messages processed here may originate from the IO or UI thread. |
| */ |
| @VisibleForTesting |
| public class AwContentsClientCallbackHelper { |
| /** |
| * Interface to tell CallbackHelper to cancel posted callbacks. |
| */ |
| public static interface CancelCallbackPoller { boolean shouldCancelAllCallbacks(); } |
| |
| // TODO(boliu): Consider removing DownloadInfo and LoginRequestInfo by using native |
| // MessageLoop to post directly to AwContents. |
| |
| private static class DownloadInfo { |
| final String mUrl; |
| final String mUserAgent; |
| final String mContentDisposition; |
| final String mMimeType; |
| final long mContentLength; |
| |
| DownloadInfo(String url, |
| String userAgent, |
| String contentDisposition, |
| String mimeType, |
| long contentLength) { |
| mUrl = url; |
| mUserAgent = userAgent; |
| mContentDisposition = contentDisposition; |
| mMimeType = mimeType; |
| mContentLength = contentLength; |
| } |
| } |
| |
| private static class LoginRequestInfo { |
| final String mRealm; |
| final String mAccount; |
| final String mArgs; |
| |
| LoginRequestInfo(String realm, String account, String args) { |
| mRealm = realm; |
| mAccount = account; |
| mArgs = args; |
| } |
| } |
| |
| private static class OnReceivedErrorInfo { |
| final AwContentsClient.AwWebResourceRequest mRequest; |
| final AwContentsClient.AwWebResourceError mError; |
| |
| OnReceivedErrorInfo(AwContentsClient.AwWebResourceRequest request, |
| AwContentsClient.AwWebResourceError error) { |
| mRequest = request; |
| mError = error; |
| } |
| } |
| |
| private static class OnSafeBrowsingHitInfo { |
| final AwContentsClient.AwWebResourceRequest mRequest; |
| final int mThreatType; |
| final Callback<AwSafeBrowsingResponse> mCallback; |
| |
| OnSafeBrowsingHitInfo(AwContentsClient.AwWebResourceRequest request, int threatType, |
| Callback<AwSafeBrowsingResponse> callback) { |
| mRequest = request; |
| mThreatType = threatType; |
| mCallback = callback; |
| } |
| } |
| |
| private static class OnReceivedHttpErrorInfo { |
| final AwContentsClient.AwWebResourceRequest mRequest; |
| final AwWebResourceResponse mResponse; |
| |
| OnReceivedHttpErrorInfo( |
| AwContentsClient.AwWebResourceRequest request, AwWebResourceResponse response) { |
| mRequest = request; |
| mResponse = response; |
| } |
| } |
| |
| private static class DoUpdateVisitedHistoryInfo { |
| final String mUrl; |
| final boolean mIsReload; |
| |
| DoUpdateVisitedHistoryInfo(String url, boolean isReload) { |
| mUrl = url; |
| mIsReload = isReload; |
| } |
| } |
| |
| private static class OnFormResubmissionInfo { |
| final Message mDontResend; |
| final Message mResend; |
| |
| OnFormResubmissionInfo(Message dontResend, Message resend) { |
| mDontResend = dontResend; |
| mResend = resend; |
| } |
| } |
| |
| private static final int MSG_ON_LOAD_RESOURCE = 1; |
| private static final int MSG_ON_PAGE_STARTED = 2; |
| private static final int MSG_ON_DOWNLOAD_START = 3; |
| private static final int MSG_ON_RECEIVED_LOGIN_REQUEST = 4; |
| private static final int MSG_ON_RECEIVED_ERROR = 5; |
| private static final int MSG_ON_NEW_PICTURE = 6; |
| private static final int MSG_ON_SCALE_CHANGED_SCALED = 7; |
| private static final int MSG_ON_RECEIVED_HTTP_ERROR = 8; |
| private static final int MSG_ON_PAGE_FINISHED = 9; |
| private static final int MSG_ON_RECEIVED_TITLE = 10; |
| private static final int MSG_ON_PROGRESS_CHANGED = 11; |
| private static final int MSG_SYNTHESIZE_PAGE_LOADING = 12; |
| private static final int MSG_DO_UPDATE_VISITED_HISTORY = 13; |
| private static final int MSG_ON_FORM_RESUBMISSION = 14; |
| private static final int MSG_ON_SAFE_BROWSING_HIT = 15; |
| |
| // Minimum period allowed between consecutive onNewPicture calls, to rate-limit the callbacks. |
| private static final long ON_NEW_PICTURE_MIN_PERIOD_MILLIS = 500; |
| // Timestamp of the most recent onNewPicture callback. |
| private long mLastPictureTime; |
| // True when a onNewPicture callback is currenly in flight. |
| private boolean mHasPendingOnNewPicture; |
| |
| private final AwContentsClient mContentsClient; |
| |
| private final Handler mHandler; |
| |
| private CancelCallbackPoller mCancelCallbackPoller; |
| |
| private class MyHandler extends Handler { |
| private MyHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| if (mCancelCallbackPoller != null && mCancelCallbackPoller.shouldCancelAllCallbacks()) { |
| removeCallbacksAndMessages(null); |
| return; |
| } |
| |
| switch(msg.what) { |
| case MSG_ON_LOAD_RESOURCE: { |
| final String url = (String) msg.obj; |
| mContentsClient.onLoadResource(url); |
| break; |
| } |
| case MSG_ON_PAGE_STARTED: { |
| final String url = (String) msg.obj; |
| mContentsClient.onPageStarted(url); |
| break; |
| } |
| case MSG_ON_DOWNLOAD_START: { |
| DownloadInfo info = (DownloadInfo) msg.obj; |
| mContentsClient.onDownloadStart(info.mUrl, info.mUserAgent, |
| info.mContentDisposition, info.mMimeType, info.mContentLength); |
| break; |
| } |
| case MSG_ON_RECEIVED_LOGIN_REQUEST: { |
| LoginRequestInfo info = (LoginRequestInfo) msg.obj; |
| mContentsClient.onReceivedLoginRequest(info.mRealm, info.mAccount, info.mArgs); |
| break; |
| } |
| case MSG_ON_RECEIVED_ERROR: { |
| OnReceivedErrorInfo info = (OnReceivedErrorInfo) msg.obj; |
| mContentsClient.onReceivedError(info.mRequest, info.mError); |
| break; |
| } |
| case MSG_ON_SAFE_BROWSING_HIT: { |
| OnSafeBrowsingHitInfo info = (OnSafeBrowsingHitInfo) msg.obj; |
| mContentsClient.onSafeBrowsingHit( |
| info.mRequest, info.mThreatType, info.mCallback); |
| break; |
| } |
| case MSG_ON_NEW_PICTURE: { |
| Picture picture = null; |
| try { |
| if (msg.obj != null) picture = (Picture) ((Callable<?>) msg.obj).call(); |
| } catch (Exception e) { |
| throw new RuntimeException("Error getting picture", e); |
| } |
| mContentsClient.onNewPicture(picture); |
| mLastPictureTime = SystemClock.uptimeMillis(); |
| mHasPendingOnNewPicture = false; |
| break; |
| } |
| case MSG_ON_SCALE_CHANGED_SCALED: { |
| float oldScale = Float.intBitsToFloat(msg.arg1); |
| float newScale = Float.intBitsToFloat(msg.arg2); |
| mContentsClient.onScaleChangedScaled(oldScale, newScale); |
| break; |
| } |
| case MSG_ON_RECEIVED_HTTP_ERROR: { |
| OnReceivedHttpErrorInfo info = (OnReceivedHttpErrorInfo) msg.obj; |
| mContentsClient.onReceivedHttpError(info.mRequest, info.mResponse); |
| break; |
| } |
| case MSG_ON_PAGE_FINISHED: { |
| final String url = (String) msg.obj; |
| mContentsClient.onPageFinished(url); |
| break; |
| } |
| case MSG_ON_RECEIVED_TITLE: { |
| final String title = (String) msg.obj; |
| mContentsClient.onReceivedTitle(title); |
| break; |
| } |
| case MSG_ON_PROGRESS_CHANGED: { |
| mContentsClient.onProgressChanged(msg.arg1); |
| break; |
| } |
| case MSG_SYNTHESIZE_PAGE_LOADING: { |
| final String url = (String) msg.obj; |
| mContentsClient.onPageStarted(url); |
| mContentsClient.onLoadResource(url); |
| mContentsClient.onProgressChanged(100); |
| mContentsClient.onPageFinished(url); |
| break; |
| } |
| case MSG_DO_UPDATE_VISITED_HISTORY: { |
| final DoUpdateVisitedHistoryInfo info = (DoUpdateVisitedHistoryInfo) msg.obj; |
| mContentsClient.doUpdateVisitedHistory(info.mUrl, info.mIsReload); |
| break; |
| } |
| case MSG_ON_FORM_RESUBMISSION: { |
| final OnFormResubmissionInfo info = (OnFormResubmissionInfo) msg.obj; |
| mContentsClient.onFormResubmission(info.mDontResend, info.mResend); |
| break; |
| } |
| default: |
| throw new IllegalStateException( |
| "AwContentsClientCallbackHelper: unhandled message " + msg.what); |
| } |
| } |
| } |
| |
| public AwContentsClientCallbackHelper(Looper looper, AwContentsClient contentsClient) { |
| mHandler = new MyHandler(looper); |
| mContentsClient = contentsClient; |
| } |
| |
| // Public for tests. |
| public void setCancelCallbackPoller(CancelCallbackPoller poller) { |
| mCancelCallbackPoller = poller; |
| } |
| |
| CancelCallbackPoller getCancelCallbackPoller() { |
| return mCancelCallbackPoller; |
| } |
| |
| public void postOnLoadResource(String url) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_LOAD_RESOURCE, url)); |
| } |
| |
| public void postOnPageStarted(String url) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_STARTED, url)); |
| } |
| |
| public void postOnDownloadStart(String url, String userAgent, String contentDisposition, |
| String mimeType, long contentLength) { |
| DownloadInfo info = new DownloadInfo(url, userAgent, contentDisposition, mimeType, |
| contentLength); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_DOWNLOAD_START, info)); |
| } |
| |
| public void postOnReceivedLoginRequest(String realm, String account, String args) { |
| LoginRequestInfo info = new LoginRequestInfo(realm, account, args); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_LOGIN_REQUEST, info)); |
| } |
| |
| public void postOnReceivedError(AwContentsClient.AwWebResourceRequest request, |
| AwContentsClient.AwWebResourceError error) { |
| OnReceivedErrorInfo info = new OnReceivedErrorInfo(request, error); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_ERROR, info)); |
| } |
| |
| public void postOnSafeBrowsingHit(AwContentsClient.AwWebResourceRequest request, int threatType, |
| Callback<AwSafeBrowsingResponse> callback) { |
| OnSafeBrowsingHitInfo info = new OnSafeBrowsingHitInfo(request, threatType, callback); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_SAFE_BROWSING_HIT, info)); |
| } |
| |
| public void postOnNewPicture(Callable<Picture> pictureProvider) { |
| if (mHasPendingOnNewPicture) return; |
| mHasPendingOnNewPicture = true; |
| long pictureTime = java.lang.Math.max(mLastPictureTime + ON_NEW_PICTURE_MIN_PERIOD_MILLIS, |
| SystemClock.uptimeMillis()); |
| mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ON_NEW_PICTURE, pictureProvider), |
| pictureTime); |
| } |
| |
| public void postOnScaleChangedScaled(float oldScale, float newScale) { |
| // The float->int->float conversion here is to avoid unnecessary allocations. The |
| // documentation states that intBitsToFloat(floatToIntBits(a)) == a for all values of a |
| // (except for NaNs which are collapsed to a single canonical NaN, but we don't care for |
| // that case). |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_SCALE_CHANGED_SCALED, |
| Float.floatToIntBits(oldScale), Float.floatToIntBits(newScale))); |
| } |
| |
| public void postOnReceivedHttpError(AwContentsClient.AwWebResourceRequest request, |
| AwWebResourceResponse response) { |
| OnReceivedHttpErrorInfo info = |
| new OnReceivedHttpErrorInfo(request, response); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_HTTP_ERROR, info)); |
| } |
| |
| public void postOnPageFinished(String url) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PAGE_FINISHED, url)); |
| } |
| |
| public void postOnReceivedTitle(String title) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_RECEIVED_TITLE, title)); |
| } |
| |
| public void postOnProgressChanged(int progress) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_PROGRESS_CHANGED, progress, 0)); |
| } |
| |
| public void postSynthesizedPageLoadingForUrlBarUpdate(String url) { |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_SYNTHESIZE_PAGE_LOADING, url)); |
| } |
| |
| public void postDoUpdateVisitedHistory(String url, boolean isReload) { |
| DoUpdateVisitedHistoryInfo info = new DoUpdateVisitedHistoryInfo(url, isReload); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_DO_UPDATE_VISITED_HISTORY, info)); |
| } |
| |
| public void postOnFormResubmission(Message dontResend, Message resend) { |
| OnFormResubmissionInfo info = new OnFormResubmissionInfo(dontResend, resend); |
| mHandler.sendMessage(mHandler.obtainMessage(MSG_ON_FORM_RESUBMISSION, info)); |
| } |
| |
| void removeCallbacksAndMessages() { |
| mHandler.removeCallbacksAndMessages(null); |
| } |
| } |