chromium/android_webview/tools/captured_sites_tests/javatests/src/org/chromium/webview_ui_test/test/util/CapturedSitesSyncWrapper.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.webview_ui_test.test.util;

import static androidx.test.InstrumentationRegistry.getInstrumentation;

import android.os.Looper;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.JsResult;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import org.chromium.base.test.util.CallbackHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * CapturedSitesSyncWrapper is a WebView wrapper to store the lock and condition provided by the
 * tests and ensure webview signal the tests once page load and javascript load is over.
 */
public class CapturedSitesSyncWrapper {
    // TODO (b/1470289) make this class inherit from WebViewSyncWrapper.
    private static final String JS_BRIDGE = "WEBVIEW_JS_BRIDGE";
    private static final String TAG = "CapturedSitesSyncWrapper";

    private final WebView mWebView;

    private CallbackHelper mPageCallback = new CallbackHelper();
    private CallbackHelper mJsCallback = new CallbackHelper();

    private List<ConsoleMessage> mErrorMessageList =
            Collections.synchronizedList(new ArrayList<ConsoleMessage>());

    public CapturedSitesSyncWrapper(WebView wv) {
        mWebView = wv;
        init();
    }

    /**
     * SyncJavaScriptBridge instance will be used by WebView to allow javascript in WebView can
     * make callbacks to signal that JavaScript is finished loading.
     * <p/>
     * For example, if a test has a long async javascript snippet that needed to be loaded before
     * the test proceed like below.
     * <p/>
     * <pre>
     * <code>
     * window.setTimeout(function() {
     *     document.findElementById("button");
     *     window.WEBVIEW_JS_BRIDGE.onJavaScriptFinished();
     *     }, 10000)
     * };
     * </code>
     * </pre>
     * <p/>
     * Despite that the javascript above takes 10 seconds to finish loading, the test will still
     * wait until javascript is finished loading
     */
    private class SyncJavaScriptBridge {
        @JavascriptInterface
        public void onJavaScriptFinished() {
            mJsCallback.notifyCalled();
        }
    }

    /** A custom WebViewClient that signals tests when onPageStarted and onPageFinished is called */
    private class SyncWebViewClient extends WebViewClient {
        @Override
        public void onPageFinished(WebView view, String url) {
            mPageCallback.notifyCalled();
            super.onPageFinished(view, url);
        }
    }

    private void init() {
        runOnUiThread(
                new Runnable() {
                    @Override
                    public void run() {
                        mWebView.getSettings().setJavaScriptEnabled(true);
                        mWebView.setWebViewClient(
                                CapturedSitesSyncWrapper.this.new SyncWebViewClient());
                        mWebView.setWebChromeClient(
                                new WebChromeClient() {
                                    @Override
                                    public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                                        if (consoleMessage.messageLevel()
                                                == ConsoleMessage.MessageLevel.ERROR) {
                                            mErrorMessageList.add(consoleMessage);
                                        }
                                        return super.onConsoleMessage(consoleMessage);
                                    }

                                    @Override
                                    public boolean onJsAlert(
                                            WebView view,
                                            String url,
                                            String message,
                                            JsResult result) {
                                        mJsCallback.notifyCalled();
                                        return super.onJsAlert(view, url, message, result);
                                    }
                                });
                        mWebView.addJavascriptInterface(
                                CapturedSitesSyncWrapper.this.new SyncJavaScriptBridge(),
                                JS_BRIDGE);
                    }
                });
    }

    private static void runOnUiThread(final Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            throw new RuntimeException(
                    "Actions in CapturedSitesSyncWrapper is not allowed to be run on "
                            + "UI thread");
        } else {
            getInstrumentation().runOnMainSync(runnable);
        }
    }

    public void loadUrlSync(final String url) {
        mErrorMessageList.clear();
        runOnUiThread(
                new Runnable() {
                    @Override
                    public void run() {
                        mWebView.getSettings().setAllowFileAccess(true);
                        mWebView.loadUrl(url);
                    }
                });
    }
}