Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 74d37b96 authored by Ben Murdoch's avatar Ben Murdoch
Browse files

Fix up the WebCoreThreadWatchdog

Make the WebCoreWatchdog aware of the WebViews it is monitoring
(rather than the Activity context which may become stale) and
ensure that the code for the prompt dialog is run on the UI
thread.

Bug: 6420310
Change-Id: Ied003938edb04858c85bcc2491c4b2c4c0ede6eb
parent 6a160984
Loading
Loading
Loading
Loading
+111 −42
Original line number Diff line number Diff line
@@ -26,6 +26,10 @@ import android.os.Message;
import android.os.Process;
import android.webkit.WebViewCore.EventHub;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

// A Runnable that will monitor if the WebCore thread is still
// processing messages by pinging it every so often. It is safe
// to call the public methods of this class from any thread.
@@ -51,25 +55,31 @@ class WebCoreThreadWatchdog implements Runnable {
    // After the first timeout, use a shorter period before re-prompting the user.
    private static final int SUBSEQUENT_TIMEOUT_PERIOD = 15 * 1000;

    private Context mContext;
    private Handler mWebCoreThreadHandler;
    private Handler mHandler;
    private boolean mPaused;

    private Set<WebViewClassic> mWebViews;

    private static WebCoreThreadWatchdog sInstance;

    public synchronized static WebCoreThreadWatchdog start(Context context,
            Handler webCoreThreadHandler) {
    public synchronized static WebCoreThreadWatchdog start(Handler webCoreThreadHandler) {
        if (sInstance == null) {
            sInstance = new WebCoreThreadWatchdog(context, webCoreThreadHandler);
            sInstance = new WebCoreThreadWatchdog(webCoreThreadHandler);
            new Thread(sInstance, "WebCoreThreadWatchdog").start();
        }
        return sInstance;
    }

    public synchronized static void updateContext(Context context) {
    public synchronized static void registerWebView(WebViewClassic w) {
        if (sInstance != null) {
            sInstance.addWebView(w);
        }
    }

    public synchronized static void unregisterWebView(WebViewClassic w) {
        if (sInstance != null) {
            sInstance.setContext(context);
            sInstance.removeWebView(w);
        }
    }

@@ -85,12 +95,18 @@ class WebCoreThreadWatchdog implements Runnable {
        }
    }

    private void setContext(Context context) {
        mContext = context;
    private void addWebView(WebViewClassic w) {
        if (mWebViews == null) {
            mWebViews = new HashSet<WebViewClassic>();
        }
        mWebViews.add(w);
    }

    private WebCoreThreadWatchdog(Context context, Handler webCoreThreadHandler) {
        mContext = context;
    private void removeWebView(WebViewClassic w) {
        mWebViews.remove(w);
    }

    private WebCoreThreadWatchdog(Handler webCoreThreadHandler) {
        mWebCoreThreadHandler = webCoreThreadHandler;
    }

@@ -147,7 +163,80 @@ class WebCoreThreadWatchdog implements Runnable {
                        break;

                    case TIMED_OUT:
                        if ((mContext == null) || !(mContext instanceof Activity)) return;
                        boolean postedDialog = false;
                        synchronized (WebCoreThreadWatchdog.class) {
                            Iterator<WebViewClassic> it = mWebViews.iterator();
                            // Check each WebView we are aware of and find one that is capable of
                            // showing the user a prompt dialog.
                            while (it.hasNext()) {
                                WebView activeView = it.next().getWebView();

                                if (activeView.getWindowToken() != null &&
                                        activeView.getViewRootImpl() != null) {
                                    postedDialog = activeView.post(new PageNotRespondingRunnable(
                                            activeView.getContext(), this));

                                    if (postedDialog) {
                                        // We placed the message into the UI thread for an attached
                                        // WebView so we've made our best attempt to display the
                                        // "page not responding" dialog to the user. Although the
                                        // message is in the queue, there is no guarantee when/if
                                        // the runnable will execute. In the case that the runnable
                                        // never executes, the user will need to terminate the
                                        // process manually.
                                        break;
                                    }
                                }
                            }

                            if (!postedDialog) {
                                // There's no active webview we can use to show the dialog, so
                                // wait again. If we never get a usable view, the user will
                                // never get the chance to terminate the process, and will
                                // need to do it manually.
                                sendMessageDelayed(obtainMessage(TIMED_OUT),
                                        SUBSEQUENT_TIMEOUT_PERIOD);
                            }
                        }
                        break;
                    }
                }
            };
        }
    }

    @Override
    public void run() {
        Looper.prepare();

        createHandler();

        // Send the initial control to WebViewCore and start the timeout timer as long as we aren't
        // paused.
        synchronized (WebCoreThreadWatchdog.class) {
            if (!mPaused) {
                mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
                        mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
                mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
            }
        }

        Looper.loop();
    }

    private class PageNotRespondingRunnable implements Runnable {
        Context mContext;
        private Handler mWatchdogHandler;

        public PageNotRespondingRunnable(Context context, Handler watchdogHandler) {
            mContext = context;
            mWatchdogHandler = watchdogHandler;
        }

        @Override
        public void run() {
            // This must run on the UI thread as it is displaying an AlertDialog.
            assert Looper.getMainLooper().getThread() == Thread.currentThread();
            new AlertDialog.Builder(mContext)
                    .setMessage(com.android.internal.R.string.webpage_unresponsive)
                    .setPositiveButton(com.android.internal.R.string.force_close,
@@ -167,42 +256,22 @@ class WebCoreThreadWatchdog implements Runnable {
                                    // we need to do is post another TIMED_OUT so that the
                                    // user will get prompted again if the WebCore thread
                                    // doesn't sort itself out.
                                            sendMessageDelayed(obtainMessage(TIMED_OUT),
                                    mWatchdogHandler.sendMessageDelayed(
                                            mWatchdogHandler.obtainMessage(TIMED_OUT),
                                            SUBSEQUENT_TIMEOUT_PERIOD);
                                }
                            })
                            .setOnCancelListener(new DialogInterface.OnCancelListener() {
                    .setOnCancelListener(
                            new DialogInterface.OnCancelListener() {
                                @Override
                                public void onCancel(DialogInterface dialog) {
                                        sendMessageDelayed(obtainMessage(TIMED_OUT),
                                    mWatchdogHandler.sendMessageDelayed(
                                            mWatchdogHandler.obtainMessage(TIMED_OUT),
                                            SUBSEQUENT_TIMEOUT_PERIOD);
                                }
                            })
                    .setIcon(android.R.drawable.ic_dialog_alert)
                    .show();
                        break;
                    }
                }
            };
        }
    }

    @Override
    public void run() {
        Looper.prepare();

        createHandler();

        // Send the initial control to WebViewCore and start the timeout timer as long as we aren't
        // paused.
        synchronized (WebCoreThreadWatchdog.class) {
            if (!mPaused) {
                mWebCoreThreadHandler.obtainMessage(EventHub.HEARTBEAT,
                        mHandler.obtainMessage(IS_ALIVE)).sendToTarget();
                mHandler.sendMessageDelayed(mHandler.obtainMessage(TIMED_OUT), TIMEOUT_PERIOD);
            }
        }

        Looper.loop();
    }
}
+0 −5
Original line number Diff line number Diff line
@@ -3365,11 +3365,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
                nativeSetPauseDrawing(mNativeClass, false);
            }
        }
        // Ensure that the watchdog has a currently valid Context to be able to display
        // a prompt dialog. For example, if the Activity was finished whilst the WebCore
        // thread was blocked and the Activity is started again, we may reuse the blocked
        // thread, but we'll have a new Activity.
        WebCoreThreadWatchdog.updateContext(mContext);
        // We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
        // to ensure that the Watchdog thread is running for the new WebView, so call
        // it outside the if block above.
+4 −1
Original line number Diff line number Diff line
@@ -178,8 +178,10 @@ public final class WebViewCore {

                // Start the singleton watchdog which will monitor the WebCore thread
                // to verify it's still processing messages.
                WebCoreThreadWatchdog.start(context, sWebCoreHandler);
                WebCoreThreadWatchdog.start(sWebCoreHandler);
            }
            // Make sure the Watchdog is aware of this new WebView.
            WebCoreThreadWatchdog.registerWebView(w);
        }
        // Create an EventHub to handle messages before and after the thread is
        // ready.
@@ -1979,6 +1981,7 @@ public final class WebViewCore {
            mEventHub.sendMessageAtFrontOfQueue(
                    Message.obtain(null, EventHub.DESTROY));
            mEventHub.blockMessages();
            WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic);
        }
    }