Skip to content

Commit 9e9bd91

Browse files
committed
fix: update near-membrane-dom to use ShadowRealm, fallback to iframe
feat: my attempt fix: ensure that globalObjectShape is used in both paths when provided refactor: split logic for better readability refactor: support linkage of window.__proto__ chain in shadow realm chore: revert debug fix: restore globalShapeObject and globalObjectVirtualizationTarget behavior in createShadowRealmVirtualEnvironment
1 parent 9719c14 commit 9e9bd91

File tree

4 files changed

+129
-11
lines changed

4 files changed

+129
-11
lines changed

packages/near-membrane-base/src/environment.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,23 @@ export class VirtualEnvironment {
4444

4545
private readonly blueGetSelectedTarget: GetSelectedTarget;
4646

47-
private readonly blueGetTransferableValue: GetTransferableValue;
47+
private blueGetTransferableValue: GetTransferableValue;
4848

49-
private readonly blueGlobalThisPointer: Pointer;
49+
private blueGlobalThisPointer: Pointer;
5050

5151
private readonly redCallableEvaluate: CallableEvaluate;
5252

5353
private readonly redCallableGetPropertyValuePointer: CallableGetPropertyValuePointer;
5454

5555
private readonly redCallableLinkPointers: CallableLinkPointers;
5656

57-
private readonly redCallableSetPrototypeOf: CallableSetPrototypeOf;
57+
private redCallableSetPrototypeOf: CallableSetPrototypeOf;
5858

59-
private readonly redCallableDefineProperties: CallableDefineProperties;
59+
private redCallableDefineProperties: CallableDefineProperties;
6060

61-
private readonly redCallableInstallLazyPropertyDescriptors: CallableInstallLazyPropertyDescriptors;
61+
private redCallableInstallLazyPropertyDescriptors: CallableInstallLazyPropertyDescriptors;
6262

63-
private readonly redGlobalThisPointer: Pointer;
63+
private redGlobalThisPointer: Pointer;
6464

6565
constructor(options: VirtualEnvironmentOptions) {
6666
if (options === undefined) {

packages/near-membrane-base/src/intrinsics.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { PropertyKeys } from './types';
55
const { assign: ObjectAssign } = Object;
66
const { ownKeys: ReflectOwnKeys } = Reflect;
77

8+
// @ts-ignore: Prevent cannot find name 'ShadowRealm' error.
9+
export const SUPPORTS_SHADOW_REALM = typeof ShadowRealm === 'function';
10+
811
/**
912
* This list must be in sync with ecma-262, anything new added to the global object
1013
* should be considered, to decide whether or not they need remapping. The default

packages/near-membrane-dom/src/browser-realm.ts

Lines changed: 114 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap,
33
Connector,
4+
CallableEvaluate,
45
createBlueConnector,
56
createRedConnector,
67
DistortionCallback,
@@ -10,6 +11,7 @@ import {
1011
linkIntrinsics,
1112
PropertyKeys,
1213
toSafeWeakMap,
14+
SUPPORTS_SHADOW_REALM,
1315
VirtualEnvironment,
1416
} from '@locker/near-membrane-base';
1517

@@ -34,10 +36,15 @@ const ObjectCtor = Object;
3436
const TypeErrorCtor = TypeError;
3537
const WeakMapCtor = WeakMap;
3638
const { prototype: DocumentProto } = Document;
39+
const { bind: FunctionProtoBind } = Function.prototype;
3740
const { prototype: NodeProto } = Node;
3841
const { remove: ElementProtoRemove, setAttribute: ElementProtoSetAttribute } = Element.prototype;
3942
const { appendChild: NodeProtoAppendChild } = NodeProto;
40-
const { assign: ObjectAssign } = ObjectCtor;
43+
const {
44+
assign: ObjectAssign,
45+
create: ObjectCreate,
46+
getOwnPropertyDescriptors: ObjectGetOwnPropertyDescriptors,
47+
} = ObjectCtor;
4148
// eslint-disable-next-line @typescript-eslint/naming-convention
4249
const { __lookupGetter__: ObjectProtoLookupGetter } = ObjectCtor.prototype as any;
4350
const { apply: ReflectApply } = Reflect;
@@ -66,8 +73,13 @@ const NodeProtoLastChildGetter: Getter = ReflectApply(ObjectProtoLookupGetter, N
6673
const blueDocumentToBlueCreateHooksCallbackMap = toSafeWeakMap(
6774
new WeakMapCtor<Document, Connector>()
6875
);
76+
// @ts-ignore: Prevent cannot find name 'ShadowRealm' error.
77+
const ShadowRealmCtor = SUPPORTS_SHADOW_REALM ? ShadowRealm : undefined;
78+
const ShadowRealmProtoEvaluate: CallableEvaluate | undefined = ShadowRealmCtor?.prototype?.evaluate;
79+
const defaultGlobalOwnKeysRegistry = { __proto__: null };
6980

7081
let defaultGlobalOwnKeys: PropertyKeys | null = null;
82+
let defaultGlobalPropertyDescriptorMap: PropertyDescriptorMap | null = null;
7183

7284
function createDetachableIframe(doc: Document): HTMLIFrameElement {
7385
const iframe: HTMLIFrameElement = ReflectApply(DocumentProtoCreateElement, doc, ['iframe']);
@@ -90,7 +102,7 @@ function createIframeVirtualEnvironment(
90102
if (typeof globalObject !== 'object' || globalObject === null) {
91103
throw new TypeErrorCtor('Missing global object virtualization target.');
92104
}
93-
const blueRefs = getCachedGlobalObjectReferences(globalObject);
105+
const blueRefs = getCachedGlobalObjectReferences(globalObject)!;
94106
if (typeof blueRefs !== 'object' || blueRefs === null) {
95107
throw new TypeErrorCtor('Invalid virtualization target.');
96108
}
@@ -180,4 +192,103 @@ function createIframeVirtualEnvironment(
180192
return env;
181193
}
182194

183-
export default createIframeVirtualEnvironment;
195+
function createShadowRealmVirtualEnvironment(
196+
globalObject: WindowProxy & typeof globalThis,
197+
globalObjectShape: object | null,
198+
providedOptions?: BrowserEnvironmentOptions
199+
): VirtualEnvironment {
200+
if (typeof globalObject !== 'object' || globalObject === null) {
201+
throw new TypeErrorCtor('Missing global object virtualization target.');
202+
}
203+
const {
204+
distortionCallback,
205+
endowments,
206+
instrumentation,
207+
// eslint-disable-next-line prefer-object-spread
208+
} = ObjectAssign({ __proto__: null }, providedOptions);
209+
210+
// If a globalObjectShape has been explicitly specified, reset the
211+
// defaultGlobalPropertyDescriptorMap to null. This will ensure that
212+
// the provided globalObjectShape is used to re-create the cached
213+
// defaultGlobalPropertyDescriptorMap.
214+
if (globalObjectShape !== null) {
215+
defaultGlobalPropertyDescriptorMap = null;
216+
}
217+
if (defaultGlobalPropertyDescriptorMap === null) {
218+
let sourceShapeOrOneTimeWindow = globalObjectShape!;
219+
let sourceIsIframe = false;
220+
if (globalObjectShape === null) {
221+
const oneTimeIframe = createDetachableIframe(globalObject.document);
222+
sourceShapeOrOneTimeWindow = ReflectApply(
223+
HTMLIFrameElementProtoContentWindowGetter,
224+
oneTimeIframe,
225+
[]
226+
)!;
227+
sourceIsIframe = true;
228+
}
229+
defaultGlobalOwnKeys = getFilteredGlobalOwnKeys(sourceShapeOrOneTimeWindow);
230+
if (sourceIsIframe) {
231+
ReflectApply(ElementProtoRemove, sourceShapeOrOneTimeWindow, []);
232+
}
233+
defaultGlobalPropertyDescriptorMap = {
234+
__proto__: null,
235+
} as unknown as PropertyDescriptorMap;
236+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(
237+
defaultGlobalPropertyDescriptorMap,
238+
ObjectGetOwnPropertyDescriptors(globalObject)
239+
);
240+
for (let i = 0, { length } = defaultGlobalOwnKeys; i < length; i += 1) {
241+
defaultGlobalOwnKeysRegistry[defaultGlobalOwnKeys[i]] = true;
242+
}
243+
for (const key in defaultGlobalPropertyDescriptorMap) {
244+
if (!(key in defaultGlobalOwnKeysRegistry)) {
245+
delete defaultGlobalPropertyDescriptorMap[key];
246+
}
247+
}
248+
}
249+
const blueRefs = getCachedGlobalObjectReferences(globalObject)!;
250+
// Create a new environment.
251+
const env = new VirtualEnvironment({
252+
blueConnector: createBlueConnector(globalObject),
253+
distortionCallback,
254+
instrumentation,
255+
redConnector: createRedConnector(
256+
ReflectApply(FunctionProtoBind, ShadowRealmProtoEvaluate, [new ShadowRealmCtor()])
257+
),
258+
});
259+
linkIntrinsics(env, globalObject);
260+
// window
261+
env.link('globalThis');
262+
// Set globalThis.__proto__ in the sandbox to a proxy of
263+
// globalObject.__proto__ and with this, the entire
264+
// structure around window proto chain should be covered.
265+
env.remapProto(globalObject, blueRefs.WindowProto);
266+
let unsafeBlueDescMap: PropertyDescriptorMap = defaultGlobalPropertyDescriptorMap;
267+
if (globalObject !== window) {
268+
unsafeBlueDescMap = { __proto__: null } as unknown as PropertyDescriptorMap;
269+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(
270+
unsafeBlueDescMap,
271+
ObjectGetOwnPropertyDescriptors(globalObject)
272+
);
273+
for (const key in unsafeBlueDescMap) {
274+
if (!(key in defaultGlobalOwnKeysRegistry)) {
275+
delete unsafeBlueDescMap[key];
276+
}
277+
}
278+
}
279+
env.remapProperties(blueRefs.window, unsafeBlueDescMap);
280+
if (endowments) {
281+
const filteredEndowments: PropertyDescriptorMap = {};
282+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(filteredEndowments, endowments);
283+
removeWindowDescriptors(filteredEndowments);
284+
env.remapProperties(blueRefs.window, filteredEndowments);
285+
}
286+
// We remap `blueRefs.WindowPropertiesProto` to an empty object because it
287+
// is "magical" in that it provides access to elements by id.
288+
env.remapProto(blueRefs.WindowProto, ObjectCreate(blueRefs.EventTargetProto));
289+
return env;
290+
}
291+
292+
export default SUPPORTS_SHADOW_REALM
293+
? createShadowRealmVirtualEnvironment
294+
: createIframeVirtualEnvironment;

test/environment/createvirtualenvironment.spec.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { SUPPORTS_SHADOW_REALM } from '@locker/near-membrane-base';
12
import createVirtualEnvironment from '@locker/near-membrane-dom';
23

34
describe('createVirtualEnvironment', () => {
@@ -37,8 +38,11 @@ describe('createVirtualEnvironment', () => {
3738
});
3839
it('options object has keepAlive: true', () => {
3940
const count = window.frames.length;
40-
const env = createVirtualEnvironment(window, { keepAlive: true });
41-
expect(window.frames.length).toBe(count + 1);
41+
const env = createVirtualEnvironment(window, {
42+
globalObjectShape: window,
43+
keepAlive: true,
44+
});
45+
expect(window.frames.length).toBe(SUPPORTS_SHADOW_REALM ? count : count + 1);
4246
expect(() => env.evaluate('')).not.toThrow();
4347
});
4448
it('options object has keepAlive: false', () => {

0 commit comments

Comments
 (0)