blob: 491cad07af29d088ec3203985c9e51cffc2ba695 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/renderer/controlled_frame/web_url_pattern_natives.h"
#include <optional>
#include <vector>
#include "base/containers/to_value_list.h"
#include "base/test/test_future.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/host_id.mojom.h"
#include "extensions/renderer/module_system.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/string_source_map.h"
#include "extensions/renderer/test_v8_extension_configuration.h"
#include "v8/include/v8.h"
namespace controlled_frame {
namespace {
v8::Local<v8::String> ToV8String(const char* str) {
return v8::String::NewFromUtf8(v8::Isolate::GetCurrent(), str)
.ToLocalChecked();
}
} // namespace
class WebUrlPatternNativesBrowserTest : public ChromeRenderViewTest {
public:
void SetUp() override {
ChromeRenderViewTest::SetUp();
v8::HandleScope handle_scope(Isolate());
v8::Local<v8::Context> context = v8::Context::New(
Isolate(),
extensions::TestV8ExtensionConfiguration::GetConfiguration());
v8::Context::Scope context_scope(context);
script_context_ = std::make_unique<extensions::ScriptContext>(
context, /* frame=*/nullptr,
extensions::mojom::HostID(
extensions::mojom::HostID::HostType::kControlledFrameEmbedder,
"isolated-app://some_app_id"),
/* extension=*/nullptr,
/* blink_isolated_world_id=*/std::nullopt,
extensions::mojom::ContextType::kWebPage,
/* effective_extension=*/nullptr,
extensions::mojom::ContextType::kWebPage);
script_context_->SetModuleSystem(std::make_unique<extensions::ModuleSystem>(
script_context_.get(), &source_map_));
script_context_->module_system()->RegisterNativeHandler(
"WebUrlPatternNatives",
std::make_unique<controlled_frame::WebUrlPatternNatives>(
script_context_.get()));
}
void TearDown() override {
script_context_->Invalidate();
ChromeRenderViewTest::TearDown();
}
protected:
void RegisterAndRequireTestModule(const std::string& module_name,
const std::string& module_source) {
extensions::ModuleSystem::NativesEnabledScope enable_natives(
script_context_->module_system());
source_map_.RegisterModule(module_name, module_source);
ASSERT_FALSE(script_context_->module_system()
->Require(module_name)
.ToLocalChecked()
.IsEmpty());
}
std::optional<base::Value> CallModuleMethod(
const std::string& module_name,
const std::string& method_name,
std::vector<v8::Local<v8::Value>>* args) {
base::test::TestFuture<std::optional<base::Value>, base::TimeTicks> future;
script_context_->module_system()->CallModuleMethodSafe(
module_name, method_name, args->size(), args->data(),
future.GetCallback());
return std::get<0>(future.Take());
}
private:
std::unique_ptr<extensions::ScriptContext> script_context_;
extensions::StringSourceMap source_map_;
};
TEST_F(WebUrlPatternNativesBrowserTest, URLPatternToMatchPatterns) {
v8::HandleScope handle_scope(Isolate());
RegisterAndRequireTestModule("TestRunner", R"(
const natives = requireNative('WebUrlPatternNatives');
exports.runTest = function(urlPatternString) {
try {
return natives.URLPatternToMatchPatterns(urlPatternString);
} catch(e) {
return [];
}
})");
struct URLPatternTestCase {
std::string input_url_pattern;
std::vector<std::string> expected_match_patterns;
};
const URLPatternTestCase kTestCases[] = {
{"http://*/*", {"http://*/*", "http://*/*?*"}},
{"*://*/*",
{"*://*/*", "*://*/*?*", "ws://*/*", "wss://*/*", "ws://*/*?*",
"wss://*/*?*"}},
{"*://asdf.com/*",
{"*://asdf.com/*", "*://asdf.com/*?*", "ws://asdf.com/*",
"wss://asdf.com/*", "ws://asdf.com/*?*", "wss://asdf.com/*?*"}},
{"https://*.asdf.com/fdsa",
{"https://*.asdf.com/fdsa", "https://*.asdf.com/fdsa?*"}},
{"ws://*.asdf.com/*", {"ws://*.asdf.com/*", "ws://*.asdf.com/*?*"}},
{"wss://*/asdf/*/fdsa", {"wss://*/asdf/*/fdsa", "wss://*/asdf/*/fdsa?*"}},
{"https://*", {"https://*/*", "https://*/*?*"}},
{"https://*/", {"https://*/", "https://*/?*"}},
{"https://*/asdf", {"https://*/asdf", "https://*/asdf?*"}},
{"http://asdf.com", {"http://asdf.com/*", "http://asdf.com/*?*"}},
{"http://asdf.com/fdsa",
{"http://asdf.com/fdsa", "http://asdf.com/fdsa?*"}},
{"http://asdf.com/fdsa/*",
{"http://asdf.com/fdsa/*", "http://asdf.com/fdsa/*?*"}},
{"http://asdf.com*", {}}, // only * or *.something in the domain
{"ftp://asdf.com/", {}}, // no ftp
{"asdf://asdf.com/", {}}, // wrong protocol
{"http://*.asdf.com/", {"http://*.asdf.com/", "http://*.asdf.com/?*"}},
{"http://*asdf.com/", {}}, // only * or *.something in the domain
{"http://asdf.*.com/", {}}, // only * or *.something in the domain
{"http:/asdf.com", {}}, // malformed protocol part
{"http://asdf.com:8080/fdsa",
{"http://asdf.com:8080/fdsa", "http://asdf.com:8080/fdsa?*"}},
{"*://asdf.com:8080/fdsa", {}},
{"http://asdf.com:80*/fdsa", {}},
{"http://asdf.com:*80/fdsa", {}},
{"*://asdf.com:*/fdsa",
{"*://asdf.com:*/fdsa", "*://asdf.com:*/fdsa?*", "ws://asdf.com:*/fdsa",
"wss://asdf.com:*/fdsa", "ws://asdf.com:*/fdsa?*",
"wss://asdf.com:*/fdsa?*"}},
{"http://asdf.com:*/fdsa",
{"http://asdf.com:*/fdsa", "http://asdf.com:*/fdsa?*"}},
{"http://asdf.com:fdsa/asdf", {}},
{"http://asdf.com/fdsa?query", {"http://asdf.com/fdsa?query"}},
{"http://asdf.com/?query=fdsa&query2=asdf",
{"http://asdf.com/?query=fdsa&query2=asdf"}},
{"http://asdf.com?query", {"http://asdf.com/?query"}},
{"http://user@asdf.com/fdsa", {}},
{"http://user:pass@asdf.com/fdsa", {}},
{"http://asdf.com/:group", {}},
{"http://:asdf.com/", {}},
};
for (const auto& test : kTestCases) {
std::vector<v8::Local<v8::Value>> args = {
ToV8String(test.input_url_pattern.c_str())};
std::optional<base::Value> match_patterns =
CallModuleMethod("TestRunner", "runTest", &args);
ASSERT_TRUE(match_patterns.has_value());
EXPECT_EQ(match_patterns.value(),
base::ToValueList(test.expected_match_patterns));
}
}
} // namespace controlled_frame