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 "extensions/common/api/scripts_internal/script_serialization.h"

#include <string_view>

#include "base/test/values_test_util.h"
#include "extensions/common/api/scripts_internal.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions::script_serialization {

using api::scripts_internal::SerializedUserScript;

namespace {

// Returns the SerializedUserScript object from the given `json` blob.
// Note: this will crash if `json` doesn't parse to a valid script.
SerializedUserScript SerializedScriptFromJson(std::string_view json) {
  base::Value value = base::test::ParseJson(json);
  std::optional<SerializedUserScript> serialized_script =
      SerializedUserScript::FromValue(value);
  return std::move(*serialized_script);
}

}  // namespace

// Tests parsing a minimally-specified serialized user script. This ensures all
// defaults are properly set.
TEST(ScriptSerializationUnitTest, ParseMinimalScript) {
  // A set of the minimal required properties, according to
  // scripts_internal.idl.
  constexpr char kMinimalScriptJson[] =
      R"({
           "id": "_dc_minimal_script",
           "js": [{"file": "script.js"}],
           "matches": ["http://matches.example/*"],
           "source": "DYNAMIC_CONTENT_SCRIPT",
           "world": "ISOLATED"
         })";

  SerializedUserScript serialized_script =
      SerializedScriptFromJson(kMinimalScriptJson);

  auto stub_extension = ExtensionBuilder("foo").Build();
  std::unique_ptr<UserScript> script = ParseSerializedUserScript(
      serialized_script, *stub_extension, /*allowed_in_incognito=*/false);

  ASSERT_TRUE(script);

  EXPECT_FALSE(script->match_all_frames());
  EXPECT_EQ(0u, script->css_scripts().size());
  EXPECT_EQ(0u, script->exclude_url_patterns().size());
  EXPECT_EQ(0u, script->exclude_globs().size());
  EXPECT_EQ("_dc_minimal_script", script->id());
  EXPECT_EQ(0u, script->globs().size());
  ASSERT_EQ(1u, script->js_scripts().size());
  EXPECT_EQ("script.js",
            script->js_scripts()[0]->relative_path().AsUTF8Unsafe());
  EXPECT_EQ(UserScript::Content::Source::kFile,
            script->js_scripts()[0]->source());
  EXPECT_THAT(script->url_patterns().ToStringVector(),
              testing::ElementsAre("http://matches.example/*"));
  EXPECT_EQ(mojom::MatchOriginAsFallbackBehavior::kNever,
            script->match_origin_as_fallback());
  EXPECT_EQ(mojom::RunLocation::kDocumentIdle, script->run_location());
  EXPECT_EQ(UserScript::Source::kDynamicContentScript, script->GetSource());
  EXPECT_EQ(mojom::ExecutionWorld::kIsolated, script->execution_world());
}

// Tests parsing a maximally-specified serialized user script. This ensures each
// field is properly deserialized and curried to the outcoming UserScript.
TEST(ScriptSerializationUnitTest, ParseMaximalScript) {
  // A set of all possible properties.
  // Note we explicitly choose non-default options to ensure they are properly
  // read.
  constexpr char kMaximalScriptJson[] =
      R"({
           "allFrames": true,
           "css": [{"file": "style.css"}],
           "excludeMatches": ["http://exclude.example/*"],
           "excludeGlobs": ["*exclude_glob*"],
           "id": "_dc_maximal_script",
           "includeGlobs": ["*include_glob*"],
           "js": [{"file": "script.js"}],
           "matches": ["http://matches.example/*"],
           "matchOriginAsFallback": true,
           "runAt": "document_start",
           "source": "DYNAMIC_CONTENT_SCRIPT",
           "world": "MAIN"
         })";

  SerializedUserScript serialized_script =
      SerializedScriptFromJson(kMaximalScriptJson);

  auto stub_extension = ExtensionBuilder("foo").Build();
  std::unique_ptr<UserScript> script = ParseSerializedUserScript(
      serialized_script, *stub_extension, /*allowed_in_incognito=*/false);

  ASSERT_TRUE(script);

  EXPECT_TRUE(script->match_all_frames());
  ASSERT_EQ(1u, script->css_scripts().size());
  EXPECT_EQ("style.css",
            script->css_scripts()[0]->relative_path().AsUTF8Unsafe());
  EXPECT_EQ(UserScript::Content::Source::kFile,
            script->css_scripts()[0]->source());
  EXPECT_THAT(script->exclude_url_patterns().ToStringVector(),
              testing::ElementsAre("http://exclude.example/*"));
  EXPECT_THAT(script->exclude_globs(), testing::ElementsAre("*exclude_glob*"));
  EXPECT_EQ("_dc_maximal_script", script->id());
  EXPECT_THAT(script->globs(), testing::ElementsAre("*include_glob*"));
  ASSERT_EQ(1u, script->js_scripts().size());
  EXPECT_EQ("script.js",
            script->js_scripts()[0]->relative_path().AsUTF8Unsafe());
  EXPECT_EQ(UserScript::Content::Source::kFile,
            script->js_scripts()[0]->source());
  EXPECT_THAT(script->url_patterns().ToStringVector(),
              testing::ElementsAre("http://matches.example/*"));
  EXPECT_EQ(mojom::MatchOriginAsFallbackBehavior::kAlways,
            script->match_origin_as_fallback());
  EXPECT_EQ(mojom::RunLocation::kDocumentStart, script->run_location());
  EXPECT_EQ(UserScript::Source::kDynamicContentScript, script->GetSource());
  EXPECT_EQ(mojom::ExecutionWorld::kMain, script->execution_world());
}

