Skip to content

Commit

Permalink
This CL implements the Asynchronous Module Definition (AMD)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
abarth@chromium.org committed Nov 17, 2013
1 parent 00509a3 commit 97f21ca
Show file tree
Hide file tree
Showing 30 changed files with 827 additions and 211 deletions.
6 changes: 6 additions & 0 deletions gin/arguments.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ Arguments::Arguments(const v8::FunctionCallbackInfo<v8::Value>& info)
Arguments::~Arguments() {
}

v8::Handle<v8::Value> Arguments::PeekNext() {
if (next_ >= info_.Length())
return v8::Handle<v8::Value>();
return info_[next_];
}

void Arguments::ThrowError() {
if (insufficient_arguments_)
return ThrowTypeError("Insufficient number of arguments.");
Expand Down
2 changes: 2 additions & 0 deletions gin/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class Arguments {
info_.GetReturnValue().Set(ConvertToV8(isolate_, val));
}

v8::Handle<v8::Value> PeekNext();

void ThrowError();
void ThrowTypeError(const std::string& message);

Expand Down
34 changes: 34 additions & 0 deletions gin/context_holder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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/context_holder.h"

#include <assert.h>
#include "gin/per_context_data.h"

namespace gin {

ContextHolder::ContextHolder(v8::Isolate* isolate)
: isolate_(isolate) {
}

ContextHolder::~ContextHolder() {
v8::HandleScope handle_scope(isolate());
v8::Handle<v8::Context> context = this->context();

PerContextData* data = PerContextData::From(context);
data->Detach(context);
delete data;

// TODO(abarth): Figure out how to set kResetInDestructor to true.
context_.Reset();
}

void ContextHolder::SetContext(v8::Handle<v8::Context> context) {
assert(context_.IsEmpty());
context_.Reset(isolate_, context);
new PerContextData(context); // Deleted in ~ContextHolder.
}

} // namespace gin
36 changes: 36 additions & 0 deletions gin/context_holder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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_CONTEXT_HOLDER_H_
#define GIN_CONTEXT_HOLDER_H_

#include <list>
#include "base/basictypes.h"
#include "v8/include/v8.h"

namespace gin {

class ContextHolder {
public:
explicit ContextHolder(v8::Isolate* isolate);
~ContextHolder();

v8::Isolate* isolate() const { return isolate_; }

v8::Handle<v8::Context> context() const {
return v8::Local<v8::Context>::New(isolate_, context_);
}

void SetContext(v8::Handle<v8::Context> context);

private:
v8::Isolate* isolate_;
v8::Persistent<v8::Context> context_;

DISALLOW_COPY_AND_ASSIGN(ContextHolder);
};

} // namespace gin

#endif // GIN_CONTEXT_HOLDER_H_
25 changes: 25 additions & 0 deletions gin/converter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "v8/include/v8.h"

using v8::Boolean;
using v8::External;
using v8::Function;
using v8::Handle;
using v8::Integer;
Expand Down Expand Up @@ -124,6 +125,30 @@ bool Converter<Handle<Object> >::FromV8(Handle<Value> val,
return true;
}

Handle<Value> Converter<Handle<External> >::ToV8(v8::Isolate* isolate,
Handle<External> val) {
return val.As<Value>();
}

bool Converter<Handle<External> >::FromV8(Handle<Value> val,
Handle<External>* out) {
if (!val->IsExternal())
return false;
*out = Handle<External>::Cast(val);
return true;
}

Handle<Value> Converter<Handle<Value> >::ToV8(v8::Isolate* isolate,
Handle<Value> val) {
return val;
}

bool Converter<Handle<Value> >::FromV8(Handle<Value> val,
Handle<Value>* out) {
*out = val;
return true;
}

v8::Handle<v8::String> StringToSymbol(v8::Isolate* isolate,
const std::string& val) {
return String::NewFromUtf8(isolate,
Expand Down
16 changes: 16 additions & 0 deletions gin/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ struct Converter<v8::Handle<v8::Object> > {
v8::Handle<v8::Object>* out);
};

template<>
struct Converter<v8::Handle<v8::External> > {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
v8::Handle<v8::External> val);
static bool FromV8(v8::Handle<v8::Value> val,
v8::Handle<v8::External>* out);
};

template<>
struct Converter<v8::Handle<v8::Value> > {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val);
static bool FromV8(v8::Handle<v8::Value> val,
v8::Handle<v8::Value>* out);
};

template<typename T>
struct Converter<std::vector<T> > {
static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
Expand Down
9 changes: 9 additions & 0 deletions gin/gin.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,22 @@
'../v8/tools/gyp/v8.gyp:v8',
],
'sources': [
'modules/module_registry.cc',
'modules/module_registry.h',
'arguments.cc',
'arguments.h',
'array_buffer.cc',
'array_buffer.h',
'converter.cc',
'converter.h',
'context_holder.cc',
'context_holder.h',
'dictionary.cc',
'dictionary.h',
'initialize.cc',
'initialize.h',
'per_context_data.cc',
'per_context_data.h',
'per_isolate_data.cc',
'per_isolate_data.h',
'runner.cc',
Expand All @@ -52,6 +58,8 @@
'gin',
],
'sources': [
'test/file_runner.cc',
'test/file_runner.h',
'test/gtest.cc',
'test/gtest.h',
'test/v8_test.cc',
Expand All @@ -68,6 +76,7 @@
'sources': [
'converter_unittest.cc',
'test/run_all_unittests.cc',
'test/run_js_tests.cc',
'runner_unittest.cc',
],
},
Expand Down
188 changes: 188 additions & 0 deletions gin/modules/module_registry.cc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 97f21ca

Please sign in to comment.