// Copyright 2015 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.chrome.browser;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.IsEqual.equalTo;
import android.content.pm.ActivityInfo;
import android.util.Base64;
import android.view.KeyEvent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabUtils;
import org.chromium.chrome.browser.tab.TabUtils.UseDesktopUserAgentCaller;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.OmniboxTestUtils;
import org.chromium.chrome.test.util.browser.TabLoadObserver;
import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.content_public.browser.test.util.UiUtils;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
import org.chromium.net.test.util.TestWebServer;
import org.chromium.ui.test.util.UiDisableIf;
import org.chromium.ui.test.util.UiRestriction;
import org.chromium.url.GURL;
import org.chromium.url.Origin;
import java.net.URL;
import java.util.Locale;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/** Navigate in UrlBar tests. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class NavigateTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
private static final String HTTPS_SCHEME = "https://";
private OmniboxTestUtils mOmnibox;
private EmbeddedTestServer mTestServer;
@Before
public void setUp() {
mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);
mTestServer =
EmbeddedTestServer.createAndStartHTTPSServer(
ApplicationProvider.getApplicationContext(), ServerCertificate.CERT_OK);
mOmnibox = new OmniboxTestUtils(mActivityTestRule.getActivity());
}
private void navigateAndObserve(final String url) throws Exception {
new TabLoadObserver(mActivityTestRule.getActivity().getActivityTab()).fullyLoadUrl(url);
// Note: Omnibox does not present the scheme.
mOmnibox.checkText(equalTo(expectedLocation(url)), null);
CriteriaHelper.pollUiThread(
() -> {
Criteria.checkThat(
"Tab url wrong",
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()),
Matchers.is(url));
});
}
/**
* Types the passed text in the omnibox to trigger a navigation. You can pass a URL or a search
* term. This code triggers suggestions and prerendering; unless you are testing these features
* specifically, you should use loadUrl() which is less prone to flakyness.
*
* @param url The URL to navigate to.
* @param expectedTitle Title that the page is expected to have. Shouldn't be set if the page
* load causes a redirect.
* @return the URL in the omnibox.
*/
private String typeInOmniboxAndNavigate(final String url, final String expectedTitle)
throws Exception {
mOmnibox.requestFocus();
mOmnibox.typeText(url, false);
mOmnibox.checkSuggestionsShown();
// Loads the url.
TabLoadObserver observer =
new TabLoadObserver(
mActivityTestRule.getActivity().getActivityTab(), expectedTitle, null);
mOmnibox.sendKey(KeyEvent.KEYCODE_ENTER);
observer.assertLoaded();
// The URL has been set before the page notification was broadcast, so it is safe to access.
return mOmnibox.getText();
}
/**
* @return the expected contents of the location bar after navigating to url.
*/
private String expectedLocation(String url) {
Assert.assertTrue("url was:" + url, url.startsWith(HTTPS_SCHEME));
return url.replaceFirst(HTTPS_SCHEME, "");
}
/** Verify Selection on the Location Bar. */
@Test
@MediumTest
@Feature({"Navigation", "Main"})
public void testNavigate() throws Exception {
String url = mTestServer.getURL("/chrome/test/data/android/navigate/simple.html");
String result = typeInOmniboxAndNavigate(url, "Simple");
Assert.assertEquals(expectedLocation(url), result);
}
@Test
@Restriction(UiRestriction.RESTRICTION_TYPE_TABLET)
@MediumTest
@Feature({"Navigation"})
public void testNavigateMany() throws Exception {
final String[] urls =
mTestServer.getURLs(
"/chrome/test/data/android/navigate/one.html",
"/chrome/test/data/android/navigate/two.html",
"/chrome/test/data/android/navigate/three.html");
final String[] titles = {"One", "Two", "Three"};
final int repeats = 3;
for (int i = 0; i < repeats; i++) {
for (int j = 0; j < urls.length; j++) {
String result = typeInOmniboxAndNavigate(urls[j], titles[j]);
Assert.assertEquals(expectedLocation(urls[j]), result);
}
}
}
/** Verify Selection on the Location Bar in Landscape Mode */
@Test
@MediumTest
@Feature({"Navigation"})
public void testNavigateLandscape() throws Exception {
mActivityTestRule
.getActivity()
.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
String url = mTestServer.getURL("/chrome/test/data/android/navigate/simple.html");
String result = typeInOmniboxAndNavigate(url, "Simple");
Assert.assertEquals(expectedLocation(url), result);
// Reset device orientation.
mActivityTestRule
.getActivity()
.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
/** Verify New Tab Open and Navigate. */
@Test
@MediumTest
@Feature({"Navigation"})
@DisabledTest(message = "https://crbug.com/346968609")
public void testOpenAndNavigate() throws Exception {
final String url = mTestServer.getURL("/chrome/test/data/android/navigate/simple.html");
navigateAndObserve(url);
final int tabCount = mActivityTestRule.getActivity().getCurrentTabModel().getCount();
ChromeTabUtils.newTabFromMenu(
InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity());
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
Assert.assertEquals(
"Wrong number of tabs",
tabCount + 1,
mActivityTestRule.getActivity().getCurrentTabModel().getCount());
String result = typeInOmniboxAndNavigate(url, "Simple");
Assert.assertEquals(expectedLocation(url), result);
}
/** Test Opening a link and verify that the desired page is loaded. */
@Test
@MediumTest
@Feature({"Navigation"})
@DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/339299609
public void testOpenLink() throws Exception {
String url1 = mTestServer.getURL("/chrome/test/data/android/google.html");
String url2 = mTestServer.getURL("/chrome/test/data/android/about.html");
navigateAndObserve(url1);
mActivityTestRule.assertWaitForPageScaleFactorMatch(0.5f);
Tab tab = mActivityTestRule.getActivity().getActivityTab();
DOMUtils.clickNode(tab.getWebContents(), "aboutLink");
ChromeTabUtils.waitForTabPageLoaded(tab, url2);
Assert.assertEquals(
"Desired Link not open",
url2,
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
}
/** Test 'Request Desktop Site' option properly affects UA client hints */
@Test
@MediumTest
@Feature({"Navigation"})
@CommandLineFlags.Add({"enable-features=UserAgentClientHint"})
// TODO(crbug.com/40612550) Remove switch when UA-CH-* launched.
public void testRequestDesktopSiteClientHints() throws Exception {
String url1 =
mTestServer.getURL(
"/set-header?Accept-CH: sec-ch-ua-arch,sec-ch-ua-platform,sec-ch-ua-model");
String url2 =
mTestServer.getURL(
"/echoheader?sec-ch-ua-arch&sec-ch-ua-mobile&sec-ch-ua-model&sec-ch-ua-platform");
final Tab tab = mActivityTestRule.getActivity().getActivityTab();
navigateAndObserve(url1);
ChromeTabUtils.waitForTabPageLoaded(tab, url1);
navigateAndObserve(url2);
ThreadUtils.runOnUiThreadBlocking(
() ->
TabUtils.switchUserAgent(
tab, /* switchToDesktop= */ true, UseDesktopUserAgentCaller.OTHER));
ChromeTabUtils.waitForTabPageLoaded(tab, url2);
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
// Note: |content| is JSON, hence lots of escaping.
Assert.assertEquals(
"Proper headers",
"\"\\\"x86\\\"\\n" + "?0\\n" + "\\\"\\\"\\n" + "\\\"Linux\\\"\"",
content);
}
/** Test 'Request Desktop Site' option properly affects UA client hints with Critical-CH */
@Test
@MediumTest
@Feature({"Navigation"})
@CommandLineFlags.Add({"enable-features=UserAgentClientHint, CriticalClientHint"})
// TODO(crbug.com/40612550) Remove switch when UA-CH-* launched.
public void testRequestDesktopSiteCriticalClientHints() throws Exception {
// TODO(crbug.com/40153192): Move EchoCriticalHeader request handler here when
// implemented
String url = mTestServer.getURL("/echocriticalheader");
final Tab tab = mActivityTestRule.getActivity().getActivityTab();
navigateAndObserve(url);
ThreadUtils.runOnUiThreadBlocking(
() ->
TabUtils.switchUserAgent(
tab, /* switchToDesktop= */ true, UseDesktopUserAgentCaller.OTHER));
ChromeTabUtils.waitForTabPageLoaded(tab, url);
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
// Note: |content| is JSON, hence lots of escaping.
Assert.assertEquals("Proper headers", "\"?0\\\"Linux\\\"\"", content);
}
/**
* Test Opening a link and verify that TabObserver#onPageLoadStarted gives the old and new URL.
*/
@Test
@MediumTest
@Feature({"Navigation"})
@DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/339299609
public void testTabObserverOnPageLoadStarted() throws Exception {
final String url1 = mTestServer.getURL("/chrome/test/data/android/google.html");
final String url2 = mTestServer.getURL("/chrome/test/data/android/about.html");
navigateAndObserve(url1);
mActivityTestRule.assertWaitForPageScaleFactorMatch(0.5f);
TabObserver onPageLoadStartedObserver =
new EmptyTabObserver() {
@Override
public void onPageLoadStarted(Tab tab, GURL newUrl) {
tab.removeObserver(this);
Assert.assertEquals(url1, ChromeTabUtils.getUrlStringOnUiThread(tab));
Assert.assertEquals(url2, newUrl.getSpec());
}
};
Tab tab = mActivityTestRule.getActivity().getActivityTab();
ThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver));
DOMUtils.clickNode(tab.getWebContents(), "aboutLink");
ChromeTabUtils.waitForTabPageLoaded(tab, url2);
Assert.assertEquals(
"Desired Link not open",
url2,
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
}
/**
* Test re-direct functionality for a web-page.
*
* @throws Exception
*/
@Test
@MediumTest
@Feature({"Navigation"})
public void testNavigateRedirect() throws Exception {
final String initialUrl =
mTestServer.getURL("/chrome/test/data/android/redirect/about.html");
final String redirectedUrl =
mTestServer.getURL("/chrome/test/data/android/redirect/one.html");
typeInOmniboxAndNavigate(initialUrl, null);
CriteriaHelper.pollInstrumentationThread(
() -> {
Criteria.checkThat(
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()),
Matchers.is(redirectedUrl));
});
}
/**
* Test fallback works as intended and that we can go back to the original URL even when
* redirected via Java redirection.
*/
@Test
@MediumTest
@Feature({"Navigation"})
public void testIntentFallbackRedirection() throws Exception {
final String fallbackUrl =
mTestServer.getURL("/chrome/test/data/android/redirect/about.html");
final String redirectUrl =
"intent://non_existent/#Intent;scheme=non_existent;"
+ "S.browser_fallback_url="
+ fallbackUrl
+ ";end";
final String initialUrl =
mTestServer.getURL(
"/chrome/test/data/android/redirect/js_redirect.html"
+ "?replace_text="
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8("PARAM_URL"),
Base64.URL_SAFE)
+ ":"
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8(redirectUrl),
Base64.URL_SAFE));
final String targetUrl = mTestServer.getURL("/chrome/test/data/android/redirect/one.html");
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// We should start on the homepage, which is something other than our test page.
String originalUrl =
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab());
Criteria.checkThat(originalUrl, Matchers.not(targetUrl));
typeInOmniboxAndNavigate(initialUrl, null);
// Now intent fallback should be triggered assuming 'non_existent' scheme cannot be handled.
CriteriaHelper.pollInstrumentationThread(
() -> {
Criteria.checkThat(
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()),
Matchers.is(targetUrl));
});
// Check if Java redirections were removed from the history.
// Note that if we try to go back in the test: NavigateToEntry() is called, but
// DidNavigate() does not get called. But in real cases we can go back to initial page
// without any problem.
// TODO(changwan): figure out why we cannot go back on this test.
int index =
mActivityTestRule
.getActivity()
.getActivityTab()
.getWebContents()
.getNavigationController()
.getLastCommittedEntryIndex();
Assert.assertEquals(1, index);
String previousNavigationUrl =
mActivityTestRule
.getActivity()
.getActivityTab()
.getWebContents()
.getNavigationController()
.getEntryAtIndex(0)
.getUrl()
.getSpec();
Assert.assertEquals(originalUrl, previousNavigationUrl);
}
/** Test navigating back. */
@Test
@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
@MediumTest
@Feature({"Navigation"})
public void testNavigateBack() throws Exception {
final String[] urls = {
mTestServer.getURL("/chrome/test/data/android/navigate/one.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/two.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/three.html")
};
for (String url : urls) {
navigateAndObserve(url);
}
final int repeats = 3;
final ToolbarManager toolbarManager = mActivityTestRule.getActivity().getToolbarManager();
for (int i = 0; i < repeats; i++) {
Assert.assertNull(
"Back button is invisible in phone toolbar",
mActivityTestRule.getActivity().findViewById(R.id.back_button));
Assert.assertEquals(
"Tab should be able to be navigated back",
Boolean.TRUE,
toolbarManager.getHandleBackPressChangedSupplier().get());
Assert.assertTrue(
"Tab has been navigated back",
ThreadUtils.runOnUiThreadBlocking(toolbarManager::back));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
}
Assert.assertEquals(
"Tab should be unable to be navigated back",
Boolean.FALSE,
toolbarManager.getHandleBackPressChangedSupplier().get());
Assert.assertNull(
"Back button is invisible in phone toolbar",
mActivityTestRule.getActivity().findViewById(R.id.back_button));
}
/** Test back and forward buttons. */
@Test
@Restriction(UiRestriction.RESTRICTION_TYPE_TABLET)
@MediumTest
@Feature({"Navigation"})
public void testNavigateBackAndForwardButtons() throws Exception {
final String[] urls = {
mTestServer.getURL("/chrome/test/data/android/navigate/one.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/two.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/three.html")
};
for (String url : urls) {
navigateAndObserve(url);
}
final int repeats = 3;
final ToolbarManager toolbarManager = mActivityTestRule.getActivity().getToolbarManager();
for (int i = 0; i < repeats; i++) {
onView(withId(R.id.back_button)).check(matches(isEnabled()));
Assert.assertEquals(
"Tab should be able to be navigated back",
Boolean.TRUE,
toolbarManager.getHandleBackPressChangedSupplier().get());
TouchCommon.singleClickView(
mActivityTestRule.getActivity().findViewById(R.id.back_button));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
Assert.assertEquals(
String.format(
Locale.US,
"URL mismatch after pressing back button for the 1st time in repetition"
+ "%d.",
i),
urls[1],
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
TouchCommon.singleClickView(
mActivityTestRule.getActivity().findViewById(R.id.back_button));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
Assert.assertEquals(
String.format(
Locale.US,
"URL mismatch after pressing back button for the 2nd time in repetition"
+ "%d.",
i),
urls[0],
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
TouchCommon.singleClickView(
mActivityTestRule.getActivity().findViewById(R.id.forward_button));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
Assert.assertEquals(
String.format(
Locale.US,
"URL mismatch after pressing fwd button for the 1st time in repetition"
+ "%d.",
i),
urls[1],
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
TouchCommon.singleClickView(
mActivityTestRule.getActivity().findViewById(R.id.forward_button));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
Assert.assertEquals(
String.format(
Locale.US,
"URL mismatch after pressing fwd button for the 2nd time in repetition"
+ "%d.",
i),
urls[2],
ChromeTabUtils.getUrlStringOnUiThread(
mActivityTestRule.getActivity().getActivityTab()));
}
for (int i = 0; i < repeats; i++) {
onView(withId(R.id.back_button)).check(matches(isEnabled()));
Assert.assertEquals(
"Tab should be able to be navigated back",
Boolean.TRUE,
toolbarManager.getHandleBackPressChangedSupplier().get());
TouchCommon.singleClickView(
mActivityTestRule.getActivity().findViewById(R.id.back_button));
UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
}
Assert.assertEquals(
"Tab should be unable to be navigated back",
Boolean.FALSE,
toolbarManager.getHandleBackPressChangedSupplier().get());
onView(withId(R.id.back_button)).check(matches(Matchers.not(isEnabled())));
}
/** Test back with tab switcher. */
@Test
@MediumTest
@Feature({"Navigation"})
@DisabledTest(message = "https://crbug.com/1410635")
@DisableFeatures({ChromeFeatureList.BACK_GESTURE_REFACTOR})
public void testNavigateBackWithTabSwitcher() throws Exception {
final String[] urls = {
mTestServer.getURL("/chrome/test/data/android/navigate/one.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/two.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/three.html")
};
for (String url : urls) {
navigateAndObserve(url);
}
String histogram = BackPressManager.getHistogramForTesting();
HistogramWatcher startSurfaceHistogram =
HistogramWatcher.newSingleRecordWatcher(
histogram,
BackPressManager.getHistogramValue(BackPressHandler.Type.START_SURFACE));
HistogramWatcher tabSwitcherHistogram =
HistogramWatcher.newSingleRecordWatcher(
histogram,
BackPressManager.getHistogramValue(BackPressHandler.Type.TAB_SWITCHER));
ChromeTabbedActivity cta = mActivityTestRule.getActivity();
TabUiTestHelper.enterTabSwitcher(cta);
Assert.assertTrue(cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
ThreadUtils.runOnUiThreadBlocking(
() -> {
mActivityTestRule.getActivity().getOnBackPressedDispatcher().onBackPressed();
});
CriteriaHelper.pollInstrumentationThread(
() -> {
int type =
mActivityTestRule
.getActivity()
.getLayoutManager()
.getActiveLayoutType();
Assert.assertEquals(LayoutType.BROWSING, type);
});
try {
startSurfaceHistogram.assertExpected();
} catch (AssertionError e) {
tabSwitcherHistogram.assertExpected(
"Either start surface or tab switcher handles back press.");
}
}
/** Test back with tab switcher. */
@Test
@MediumTest
@Feature({"Navigation"})
@EnableFeatures({ChromeFeatureList.BACK_GESTURE_REFACTOR})
@DisabledTest(message = "https://crbug.com/1410635")
public void testNavigateBackWithTabSwitcher_BackPressRefactor() throws Exception {
// Disable iph
ThreadUtils.runOnUiThreadBlocking(
() -> {
BackPressManager backPressManager =
mActivityTestRule.getActivity().getBackPressManagerForTesting();
backPressManager.removeHandler(BackPressHandler.Type.TEXT_BUBBLE);
});
testNavigateBackWithTabSwitcher();
}
@Test
@DisableIf.Build(hardware_is = "sprout", message = "fails on android-one: crbug.com/540723")
@MediumTest
@Feature({"Navigation"})
public void testWindowOpenUrlSpoof() throws Exception {
// TODO(jbudorick): Convert this from TestWebServer to EmbeddedTestServer.
TestWebServer webServer = TestWebServer.start();
try {
// Make sure that we start with one tab.
final TabModel model =
mActivityTestRule.getActivity().getTabModelSelector().getModel(false);
final Semaphore urlServedSemaphore = new Semaphore(0);
Runnable checkAction =
() -> {
final Tab tab = TabModelUtils.getCurrentTab(model);
// Make sure that we are showing the spoofed data and a blank URL.
String url = getTabUrlOnUIThread(tab);
boolean spoofedUrl = "".equals(url) || "about:blank".equals(url);
Assert.assertTrue("URL Spoofed", spoofedUrl);
Assert.assertEquals(
"Not showing mocked content", "\"Spoofed\"", getTabBodyText(tab));
urlServedSemaphore.release();
};
// Mock out the test URL
final String mockedUrl =
webServer.setResponseWithRunnableAction(
"/mockme.html",
"<html> <head> <meta name=\"viewport\" "
+ " content=\"initial-scale=0.75,maximum-scale=0.75,user-scalable=no\">"
+ " </head> <body>Real</body></html>",
null,
checkAction);
// Navigate to the spoofable URL
mActivityTestRule.loadUrl(
UrlUtils.encodeHtmlDataUri(
"<head> <meta name=\"viewport\" "
+ " content=\"initial-scale=0.5,maximum-scale=0.5,user-scalable=no\"></head><script>"
+ " function spoof() { var w = open(); w.opener = null; "
+ " w.document.write('Spoofed'); w.location = '"
+ mockedUrl
+ "'"
+ " }"
+ "</script>"
+ "<body id='body' onclick='spoof()'></body>"));
mActivityTestRule.assertWaitForPageScaleFactorMatch(0.5f);
// Click the page, which triggers the URL load.
DOMUtils.clickNode(mActivityTestRule.getActivity().getCurrentWebContents(), "body");
// Wait for the proper URL to be served.
Assert.assertTrue(urlServedSemaphore.tryAcquire(5, TimeUnit.SECONDS));
// Wait for the url to change.
final Tab tab = TabModelUtils.getCurrentTab(model);
mActivityTestRule.assertWaitForPageScaleFactorMatch(0.75f);
CriteriaHelper.pollInstrumentationThread(
() -> {
Criteria.checkThat(getTabUrlOnUIThread(tab), Matchers.is(mockedUrl));
},
5000,
50);
// Make sure that we're showing new content now.
Assert.assertEquals("Still showing spoofed data", "\"Real\"", getTabBodyText(tab));
} finally {
webServer.shutdown();
}
}
/**
* Test that if the browser launches a renderer initiated intent towards itself, the url load
* will be renderer initiated and has the correct origin.
*
* @throws Exception
*/
@Test
@MediumTest
@Feature({"Navigation"})
@DisabledTest(message = "crbug.com/1130419")
public void testRendererInitiatedIntentNavigate() throws Exception {
final String finalUrl =
mTestServer.getURL("/chrome/test/data/android/renderer_initiated/final.html");
// The launched intent will have the following format:
// android-app://package_name/xx.xx.xx.xx:xxxx/testDirs/final.html.
final String intentUrl =
"android-app://"
+ ContextUtils.getApplicationContext().getPackageName()
+ "/"
+ finalUrl.replace("://", "/");
// The second page will launch the |intentUrl| to load |finalUrl|.
final String secondUrl =
mTestServer.getURL(
"/chrome/test/data/android/renderer_initiated/renderer_initiated.html?replace_text="
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8("URL"), Base64.URL_SAFE)
+ ":"
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8(intentUrl),
Base64.URL_SAFE));
// Passing |secondUrl| to the first page, so that clicking on the link will trigger the
// renderer initiated intent.ss
final String firstUrl =
mTestServer.getURL(
"/chrome/test/data/android/renderer_initiated/first.html"
+ "?replace_text="
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8("PARAM_URL"),
Base64.URL_SAFE)
+ ":"
+ Base64.encodeToString(
ApiCompatibilityUtils.getBytesUtf8(secondUrl),
Base64.URL_SAFE));
navigateAndObserve(firstUrl);
mActivityTestRule.assertWaitForPageScaleFactorMatch(0.5f);
TabObserver onPageLoadStartedObserver =
new EmptyTabObserver() {
@Override
public void onLoadUrl(
Tab tab, LoadUrlParams params, LoadUrlResult loadUrlResult) {
tab.removeObserver(this);
// Check that the final URL will be loaded properly, and the navigation
// is renderer initiated and has the correct origin.
Assert.assertEquals(finalUrl, params.getUrl());
Assert.assertEquals(true, params.getIsRendererInitiated());
try {
URL url = new URL(finalUrl);
Origin origin = params.getInitiatorOrigin();
Assert.assertEquals(url.getHost(), origin.getHost());
} catch (Exception e) {
Assert.fail("Cannot parse URL:" + finalUrl);
}
}
};
Tab tab = mActivityTestRule.getActivity().getActivityTab();
ThreadUtils.runOnUiThreadBlocking(() -> tab.addObserver(onPageLoadStartedObserver));
DOMUtils.clickNode(tab.getWebContents(), "rendererInitiated");
ChromeTabUtils.waitForTabPageLoaded(tab, finalUrl);
}
private String getTabUrlOnUIThread(final Tab tab) {
return ThreadUtils.runOnUiThreadBlocking(
() -> ChromeTabUtils.getUrlStringOnUiThread(tab));
}
private String getTabBodyText(Tab tab) {
try {
return JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.innerText");
} catch (Exception ex) {
assert false : "Unexpected Exception";
}
return null;
}
}