// Tests serializing a UserScript object to a SerializedUserScript.
TEST(ScriptSerializationUnitTest, SerializeUserScript) {
  auto stub_extension = ExtensionBuilder("foo").Build();
  const int valid_schemes = UserScript::ValidUserScriptSchemes();

  UserScript script;
  script.set_host_id(mojom::HostID(mojom::HostID::HostType::kExtensions,
                                   stub_extension->id()));

  script.set_match_all_frames(true);
  script.css_scripts().push_back(UserScript::Content::CreateFile(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("style.css")),
      stub_extension->GetResourceURL("style.css")));
  script.add_exclude_url_pattern(
      URLPattern(valid_schemes, "http://exclude.example/*"));
  script.add_exclude_glob("*exclude_glob*");
  script.set_id("_dc_user_script");
  script.add_glob("*include_glob*");
  script.js_scripts().push_back(UserScript::Content::CreateFile(
      base::FilePath(), base::FilePath(FILE_PATH_LITERAL("script.js")),
      stub_extension->GetResourceURL("script.js")));
  script.add_url_pattern(URLPattern(valid_schemes, "http://matches.example/*"));
  script.set_match_origin_as_fallback(
      mojom::MatchOriginAsFallbackBehavior::kAlways);
  script.set_run_location(mojom::RunLocation::kDocumentStart);
  script.set_execution_world(mojom::ExecutionWorld::kMain);

  SerializedUserScript serialized = SerializeUserScript(script);
  constexpr char kExpectedJson[] =
      R"({
           "allFrames": true,
           "css": [{"file": "style.css"}],
           "excludeMatches": ["http://exclude.example/*"],
           "excludeGlobs": ["*exclude_glob*"],
           "id": "_dc_user_script",
           "includeGlobs": ["*include_glob*"],
           "js": [{"file": "script.js"}],
           "matches": ["http://matches.example/*"],
           "matchOriginAsFallback": true,
           "runAt": "document_start",
           "source": "DYNAMIC_CONTENT_SCRIPT",
           "world": "MAIN"
         })";
  EXPECT_THAT(serialized.ToValue(), base::test::IsJson(kExpectedJson));
}

// Tests that scripts cannot specify `"match_origin_as_fallback": true` if
// they include match patterns with paths.
TEST(ScriptSerializationUnitTest, DisallowMatchOriginAsFallbackWithPaths) {
  static constexpr char kScriptJson[] =
      R"({
           "id": "_dc_script",
           "js": [{"file": "script.js"}],
           "matches": ["http://matches.example/path"],
           "matchOriginAsFallback": true,
           "source": "DYNAMIC_CONTENT_SCRIPT",
           "world": "ISOLATED"
         })";

  SerializedUserScript serialized_script =
      SerializedScriptFromJson(kScriptJson);

  std::u16string error;
  auto stub_extension = ExtensionBuilder("foo").Build();
  std::unique_ptr<UserScript> script =
      ParseSerializedUserScript(serialized_script, *stub_extension,
                                /*allowed_in_incognito=*/false, &error);

  EXPECT_FALSE(script);
  EXPECT_EQ(error, manifest_errors::kMatchOriginAsFallbackCantHavePaths);
}

}  // namespace extensions::script_serialization