blob: de7798366fbb60936a840e4772920052eddcff0c [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
using content::URLLoaderInterceptor;
namespace {
constexpr char kBaseDataDir[] = "content/test/data/origin_trials/";
void NavigateViaRenderer(content::WebContents* web_contents, const GURL& url) {
EXPECT_TRUE(
content::ExecJs(web_contents->GetPrimaryMainFrame(),
base::StrCat({"location.href='", url.spec(), "';"})));
// Enqueue a no-op script execution, which will block until the navigation
// initiated above completes.
EXPECT_TRUE(content::ExecJs(web_contents->GetPrimaryMainFrame(), "true"));
EXPECT_TRUE(content::WaitForLoadStop(web_contents));
EXPECT_EQ(web_contents->GetLastCommittedURL(), url);
}
} // namespace
namespace content {
class OriginTrialsBrowserTest : public content::ContentBrowserTest {
public:
OriginTrialsBrowserTest() : ContentBrowserTest() {}
OriginTrialsBrowserTest(const OriginTrialsBrowserTest&) = delete;
OriginTrialsBrowserTest& operator=(const OriginTrialsBrowserTest&) = delete;
~OriginTrialsBrowserTest() override {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kExposeInternalsForTesting);
}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
// We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since
// the origin trial token in the response is associated with a fixed
// origin, whereas EmbeddedTestServer serves content on a random port.
url_loader_interceptor_ =
std::make_unique<URLLoaderInterceptor>(base::BindLambdaForTesting(
[&](URLLoaderInterceptor::RequestParams* params) -> bool {
URLLoaderInterceptor::WriteResponse(
base::StrCat({kBaseDataDir, params->url_request.url.path()}),
params->client.get());
return true;
}));
}
void TearDownOnMainThread() override {
url_loader_interceptor_.reset();
ContentBrowserTest::TearDownOnMainThread();
}
RenderFrameHost* GetFrameByName(const std::string frame_name) {
return FrameMatchingPredicate(
shell()->web_contents()->GetPrimaryPage(),
base::BindRepeating(FrameMatchesName, frame_name));
}
RenderFrameHost* GetPrimaryMainFrame() {
return shell()->web_contents()->GetPrimaryMainFrame();
}
testing::AssertionResult HasTrialEnabled(RenderFrameHost* frame) {
// Test if we can invoke normalMethod(), which is only available when the
// Frobulate OT is enabled.
return content::ExecJs(frame,
"internals.originTrialsTest().normalMethod();");
}
testing::AssertionResult HasNavigationTrialEnabled(RenderFrameHost* frame) {
// Test if we can invoke navigationMethod(), which is only available when
// the FrobulateNavigation OT is enabled.
return content::ExecJs(frame,
"internals.originTrialsTest().navigationMethod();");
}
private:
std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_;
};
IN_PROC_BROWSER_TEST_F(OriginTrialsBrowserTest, Basic) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("https://example.test/basic.html")));
EXPECT_TRUE(HasTrialEnabled(GetPrimaryMainFrame()));
}
IN_PROC_BROWSER_TEST_F(OriginTrialsBrowserTest,
NonNavigationTrialNotActivatedAcrossNavigations) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("https://example.test/basic.html")));
EXPECT_TRUE(HasTrialEnabled(GetPrimaryMainFrame()));
NavigateViaRenderer(shell()->web_contents(),
GURL("https://other.test/notrial.html"));
EXPECT_FALSE(HasTrialEnabled(GetPrimaryMainFrame()));
}
IN_PROC_BROWSER_TEST_F(OriginTrialsBrowserTest, Navigation) {
EXPECT_TRUE(
NavigateToURL(shell(), GURL("https://example.test/navigation.html")));
EXPECT_TRUE(HasNavigationTrialEnabled(GetPrimaryMainFrame()));
}
IN_PROC_BROWSER_TEST_F(OriginTrialsBrowserTest,
NavigationTrialActivatedAcrossNavigations) {
EXPECT_TRUE(
NavigateToURL(shell(), GURL("https://example.test/navigation.html")));
EXPECT_TRUE(HasNavigationTrialEnabled(GetPrimaryMainFrame()));
NavigateViaRenderer(shell()->web_contents(),
GURL("https://other.test/notrial.html"));
// Navigation trial should be enabled after navigating from navigation.html,
// because it is a cross-navigation OT.
EXPECT_TRUE(HasNavigationTrialEnabled(GetPrimaryMainFrame()));
NavigateViaRenderer(shell()->web_contents(),
GURL("https://other.test/basic.html"));
// Navigation trial should not be enabled after a second navigation, because
// cross-navigation OTs should only be forwarded to immediate navigations from
// where the trial was activated.
EXPECT_FALSE(HasNavigationTrialEnabled(GetPrimaryMainFrame()));
}
const char kCallWorkerScript[] =
"(() => {"
" const worker = new Worker('/worker.js');"
" const waitResult = new Promise((resolve, reject) => {"
" worker.onmessage = function(e) {"
" (e.data?resolve:reject)(`return ${e.data} from worker`);"
" };"
" });"
" worker.postMessage('ping');"
" return waitResult;"
"})()";
class ForceEnabledOriginTrialsBrowserTest
: public OriginTrialsBrowserTest,
public testing::WithParamInterface<bool>,
public WebContentsObserver {
public:
ForceEnabledOriginTrialsBrowserTest() = default;
~ForceEnabledOriginTrialsBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
OriginTrialsBrowserTest::SetUpCommandLine(command_line);
if (disable_site_isolation_)
command_line->AppendSwitch(switches::kDisableSiteIsolation);
}
void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override {
if (navigation_handle->GetURL() == url_to_enable_trial_) {
navigation_handle->ForceEnableOriginTrials(
std::vector<std::string>({"Frobulate"}));
}
}
void set_url_to_enable_trial(const GURL& url) { url_to_enable_trial_ = url; }
protected:
bool disable_site_isolation_ = GetParam();
const GURL main_url_ =
GURL("https://example.test/force_enabled_main_frame.html");
private:
GURL url_to_enable_trial_;
};
IN_PROC_BROWSER_TEST_P(ForceEnabledOriginTrialsBrowserTest,
ForceEnabledOriginTrials_MainPage) {
set_url_to_enable_trial(main_url_);
Observe(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
// Trial should be enabled for main frame.
EXPECT_TRUE(HasTrialEnabled(GetPrimaryMainFrame()));
// OT are enabled per-frame. Subframes should not have OT.
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("same-origin")));
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("cross-origin")));
EXPECT_FALSE(ExecJs(GetFrameByName("same-origin"), kCallWorkerScript));
// With site isolation, the cross-site iframe on |main_url_| will get its own
// process. Otherwise, we'll only get one main frame process.
ASSERT_EQ(AreAllSitesIsolatedForTesting() ? 2 : 1,
RenderProcessHost::GetCurrentRenderProcessCountForTesting());
// OT does not persist when we navigated away.
NavigateViaRenderer(shell()->web_contents(),
GURL("https://other.test/notrial.html"));
EXPECT_FALSE(HasTrialEnabled(GetPrimaryMainFrame()));
}
IN_PROC_BROWSER_TEST_P(ForceEnabledOriginTrialsBrowserTest,
ForceEnabledOriginTrials_IframeInPage) {
set_url_to_enable_trial(main_url_.Resolve("/notrial.html"));
Observe(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
// Main frame does not have trial.
EXPECT_FALSE(HasTrialEnabled(GetPrimaryMainFrame()));
// Same-origin frame (which loads notrial.html) has trial.
EXPECT_TRUE(HasTrialEnabled(GetFrameByName("same-origin")));
// Cross-origin frame has no trial.
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("cross-origin")));
// Worker in same-origin frame (which loads notrial.html>worker.js) has trial.
EXPECT_TRUE(ExecJs(GetFrameByName("same-origin"), kCallWorkerScript));
// When Iframe navigates away, it loses origin trial.
const GURL url("https://other.test/notrial.html");
TestNavigationObserver navigation_observer(url);
navigation_observer.WatchExistingWebContents();
ASSERT_TRUE(ExecJs(GetFrameByName("same-origin"),
content::JsReplace("location.href=$1", url.spec())));
navigation_observer.WaitForNavigationFinished();
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("same-origin")));
}
IN_PROC_BROWSER_TEST_P(ForceEnabledOriginTrialsBrowserTest,
ForceEnabledOriginTrials_InjectedIframe) {
const GURL frame_url("https://newly-loaded.test/notrial.html");
set_url_to_enable_trial(frame_url);
Observe(shell()->web_contents());
EXPECT_TRUE(NavigateToURL(shell(), main_url_));
// Main frame and all iframes don't have trial.
EXPECT_FALSE(HasTrialEnabled(GetPrimaryMainFrame()));
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("same-origin")));
EXPECT_FALSE(HasTrialEnabled(GetFrameByName("cross-origin")));
EXPECT_FALSE(ExecJs(GetFrameByName("same-origin"), kCallWorkerScript));
// Create an iframe with origin trial and wait for it to load
TestNavigationObserver navigation_observer(frame_url);
navigation_observer.WatchExistingWebContents();
ASSERT_TRUE(ExecJs(
GetFrameByName("same-origin"),
content::JsReplace("{"
" const ifrm = document.createElement('iframe');"
" ifrm.name='new-frame';"
" ifrm.src=$1;"
" document.body.appendChild(ifrm);"
"}",
frame_url.spec())));
navigation_observer.WaitForNavigationFinished();
// The newly created iframe should have origin trial.
EXPECT_TRUE(HasTrialEnabled(GetFrameByName("new-frame")));
}
INSTANTIATE_TEST_SUITE_P(SiteIsolation,
ForceEnabledOriginTrialsBrowserTest,
/*disable_site_isolation=*/::testing::Bool());
} // namespace content