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

feat(ses,module-source): Add ModuleSource shim #2463

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/module-source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"main": "./index.js",
"exports": {
".": "./index.js",
"./shim.js": "./shim.js",
"./package.json": "./package.json"
},
"scripts": {
Expand Down
10 changes: 10 additions & 0 deletions packages/module-source/shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* global globalThis */

import { ModuleSource } from './index.js';

Object.defineProperty(globalThis, 'ModuleSource', {
value: ModuleSource,
enumerable: false,
writable: true,
configurable: true,
});
15 changes: 15 additions & 0 deletions packages/module-source/src/module-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ const analyzeModule = makeModuleAnalyzer();
* @property {SourceMapHook} [sourceMapHook]
*/

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
function AbstractModuleSource() {
// no-op, safe to super()
}

// WebAssembly and ModuleSource are both in motion.
// The Source Phase Imports proposal implies an additional AbstractModuleSource
// layer above the existing WebAssembly.Module that would be shared by
// the JavaScript ModuleSource prototype chain.
// At time of writing, no version of WebAssembly provides the shared base class,
// and the shimmed ModuleSource gains nothing from sharing that prototype when
// it comes into being.

// XXX implements import('ses').PrecompiledModuleSource but adding
// `@implements` errors that this isn't a class and `@returns` errors that
// there's no value returned.
Expand Down Expand Up @@ -100,3 +113,5 @@ export function ModuleSource(source, opts = {}) {
this.__needsImportMeta__ = needsImportMeta;
freeze(this);
}

Object.setPrototypeOf(ModuleSource.prototype, AbstractModuleSource.prototype);
14 changes: 14 additions & 0 deletions packages/ses/src/get-anonymous-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,19 @@ export const getAnonymousIntrinsics = () => {
);
}

if (globalThis.ModuleSource) {
const AbstractModuleSourcePrototype = getPrototypeOf(
globalThis.ModuleSource.prototype,
);
intrinsics['%AbstractModuleSourcePrototype%'] =
AbstractModuleSourcePrototype;
intrinsics['%AbstractModuleSource%'] =
AbstractModuleSourcePrototype.constructor;
}
Comment on lines +164 to +172
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A valid but annoying feedback reviewers might provide here is that we can’t anticipate whether ModuleSource will land in the language with or without an AbstractModuleSource on its prototype chain, so we could hedge our bets and add a repair for ModuleSource to force it to appear one way or the other, so that lockdown() doesn’t break if the AbstractModuleSource is absent. Or, we could go the other way and not have AbstractModuleSource by default, in which case SES would just delete it and issue a warning if it showed up.


if (globalThis.ModuleSource) {
intrinsics['%ModuleSourcePrototype%'] = globalThis.ModuleSource.prototype;
}

return intrinsics;
};
4 changes: 3 additions & 1 deletion packages/ses/src/permits-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ export default function whitelistIntrinsics(
}
}

throw TypeError(`Unexpected whitelist permit ${permit} at ${path}`);
throw TypeError(
`Unexpected property ${prop} with permit ${permit} at ${path}`,
);
}

/*
Expand Down
23 changes: 23 additions & 0 deletions packages/ses/src/permits.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,12 @@ export const universalPropertyNames = {

// ESNext

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
ModuleSource: 'ModuleSource',

lockdown: 'lockdown',
harden: 'harden',

HandledPromise: 'HandledPromise', // TODO: Until Promise.delegate (see below).
};

Expand Down Expand Up @@ -1505,6 +1509,25 @@ export const permitted = {
resolve: fn,
},

// https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
'%AbstractModuleSourcePrototype%': {
'@@toStringTag': getter,
constructor: '%AbstractModuleSource%',
},
'%AbstractModuleSource%': {
'[[Proto]]': '%FunctionPrototype%',
prototype: '%AbstractModuleSourcePrototype%',
},
'%ModuleSourcePrototype%': {
'[[Proto]]': '%AbstractModuleSourcePrototype%',
'@@toStringTag': getter,
constructor: 'ModuleSource',
},
ModuleSource: {
'[[Proto]]': '%FunctionPrototype%',
prototype: '%ModuleSourcePrototype%',
},

Promise: {
// Properties of the Promise Constructor
'[[Proto]]': '%FunctionPrototype%',
Expand Down
9 changes: 8 additions & 1 deletion packages/ses/test/module-source.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/// <reference types="ses">

import test from 'ava';
import '../index.js';
import { ModuleSource } from '@endo/module-source';
import '@endo/module-source/shim.js';

lockdown();

Expand Down Expand Up @@ -42,3 +44,8 @@ test('module source constructor', t => {
'ModuleSource imports should be frozen',
);
});

test('ModuleSource is a shared intrinsic', t => {
t.truthy(ModuleSource === new Compartment().globalThis.ModuleSource);
t.truthy(Object.isFrozen(ModuleSource));
});
Loading