Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partial tests for import defer #4278

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ Atomics.pause
# https://github.com/tc39/proposal-is-error
Error.isError

# Deferred import evaluation
# https://tc39.es/proposal-defer-import-eval
import-defer

## Standard language features
#
# Language features that have been included in a published version of the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as depDeferredNamespace from "./dep_FIXTURE.js";

export { depDeferredNamespace };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

export const foo = 1;
export const bar = 2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-modulenamespacecreate
description: >
Deferred namespace objects have the correct MOP implementation
info: |
ModuleNamespaceCreate ( _module_, _exports_, _phase_ )
1. Let _internalSlotsList_ be the internal slots listed in <emu-xref href="#table-internal-slots-of-module-namespace-exotic-objects"></emu-xref>.
1. Let _M_ be MakeBasicObject(_internalSlotsList_).
1. Set _M_'s essential internal methods to the definitions specified in <emu-xref href="#sec-module-namespace-exotic-objects"></emu-xref>.
1. ...

[[GetPrototypeOf]] ( )
1. Return null.

[[IsExtensible]] ( )
1. Return false.

flags: [module]
features: [import-defer]
includes: [propertyHelper.js, compareArray.js]
---*/

import defer * as ns from "./dep_FIXTURE.js";

assert.sameValue(typeof ns, "object", "Deferred namespaces are objects");

assert(!Reflect.isExtensible(ns), "Deferred namespaces are not extensible");
assert.sameValue(Reflect.preventExtensions(ns), true, "Deferred namespaces can made non-extensible");

assert.sameValue(Reflect.getPrototypeOf(ns), null, "Deferred namespaces have a null prototype");
assert.sameValue(Reflect.setPrototypeOf(ns, {}), false, "Deferred namespaces' prototype cannot be changed");
assert.sameValue(Reflect.setPrototypeOf(ns, null), true, "Deferred namespaces' prototype can be 'set' to null");

assert.throws(TypeError, () => Reflect.apply(ns, null, []), "Deferred namespaces are not callable");
assert.throws(TypeError, () => Reflect.construct(ns, [], ns), "Deferred namespaces are not constructable");

assert.compareArray(
Reflect.ownKeys(ns),
["bar", "foo", Symbol.toStringTag],
"Deferred namespaces' keys are the exports sorted alphabetically, followed by @@toStringTag"
);

verifyProperty(ns, "foo", {
value: 1,
writable: true,
enumerable: true,
configurable: false,
});
assert.sameValue(Reflect.getOwnPropertyDescriptor(ns, "non-existent"), undefined, "No descriptors for non-exports");
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-getmodulenamespace
description: >
Deferred namespace objects are created and cached appropriately
info: |
GetModuleNamespace ( _module_, _phase_ )
1. ...
1. If _phase_ is ~defer~, let _namespace_ be _module_.[[DeferredNamespace]], otherwise let _namespace_ be _module_.[[Namespace]].
1. If _namespace_ is ~empty~, then
1. ...
1. Set _namespace_ to ModuleNamespaceCreate(_module_, _unambiguousNames_, _phase_).
1. Return _namespace_.

ModuleNamespaceCreate ( _module_, _exports_, _phase_ )
1. ...
1. Let _M_ be MakeBasicObject(_internalSlotsList_).
1. ...
1. If _phase_ is ~defer~, then
1. Set _module_.[[DeferredNamespace]] to _M_.
1. ...
1. Else,
1. Set _module_.[[Namespace]] to _M_.
1. ...
1. Return _M_.

flags: [module]
features: [import-defer]
---*/

import * as nsEager from "./dep_FIXTURE.js";

import defer * as nsDeferred1 from "./dep_FIXTURE.js";
import defer * as nsDeferred2 from "./dep_FIXTURE.js";
import { depDeferredNamespace as nsDeferred3 } from "./dep-defer-ns_FIXTURE.js";
const nsDeferred4 = await import.defer("./dep_FIXTURE.js");

assert.sameValue(nsDeferred1, nsDeferred2, "Deferred import of the same module twice gives the same object");
assert.sameValue(nsDeferred1, nsDeferred3, "Deferred import of the same module twice from different files gives the same object");
assert.sameValue(nsDeferred1, nsDeferred4, "Static and dynamic deferred import of the same module gives the same object");
assert.notSameValue(nsDeferred1, nsEager, "Deferred namespaces are distinct from eager namespaces");
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import { resolveFirst, resolveThird, second } from "./promises_FIXTURE.js";
import "./dep-1.1_FIXTURE.js"

await Promise.resolve();

resolveFirst();

await second;
nicolo-ribaudo marked this conversation as resolved.
Show resolved Hide resolved
nicolo-ribaudo marked this conversation as resolved.
Show resolved Hide resolved

