diff --git a/packages/near-membrane-base/src/connector.ts b/packages/near-membrane-base/src/connector.ts index 9d13e862..81036170 100644 --- a/packages/near-membrane-base/src/connector.ts +++ b/packages/near-membrane-base/src/connector.ts @@ -1,11 +1,23 @@ import { createMembraneMarshall } from './membrane'; const TypeErrorCtor = TypeError; -const marshallSourceTextInStrictMode = `(function(){'use strict';return (${createMembraneMarshall.toString()})})()`; +// istanbul ignore next +const marshallSourceTextInStrictMode = ` +(function(){ + 'use strict'; + (${function initializeShadowRealm() { + if (typeof Error.stackTraceLimit === 'number') { + // The default stack trace limit is 10. + // Increasing to 20 as a baby step. + Error.stackTraceLimit *= 2; + } + }.toString()})(); + return (${createMembraneMarshall.toString()}) +})()`; // eslint-disable-next-line no-eval export function createConnector(evaluator: typeof eval) { if (!evaluator) { - throw new TypeErrorCtor('Missing evaluator function'); + throw new TypeErrorCtor('Missing evaluator function.'); } // The result of this eval will be a function that returns a function. // The hooks connector is the last returned function, so we invoke the diff --git a/packages/near-membrane-base/src/environment.ts b/packages/near-membrane-base/src/environment.ts index b71ef7bc..60808ae5 100644 --- a/packages/near-membrane-base/src/environment.ts +++ b/packages/near-membrane-base/src/environment.ts @@ -38,7 +38,6 @@ const UNDEFINED_SYMBOL = Symbol.for('@@membraneUndefinedValue'); const ErrorCtor = Error; const { assign: ObjectAssign, keys: ObjectKeys } = Object; -const { hasOwnProperty: ObjectProtoHasOwnProperty } = Object.prototype; const { apply: ReflectApply, ownKeys: ReflectOwnKeys } = Reflect; const { slice: StringProtoSlice, toUpperCase: StringProtoToUpperCase } = String.prototype; @@ -88,12 +87,10 @@ export class VirtualEnvironment { this.redConnector = redConnector; let supportFlags = SupportFlagsEnum.None; - const supportProps = support ? ObjectKeys(support) : []; - for (let i = 0, len = supportProps.length; i < len; i += 1) { - const key = capitalizeFirstChar(supportProps[i]); - if (ReflectApply(ObjectProtoHasOwnProperty, SupportFlagsEnum, [key])) { - supportFlags |= SupportFlagsEnum[key]; - } + const supportKeys = support ? ObjectKeys(support) : []; + for (let i = 0, len = supportKeys.length; i < len; i += 1) { + const enumKey = capitalizeFirstChar(supportKeys[i]); + supportFlags |= SupportFlagsEnum[enumKey]; } let blueHooks: Parameters; diff --git a/packages/near-membrane-base/src/intrinsics.ts b/packages/near-membrane-base/src/intrinsics.ts index 8f452db0..0385d02f 100644 --- a/packages/near-membrane-base/src/intrinsics.ts +++ b/packages/near-membrane-base/src/intrinsics.ts @@ -136,8 +136,11 @@ function assignFilteredGlobalObjectShapeDescriptors void; -// eslint-disable-next-line no-shadow -export enum SupportFlagsEnum { - None = 0, - MagicMarker = 1 << 0, -} - export type DistortionCallback = (target: ProxyTarget) => ProxyTarget; export interface InitLocalOptions { distortionCallback?: DistortionCallback; instrumentation?: InstrumentationHooks; } +// eslint-disable-next-line no-shadow +export enum SupportFlagsEnum { + None = 0, + MagicMarker = 1 << 0, +} +ReflectSetPrototypeOf(SupportFlagsEnum, null); // istanbul ignore next export function createMembraneMarshall() { - // eslint-disable-next-line no-shadow - enum MarshallSupportFlagsField { - None = 0, - MagicMarker = 1 << 0, - } - - // eslint-disable-next-line no-shadow - enum TargetIntegrityTraits { - None = 0, - IsNotExtensible = 1 << 0, - IsSealed = 1 << 1, - IsFrozen = 1 << 2, - Revoked = 1 << 4, - } - - // eslint-disable-next-line no-shadow - enum TargetTraits { - None = 0, - IsArray = 1 << 0, - IsFunction = 1 << 1, - IsObject = 1 << 2, - IsArrowFunction = 1 << 3, - Revoked = 1 << 4, - } - const { eval: cachedLocalEval } = globalThis; const ArrayCtor = Array; const { isArray: isArrayOrNotOrThrowForRevoked } = Array; @@ -169,29 +146,58 @@ export function createMembraneMarshall() { Object.prototype; const { revocable: ProxyRevocable } = Proxy; const { - defineProperty: ReflectDefineProperty, - getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor, - setPrototypeOf: ReflectSetPrototypeOf, apply: ReflectApply, construct: ReflectConstruct, + defineProperty: ReflectDefineProperty, deleteProperty: ReflectDeleteProperty, get: ReflectGet, - set: ReflectSet, - has: ReflectHas, + getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor, getPrototypeOf: ReflectGetPrototypeOf, + has: ReflectHas, isExtensible: ReflectIsExtensible, ownKeys: ReflectOwnKeys, preventExtensions: ReflectPreventExtensions, + set: ReflectSet, + // eslint-disable-next-line @typescript-eslint/no-shadow, no-shadow + setPrototypeOf: ReflectSetPrototypeOf, } = Reflect; const { slice: StringProtoSlice } = String.prototype; const TypeErrorCtor = TypeError; const { get: WeakMapProtoGet, set: WeakMapProtoSet } = WeakMap.prototype; + // @rollup/plugin-replace replaces `DEV_MODE` references. + const DEV_MODE = true; const LOCKER_LIVE_MARKER_SYMBOL = Symbol.for('@@lockerLiveValue'); const LOCKER_MAGIC_MARKER_SYMBOL = Symbol.for('@@lockerMagicValue'); const { toStringTag: TO_STRING_TAG_SYMBOL } = Symbol; const UNDEFINED_SYMBOL = Symbol.for('@@membraneUndefinedValue'); + // eslint-disable-next-line no-shadow + enum MarshallSupportFlagsField { + None = 0, + MagicMarker = 1 << 0, + } + ReflectSetPrototypeOf(MarshallSupportFlagsField, null); + // eslint-disable-next-line no-shadow + enum TargetIntegrityTraits { + None = 0, + IsNotExtensible = 1 << 0, + IsSealed = 1 << 1, + IsFrozen = 1 << 2, + Revoked = 1 << 4, + } + ReflectSetPrototypeOf(TargetIntegrityTraits, null); + // eslint-disable-next-line no-shadow + enum TargetTraits { + None = 0, + IsArray = 1 << 0, + IsFunction = 1 << 1, + IsObject = 1 << 2, + IsArrowFunction = 1 << 3, + Revoked = 1 << 4, + } + ReflectSetPrototypeOf(TargetTraits, null); + return function createHooksCallback( color: string, trapMutations: boolean, @@ -205,12 +211,9 @@ export function createMembraneMarshall() { const proxyTargetToPointerMap = new WeakMap(); - // @rollup/plugin-replace replaces `DEV_MODE` references. - const DEV_MODE = true; - const SUPPORT_MAGIC_MARKER = !!(supportFlags & MarshallSupportFlagsField.MagicMarker); - const INBOUND_INSTRUMENTATION_LABEL = `to:${color}`; const OUTBOUND_INSTRUMENTATION_LABEL = `from:${color}`; + const SUPPORT_MAGIC_MARKER = !!(supportFlags & MarshallSupportFlagsField.MagicMarker); let foreignCallablePushTarget: CallablePushTarget; let foreignCallableApply: CallableApply; @@ -229,15 +232,6 @@ export function createMembraneMarshall() { let foreignCallableHasOwnProperty: CallableHasOwnProperty; let selectedTarget: undefined | ProxyTarget; - if ( - ReflectApply(ObjectProtoHasOwnProperty, Error, ['stackTraceLimit']) && - typeof Error.stackTraceLimit === 'number' - ) { - // The default stack trace limit is 10. - // Increasing to 20 as a baby step. - Error.stackTraceLimit *= 2; - } - function copyForeignDescriptorsIntoShadowTarget( shadowTarget: ShadowTarget, foreignTargetPointer: Pointer @@ -272,7 +266,9 @@ export function createMembraneMarshall() { key, callbackWithDescriptor ); - (descriptors as any)[key] = safeDesc!; + if (safeDesc!) { + (descriptors as any)[key] = safeDesc; + } } // Use `ObjectDefineProperties` instead of individual // `ReflectDefineProperty` calls for better performance. @@ -313,7 +309,7 @@ export function createMembraneMarshall() { function createPointer(originalTarget: ProxyTarget): () => void { // assert: originalTarget is a ProxyTarget - const pointer = () => { + const pointer = (): void => { // assert: selectedTarget is undefined selectedTarget = originalTarget; }; @@ -431,9 +427,9 @@ export function createMembraneMarshall() { function getSelectedTarget(): any { // assert: selectedTarget is a ProxyTarget - const r = selectedTarget; + const result = selectedTarget; selectedTarget = undefined; - return r; + return result; } function getTargetTraits(target: object): TargetTraits { @@ -545,12 +541,12 @@ export function createMembraneMarshall() { } function getTransferableValue(value: any): PointerOrPrimitive { - // internationally ignoring the case of (typeof document.all === 'undefined') because - // in the reserve membrane, you never get one of those exotic objects + // Internationally ignoring the case of (typeof document.all === 'undefined') + // because in the reserve membrane, you never get one of those exotic objects. if (typeof value === 'undefined') { return undefined; } - // TODO: what other ways to optimize this method? + // TODO: What other ways to optimize this method? if (value === null || (typeof value !== 'function' && typeof value !== 'object')) { return value; } @@ -739,7 +735,7 @@ export function createMembraneMarshall() { if (foreignCallableHasOwnProperty(foreignTargetPointer, key)) { return true; } - // avoiding calling the has trap for any proto chain operation, + // Avoiding calling the has trap for any proto chain operation, // instead we implement the regular logic here in this trap. let currentObject = liveGetPrototypeOf(foreignTargetPointer); while (currentObject) { @@ -1007,7 +1003,19 @@ export function createMembraneMarshall() { const { length: combinedOffset } = combinedArgs; combinedArgs.length += argsLen; for (let i = 0, len = argsLen; i < len; i += 1) { - combinedArgs[i + combinedOffset] = getTransferableValue(args[i]); + const arg = args[i]; + const combinedIndex = i + combinedOffset; + // Inlining `getTransferableValue`. + if (typeof arg === 'undefined') { + combinedArgs[combinedIndex] = undefined; + } else if ( + arg === null || + (typeof arg !== 'function' && typeof arg !== 'object') + ) { + combinedArgs[combinedIndex] = arg; + } else { + combinedArgs[combinedIndex] = getTransferablePointer(arg); + } } return getLocalValue(ReflectApply(foreignCallableApply, undefined, combinedArgs)); }; @@ -1030,7 +1038,19 @@ export function createMembraneMarshall() { const { length: combinedOffset } = combinedArgs; combinedArgs.length += argsLen; for (let i = 0, len = argsLen; i < len; i += 1) { - combinedArgs[i + combinedOffset] = getTransferableValue(args[i]); + const arg = args[i]; + const combinedIndex = i + combinedOffset; + // Inline `getTransferableValue`. + if (typeof arg === 'undefined') { + combinedArgs[combinedIndex] = undefined; + } else if ( + arg === null || + (typeof arg !== 'function' && typeof arg !== 'object') + ) { + combinedArgs[combinedIndex] = arg; + } else { + combinedArgs[combinedIndex] = getTransferablePointer(arg); + } } return getLocalValue( ReflectApply(foreignCallableConstruct, undefined, combinedArgs) diff --git a/packages/near-membrane-dom/src/window.ts b/packages/near-membrane-dom/src/window.ts index 4533771e..6cbd7fb8 100644 --- a/packages/near-membrane-dom/src/window.ts +++ b/packages/near-membrane-dom/src/window.ts @@ -22,7 +22,6 @@ interface BaseReferencesRecord extends Object { */ interface CachedBlueReferencesRecord extends BaseReferencesRecord { EventTargetProtoDescriptors: PropertyDescriptorMap; - WindowProtoDescriptors: PropertyDescriptorMap; } const cachedBlueGlobalMap: WeakMap = new WeakMap(); @@ -55,7 +54,6 @@ export function getCachedBlueReferences( ReflectApply(WeakMapProtoSet, cachedBlueGlobalMap, [window, record]); // intentionally avoiding remapping any Window.prototype descriptor, // there is nothing in this prototype that needs to be remapped. - record.WindowProtoDescriptors = {}; record.EventTargetProtoDescriptors = ObjectGetOwnPropertyDescriptors(record.EventTargetProto); return record; @@ -107,10 +105,10 @@ function filterWindowDescriptors( ObjectAssign(to, endowmentsDescriptors); // removing unforgeable descriptors that cannot be installed - delete to.location; delete to.document; - delete to.window; + delete to.location; delete to.top; + delete to.window; // Some DOM APIs do brand checks for TypeArrays and others objects, // in this case, if the API is not dangerous, and works in a detached // iframe, we can let the sandbox to use the iframe's api directly, @@ -134,7 +132,6 @@ export function tameDOM( env.remap(blueRefs.window, globalDescriptors); // remapping unforgeable objects env.remap(blueRefs.EventTargetProto, blueRefs.EventTargetProtoDescriptors); - env.remap(blueRefs.WindowProto, blueRefs.WindowProtoDescriptors); /** * WindowProperties.prototype is magical, it provide access to any * object that "clobbers" the WindowProxy instance for easy access. E.g.: