1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.net.Uri;
25import android.net.http.SslCertificate;
26import android.net.http.SslError;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Message;
30import android.os.SystemClock;
31import android.provider.Browser;
32import android.util.Log;
33import android.view.KeyEvent;
34import com.android.internal.R;
35
36import java.net.MalformedURLException;
37import java.net.URL;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41
42/**
43 * This class is a proxy class for handling WebCore -> UI thread messaging. All
44 * the callback functions are called from the WebCore thread and messages are
45 * posted to the UI thread for the actual client callback.
46 */
47/*
48 * This class is created in the UI thread so its handler and any private classes
49 * that extend Handler will operate in the UI thread.
50 */
51class CallbackProxy extends Handler {
52    // Logging tag
53    private static final String LOGTAG = "CallbackProxy";
54    // Instance of WebViewClient that is the client callback.
55    private volatile WebViewClient mWebViewClient;
56    // Instance of WebChromeClient for handling all chrome functions.
57    private volatile WebChromeClient mWebChromeClient;
58    // Instance of WebViewClassic for handling UI requests.
59    private final WebViewClassic mWebView;
60    // Client registered callback listener for download events
61    private volatile DownloadListener mDownloadListener;
62    // Keep track of multiple progress updates.
63    private boolean mProgressUpdatePending;
64    // Keep track of the last progress amount.
65    // Start with 100 to indicate it is not in load for the empty page.
66    private volatile int mLatestProgress = 100;
67    // Back/Forward list
68    private final WebBackForwardListClassic mBackForwardList;
69    // Back/Forward list client
70    private volatile WebBackForwardListClient mWebBackForwardListClient;
71    // Used to call startActivity during url override.
72    private final Context mContext;
73    // block messages flag for destroy
74    private boolean mBlockMessages;
75
76    // Message IDs
77    private static final int PAGE_STARTED                         = 100;
78    private static final int RECEIVED_ICON                        = 101;
79    private static final int RECEIVED_TITLE                       = 102;
80    private static final int OVERRIDE_URL                         = 103;
81    private static final int AUTH_REQUEST                         = 104;
82    private static final int SSL_ERROR                            = 105;
83    private static final int PROGRESS                             = 106;
84    private static final int UPDATE_VISITED                       = 107;
85    private static final int LOAD_RESOURCE                        = 108;
86    private static final int CREATE_WINDOW                        = 109;
87    private static final int CLOSE_WINDOW                         = 110;
88    private static final int SAVE_PASSWORD                        = 111;
89    private static final int JS_DIALOG                            = 112;
90    private static final int ASYNC_KEYEVENTS                      = 116;
91    private static final int DOWNLOAD_FILE                        = 118;
92    private static final int REPORT_ERROR                         = 119;
93    private static final int RESEND_POST_DATA                     = 120;
94    private static final int PAGE_FINISHED                        = 121;
95    private static final int REQUEST_FOCUS                        = 122;
96    private static final int SCALE_CHANGED                        = 123;
97    private static final int RECEIVED_CERTIFICATE                 = 124;
98    private static final int SWITCH_OUT_HISTORY                   = 125;
99    private static final int EXCEEDED_DATABASE_QUOTA              = 126;
100    private static final int REACHED_APPCACHE_MAXSIZE             = 127;
101    private static final int JS_TIMEOUT                           = 128;
102    private static final int ADD_MESSAGE_TO_CONSOLE               = 129;
103    private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT  = 130;
104    private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT  = 131;
105    private static final int RECEIVED_TOUCH_ICON_URL              = 132;
106    private static final int GET_VISITED_HISTORY                  = 133;
107    private static final int OPEN_FILE_CHOOSER                    = 134;
108    private static final int ADD_HISTORY_ITEM                     = 135;
109    private static final int HISTORY_INDEX_CHANGED                = 136;
110    private static final int AUTH_CREDENTIALS                     = 137;
111    private static final int AUTO_LOGIN                           = 140;
112    private static final int CLIENT_CERT_REQUEST                  = 141;
113    private static final int PROCEEDED_AFTER_SSL_ERROR            = 144;
114
115    // Message triggered by the client to resume execution
116    private static final int NOTIFY                               = 200;
117
118    // Result transportation object for returning results across thread
119    // boundaries.
120    private static class ResultTransport<E> {
121        // Private result object
122        private E mResult;
123
124        public ResultTransport(E defaultResult) {
125            mResult = defaultResult;
126        }
127
128        public synchronized void setResult(E result) {
129            mResult = result;
130        }
131
132        public synchronized E getResult() {
133            return mResult;
134        }
135    }
136
137    private class JsResultReceiver implements JsResult.ResultReceiver {
138        // This prevents a user from interacting with the result before WebCore is
139        // ready to handle it.
140        private boolean mReady;
141        // Tells us if the user tried to confirm or cancel the result before WebCore
142        // is ready.
143        private boolean mTriedToNotifyBeforeReady;
144
145        public JsPromptResult mJsResult = new JsPromptResult(this);
146
147        final void setReady() {
148            mReady = true;
149            if (mTriedToNotifyBeforeReady) {
150                notifyCallbackProxy();
151            }
152        }
153
154        /* Wake up the WebCore thread. */
155        @Override
156        public void onJsResultComplete(JsResult result) {
157            if (mReady) {
158                notifyCallbackProxy();
159            } else {
160                mTriedToNotifyBeforeReady = true;
161            }
162        }
163
164        private void notifyCallbackProxy() {
165            synchronized (CallbackProxy.this) {
166                CallbackProxy.this.notify();
167            }
168        }
169}
170
171    /**
172     * Construct a new CallbackProxy.
173     */
174    public CallbackProxy(Context context, WebViewClassic w) {
175        // Used to start a default activity.
176        mContext = context;
177        mWebView = w;
178        mBackForwardList = new WebBackForwardListClassic(this);
179    }
180
181    protected synchronized void blockMessages() {
182        mBlockMessages = true;
183    }
184
185    protected synchronized boolean messagesBlocked() {
186        return mBlockMessages;
187    }
188
189    protected void shutdown() {
190        removeCallbacksAndMessages(null);
191        setWebViewClient(null);
192        setWebChromeClient(null);
193    }
194
195    /**
196     * Set the WebViewClient.
197     * @param client An implementation of WebViewClient.
198     */
199    public void setWebViewClient(WebViewClient client) {
200        mWebViewClient = client;
201    }
202
203    /**
204     * Get the WebViewClient.
205     * @return the current WebViewClient instance.
206     */
207    public WebViewClient getWebViewClient() {
208       return mWebViewClient;
209    }
210
211    /**
212     * Set the WebChromeClient.
213     * @param client An implementation of WebChromeClient.
214     */
215    public void setWebChromeClient(WebChromeClient client) {
216        mWebChromeClient = client;
217    }
218
219    /**
220     * Get the WebChromeClient.
221     * @return the current WebChromeClient instance.
222     */
223    public WebChromeClient getWebChromeClient() {
224       return mWebChromeClient;
225    }
226
227    /**
228     * Set the client DownloadListener.
229     * @param client An implementation of DownloadListener.
230     */
231    public void setDownloadListener(DownloadListener client) {
232        mDownloadListener = client;
233    }
234
235    /**
236     * Get the Back/Forward list to return to the user or to update the cached
237     * history list.
238     */
239    public WebBackForwardListClassic getBackForwardList() {
240        return mBackForwardList;
241    }
242
243    void setWebBackForwardListClient(WebBackForwardListClient client) {
244        mWebBackForwardListClient = client;
245    }
246
247    WebBackForwardListClient getWebBackForwardListClient() {
248        return mWebBackForwardListClient;
249    }
250
251    /**
252     * Called by the UI side.  Calling overrideUrlLoading from the WebCore
253     * side will post a message to call this method.
254     */
255    public boolean uiOverrideUrlLoading(String overrideUrl) {
256        if (overrideUrl == null || overrideUrl.length() == 0) {
257            return false;
258        }
259        boolean override = false;
260        if (mWebViewClient != null) {
261            override = mWebViewClient.shouldOverrideUrlLoading(mWebView.getWebView(),
262                    overrideUrl);
263        } else {
264            Intent intent = new Intent(Intent.ACTION_VIEW,
265                    Uri.parse(overrideUrl));
266            intent.addCategory(Intent.CATEGORY_BROWSABLE);
267            // If another application is running a WebView and launches the
268            // Browser through this Intent, we want to reuse the same window if
269            // possible.
270            intent.putExtra(Browser.EXTRA_APPLICATION_ID,
271                    mContext.getPackageName());
272            try {
273                mContext.startActivity(intent);
274                override = true;
275            } catch (ActivityNotFoundException ex) {
276                // If no application can handle the URL, assume that the
277                // browser can handle it.
278            }
279        }
280        return override;
281    }
282
283    /**
284     * Called by UI side.
285     */
286    public boolean uiOverrideKeyEvent(KeyEvent event) {
287        if (mWebViewClient != null) {
288            return mWebViewClient.shouldOverrideKeyEvent(mWebView.getWebView(), event);
289        }
290        return false;
291    }
292
293    @Override
294    public void handleMessage(Message msg) {
295        // We don't have to do synchronization because this function operates
296        // in the UI thread. The WebViewClient and WebChromeClient functions
297        // that check for a non-null callback are ok because java ensures atomic
298        // 32-bit reads and writes.
299        if (messagesBlocked()) return;
300        switch (msg.what) {
301            case PAGE_STARTED:
302                String startedUrl = msg.getData().getString("url");
303                mWebView.onPageStarted(startedUrl);
304                if (mWebViewClient != null) {
305                    mWebViewClient.onPageStarted(mWebView.getWebView(), startedUrl,
306                            (Bitmap) msg.obj);
307                }
308                break;
309
310            case PAGE_FINISHED:
311                String finishedUrl = (String) msg.obj;
312                mWebView.onPageFinished(finishedUrl);
313                if (mWebViewClient != null) {
314                    mWebViewClient.onPageFinished(mWebView.getWebView(), finishedUrl);
315                }
316                break;
317
318            case RECEIVED_ICON:
319                if (mWebChromeClient != null) {
320                    mWebChromeClient.onReceivedIcon(mWebView.getWebView(), (Bitmap) msg.obj);
321                }
322                break;
323
324            case RECEIVED_TOUCH_ICON_URL:
325                if (mWebChromeClient != null) {
326                    mWebChromeClient.onReceivedTouchIconUrl(mWebView.getWebView(),
327                            (String) msg.obj, msg.arg1 == 1);
328                }
329                break;
330
331            case RECEIVED_TITLE:
332                if (mWebChromeClient != null) {
333                    mWebChromeClient.onReceivedTitle(mWebView.getWebView(),
334                            (String) msg.obj);
335                }
336                break;
337
338            case REPORT_ERROR:
339                if (mWebViewClient != null) {
340                    int reasonCode = msg.arg1;
341                    final String description  = msg.getData().getString("description");
342                    final String failUrl  = msg.getData().getString("failingUrl");
343                    mWebViewClient.onReceivedError(mWebView.getWebView(), reasonCode,
344                            description, failUrl);
345                }
346                break;
347
348            case RESEND_POST_DATA:
349                Message resend =
350                        (Message) msg.getData().getParcelable("resend");
351                Message dontResend =
352                        (Message) msg.getData().getParcelable("dontResend");
353                if (mWebViewClient != null) {
354                    mWebViewClient.onFormResubmission(mWebView.getWebView(), dontResend,
355                            resend);
356                } else {
357                    dontResend.sendToTarget();
358                }
359                break;
360
361            case OVERRIDE_URL:
362                String overrideUrl = msg.getData().getString("url");
363                boolean override = uiOverrideUrlLoading(overrideUrl);
364                ResultTransport<Boolean> result =
365                        (ResultTransport<Boolean>) msg.obj;
366                synchronized (this) {
367                    result.setResult(override);
368                    notify();
369                }
370                break;
371
372            case AUTH_REQUEST:
373                if (mWebViewClient != null) {
374                    HttpAuthHandler handler = (HttpAuthHandler) msg.obj;
375                    String host = msg.getData().getString("host");
376                    String realm = msg.getData().getString("realm");
377                    mWebViewClient.onReceivedHttpAuthRequest(mWebView.getWebView(), handler,
378                            host, realm);
379                }
380                break;
381
382            case SSL_ERROR:
383                if (mWebViewClient != null) {
384                    HashMap<String, Object> map =
385                        (HashMap<String, Object>) msg.obj;
386                    mWebViewClient.onReceivedSslError(mWebView.getWebView(),
387                            (SslErrorHandler) map.get("handler"),
388                            (SslError) map.get("error"));
389                }
390                break;
391
392            case PROCEEDED_AFTER_SSL_ERROR:
393                if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
394                    ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
395                            mWebView.getWebView(),
396                            (SslError) msg.obj);
397                }
398                break;
399
400            case CLIENT_CERT_REQUEST:
401                if (mWebViewClient != null  && mWebViewClient instanceof WebViewClientClassicExt) {
402                    HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
403                    ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
404                            mWebView.getWebView(),
405                            (ClientCertRequestHandler) map.get("handler"),
406                            (String) map.get("host_and_port"));
407                }
408                break;
409
410            case PROGRESS:
411                // Synchronize to ensure mLatestProgress is not modified after
412                // setProgress is called and before mProgressUpdatePending is
413                // changed.
414                synchronized (this) {
415                    if (mWebChromeClient != null) {
416                        mWebChromeClient.onProgressChanged(mWebView.getWebView(),
417                                mLatestProgress);
418                    }
419                    mProgressUpdatePending = false;
420                }
421                break;
422
423            case UPDATE_VISITED:
424                if (mWebViewClient != null) {
425                    mWebViewClient.doUpdateVisitedHistory(mWebView.getWebView(),
426                            (String) msg.obj, msg.arg1 != 0);
427                }
428                break;
429
430            case LOAD_RESOURCE:
431                if (mWebViewClient != null) {
432                    mWebViewClient.onLoadResource(mWebView.getWebView(), (String) msg.obj);
433                }
434                break;
435
436            case DOWNLOAD_FILE:
437                if (mDownloadListener != null) {
438                    String url = msg.getData().getString("url");
439                    String userAgent = msg.getData().getString("userAgent");
440                    String contentDisposition =
441                        msg.getData().getString("contentDisposition");
442                    String mimetype = msg.getData().getString("mimetype");
443                    String referer = msg.getData().getString("referer");
444                    Long contentLength = msg.getData().getLong("contentLength");
445
446                    if (mDownloadListener instanceof BrowserDownloadListener) {
447                        ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
448                             userAgent, contentDisposition, mimetype, referer, contentLength);
449                    } else {
450                        mDownloadListener.onDownloadStart(url, userAgent,
451                             contentDisposition, mimetype, contentLength);
452                    }
453                }
454                break;
455
456            case CREATE_WINDOW:
457                if (mWebChromeClient != null) {
458                    if (!mWebChromeClient.onCreateWindow(mWebView.getWebView(),
459                                msg.arg1 == 1, msg.arg2 == 1,
460                                (Message) msg.obj)) {
461                        synchronized (this) {
462                            notify();
463                        }
464                    }
465                    mWebView.dismissZoomControl();
466                }
467                break;
468
469            case REQUEST_FOCUS:
470                if (mWebChromeClient != null) {
471                    mWebChromeClient.onRequestFocus(mWebView.getWebView());
472                }
473                break;
474
475            case CLOSE_WINDOW:
476                if (mWebChromeClient != null) {
477                    mWebChromeClient.onCloseWindow(((WebViewClassic) msg.obj).getWebView());
478                }
479                break;
480
481            case SAVE_PASSWORD:
482                Bundle bundle = msg.getData();
483                String schemePlusHost = bundle.getString("host");
484                String username = bundle.getString("username");
485                String password = bundle.getString("password");
486                // If the client returned false it means that the notify message
487                // will not be sent and we should notify WebCore ourselves.
488                if (!mWebView.onSavePassword(schemePlusHost, username, password,
489                            (Message) msg.obj)) {
490                    synchronized (this) {
491                        notify();
492                    }
493                }
494                break;
495
496            case ASYNC_KEYEVENTS:
497                if (mWebViewClient != null) {
498                    mWebViewClient.onUnhandledKeyEvent(mWebView.getWebView(),
499                            (KeyEvent) msg.obj);
500                }
501                break;
502
503            case EXCEEDED_DATABASE_QUOTA:
504                if (mWebChromeClient != null) {
505                    HashMap<String, Object> map =
506                            (HashMap<String, Object>) msg.obj;
507                    String databaseIdentifier =
508                            (String) map.get("databaseIdentifier");
509                    String url = (String) map.get("url");
510                    long quota =
511                            ((Long) map.get("quota")).longValue();
512                    long totalQuota =
513                            ((Long) map.get("totalQuota")).longValue();
514                    long estimatedDatabaseSize =
515                            ((Long) map.get("estimatedDatabaseSize")).longValue();
516                    WebStorage.QuotaUpdater quotaUpdater =
517                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
518
519                    mWebChromeClient.onExceededDatabaseQuota(url,
520                            databaseIdentifier, quota, estimatedDatabaseSize,
521                            totalQuota, quotaUpdater);
522                }
523                break;
524
525            case REACHED_APPCACHE_MAXSIZE:
526                if (mWebChromeClient != null) {
527                    HashMap<String, Object> map =
528                            (HashMap<String, Object>) msg.obj;
529                    long requiredStorage =
530                            ((Long) map.get("requiredStorage")).longValue();
531                    long quota =
532                        ((Long) map.get("quota")).longValue();
533                    WebStorage.QuotaUpdater quotaUpdater =
534                        (WebStorage.QuotaUpdater) map.get("quotaUpdater");
535
536                    mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
537                            quota, quotaUpdater);
538                }
539                break;
540
541            case GEOLOCATION_PERMISSIONS_SHOW_PROMPT:
542                if (mWebChromeClient != null) {
543                    HashMap<String, Object> map =
544                            (HashMap<String, Object>) msg.obj;
545                    String origin = (String) map.get("origin");
546                    GeolocationPermissions.Callback callback =
547                            (GeolocationPermissions.Callback)
548                            map.get("callback");
549                    mWebChromeClient.onGeolocationPermissionsShowPrompt(origin,
550                            callback);
551                }
552                break;
553
554            case GEOLOCATION_PERMISSIONS_HIDE_PROMPT:
555                if (mWebChromeClient != null) {
556                    mWebChromeClient.onGeolocationPermissionsHidePrompt();
557                }
558                break;
559
560            case JS_DIALOG:
561                if (mWebChromeClient != null) {
562                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
563                    JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
564                    if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
565                        helper.showDialog(mContext);
566                    }
567                    receiver.setReady();
568                }
569                break;
570
571            case JS_TIMEOUT:
572                if(mWebChromeClient != null) {
573                    final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
574                    final JsResult res = receiver.mJsResult;
575                    if (mWebChromeClient.onJsTimeout()) {
576                        res.confirm();
577                    } else {
578                        res.cancel();
579                    }
580                    receiver.setReady();
581                }
582                break;
583
584            case RECEIVED_CERTIFICATE:
585                mWebView.setCertificate((SslCertificate) msg.obj);
586                break;
587
588            case NOTIFY:
589                synchronized (this) {
590                    notify();
591                }
592                break;
593
594            case SCALE_CHANGED:
595                if (mWebViewClient != null) {
596                    mWebViewClient.onScaleChanged(mWebView.getWebView(), msg.getData()
597                            .getFloat("old"), msg.getData().getFloat("new"));
598                }
599                break;
600
601            case SWITCH_OUT_HISTORY:
602                mWebView.switchOutDrawHistory();
603                break;
604
605            case ADD_MESSAGE_TO_CONSOLE:
606                if (mWebChromeClient == null) {
607                    break;
608                }
609                String message = msg.getData().getString("message");
610                String sourceID = msg.getData().getString("sourceID");
611                int lineNumber = msg.getData().getInt("lineNumber");
612                int msgLevel = msg.getData().getInt("msgLevel");
613                int numberOfMessageLevels = ConsoleMessage.MessageLevel.values().length;
614                // Sanity bounds check as we'll index an array with msgLevel
615                if (msgLevel < 0 || msgLevel >= numberOfMessageLevels) {
616                    msgLevel = 0;
617                }
618
619                ConsoleMessage.MessageLevel messageLevel =
620                        ConsoleMessage.MessageLevel.values()[msgLevel];
621
622                if (!mWebChromeClient.onConsoleMessage(new ConsoleMessage(message, sourceID,
623                        lineNumber, messageLevel))) {
624                    // If false was returned the user did not provide their own console function so
625                    //  we should output some default messages to the system log.
626                    String logTag = "Web Console";
627                    String logMessage = message + " at " + sourceID + ":" + lineNumber;
628
629                    switch (messageLevel) {
630                        case TIP:
631                            Log.v(logTag, logMessage);
632                            break;
633                        case LOG:
634                            Log.i(logTag, logMessage);
635                            break;
636                        case WARNING:
637                            Log.w(logTag, logMessage);
638                            break;
639                        case ERROR:
640                            Log.e(logTag, logMessage);
641                            break;
642                        case DEBUG:
643                            Log.d(logTag, logMessage);
644                            break;
645                    }
646                }
647
648                break;
649
650            case GET_VISITED_HISTORY:
651                if (mWebChromeClient != null) {
652                    mWebChromeClient.getVisitedHistory((ValueCallback<String[]>)msg.obj);
653                }
654                break;
655
656            case OPEN_FILE_CHOOSER:
657                if (mWebChromeClient != null) {
658                    UploadFileMessageData data = (UploadFileMessageData)msg.obj;
659                    mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
660                            data.getCapture());
661                }
662                break;
663
664            case ADD_HISTORY_ITEM:
665                if (mWebBackForwardListClient != null) {
666                    mWebBackForwardListClient.onNewHistoryItem(
667                            (WebHistoryItem) msg.obj);
668                }
669                break;
670
671            case HISTORY_INDEX_CHANGED:
672                if (mWebBackForwardListClient != null) {
673                    mWebBackForwardListClient.onIndexChanged(
674                            (WebHistoryItem) msg.obj, msg.arg1);
675                }
676                break;
677            case AUTH_CREDENTIALS: {
678                String host = msg.getData().getString("host");
679                String realm = msg.getData().getString("realm");
680                username = msg.getData().getString("username");
681                password = msg.getData().getString("password");
682                mWebView.setHttpAuthUsernamePassword(
683                        host, realm, username, password);
684                break;
685            }
686            case AUTO_LOGIN: {
687                if (mWebViewClient != null) {
688                    String realm = msg.getData().getString("realm");
689                    String account = msg.getData().getString("account");
690                    String args = msg.getData().getString("args");
691                    mWebViewClient.onReceivedLoginRequest(mWebView.getWebView(), realm,
692                            account, args);
693                }
694                break;
695            }
696        }
697    }
698
699    /**
700     * Return the latest progress.
701     */
702    public int getProgress() {
703        return mLatestProgress;
704    }
705
706    /**
707     * Called by WebCore side to switch out of history Picture drawing mode
708     */
709    void switchOutDrawHistory() {
710        sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
711    }
712
713    //--------------------------------------------------------------------------
714    // WebViewClient functions.
715    // NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
716    // it is not necessary to include it here.
717    //--------------------------------------------------------------------------
718
719    // Performance probe
720    private static final boolean PERF_PROBE = false;
721    private long mWebCoreThreadTime;
722    private long mWebCoreIdleTime;
723
724    /*
725     * If PERF_PROBE is true, this block needs to be added to MessageQueue.java.
726     * startWait() and finishWait() should be called before and after wait().
727
728    private WaitCallback mWaitCallback = null;
729    public static interface WaitCallback {
730        void startWait();
731        void finishWait();
732    }
733    public final void setWaitCallback(WaitCallback callback) {
734        mWaitCallback = callback;
735    }
736    */
737
738    // un-comment this block if PERF_PROBE is true
739    /*
740    private IdleCallback mIdleCallback = new IdleCallback();
741
742    private final class IdleCallback implements MessageQueue.WaitCallback {
743        private long mStartTime = 0;
744
745        public void finishWait() {
746            mWebCoreIdleTime += SystemClock.uptimeMillis() - mStartTime;
747        }
748
749        public void startWait() {
750            mStartTime = SystemClock.uptimeMillis();
751        }
752    }
753    */
754
755    public void onPageStarted(String url, Bitmap favicon) {
756        // We need to send the message even if no WebViewClient is set, because we need to call
757        // WebView.onPageStarted().
758
759        // Performance probe
760        if (PERF_PROBE) {
761            mWebCoreThreadTime = SystemClock.currentThreadTimeMillis();
762            mWebCoreIdleTime = 0;
763            // un-comment this if PERF_PROBE is true
764//            Looper.myQueue().setWaitCallback(mIdleCallback);
765        }
766        Message msg = obtainMessage(PAGE_STARTED);
767        msg.obj = favicon;
768        msg.getData().putString("url", url);
769        sendMessage(msg);
770    }
771
772    public void onPageFinished(String url) {
773        // Performance probe
774        if (PERF_PROBE) {
775            // un-comment this if PERF_PROBE is true
776//            Looper.myQueue().setWaitCallback(null);
777            Log.d("WebCore", "WebCore thread used " +
778                    (SystemClock.currentThreadTimeMillis() - mWebCoreThreadTime)
779                    + " ms and idled " + mWebCoreIdleTime + " ms");
780        }
781        Message msg = obtainMessage(PAGE_FINISHED, url);
782        sendMessage(msg);
783    }
784
785    // Because this method is public and because CallbackProxy is mistakenly
786    // party of the public classes, we cannot remove this method.
787    public void onTooManyRedirects(Message cancelMsg, Message continueMsg) {
788        // deprecated.
789    }
790
791    public void onReceivedError(int errorCode, String description,
792            String failingUrl) {
793        // Do an unsynchronized quick check to avoid posting if no callback has
794        // been set.
795        if (mWebViewClient == null) {
796            return;
797        }
798
799        Message msg = obtainMessage(REPORT_ERROR);
800        msg.arg1 = errorCode;
801        msg.getData().putString("description", description);
802        msg.getData().putString("failingUrl", failingUrl);
803        sendMessage(msg);
804    }
805
806    public void onFormResubmission(Message dontResend,
807            Message resend) {
808        // Do an unsynchronized quick check to avoid posting if no callback has
809        // been set.
810        if (mWebViewClient == null) {
811            dontResend.sendToTarget();
812            return;
813        }
814
815        Message msg = obtainMessage(RESEND_POST_DATA);
816        Bundle bundle = msg.getData();
817        bundle.putParcelable("resend", resend);
818        bundle.putParcelable("dontResend", dontResend);
819        sendMessage(msg);
820    }
821
822    /**
823     * Called by the WebCore side
824     */
825    public boolean shouldOverrideUrlLoading(String url) {
826        // We have a default behavior if no client exists so always send the
827        // message.
828        ResultTransport<Boolean> res = new ResultTransport<Boolean>(false);
829        Message msg = obtainMessage(OVERRIDE_URL);
830        msg.getData().putString("url", url);
831        msg.obj = res;
832        sendMessageToUiThreadSync(msg);
833        return res.getResult().booleanValue();
834    }
835
836    public void onReceivedHttpAuthRequest(HttpAuthHandler handler,
837            String hostName, String realmName) {
838        // Do an unsynchronized quick check to avoid posting if no callback has
839        // been set.
840        if (mWebViewClient == null) {
841            handler.cancel();
842            return;
843        }
844        Message msg = obtainMessage(AUTH_REQUEST, handler);
845        msg.getData().putString("host", hostName);
846        msg.getData().putString("realm", realmName);
847        sendMessage(msg);
848    }
849
850    public void onReceivedSslError(SslErrorHandler handler, SslError error) {
851        // Do an unsynchronized quick check to avoid posting if no callback has
852        // been set.
853        if (mWebViewClient == null) {
854            handler.cancel();
855            return;
856        }
857        Message msg = obtainMessage(SSL_ERROR);
858        HashMap<String, Object> map = new HashMap();
859        map.put("handler", handler);
860        map.put("error", error);
861        msg.obj = map;
862        sendMessage(msg);
863    }
864
865    public void onProceededAfterSslError(SslError error) {
866        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
867            return;
868        }
869        Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
870        msg.obj = error;
871        sendMessage(msg);
872    }
873
874    public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
875        // Do an unsynchronized quick check to avoid posting if no callback has
876        // been set.
877        if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
878            handler.cancel();
879            return;
880        }
881        Message msg = obtainMessage(CLIENT_CERT_REQUEST);
882        HashMap<String, Object> map = new HashMap();
883        map.put("handler", handler);
884        map.put("host_and_port", host_and_port);
885        msg.obj = map;
886        sendMessage(msg);
887    }
888
889    public void onReceivedCertificate(SslCertificate certificate) {
890        // here, certificate can be null (if the site is not secure)
891        sendMessage(obtainMessage(RECEIVED_CERTIFICATE, certificate));
892    }
893
894    public void doUpdateVisitedHistory(String url, boolean isReload) {
895        // Do an unsynchronized quick check to avoid posting if no callback has
896        // been set.
897        if (mWebViewClient == null) {
898            return;
899        }
900        sendMessage(obtainMessage(UPDATE_VISITED, isReload ? 1 : 0, 0, url));
901    }
902
903    WebResourceResponse shouldInterceptRequest(String url) {
904        if (mWebViewClient == null) {
905            return null;
906        }
907        // Note: This method does _not_ send a message.
908        WebResourceResponse r =
909                mWebViewClient.shouldInterceptRequest(mWebView.getWebView(), url);
910        if (r == null) {
911            sendMessage(obtainMessage(LOAD_RESOURCE, url));
912        }
913        return r;
914    }
915
916    public void onUnhandledKeyEvent(KeyEvent event) {
917        // Do an unsynchronized quick check to avoid posting if no callback has
918        // been set.
919        if (mWebViewClient == null) {
920            return;
921        }
922        sendMessage(obtainMessage(ASYNC_KEYEVENTS, event));
923    }
924
925    public void onScaleChanged(float oldScale, float newScale) {
926        // Do an unsynchronized quick check to avoid posting if no callback has
927        // been set.
928        if (mWebViewClient == null) {
929            return;
930        }
931        Message msg = obtainMessage(SCALE_CHANGED);
932        Bundle bundle = msg.getData();
933        bundle.putFloat("old", oldScale);
934        bundle.putFloat("new", newScale);
935        sendMessage(msg);
936    }
937
938    void onReceivedLoginRequest(String realm, String account, String args) {
939        // Do an unsynchronized quick check to avoid posting if no callback has
940        // been set.
941        if (mWebViewClient == null) {
942            return;
943        }
944        Message msg = obtainMessage(AUTO_LOGIN);
945        Bundle bundle = msg.getData();
946        bundle.putString("realm", realm);
947        bundle.putString("account", account);
948        bundle.putString("args", args);
949        sendMessage(msg);
950    }
951
952    //--------------------------------------------------------------------------
953    // DownloadListener functions.
954    //--------------------------------------------------------------------------
955
956    /**
957     * Starts a download if a download listener has been registered, otherwise
958     * return false.
959     */
960    public boolean onDownloadStart(String url, String userAgent,
961            String contentDisposition, String mimetype, String referer,
962            long contentLength) {
963        // Do an unsynchronized quick check to avoid posting if no callback has
964        // been set.
965        if (mDownloadListener == null) {
966            // Cancel the download if there is no browser client.
967            return false;
968        }
969
970        Message msg = obtainMessage(DOWNLOAD_FILE);
971        Bundle bundle = msg.getData();
972        bundle.putString("url", url);
973        bundle.putString("userAgent", userAgent);
974        bundle.putString("mimetype", mimetype);
975        bundle.putString("referer", referer);
976        bundle.putLong("contentLength", contentLength);
977        bundle.putString("contentDisposition", contentDisposition);
978        sendMessage(msg);
979        return true;
980    }
981
982
983    //--------------------------------------------------------------------------
984    // WebView specific functions that do not interact with a client. These
985    // functions just need to operate within the UI thread.
986    //--------------------------------------------------------------------------
987
988    public boolean onSavePassword(String schemePlusHost, String username,
989            String password, Message resumeMsg) {
990        // resumeMsg should be null at this point because we want to create it
991        // within the CallbackProxy.
992        if (DebugFlags.CALLBACK_PROXY) {
993            junit.framework.Assert.assertNull(resumeMsg);
994        }
995        resumeMsg = obtainMessage(NOTIFY);
996
997        Message msg = obtainMessage(SAVE_PASSWORD, resumeMsg);
998        Bundle bundle = msg.getData();
999        bundle.putString("host", schemePlusHost);
1000        bundle.putString("username", username);
1001        bundle.putString("password", password);
1002        sendMessageToUiThreadSync(msg);
1003        // Doesn't matter here
1004        return false;
1005    }
1006
1007    public void onReceivedHttpAuthCredentials(String host, String realm,
1008            String username, String password) {
1009        Message msg = obtainMessage(AUTH_CREDENTIALS);
1010        msg.getData().putString("host", host);
1011        msg.getData().putString("realm", realm);
1012        msg.getData().putString("username", username);
1013        msg.getData().putString("password", password);
1014        sendMessage(msg);
1015    }
1016
1017    //--------------------------------------------------------------------------
1018    // WebChromeClient methods
1019    //--------------------------------------------------------------------------
1020
1021    public void onProgressChanged(int newProgress) {
1022        // Synchronize so that mLatestProgress is up-to-date.
1023        synchronized (this) {
1024            // update mLatestProgress even mWebChromeClient is null as
1025            // WebView.getProgress() needs it
1026            if (mLatestProgress == newProgress) {
1027                return;
1028            }
1029            mLatestProgress = newProgress;
1030            if (mWebChromeClient == null) {
1031                return;
1032            }
1033            if (!mProgressUpdatePending) {
1034                sendEmptyMessage(PROGRESS);
1035                mProgressUpdatePending = true;
1036            }
1037        }
1038    }
1039
1040    public BrowserFrame createWindow(boolean dialog, boolean userGesture) {
1041        // Do an unsynchronized quick check to avoid posting if no callback has
1042        // been set.
1043        if (mWebChromeClient == null) {
1044            return null;
1045        }
1046
1047        WebView.WebViewTransport transport =
1048            mWebView.getWebView().new WebViewTransport();
1049        final Message msg = obtainMessage(NOTIFY);
1050        msg.obj = transport;
1051        sendMessageToUiThreadSync(obtainMessage(CREATE_WINDOW, dialog ? 1 : 0,
1052                userGesture ? 1 : 0, msg));
1053        WebViewClassic w = WebViewClassic.fromWebView(transport.getWebView());
1054        if (w != null) {
1055            WebViewCore core = w.getWebViewCore();
1056            // If WebView.destroy() has been called, core may be null.  Skip
1057            // initialization in that case and return null.
1058            if (core != null) {
1059                core.initializeSubwindow();
1060                return core.getBrowserFrame();
1061            }
1062        }
1063        return null;
1064    }
1065
1066    public void onRequestFocus() {
1067        // Do an unsynchronized quick check to avoid posting if no callback has
1068        // been set.
1069        if (mWebChromeClient == null) {
1070            return;
1071        }
1072
1073        sendEmptyMessage(REQUEST_FOCUS);
1074    }
1075
1076    public void onCloseWindow(WebViewClassic window) {
1077        // Do an unsynchronized quick check to avoid posting if no callback has
1078        // been set.
1079        if (mWebChromeClient == null) {
1080            return;
1081        }
1082        sendMessage(obtainMessage(CLOSE_WINDOW, window));
1083    }
1084
1085    public void onReceivedIcon(Bitmap icon) {
1086        // The current item might be null if the icon was already stored in the
1087        // database and this is a new WebView.
1088        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
1089        if (i != null) {
1090            i.setFavicon(icon);
1091        }
1092        // Do an unsynchronized quick check to avoid posting if no callback has
1093        // been set.
1094        if (mWebChromeClient == null) {
1095            return;
1096        }
1097        sendMessage(obtainMessage(RECEIVED_ICON, icon));
1098    }
1099
1100    /* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
1101        // We should have a current item but we do not want to crash so check
1102        // for null.
1103        WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
1104        if (i != null) {
1105            i.setTouchIconUrl(url, precomposed);
1106        }
1107        // Do an unsynchronized quick check to avoid posting if no callback has
1108        // been set.
1109        if (mWebChromeClient == null) {
1110            return;
1111        }
1112        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL,
1113                precomposed ? 1 : 0, 0, url));
1114    }
1115
1116    public void onReceivedTitle(String title) {
1117        // Do an unsynchronized quick check to avoid posting if no callback has
1118        // been set.
1119        if (mWebChromeClient == null) {
1120            return;
1121        }
1122        sendMessage(obtainMessage(RECEIVED_TITLE, title));
1123    }
1124
1125    public void onJsAlert(String url, String message) {
1126        // Do an unsynchronized quick check to avoid posting if no callback has
1127        // been set.
1128        if (mWebChromeClient == null) {
1129            return;
1130        }
1131        JsResultReceiver result = new JsResultReceiver();
1132        Message alert = obtainMessage(JS_DIALOG, result);
1133        alert.getData().putString("message", message);
1134        alert.getData().putString("url", url);
1135        alert.getData().putInt("type", JsDialogHelper.ALERT);
1136        sendMessageToUiThreadSync(alert);
1137    }
1138
1139    public boolean onJsConfirm(String url, String message) {
1140        // Do an unsynchronized quick check to avoid posting if no callback has
1141        // been set.
1142        if (mWebChromeClient == null) {
1143            return false;
1144        }
1145        JsResultReceiver result = new JsResultReceiver();
1146        Message confirm = obtainMessage(JS_DIALOG, result);
1147        confirm.getData().putString("message", message);
1148        confirm.getData().putString("url", url);
1149        confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
1150        sendMessageToUiThreadSync(confirm);
1151        return result.mJsResult.getResult();
1152    }
1153
1154    public String onJsPrompt(String url, String message, String defaultValue) {
1155        // Do an unsynchronized quick check to avoid posting if no callback has
1156        // been set.
1157        if (mWebChromeClient == null) {
1158            return null;
1159        }
1160        JsResultReceiver result = new JsResultReceiver();
1161        Message prompt = obtainMessage(JS_DIALOG, result);
1162        prompt.getData().putString("message", message);
1163        prompt.getData().putString("default", defaultValue);
1164        prompt.getData().putString("url", url);
1165        prompt.getData().putInt("type", JsDialogHelper.PROMPT);
1166        sendMessageToUiThreadSync(prompt);
1167        return result.mJsResult.getStringResult();
1168    }
1169
1170    public boolean onJsBeforeUnload(String url, String message) {
1171        // Do an unsynchronized quick check to avoid posting if no callback has
1172        // been set.
1173        if (mWebChromeClient == null) {
1174            return true;
1175        }
1176        JsResultReceiver result = new JsResultReceiver();
1177        Message unload = obtainMessage(JS_DIALOG, result);
1178        unload.getData().putString("message", message);
1179        unload.getData().putString("url", url);
1180        unload.getData().putInt("type", JsDialogHelper.UNLOAD);
1181        sendMessageToUiThreadSync(unload);
1182        return result.mJsResult.getResult();
1183    }
1184
1185    /**
1186     * Called by WebViewCore to inform the Java side that the current origin
1187     * has overflowed it's database quota. Called in the WebCore thread so
1188     * posts a message to the UI thread that will prompt the WebChromeClient
1189     * for what to do. On return back to C++ side, the WebCore thread will
1190     * sleep pending a new quota value.
1191     * @param url The URL that caused the quota overflow.
1192     * @param databaseIdentifier The identifier of the database that the
1193     *     transaction that caused the overflow was running on.
1194     * @param quota The current quota the origin is allowed.
1195     * @param estimatedDatabaseSize The estimated size of the database.
1196     * @param totalQuota is the sum of all origins' quota.
1197     * @param quotaUpdater An instance of a class encapsulating a callback
1198     *     to WebViewCore to run when the decision to allow or deny more
1199     *     quota has been made.
1200     */
1201    public void onExceededDatabaseQuota(
1202            String url, String databaseIdentifier, long quota,
1203            long estimatedDatabaseSize, long totalQuota,
1204            WebStorage.QuotaUpdater quotaUpdater) {
1205        if (mWebChromeClient == null) {
1206            // Native-side logic prevents the quota being updated to a smaller
1207            // value.
1208            quotaUpdater.updateQuota(quota);
1209            return;
1210        }
1211
1212        Message exceededQuota = obtainMessage(EXCEEDED_DATABASE_QUOTA);
1213        HashMap<String, Object> map = new HashMap();
1214        map.put("databaseIdentifier", databaseIdentifier);
1215        map.put("url", url);
1216        map.put("quota", quota);
1217        map.put("estimatedDatabaseSize", estimatedDatabaseSize);
1218        map.put("totalQuota", totalQuota);
1219        map.put("quotaUpdater", quotaUpdater);
1220        exceededQuota.obj = map;
1221        sendMessage(exceededQuota);
1222    }
1223
1224    /**
1225     * Called by WebViewCore to inform the Java side that the appcache has
1226     * exceeded its max size.
1227     * @param requiredStorage is the amount of storage, in bytes, that would be
1228     * needed in order for the last appcache operation to succeed.
1229     * @param quota is the current quota (for all origins).
1230     * @param quotaUpdater An instance of a class encapsulating a callback
1231     * to WebViewCore to run when the decision to allow or deny a bigger
1232     * app cache size has been made.
1233     */
1234    public void onReachedMaxAppCacheSize(long requiredStorage,
1235            long quota, WebStorage.QuotaUpdater quotaUpdater) {
1236        if (mWebChromeClient == null) {
1237            // Native-side logic prevents the quota being updated to a smaller
1238            // value.
1239            quotaUpdater.updateQuota(quota);
1240            return;
1241        }
1242
1243        Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
1244        HashMap<String, Object> map = new HashMap();
1245        map.put("requiredStorage", requiredStorage);
1246        map.put("quota", quota);
1247        map.put("quotaUpdater", quotaUpdater);
1248        msg.obj = map;
1249        sendMessage(msg);
1250    }
1251
1252    /**
1253     * Called by WebViewCore to instruct the browser to display a prompt to ask
1254     * the user to set the Geolocation permission state for the given origin.
1255     * @param origin The origin requesting Geolocation permsissions.
1256     * @param callback The callback to call once a permission state has been
1257     *     obtained.
1258     */
1259    public void onGeolocationPermissionsShowPrompt(String origin,
1260            GeolocationPermissions.Callback callback) {
1261        if (mWebChromeClient == null) {
1262            return;
1263        }
1264
1265        Message showMessage =
1266                obtainMessage(GEOLOCATION_PERMISSIONS_SHOW_PROMPT);
1267        HashMap<String, Object> map = new HashMap();
1268        map.put("origin", origin);
1269        map.put("callback", callback);
1270        showMessage.obj = map;
1271        sendMessage(showMessage);
1272    }
1273
1274    /**
1275     * Called by WebViewCore to instruct the browser to hide the Geolocation
1276     * permissions prompt.
1277     */
1278    public void onGeolocationPermissionsHidePrompt() {
1279        if (mWebChromeClient == null) {
1280            return;
1281        }
1282
1283        Message hideMessage = obtainMessage(GEOLOCATION_PERMISSIONS_HIDE_PROMPT);
1284        sendMessage(hideMessage);
1285    }
1286
1287    /**
1288     * Called by WebViewCore when we have a message to be added to the JavaScript
1289     * error console. Sends a message to the Java side with the details.
1290     * @param message The message to add to the console.
1291     * @param lineNumber The lineNumber of the source file on which the error
1292     *     occurred.
1293     * @param sourceID The filename of the source file in which the error
1294     *     occurred.
1295     * @param msgLevel The message level, corresponding to the MessageLevel enum in
1296     *     WebCore/page/Console.h
1297     */
1298    public void addMessageToConsole(String message, int lineNumber, String sourceID, int msgLevel) {
1299        if (mWebChromeClient == null) {
1300            return;
1301        }
1302
1303        Message msg = obtainMessage(ADD_MESSAGE_TO_CONSOLE);
1304        msg.getData().putString("message", message);
1305        msg.getData().putString("sourceID", sourceID);
1306        msg.getData().putInt("lineNumber", lineNumber);
1307        msg.getData().putInt("msgLevel", msgLevel);
1308        sendMessage(msg);
1309    }
1310
1311    public boolean onJsTimeout() {
1312        //always interrupt timedout JS by default
1313        if (mWebChromeClient == null) {
1314            return true;
1315        }
1316        JsResultReceiver result = new JsResultReceiver();
1317        Message timeout = obtainMessage(JS_TIMEOUT, result);
1318        sendMessageToUiThreadSync(timeout);
1319        return result.mJsResult.getResult();
1320    }
1321
1322    public void getVisitedHistory(ValueCallback<String[]> callback) {
1323        if (mWebChromeClient == null) {
1324            return;
1325        }
1326        Message msg = obtainMessage(GET_VISITED_HISTORY);
1327        msg.obj = callback;
1328        sendMessage(msg);
1329    }
1330
1331    private static class UploadFileMessageData {
1332        private UploadFile mCallback;
1333        private String mAcceptType;
1334        private String mCapture;
1335
1336        public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
1337            mCallback = uploadFile;
1338            mAcceptType = acceptType;
1339            mCapture = capture;
1340        }
1341
1342        public UploadFile getUploadFile() {
1343            return mCallback;
1344        }
1345
1346        public String getAcceptType() {
1347            return mAcceptType;
1348        }
1349
1350        public String getCapture() {
1351            return mCapture;
1352        }
1353    }
1354
1355    private class UploadFile implements ValueCallback<Uri> {
1356        private Uri mValue;
1357        public void onReceiveValue(Uri value) {
1358            mValue = value;
1359            synchronized (CallbackProxy.this) {
1360                CallbackProxy.this.notify();
1361            }
1362        }
1363        public Uri getResult() {
1364            return mValue;
1365        }
1366    }
1367
1368    /**
1369     * Called by WebViewCore to open a file chooser.
1370     */
1371    /* package */ Uri openFileChooser(String acceptType, String capture) {
1372        if (mWebChromeClient == null) {
1373            return null;
1374        }
1375        Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
1376        UploadFile uploadFile = new UploadFile();
1377        UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
1378        myMessage.obj = data;
1379        sendMessageToUiThreadSync(myMessage);
1380        return uploadFile.getResult();
1381    }
1382
1383    void onNewHistoryItem(WebHistoryItem item) {
1384        if (mWebBackForwardListClient == null) {
1385            return;
1386        }
1387        Message msg = obtainMessage(ADD_HISTORY_ITEM, item);
1388        sendMessage(msg);
1389    }
1390
1391    void onIndexChanged(WebHistoryItem item, int index) {
1392        if (mWebBackForwardListClient == null) {
1393            return;
1394        }
1395        Message msg = obtainMessage(HISTORY_INDEX_CHANGED, index, 0, item);
1396        sendMessage(msg);
1397    }
1398
1399    private synchronized void sendMessageToUiThreadSync(Message msg) {
1400        sendMessage(msg);
1401        WebCoreThreadWatchdog.pause();
1402        try {
1403            wait();
1404        } catch (InterruptedException e) {
1405            Log.e(LOGTAG, "Caught exception waiting for synchronous UI message to be processed");
1406            Log.e(LOGTAG, Log.getStackTraceString(e));
1407        }
1408        WebCoreThreadWatchdog.resume();
1409    }
1410}
1411