910e62b5创建于 1月15日历史提交
// 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