910e62b5创建于 1月15日历史提交
// Copyright 2023 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/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "content/browser/web_contents/web_contents_impl.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/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/background_resource_fetch_histograms.h"

namespace content {
namespace {

using testing::ElementsAre;

// TODO(crbug.com/40244488): When we will add VirtualTestSuites of web tests for
// BackgroundResourceFetch feature, duplicated basic tests should be deleted
// from BackgroundResourceFetchBrowserTest.
class BackgroundResourceFetchBrowserTest : public ContentBrowserTest {
 public:
  BackgroundResourceFetchBrowserTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {blink::features::kBackgroundResourceFetch,
         // Needed to trigger cache-aware loading
         blink::features::kWebFontsCacheAwareTimeoutAdaption},
        /*disabled_features=*/{});
  }
  BackgroundResourceFetchBrowserTest(
      const BackgroundResourceFetchBrowserTest&) = delete;
  BackgroundResourceFetchBrowserTest& operator=(
      const BackgroundResourceFetchBrowserTest&) = delete;

  ~BackgroundResourceFetchBrowserTest() override = default;

 protected:
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  WebContentsImpl* web_contents() const {
    return static_cast<WebContentsImpl*>(shell()->web_contents());
  }
  RenderFrameHostImpl* current_frame_host() {
    return web_contents()->GetPrimaryFrameTree().root()->current_frame_host();
  }

  void StartServerAndNavigateToTestPage() {
    ASSERT_TRUE(embedded_test_server()->Start());
    EXPECT_TRUE(
        NavigateToURL(shell(), embedded_test_server()->GetURL("/hello.html")));
  }

