| // 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 <stddef.h> |
| #include <stdint.h> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "gin/arguments.h" |
| #include "gin/converter.h" |
| #include "gin/modules/module_registry_observer.h" |
| #include "gin/per_context_data.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 { |
| |
| // Key for base::SupportsUserData::Data. |
| const char kModuleRegistryKey[] = "ModuleRegistry"; |
| |
| struct ModuleRegistryData : public base::SupportsUserData::Data { |
| std::unique_ptr<ModuleRegistry> registry; |
| }; |
| |
| 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::Local<Value> factory; |
| |
| if (!args.PeekNext().IsEmpty() && args.PeekNext()->IsString()) |
| args.GetNext(&id); |
| if (!args.PeekNext().IsEmpty() && args.PeekNext()->IsArray()) |
| args.GetNext(&dependencies); |
| if (!args.GetNext(&factory)) |
| return args.ThrowError(); |
| |
| std::unique_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(), std::move(pending)); |
| } |
| |
| 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; |
| } |
| |
| } // namespace |
| |
| ModuleRegistry::ModuleRegistry(Isolate* isolate) |
| : modules_(isolate, Object::New(isolate)) { |
| } |
| |
| ModuleRegistry::~ModuleRegistry() { |
| modules_.Reset(); |
| } |
| |
| // static |
| void ModuleRegistry::RegisterGlobals(Isolate* isolate, |
| v8::Local<ObjectTemplate> templ) { |
| templ->Set(StringToSymbol(isolate, "define"), GetDefineTemplate(isolate)); |
| } |
| |
| // static |
| bool ModuleRegistry::InstallGlobals(v8::Isolate* isolate, |
| v8::Local<v8::Object> obj) { |
| v8::Local<v8::Function> function; |
| auto maybe_function = |
| GetDefineTemplate(isolate)->GetFunction(isolate->GetCurrentContext()); |
| if (!maybe_function.ToLocal(&function)) |
| return false; |
| return SetProperty(isolate, obj, StringToSymbol(isolate, "define"), function); |
| } |
| |
| // static |
| ModuleRegistry* ModuleRegistry::From(v8::Local<Context> context) { |
| PerContextData* data = PerContextData::From(context); |
| if (!data) |
| return NULL; |
| |
| ModuleRegistryData* registry_data = static_cast<ModuleRegistryData*>( |
| data->GetUserData(kModuleRegistryKey)); |
| if (!registry_data) { |
| // PerContextData takes ownership of ModuleRegistryData. |
| registry_data = new ModuleRegistryData; |
| registry_data->registry.reset(new ModuleRegistry(context->GetIsolate())); |
| data->SetUserData(kModuleRegistryKey, registry_data); |
| } |
| return registry_data->registry.get(); |
| } |
| |
| void ModuleRegistry::AddObserver(ModuleRegistryObserver* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void ModuleRegistry::RemoveObserver(ModuleRegistryObserver* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void ModuleRegistry::AddBuiltinModule(Isolate* isolate, const std::string& id, |
| v8::Local<Value> module) { |
| DCHECK(!id.empty()); |
| RegisterModule(isolate, id, module); |
| } |
| |
| void ModuleRegistry::AddPendingModule(Isolate* isolate, |
| std::unique_ptr<PendingModule> pending) { |
| const std::string pending_id = pending->id; |
| const std::vector<std::string> pending_dependencies = pending->dependencies; |
| AttemptToLoad(isolate, std::move(pending)); |
| FOR_EACH_OBSERVER(ModuleRegistryObserver, observer_list_, |
| OnDidAddPendingModule(pending_id, pending_dependencies)); |
| } |
| |
| 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; |
| } |
| waiting_callbacks_.insert(std::make_pair(id, callback)); |
| |
| for (size_t i = 0; i < pending_modules_.size(); ++i) { |
| if (pending_modules_[i]->id == id) |
| return; |
| } |
| |
| unsatisfied_dependencies_.insert(id); |
| } |
| |
| bool ModuleRegistry::RegisterModule(Isolate* isolate, |
| const std::string& id, |
| v8::Local<Value> module) { |
| if (id.empty() || module.IsEmpty()) |
| return false; |
| |
| v8::Local<Object> modules = Local<Object>::New(isolate, modules_); |
| if (!SetProperty(isolate, modules, StringToSymbol(isolate, id), module)) |
| return false; |
| unsatisfied_dependencies_.erase(id); |
| available_modules_.insert(id); |
| |
| std::pair<LoadModuleCallbackMap::iterator, LoadModuleCallbackMap::iterator> |
| range = waiting_callbacks_.equal_range(id); |
| std::vector<LoadModuleCallback> callbacks; |
| callbacks.reserve(waiting_callbacks_.count(id)); |
| for (LoadModuleCallbackMap::iterator it = range.first; it != range.second; |
| ++it) { |
| callbacks.push_back(it->second); |
| } |
| waiting_callbacks_.erase(range.first, range.second); |
| for (std::vector<LoadModuleCallback>::iterator it = callbacks.begin(); |
| it != callbacks.end(); |
| ++it) { |
| // Should we call the callback asynchronously? |
| it->Run(module); |
| } |
| return true; |
| } |
| |
| 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; |
| } |
| |
| bool ModuleRegistry::Load(Isolate* isolate, |
| std::unique_ptr<PendingModule> pending) { |
| if (!pending->id.empty() && available_modules_.count(pending->id)) |
| return true; // We've already loaded this module. |
| |
| uint32_t argc = static_cast<uint32_t>(pending->dependencies.size()); |
| std::vector<v8::Local<Value> > argv(argc); |
| for (uint32_t i = 0; i < argc; ++i) |
| argv[i] = GetModule(isolate, pending->dependencies[i]); |
| |
| v8::Local<Value> module = Local<Value>::New(isolate, pending->factory); |
| |
| v8::Local<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); |
| } |
| |
| return RegisterModule(isolate, pending->id, module); |
| } |
| |
| bool ModuleRegistry::AttemptToLoad(Isolate* isolate, |
| std::unique_ptr<PendingModule> pending) { |
| if (!CheckDependencies(pending.get())) { |
| pending_modules_.push_back(pending.release()); |
| return false; |
| } |
| return Load(isolate, std::move(pending)); |
| } |
| |
| v8::Local<v8::Value> ModuleRegistry::GetModule(v8::Isolate* isolate, |
| const std::string& id) { |
| v8::Local<Object> modules = Local<Object>::New(isolate, modules_); |
| v8::Local<String> key = StringToSymbol(isolate, id); |
| DCHECK(modules->HasOwnProperty(isolate->GetCurrentContext(), key).FromJust()); |
| return modules->Get(isolate->GetCurrentContext(), key).ToLocalChecked(); |
| } |
| |
| 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) { |
| std::unique_ptr<PendingModule> pending(pending_modules[i]); |
| pending_modules[i] = NULL; |
| if (AttemptToLoad(isolate, std::move(pending))) |
| keep_trying = true; |
| } |
| } |
| } |
| |
| } // namespace gin |