summaryrefslogtreecommitdiffstats
path: root/gin/modules
diff options
context:
space:
mode:
authorabarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-17 17:46:07 +0000
committerabarth@chromium.org <abarth@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-17 17:46:07 +0000
commit97f21cad04bb80834a8cc84bfc3dd24a96531a16 (patch)
tree4e49aa68b49f90e4e68c220cc9d9b04be95a6a5d /gin/modules
parent00509a3ac0c1c11e97852d2331d116df6754eb5d (diff)
downloadchromium_src-97f21cad04bb80834a8cc84bfc3dd24a96531a16.zip
chromium_src-97f21cad04bb80834a8cc84bfc3dd24a96531a16.tar.gz
chromium_src-97f21cad04bb80834a8cc84bfc3dd24a96531a16.tar.bz2
This CL implements the Asynchronous Module Definition (AMD)
API, which we plan to use for JavaScript in Mojo. We don't yet implement every feature in the AMD spec <https://github.com/amdjs/amdjs-api/wiki/AMD>, but we implement the basic framework, which will let us get started writing and testing JavaScript modules in Mojo. The two other leading choices for a modules system are CommonJS and ES6 modules. We decided not to use CommonJS, despite its popularity, because it implies the ability to load modules synchronously. That works well in server environments like node.js, but it won't work well for Mojo where modules might be loaded across a network. I would really like to have used ES6 modules, but the spec isn't finalized yet and V8 doesn't yet implement them. It's likely that we'll replace this AMD module system with ES6 modules once ES6 modules are ready. Structurally, I've implemented AMD in the ModuleRegistry class in a new "modules" directory in Gin. Nothing else in Gin (except the tests) depends on ModuleRegistry, which means folks are free to use Gin without AMD. At the Mojo layer, I've added a dependency on AMD. BUG=317398 Review URL: https://codereview.chromium.org/62333018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235543 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'gin/modules')
-rw-r--r--gin/modules/module_registry.cc188
-rw-r--r--gin/modules/module_registry.h65
-rw-r--r--gin/modules/module_registry_unittests.js30
3 files changed, 283 insertions, 0 deletions
diff --git a/gin/modules/module_registry.cc b/gin/modules/module_registry.cc
new file mode 100644
index 0000000..78a102c
--- /dev/null
+++ b/gin/modules/module_registry.cc
@@ -0,0 +1,188 @@
+// Copyright 2013 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 "gin/modules/module_registry.h"
+
+#include <assert.h>
+#include <string>
+#include <vector>
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/per_isolate_data.h"
+#include "gin/wrapper_info.h"
+
+using v8::External;
+using v8::Handle;
+using v8::Isolate;
+using v8::ObjectTemplate;
+
+namespace gin {
+
+struct PendingModule {
+ PendingModule();
+ ~PendingModule();
+
+ std::string id;
+ std::vector<std::string> dependencies;
+ v8::Persistent<v8::Value> factory;
+};
+
+namespace {
+
+void Define(const v8::FunctionCallbackInfo<v8::Value>& info) {
+ Arguments args(info);
+
+ if (!info.Length())
+ return args.ThrowTypeError("At least one argument is required.");
+
+ std::string id;
+ std::vector<std::string> dependencies;
+ Handle<v8::Value> factory;
+
+ if (args.PeekNext()->IsString())
+ args.GetNext(&id);
+ if (args.PeekNext()->IsArray())
+ args.GetNext(&dependencies);
+ if (!args.GetNext(&factory))
+ return args.ThrowError();
+
+ PendingModule* pending = new PendingModule;
+ pending->id = id;
+ pending->dependencies = dependencies;
+ pending->factory.Reset(args.isolate(), factory);
+
+ ModuleRegistry* registry =
+ ModuleRegistry::From(args.isolate()->GetCurrentContext());
+ registry->AddPendingModule(args.isolate(), pending);
+}
+
+WrapperInfo g_wrapper_info = {};
+
+v8::Local<v8::FunctionTemplate> GetDefineTemplate(v8::Isolate* isolate) {
+ PerIsolateData* data = PerIsolateData::From(isolate);
+ v8::Local<v8::FunctionTemplate> templ = data->GetFunctionTemplate(
+ &g_wrapper_info);
+ if (templ.IsEmpty()) {
+ templ = v8::FunctionTemplate::New(Define);
+ data->SetFunctionTemplate(&g_wrapper_info, templ);
+ }
+ return templ;
+}
+
+Handle<v8::String> GetHiddenValueKey(v8::Isolate* isolate) {
+ return StringToSymbol(isolate, "::gin::ModuleRegistry");
+}
+
+} // namespace
+
+
+PendingModule::PendingModule() {
+}
+
+PendingModule::~PendingModule() {
+ factory.Reset();
+}
+
+ModuleRegistry::ModuleRegistry(v8::Isolate* isolate)
+ : modules_(isolate, v8::Object::New()) {
+}
+
+ModuleRegistry::~ModuleRegistry() {
+ for (PendingModuleList::iterator it = pending_modules_.begin();
+ it != pending_modules_.end(); ++it) {
+ delete *it;
+ }
+ modules_.Reset();
+}
+
+void ModuleRegistry::RegisterGlobals(v8::Isolate* isolate,
+ Handle<v8::ObjectTemplate> templ) {
+ templ->Set(StringToSymbol(isolate, "define"), GetDefineTemplate(isolate));
+}
+
+void ModuleRegistry::AddBuiltinModule(Isolate* isolate,
+ const std::string& id,
+ Handle<ObjectTemplate> templ) {
+ assert(!id.empty());
+ Handle<v8::Object> modules = v8::Local<v8::Object>::New(isolate, modules_);
+ modules->Set(StringToV8(isolate, id), templ->NewInstance());
+}
+
+ModuleRegistry* ModuleRegistry::From(Handle<v8::Context> context) {
+ v8::Isolate* isolate = context->GetIsolate();
+ Handle<v8::String> key = GetHiddenValueKey(isolate);
+ Handle<v8::Value> value = context->Global()->GetHiddenValue(key);
+ Handle<v8::External> external;
+ if (value.IsEmpty() || !ConvertFromV8(value, &external)) {
+ PerContextData* data = PerContextData::From(context);
+ if (!data)
+ return NULL;
+ ModuleRegistry* registry = new ModuleRegistry(isolate);
+ context->Global()->SetHiddenValue(key, v8::External::New(registry));
+ data->AddSupplement(registry);
+ return registry;
+ }
+ return static_cast<ModuleRegistry*>(external->Value());
+}
+
+void ModuleRegistry::AddPendingModule(v8::Isolate* isolate,
+ PendingModule* pending) {
+ if (AttemptToLoad(isolate, pending))
+ AttemptToLoadPendingModules(isolate);
+}
+
+void ModuleRegistry::Detach(Handle<v8::Context> context) {
+ context->Global()->SetHiddenValue(GetHiddenValueKey(context->GetIsolate()),
+ Handle<v8::Value>());
+}
+
+bool ModuleRegistry::AttemptToLoad(v8::Isolate* isolate,
+ PendingModule* pending) {
+ Handle<v8::Object> modules = v8::Local<v8::Object>::New(isolate, modules_);
+ Handle<v8::String> key = StringToV8(isolate, pending->id);
+
+ if (!pending->id.empty() && modules->HasOwnProperty(key)) {
+ // We've already loaded a module with this name. Ignore the new one.
+ delete pending;
+ return true;
+ }
+
+ size_t argc = pending->dependencies.size();
+ std::vector<Handle<v8::Value> > argv(argc);
+ for (size_t i = 0; i < argc; ++i) {
+ Handle<v8::String> key = StringToV8(isolate, pending->dependencies[i]);
+ if (!modules->HasOwnProperty(key)) {
+ pending_modules_.push_back(pending);
+ return false;
+ }
+ argv[i] = modules->Get(key);
+ }
+
+ Handle<v8::Value> module = v8::Local<v8::Value>::New(
+ isolate, pending->factory);
+
+ Handle<v8::Function> factory;
+ if (ConvertFromV8(module, &factory)) {
+ v8::Handle<v8::Object> global = isolate->GetCurrentContext()->Global();
+ module = factory->Call(global, argc, argv.data());
+ // TODO(abarth): What should we do with exceptions?
+ }
+
+ if (!pending->id.empty() && !module.IsEmpty())
+ modules->Set(key, module);
+
+ delete pending;
+ return true;
+}
+
+void ModuleRegistry::AttemptToLoadPendingModules(v8::Isolate* isolate) {
+ PendingModuleList pending_modules;
+ pending_modules.swap(pending_modules_);
+ for (PendingModuleList::iterator it = pending_modules.begin();
+ it != pending_modules.end(); ++it) {
+ AttemptToLoad(isolate, *it);
+ }
+}
+
+} // namespace gin
diff --git a/gin/modules/module_registry.h b/gin/modules/module_registry.h
new file mode 100644
index 0000000..7cff355
--- /dev/null
+++ b/gin/modules/module_registry.h
@@ -0,0 +1,65 @@
+// Copyright 2013 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 GIN_MODULES_MODULE_REGISTRY_H_
+#define GIN_MODULES_MODULE_REGISTRY_H_
+
+#include <list>
+#include <string>
+#include "base/compiler_specific.h"
+#include "gin/per_context_data.h"
+
+namespace gin {
+
+struct PendingModule;
+
+// This class implements the Asynchronous Module Definition (AMD) API.
+// https://github.com/amdjs/amdjs-api/wiki/AMD
+//
+// Our implementation isn't complete yet. Missing features:
+// 1) Built-in support for require, exports, and module.
+// 2) Path resoltuion in module names.
+//
+// For these reasons, we don't have an "amd" property on the "define"
+// function. The spec says we should only add that property once our
+// implementation complies with the specification.
+//
+class ModuleRegistry : public ContextSupplement {
+ public:
+ static ModuleRegistry* From(v8::Handle<v8::Context> context);
+
+ static void RegisterGlobals(v8::Isolate* isolate,
+ v8::Handle<v8::ObjectTemplate> templ);
+
+ // The caller must have already entered our context.
+ void AddBuiltinModule(v8::Isolate* isolate,
+ const std::string& id,
+ v8::Handle<v8::ObjectTemplate> templ);
+
+ // Takes ownership of |pending|. The caller must have already entered
+ // our context.
+ void AddPendingModule(v8::Isolate* isolate, PendingModule* pending);
+
+ private:
+ typedef std::list<PendingModule*> PendingModuleList; // Owning reference.
+
+ explicit ModuleRegistry(v8::Isolate* isolate);
+ virtual ~ModuleRegistry();
+
+ // From ContextSupplement:
+ virtual void Detach(v8::Handle<v8::Context> context) OVERRIDE;
+
+ // Takes ownership of |pending|.
+ bool AttemptToLoad(v8::Isolate* isolate, PendingModule* pending);
+ void AttemptToLoadPendingModules(v8::Isolate* isolate);
+
+ v8::Persistent<v8::Object> modules_;
+ PendingModuleList pending_modules_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleRegistry);
+};
+
+} // namespace gin
+
+#endif // GIN_MODULES_MODULE_REGISTRY_H_
diff --git a/gin/modules/module_registry_unittests.js b/gin/modules/module_registry_unittests.js
new file mode 100644
index 0000000..ca70148
--- /dev/null
+++ b/gin/modules/module_registry_unittests.js
@@ -0,0 +1,30 @@
+// Copyright 2013 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.
+
+define("module0", function() {
+ return {
+ "foo": "bar",
+ }
+});
+
+define("module2", [
+ "gtest",
+ "module0",
+ "module1"
+ ], function(gtest, module0, module1) {
+ gtest.expectEqual(module0.foo, "bar",
+ "module0.foo is " + module0.foo);
+ gtest.expectFalse(module0.bar,
+ "module0.bar is " + module0.bar);
+ gtest.expectEqual(module1.baz, "qux",
+ "module1.baz is " + module1.baz);
+ gtest.expectFalse(module1.qux,
+ "module1.qux is " + module1.qux);
+
+ this.result = "PASS";
+});
+
+define("module1", {
+ "baz": "qux",
+});