| Index: chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
|
| diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9ceb1636102df1941c251e2d536f109b3a8c305c
|
| --- /dev/null
|
| +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BindingManagerIntegrationTest.java
|
| @@ -0,0 +1,447 @@
|
| +// Copyright 2015 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.chrome.browser;
|
| +
|
| +import android.test.FlakyTest;
|
| +import android.test.suitebuilder.annotation.LargeTest;
|
| +import android.util.SparseArray;
|
| +
|
| +import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.test.util.CommandLineFlags;
|
| +import org.chromium.base.test.util.Feature;
|
| +import org.chromium.chrome.browser.compositor.layouts.Layout;
|
| +import org.chromium.chrome.browser.tabmodel.ChromeTabCreator;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
|
| +import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
|
| +import org.chromium.chrome.browser.tabmodel.TabModelUtils;
|
| +import org.chromium.chrome.test.ChromeActivityTestCaseBase;
|
| +import org.chromium.chrome.test.util.ChromeTabUtils;
|
| +import org.chromium.chrome.test.util.TestHttpServerClient;
|
| +import org.chromium.content.browser.BindingManager;
|
| +import org.chromium.content.browser.ChildProcessConnection;
|
| +import org.chromium.content.browser.ChildProcessLauncher;
|
| +import org.chromium.content.browser.test.util.Criteria;
|
| +import org.chromium.content.browser.test.util.CriteriaHelper;
|
| +import org.chromium.content_public.browser.LoadUrlParams;
|
| +import org.chromium.ui.base.DeviceFormFactor;
|
| +
|
| +import java.util.concurrent.Callable;
|
| +
|
| +/**
|
| + * Integration tests for the BindingManager API. This test plants a mock BindingManager
|
| + * implementation and verifies that the signals it relies on are correctly delivered.
|
| + */
|
| +@CommandLineFlags.Add(ChromeSwitches.DISABLE_DOCUMENT_MODE) // crbug.com/414719
|
| +public class BindingManagerIntegrationTest extends ChromeActivityTestCaseBase<ChromeActivity> {
|
| +
|
| + private static class MockBindingManager implements BindingManager {
|
| + // Maps pid to the last received visibility state of the renderer.
|
| + private final SparseArray<Boolean> mProcessInForegroundMap = new SparseArray<Boolean>();
|
| + // Maps pid to a string recording calls to setInForeground() and visibilityDetermined().
|
| + private final SparseArray<String> mVisibilityCallsMap = new SparseArray<String>();
|
| +
|
| + boolean isInForeground(int pid) {
|
| + return mProcessInForegroundMap.get(pid);
|
| + }
|
| +
|
| + boolean isInBackground(int pid) {
|
| + return !mProcessInForegroundMap.get(pid);
|
| + }
|
| +
|
| + boolean setInForegroundWasCalled(int pid) {
|
| + return mProcessInForegroundMap.get(pid) != null;
|
| + }
|
| +
|
| + String getVisibilityCalls(int pid) {
|
| + return mVisibilityCallsMap.get(pid);
|
| + }
|
| +
|
| + @Override
|
| + public void addNewConnection(int pid, ChildProcessConnection connection) {
|
| + mVisibilityCallsMap.put(pid, "");
|
| + }
|
| +
|
| + @Override
|
| + public void setInForeground(int pid, boolean inForeground) {
|
| + mProcessInForegroundMap.put(pid, inForeground);
|
| +
|
| + if (inForeground) {
|
| + mVisibilityCallsMap.put(pid, mVisibilityCallsMap.get(pid) + "FG;");
|
| + } else {
|
| + mVisibilityCallsMap.put(pid, mVisibilityCallsMap.get(pid) + "BG;");
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public void determinedVisibility(int pid) {
|
| + mVisibilityCallsMap.put(pid, mVisibilityCallsMap.get(pid) + "DETERMINED;");
|
| + }
|
| +
|
| + @Override
|
| + public void onSentToBackground() {}
|
| +
|
| + @Override
|
| + public void onBroughtToForeground() {}
|
| +
|
| + @Override
|
| + public boolean isOomProtected(int pid) {
|
| + return false;
|
| + }
|
| +
|
| + @Override
|
| + public void clearConnection(int pid) {}
|
| + }
|
| +
|
| + private MockBindingManager mBindingManager;
|
| +
|
| + private static final String FILE_PATH = "chrome/test/data/android/test.html";
|
| + // about:version will always be handled by a different renderer than a local file.
|
| + private static final String ABOUT_VERSION_PATH = "chrome://version/";
|
| +
|
| + public BindingManagerIntegrationTest() {
|
| + super(ChromeActivity.class);
|
| + }
|
| +
|
| + /**
|
| + * Verifies that the .setProcessInForeground() signal is called correctly as the tabs are
|
| + * created and switched.
|
| + */
|
| + @LargeTest
|
| + @Feature({"ProcessManagement"})
|
| + public void testTabSwitching() throws InterruptedException {
|
| + // Create two tabs and wait until they are loaded, so that their renderers are around.
|
| + final Tab[] tabs = new Tab[2];
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Foreground tab.
|
| + ChromeTabCreator tabCreator = getActivity().getCurrentTabCreator();
|
| + tabs[0] = tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_KEYBOARD, null);
|
| + // Background tab.
|
| + tabs[1] = tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_LONGPRESS_BACKGROUND, null);
|
| + // On Svelte devices the background tab would not be loaded automatically, so
|
| + // trigger the load manually.
|
| + tabs[1].show(TabSelectionType.FROM_USER);
|
| + tabs[1].hide();
|
| + }
|
| + });
|
| + ChromeTabUtils.waitForTabPageLoaded(tabs[0], TestHttpServerClient.getUrl(FILE_PATH));
|
| + ChromeTabUtils.waitForTabPageLoaded(tabs[1], TestHttpServerClient.getUrl(FILE_PATH));
|
| +
|
| + // Wait for the new tab animations on phones to finish.
|
| + if (!DeviceFormFactor.isTablet(getActivity())
|
| + && getActivity() instanceof CompositorChromeActivity) {
|
| + final CompositorChromeActivity activity = (CompositorChromeActivity) getActivity();
|
| + assertTrue("Did not finish animation",
|
| + CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + Layout layout = activity.getCompositorViewHolder()
|
| + .getLayoutManager().getActiveLayout();
|
| + return !layout.isLayoutAnimating();
|
| + }
|
| + }));
|
| + }
|
| + getInstrumentation().waitForIdleSync();
|
| +
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Make sure that the renderers were spawned.
|
| + assertTrue(tabs[0].getContentViewCore().getCurrentRenderProcessId() > 0);
|
| + assertTrue(tabs[1].getContentViewCore().getCurrentRenderProcessId() > 0);
|
| +
|
| + // Verify that the renderer of the foreground tab was signalled as visible.
|
| + assertTrue(mBindingManager.isInForeground(
|
| + tabs[0].getContentViewCore().getCurrentRenderProcessId()));
|
| + // Verify that the renderer of the tab loaded in background was signalled as not
|
| + // visible.
|
| + assertTrue(mBindingManager.isInBackground(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId()));
|
| +
|
| + // Select tabs[1] and verify that the renderer visibility was flipped.
|
| + TabModelUtils.setIndex(getActivity().getCurrentTabModel(), indexOf(tabs[1]));
|
| + assertTrue(mBindingManager.isInBackground(
|
| + tabs[0].getContentViewCore().getCurrentRenderProcessId()));
|
| + assertTrue(mBindingManager.isInForeground(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId()));
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Verifies that the .setProcessInForeground() signal is called correctly when a tab that
|
| + * crashed in background is restored in foreground. This is a regression test for
|
| + * http://crbug.com/399521.
|
| + */
|
| + @LargeTest
|
| + @Feature({"ProcessManagement"})
|
| + public void testCrashInBackground() throws InterruptedException {
|
| + // Create two tabs and wait until they are loaded, so that their renderers are around.
|
| + final Tab[] tabs = new Tab[2];
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Foreground tab.
|
| + ChromeTabCreator tabCreator = getActivity().getCurrentTabCreator();
|
| + tabs[0] = tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_KEYBOARD, null);
|
| + // Background tab.
|
| + tabs[1] = tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_LONGPRESS_BACKGROUND, null);
|
| + // On Svelte devices the background tab would not be loaded automatically, so
|
| + // trigger the load manually.
|
| + tabs[1].show(TabSelectionType.FROM_USER);
|
| + tabs[1].hide();
|
| + }
|
| + });
|
| + ChromeTabUtils.waitForTabPageLoaded(tabs[0], TestHttpServerClient.getUrl(FILE_PATH));
|
| + ChromeTabUtils.waitForTabPageLoaded(tabs[1], TestHttpServerClient.getUrl(FILE_PATH));
|
| +
|
| + // Wait for the new tab animations on phones to finish.
|
| + if (!DeviceFormFactor.isTablet(getActivity())
|
| + && getActivity() instanceof CompositorChromeActivity) {
|
| + final CompositorChromeActivity activity = (CompositorChromeActivity) getActivity();
|
| + assertTrue("Did not finish animation",
|
| + CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + Layout layout = activity.getCompositorViewHolder()
|
| + .getLayoutManager().getActiveLayout();
|
| + return !layout.isLayoutAnimating();
|
| + }
|
| + }));
|
| + }
|
| + getInstrumentation().waitForIdleSync();
|
| +
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Make sure that the renderers were spawned.
|
| + assertTrue(tabs[0].getContentViewCore().getCurrentRenderProcessId() > 0);
|
| + assertTrue(tabs[1].getContentViewCore().getCurrentRenderProcessId() > 0);
|
| +
|
| + // Verify that the renderer of the foreground tab was signalled as visible.
|
| + assertTrue(mBindingManager.isInForeground(
|
| + tabs[0].getContentViewCore().getCurrentRenderProcessId()));
|
| + // Verify that the renderer of the tab loaded in background was signalled as not
|
| + // visible.
|
| + assertTrue(mBindingManager.isInBackground(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId()));
|
| + }
|
| + });
|
| +
|
| + // Kill the renderer and wait for the crash to be noted by the browser process.
|
| + assertTrue(ChildProcessLauncher.crashProcessForTesting(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId()));
|
| +
|
| + assertTrue("Renderer crash wasn't noticed by the browser.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return tabs[1].getContentViewCore().getCurrentRenderProcessId() == 0;
|
| + }
|
| + }));
|
| +
|
| + // Switch to the tab that crashed in background.
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + TabModelUtils.setIndex(getActivity().getCurrentTabModel(), indexOf(tabs[1]));
|
| + }
|
| + });
|
| +
|
| + // Wait until the process is spawned and its visibility is determined.
|
| + assertTrue("Process for the crashed tab was not respawned.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return tabs[1].getContentViewCore().getCurrentRenderProcessId() != 0;
|
| + }
|
| + }));
|
| +
|
| + assertTrue("isInForeground() was not called for the process.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return mBindingManager.setInForegroundWasCalled(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId());
|
| + }
|
| + }));
|
| +
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Verify the visibility of the renderers.
|
| + assertTrue(mBindingManager.isInBackground(
|
| + tabs[0].getContentViewCore().getCurrentRenderProcessId()));
|
| + assertTrue(mBindingManager.isInForeground(
|
| + tabs[1].getContentViewCore().getCurrentRenderProcessId()));
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Verifies that a renderer that crashes in foreground has the correct visibility when
|
| + * recreated.
|
| + */
|
| + @LargeTest
|
| + @Feature({"ProcessManagement"})
|
| + public void testCrashInForeground() throws InterruptedException {
|
| + // Create a tab in foreground and wait until it is loaded.
|
| + final Tab tab = ThreadUtils.runOnUiThreadBlockingNoException(
|
| + new Callable<Tab>() {
|
| + @Override
|
| + public Tab call() throws Exception {
|
| + ChromeTabCreator tabCreator = getActivity().getCurrentTabCreator();
|
| + return tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_KEYBOARD, null);
|
| + }
|
| + });
|
| + ChromeTabUtils.waitForTabPageLoaded(tab, TestHttpServerClient.getUrl(FILE_PATH));
|
| + getInstrumentation().waitForIdleSync();
|
| +
|
| + // Kill the renderer and wait for the crash to be noted by the browser process.
|
| + assertTrue(ChildProcessLauncher.crashProcessForTesting(
|
| + tab.getContentViewCore().getCurrentRenderProcessId()));
|
| +
|
| + assertTrue("Renderer crash wasn't noticed by the browser.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return tab.getContentViewCore().getCurrentRenderProcessId() == 0;
|
| + }
|
| + }));
|
| +
|
| + // Reload the tab, respawning the renderer.
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + tab.reload();
|
| + }
|
| + });
|
| +
|
| + // Wait until the process is spawned and its visibility is determined.
|
| + assertTrue("Process for the crashed tab was not respawned.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return tab.getContentViewCore().getCurrentRenderProcessId() != 0;
|
| + }
|
| + }));
|
| +
|
| + assertTrue("isInForeground() was not called for the process.",
|
| + CriteriaHelper.pollForCriteria(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return mBindingManager.setInForegroundWasCalled(
|
| + tab.getContentViewCore().getCurrentRenderProcessId());
|
| + }
|
| + }));
|
| +
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + // Verify the visibility of the renderer.
|
| + assertTrue(mBindingManager.isInForeground(
|
| + tab.getContentViewCore().getCurrentRenderProcessId()));
|
| + }
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Ensures correctness of the visibilityDetermined() calls, that should be always preceded by
|
| + * setInForeground().
|
| + *
|
| + * Bug: https://crbug.com/474543
|
| + * @LargeTest
|
| + * @Feature({"ProcessManagement"})
|
| + */
|
| + @FlakyTest
|
| + public void testVisibilityDetermined() throws InterruptedException {
|
| + // Create a tab in foreground and wait until it is loaded.
|
| + final Tab fgTab = ThreadUtils.runOnUiThreadBlockingNoException(
|
| + new Callable<Tab>() {
|
| + @Override
|
| + public Tab call() {
|
| + ChromeTabCreator tabCreator = getActivity().getCurrentTabCreator();
|
| + return tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_KEYBOARD, null);
|
| + }});
|
| + ChromeTabUtils.waitForTabPageLoaded(fgTab, TestHttpServerClient.getUrl(FILE_PATH));
|
| + int initialNavigationPid = fgTab.getContentViewCore().getCurrentRenderProcessId();
|
| + // Ensure the following calls happened:
|
| + // - FG - setInForeground(true) - when the tab is created in the foreground
|
| + // - DETERMINED - visibilityDetermined() - after the initial navigation is committed
|
| + assertEquals("FG;DETERMINED;", mBindingManager.getVisibilityCalls(initialNavigationPid));
|
| +
|
| + // Navigate to about:version which requires a different renderer.
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + fgTab.loadUrl(new LoadUrlParams(ABOUT_VERSION_PATH));
|
| + }
|
| + });
|
| + ChromeTabUtils.waitForTabPageLoaded(fgTab, ABOUT_VERSION_PATH);
|
| + int secondNavigationPid = fgTab.getContentViewCore().getCurrentRenderProcessId();
|
| + assertTrue(secondNavigationPid != initialNavigationPid);
|
| + // Ensure the following calls happened:
|
| + // - BG - setInForeground(false) - when the renderer is created for uncommited frame
|
| + // - FG - setInForeground(true) - when the frame is swapped in on commit
|
| + // - DETERMINED - visibilityDetermined() - after the navigation is committed
|
| + assertEquals("BG;FG;DETERMINED;", mBindingManager.getVisibilityCalls(secondNavigationPid));
|
| +
|
| + // Open a tab in the background and load it.
|
| + final Tab bgTab = ThreadUtils.runOnUiThreadBlockingNoException(
|
| + new Callable<Tab>() {
|
| + @Override
|
| + public Tab call() {
|
| + ChromeTabCreator tabCreator = getActivity().getCurrentTabCreator();
|
| + Tab tab = tabCreator.createNewTab(
|
| + new LoadUrlParams(TestHttpServerClient.getUrl(FILE_PATH)),
|
| + TabLaunchType.FROM_LONGPRESS_BACKGROUND, null);
|
| + // On Svelte devices the background tab would not be loaded automatically,
|
| + // so trigger the load manually.
|
| + tab.show(TabSelectionType.FROM_USER);
|
| + tab.hide();
|
| + return tab;
|
| + }});
|
| + ChromeTabUtils.waitForTabPageLoaded(bgTab, TestHttpServerClient.getUrl(FILE_PATH));
|
| + int bgNavigationPid = bgTab.getContentViewCore().getCurrentRenderProcessId();
|
| + // Ensure the following calls happened:
|
| + // - BG - setInForeground(false) - when tab is created in the background
|
| + // - DETERMINED - visibilityDetermined() - after the navigation is committed
|
| + assertEquals("BG;DETERMINED;", mBindingManager.getVisibilityCalls(bgNavigationPid));
|
| + }
|
| +
|
| + @Override
|
| + public void startMainActivity() throws InterruptedException {
|
| + startMainActivityOnBlankPage();
|
| + }
|
| +
|
| + @Override
|
| + protected void setUp() throws Exception {
|
| + super.setUp();
|
| +
|
| + // Hook in the test binding manager.
|
| + mBindingManager = new MockBindingManager();
|
| + ChildProcessLauncher.setBindingManagerForTesting(mBindingManager);
|
| + }
|
| +
|
| + /**
|
| + * @return the index of the given tab in the current tab model
|
| + */
|
| + private int indexOf(Tab tab) {
|
| + return getActivity().getCurrentTabModel().indexOf(tab);
|
| + }
|
| +}
|
|
|