Skip to content

Commit

Permalink
fix: update near-membrane-dom to use ShadowRealm
Browse files Browse the repository at this point in the history
  • Loading branch information
rwaldron committed Jan 10, 2022
1 parent 1c91a19 commit 72cb157
Show file tree
Hide file tree
Showing 3 changed files with 5 additions and 186 deletions.
150 changes: 5 additions & 145 deletions packages/near-membrane-dom/src/browser-realm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,140 +12,11 @@ import {

import { getCachedBlueReferences, linkUnforgeables, tameDOM } from './window';

const IFRAME_SANDBOX_ATTRIBUTE_VALUE = 'allow-same-origin allow-scripts';

const emptyArray: [] = [];
const {
close: DocumentProtoClose,
createElement: DocumentProtoCreateElement,
open: DocumentProtoOpen,
} = document;
const { remove: ElementProtoRemove, setAttribute: ElementProtoSetAttribute } = Element.prototype;
const { appendChild: NodeProtoAppendChild } = Node.prototype;
const { assign: ObjectAssign } = Object;
const {
// eslint-disable-next-line @typescript-eslint/naming-convention
__lookupGetter__: ObjectProto__lookupGetter__,
hasOwnProperty: ObjectProtoHasOwnProperty,
} = Object.prototype as any;
const { apply: ReflectApply } = Reflect;

function ObjectLookupOwnGetter(obj: object, key: PropertyKey): Function | undefined {
// Since this function is only used internally, and would not otherwise be
// reachable by user code, istanbul can ignore test coverage for the
// following condition.
// istanbul ignore next
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (obj === null || obj === undefined || !ReflectApply(ObjectProtoHasOwnProperty, obj, [key])) {
return undefined;
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return ReflectApply(ObjectProto__lookupGetter__, obj, [key]);
}

const DocumentProtoBodyGetter = ObjectLookupOwnGetter(Document.prototype, 'body')!;
const HTMLElementProtoStyleGetter = ObjectLookupOwnGetter(HTMLElement.prototype, 'style')!;
const HTMLIFrameElementProtoContentWindowGetter = ObjectLookupOwnGetter(
HTMLIFrameElement.prototype,
'contentWindow'
)!;
const NodeProtoIsConnectedGetter = ObjectLookupOwnGetter(Node.prototype, 'isConnected')!;
const NodeProtoLastChildGetter = ObjectLookupOwnGetter(Node.prototype, 'lastChild')!;

function DocumentBody(doc: Document): typeof Document.prototype.body {
return ReflectApply(DocumentProtoBodyGetter, doc, emptyArray);
}

function DocumentClose(doc: Document): ReturnType<typeof Document.prototype.close> {
return ReflectApply(DocumentProtoClose, doc, emptyArray);
}

function DocumentCreateElement(
doc: Document,
tagName: string
): ReturnType<typeof Document.prototype.createElement> {
return ReflectApply(DocumentProtoCreateElement, doc, [tagName]);
}

function DocumentOpen(doc: Document): ReturnType<typeof Document.prototype.open> {
return ReflectApply(DocumentProtoOpen, doc, emptyArray);
}

function ElementSetAttribute(
el: Element,
name: string,
value: string
): ReturnType<typeof Element.prototype.setAttribute> {
return ReflectApply(ElementProtoSetAttribute, el, [name, value]);
}

function ElementRemove(element: Element): Element {
return ReflectApply(ElementProtoRemove, element, emptyArray);
}

function HTMLElementStyleGetter(el: HTMLElement): typeof HTMLElement.prototype.style {
return ReflectApply(HTMLElementProtoStyleGetter, el, emptyArray);
}

function HTMLIFrameElementContentWindowGetter(
iframe: HTMLIFrameElement
): typeof HTMLIFrameElement.prototype.contentWindow {
return ReflectApply(HTMLIFrameElementProtoContentWindowGetter, iframe, emptyArray);
}

function NodeAppendChild(parent: Node, child: ChildNode): ChildNode {
return ReflectApply(NodeProtoAppendChild, parent, [child]);
}

// It's impossible to test whether NodeLastChild(document) is reached
// in a normal Karma test environment, because the document will always
// have a body element. Ignore this in coverage reporting to
// avoid a penalty.
// istanbul ignore next
function NodeLastChild(node: Node): typeof Node.prototype.lastChild {
return ReflectApply(NodeProtoLastChildGetter, node, emptyArray);
}

function NodeIsConnected(node: Node): boolean {
return ReflectApply(NodeProtoIsConnectedGetter, node, emptyArray);
}

function createDetachableIframe(): HTMLIFrameElement {
// @ts-ignore document global ref - in browsers
const iframe = DocumentCreateElement(document, 'iframe') as HTMLIFrameElement;
// It's impossible to test whether NodeLastChild(document) is reached
// in a normal Karma test environment. (See explanation above,
// at NodeLastChild definition.)
const parent = DocumentBody(document) || /* istanbul ignore next */ NodeLastChild(document);
const style = HTMLElementStyleGetter(iframe);
style.display = 'none';
ElementSetAttribute(iframe, 'sandbox', IFRAME_SANDBOX_ATTRIBUTE_VALUE);
NodeAppendChild(parent, iframe);
return iframe;
}

function removeIframe(iframe: HTMLIFrameElement) {
// In Chrome debugger statements will be ignored when the iframe is removed
// from the document. Other browsers like Firefox and Safari work as expected.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1015462

// Because the detachable iframe is created, and then optionally removed, all
// within the execution of createVirtualEnvironment, there is no point in which
// test code can interfere with iframe element, or its prototype chain, in
// order to remove it from the DOM, or stub the isConnected accessor
// (the latter is already too late before createVirtualEnvironment is ever called).
// For this reason, ignore the lack of `else` path coverage.
//
// istanbul ignore else
if (NodeIsConnected(iframe)) {
ElementRemove(iframe);
}
}

interface BrowserEnvironmentOptions {
distortionCallback?: DistortionCallback;
endowments?: object;
keepAlive?: boolean;
support?: SupportFlagsObject;
instrumentation?: InstrumentationHooks;
}
Expand All @@ -165,17 +36,16 @@ export default function createVirtualEnvironment(
const options = ObjectAssign(
{
__proto__: null,
keepAlive: false,
},
providedOptions
);
// eslint-disable-next-line prefer-object-spread
const { distortionCallback, endowments = {}, keepAlive, support, instrumentation } = options;
const iframe = createDetachableIframe();
const redWindow = HTMLIFrameElementContentWindowGetter(iframe)!.window;
const { document: redDocument } = redWindow;
const { distortionCallback, endowments = {}, support, instrumentation } = options;
// @ts-ignore: TypeScript doesn't know what a ShadowRealm is yet.
const shadowRealm = new ShadowRealm();
const shadowRealmEval = shadowRealm.evaluate.bind(shadowRealm);
const blueConnector = createHooksCallback;
const redConnector = createConnector(redWindow.eval);
const redConnector = createConnector(shadowRealmEval);
// Extract the global references and descriptors before any interference.
const blueRefs = getCachedBlueReferences(globalObjectVirtualizationTarget);
// Create a new environment.
Expand All @@ -190,15 +60,5 @@ export default function createVirtualEnvironment(
linkIntrinsics(env, globalObjectVirtualizationTarget);
linkUnforgeables(env, globalObjectVirtualizationTarget);
tameDOM(env, blueRefs, getResolvedShapeDescriptors(globalObjectShape, endowments));
// Once we get the iframe info ready, and all mapped, we can proceed
// to detach the iframe only if the keepAlive option isn't true.
if (keepAlive !== true) {
removeIframe(iframe);
} else {
// TODO: Temporary hack to preserve the document reference in Firefox.
// https://bugzilla.mozilla.org/show_bug.cgi?id=543435
DocumentOpen(redDocument);
DocumentClose(redDocument);
}
return env;
}
31 changes: 0 additions & 31 deletions test/dom/ffbug.spec.js

This file was deleted.

10 changes: 0 additions & 10 deletions test/environment/createvirtualenvironment.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ describe('createVirtualEnvironment', () => {
const env = createVirtualEnvironment(window, window, { endowments: {} });
expect(() => env.evaluate('')).not.toThrow();
});
it('options object has keepAlive: true', () => {
const count = window.frames.length;
const env = createVirtualEnvironment(window, window, { keepAlive: true });
expect(window.frames.length).toBe(count + 1);
expect(() => env.evaluate('')).not.toThrow();
});
it('options object has keepAlive: false', () => {
const env = createVirtualEnvironment(window, window, { keepAlive: false });
expect(() => env.evaluate('')).not.toThrow();
});
});
});
});

0 comments on commit 72cb157

Please sign in to comment.