diff options
author | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-26 19:48:04 +0000 |
---|---|---|
committer | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-26 19:48:04 +0000 |
commit | f80685c31c04410dc90bc45f492f4adc73ace117 (patch) | |
tree | 10bc5ea060c8538b846ff79a44beb0ab18947904 /extensions | |
parent | 5f2d097e8e839a4ab180cc8f63a5ddec4378e408 (diff) | |
download | chromium_src-f80685c31c04410dc90bc45f492f4adc73ace117.zip chromium_src-f80685c31c04410dc90bc45f492f4adc73ace117.tar.gz chromium_src-f80685c31c04410dc90bc45f492f4adc73ace117.tar.bz2 |
Add support for writing unit tests for Mojo-backed apps/extensions APIs.
This change adds the infrastructure for unit testing the JS parts of
apps and extensions APIs implemented on top of Mojo services. The test
environment provides a partial implementation of the JS test API
provided to browser tests. A TestServiceProvider implementation is
used to provide test implementations of the appropriate Mojo services to
the JS code.
BUG=389016
Review URL: https://codereview.chromium.org/399363002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285792 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
19 files changed, 724 insertions, 123 deletions
diff --git a/extensions/DEPS b/extensions/DEPS index f544c73..f0606ba 100644 --- a/extensions/DEPS +++ b/extensions/DEPS @@ -5,8 +5,10 @@ include_rules = [ "+content/public/common", "+content/public/test", "+crypto", + "+grit/content_resources.h", "+grit/extensions_renderer_resources.h", "+grit/extensions_resources.h", + "+mojo/public", "+testing", "+ui", diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index 7faa191..ece9476 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -535,7 +535,9 @@ 'dependencies': [ 'extensions_resources.gyp:extensions_resources', '../chrome/chrome_resources.gyp:chrome_resources', + '../content/content_resources.gyp:content_resources', '../gin/gin.gyp:gin', + '../mojo/mojo.gyp:mojo_js_bindings', '../third_party/WebKit/public/blink.gyp:blink', ], 'include_dirs': [ @@ -733,6 +735,7 @@ 'target_name': 'extensions_pak', 'type': 'none', 'dependencies': [ + '../content/content_resources.gyp:content_resources', '../ui/strings/ui_strings.gyp:ui_strings', 'extensions_resources.gyp:extensions_resources', ], @@ -741,6 +744,7 @@ 'action_name': 'repack_extensions_pak', 'variables': { 'pak_inputs': [ + '<(SHARED_INTERMEDIATE_DIR)/content/content_resources.pak', '<(SHARED_INTERMEDIATE_DIR)/extensions/extensions_resources.pak', '<(SHARED_INTERMEDIATE_DIR)/extensions/extensions_renderer_resources.pak', '<(SHARED_INTERMEDIATE_DIR)/ui/strings/app_locale_settings_en-US.pak', @@ -763,6 +767,11 @@ '../base/base.gyp:base', '../base/base.gyp:test_support_base', '../content/content_shell_and_tests.gyp:test_support_content', + '../device/serial/serial.gyp:device_serial', + '../mojo/mojo.gyp:mojo_environment_chromium', + '../mojo/mojo.gyp:mojo_cpp_bindings', + '../mojo/mojo.gyp:mojo_js_bindings_lib', + '../mojo/mojo.gyp:mojo_system_impl', '../testing/gmock.gyp:gmock', '../testing/gtest.gyp:gtest', 'extensions_common', @@ -781,6 +790,9 @@ 'common/one_shot_event_unittest.cc', 'common/permissions/manifest_permission_set_unittest.cc', 'common/user_script_unittest.cc', + 'renderer/api_test_base.cc', + 'renderer/api_test_base.h', + 'renderer/api_test_base_unittest.cc', 'renderer/event_unittest.cc', 'renderer/json_schema_unittest.cc', 'renderer/messaging_utils_unittest.cc', diff --git a/extensions/renderer/DEPS b/extensions/renderer/DEPS index ab21030..2b18364 100644 --- a/extensions/renderer/DEPS +++ b/extensions/renderer/DEPS @@ -2,6 +2,7 @@ include_rules = [ "+content/public/renderer", "+gin", + "+mojo/bindings/js", "+third_party/skia/include/core", diff --git a/extensions/renderer/api_test_base.cc b/extensions/renderer/api_test_base.cc new file mode 100644 index 0000000..f0943c9 --- /dev/null +++ b/extensions/renderer/api_test_base.cc @@ -0,0 +1,221 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/api_test_base.h" + +#include <vector> + +#include "base/run_loop.h" +#include "extensions/common/extension_urls.h" +#include "extensions/renderer/dispatcher.h" +#include "extensions/renderer/process_info_native_handler.h" +#include "gin/converter.h" +#include "gin/dictionary.h" +#include "mojo/bindings/js/core.h" +#include "mojo/bindings/js/handle.h" +#include "mojo/bindings/js/support.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/system/core.h" + +namespace extensions { +namespace { + +// Natives for the implementation of the unit test version of chrome.test. Calls +// the provided |quit_closure| when either notifyPass or notifyFail is called. +class TestNatives : public gin::Wrappable<TestNatives> { + public: + static gin::Handle<TestNatives> Create(v8::Isolate* isolate, + const base::Closure& quit_closure) { + return gin::CreateHandle(isolate, new TestNatives(quit_closure)); + } + + virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) OVERRIDE { + return Wrappable<TestNatives>::GetObjectTemplateBuilder(isolate) + .SetMethod("Log", &TestNatives::Log) + .SetMethod("NotifyPass", &TestNatives::NotifyPass) + .SetMethod("NotifyFail", &TestNatives::NotifyFail); + } + + void Log(const std::string& value) { logs_ += value + "\n"; } + void NotifyPass() { FinishTesting(); } + + void NotifyFail(const std::string& message) { + FinishTesting(); + FAIL() << logs_ << message; + } + + void FinishTesting() { + base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure_); + } + + static gin::WrapperInfo kWrapperInfo; + + private: + explicit TestNatives(const base::Closure& quit_closure) + : quit_closure_(quit_closure) {} + + const base::Closure quit_closure_; + std::string logs_; +}; + +gin::WrapperInfo TestNatives::kWrapperInfo = {gin::kEmbedderNativeGin}; + +} // namespace + +gin::WrapperInfo TestServiceProvider::kWrapperInfo = {gin::kEmbedderNativeGin}; + +gin::Handle<TestServiceProvider> TestServiceProvider::Create( + v8::Isolate* isolate) { + return gin::CreateHandle(isolate, new TestServiceProvider()); +} + +TestServiceProvider::~TestServiceProvider() { +} + +gin::ObjectTemplateBuilder TestServiceProvider::GetObjectTemplateBuilder( + v8::Isolate* isolate) { + return Wrappable<TestServiceProvider>::GetObjectTemplateBuilder(isolate) + .SetMethod("connectToService", &TestServiceProvider::ConnectToService); +} + +mojo::Handle TestServiceProvider::ConnectToService( + const std::string& service_name) { + EXPECT_EQ(1u, service_factories_.count(service_name)) + << "Unregistered service " << service_name << " requested."; + mojo::MessagePipe pipe; + std::map<std::string, + base::Callback<void(mojo::ScopedMessagePipeHandle)> >::iterator it = + service_factories_.find(service_name); + if (it != service_factories_.end()) + it->second.Run(pipe.handle0.Pass()); + return pipe.handle1.release(); +} + +TestServiceProvider::TestServiceProvider() { +} + +ApiTestBase::ApiTestBase() { +} +ApiTestBase::~ApiTestBase() { +} + +void ApiTestBase::SetUp() { + ModuleSystemTest::SetUp(); + InitializeEnvironment(); + RegisterModules(); +} + +void ApiTestBase::RegisterModules() { + v8_schema_registry_.reset(new V8SchemaRegistry); + const std::vector<std::pair<std::string, int> > resources = + Dispatcher::GetJsResources(); + for (std::vector<std::pair<std::string, int> >::const_iterator resource = + resources.begin(); + resource != resources.end(); + ++resource) { + if (resource->first != "test_environment_specific_bindings") + env()->RegisterModule(resource->first, resource->second); + } + Dispatcher::RegisterNativeHandlers(env()->module_system(), + env()->context(), + NULL, + NULL, + v8_schema_registry_.get()); + env()->module_system()->RegisterNativeHandler( + "process", + scoped_ptr<NativeHandler>(new ProcessInfoNativeHandler( + env()->context(), + env()->context()->GetExtensionID(), + env()->context()->GetContextTypeDescription(), + false, + 2, + false))); + env()->RegisterTestFile("test_environment_specific_bindings", + "unit_test_environment_specific_bindings.js"); + + env()->OverrideNativeHandler("activityLogger", + "exports.LogAPICall = function() {};"); + env()->OverrideNativeHandler( + "apiDefinitions", + "exports.GetExtensionAPIDefinitionsForTest = function() { return [] };"); + env()->OverrideNativeHandler( + "event_natives", + "exports.AttachEvent = function() {};" + "exports.DetachEvent = function() {};" + "exports.AttachFilteredEvent = function() {};" + "exports.AttachFilteredEvent = function() {};" + "exports.MatchAgainstEventFilter = function() { return [] };"); + + gin::ModuleRegistry::From(env()->context()->v8_context()) + ->AddBuiltinModule(env()->isolate(), + mojo::js::Core::kModuleName, + mojo::js::Core::GetModule(env()->isolate())); + gin::ModuleRegistry::From(env()->context()->v8_context()) + ->AddBuiltinModule(env()->isolate(), + mojo::js::Support::kModuleName, + mojo::js::Support::GetModule(env()->isolate())); + gin::Handle<TestServiceProvider> service_provider = + TestServiceProvider::Create(env()->isolate()); + service_provider_ = service_provider.get(); + gin::ModuleRegistry::From(env()->context()->v8_context()) + ->AddBuiltinModule(env()->isolate(), + "content/public/renderer/service_provider", + service_provider.ToV8()); +} + +void ApiTestBase::InitializeEnvironment() { + gin::Dictionary global(env()->isolate(), + env()->context()->v8_context()->Global()); + gin::Dictionary navigator(gin::Dictionary::CreateEmpty(env()->isolate())); + navigator.Set("appVersion", base::StringPiece("")); + global.Set("navigator", navigator); + gin::Dictionary chrome(gin::Dictionary::CreateEmpty(env()->isolate())); + global.Set("chrome", chrome); + gin::Dictionary extension(gin::Dictionary::CreateEmpty(env()->isolate())); + chrome.Set("extension", extension); + gin::Dictionary runtime(gin::Dictionary::CreateEmpty(env()->isolate())); + chrome.Set("runtime", runtime); +} + +void ApiTestBase::RunTest(const std::string& file_name, + const std::string& test_name) { + env()->RegisterTestFile("testBody", file_name); + ExpectNoAssertionsMade(); + base::RunLoop run_loop; + gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule( + env()->isolate(), + "testNatives", + TestNatives::Create(env()->isolate(), run_loop.QuitClosure()).ToV8()); + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(&ApiTestBase::RunTestInner, + base::Unretained(this), + test_name, + run_loop.QuitClosure())); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ApiTestBase::RunPromisesAgain, base::Unretained(this))); + run_loop.Run(); +} + +void ApiTestBase::RunTestInner(const std::string& test_name, + const base::Closure& quit_closure) { + v8::HandleScope scope(env()->isolate()); + ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system()); + v8::Handle<v8::Value> result = + env()->module_system()->CallModuleMethod("testBody", test_name); + if (!result->IsTrue()) { + base::MessageLoop::current()->PostTask(FROM_HERE, quit_closure); + FAIL() << "Failed to run test \"" << test_name << "\""; + } +} + +void ApiTestBase::RunPromisesAgain() { + RunResolvedPromises(); + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&ApiTestBase::RunPromisesAgain, base::Unretained(this))); +} + +} // namespace extensions diff --git a/extensions/renderer/api_test_base.h b/extensions/renderer/api_test_base.h new file mode 100644 index 0000000..4d74995 --- /dev/null +++ b/extensions/renderer/api_test_base.h @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_RENDERER_API_TEST_BASE_H_ +#define EXTENSIONS_RENDERER_API_TEST_BASE_H_ + +#include <map> +#include <string> +#include <utility> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "extensions/renderer/module_system_test.h" +#include "extensions/renderer/v8_schema_registry.h" +#include "gin/handle.h" +#include "gin/modules/module_registry.h" +#include "gin/object_template_builder.h" +#include "gin/wrappable.h" +#include "mojo/bindings/js/handle.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/system/core.h" + +namespace extensions { + +class V8SchemaRegistry; + +// A ServiceProvider that provides access from JS modules to services registered +// by AddService() calls. +class TestServiceProvider : public gin::Wrappable<TestServiceProvider> { + public: + static gin::Handle<TestServiceProvider> Create(v8::Isolate* isolate); + virtual ~TestServiceProvider(); + + virtual gin::ObjectTemplateBuilder GetObjectTemplateBuilder( + v8::Isolate* isolate) OVERRIDE; + + template <typename Interface> + void AddService(const base::Callback<void(mojo::InterfaceRequest<Interface>)> + service_factory) { + service_factories_.insert(std::make_pair( + Interface::Name_, + base::Bind(ForwardToServiceFactory<Interface>, service_factory))); + } + + static gin::WrapperInfo kWrapperInfo; + + private: + TestServiceProvider(); + + mojo::Handle ConnectToService(const std::string& service_name); + + template <typename Interface> + static void ForwardToServiceFactory( + const base::Callback<void(mojo::InterfaceRequest<Interface>)> + service_factory, + mojo::ScopedMessagePipeHandle handle) { + service_factory.Run(mojo::MakeRequest<Interface>(handle.Pass())); + } + std::map<std::string, base::Callback<void(mojo::ScopedMessagePipeHandle)> > + service_factories_; +}; + +// A base class for unit testing apps/extensions API custom bindings implemented +// on Mojo services. To use: +// 1. Register test Mojo service implementations on service_provider(). +// 2. Write JS tests in extensions/test/data/test_file.js. +// 3. Write one C++ test function for each JS test containing +// RunTest("test_file.js", "testFunctionName"). +// See extensions/renderer/api_test_base_unittest.cc and +// extensions/test/data/api_test_base_unittest.js for sample usage. +class ApiTestBase : public ModuleSystemTest { + protected: + ApiTestBase(); + virtual ~ApiTestBase(); + virtual void SetUp() OVERRIDE; + void RunTest(const std::string& file_name, const std::string& test_name); + TestServiceProvider* service_provider() { return service_provider_; } + + private: + void RegisterModules(); + void InitializeEnvironment(); + void RunTestInner(const std::string& test_name, + const base::Closure& quit_closure); + void RunPromisesAgain(); + + base::MessageLoop message_loop_; + TestServiceProvider* service_provider_; + scoped_ptr<V8SchemaRegistry> v8_schema_registry_; +}; + +} // namespace extensions + +#endif // EXTENSIONS_RENDERER_API_TEST_BASE_H_ diff --git a/extensions/renderer/api_test_base_unittest.cc b/extensions/renderer/api_test_base_unittest.cc new file mode 100644 index 0000000..953d7b5 --- /dev/null +++ b/extensions/renderer/api_test_base_unittest.cc @@ -0,0 +1,34 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/renderer/api_test_base.h" + +namespace extensions { + +class ApiTestBaseTest : public ApiTestBase { + public: + virtual void SetUp() OVERRIDE { ApiTestBase::SetUp(); } +}; + +TEST_F(ApiTestBaseTest, TestEnvironment) { + RunTest("api_test_base_unittest.js", "testEnvironment"); +} + +TEST_F(ApiTestBaseTest, TestPromisesRun) { + RunTest("api_test_base_unittest.js", "testPromisesRun"); +} + +TEST_F(ApiTestBaseTest, TestCommonModulesAreAvailable) { + RunTest("api_test_base_unittest.js", "testCommonModulesAreAvailable"); +} + +TEST_F(ApiTestBaseTest, TestMojoModulesAreAvailable) { + RunTest("api_test_base_unittest.js", "testMojoModulesAreAvailable"); +} + +TEST_F(ApiTestBaseTest, TestTestBindings) { + RunTest("api_test_base_unittest.js", "testTestBindings"); +} + +} // namespace extensions diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc index be09e31..a00e7d6 100644 --- a/extensions/renderer/dispatcher.cc +++ b/extensions/renderer/dispatcher.cc @@ -75,7 +75,9 @@ #include "extensions/renderer/user_gestures_native_handler.h" #include "extensions/renderer/utils_native_handler.h" #include "extensions/renderer/v8_context_native_handler.h" +#include "grit/content_resources.h" #include "grit/extensions_renderer_resources.h" +#include "mojo/public/js/bindings/constants.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/platform/WebURLRequest.h" #include "third_party/WebKit/public/web/WebCustomElement.h" @@ -457,6 +459,149 @@ void Dispatcher::ClearPortData(int port_id) { port_to_tab_id_map_.erase(port_id); } +// static +std::vector<std::pair<std::string, int> > Dispatcher::GetJsResources() { + std::vector<std::pair<std::string, int> > resources; + + // Libraries. + resources.push_back(std::make_pair("entryIdManager", IDR_ENTRY_ID_MANAGER)); + resources.push_back(std::make_pair(kEventBindings, IDR_EVENT_BINDINGS_JS)); + resources.push_back(std::make_pair("imageUtil", IDR_IMAGE_UTIL_JS)); + resources.push_back(std::make_pair("json_schema", IDR_JSON_SCHEMA_JS)); + resources.push_back(std::make_pair("lastError", IDR_LAST_ERROR_JS)); + resources.push_back(std::make_pair("messaging", IDR_MESSAGING_JS)); + resources.push_back( + std::make_pair("messaging_utils", IDR_MESSAGING_UTILS_JS)); + resources.push_back(std::make_pair(kSchemaUtils, IDR_SCHEMA_UTILS_JS)); + resources.push_back(std::make_pair("sendRequest", IDR_SEND_REQUEST_JS)); + resources.push_back(std::make_pair("setIcon", IDR_SET_ICON_JS)); + resources.push_back(std::make_pair("test", IDR_TEST_CUSTOM_BINDINGS_JS)); + resources.push_back( + std::make_pair("test_environment_specific_bindings", + IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS)); + resources.push_back(std::make_pair("uncaught_exception_handler", + IDR_UNCAUGHT_EXCEPTION_HANDLER_JS)); + resources.push_back(std::make_pair("unload_event", IDR_UNLOAD_EVENT_JS)); + resources.push_back(std::make_pair("utils", IDR_UTILS_JS)); + resources.push_back( + std::make_pair(mojo::kBufferModuleName, IDR_MOJO_BUFFER_JS)); + resources.push_back( + std::make_pair(mojo::kCodecModuleName, IDR_MOJO_CODEC_JS)); + resources.push_back( + std::make_pair(mojo::kConnectionModuleName, IDR_MOJO_CONNECTION_JS)); + resources.push_back( + std::make_pair(mojo::kConnectorModuleName, IDR_MOJO_CONNECTOR_JS)); + resources.push_back( + std::make_pair(mojo::kRouterModuleName, IDR_MOJO_ROUTER_JS)); + resources.push_back( + std::make_pair(mojo::kUnicodeModuleName, IDR_MOJO_UNICODE_JS)); + + // Custom bindings. + resources.push_back( + std::make_pair("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS)); + resources.push_back( + std::make_pair("contextMenus", IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS)); + resources.push_back( + std::make_pair("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS)); + resources.push_back(std::make_pair("i18n", IDR_I18N_CUSTOM_BINDINGS_JS)); + resources.push_back( + std::make_pair("permissions", IDR_PERMISSIONS_CUSTOM_BINDINGS_JS)); + resources.push_back( + std::make_pair("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS)); + resources.push_back(std::make_pair("binding", IDR_BINDING_JS)); + + // Custom types sources. + resources.push_back(std::make_pair("StorageArea", IDR_STORAGE_AREA_JS)); + + // Platform app sources that are not API-specific.. + resources.push_back(std::make_pair("platformApp", IDR_PLATFORM_APP_JS)); + + return resources; +} + +// NOTE: please use the naming convention "foo_natives" for these. +// static +void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system, + ScriptContext* context, + Dispatcher* dispatcher, + RequestSender* request_sender, + V8SchemaRegistry* v8_schema_registry) { + module_system->RegisterNativeHandler( + "chrome", scoped_ptr<NativeHandler>(new ChromeNativeHandler(context))); + module_system->RegisterNativeHandler( + "lazy_background_page", + scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(context))); + module_system->RegisterNativeHandler( + "logging", scoped_ptr<NativeHandler>(new LoggingNativeHandler(context))); + module_system->RegisterNativeHandler("schema_registry", + v8_schema_registry->AsNativeHandler()); + module_system->RegisterNativeHandler( + "print", scoped_ptr<NativeHandler>(new PrintNativeHandler(context))); + module_system->RegisterNativeHandler( + "test_features", + scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context))); + module_system->RegisterNativeHandler( + "user_gestures", + scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context))); + module_system->RegisterNativeHandler( + "utils", scoped_ptr<NativeHandler>(new UtilsNativeHandler(context))); + module_system->RegisterNativeHandler( + "v8_context", + scoped_ptr<NativeHandler>( + new V8ContextNativeHandler(context, dispatcher))); + module_system->RegisterNativeHandler( + "event_natives", + scoped_ptr<NativeHandler>(new EventBindings(dispatcher, context))); + module_system->RegisterNativeHandler( + "messaging_natives", + scoped_ptr<NativeHandler>(MessagingBindings::Get(dispatcher, context))); + module_system->RegisterNativeHandler( + "apiDefinitions", + scoped_ptr<NativeHandler>( + new ApiDefinitionsNatives(dispatcher, context))); + module_system->RegisterNativeHandler( + "sendRequest", + scoped_ptr<NativeHandler>( + new SendRequestNatives(request_sender, context))); + module_system->RegisterNativeHandler( + "setIcon", + scoped_ptr<NativeHandler>(new SetIconNatives(request_sender, context))); + module_system->RegisterNativeHandler( + "activityLogger", + scoped_ptr<NativeHandler>(new APIActivityLogger(context))); + module_system->RegisterNativeHandler( + "renderViewObserverNatives", + scoped_ptr<NativeHandler>(new RenderViewObserverNatives(context))); + + // Natives used by multiple APIs. + module_system->RegisterNativeHandler( + "file_system_natives", + scoped_ptr<NativeHandler>(new FileSystemNatives(context))); + + // Custom bindings. + module_system->RegisterNativeHandler( + "app_runtime", + scoped_ptr<NativeHandler>(new AppRuntimeCustomBindings(context))); + module_system->RegisterNativeHandler( + "blob_natives", + scoped_ptr<NativeHandler>(new BlobNativeHandler(context))); + module_system->RegisterNativeHandler( + "context_menus", + scoped_ptr<NativeHandler>(new ContextMenusCustomBindings(context))); + module_system->RegisterNativeHandler( + "css_natives", scoped_ptr<NativeHandler>(new CssNativeHandler(context))); + module_system->RegisterNativeHandler( + "document_natives", + scoped_ptr<NativeHandler>(new DocumentCustomBindings(context))); + module_system->RegisterNativeHandler( + "i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings(context))); + module_system->RegisterNativeHandler( + "id_generator", + scoped_ptr<NativeHandler>(new IdGeneratorCustomBindings(context))); + module_system->RegisterNativeHandler( + "runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context))); +} + bool Dispatcher::OnControlMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(Dispatcher, message) @@ -962,29 +1107,11 @@ void Dispatcher::RegisterBinding(const std::string& api_name, // NOTE: please use the naming convention "foo_natives" for these. void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system, ScriptContext* context) { - module_system->RegisterNativeHandler( - "chrome", scoped_ptr<NativeHandler>(new ChromeNativeHandler(context))); - module_system->RegisterNativeHandler( - "lazy_background_page", - scoped_ptr<NativeHandler>(new LazyBackgroundPageNativeHandler(context))); - module_system->RegisterNativeHandler( - "logging", scoped_ptr<NativeHandler>(new LoggingNativeHandler(context))); - module_system->RegisterNativeHandler("schema_registry", - v8_schema_registry_->AsNativeHandler()); - module_system->RegisterNativeHandler( - "print", scoped_ptr<NativeHandler>(new PrintNativeHandler(context))); - module_system->RegisterNativeHandler( - "test_features", - scoped_ptr<NativeHandler>(new TestFeaturesNativeHandler(context))); - module_system->RegisterNativeHandler( - "user_gestures", - scoped_ptr<NativeHandler>(new UserGesturesNativeHandler(context))); - module_system->RegisterNativeHandler( - "utils", scoped_ptr<NativeHandler>(new UtilsNativeHandler(context))); - module_system->RegisterNativeHandler( - "v8_context", - scoped_ptr<NativeHandler>(new V8ContextNativeHandler(context, this))); - + RegisterNativeHandlers(module_system, + context, + this, + request_sender_.get(), + v8_schema_registry_.get()); const Extension* extension = context->extension(); int manifest_version = extension ? extension->manifest_version() : 1; bool send_request_disabled = @@ -1000,95 +1127,17 @@ void Dispatcher::RegisterNativeHandlers(ModuleSystem* module_system, manifest_version, send_request_disabled))); - module_system->RegisterNativeHandler( - "event_natives", - scoped_ptr<NativeHandler>(new EventBindings(this, context))); - module_system->RegisterNativeHandler( - "messaging_natives", - scoped_ptr<NativeHandler>(MessagingBindings::Get(this, context))); - module_system->RegisterNativeHandler( - "apiDefinitions", - scoped_ptr<NativeHandler>(new ApiDefinitionsNatives(this, context))); - module_system->RegisterNativeHandler( - "sendRequest", - scoped_ptr<NativeHandler>( - new SendRequestNatives(request_sender_.get(), context))); - module_system->RegisterNativeHandler( - "setIcon", - scoped_ptr<NativeHandler>( - new SetIconNatives(request_sender_.get(), context))); - module_system->RegisterNativeHandler( - "activityLogger", - scoped_ptr<NativeHandler>(new APIActivityLogger(context))); - module_system->RegisterNativeHandler( - "renderViewObserverNatives", - scoped_ptr<NativeHandler>(new RenderViewObserverNatives(context))); - - // Natives used by multiple APIs. - module_system->RegisterNativeHandler( - "file_system_natives", - scoped_ptr<NativeHandler>(new FileSystemNatives(context))); - - // Custom bindings. - module_system->RegisterNativeHandler( - "app_runtime", - scoped_ptr<NativeHandler>(new AppRuntimeCustomBindings(context))); - module_system->RegisterNativeHandler( - "blob_natives", - scoped_ptr<NativeHandler>(new BlobNativeHandler(context))); - module_system->RegisterNativeHandler( - "context_menus", - scoped_ptr<NativeHandler>(new ContextMenusCustomBindings(context))); - module_system->RegisterNativeHandler( - "css_natives", scoped_ptr<NativeHandler>(new CssNativeHandler(context))); - module_system->RegisterNativeHandler( - "document_natives", - scoped_ptr<NativeHandler>(new DocumentCustomBindings(context))); - module_system->RegisterNativeHandler( - "i18n", scoped_ptr<NativeHandler>(new I18NCustomBindings(context))); - module_system->RegisterNativeHandler( - "id_generator", - scoped_ptr<NativeHandler>(new IdGeneratorCustomBindings(context))); - module_system->RegisterNativeHandler( - "runtime", scoped_ptr<NativeHandler>(new RuntimeCustomBindings(context))); - delegate_->RegisterNativeHandlers(this, module_system, context); } void Dispatcher::PopulateSourceMap() { - // Libraries. - source_map_.RegisterSource("entryIdManager", IDR_ENTRY_ID_MANAGER); - source_map_.RegisterSource(kEventBindings, IDR_EVENT_BINDINGS_JS); - source_map_.RegisterSource("imageUtil", IDR_IMAGE_UTIL_JS); - source_map_.RegisterSource("json_schema", IDR_JSON_SCHEMA_JS); - source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS); - source_map_.RegisterSource("messaging", IDR_MESSAGING_JS); - source_map_.RegisterSource("messaging_utils", IDR_MESSAGING_UTILS_JS); - source_map_.RegisterSource(kSchemaUtils, IDR_SCHEMA_UTILS_JS); - source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS); - source_map_.RegisterSource("setIcon", IDR_SET_ICON_JS); - source_map_.RegisterSource("test", IDR_TEST_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("uncaught_exception_handler", - IDR_UNCAUGHT_EXCEPTION_HANDLER_JS); - source_map_.RegisterSource("unload_event", IDR_UNLOAD_EVENT_JS); - source_map_.RegisterSource("utils", IDR_UTILS_JS); - - // Custom bindings. - source_map_.RegisterSource("app.runtime", IDR_APP_RUNTIME_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("contextMenus", - IDR_CONTEXT_MENUS_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("i18n", IDR_I18N_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("permissions", IDR_PERMISSIONS_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("runtime", IDR_RUNTIME_CUSTOM_BINDINGS_JS); - source_map_.RegisterSource("binding", IDR_BINDING_JS); - - // Custom types sources. - source_map_.RegisterSource("StorageArea", IDR_STORAGE_AREA_JS); - - // Platform app sources that are not API-specific.. - source_map_.RegisterSource("platformApp", IDR_PLATFORM_APP_JS); - + const std::vector<std::pair<std::string, int> > resources = GetJsResources(); + for (std::vector<std::pair<std::string, int> >::const_iterator resource = + resources.begin(); + resource != resources.end(); + ++resource) { + source_map_.RegisterSource(resource->first, resource->second); + } delegate_->PopulateSourceMap(&source_map_); } diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h index b6a630d..a0308a1 100644 --- a/extensions/renderer/dispatcher.h +++ b/extensions/renderer/dispatcher.h @@ -8,6 +8,7 @@ #include <map> #include <set> #include <string> +#include <utility> #include <vector> #include "base/scoped_observer.h" @@ -136,6 +137,15 @@ class Dispatcher : public content::RenderProcessObserver, void ClearPortData(int port_id); + // Returns a list of (module name, resource id) pairs for the JS modules to + // add to the source map. + static std::vector<std::pair<std::string, int> > GetJsResources(); + static void RegisterNativeHandlers(ModuleSystem* module_system, + ScriptContext* context, + Dispatcher* dispatcher, + RequestSender* request_sender, + V8SchemaRegistry* v8_schema_registry); + private: friend class ::ChromeRenderViewTest; FRIEND_TEST_ALL_PREFIXES(RendererPermissionsPolicyDelegateTest, diff --git a/extensions/renderer/module_system.cc b/extensions/renderer/module_system.cc index ab265fa..1f33764 100644 --- a/extensions/renderer/module_system.cc +++ b/extensions/renderer/module_system.cc @@ -230,9 +230,10 @@ v8::Local<v8::Value> ModuleSystem::RequireForJsInner( v8::Local<v8::Value> ModuleSystem::CallModuleMethod( const std::string& module_name, const std::string& method_name) { - v8::HandleScope handle_scope(GetIsolate()); + v8::EscapableHandleScope handle_scope(GetIsolate()); v8::Handle<v8::Value> no_args; - return CallModuleMethod(module_name, method_name, 0, &no_args); + return handle_scope.Escape( + CallModuleMethod(module_name, method_name, 0, &no_args)); } v8::Local<v8::Value> ModuleSystem::CallModuleMethod( diff --git a/extensions/renderer/module_system_test.cc b/extensions/renderer/module_system_test.cc index 72f1dbb..4f4675e 100644 --- a/extensions/renderer/module_system_test.cc +++ b/extensions/renderer/module_system_test.cc @@ -131,7 +131,7 @@ ModuleSystemTestEnvironment::ModuleSystemTestEnvironment(v8::Isolate* isolate) context_.reset(new ScriptContext(context_holder_->context(), NULL, // WebFrame NULL, // Extension - Feature::UNSPECIFIED_CONTEXT)); + Feature::BLESSED_EXTENSION_CONTEXT)); context_->v8_context()->Enter(); assert_natives_ = new AssertNatives(context_.get()); diff --git a/extensions/renderer/resources/browser_test_environment_specific_bindings.js b/extensions/renderer/resources/browser_test_environment_specific_bindings.js new file mode 100644 index 0000000..f540862 --- /dev/null +++ b/extensions/renderer/resources/browser_test_environment_specific_bindings.js @@ -0,0 +1,15 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function registerHooks(api) { +} + +function testDone(runNextTest) { + // Use setTimeout here to allow previous test contexts to be + // eligible for garbage collection. + setTimeout(runNextTest, 0); +} + +exports.registerHooks = registerHooks; +exports.testDone = testDone; diff --git a/extensions/renderer/resources/extensions_renderer_resources.grd b/extensions/renderer/resources/extensions_renderer_resources.grd index 1b5742e..a2c0283 100644 --- a/extensions/renderer/resources/extensions_renderer_resources.grd +++ b/extensions/renderer/resources/extensions_renderer_resources.grd @@ -19,6 +19,7 @@ <include name="IDR_SCHEMA_UTILS_JS" file="schema_utils.js" type="BINDATA" /> <include name="IDR_SEND_REQUEST_JS" file="send_request.js" type="BINDATA" /> <include name="IDR_SET_ICON_JS" file="set_icon.js" type="BINDATA" /> + <include name="IDR_BROWSER_TEST_ENVIRONMENT_SPECIFIC_BINDINGS_JS" file="browser_test_environment_specific_bindings.js" type="BINDATA" /> <include name="IDR_TEST_CUSTOM_BINDINGS_JS" file="test_custom_bindings.js" type="BINDATA" /> <include name="IDR_UNCAUGHT_EXCEPTION_HANDLER_JS" file="uncaught_exception_handler.js" type="BINDATA" /> <include name="IDR_UNLOAD_EVENT_JS" file="unload_event.js" type="BINDATA" /> diff --git a/extensions/renderer/resources/test_custom_bindings.js b/extensions/renderer/resources/test_custom_bindings.js index 5fd4549..188a9c8 100644 --- a/extensions/renderer/resources/test_custom_bindings.js +++ b/extensions/renderer/resources/test_custom_bindings.js @@ -7,10 +7,9 @@ var binding = require('binding').Binding.create('test'); -var chrome = requireNative('chrome').GetChrome(); +var environmentSpecificBindings = require('test_environment_specific_bindings'); var GetExtensionAPIDefinitionsForTest = requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest; -var GetAvailability = requireNative('v8_context').GetAvailability; var GetAPIFeatures = requireNative('test_features').GetAPIFeatures; var uncaughtExceptionHandler = require('uncaught_exception_handler'); var userGestures = requireNative('user_gestures'); @@ -37,9 +36,7 @@ binding.registerCustomHook(function(api) { } function testDone() { - // Use setTimeout here to allow previous test contexts to be - // eligible for garbage collection. - setTimeout(chromeTest.runNextTest, 0); + environmentSpecificBindings.testDone(chromeTest.runNextTest); } function allTestsDone() { @@ -60,7 +57,7 @@ binding.registerCustomHook(function(api) { return function() { if (called != null) { var redundantPrefix = 'Error\n'; - chrome.test.fail( + chromeTest.fail( 'Callback has already been run. ' + 'First call:\n' + $String.slice(called, redundantPrefix.length) + '\n' + @@ -355,6 +352,8 @@ binding.registerCustomHook(function(api) { chromeTest.assertEq(typeof(callback), 'function'); uncaughtExceptionHandler.setHandler(callback); }); + + environmentSpecificBindings.registerHooks(api); }); exports.binding = binding.generate(); diff --git a/extensions/renderer/script_context.cc b/extensions/renderer/script_context.cc index 0f40f9a..97e6c76 100644 --- a/extensions/renderer/script_context.cc +++ b/extensions/renderer/script_context.cc @@ -121,6 +121,8 @@ void ScriptContext::DispatchEvent(const char* event_name, } void ScriptContext::DispatchOnUnloadEvent() { + v8::HandleScope handle_scope(isolate()); + v8::Context::Scope context_scope(v8_context()); module_system_->CallModuleMethod("unload_event", "dispatch"); } diff --git a/extensions/test/DEPS b/extensions/test/DEPS new file mode 100644 index 0000000..26b3ad9b --- /dev/null +++ b/extensions/test/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/embedder", +] diff --git a/extensions/test/data/api_test_base_unittest.js b/extensions/test/data/api_test_base_unittest.js new file mode 100644 index 0000000..c402d59 --- /dev/null +++ b/extensions/test/data/api_test_base_unittest.js @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var test = require('test').binding; +var unittestBindings = require('test_environment_specific_bindings'); + +unittestBindings.exportTests([ + function testEnvironment() { + test.assertTrue(!!$Array); + test.assertTrue(!!$Function); + test.assertTrue(!!$JSON); + test.assertTrue(!!$Object); + test.assertTrue(!!$RegExp); + test.assertTrue(!!$String); + test.assertTrue(!!privates); + test.assertTrue(!!define); + test.assertTrue(!!require); + test.assertTrue(!!requireNative); + test.assertTrue(!!requireAsync); + test.assertEq(undefined, chrome.runtime.lastError); + test.assertEq(undefined, chrome.extension.lastError); + test.succeed(); + }, + function testPromisesRun() { + Promise.resolve().then(test.callbackPass()); + }, + function testCommonModulesAreAvailable() { + var binding = require('binding'); + var sendRequest = require('sendRequest'); + var lastError = require('lastError'); + test.assertTrue(!!binding); + test.assertTrue(!!sendRequest); + test.assertTrue(!!lastError); + test.succeed(); + }, + function testMojoModulesAreAvailable() { + Promise.all([ + requireAsync('mojo/public/js/bindings/connection'), + requireAsync('mojo/public/js/bindings/core'), + requireAsync('content/public/renderer/service_provider'), + ]).then(test.callback(function(modules) { + var connection = modules[0]; + var core = modules[1]; + var serviceProvider = modules[2]; + test.assertTrue(!!connection.Connection); + test.assertTrue(!!core.createMessagePipe); + test.assertTrue(!!serviceProvider.connectToService); + })); + }, + function testTestBindings() { + var counter = 0; + function increment() { + counter++; + } + test.runWithUserGesture(increment); + test.runWithoutUserGesture(increment); + test.runWithModuleSystem(increment); + test.assertEq(3, counter); + test.assertFalse(test.isProcessingUserGesture()); + test.assertTrue(!!test.getApiFeatures()); + test.assertEq(0, test.getApiDefinitions().length); + test.succeed(); + } +], test.runTests, exports); diff --git a/extensions/test/data/unit_test_environment_specific_bindings.js b/extensions/test/data/unit_test_environment_specific_bindings.js new file mode 100644 index 0000000..245d050 --- /dev/null +++ b/extensions/test/data/unit_test_environment_specific_bindings.js @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +var nativesPromise = requireAsync('testNatives'); + +function registerHooks(api) { + var chromeTest = api.compiledApi; + var apiFunctions = api.apiFunctions; + + apiFunctions.setHandleRequest('notifyPass', function() { + nativesPromise.then(function(natives) { + natives.NotifyPass(); + }); + }); + + apiFunctions.setHandleRequest('notifyFail', function(message) { + nativesPromise.then(function(natives) { + natives.NotifyFail(message); + }); + }); + + apiFunctions.setHandleRequest('log', function() { + nativesPromise.then(function(natives) { + natives.Log($Array.join(arguments, ' ')); + }); + }); + +} + +function testDone(runNextTest) { + // Use a promise here to allow previous test contexts to be eligible for + // garbage collection. + Promise.resolve().then(function() { + runNextTest(); + }); +} + +function exportTests(tests, runTests, exports) { + $Array.forEach(tests, function(test) { + exports[test.name] = function() { + runTests([test]); + return true; + } + }); +} + +exports.registerHooks = registerHooks; +exports.testDone = testDone; +exports.exportTests = exportTests; diff --git a/extensions/test/extensions_unittests_main.cc b/extensions/test/extensions_unittests_main.cc index b13ce80..be46e85 100644 --- a/extensions/test/extensions_unittests_main.cc +++ b/extensions/test/extensions_unittests_main.cc @@ -13,6 +13,7 @@ #include "extensions/common/constants.h" #include "extensions/common/extension_paths.h" #include "extensions/test/test_extensions_client.h" +#include "mojo/embedder/embedder.h" #include "ui/base/resource/resource_bundle.h" namespace { @@ -96,6 +97,7 @@ void ExtensionsTestSuite::Shutdown() { int main(int argc, char** argv) { content::UnitTestTestSuite test_suite(new ExtensionsTestSuite(argc, argv)); + mojo::embedder::Init(); return base::LaunchUnitTests(argc, argv, base::Bind(&content::UnitTestTestSuite::Run, diff --git a/extensions/test/test_extensions_client.cc b/extensions/test/test_extensions_client.cc index 8cee49a..47a6a47 100644 --- a/extensions/test/test_extensions_client.cc +++ b/extensions/test/test_extensions_client.cc @@ -4,15 +4,30 @@ #include "extensions/test/test_extensions_client.h" +#include "extensions/common/api/generated_schemas.h" #include "extensions/common/common_manifest_handlers.h" +#include "extensions/common/features/api_feature.h" +#include "extensions/common/features/base_feature_provider.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/features/json_feature_provider_source.h" +#include "extensions/common/features/manifest_feature.h" +#include "extensions/common/features/permission_feature.h" #include "extensions/common/manifest_handler.h" #include "extensions/common/url_pattern_set.h" #include "extensions/test/test_permission_message_provider.h" +#include "grit/extensions_resources.h" namespace extensions { +namespace { + +template <class FeatureClass> +SimpleFeature* CreateFeature() { + return new FeatureClass; +} + +} // namespace + TestExtensionsClient::TestExtensionsClient() { } @@ -34,17 +49,42 @@ TestExtensionsClient::GetPermissionMessageProvider() const { return provider; } -// TODO(yoz): Implement something reasonable here. scoped_ptr<FeatureProvider> TestExtensionsClient::CreateFeatureProvider( const std::string& name) const { - return scoped_ptr<FeatureProvider>(); + scoped_ptr<FeatureProvider> provider; + scoped_ptr<JSONFeatureProviderSource> source( + CreateFeatureProviderSource(name)); + if (name == "api") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature<APIFeature>)); + } else if (name == "manifest") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature<ManifestFeature>)); + } else if (name == "permission") { + provider.reset(new BaseFeatureProvider(source->dictionary(), + CreateFeature<PermissionFeature>)); + } else { + NOTREACHED(); + } + return provider.Pass(); } -// TODO(yoz): Implement something reasonable here. scoped_ptr<JSONFeatureProviderSource> TestExtensionsClient::CreateFeatureProviderSource( const std::string& name) const { - return scoped_ptr<JSONFeatureProviderSource>(); + scoped_ptr<JSONFeatureProviderSource> source( + new JSONFeatureProviderSource(name)); + if (name == "api") { + source->LoadJSON(IDR_EXTENSION_API_FEATURES); + } else if (name == "manifest") { + source->LoadJSON(IDR_EXTENSION_MANIFEST_FEATURES); + } else if (name == "permission") { + source->LoadJSON(IDR_EXTENSION_PERMISSION_FEATURES); + } else { + NOTREACHED(); + source.reset(); + } + return source.Pass(); } void TestExtensionsClient::FilterHostPermissions( @@ -77,12 +117,12 @@ bool TestExtensionsClient::IsScriptableURL(const GURL& url, bool TestExtensionsClient::IsAPISchemaGenerated( const std::string& name) const { - return false; + return core_api::GeneratedSchemas::IsGenerated(name); } base::StringPiece TestExtensionsClient::GetAPISchema( const std::string& name) const { - return base::StringPiece(); + return core_api::GeneratedSchemas::Get(name); } void TestExtensionsClient::RegisterAPISchemaResources(ExtensionAPI* api) const { |