// Copyright 2014 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 "base/memory/scoped_ptr.h" #include "extensions/renderer/module_system.h" #include "extensions/renderer/module_system_test.h" #include "gin/modules/module_registry.h" namespace extensions { class CounterNatives : public ObjectBackedNativeHandler { public: explicit CounterNatives(ScriptContext* context) : ObjectBackedNativeHandler(context), counter_(0) { RouteFunction("Get", base::Bind(&CounterNatives::Get, base::Unretained(this))); RouteFunction( "Increment", base::Bind(&CounterNatives::Increment, base::Unretained(this))); } void Get(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(static_cast(counter_)); } void Increment(const v8::FunctionCallbackInfo& args) { counter_++; } private: int counter_; }; class TestExceptionHandler : public ModuleSystem::ExceptionHandler { public: TestExceptionHandler() : handled_exception_(false) {} void HandleUncaughtException(const v8::TryCatch& try_catch) override { handled_exception_ = true; } bool handled_exception() const { return handled_exception_; } private: bool handled_exception_; }; TEST_F(ModuleSystemTest, TestExceptionHandling) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); TestExceptionHandler* handler = new TestExceptionHandler; scoped_ptr scoped_handler(handler); ASSERT_FALSE(handler->handled_exception()); env()->module_system()->SetExceptionHandlerForTest(scoped_handler.Pass()); env()->RegisterModule("test", "throw 'hi';"); env()->module_system()->Require("test"); ASSERT_TRUE(handler->handled_exception()); ExpectNoAssertionsMade(); } TEST_F(ModuleSystemTest, TestRequire) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "exports.Add = function(x, y) { return x + y; };"); env()->RegisterModule("test", "var Add = require('add').Add;" "requireNative('assert').AssertTrue(Add(3, 5) == 8);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestNestedRequire) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "exports.Add = function(x, y) { return x + y; };"); env()->RegisterModule("double", "var Add = require('add').Add;" "exports.Double = function(x) { return Add(x, x); };"); env()->RegisterModule("test", "var Double = require('double').Double;" "requireNative('assert').AssertTrue(Double(3) == 6);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestModuleInsulation) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("x", "var x = 10;" "exports.X = function() { return x; };"); env()->RegisterModule("y", "var x = 15;" "require('x');" "exports.Y = function() { return x; };"); env()->RegisterModule("test", "var Y = require('y').Y;" "var X = require('x').X;" "var assert = requireNative('assert');" "assert.AssertTrue(!this.hasOwnProperty('x'));" "assert.AssertTrue(Y() == 15);" "assert.AssertTrue(X() == 10);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestNativesAreDisabledOutsideANativesEnabledScope) { env()->RegisterModule("test", "var assert;" "try {" " assert = requireNative('assert');" "} catch (e) {" " var caught = true;" "}" "if (assert) {" " assert.AssertTrue(true);" "}"); env()->module_system()->Require("test"); ExpectNoAssertionsMade(); } TEST_F(ModuleSystemTest, TestNativesAreEnabledWithinANativesEnabledScope) { env()->RegisterModule("test", "var assert = requireNative('assert');" "assert.AssertTrue(true);"); { ModuleSystem::NativesEnabledScope natives_enabled(env()->module_system()); { ModuleSystem::NativesEnabledScope natives_enabled_inner( env()->module_system()); } env()->module_system()->Require("test"); } } TEST_F(ModuleSystemTest, TestLazyField) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("lazy", "exports.x = 5;"); v8::Handle object = env()->CreateGlobal("object"); env()->module_system()->SetLazyField(object, "blah", "lazy", "x"); env()->RegisterModule("test", "var assert = requireNative('assert');" "assert.AssertTrue(object.blah == 5);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestLazyFieldYieldingObject) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule( "lazy", "var object = {};" "object.__defineGetter__('z', function() { return 1; });" "object.x = 5;" "object.y = function() { return 10; };" "exports.object = object;"); v8::Handle object = env()->CreateGlobal("object"); env()->module_system()->SetLazyField(object, "thing", "lazy", "object"); env()->RegisterModule("test", "var assert = requireNative('assert');" "assert.AssertTrue(object.thing.x == 5);" "assert.AssertTrue(object.thing.y() == 10);" "assert.AssertTrue(object.thing.z == 1);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestLazyFieldIsOnlyEvaledOnce) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->module_system()->RegisterNativeHandler( "counter", scoped_ptr(new CounterNatives(env()->context()))); env()->RegisterModule("lazy", "requireNative('counter').Increment();" "exports.x = 5;"); v8::Handle object = env()->CreateGlobal("object"); env()->module_system()->SetLazyField(object, "x", "lazy", "x"); env()->RegisterModule("test", "var assert = requireNative('assert');" "var counter = requireNative('counter');" "assert.AssertTrue(counter.Get() == 0);" "object.x;" "assert.AssertTrue(counter.Get() == 1);" "object.x;" "assert.AssertTrue(counter.Get() == 1);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestRequireNativesAfterLazyEvaluation) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("lazy", "exports.x = 5;"); v8::Handle object = env()->CreateGlobal("object"); env()->module_system()->SetLazyField(object, "x", "lazy", "x"); env()->RegisterModule("test", "object.x;" "requireNative('assert').AssertTrue(true);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestTransitiveRequire) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("dependency", "exports.x = 5;"); env()->RegisterModule("lazy", "exports.output = require('dependency');"); v8::Handle object = env()->CreateGlobal("object"); env()->module_system()->SetLazyField(object, "thing", "lazy", "output"); env()->RegisterModule("test", "var assert = requireNative('assert');" "assert.AssertTrue(object.thing.x == 5);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestModulesOnlyGetEvaledOnce) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->module_system()->RegisterNativeHandler( "counter", scoped_ptr(new CounterNatives(env()->context()))); env()->RegisterModule("incrementsWhenEvaled", "requireNative('counter').Increment();"); env()->RegisterModule("test", "var assert = requireNative('assert');" "var counter = requireNative('counter');" "assert.AssertTrue(counter.Get() == 0);" "require('incrementsWhenEvaled');" "assert.AssertTrue(counter.Get() == 1);" "require('incrementsWhenEvaled');" "assert.AssertTrue(counter.Get() == 1);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestOverrideNativeHandler) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->OverrideNativeHandler("assert", "exports.AssertTrue = function() {};"); env()->RegisterModule("test", "requireNative('assert').AssertTrue(true);"); ExpectNoAssertionsMade(); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestOverrideNonExistentNativeHandler) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->OverrideNativeHandler("thing", "exports.x = 5;"); env()->RegisterModule("test", "var assert = requireNative('assert');" "assert.AssertTrue(requireNative('thing').x == 5);"); env()->module_system()->Require("test"); } TEST_F(ModuleSystemTest, TestRequireAsync) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "define('add', [], function() {" " return { Add: function(x, y) { return x + y; } };" "});"); env()->RegisterModule("math", "define('math', ['add'], function(add) {" " return { Add: add.Add };" "});"); env()->RegisterModule( "test", "requireAsync('math').then(function(math) {" " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);" "});"); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncInParallel) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "define('add', [], function() {" " return { Add: function(x, y) { return x + y; } };" "});"); env()->RegisterModule( "subtract", "define('subtract', [], function() {" " return { Subtract: function(x, y) { return x - y; } };" "});"); env()->RegisterModule( "math", "exports.AddAndSubtract = function(x, y, z) {" " return Promise.all([requireAsync('add')," " requireAsync('subtract')" " ]).then(function(modules) {" " return modules[1].Subtract(modules[0].Add(x, y), z);" " });" "};"); env()->RegisterModule("test", "var AddAndSubtract = require('math').AddAndSubtract;" "AddAndSubtract(3, 5, 2).then(function(result) {" " requireNative('assert').AssertTrue(result == 6);" "});"); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestNestedRequireAsyncs) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("first", "define('first', [], function() {" " return { next: 'second' };" "});"); env()->RegisterModule("second", "define('second', [], function() {" " return { next: '' };" "});"); env()->RegisterModule( "test", "requireAsync('first').then(function(module) {" " return requireAsync(module.next)" "}).then(function(module) {" " requireNative('assert').AssertTrue(module.next === '');" "});"); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireFromAMDModule) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "exports.Add = function(x, y) { return x + y; };"); env()->RegisterModule("math", "define('math', [], function() {" " var add = require('add');" " return { Add: add.Add };" "});"); env()->RegisterModule( "test", "requireAsync('math').then(function(math) {" " requireNative('assert').AssertTrue(math.Add(3, 5) == 8);" "});"); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncFromAMDModule) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("add", "define('add', [], function() {" " return { Add: function(x, y) { return x + y; } };" "});"); env()->RegisterModule("math", "define('math', [], function() {" " function Add(x, y) {" " return requireAsync('add').then(function(add) {" " return add.Add(x, y);" " });" " }" " return { Add: Add };" "});"); env()->RegisterModule("test", "requireAsync('math').then(function(math) {" " return math.Add(3, 6);" "}).then(function(result) {" " requireNative('assert').AssertTrue(result == 9);" "});"); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncFromAnotherContext) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule( "test", "requireAsync('natives').then(function(natives) {" " natives.requireAsync('ping').then(function(ping) {" " return ping();" " }).then(function(result) {" " requireNative('assert').AssertTrue(result == 'pong');" " });" "});"); scoped_ptr other_env = CreateEnvironment(); other_env->RegisterModule("ping", "define('ping', ['natives'], function(natives) {" " return function() {" " return 'pong';" " }" "});"); gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule( env()->isolate(), "natives", other_env->module_system()->NewInstance()); gin::ModuleRegistry::From(other_env->context()->v8_context()) ->AddBuiltinModule( env()->isolate(), "natives", env()->module_system()->NewInstance()); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncBetweenContexts) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("pong", "define('pong', [], function() {" " return function() { return 'done'; };" "});"); env()->RegisterModule( "test", "requireAsync('natives').then(function(natives) {" " natives.requireAsync('ping').then(function(ping) {" " return ping();" " }).then(function(pong) {" " return pong();" " }).then(function(result) {" " requireNative('assert').AssertTrue(result == 'done');" " });" "});"); scoped_ptr other_env = CreateEnvironment(); other_env->RegisterModule("ping", "define('ping', ['natives'], function(natives) {" " return function() {" " return natives.requireAsync('pong');" " }" "});"); gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule( env()->isolate(), "natives", other_env->module_system()->NewInstance()); gin::ModuleRegistry::From(other_env->context()->v8_context()) ->AddBuiltinModule( env()->isolate(), "natives", env()->module_system()->NewInstance()); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleRegistry) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("test", "requireAsync('natives').then(function(natives) {" " var AssertTrue = requireNative('assert').AssertTrue;" " natives.requireAsync('foo').then(function() {" " AssertTrue(false);" " }).catch(function(error) {" " AssertTrue(error.message == " " 'Extension view no longer exists');" " });" "});"); scoped_ptr other_env = CreateEnvironment(); gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule( env()->isolate(), "natives", other_env->module_system()->NewInstance()); other_env->ShutdownGin(); env()->module_system()->Require("test"); RunResolvedPromises(); } TEST_F(ModuleSystemTest, TestRequireAsyncFromContextWithNoModuleSystem) { ModuleSystem::NativesEnabledScope natives_enabled_scope( env()->module_system()); env()->RegisterModule("test", "requireAsync('natives').then(function(natives) {" " requireNative('assert').AssertTrue(" " natives.requireAsync('foo') === undefined);" "});"); scoped_ptr other_env = CreateEnvironment(); gin::ModuleRegistry::From(env()->context()->v8_context())->AddBuiltinModule( env()->isolate(), "natives", other_env->module_system()->NewInstance()); other_env->ShutdownModuleSystem(); env()->module_system()->Require("test"); RunResolvedPromises(); } } // namespace extensions