Commit d9f51dad authored by sammc@chromium.org's avatar sammc@chromium.org

Add support for using AMD modules from extensions modules.

This adds requireAsync, which returns a promise for the requested
module, allowing extension modules to asynchronously import AMD modules:

requireAsync('foo').then(function(foo) {
  // Use foo.
});

BUG=390397

Review URL: https://codereview.chromium.org/359413004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281957 0039d316-1c4b-4281-b951-d872f2087c98
parent 86fcf7f3
......@@ -17,6 +17,7 @@ include_rules = [
"+content/public/renderer",
"+extensions/common",
"+extensions/renderer",
"+gin",
"+grit", # For generated headers
"+ppapi/c",
"+ppapi/shared_impl",
......
......@@ -13,18 +13,18 @@ class JsonSchemaTest : public ModuleSystemTest {
virtual void SetUp() OVERRIDE {
ModuleSystemTest::SetUp();
RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
RegisterModule("utils", IDR_UTILS_JS);
env()->RegisterModule("json_schema", IDR_JSON_SCHEMA_JS);
env()->RegisterModule("utils", IDR_UTILS_JS);
context_->module_system()->RegisterNativeHandler("schema_registry",
schema_registry_.AsNativeHandler());
env()->module_system()->RegisterNativeHandler(
"schema_registry", schema_registry_.AsNativeHandler());
RegisterTestFile("json_schema_test", "json_schema_test.js");
env()->RegisterTestFile("json_schema_test", "json_schema_test.js");
}
protected:
void TestFunction(const std::string& test_name) {
context_->module_system()->CallModuleMethod("json_schema_test", test_name);
env()->module_system()->CallModuleMethod("json_schema_test", test_name);
}
private:
......
......@@ -12,19 +12,22 @@ namespace {
class MessagingUtilsUnittest : public ModuleSystemTest {
protected:
void RegisterTestModule(const char* code) {
RegisterModule("test", base::StringPrintf(
"var assert = requireNative('assert');\n"
"var AssertTrue = assert.AssertTrue;\n"
"var AssertFalse = assert.AssertFalse;\n"
"var messagingUtils = require('messaging_utils');\n"
"%s", code));
env()->RegisterModule(
"test",
base::StringPrintf(
"var assert = requireNative('assert');\n"
"var AssertTrue = assert.AssertTrue;\n"
"var AssertFalse = assert.AssertFalse;\n"
"var messagingUtils = require('messaging_utils');\n"
"%s",
code));
}
private:
virtual void SetUp() OVERRIDE {
ModuleSystemTest::SetUp();
RegisterModule("messaging_utils", IDR_MESSAGING_UTILS_JS);
env()->RegisterModule("messaging_utils", IDR_MESSAGING_UTILS_JS);
}
};
......@@ -35,65 +38,65 @@ TEST_F(MessagingUtilsUnittest, TestNothing) {
TEST_F(MessagingUtilsUnittest, NoArguments) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments();\n"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, ZeroArguments) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments([]);"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, TooManyArgumentsNoOptions) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments(\n"
" ['a', 'b', 'c', 'd']);\n"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, TooManyArgumentsWithOptions) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments(\n"
" ['a', 'b', 'c', 'd', 'e'], true);\n"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionNoOptions) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments(\n"
" ['a', 'b', 'c']);\n"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, FinalArgumentIsNotAFunctionWithOptions) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments(\n"
" ['a', 'b', 'c', 'd'], true);\n"
"AssertTrue(args === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, OneStringArgument) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
// Because the request argument is required, a single argument must get
// mapped to it rather than to the optional targetId argument.
RegisterTestModule(
......@@ -102,12 +105,12 @@ TEST_F(MessagingUtilsUnittest, OneStringArgument) {
"AssertTrue(args[0] === null);\n"
"AssertTrue(args[1] == 'a');\n"
"AssertTrue(args[2] === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, OneStringAndOneNullArgument) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
// Explicitly specifying null as the request is allowed.
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments(['a', null]);\n"
......@@ -115,24 +118,24 @@ TEST_F(MessagingUtilsUnittest, OneStringAndOneNullArgument) {
"AssertTrue(args[0] == 'a');\n"
"AssertTrue(args[1] === null);\n"
"AssertTrue(args[2] === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, OneNullAndOneStringArgument) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"var args = messagingUtils.alignSendMessageArguments([null, 'a']);\n"
"AssertTrue(args.length == 3);\n"
"AssertTrue(args[0] === null);\n"
"AssertTrue(args[1] == 'a');\n"
"AssertTrue(args[2] === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, OneStringAndOneFunctionArgument) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
// When the arguments are a string and a function, the function is
// unambiguously the responseCallback. Because the request argument is
// required, the remaining argument must get mapped to it rather than to the
......@@ -144,12 +147,12 @@ TEST_F(MessagingUtilsUnittest, OneStringAndOneFunctionArgument) {
"AssertTrue(args[0] === null);\n"
"AssertTrue(args[1] == 'a');\n"
"AssertTrue(args[2] == cb);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, OneStringAndOneObjectArgument) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
// This tests an ambiguous set of arguments when options are present:
// chrome.runtime.sendMessage('target', {'msg': 'this is a message'});
// vs.
......@@ -168,12 +171,12 @@ TEST_F(MessagingUtilsUnittest, OneStringAndOneObjectArgument) {
"AssertTrue(args[1] == obj);\n"
"AssertTrue(args[2] === null);\n"
"AssertTrue(args[3] === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
TEST_F(MessagingUtilsUnittest, TwoObjectArguments) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
// When two non-string arguments are provided and options are present, the
// two arguments must match request and options, respectively, because
// targetId must be a string.
......@@ -187,7 +190,7 @@ TEST_F(MessagingUtilsUnittest, TwoObjectArguments) {
"AssertTrue(args[1] == obj1);\n"
"AssertTrue(args[2] == obj2);\n"
"AssertTrue(args[3] === null);");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
} // namespace
......
......@@ -12,55 +12,52 @@ class SafeBuiltinsUnittest : public ModuleSystemTest {
TEST_F(SafeBuiltinsUnittest, TestNotOriginalObject) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
RegisterModule("test",
"var assert = requireNative('assert');\n"
"Array.foo = 10;\n"
"assert.AssertTrue(!$Array.hasOwnProperty('foo'));\n"
);
context_->module_system()->Require("test");
env()->module_system());
env()->RegisterModule("test",
"var assert = requireNative('assert');\n"
"Array.foo = 10;\n"
"assert.AssertTrue(!$Array.hasOwnProperty('foo'));\n");
env()->module_system()->Require("test");
}
TEST_F(SafeBuiltinsUnittest, TestSelf) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
RegisterModule("test",
"var assert = requireNative('assert');\n"
"Array.foo = 10;\n"
"assert.AssertTrue($Array.self.foo == 10);\n"
"var arr = $Array.self(1);\n"
"assert.AssertTrue(arr.length == 1);\n"
"assert.AssertTrue(arr[0] === undefined);\n"
);
context_->module_system()->Require("test");
env()->module_system());
env()->RegisterModule("test",
"var assert = requireNative('assert');\n"
"Array.foo = 10;\n"
"assert.AssertTrue($Array.self.foo == 10);\n"
"var arr = $Array.self(1);\n"
"assert.AssertTrue(arr.length == 1);\n"
"assert.AssertTrue(arr[0] === undefined);\n");
env()->module_system()->Require("test");
}
TEST_F(SafeBuiltinsUnittest, TestStaticFunction) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
RegisterModule("test",
"var assert = requireNative('assert');\n"
"Object.keys = function() {throw new Error()};\n"
"var obj = {a: 10};\n"
"var keys = $Object.keys(obj);\n"
"assert.AssertTrue(keys.length == 1);\n"
"assert.AssertTrue(keys[0] == 'a');\n"
);
context_->module_system()->Require("test");
env()->module_system());
env()->RegisterModule("test",
"var assert = requireNative('assert');\n"
"Object.keys = function() {throw new Error()};\n"
"var obj = {a: 10};\n"
"var keys = $Object.keys(obj);\n"
"assert.AssertTrue(keys.length == 1);\n"
"assert.AssertTrue(keys[0] == 'a');\n");
env()->module_system()->Require("test");
}
TEST_F(SafeBuiltinsUnittest, TestInstanceMethod) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
RegisterModule("test",
env()->module_system());
env()->RegisterModule(
"test",
"var assert = requireNative('assert');\n"
"Array.prototype.push = function() {throw new Error();}\n"
"var arr = []\n"
"$Array.push(arr, 1);\n"
"assert.AssertTrue(arr.length == 1);\n"
"assert.AssertTrue(arr[0] == 1);\n"
);
context_->module_system()->Require("test");
"assert.AssertTrue(arr[0] == 1);\n");
env()->module_system()->Require("test");
}
// NOTE: JSON is already tested in ExtensionApiTest.Messaging, via
......
......@@ -12,26 +12,26 @@ namespace {
class UtilsUnittest : public ModuleSystemTest {
protected:
void RegisterTestModule(const char* code) {
RegisterModule("test",
base::StringPrintf(
"var assert = requireNative('assert');\n"
"var AssertTrue = assert.AssertTrue;\n"
"var AssertFalse = assert.AssertFalse;\n"
"var utils = require('utils');\n"
"%s",
code));
env()->RegisterModule("test",
base::StringPrintf(
"var assert = requireNative('assert');\n"
"var AssertTrue = assert.AssertTrue;\n"
"var AssertFalse = assert.AssertFalse;\n"
"var utils = require('utils');\n"
"%s",
code));
}
private:
virtual void SetUp() OVERRIDE {
ModuleSystemTest::SetUp();
RegisterModule("utils", IDR_UTILS_JS);
OverrideNativeHandler("schema_registry",
"exports.GetSchema = function() {};");
OverrideNativeHandler("logging",
"exports.CHECK = function() {};\n"
"exports.WARNING = function() {};");
env()->RegisterModule("utils", IDR_UTILS_JS);
env()->OverrideNativeHandler("schema_registry",
"exports.GetSchema = function() {};");
env()->OverrideNativeHandler("logging",
"exports.CHECK = function() {};\n"
"exports.WARNING = function() {};");
}
};
......@@ -41,7 +41,7 @@ TEST_F(UtilsUnittest, TestNothing) {
TEST_F(UtilsUnittest, SuperClass) {
ModuleSystem::NativesEnabledScope natives_enabled_scope(
context_->module_system());
env()->module_system());
RegisterTestModule(
"function SuperClassImpl() {}\n"
"\n"
......@@ -116,7 +116,7 @@ TEST_F(UtilsUnittest, SuperClass) {
"AssertTrue(subsub instanceof SubClass);\n"
"AssertTrue(subsub instanceof SubSubClass);\n");
context_->module_system()->Require("test");
env()->module_system()->Require("test");
}
} // namespace
......
......@@ -14,6 +14,7 @@ include_rules = [
# within content/ and a test within chrome/.
"+content/public",
"+gin/public",
"+grit", # For generated headers
"+media/base",
"+mojo/embedder",
......
......@@ -62,7 +62,8 @@ base::LazyInstance<V8ExtensionConfigurator>::Leaky g_v8_extension_configurator =
} // namespace
// Native JS functions for doing asserts.
class ModuleSystemTest::AssertNatives : public ObjectBackedNativeHandler {
class ModuleSystemTestEnvironment::AssertNatives
: public ObjectBackedNativeHandler {
public:
explicit AssertNatives(extensions::ChromeV8Context* context)
: ObjectBackedNativeHandler(context),
......@@ -95,7 +96,8 @@ class ModuleSystemTest::AssertNatives : public ObjectBackedNativeHandler {
};
// Source map that operates on std::strings.
class ModuleSystemTest::StringSourceMap : public ModuleSystem::SourceMap {
class ModuleSystemTestEnvironment::StringSourceMap
: public extensions::ModuleSystem::SourceMap {
public:
StringSourceMap() {}
virtual ~StringSourceMap() {}
......@@ -120,19 +122,20 @@ class ModuleSystemTest::StringSourceMap : public ModuleSystem::SourceMap {
std::map<std::string, std::string> source_map_;
};
ModuleSystemTest::ModuleSystemTest()
: isolate_(v8::Isolate::GetCurrent()),
handle_scope_(isolate_),
context_(
new extensions::ChromeV8Context(
v8::Context::New(
isolate_,
g_v8_extension_configurator.Get().GetConfiguration()),
NULL, // WebFrame
NULL, // Extension
extensions::Feature::UNSPECIFIED_CONTEXT)),
source_map_(new StringSourceMap()),
should_assertions_be_made_(true) {
ModuleSystemTestEnvironment::ModuleSystemTestEnvironment(
gin::IsolateHolder* isolate_holder)
: isolate_holder_(isolate_holder),
context_holder_(new gin::ContextHolder(isolate_holder_->isolate())),
handle_scope_(isolate_holder_->isolate()),
source_map_(new StringSourceMap()) {
context_holder_->SetContext(
v8::Context::New(isolate_holder->isolate(),
g_v8_extension_configurator.Get().GetConfiguration()));
context_.reset(new extensions::ChromeV8Context(
context_holder_->context(),
NULL, // WebFrame
NULL, // Extension
extensions::Feature::UNSPECIFIED_CONTEXT));
context_->v8_context()->Enter();
assert_natives_ = new AssertNatives(context_.get());
......@@ -152,30 +155,33 @@ ModuleSystemTest::ModuleSystemTest()
scoped_ptr<ModuleSystem::ExceptionHandler>(new FailsOnException));
}
ModuleSystemTest::~ModuleSystemTest() {
context_->v8_context()->Exit();
ModuleSystemTestEnvironment::~ModuleSystemTestEnvironment() {
if (context_)
context_->v8_context()->Exit();
}
void ModuleSystemTest::RegisterModule(const std::string& name,
const std::string& code) {
void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
const std::string& code) {
source_map_->RegisterModule(name, code);
}
void ModuleSystemTest::RegisterModule(const std::string& name,
int resource_id) {
void ModuleSystemTestEnvironment::RegisterModule(const std::string& name,
int resource_id) {
const std::string& code = ResourceBundle::GetSharedInstance().
GetRawDataResource(resource_id).as_string();
source_map_->RegisterModule(name, code);
}
void ModuleSystemTest::OverrideNativeHandler(const std::string& name,
const std::string& code) {
void ModuleSystemTestEnvironment::OverrideNativeHandler(
const std::string& name,
const std::string& code) {
RegisterModule(name, code);
context_->module_system()->OverrideNativeHandlerForTest(name);
}
void ModuleSystemTest::RegisterTestFile(const std::string& module_name,
const std::string& file_name) {
void ModuleSystemTestEnvironment::RegisterTestFile(
const std::string& module_name,
const std::string& file_name) {
base::FilePath test_js_file_path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_js_file_path));
test_js_file_path = test_js_file_path.AppendASCII("extensions")
......@@ -185,22 +191,49 @@ void ModuleSystemTest::RegisterTestFile(const std::string& module_name,
source_map_->RegisterModule(module_name, test_js);
}
void ModuleSystemTest::TearDown() {
// All tests must assert at least once unless otherwise specified.
EXPECT_EQ(should_assertions_be_made_,
assert_natives_->assertion_made());
EXPECT_FALSE(assert_natives_->failed());
void ModuleSystemTestEnvironment::ShutdownGin() {
context_holder_.reset();
}
void ModuleSystemTest::ExpectNoAssertionsMade() {
should_assertions_be_made_ = false;
void ModuleSystemTestEnvironment::ShutdownModuleSystem() {
context_->v8_context()->Exit();
context_.reset();
}
v8::Handle<v8::Object> ModuleSystemTest::CreateGlobal(const std::string& name) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Handle<v8::Object> ModuleSystemTestEnvironment::CreateGlobal(
const std::string& name) {
v8::Isolate* isolate = isolate_holder_->isolate();
v8::EscapableHandleScope handle_scope(isolate);
v8::Local<v8::Object> object = v8::Object::New(isolate);
isolate->GetCurrentContext()->Global()->Set(
v8::String::NewFromUtf8(isolate, name.c_str()), object);
return handle_scope.Escape(object);
}
ModuleSystemTest::ModuleSystemTest()
: isolate_holder_(v8::Isolate::GetCurrent(), NULL),
env_(CreateEnvironment()),
should_assertions_be_made_(true) {
}
ModuleSystemTest::~ModuleSystemTest() {
}
void ModuleSystemTest::TearDown() {
// All tests must assert at least once unless otherwise specified.
EXPECT_EQ(should_assertions_be_made_,
env_->assert_natives()->assertion_made());
EXPECT_FALSE(env_->assert_natives()->failed());
}
scoped_ptr<ModuleSystemTestEnvironment> ModuleSystemTest::CreateEnvironment() {
return make_scoped_ptr(new ModuleSystemTestEnvironment(&isolate_holder_));
}
void ModuleSystemTest::ExpectNoAssertionsMade() {
should_assertions_be_made_ = false;
}
void ModuleSystemTest::RunResolvedPromises() {
isolate_holder_.isolate()->RunMicrotasks();
}
......@@ -8,9 +8,63 @@
#include "chrome/renderer/extensions/chrome_v8_context.h"
#include "extensions/renderer/module_system.h"
#include "extensions/renderer/scoped_persistent.h"
#include "gin/public/context_holder.h"
#include "gin/public/isolate_holder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "v8/include/v8.h"
class ModuleSystemTestEnvironment {
public:
class AssertNatives;
class StringSourceMap;
explicit ModuleSystemTestEnvironment(gin::IsolateHolder* isolate_holder);
~ModuleSystemTestEnvironment();
// Register a named JS module in the module system.
void RegisterModule(const std::string& name, const std::string& code);
// Register a named JS module with source retrieved from a ResourceBundle.
void RegisterModule(const std::string& name, int resource_id);
// Register a named JS module in the module system and tell the module system
// to use it to handle any requireNative() calls for native modules with that
// name.
void OverrideNativeHandler(const std::string& name, const std::string& code);
// Registers |file_name| from chrome/test/data/extensions as a module name
// |module_name|.
void RegisterTestFile(const std::string& module_name,
const std::string& file_name);
// Create an empty object in the global scope with name |name|.
v8::Handle<v8::Object> CreateGlobal(const std::string& name);
void ShutdownGin();
void ShutdownModuleSystem();
extensions::ModuleSystem* module_system() {
return context_->module_system();
}
extensions::ChromeV8Context* context() { return context_.get(); }
v8::Isolate* isolate() { return isolate_holder_->isolate(); }
AssertNatives* assert_natives() { return assert_natives_; }
private:
gin::IsolateHolder* isolate_holder_;
scoped_ptr<gin::ContextHolder> context_holder_;
v8::HandleScope handle_scope_;
scoped_ptr<extensions::ChromeV8Context> context_;
AssertNatives* assert_natives_;
scoped_ptr<StringSourceMap> source_map_;
DISALLOW_COPY_AND_ASSIGN(ModuleSystemTestEnvironment);
};
// Test fixture for testing JS that makes use of the module system.
//
// Typically tests will look like:
......@@ -33,36 +87,21 @@ class ModuleSystemTest : public testing::Test {
virtual void TearDown() OVERRIDE;
protected:
// Register a named JS module in the module system.
void RegisterModule(const std::string& name, const std::string& code);
// Register a named JS module with source retrieved from a ResourceBundle.
void RegisterModule(const std::string& name, int resource_id);
// Register a named JS module in the module system and tell the module system
// to use it to handle any requireNative() calls for native modules with that
// name.
void OverrideNativeHandler(const std::string& name, const std::string& code);
ModuleSystemTestEnvironment* env() { return env_.get(); }
// Registers |file_name| from chrome/test/data/extensions as a module name
// |module_name|.
void RegisterTestFile(const std::string& module_name,
const std::string& file_name);
scoped_ptr<ModuleSystemTestEnvironment> CreateEnvironment();
// Make the test fail if any asserts are called. By default a test will fail
// if no asserts are called.
void ExpectNoAssertionsMade();
// Create an empty object in the global scope with name |name|.
v8::Handle<v8::Object> CreateGlobal(const std::string& name);
// Runs promises that have been resolved. Resolved promises will not run
// until this is called.
void RunResolvedPromises();
v8::Isolate* isolate_;
v8::HandleScope handle_scope_;
scoped_ptr<extensions::ChromeV8Context> context_;
class AssertNatives;
AssertNatives* assert_natives_;
class StringSourceMap;
scoped_ptr<StringSourceMap> source_map_;
private:
gin::IsolateHolder isolate_holder_;
scoped_ptr<ModuleSystemTestEnvironment> env_;
bool should_assertions_be_made_;
private:
......
......@@ -476,6 +476,7 @@
'dependencies': [
'extensions_resources.gyp:extensions_resources',
'../chrome/chrome_resources.gyp:chrome_resources',
'../gin/gin.gyp:gin',
'../third_party/WebKit/public/blink.gyp:blink',
],
'include_dirs': [
......
......@@ -3,6 +3,8 @@ include_rules = [
"+content/public/common",
"+content/public/renderer",
"+gin",
"+third_party/skia/include/core",
"+third_party/WebKit/public/platform",
......
......@@ -16,6 +16,7 @@
#include "extensions/renderer/console.h"
#include "extensions/renderer/safe_builtins.h"
#include "extensions/renderer/script_context.h"
#include "gin/modules/module_registry.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
......@@ -121,13 +122,17 @@ ModuleSystem::ModuleSystem(ScriptContext* context, SourceMap* source_map)
context_(context),
source_map_(source_map),
natives_enabled_(0),
exception_handler_(new DefaultExceptionHandler(context)) {
exception_handler_(new DefaultExceptionHandler(context)),
weak_factory_(this) {
RouteFunction(
"require",
base::Bind(&ModuleSystem::RequireForJs, base::Unretained(this)));
RouteFunction(
"requireNative",
base::Bind(&ModuleSystem::RequireNative, base::Unretained(this)));
RouteFunction(
"requireAsync",
base::Bind(&ModuleSystem::RequireAsync, base::Unretained(this)));
RouteFunction("privates",
base::Bind(&ModuleSystem::Private, base::Unretained(this)));
......@@ -137,6 +142,8 @@ ModuleSystem::ModuleSystem(ScriptContext* context, SourceMap* source_map)
v8::Object::New(isolate));
global->SetHiddenValue(v8::String::NewFromUtf8(isolate, kModuleSystem),
v8::External::New(isolate, this));
gin::ModuleRegistry::From(context->v8_context())->AddObserver(this);
}
ModuleSystem::~ModuleSystem() { Invalidate(); }
......@@ -215,55 +222,7 @@ v8::Local<v8::Value> ModuleSystem::RequireForJsInner(
if (!exports->IsUndefined())
return handle_scope.Escape(exports);
std::string module_name_str = *v8::String::Utf8Value(module_name);
v8::Handle<v8::Value> source(GetSource(module_name_str));
if (source.IsEmpty() || source->IsUndefined()) {
Fatal(context_, "No source for require(" + module_name_str + ")");
return v8::Undefined(GetIsolate());
}
v8::Handle<v8::String> wrapped_source(
WrapSource(v8::Handle<v8::String>::Cast(source)));
// Modules are wrapped in (function(){...}) so they always return functions.
v8::Handle<v8::Value> func_as_value = RunString(wrapped_source, module_name);
if (func_as_value.IsEmpty() || func_as_value->IsUndefined()) {
Fatal(context_, "Bad source for require(" + module_name_str + ")");
return v8::Undefined(GetIsolate());
}
v8::Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(func_as_value);
exports = v8::Object::New(GetIsolate());
v8::Handle<v8::Object> natives(NewInstance());
CHECK(!natives.IsEmpty()); // this can happen if v8 has issues
// These must match the argument order in WrapSource.
v8::Handle<v8::Value> args[] = {
// CommonJS.
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "require", v8::String::kInternalizedString)),
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "requireNative", v8::String::kInternalizedString)),
exports,
// Libraries that we magically expose to every module.
console::AsV8Object(),
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "privates", v8::String::kInternalizedString)),
// Each safe builtin. Keep in order with the arguments in WrapSource.
context_->safe_builtins()->GetArray(),
context_->safe_builtins()->GetFunction(),
context_->safe_builtins()->GetJSON(),
context_->safe_builtins()->GetObjekt(),
context_->safe_builtins()->GetRegExp(),
context_->safe_builtins()->GetString(), };
{
v8::TryCatch try_catch;
try_catch.SetCaptureMessage(true);
context_->CallFunction(func, arraysize(args), args);
if (try_catch.HasCaught()) {
HandleException(try_catch);
return v8::Undefined(GetIsolate());
}
}
exports = LoadModule(*v8::String::Utf8Value(module_name));
modules->Set(module_name, exports);
return handle_scope.Escape(exports);
}
......@@ -558,12 +517,38 @@ v8::Handle<v8::Value> ModuleSystem::RequireNativeFromString(
return i->second->NewInstance();
}
void ModuleSystem::RequireAsync(
const v8::FunctionCallbackInfo<v8::Value>& args) {
CHECK_EQ(1, args.Length());
std::string module_name = *v8::String::Utf8Value(args[0]->ToString());
v8::Handle<v8::Promise::Resolver> resolver(
v8::Promise::Resolver::New(GetIsolate()));
args.GetReturnValue().Set(resolver->GetPromise());
scoped_ptr<v8::UniquePersistent<v8::Promise::Resolver> > persistent_resolver(
new v8::UniquePersistent<v8::Promise::Resolver>(GetIsolate(), resolver));
gin::ModuleRegistry* module_registry =
gin::ModuleRegistry::From(context_->v8_context());
if (!module_registry) {
Warn(GetIsolate(), "Extension view no longer exists");
resolver->Reject(v8::Exception::Error(v8::String::NewFromUtf8(
GetIsolate(), "Extension view no longer exists")));
return;
}
module_registry->LoadModule(GetIsolate(),
module_name,
base::Bind(&ModuleSystem::OnModuleLoaded,
weak_factory_.GetWeakPtr(),
base::Passed(&persistent_resolver)));
if (module_registry->available_modules().count(module_name) == 0)
LoadModule(module_name);
}
v8::Handle<v8::String> ModuleSystem::WrapSource(v8::Handle<v8::String> source) {
v8::EscapableHandleScope handle_scope(GetIsolate());
// Keep in order with the arguments in RequireForJsInner.
v8::Handle<v8::String> left = v8::String::NewFromUtf8(
GetIsolate(),
"(function(require, requireNative, exports, "
"(function(define, require, requireNative, requireAsync, exports, "
"console, privates,"
"$Array, $Function, $JSON, $Object, $RegExp, $String) {"
"'use strict';");
......@@ -586,4 +571,98 @@ void ModuleSystem::Private(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(privates);
}
v8::Handle<v8::Value> ModuleSystem::LoadModule(const std::string& module_name) {
v8::EscapableHandleScope handle_scope(GetIsolate());
v8::Context::Scope context_scope(context()->v8_context());
v8::Handle<v8::Value> source(GetSource(module_name));
if (source.IsEmpty() || source->IsUndefined()) {
Fatal(context_, "No source for require(" + module_name + ")");
return v8::Undefined(GetIsolate());
}
v8::Handle<v8::String> wrapped_source(
WrapSource(v8::Handle<v8::String>::Cast(source)));
// Modules are wrapped in (function(){...}) so they always return functions.
v8::Handle<v8::Value> func_as_value =
RunString(wrapped_source,
v8::String::NewFromUtf8(GetIsolate(), module_name.c_str()));
if (func_as_value.IsEmpty() || func_as_value->IsUndefined()) {
Fatal(context_, "Bad source for require(" + module_name + ")");
return v8::Undefined(GetIsolate());
}
v8::Handle<v8::Function> func = v8::Handle<v8::Function>::Cast(func_as_value);
v8::Handle<v8::Object> define_object = v8::Object::New(GetIsolate());
gin::ModuleRegistry::InstallGlobals(GetIsolate(), define_object);
v8::Local<v8::Value> exports = v8::Object::New(GetIsolate());
v8::Handle<v8::Object> natives(NewInstance());
CHECK(!natives.IsEmpty()); // this can happen if v8 has issues
// These must match the argument order in WrapSource.
v8::Handle<v8::Value> args[] = {
// AMD.
define_object->Get(v8::String::NewFromUtf8(GetIsolate(), "define")),
// CommonJS.
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "require", v8::String::kInternalizedString)),
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "requireNative", v8::String::kInternalizedString)),
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "requireAsync", v8::String::kInternalizedString)),
exports,
// Libraries that we magically expose to every module.
console::AsV8Object(),
natives->Get(v8::String::NewFromUtf8(
GetIsolate(), "privates", v8::String::kInternalizedString)),
// Each safe builtin. Keep in order with the arguments in WrapSource.
context_->safe_builtins()->GetArray(),
context_->safe_builtins()->GetFunction(),
context_->safe_builtins()->GetJSON(),
context_->safe_builtins()->GetObjekt(),
context_->safe_builtins()->GetRegExp(),
context_->safe_builtins()->GetString(),
};
{
v8::TryCatch try_catch;
try_catch.SetCaptureMessage(true);
context_->CallFunction(func, arraysize(args), args);
if (try_catch.HasCaught()) {
HandleException(try_catch);
return v8::Undefined(GetIsolate());
}
}
return handle_scope.Escape(exports);
}
void ModuleSystem::OnDidAddPendingModule(
const std::string& id,
const std::vector<std::string>& dependencies) {
if (!source_map_->Contains(id))
return;
gin::ModuleRegistry* registry =
gin::ModuleRegistry::From(context_->v8_context());
DCHECK(registry);
for (std::vector<std::string>::const_iterator it = dependencies.begin();
it != dependencies.end();
++it) {
if (registry->available_modules().count(*it) == 0)
LoadModule(*it);
}
registry->AttemptToLoadMoreModules(GetIsolate());
}
void ModuleSystem::OnModuleLoaded(
scoped_ptr<v8::UniquePersistent<v8::Promise::Resolver> > resolver,
v8::Handle<v8::Value> value) {
if (!is_valid())
return;
v8::HandleScope handle_scope(GetIsolate());
v8::Handle<v8::Promise::Resolver> resolver_local(
v8::Local<v8::Promise::Resolver>::New(GetIsolate(), *resolver));
resolver_local->Resolve(value);
}
} // namespace extensions
......@@ -15,6 +15,7 @@
#include "base/memory/scoped_ptr.h"
#include "extensions/renderer/native_handler.h"
#include "extensions/renderer/object_backed_native_handler.h"
#include "gin/modules/module_registry_observer.h"
#include "v8/include/v8.h"
namespace extensions {
......@@ -37,7 +38,8 @@ class ScriptContext;
// Note that a ModuleSystem must be used only in conjunction with a single
// v8::Context.
// TODO(koz): Rename this to JavaScriptModuleSystem.
class ModuleSystem : public ObjectBackedNativeHandler {
class ModuleSystem : public ObjectBackedNativeHandler,
public gin::ModuleRegistryObserver {
public:
class SourceMap {
public:
......@@ -158,9 +160,6 @@ class ModuleSystem : public ObjectBackedNativeHandler {
// Called when an exception is thrown but not caught.
void HandleException(const v8::TryCatch& try_catch);
// Ensure that require_ has been evaluated from require.js.
void EnsureRequireLoaded();
void RequireForJs(const v8::FunctionCallbackInfo<v8::Value>& args);
v8::Local<v8::Value> RequireForJsInner(v8::Handle<v8::String> module_name);
......@@ -183,15 +182,29 @@ class ModuleSystem : public ObjectBackedNativeHandler {
v8::Handle<v8::Value> RequireNativeFromString(const std::string& native_name);
void RequireNative(const v8::FunctionCallbackInfo<v8::Value>& args);
// Wraps |source| in a (function(require, requireNative, exports) {...}).
// Return a promise for a requested module.
// |args[0]| - the name of a module.
void RequireAsync(const v8::FunctionCallbackInfo<v8::Value>& args);
// Wraps |source| in a (function(define, require, requireNative, ...) {...}).
v8::Handle<v8::String> WrapSource(v8::Handle<v8::String> source);
// NativeHandler implementation which returns the private area of an Object.
void Private(const v8::FunctionCallbackInfo<v8::Value>& args);
// NativeHandler implementation which returns a function wrapper for a
// provided function.
void CreateFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& args);
// Loads and runs a Javascript module.
v8::Handle<v8::Value> LoadModule(const std::string& module_name);
// Invoked when a module is loaded in response to a requireAsync call.
// Resolves |resolver| with |value|.
void OnModuleLoaded(
scoped_ptr<v8::UniquePersistent<v8::Promise::Resolver> > resolver,
v8::Handle<v8::Value> value);
// gin::ModuleRegistryObserver overrides.
virtual void OnDidAddPendingModule(
const std::string& id,
const std::vector<std::string>& dependencies) OVERRIDE;
ScriptContext* context_;
......@@ -212,6 +225,8 @@ class ModuleSystem : public ObjectBackedNativeHandler {
std::set<std::string> overridden_native_handlers_;
base::WeakPtrFactory<ModuleSystem> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ModuleSystem);
};
......
......@@ -16,6 +16,7 @@
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/features/base_feature_provider.h"
#include "gin/per_context_data.h"
#include "third_party/WebKit/public/web/WebDataSource.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
......@@ -42,6 +43,7 @@ ScriptContext::ScriptContext(const v8::Handle<v8::Context>& v8_context,
<< " extension id: " << GetExtensionID() << "\n"
<< " frame: " << web_frame_ << "\n"
<< " context type: " << GetContextTypeDescription();
gin::PerContextData::From(v8_context)->set_runner(this);
}
ScriptContext::~ScriptContext() {
......@@ -221,4 +223,21 @@ void ScriptContext::OnResponseReceived(const std::string& name,
<< *v8::String::Utf8Value(retval);
}
void ScriptContext::Run(const std::string& source,
const std::string& resource_name) {
module_system_->RunString(source, resource_name);
}
v8::Handle<v8::Value> ScriptContext::Call(v8::Handle<v8::Function> function,
v8::Handle<v8::Value> receiver,
int argc,
v8::Handle<v8::Value> argv[]) {
return CallFunction(function, argc, argv);
}
gin::ContextHolder* ScriptContext::GetContextHolder() {
v8::HandleScope handle_scope(isolate());
return gin::PerContextData::From(v8_context())->context_holder();
}
} // namespace extensions
......@@ -14,6 +14,7 @@
#include "extensions/renderer/request_sender.h"
#include "extensions/renderer/safe_builtins.h"
#include "extensions/renderer/scoped_persistent.h"
#include "gin/runner.h"
#include "v8/include/v8.h"
namespace blink {
......@@ -28,7 +29,7 @@ namespace extensions {
class Extension;
// Extensions wrapper for a v8 context.
class ScriptContext : public RequestSender::Source {
class ScriptContext : public RequestSender::Source, public gin::Runner {
public:
ScriptContext(const v8::Handle<v8::Context>& context,
blink::WebFrame* frame,
......@@ -120,6 +121,15 @@ class ScriptContext : public RequestSender::Source {
const base::ListValue& response,
const std::string& error) OVERRIDE;
// gin::Runner overrides.
virtual void Run(const std::string& source,
const std::string& resource_name) OVERRIDE;
virtual v8::Handle<v8::Value> Call(v8::Handle<v8::Function> function,
v8::Handle<v8::Value> receiver,
int argc,
v8::Handle<v8::Value> argv[]) OVERRIDE;
virtual gin::ContextHolder* GetContextHolder() OVERRIDE;
protected:
// The v8 context the bindings are accessible to.
ScopedPersistent<v8::Context> v8_context_;
......
......@@ -7,6 +7,8 @@
#include "extensions/common/features/feature.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_context_set.h"
#include "gin/public/context_holder.h"
#include "gin/public/isolate_holder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "v8/include/v8.h"
......@@ -19,15 +21,20 @@ TEST(ScriptContextSet, Lifecycle) {
ScriptContextSet context_set;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
gin::IsolateHolder isolate_holder(isolate, NULL);
v8::HandleScope handle_scope(isolate);
v8::Handle<v8::Context> v8_context(v8::Context::New(isolate));
gin::ContextHolder context_holder(isolate);
context_holder.SetContext(v8::Context::New(isolate));
// Dirty hack, but we don't actually need the frame, and this is easier than
// creating a whole webview.
blink::WebFrame* frame = reinterpret_cast<blink::WebFrame*>(1);
const Extension* extension = NULL;
ScriptContext* context = new ScriptContext(
v8_context, frame, extension, Feature::BLESSED_EXTENSION_CONTEXT);
ScriptContext* context =
new ScriptContext(context_holder.context(),
frame,
extension,
Feature::BLESSED_EXTENSION_CONTEXT);
context_set.Add(context);
EXPECT_EQ(1u, context_set.GetAll().count(context));
......
......@@ -105,13 +105,13 @@ v8::Handle<v8::Context> V8SchemaRegistry::GetOrCreateContext(
v8::Isolate* isolate) {
// It's ok to create local handles in this function, since this is only called
// when we have a HandleScope.
if (context_.IsEmpty()) {
v8::Handle<v8::Context> context = v8::Context::New(isolate);
context_.reset(context);
if (!context_holder_) {
context_holder_.reset(new gin::ContextHolder(isolate));
context_holder_->SetContext(v8::Context::New(isolate));
schema_cache_.reset(new SchemaCache(isolate));
return context;
return context_holder_->context();
}
return context_.NewHandle(isolate);
return context_holder_->context();
}
} // namespace extensions
......@@ -11,7 +11,7 @@
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "extensions/renderer/scoped_persistent.h"
#include "gin/public/context_holder.h"
#include "v8/include/v8-util.h"
#include "v8/include/v8.h"
......@@ -43,9 +43,9 @@ class V8SchemaRegistry {
typedef v8::StdPersistentValueMap<std::string, v8::Object> SchemaCache;
scoped_ptr<SchemaCache> schema_cache_;
// Single per-instance v8::Context to create v8::Values.
// Single per-instance gin::ContextHolder to create v8::Values.
// Created lazily via GetOrCreateContext.
ScopedPersistent<v8::Context> context_;
scoped_ptr<gin::ContextHolder> context_holder_;
DISALLOW_COPY_AND_ASSIGN(V8SchemaRegistry);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment