Skip to content

Commit 1560704

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 0077472 commit 1560704

4 files changed

Lines changed: 128 additions & 10 deletions

File tree

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 @@ const { includes: ArrayProtoIncludes } = Array.prototype;
55
const { assign: ObjectAssign } = Object;
66
const { apply: ReflectApply, 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: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import {
22
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap,
3+
CallableEvaluate,
34
createBlueConnector,
45
createRedConnector,
56
getFilteredGlobalOwnKeys,
67
linkIntrinsics,
78
DistortionCallback,
89
Instrumentation,
910
PropertyKeys,
11+
SUPPORTS_SHADOW_REALM,
1012
VirtualEnvironment,
1113
} from '@locker/near-membrane-base';
1214

@@ -30,10 +32,15 @@ const IFRAME_SANDBOX_ATTRIBUTE_VALUE = 'allow-same-origin allow-scripts';
3032
const ObjectCtor = Object;
3133
const TypeErrorCtor = TypeError;
3234
const { prototype: DocumentProto } = Document;
35+
const { bind: FunctionProtoBind } = Function.prototype;
3336
const { prototype: NodeProto } = Node;
3437
const { remove: ElementProtoRemove, setAttribute: ElementProtoSetAttribute } = Element.prototype;
3538
const { appendChild: NodeProtoAppendChild } = NodeProto;
36-
const { assign: ObjectAssign } = ObjectCtor;
39+
const {
40+
assign: ObjectAssign,
41+
create: ObjectCreate,
42+
getOwnPropertyDescriptors: ObjectGetOwnPropertyDescriptors,
43+
} = ObjectCtor;
3744
// eslint-disable-next-line @typescript-eslint/naming-convention
3845
const { __lookupGetter__: ObjectProto__lookupGetter__ } = ObjectCtor.prototype as any;
3946
const { apply: ReflectApply } = Reflect;
@@ -56,9 +63,14 @@ const HTMLIFrameElementProtoContentWindowGetter = ReflectApply(
5663
const NodeProtoLastChildGetter = ReflectApply(ObjectProto__lookupGetter__, NodeProto, [
5764
'lastChild',
5865
])!;
66+
// @ts-ignore: Prevent cannot find name 'ShadowRealm' error.
67+
const ShadowRealmCtor = SUPPORTS_SHADOW_REALM ? ShadowRealm : undefined;
68+
const ShadowRealmProtoEvaluate: CallableEvaluate | undefined = ShadowRealmCtor?.prototype?.evaluate;
69+
const defaultGlobalOwnKeysRegistry = { __proto__: null };
5970
const docRef = document;
6071

6172
let defaultGlobalOwnKeys: PropertyKeys | null = null;
73+
let defaultGlobalPropertyDescriptorMap: PropertyDescriptorMap | null = null;
6274

6375
function createDetachableIframe(): HTMLIFrameElement {
6476
const iframe = ReflectApply(DocumentProtoCreateElement, docRef, [
@@ -163,4 +175,103 @@ function createIframeVirtualEnvironment(
163175
return env;
164176
}
165177

166-
export default createIframeVirtualEnvironment;
178+
function createShadowRealmVirtualEnvironment(
179+
globalObject: WindowProxy & typeof globalThis,
180+
globalObjectShape: object | null,
181+
providedOptions?: BrowserEnvironmentOptions
182+
): VirtualEnvironment {
183+
if (typeof globalObject !== 'object' || globalObject === null) {
184+
throw new TypeErrorCtor('Missing global object virtualization target.');
185+
}
186+
const {
187+
distortionCallback,
188+
endowments,
189+
instrumentation,
190+
// eslint-disable-next-line prefer-object-spread
191+
} = ObjectAssign({ __proto__: null }, providedOptions);
192+
193+
// If a globalObjectShape has been explicitly specified, reset the
194+
// defaultGlobalPropertyDescriptorMap to null. This will ensure that
195+
// the provided globalObjectShape is used to re-create the cached
196+
// defaultGlobalPropertyDescriptorMap.
197+
if (globalObjectShape !== null) {
198+
defaultGlobalPropertyDescriptorMap = null;
199+
}
200+
if (defaultGlobalPropertyDescriptorMap === null) {
201+
let sourceShapeOrOneTimeWindow = globalObjectShape!;
202+
let sourceIsIframe = false;
203+
if (globalObjectShape === null) {
204+
const oneTimeIframe = createDetachableIframe();
205+
sourceShapeOrOneTimeWindow = ReflectApply(
206+
HTMLIFrameElementProtoContentWindowGetter,
207+
oneTimeIframe,
208+
[]
209+
)!;
210+
sourceIsIframe = true;
211+
}
212+
defaultGlobalOwnKeys = getFilteredGlobalOwnKeys(sourceShapeOrOneTimeWindow);
213+
if (sourceIsIframe) {
214+
ReflectApply(ElementProtoRemove, sourceShapeOrOneTimeWindow, []);
215+
}
216+
defaultGlobalPropertyDescriptorMap = {
217+
__proto__: null,
218+
} as unknown as PropertyDescriptorMap;
219+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(
220+
defaultGlobalPropertyDescriptorMap,
221+
ObjectGetOwnPropertyDescriptors(globalObject)
222+
);
223+
for (let i = 0, { length } = defaultGlobalOwnKeys; i < length; i += 1) {
224+
defaultGlobalOwnKeysRegistry[defaultGlobalOwnKeys[i]] = true;
225+
}
226+
for (const key in defaultGlobalPropertyDescriptorMap) {
227+
if (!(key in defaultGlobalOwnKeysRegistry)) {
228+
delete defaultGlobalPropertyDescriptorMap[key];
229+
}
230+
}
231+
}
232+
const blueRefs = getCachedGlobalObjectReferences(globalObject);
233+
// Create a new environment.
234+
const env = new VirtualEnvironment({
235+
blueConnector: createBlueConnector(globalObject),
236+
distortionCallback,
237+
instrumentation,
238+
redConnector: createRedConnector(
239+
ReflectApply(FunctionProtoBind, ShadowRealmProtoEvaluate, [new ShadowRealmCtor()])
240+
),
241+
});
242+
linkIntrinsics(env, globalObject);
243+
// window
244+
env.link('globalThis');
245+
// Set globalThis.__proto__ in the sandbox to a proxy of
246+
// globalObject.__proto__ and with this, the entire
247+
// structure around window proto chain should be covered.
248+
env.remapProto(globalObject, blueRefs.WindowProto);
249+
let unsafeBlueDescMap: PropertyDescriptorMap = defaultGlobalPropertyDescriptorMap;
250+
if (globalObject !== window) {
251+
unsafeBlueDescMap = { __proto__: null } as unknown as PropertyDescriptorMap;
252+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(
253+
unsafeBlueDescMap,
254+
ObjectGetOwnPropertyDescriptors(globalObject)
255+
);
256+
for (const key in unsafeBlueDescMap) {
257+
if (!(key in defaultGlobalOwnKeysRegistry)) {
258+
delete unsafeBlueDescMap[key];
259+
}
260+
}
261+
}
262+
env.remapProperties(blueRefs.window, unsafeBlueDescMap);
263+
if (endowments) {
264+
const filteredEndowments: PropertyDescriptorMap = {};
265+
assignFilteredGlobalDescriptorsFromPropertyDescriptorMap(filteredEndowments, endowments);
266+
removeWindowDescriptors(filteredEndowments);
267+
env.remapProperties(blueRefs.window, filteredEndowments);
268+
}
269+
// We remap `blueRefs.WindowPropertiesProto` to an empty object because it
270+
// is "magical" in that it provides access to elements by id.
271+
env.remapProto(blueRefs.WindowProto, ObjectCreate(blueRefs.EventTargetProto));
272+
return env;
273+
}
274+
275+
export default SUPPORTS_SHADOW_REALM
276+
? createShadowRealmVirtualEnvironment
277+
: 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', () => {
@@ -29,8 +30,11 @@ describe('createVirtualEnvironment', () => {
2930
});
3031
it('options object has keepAlive: true', () => {
3132
const count = window.frames.length;
32-
const env = createVirtualEnvironment(window, { keepAlive: true });
33-
expect(window.frames.length).toBe(count + 1);
33+
const env = createVirtualEnvironment(window, window, {
34+
globalObjectShape: window,
35+
keepAlive: true,
36+
});
37+
expect(window.frames.length).toBe(SUPPORTS_SHADOW_REALM ? count : count + 1);
3438
expect(() => env.evaluate('')).not.toThrow();
3539
});
3640
it('options object has keepAlive: false', () => {

0 commit comments

Comments
 (0)