resolveThird();
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as ns from "./dep-1-tla_FIXTURE.js";

export let foo = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import { first, third, rejectDone, resolveDone, resolveSecond } from "./promises_FIXTURE.js";
import defer * as ns from "./dep-1.1.1_FIXTURE.js";

// dep-1 is now in the ~evaluating~ state
try {
ns.foo;
} catch (error) {
globalThis["error on ns.foo while evaluating"] = error;
}

first.then(() => {
// dep-1 is now in the ~evaluating-async~ state
try {
ns.foo;
} catch (error) {
globalThis["error on ns.foo while evaluating-async"] = error;
}
resolveSecond();
}).then(() => {
return third.then(() => {
// dep-1 is now in the ~evaluated~ state
let foo = ns.foo;
globalThis["value of ns.foo when evaluated"] = foo;
})
}).then(resolveDone, rejectDone);
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger evaluation of modules that have dependencies in their ~evaluating-async~ phase
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...

EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...

ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.

flags: [module, async]
features: [import-defer, top-level-await]
includes: [asyncHelpers.js]
---*/

import { done } from "./promises_FIXTURE.js";
import "./dep-1-tla_FIXTURE.js";

asyncTest(async () => {
await done;
assert(globalThis["error on ns.foo while evaluating"] instanceof TypeError, "ns.foo while evaluating throws a TypeError");
assert(globalThis["error on ns.foo while evaluating-async"] instanceof TypeError, "ns.foo while evaluating-async throws a TypeError");
assert.sameValue(globalThis["value of ns.foo when evaluated"], 1);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

export let resolveDone, rejectDone;
export const done = new Promise((r, j) => (resolveDone = r, rejectDone = j));

export let resolveFirst, rejectFirst;
export const first = new Promise((r, j) => (resolveFirst = r, rejectFirst = j));

export let resolveSecond, rejectSecond;
export const second = new Promise((r, j) => (resolveSecond = r, rejectSecond = j));

export let resolveThird, rejectThird;
export const third = new Promise((r, j) => (resolveThird = r, rejectThird = j));
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as dep2 from "./dep-2_FIXTURE.js";

globalThis.dep3evaluated = false;

try {
dep2.foo;
} catch (error) {
globalThis["evaluating dep2.foo error"] = error;
}

globalThis["evaluating dep2.foo evaluates dep3"] = globalThis.dep3evaluated;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import "./dep-3_FIXTURE.js";
import "./main.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

globalThis.dep3evaluated = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger their own evaluation
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...

EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...

ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.

flags: [module]
features: [import-defer]
---*/

import "./dep-1_FIXTURE.js";

assert(globalThis["evaluating dep2.foo error"] instanceof TypeError, "evaluating dep2.foo throws a TypeError");
assert(!globalThis["evaluating dep2.foo evaluates dep3"], "evaluating dep2.foo does not evaluate dep3");
assert(!globalThis.dep3evaluated, "dep3 is not evaluated at all");
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import { resolveFirst, resolveThird, second } from "./promises_FIXTURE.js";
import "./dep-1.1_FIXTURE.js"

await Promise.resolve();

resolveFirst();

await second;
nicolo-ribaudo marked this conversation as resolved.
Show resolved Hide resolved
nicolo-ribaudo marked this conversation as resolved.
Show resolved Hide resolved

resolveThird();

export let foo = 1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import { first, third, resolveSecond, rejectDone, resolveDone } from "./promises_FIXTURE.js";
import defer * as ns from "./dep-1-tla_FIXTURE.js";

// ns is now in the ~evaluating~ state
try {
ns.foo;
} catch (error) {
globalThis["error on ns.foo while evaluating"] = error;
}

first.then(() => {
// ns is now in the ~evaluating-async~ state
try {
ns.foo;
} catch (error) {
globalThis["error on ns.foo while evaluating-async"] = error;
}
resolveSecond();
}).then(() => {
return third.then(() => {
// ns is now in the ~evaluated~ state
let foo = ns.foo;
globalThis["value of ns.foo when evaluated"] = foo;
})
}).then(resolveDone, rejectDone);
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger evaluation of modules that are in their ~evaluating-async~ phase
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...

EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...

ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.

flags: [module, async]
features: [import-defer, top-level-await]
includes: [asyncHelpers.js]
---*/

import { done } from "./promises_FIXTURE.js";
import "./dep-1-tla_FIXTURE.js";

asyncTest(async () => {
await done;
assert(globalThis["error on ns.foo while evaluating"] instanceof TypeError, "ns.foo while evaluating throws a TypeError");
assert(globalThis["error on ns.foo while evaluating-async"] instanceof TypeError, "ns.foo while evaluating-async throws a TypeError");
assert.sameValue(globalThis["value of ns.foo when evaluated"], 1);
});
Loading
Loading