// 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 <string>
#include <vector>
#include "base/logging.h"
#include "gin/arguments.h"
#include "gin/converter.h"
#include "gin/per_isolate_data.h"
#include "gin/public/wrapper_info.h"
#include "gin/runner.h"
using v8::Context;
using v8::External;
using v8::Function;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::ObjectTemplate;
using v8::Persistent;
using v8::StackTrace;
using v8::String;
using v8::Value;
namespace gin {
struct PendingModule {
PendingModule();
~PendingModule();
std::string id;
std::vector<std::string> dependencies;
Persistent<Value> factory;
};
PendingModule::PendingModule() {
}
PendingModule::~PendingModule() {
factory.Reset();
}
namespace {
void Define(const v8::FunctionCallbackInfo<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;
v8::Handle<Value> factory;
if (args.PeekNext()->IsString())
args.GetNext(&id);
if (args.PeekNext()->IsArray())
args.GetNext(&dependencies);
if (!args.GetNext(&factory))
return args.ThrowError();
scoped_ptr<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.Pass());
}
WrapperInfo g_wrapper_info = { kEmbedderNativeGin };
Local<FunctionTemplate> GetDefineTemplate(Isolate* isolate) {
PerIsolateData* data = PerIsolateData::From(isolate);
Local<FunctionTemplate> templ = data->GetFunctionTemplate(
&g_wrapper_info);
if (templ.IsEmpty()) {
templ = FunctionTemplate::New(isolate, Define);
data->SetFunctionTemplate(&g_wrapper_info, templ);
}
return templ;
}
v8::Handle<String> GetHiddenValueKey(Isolate* isolate) {
return StringToSymbol(isolate, "::gin::ModuleRegistry");
}
} // namespace
ModuleRegistry::ModuleRegistry(Isolate* isolate)
: modules_(isolate, Object::New()) {
}
ModuleRegistry::~ModuleRegistry() {
modules_.Reset();
}
void ModuleRegistry::RegisterGlobals(Isolate* isolate,
v8::Handle<ObjectTemplate> templ) {
templ->Set(StringToSymbol(isolate, "define"), GetDefineTemplate(isolate));
}
ModuleRegistry* ModuleRegistry::From(v8::Handle<Context> context) {
Isolate* isolate = context->GetIsolate();
v8::Handle<String> key = GetHiddenValueKey(isolate);
v8::Handle<Value> value = context->Global()->GetHiddenValue(key);
v8::Handle<External> external;
if (value.IsEmpty() || !ConvertFromV8(isolate, value, &external)) {
PerContextData* data = PerContextData::From(context);
if (!data)
return NULL;
ModuleRegistry* registry = new ModuleRegistry(isolate);
context->Global()->SetHiddenValue(key, External::New(isolate, registry));
data->AddSupplement(scoped_ptr<ContextSupplement>(registry));
return registry;
}
return static_cast<ModuleRegistry*>(external->Value());
}
void ModuleRegistry::AddBuiltinModule(Isolate* isolate,
const std::string& id,
v8::Handle<ObjectTemplate> templ) {
DCHECK(!id.empty());
RegisterModule(isolate, id, templ->NewInstance());
}
void ModuleRegistry::AddPendingModule(Isolate* isolate,
scoped_ptr<PendingModule> pending) {
AttemptToLoad(isolate, pending.Pass());
}
void ModuleRegistry::LoadModule(Isolate* isolate,
const std::string& id,
LoadModuleCallback callback) {
if (available_modules_.find(id) != available_modules_.end()) {
// Should we call the callback asynchronously?
callback.Run(GetModule(isolate, id));
return;
}
// Should we support multiple callers waiting on the same module?
DCHECK(waiting_callbacks_.find(id) == waiting_callbacks_.end());
waiting_callbacks_[id] = callback;
unsatisfied_dependencies_.insert(id);
}
void ModuleRegistry::RegisterModule(Isolate* isolate,
const std::string& id,
v8::Handle<Value> module) {
if (id.empty() || module.IsEmpty())
return;
unsatisfied_dependencies_.erase(id);
available_modules_.insert(id);
v8::Handle<Object> modules = Local<Object>::New(isolate, modules_);
modules->Set(StringToSymbol(isolate, id), module);
LoadModuleCallbackMap::iterator it = waiting_callbacks_.find(id);
if (it == waiting_callbacks_.end())
return;
LoadModuleCallback callback = it->second;
waiting_callbacks_.erase(it);
// Should we call the callback asynchronously?
callback.Run(module);
}
void ModuleRegistry::Detach(v8::Handle<Context> context) {
context->Global()->SetHiddenValue(GetHiddenValueKey(context->GetIsolate()),
v8::Handle<Value>());
}
bool ModuleRegistry::CheckDependencies(PendingModule* pending) {
size_t num_missing_dependencies = 0;
size_t len = pending->dependencies.size();
for (size_t i = 0; i < len; ++i) {
const std::string& dependency = pending->dependencies[i];
if (available_modules_.count(dependency))
continue;
unsatisfied_dependencies_.insert(dependency);
num_missing_dependencies++;
}
return num_missing_dependencies == 0;
}
void ModuleRegistry::Load(Isolate* isolate, scoped_ptr<PendingModule> pending) {
if (!pending->id.empty() && available_modules_.count(pending->id))
return; // We've already loaded this module.
uint32_t argc = static_cast<uint32_t>(pending->dependencies.size());
std::vector<v8::Handle<Value> > argv(argc);
for (uint32_t i = 0; i < argc; ++i)
argv[i] = GetModule(isolate, pending->dependencies[i]);
v8::Handle<Value> module = Local<Value>::New(isolate, pending->factory);
v8::Handle<Function> factory;
if (ConvertFromV8(isolate, module, &factory)) {
PerContextData* data = PerContextData::From(isolate->GetCurrentContext());
Runner* runner = data->runner();
module = runner->Call(factory, runner->global(), argc,
argv.empty() ? NULL : &argv.front());
if (pending->id.empty())
ConvertFromV8(isolate, factory->GetScriptOrigin().ResourceName(),
&pending->id);
}
RegisterModule(isolate, pending->id, module);
}
bool ModuleRegistry::AttemptToLoad(Isolate* isolate,
scoped_ptr<PendingModule> pending) {
if (!CheckDependencies(pending.get())) {
pending_modules_.push_back(pending.release());
return false;
}
Load(isolate, pending.Pass());
return true;
}
v8::Handle<v8::Value> ModuleRegistry::GetModule(v8::Isolate* isolate,
const std::string& id) {
v8::Handle<Object> modules = Local<Object>::New(isolate, modules_);
v8::Handle<String> key = StringToSymbol(isolate, id);
DCHECK(modules->HasOwnProperty(key));
return modules->Get(key);
}
void ModuleRegistry::AttemptToLoadMoreModules(Isolate* isolate) {
bool keep_trying = true;
while (keep_trying) {
keep_trying = false;
PendingModuleVector pending_modules;
pending_modules.swap(pending_modules_);
for (size_t i = 0; i < pending_modules.size(); ++i) {
scoped_ptr<PendingModule> pending(pending_modules[i]);
pending_modules[i] = NULL;
if (AttemptToLoad(isolate, pending.Pass()))
keep_trying = true;
}
}
}
} // namespace gin