|  | // Copyright 2014 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/renderer/module_system_test.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/lazy_instance.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/path_service.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/values.h" | 
|  | #include "extensions/common/extension_builder.h" | 
|  | #include "extensions/common/extension_paths.h" | 
|  | #include "extensions/common/mojom/context_type.mojom.h" | 
|  | #include "extensions/common/utils/extension_utils.h" | 
|  | #include "extensions/renderer/ipc_message_sender.h" | 
|  | #include "extensions/renderer/logging_native_handler.h" | 
|  | #include "extensions/renderer/native_extension_bindings_system.h" | 
|  | #include "extensions/renderer/object_backed_native_handler.h" | 
|  | #include "extensions/renderer/safe_builtins.h" | 
|  | #include "extensions/renderer/script_context_set.h" | 
|  | #include "extensions/renderer/string_source_map.h" | 
|  | #include "extensions/renderer/test_v8_extension_configuration.h" | 
|  | #include "extensions/renderer/utils_native_handler.h" | 
|  | #include "gin/converter.h" | 
|  | #include "ui/base/resource/resource_bundle.h" | 
|  | #include "v8/include/v8-context.h" | 
|  | #include "v8/include/v8-function-callback.h" | 
|  | #include "v8/include/v8-isolate.h" | 
|  | #include "v8/include/v8-microtask-queue.h" | 
|  | #include "v8/include/v8-object.h" | 
|  | #include "v8/include/v8-primitive.h" | 
|  | #include "v8/include/v8-statistics.h" | 
|  |  | 
|  | namespace extensions { | 
|  | namespace { | 
|  |  | 
|  | class FailsOnException : public ModuleSystem::ExceptionHandler { | 
|  | public: | 
|  | FailsOnException() : ModuleSystem::ExceptionHandler(nullptr) {} | 
|  | void HandleUncaughtException(const v8::TryCatch& try_catch) override { | 
|  | FAIL() << "Uncaught exception: " << CreateExceptionString(try_catch); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class GetAPINatives : public ObjectBackedNativeHandler { | 
|  | public: | 
|  | GetAPINatives(ScriptContext* context, | 
|  | NativeExtensionBindingsSystem* bindings_system) | 
|  | : ObjectBackedNativeHandler(context), bindings_system_(bindings_system) { | 
|  | DCHECK(bindings_system_); | 
|  | } | 
|  |  | 
|  | GetAPINatives(const GetAPINatives&) = delete; | 
|  | GetAPINatives& operator=(const GetAPINatives&) = delete; | 
|  |  | 
|  | ~GetAPINatives() override {} | 
|  |  | 
|  | // ObjectBackedNativeHandler: | 
|  | void AddRoutes() override { | 
|  | auto get_api = [](ScriptContext* context, | 
|  | NativeExtensionBindingsSystem* bindings_system, | 
|  | const v8::FunctionCallbackInfo<v8::Value>& args) { | 
|  | CHECK_EQ(1, args.Length()); | 
|  | CHECK(args[0]->IsString()); | 
|  | std::string api_name = gin::V8ToString(context->isolate(), args[0]); | 
|  | v8::Local<v8::Object> api; | 
|  | if (bindings_system) { | 
|  | api = bindings_system->GetAPIObjectForTesting(context, api_name); | 
|  | } else { | 
|  | v8::Local<v8::Object> full_binding; | 
|  | CHECK( | 
|  | context->module_system()->Require(api_name).ToLocal(&full_binding)) | 
|  | << "Failed to get: " << api_name; | 
|  | v8::Local<v8::Value> api_value; | 
|  | CHECK(full_binding | 
|  | ->Get(context->v8_context(), | 
|  | gin::StringToSymbol(context->isolate(), "binding")) | 
|  | .ToLocal(&api_value)) | 
|  | << "Failed to get: " << api_name; | 
|  | CHECK(api_value->IsObject()) << "Failed to get: " << api_name; | 
|  | api = api_value.As<v8::Object>(); | 
|  | } | 
|  | args.GetReturnValue().Set(api); | 
|  | }; | 
|  |  | 
|  | RouteHandlerFunction( | 
|  | "get", base::BindRepeating(get_api, context(), bindings_system_)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | raw_ptr<NativeExtensionBindingsSystem> bindings_system_ = nullptr; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Native JS functions for doing asserts. | 
|  | class ModuleSystemTestEnvironment::AssertNatives | 
|  | : public ObjectBackedNativeHandler { | 
|  | public: | 
|  | explicit AssertNatives(ScriptContext* context) | 
|  | : ObjectBackedNativeHandler(context), | 
|  | assertion_made_(false), | 
|  | failed_(false) {} | 
|  |  | 
|  | // ObjectBackedNativeHandler: | 
|  | void AddRoutes() override { | 
|  | RouteHandlerFunction("AssertTrue", | 
|  | base::BindRepeating(&AssertNatives::AssertTrue, | 
|  | base::Unretained(this))); | 
|  | RouteHandlerFunction("AssertFalse", | 
|  | base::BindRepeating(&AssertNatives::AssertFalse, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | bool assertion_made() { return assertion_made_; } | 
|  | bool failed() { return failed_; } | 
|  |  | 
|  | void AssertTrue(const v8::FunctionCallbackInfo<v8::Value>& args) { | 
|  | CHECK_EQ(1, args.Length()); | 
|  | assertion_made_ = true; | 
|  | failed_ = failed_ || !args[0]->ToBoolean(args.GetIsolate())->Value(); | 
|  | } | 
|  |  | 
|  | void AssertFalse(const v8::FunctionCallbackInfo<v8::Value>& args) { | 
|  | CHECK_EQ(1, args.Length()); | 
|  | assertion_made_ = true; | 
|  | failed_ = failed_ || args[0]->ToBoolean(args.GetIsolate())->Value(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool assertion_made_; | 
|  | bool failed_; | 
|  | }; | 
|  |  | 
|  | ModuleSystemTestEnvironment::ModuleSystemTestEnvironment( | 
|  | v8::Isolate* isolate, | 
|  | ScriptContextSet* context_set, | 
|  | scoped_refptr<const Extension> extension) | 
|  | : isolate_(isolate), | 
|  | handle_scope_(isolate_), | 
|  | context_holder_(std::make_unique<gin::ContextHolder>(isolate_)), | 
|  | extension_(extension), | 
|  | context_set_(context_set), | 
|  | source_map_(std::make_unique<StringSourceMap>()) { | 
|  | context_holder_->SetContext(v8::Context::New( | 
|  | isolate, TestV8ExtensionConfiguration::GetConfiguration())); | 
|  |  | 
|  | { | 
|  | auto context = std::make_unique<ScriptContext>( | 
|  | context_holder_->context(), | 
|  | nullptr,  // WebFrame | 
|  | GenerateHostIdFromExtensionId(extension_->id()), extension_.get(), | 
|  | /*blink_isolated_world_id=*/std::nullopt, | 
|  | mojom::ContextType::kPrivilegedExtension, extension_.get(), | 
|  | mojom::ContextType::kPrivilegedExtension); | 
|  | context_ = context.get(); | 
|  | context_set_->AddForTesting(std::move(context)); | 
|  | } | 
|  |  | 
|  | context_->v8_context()->Enter(); | 
|  | assert_natives_ = new AssertNatives(context_); | 
|  |  | 
|  | bindings_system_ = std::make_unique<NativeExtensionBindingsSystem>( | 
|  | /*delegate=*/nullptr, /*ipc_message_sender=*/nullptr); | 
|  |  | 
|  | { | 
|  | std::unique_ptr<ModuleSystem> module_system( | 
|  | new ModuleSystem(context_, source_map_.get())); | 
|  | context_->SetModuleSystem(std::move(module_system)); | 
|  | } | 
|  | ModuleSystem* module_system = context_->module_system(); | 
|  | module_system->RegisterNativeHandler( | 
|  | "assert", std::unique_ptr<NativeHandler>(assert_natives_)); | 
|  | module_system->RegisterNativeHandler( | 
|  | "logging", | 
|  | std::unique_ptr<NativeHandler>(new LoggingNativeHandler(context_))); | 
|  | module_system->RegisterNativeHandler( | 
|  | "utils", | 
|  | std::unique_ptr<NativeHandler>(new UtilsNativeHandler(context_))); | 
|  | module_system->RegisterNativeHandler( | 
|  | "apiGetter", | 
|  | std::make_unique<GetAPINatives>(context_, bindings_system_.get())); | 
|  | module_system->SetExceptionHandlerForTest( | 
|  | std::unique_ptr<ModuleSystem::ExceptionHandler>(new FailsOnException)); | 
|  |  | 
|  | bindings_system_->DidCreateScriptContext(context_); | 
|  | bindings_system_->UpdateBindingsForContext(context_); | 
|  | } | 
|  |  | 
|  | ModuleSystemTestEnvironment::~ModuleSystemTestEnvironment() { | 
|  | if (context_) | 
|  | ShutdownModuleSystem(); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::RegisterModule(const std::string& name, | 
|  | const std::string& code) { | 
|  | source_map_->RegisterModule(name, code); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::RegisterModule(const std::string& name, | 
|  | int resource_id, | 
|  | bool gzipped) { | 
|  | std::string code = | 
|  | ui::ResourceBundle::GetSharedInstance().LoadDataResourceString( | 
|  | resource_id); | 
|  | source_map_->RegisterModule(name, code, gzipped); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::OverrideNativeHandler( | 
|  | const std::string& name, | 
|  | const std::string& code) { | 
|  | RegisterModule(name, code); | 
|  | context_->module_system()->OverrideNativeHandlerForTest(name); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::RegisterTestFile( | 
|  | const std::string& module_name, | 
|  | const std::string& file_name) { | 
|  | base::FilePath test_js_file_path; | 
|  | ASSERT_TRUE(base::PathService::Get(DIR_TEST_DATA, &test_js_file_path)); | 
|  | test_js_file_path = test_js_file_path.AppendASCII(file_name); | 
|  | std::string test_js; | 
|  | ASSERT_TRUE(base::ReadFileToString(test_js_file_path, &test_js)); | 
|  | source_map_->RegisterModule(module_name, test_js); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::ShutdownGin() { | 
|  | context_holder_.reset(); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::ShutdownModuleSystem() { | 
|  | CHECK(context_->is_valid()); | 
|  | context_->v8_context()->Exit(); | 
|  | context_set_->Remove(context_); | 
|  | context_ = nullptr; | 
|  | assert_natives_ = nullptr; | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | v8::Local<v8::Object> ModuleSystemTestEnvironment::CreateGlobal( | 
|  | const std::string& name) { | 
|  | v8::EscapableHandleScope handle_scope(isolate_); | 
|  | v8::MicrotasksScope microtasks(isolate_->GetCurrentContext(), | 
|  | v8::MicrotasksScope::kDoNotRunMicrotasks); | 
|  | v8::Local<v8::Object> object = v8::Object::New(isolate_); | 
|  | isolate_->GetCurrentContext() | 
|  | ->Global() | 
|  | ->Set(context_->v8_context(), | 
|  | v8::String::NewFromUtf8(isolate_, name.c_str(), | 
|  | v8::NewStringType::kInternalized) | 
|  | .ToLocalChecked(), | 
|  | object) | 
|  | .ToChecked(); | 
|  | return handle_scope.Escape(object); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTestEnvironment::SetLazyField( | 
|  | v8::Local<v8::Object> object, | 
|  | const std::string& field, | 
|  | const std::string& module_name, | 
|  | const std::string& module_field) { | 
|  | module_system()->SetLazyField(object, field, module_name, module_field); | 
|  | } | 
|  |  | 
|  | ModuleSystemTest::ModuleSystemTest() | 
|  | : isolate_holder_(task_environment_.GetMainThreadTaskRunner(), | 
|  | gin::IsolateHolder::IsolateType::kTest), | 
|  | isolate_(isolate_holder_.isolate()), | 
|  | context_set_(&extension_ids_), | 
|  | should_assertions_be_made_(true) {} | 
|  |  | 
|  | ModuleSystemTest::~ModuleSystemTest() { | 
|  | } | 
|  |  | 
|  | void ModuleSystemTest::SetUp() { | 
|  | isolate_->Enter(); | 
|  | extension_ = CreateExtension(); | 
|  | env_ = CreateEnvironment(); | 
|  | base::CommandLine::ForCurrentProcess()->AppendSwitch("test-type"); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTest::TearDown() { | 
|  | // All tests must assert at least once unless otherwise specified. | 
|  | if (env_->assert_natives()) {  // The context may have already been shutdown. | 
|  | EXPECT_EQ(should_assertions_be_made_, | 
|  | env_->assert_natives()->assertion_made()); | 
|  | EXPECT_FALSE(env_->assert_natives()->failed()); | 
|  | } else { | 
|  | EXPECT_FALSE(should_assertions_be_made_); | 
|  | } | 
|  | env_.reset(); | 
|  | v8::HeapStatistics stats; | 
|  | isolate_->GetHeapStatistics(&stats); | 
|  | size_t old_heap_size = 0; | 
|  | // Run the GC until the heap size reaches a steady state to ensure that | 
|  | // all the garbage is collected. | 
|  | while (stats.used_heap_size() != old_heap_size) { | 
|  | old_heap_size = stats.used_heap_size(); | 
|  | isolate_->RequestGarbageCollectionForTesting( | 
|  | v8::Isolate::kFullGarbageCollection); | 
|  | isolate_->GetHeapStatistics(&stats); | 
|  | } | 
|  | isolate_->Exit(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<const Extension> ModuleSystemTest::CreateExtension() { | 
|  | base::Value::Dict manifest = base::Value::Dict() | 
|  | .Set("name", "test") | 
|  | .Set("version", "1.0") | 
|  | .Set("manifest_version", 2); | 
|  | return ExtensionBuilder().SetManifest(std::move(manifest)).Build(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ModuleSystemTestEnvironment> | 
|  | ModuleSystemTest::CreateEnvironment() { | 
|  | return std::make_unique<ModuleSystemTestEnvironment>(isolate_, &context_set_, | 
|  | extension_); | 
|  | } | 
|  |  | 
|  | void ModuleSystemTest::ExpectNoAssertionsMade() { | 
|  | should_assertions_be_made_ = false; | 
|  | } | 
|  |  | 
|  | void ModuleSystemTest::RunResolvedPromises() { | 
|  | v8::MicrotasksScope::PerformCheckpoint(isolate_); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |