Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
add_task(async function testSandbox() {
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
Cu.evalInSandbox(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0);
Cu.evalInSandbox(`ns.incCounter();`, sb);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1);
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1");
});
add_task(async function testNoWindowSandbox() {
// Sandbox without window doesn't have ScriptLoader, and Sandbox's
// ModuleLoader cannot be created.
const systemPrincipal = Components.Constructor(
"@mozilla.org/systemprincipal;1",
"nsIPrincipal"
)();
const sandboxOpts = {
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(systemPrincipal, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /No ModuleLoader found/);
}
Assert.ok(caught);
});
add_task(async function testWindow() {
const win1 = createChromeWindow();
win1.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(win1.eval(`ns.getCounter();`), 0);
win1.eval(`ns.incCounter();`);
Assert.equal(win1.eval(`ns.getCounter();`), 1);
Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testReImport() {
// Re-importing the same module should return the same thing.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
Cu.evalInSandbox(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 0);
Cu.evalInSandbox(`ns.incCounter();`, sb);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 1);
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1");
Cu.evalInSandbox(`
var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb);
// The counter should be shared, and also not reset.
Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 1);
Cu.evalInSandbox(`ns2.incCounter();`, sb);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb), 2);
Assert.equal(Cu.evalInSandbox(`ns2.getCounter();`, sb), 2);
// The top-level script shouldn't be executed twice.
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb), "2,1");
});
add_task(async function testNotFound() {
// Importing non-existent file should throw error.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/not_found.mjs", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /Failed to load/);
}
Assert.ok(caught);
});
add_task(async function testParseError() {
// Parse error should be thrown.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/es6module_parse_error.js", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /unexpected token/);
}
Assert.ok(caught);
});
add_task(async function testParseErrorInImport() {
// Parse error in imported module should be thrown.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/es6module_parse_error_in_import.js", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /unexpected token/);
}
Assert.ok(caught);
});
add_task(async function testImportError() {
// Error for nested import should be thrown.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/es6module_import_error.js", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /doesn't provide an export named/);
}
Assert.ok(caught);
});
add_task(async function testExecutionError() {
// Error while execution the top-level script should be thrown.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/es6module_throws.js", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /foobar/);
}
Assert.ok(caught);
// Re-import should throw the same error.
caught = false;
try {
Cu.evalInSandbox(`
ChromeUtils.importESModule("resource://test/es6module_throws.js", {
global: "current",
});
`, sb);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /foobar/);
}
Assert.ok(caught);
});
add_task(async function testImportNestShared() {
// Importing system ESM should work.
const win1 = createChromeWindow();
const result = win1.eval(`
const { func1 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_shared_1.mjs", {
global: "current",
});
func1();
`);
Assert.equal(result, 27);
});
add_task(async function testImportNestNonSharedSame() {
// For the same global, nested import for non-shared global is allowed while
// executing top-level script.
const win1 = createChromeWindow();
const result = win1.eval(`
const { func } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_1.mjs", {
global: "current",
});
func();
`);
Assert.equal(result, 10);
});
add_task(async function testImportNestNonSharedDifferent() {
// For the different globals, nested import for non-shared global isn't
// allowed while executing top-level script.
const win1 = createChromeWindow();
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
win1.sb = new Cu.Sandbox(uri, sandboxOpts);
let caught = false;
try {
win1.eval(`
global: "current",
});
`);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /cannot be used for different global/);
}
Assert.ok(caught);
});
add_task(async function testImportNestNonSharedAfterImport() {
// Nested import for non-shared global is allowed after the import, both for
// the same and different globals.
const win1 = createChromeWindow();
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
win1.sb = new Cu.Sandbox(uri, sandboxOpts);
const result = win1.eval(`
const { func3 } = ChromeUtils.importESModule("resource://test/non_shared_nest_import_non_shared_3.mjs", {
global: "current",
});
// Nested import happens here.
func3();
`);
Assert.equal(result, 22);
});
add_task(async function testIsolationWithSandbox() {
// Modules should be isolated for each sandbox.
const uri = "http://example.com/";
const window = createContentWindow(uri);
const sandboxOpts = {
sandboxPrototype: window,
wantGlobalProperties: ["ChromeUtils"],
};
const sb1 = new Cu.Sandbox(uri, sandboxOpts);
const sb2 = new Cu.Sandbox(uri, sandboxOpts);
const sb3 = new Cu.Sandbox(uri, sandboxOpts);
// Verify modules in 2 sandboxes are isolated.
Cu.evalInSandbox(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb1);
Cu.evalInSandbox(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb2);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 0);
Cu.evalInSandbox(`ns.incCounter();`, sb1);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1);
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb1), "2,1");
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 0);
Cu.evalInSandbox(`ns.incCounter();`, sb2);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1);
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb2), "2,1");
// Verify importing after any modification to different global doesn't affect.
const ns3 = Cu.evalInSandbox(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`, sb3);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 0);
Cu.evalInSandbox(`ns.incCounter();`, sb3);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1);
Assert.equal(Cu.evalInSandbox(`globalThis["loaded"].join(",")`, sb3), "2,1");
// Verify yet another modification are still isolated.
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 1);
Cu.evalInSandbox(`ns.incCounter();`, sb1);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb1), 2);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 1);
Cu.evalInSandbox(`ns.incCounter();`, sb2);
Cu.evalInSandbox(`ns.incCounter();`, sb2);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb2), 3);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 1);
Cu.evalInSandbox(`ns.incCounter();`, sb3);
Cu.evalInSandbox(`ns.incCounter();`, sb3);
Cu.evalInSandbox(`ns.incCounter();`, sb3);
Assert.equal(Cu.evalInSandbox(`ns.getCounter();`, sb3), 4);
// Verify the module's `globalThis` points the target global.
Cu.evalInSandbox(`ns.putCounter();`, sb1);
Cu.evalInSandbox(`ns.putCounter();`, sb2);
Cu.evalInSandbox(`ns.putCounter();`, sb3);
const counter1 = Cu.evalInSandbox(`globalThis["counter"]`, sb1);
Assert.equal(counter1, 2);
const counter2 = Cu.evalInSandbox(`globalThis["counter"]`, sb2);
Assert.equal(counter2, 3);
const counter3 = Cu.evalInSandbox(`globalThis["counter"]`, sb3);
Assert.equal(counter3, 4);
});
add_task(async function testIsolationWithWindow() {
// Modules should be isolated for each window.
const win1 = createChromeWindow();
const win2 = createChromeWindow();
const win3 = createChromeWindow();
// Verify modules in 2 sandboxes are isolated.
win1.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
win2.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(win1.eval(`ns.getCounter();`), 0);
win1.eval(`ns.incCounter();`);
Assert.equal(win1.eval(`ns.getCounter();`), 1);
Assert.equal(win1.eval(`globalThis["loaded"].join(",")`), "2,1");
Assert.equal(win2.eval(`ns.getCounter();`), 0);
win2.eval(`ns.incCounter();`);
Assert.equal(win2.eval(`ns.getCounter();`), 1);
Assert.equal(win2.eval(`globalThis["loaded"].join(",")`), "2,1");
// Verify importing after any modification to different global doesn't affect.
const ns3 = win3.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(win3.eval(`ns.getCounter();`), 0);
win3.eval(`ns.incCounter();`);
Assert.equal(win3.eval(`ns.getCounter();`), 1);
Assert.equal(win3.eval(`globalThis["loaded"].join(",")`), "2,1");
// Verify yet another modification are still isolated.
Assert.equal(win1.eval(`ns.getCounter();`), 1);
win1.eval(`ns.incCounter();`);
Assert.equal(win1.eval(`ns.getCounter();`), 2);
Assert.equal(win2.eval(`ns.getCounter();`), 1);
win2.eval(`ns.incCounter();`);
win2.eval(`ns.incCounter();`);
Assert.equal(win2.eval(`ns.getCounter();`), 3);
Assert.equal(win3.eval(`ns.getCounter();`), 1);
win3.eval(`ns.incCounter();`);
win3.eval(`ns.incCounter();`);
win3.eval(`ns.incCounter();`);
Assert.equal(win3.eval(`ns.getCounter();`), 4);
// Verify the module's `globalThis` points the target global.
win1.eval(`ns.putCounter();`);
win2.eval(`ns.putCounter();`);
win3.eval(`ns.putCounter();`);
const counter1 = win1.eval(`globalThis["counter"]`);
Assert.equal(counter1, 2);
const counter2 = win2.eval(`globalThis["counter"]`);
Assert.equal(counter2, 3);
const counter3 = win3.eval(`globalThis["counter"]`);
Assert.equal(counter3, 4);
});
add_task(async function testSyncImportBeforeAsyncImportTopLevel() {
const window = createChromeWindow();
window.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns.getCounter();`), 0);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 1);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
window.eval(`
var ns2 = null;
const nsPromise = import("resource://test/non_shared_1.mjs");
nsPromise.then(v => { ns2 = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns2 !== null`)
);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportBeforeAsyncImportDependency() {
const window = createChromeWindow();
window.eval(`
globalThis["loaded"] = [];
var ns = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns.getCounter();`), 0);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 1);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
window.eval(`
var ns2 = null;
const nsPromise = import("resource://test/import_non_shared_1.mjs");
nsPromise.then(v => { ns2 = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns2 !== null`)
);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportAfterAsyncImportTopLevel() {
const window = createChromeWindow();
window.eval(`
var ns = null;
globalThis["loaded"] = [];
const nsPromise = import("resource://test/non_shared_1.mjs");
nsPromise.then(v => { ns = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns !== null`)
);
Assert.equal(window.eval(`ns.getCounter();`), 0);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 1);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
window.eval(`
var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportAfterAsyncImportDependency() {
const window = createChromeWindow();
window.eval(`
var ns = null;
globalThis["loaded"] = [];
const nsPromise = import("resource://test/non_shared_1.mjs");
nsPromise.then(v => { ns = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns !== null`)
);
Assert.equal(window.eval(`ns.getCounter();`), 0);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 1);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
window.eval(`
var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportWhileAsyncImportTopLevel() {
const window = createChromeWindow();
window.eval(`
var ns = null;
globalThis["loaded"] = [];
const nsPromise = import("resource://test/non_shared_1.mjs");
nsPromise.then(v => { ns = v; });
`);
window.eval(`
var ns2 = ChromeUtils.importESModule("resource://test/non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns2.getCounter();`), 0);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns !== null`)
);
Assert.equal(window.eval(`ns.getCounter();`), 1);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportWhileAsyncImportDependency() {
const window = createChromeWindow();
window.eval(`
var ns = null;
globalThis["loaded"] = [];
const nsPromise = import("resource://test/non_shared_1.mjs");
nsPromise.then(v => { ns = v; });
`);
window.eval(`
var ns2 = ChromeUtils.importESModule("resource://test/import_non_shared_1.mjs", {
global: "current",
});
`);
Assert.equal(window.eval(`ns2.getCounter();`), 0);
window.eval(`ns2.incCounter();`);
Assert.equal(window.eval(`ns2.getCounter();`), 1);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns !== null`)
);
Assert.equal(window.eval(`ns.getCounter();`), 1);
window.eval(`ns.incCounter();`);
Assert.equal(window.eval(`ns.getCounter();`), 2);
Assert.equal(window.eval(`ns2.getCounter();`), 2);
Assert.equal(window.eval(`globalThis["loaded"].join(",")`), "2,1");
});
add_task(async function testSyncImportBeforeAsyncImportTLA() {
// Top-level-await is not supported by sync import.
const window = createChromeWindow();
let caught = false;
try {
window.eval(`
ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", {
global: "current",
});
`);
} catch (e) {
caught = true;
Assert.stringMatches(e.message, /top level await is not supported/);
}
Assert.ok(caught);
window.eval(`
var ns2 = null;
const nsPromise = import("resource://test/es6module_top_level_await.js");
nsPromise.then(v => { ns2 = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns2 !== null`)
);
Assert.equal(window.eval(`ns2.foo();`), 10);
});
add_task(async function testSyncImportAfterAsyncImportTLA() {
// Top-level-await is not supported by sync import, but if the module is
// already imported, the existing module namespace is returned.
const window = createChromeWindow();
window.eval(`
var ns2 = null;
const nsPromise = import("resource://test/es6module_top_level_await.js");
nsPromise.then(v => { ns2 = v; });
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns2 !== null`)
);
Assert.equal(window.eval(`ns2.foo();`), 10);
window.eval(`
var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", {
global: "current",
});
`);
Assert.equal(window.eval(`ns.foo();`), 10);
Assert.equal(window.eval(`ns2.foo == ns.foo;`), true);
});
add_task(async function testSyncImportWhileAsyncImportTLA() {
// Top-level-await is not supported by sync import, but if the module is
// already fetching, ChromeUtils.importESModule waits for it and, the
// async-imported module namespace is returned.
const window = createChromeWindow();
window.eval(`
var ns2 = null;
const nsPromise = import("resource://test/es6module_top_level_await.js");
nsPromise.then(v => { ns2 = v; });
`);
window.eval(`
var ns = ChromeUtils.importESModule("resource://test/es6module_top_level_await.js", {
global: "current",
});
`);
Services.tm.spinEventLoopUntil(
"Wait until dynamic import finishes",
() => window.eval(`ns2 !== null`)
);
Assert.equal(window.eval(`ns2.foo();`), 10);
Assert.equal(window.eval(`ns.foo();`), 10);
Assert.equal(window.eval(`ns2.foo == ns.foo;`), true);
});