blob: aa1465a091006f0e0482a98b52dd202556ccdb43 [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/browser/extension_user_script_loader.h"
#include "base/one_shot_event.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/scripting_constants.h"
#include "extensions/browser/scripting_utils.h"
#include "extensions/browser/state_store.h"
#include "extensions/browser/user_script_manager.h"
#include "net/dns/mock_host_resolver.h"
namespace extensions {
namespace {
// A StateStore entry using the legacy format that relied on
// api::content_scripts::ContentScript and hand-modification.
constexpr char kOldFormatEntry[] =
R"([{
"all_frames": true,
"exclude_matches": ["http://exclude.example/*"],
"id": "_dc_foo",
"js": ["script.js"],
"match_origin_as_fallback": true,
"matches": ["http://example.com/*"],
"run_at": "document_end",
"world":"ISOLATED"
}])";
} // namespace
class ExtensionUserScriptLoaderBrowserTest : public ExtensionApiTest {
public:
ExtensionUserScriptLoaderBrowserTest() = default;
~ExtensionUserScriptLoaderBrowserTest() override = default;
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(StartEmbeddedTestServer());
}
StateStore* dynamic_scripts_store() {
return extension_system()->dynamic_user_scripts_store();
}
ExtensionSystem* extension_system() {
return ExtensionSystem::Get(profile());
}
void FlushScriptStore() {
base::RunLoop run_loop;
dynamic_scripts_store()->FlushForTesting(run_loop.QuitWhenIdleClosure());
run_loop.Run();
}
void WaitForSystemReady() {
base::RunLoop run_loop;
extension_system()->ready().Post(FROM_HERE, run_loop.QuitWhenIdleClosure());
run_loop.Run();
}
};
// This series of tests exercises that the migration we have in place for our
// serializations of user scripts works properly, preserving old records. It is
// split into three steps.
// TODO(crbug.com/40286091): We can remove this test once the migration
// is fully complete.
// Step 1: Load an extension and populate it with old-style data.
IN_PROC_BROWSER_TEST_F(ExtensionUserScriptLoaderBrowserTest,
PRE_PRE_OldDynamicContentScriptEntriesAreMigrated) {
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("scripting/dynamic_scripts_stub"));
ASSERT_TRUE(extension);
ASSERT_TRUE(dynamic_scripts_store());
// We hard-code the entries in the state store, since writing them newly would
// use the new format.
dynamic_scripts_store()->SetExtensionValue(
extension->id(), scripting::kRegisteredScriptsStorageKey,
base::test::ParseJson(kOldFormatEntry));
FlushScriptStore();
URLPatternSet patterns;
patterns.AddPattern(
URLPattern(URLPattern::SCHEME_ALL, "http://example.com/*"));
scripting::SetPersistentScriptURLPatterns(profile(), extension->id(),
std::move(patterns));
}
// Step 2: Restart the browser, and ensure the scripts are still appropriately
// registered.
IN_PROC_BROWSER_TEST_F(ExtensionUserScriptLoaderBrowserTest,
PRE_OldDynamicContentScriptEntriesAreMigrated) {
WaitForSystemReady();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("example.com", "/simple.html")));
EXPECT_EQ(u"script injected", GetActiveWebContents()->GetTitle());
const Extension* extension = nullptr;
for (const auto& entry :
ExtensionRegistry::Get(profile())->enabled_extensions()) {
if (entry->name() == "Dynamic Content Scripts Stub") {
extension = entry.get();
break;
}
}
ASSERT_TRUE(extension);
ExtensionUserScriptLoader* loader =
extension_system()
->user_script_manager()
->GetUserScriptLoaderForExtension(extension->id());
ASSERT_TRUE(loader);
// We don't currently auto-migrate scripts. This means that to trigger the
// update to the new type, we remove and re-add the scripts.
const UserScriptList& loaded_dynamic_scripts =
loader->GetLoadedDynamicScripts();
ASSERT_EQ(1u, loaded_dynamic_scripts.size());
UserScriptList copied_scripts;
copied_scripts.push_back(
UserScript::CopyMetadataFrom(*loaded_dynamic_scripts[0]));
{
base::test::TestFuture<const std::optional<std::string>&> future;
loader->ClearDynamicScripts(UserScript::Source::kDynamicContentScript,
future.GetCallback());
EXPECT_EQ("<no error>", future.Get().value_or("<no error>"));
}
{
std::string id = copied_scripts[0]->id();
base::test::TestFuture<const std::optional<std::string>&> future;
loader->AddPendingDynamicScriptIDs({id});
loader->AddDynamicScripts(std::move(copied_scripts), {id},
future.GetCallback());
EXPECT_EQ("<no error>", future.Get().value_or("<no error>"));
}
EXPECT_EQ(1u, loader->GetLoadedDynamicScripts().size());
// Verify as well that the serialized values are now migrated to the new type.
FlushScriptStore();
base::test::TestFuture<std::optional<base::Value>> value_future;
dynamic_scripts_store()->GetExtensionValue(
extension->id(), scripting::kRegisteredScriptsStorageKey,
value_future.GetCallback());
std::optional<base::Value> value = value_future.Take();
ASSERT_TRUE(value);
ASSERT_TRUE(value->is_list());
ASSERT_EQ(1u, value->GetList().size());
const base::Value& entry = value->GetList()[0];
ASSERT_TRUE(entry.is_dict());
// The presence (and validity) of a "source" entry are an indication that the
// serialization is using the new type.
const std::string* source_string = entry.GetDict().FindString("source");
ASSERT_TRUE(source_string);
EXPECT_EQ("DYNAMIC_CONTENT_SCRIPT", *source_string);
}
// Step 3: Restart the browser a third and final time. Scripts should still
// inject, having loaded from the new format.
IN_PROC_BROWSER_TEST_F(ExtensionUserScriptLoaderBrowserTest,
OldDynamicContentScriptEntriesAreMigrated) {
WaitForSystemReady();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
embedded_test_server()->GetURL("example.com", "/simple.html")));
EXPECT_EQ(u"script injected", GetActiveWebContents()->GetTitle());
}
} // namespace extensions