blob: 7f62cd315338de992bd14ac4a8091ec9c77b6b13 [file] [log] [blame]
// 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