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: perf work #230

Merged
merged 1 commit into from
Oct 27, 2021
Merged
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
16 changes: 14 additions & 2 deletions packages/near-membrane-base/src/connector.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 4 additions & 7 deletions packages/near-membrane-base/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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])) {
jdalton marked this conversation as resolved.
Show resolved Hide resolved
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<HooksCallback>;
Expand Down
15 changes: 5 additions & 10 deletions packages/near-membrane-base/src/intrinsics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,11 @@ function assignFilteredGlobalObjectShapeDescriptors<T extends PropertyDescriptor
// will be ignored if present in the endowments object.
// TODO: what if the intent is to polyfill one of those
// intrinsics?
if (!isIntrinsicGlobalName(key) && !isReflectiveGlobalName(key)) {
const unsafeDesc = ReflectGetOwnPropertyDescriptor(source, key)!;
if (
!ReflectApply(SetProtoHas, ESGlobalKeys, [key]) &&
!ReflectApply(ArrayProtoIncludes, ReflectiveIntrinsicObjectNames, [key])
) {
const unsafeDesc = ReflectGetOwnPropertyDescriptor(source, key);
// Safari 14.0.x (macOS) and 14.2 (iOS) have a bug where 'showModalDialog'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Easy inline at the intrinsics level ☝️

// is returned in the list of own keys produces by ReflectOwnKeys(iframeWindow),
// however 'showModalDialog' is not an own property and produces
Expand All @@ -156,14 +159,6 @@ function assignFilteredGlobalObjectShapeDescriptors<T extends PropertyDescriptor
return descriptorMap;
}

function isIntrinsicGlobalName(key: string | symbol): boolean {
return ReflectApply(SetProtoHas, ESGlobalKeys, [key]);
}

function isReflectiveGlobalName(key: string | symbol): boolean {
return ReflectApply(ArrayProtoIncludes, ReflectiveIntrinsicObjectNames, [key]);
}

export function linkIntrinsics(
env: VirtualEnvironment,
globalObjectVirtualizationTarget: typeof globalThis
Expand Down
142 changes: 81 additions & 61 deletions packages/near-membrane-base/src/membrane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@

import { InstrumentationHooks } from './instrumentation';

const { setPrototypeOf: ReflectSetPrototypeOf } = Reflect;

export type Pointer = CallableFunction;
type PrimitiveValue = bigint | boolean | null | number | string | symbol | undefined;
type PointerOrPrimitive = Pointer | PrimitiveValue;
type Primitive = bigint | boolean | null | number | string | symbol | undefined;
type PointerOrPrimitive = Pointer | Primitive;
export type ProxyTarget = CallableFunction | any[] | object;
type ShadowTarget = CallableFunction | any[] | object;
type CallablePushTarget = (
Expand Down Expand Up @@ -116,45 +118,20 @@ export type HooksCallback = (
callableGetUnbrandedTag: CallableGetUnbrandedTag,
callableHasOwnProperty: CallableHasOwnProperty
) => 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;
Expand All @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nulled the prototypes of our enums to avoid hasOwnProperty checks on them.

// 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,
Expand All @@ -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;
Expand All @@ -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;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👆 Moved that higher to avoid just setting it over and over and over.

function copyForeignDescriptorsIntoShadowTarget(
shadowTarget: ShadowTarget,
foreignTargetPointer: Pointer
Expand Down Expand Up @@ -272,7 +266,9 @@ export function createMembraneMarshall() {
key,
callbackWithDescriptor
);
(descriptors as any)[key] = safeDesc!;
if (safeDesc!) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just because there is an own property doesn't mean getOwnPropertyDescriptor will return something. There's that WebKit bug Rick workaround. This is more of that.

Copy link
Contributor

Choose a reason for hiding this comment

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

does this means we can remove the unnecessary condition for undefined descriptors somewhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

does this means we can remove the unnecessary condition for undefined descriptors somewhere else?

No, the above is a guard that is needed for the bug case. Just because a property is an own property doesn't necessarily mean we will get a descriptor (in a perfect world, that made logical sense, we could trust that we would get a descriptor if an own property exists, but in this buggy world it is not guaranteed so the truthy guard is needed)

(descriptors as any)[key] = safeDesc;
}
}
// Use `ObjectDefineProperties` instead of individual
// `ReflectDefineProperty` calls for better performance.
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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));
};
Expand All @@ -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`.
Copy link
Contributor

Choose a reason for hiding this comment

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

If JavaScript were to support Macros 🙊

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)
Expand Down
7 changes: 2 additions & 5 deletions packages/near-membrane-dom/src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface BaseReferencesRecord extends Object {
*/
interface CachedBlueReferencesRecord extends BaseReferencesRecord {
EventTargetProtoDescriptors: PropertyDescriptorMap;
WindowProtoDescriptors: PropertyDescriptorMap;
}

const cachedBlueGlobalMap: WeakMap<typeof globalThis, CachedBlueReferencesRecord> = new WeakMap();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

☝️ This call to remap is a noop since WindowProtoDescriptors is an empty object and now removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

lol, I forgot that I have a branch with this change and the removal of record.WindowProtoDescriptors = {};. Glad to see it go!

/**
* WindowProperties.prototype is magical, it provide access to any
* object that "clobbers" the WindowProxy instance for easy access. E.g.:
Expand Down