chromium/content/public/android/javatests/src/org/chromium/content/browser/JavaBridgeActivityTestRule.java

// Copyright 2017 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.content.browser;

import android.webkit.JavascriptInterface;

import org.junit.Assert;

import org.chromium.base.Log;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.JavascriptInjector;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content_shell_apk.ContentShellActivity;
import org.chromium.content_shell_apk.ContentShellActivityTestRule;

import java.lang.annotation.Annotation;

/** ActivityTestRule with common functionality for testing the Java Bridge. */
public class JavaBridgeActivityTestRule extends ContentShellActivityTestRule {
    /** Shared name for batched JavaBridge tests. */
    public static final String BATCH = "JavaBridgeActivityTestRule";

    private TestCallbackHelperContainer mTestCallbackHelperContainer;

    public static class Controller {
        private static final int RESULT_WAIT_TIME = 5000;

        private boolean mIsResultReady;

        protected synchronized void notifyResultIsReady() {
            mIsResultReady = true;
            notify();
        }

        protected synchronized void waitForResult() {
            while (!mIsResultReady) {
                try {
                    wait(RESULT_WAIT_TIME);
                } catch (Exception e) {
                    continue;
                }
                if (!mIsResultReady) {
                    Assert.fail("Wait timed out");
                }
            }
            mIsResultReady = false;
        }
    }

    public TestCallbackHelperContainer getTestCallBackHelperContainer() {
        return mTestCallbackHelperContainer;
    }

    /** Sets up the ContentView. Intended to be called from setUp(). */
    public void setUpContentView() {
        // This starts the activity, so must be called on the test thread.
        final ContentShellActivity activity =
                launchContentShellWithUrl(
                        UrlUtils.encodeHtmlDataUri("<html><head></head><body>test</body></html>"));
        waitForActiveShellToBeDoneLoading();

        try {
            runOnUiThread(
                    new Runnable() {
                        @Override
                        public void run() {
                            mTestCallbackHelperContainer =
                                    new TestCallbackHelperContainer(
                                            activity.getActiveWebContents());
                        }
                    });
        } catch (Throwable e) {
            throw new RuntimeException(
                    "Failed to set up ContentView: " + Log.getStackTraceString(e));
        }
    }

    public void executeJavaScript(String script) throws Throwable {
        runOnUiThread(
                new Runnable() {
                    @Override
                    public void run() {
                        // When a JavaScript URL is executed, if the value of the last
                        // expression evaluated is not 'undefined', this value is
                        // converted to a string and used as the new document for the
                        // frame. We don't want this behaviour, so wrap the script in
                        // an anonymous function.
                        getWebContents()
                                .getNavigationController()
                                .loadUrl(
                                        new LoadUrlParams(
                                                "javascript:(function() { " + script + " })()"));
                    }
                });
    }

    public void injectObjectAndReload(Object object, String name) {
        Class<? extends Annotation> requiredAnnotation = JavascriptInterface.class;
        injectObjectAndReload(object, name, requiredAnnotation);
    }

    public void injectObjectAndReload(
            Object object, String name, Class<? extends Annotation> requiredAnnotation) {
        injectObjectsAndReload(object, name, null, null, requiredAnnotation);
    }

    public void injectObjectsAndReload(
            final Object object1,
            final String name1,
            final Object object2,
            final String name2,
            final Class<? extends Annotation> requiredAnnotation) {
        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
                mTestCallbackHelperContainer.getOnPageFinishedHelper();
        int currentCallCount = onPageFinishedHelper.getCallCount();
        try {
            runOnUiThread(
                    new Runnable() {
                        @Override
                        public void run() {
                            WebContents webContents = getWebContents();
                            JavascriptInjector injector =
                                    JavascriptInjector.fromWebContents(webContents);
                            injector.addPossiblyUnsafeInterface(object1, name1, requiredAnnotation);
                            if (object2 != null && name2 != null) {
                                injector.addPossiblyUnsafeInterface(
                                        object2, name2, requiredAnnotation);
                            }
                            webContents.getNavigationController().reload(true);
                        }
                    });
            onPageFinishedHelper.waitForCallback(currentCallCount);
        } catch (Throwable e) {
            throw new RuntimeException(
                    "Failed to injectObjectsAndReload: " + Log.getStackTraceString(e));
        }
    }

    public void synchronousPageReload() throws Throwable {
        TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
                mTestCallbackHelperContainer.getOnPageFinishedHelper();
        int currentCallCount = onPageFinishedHelper.getCallCount();
        runOnUiThread(
                new Runnable() {
                    @Override
                    public void run() {
                        getWebContents().getNavigationController().reload(true);
                    }
                });
        onPageFinishedHelper.waitForCallback(currentCallCount);
    }

    @Override
    protected void before() throws Throwable {
        super.before();
        setUpContentView();
    }
}