  void TestScriptLoad(const std::string& path, bool expect_success) {
    const std::string script = JsReplace(R"(
      new Promise(resolve => {
          window.resolveTest = resolve;
          const script = document.createElement('script');
          script.src = $1;
          script.addEventListener('error', () => {resolve('load error')});
          document.body.appendChild(script);
        });
    )",
                                         path);
    EXPECT_EQ(expect_success ? "script executed" : "load error",
              EvalJs(shell(), script));
  }

  void TestFontLoad(const std::string& path, bool expect_success) {
    const std::string script = JsReplace(R"(
      new Promise(resolve => {
          const fontface =
              new FontFace('CustomFont', $1);
          document.fonts.add(fontface);
          fontface.load()
            .then(() => {resolve('font loaded');})
            .catch(() => {resolve('load error');});
        });
    )",
                                         path);
    EXPECT_EQ(expect_success ? "font loaded" : "load error",
              EvalJs(shell(), script));
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, ScriptLoad) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;
  // Test normal script load behavior.
  TestScriptLoad("background_resource_fetch/test.js", /*expect_success=*/true);
  FetchHistogramsFromChildProcesses();
  EXPECT_THAT(histograms.GetAllSamples(
                  blink::kBackgroundResourceFetchSupportStatusHistogramName),
              ElementsAre(base::Bucket(
                  blink::BackgroundResourceFetchSupportStatus::kSupported, 1)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, ScriptLoadFailure) {
  StartServerAndNavigateToTestPage();
  // Test script load failure.
  TestScriptLoad("background_resource_fetch/404.js", /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       ScriptLoadFailureAfterResponse) {
  StartServerAndNavigateToTestPage();
  // Test script load failure behavior after receiving a response header.
  TestScriptLoad("background_resource_fetch/invalid-chunked-encoding.js",
                 /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, ScriptLoadTwice) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;

  // Test normal script load behavior.
  TestScriptLoad("background_resource_fetch/test.js?1",
                 /*expect_success=*/true);
  // Test normal script load behavior again.
  TestScriptLoad("background_resource_fetch/test.js?2",
                 /*expect_success=*/true);

  FetchHistogramsFromChildProcesses();
  EXPECT_THAT(histograms.GetAllSamples(
                  blink::kBackgroundResourceFetchSupportStatusHistogramName),
              ElementsAre(base::Bucket(
                  blink::BackgroundResourceFetchSupportStatus::kSupported, 2)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, ScriptLoadStop) {
  StartServerAndNavigateToTestPage();
  // window.stop() should cause script load error.
  EXPECT_EQ("load error", EvalJs(shell(), R"(
        new Promise(resolve => {
            window.resolveTest = resolve;
            const script = document.createElement('script');
            script.src = 'background_resource_fetch/test.js';
            script.addEventListener('error', () => {resolve('load error')});
            document.body.appendChild(script);
            window.stop();
          });
      )"));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       NavigationWhileLoadingScript) {
  StartServerAndNavigateToTestPage();
  EXPECT_TRUE(ExecJs(shell(), R"(
        (() => {
            const script = document.createElement('script');
            script.src = 'slow?10';
            document.body.appendChild(script);
          })();
      )"));

  GURL new_url(embedded_test_server()->GetURL("/hello.html?2"));
  EXPECT_TRUE(ExecJs(shell(), JsReplace("location.replace($1)", new_url)));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       UnupportedSyncRequest) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;

  // Fetch empty.html using sync XMLHttpRequest.
  EXPECT_EQ(200, EvalJs(shell(), R"(
        (() => {
          const request = new XMLHttpRequest();
          request.open('GET', '/hello.html', false);
          request.send(null);
          return request.status;
        })();
      )"));

  FetchHistogramsFromChildProcesses();

  // Sync XMLHttpRequest is not supported. So the kUnsupportedSyncRequest UMA
  // must have been recorded.
  EXPECT_THAT(
      histograms.GetAllSamples(
          blink::kBackgroundResourceFetchSupportStatusHistogramName),
      ElementsAre(base::Bucket(
          blink::BackgroundResourceFetchSupportStatus::kUnsupportedSyncRequest,
          1)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       UnupportedNonGetRequest) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;

  // Fetch empty.html using POST method.
  EXPECT_EQ("", EvalJs(shell(), R"(
        new Promise(async (resolve) => {
            const res = await fetch('empty.html',
                                    {method: 'POST', body: 'body'});
            resolve(await res.text());
          });
      )"));

  FetchHistogramsFromChildProcesses();

  // POST method is not supported. So the kUnsupportedNonGetRequest UMA must
  // have been recorded.
  EXPECT_THAT(
      histograms.GetAllSamples(
          blink::kBackgroundResourceFetchSupportStatusHistogramName),
      ElementsAre(base::Bucket(blink::BackgroundResourceFetchSupportStatus::
                                   kUnsupportedNonGetRequest,
                               1)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       UnupportedNonHttpUrlRequest) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;

  // Fetch chrome-untrusted://example.com/.
  EXPECT_EQ("failed", EvalJs(shell(), R"(
        new Promise(async (resolve) => {
            try {
              await fetch('chrome-untrusted://example.com/');
            } catch (e) {
              resolve('failed');
            }
          });
      )"));

  FetchHistogramsFromChildProcesses();

  // Non HTTP url request not supported. So the UnupportedNonHttpUrlRequest UMA
  // must have been recorded.
  EXPECT_THAT(
      histograms.GetAllSamples(
          blink::kBackgroundResourceFetchSupportStatusHistogramName),
      ElementsAre(base::Bucket(blink::BackgroundResourceFetchSupportStatus::
                                   kUnsupportedNonHttpUrlRequest,
                               1)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       UnupportedKeepAliveRequest) {
  StartServerAndNavigateToTestPage();
  base::HistogramTester histograms;

  // Fetch empty.html with {keepalive: true}.
  EXPECT_EQ("", EvalJs(shell(), R"(
        new Promise(async (resolve) => {
            const res = await fetch('empty.html', {keepalive: true});
            resolve(await res.text());
          });
      )"));

  FetchHistogramsFromChildProcesses();

  // POST method is not supported. So the UnupportedKeepAliveRequest UMA must
  // have been recorded.
  EXPECT_THAT(
      histograms.GetAllSamples(
          blink::kBackgroundResourceFetchSupportStatusHistogramName),
      ElementsAre(base::Bucket(blink::BackgroundResourceFetchSupportStatus::
                                   kUnsupportedKeepAliveRequest,
                               1)));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, FontLoad) {
  StartServerAndNavigateToTestPage();
  // Test font loading behavior that triggers cache-aware loading when
  // WebFontsCacheAwareTimeoutAdaption feature is enabled.
  TestFontLoad("url(background_resource_fetch/SpaceOnly.otf)",
               /*expect_success=*/true);
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, FontLoadFailure) {
  StartServerAndNavigateToTestPage();
  // Test font loading failure behavior that triggers cache-aware loading when
  // WebFontsCacheAwareTimeoutAdaption feature is enabled.
  TestFontLoad("url(background_resource_fetch/not_found.otf)",
               /*expect_success=*/false);
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest,
                       ScriptLoadAfterCrossOriginSameSiteNavigation) {
  StartServerAndNavigateToTestPage();
  GURL url(embedded_test_server()->GetURL("example.com", "/hello.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));

  // Test normal script load behavior.
  TestScriptLoad("background_resource_fetch/test.js", /*expect_success=*/true);

  // Navigate to a cross origin same site page. This is intended to check that
  // `background_resource_fetch_context_` which is attached to the
  // RenderFrameImpl is replaced. If `background_resource_fetch_context_` is not
  // replaced, the network request after the navigation will cause the initiator
  // lock compatibility check failure in the network process.
  GURL new_url(
      embedded_test_server()->GetURL("sub.example.com", "/hello.html"));
  EXPECT_TRUE(ExecJs(shell(), JsReplace("location.replace($1)", new_url)));
  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));

  // Test normal script load behavior after navigation.
  TestScriptLoad("background_resource_fetch/test.js", /*expect_success=*/true);
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, Redirect) {
  StartServerAndNavigateToTestPage();
  EXPECT_EQ("Redirected", EvalJs(shell(), R"(
        new Promise(async (resolve) => {
            const res = await fetch('/background_resource_fetch/redirect');
            resolve(await res.text());
          });
      )"));
}

IN_PROC_BROWSER_TEST_F(BackgroundResourceFetchBrowserTest, RedirectRejected) {
  StartServerAndNavigateToTestPage();
  EXPECT_EQ("fetch failed", EvalJs(shell(), R"(
        new Promise(async (resolve) => {
            try {
              await fetch('/background_resource_fetch/redirect',
                          {redirect: 'error'});
            } catch (e) {
              resolve('fetch failed')
            }
          });
      )"));
}

}  // namespace
}  // namespace content