Skip to content

Commit

Permalink
perf_hooks: hrtime idlharness
Browse files Browse the repository at this point in the history
1. Enforce receiver checks on IDL interface `Performance.now`.
2. Avoid prototype manipulation on constructing `Performance` with
   `ReflectConstruct`.
3. `defineReplaceableAttribute` should create IDL getter/setter.
  • Loading branch information
legendecas committed Sep 1, 2022
1 parent 82cfb50 commit a94deca
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 151 deletions.
31 changes: 25 additions & 6 deletions lib/internal/bootstrap/browser.js
Expand Up @@ -75,8 +75,8 @@ exposeInterface(globalThis, 'Blob', buffer.Blob);
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
const perf_hooks = require('perf_hooks');
exposeInterface(globalThis, 'Performance', perf_hooks.Performance);
defineReplacableAttribute(globalThis, 'performance',
perf_hooks.performance);
defineReplaceableAttribute(globalThis, 'performance',
perf_hooks.performance);

function createGlobalConsole() {
const consoleFromNode =
Expand Down Expand Up @@ -114,14 +114,33 @@ function exposeGetterAndSetter(target, name, getter, setter = undefined) {
});
}

// https://heycam.github.io/webidl/#Replaceable
function defineReplacableAttribute(target, name, value) {
// https://webidl.spec.whatwg.org/#Replaceable
function defineReplaceableAttribute(target, name, value) {
let slot = value;

// https://webidl.spec.whatwg.org/#dfn-attribute-getter
function get() {
return slot;
}
ObjectDefineProperty(get, 'name', {
__proto__: null,
value: `get ${name}`,
});

function set(value) {
slot = value;
}
ObjectDefineProperty(set, 'name', {
__proto__: null,
value: `set ${name}`,
});

ObjectDefineProperty(target, name, {
__proto__: null,
writable: true,
enumerable: true,
configurable: true,
value,
get,
set,
});
}

Expand Down
228 changes: 101 additions & 127 deletions lib/internal/perf/performance.js
@@ -1,9 +1,9 @@
'use strict';

const {
ObjectDefineProperty,
ObjectDefineProperties,
ObjectSetPrototypeOf,
ReflectConstruct,
SymbolToStringTag,
} = primordials;

const {
Expand All @@ -17,9 +17,13 @@ const {
EventTarget,
Event,
kTrustEvent,
initEventTarget,
} = require('internal/event_target');

const { now } = require('internal/perf/utils');
const {
now,
kPerformanceBrand,
} = require('internal/perf/utils');

const { markResourceTiming } = require('internal/perf/resource_timing');

Expand All @@ -38,8 +42,9 @@ const {
const { eventLoopUtilization } = require('internal/perf/event_loop_utilization');
const nodeTiming = require('internal/perf/nodetiming');
const timerify = require('internal/perf/timerify');
const { customInspectSymbol: kInspect } = require('internal/util');
const { customInspectSymbol: kInspect, kEnumerableProperty, kEmptyObject } = require('internal/util');
const { inspect } = require('util');
const { validateInternalField } = require('internal/validators');

const {
getTimeOriginTimestamp
Expand All @@ -63,177 +68,147 @@ class Performance extends EventTarget {
timeOrigin: this.timeOrigin,
}, opts)}`;
}
}

function toJSON() {
return {
nodeTiming: this.nodeTiming,
timeOrigin: this.timeOrigin,
eventLoopUtilization: this.eventLoopUtilization()
};
}
clearMarks(name) {
if (name !== undefined) {
name = `${name}`;
}
clearMarkTimings(name);
clearEntriesFromBuffer('mark', name);
}

function clearMarks(name) {
if (name !== undefined) {
name = `${name}`;
clearMeasures(name) {
if (name !== undefined) {
name = `${name}`;
}
clearEntriesFromBuffer('measure', name);
}
clearMarkTimings(name);
clearEntriesFromBuffer('mark', name);
}

function clearMeasures(name) {
if (name !== undefined) {
name = `${name}`;
clearResourceTimings(name = undefined) {
if (name !== undefined) {
name = `${name}`;
}
clearEntriesFromBuffer('resource', name);
}

getEntries() {
return filterBufferMapByNameAndType();
}
clearEntriesFromBuffer('measure', name);
}

function clearResourceTimings(name) {
if (name !== undefined) {
getEntriesByName(name) {
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('name');
}
name = `${name}`;
return filterBufferMapByNameAndType(name, undefined);
}
clearEntriesFromBuffer('resource', name);
}

function getEntries() {
return filterBufferMapByNameAndType();
}
getEntriesByType(type) {
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('type');
}
type = `${type}`;
return filterBufferMapByNameAndType(undefined, type);
}

function getEntriesByName(name) {
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('name');
mark(name, options = kEmptyObject) {
return mark(name, options);
}
name = `${name}`;
return filterBufferMapByNameAndType(name, undefined);
}

function getEntriesByType(type) {
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('type');
measure(name, startOrMeasureOptions, endMark) {
return measure(name, startOrMeasureOptions, endMark);
}
type = `${type}`;
return filterBufferMapByNameAndType(undefined, type);
}

class InternalPerformance extends EventTarget {}
InternalPerformance.prototype.constructor = Performance.prototype.constructor;
ObjectSetPrototypeOf(InternalPerformance.prototype, Performance.prototype);
now() {
validateInternalField(this, kPerformanceBrand, 'Performance');
return now();
}

setResourceTimingBufferSize(maxSize) {
return setResourceTimingBufferSize(maxSize);
}

get timeOrigin() {
validateInternalField(this, kPerformanceBrand, 'Performance');
return getTimeOriginTimestamp();
}

toJSON() {
validateInternalField(this, kPerformanceBrand, 'Performance');
return {
nodeTiming: this.nodeTiming,
timeOrigin: this.timeOrigin,
eventLoopUtilization: this.eventLoopUtilization()
};
}
}

ObjectDefineProperties(Performance.prototype, {
clearMarks: {
__proto__: null,
configurable: true,
clearMarks: kEnumerableProperty,
clearMeasures: kEnumerableProperty,
clearResourceTimings: kEnumerableProperty,
getEntries: kEnumerableProperty,
getEntriesByName: kEnumerableProperty,
getEntriesByType: kEnumerableProperty,
mark: kEnumerableProperty,
measure: kEnumerableProperty,
now: kEnumerableProperty,
timeOrigin: kEnumerableProperty,
toJSON: kEnumerableProperty,
setResourceTimingBufferSize: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
writable: false,
enumerable: false,
value: clearMarks,
},
clearMeasures: {
__proto__: null,
configurable: true,
enumerable: false,
value: clearMeasures,
},
clearResourceTimings: {
__proto__: null,
configurable: true,
enumerable: false,
value: clearResourceTimings,
value: 'Performance',
},

// Node.js specific extensions.
eventLoopUtilization: {
__proto__: null,
configurable: true,
// Node.js specific extensions.
enumerable: false,
writable: true,
value: eventLoopUtilization,
},
getEntries: {
__proto__: null,
configurable: true,
enumerable: false,
value: getEntries,
},
getEntriesByName: {
__proto__: null,
configurable: true,
enumerable: false,
value: getEntriesByName,
},
getEntriesByType: {
__proto__: null,
configurable: true,
enumerable: false,
value: getEntriesByType,
},
mark: {
__proto__: null,
configurable: true,
enumerable: false,
value: mark,
},
measure: {
__proto__: null,
configurable: true,
enumerable: false,
value: measure,
},
nodeTiming: {
__proto__: null,
configurable: true,
// Node.js specific extensions.
enumerable: false,
writable: true,
value: nodeTiming,
},
// In the browser, this function is not public. However, it must be used inside fetch
// which is a Node.js dependency, not a internal module
markResourceTiming: {
__proto__: null,
configurable: true,
// Node.js specific extensions.
enumerable: false,
writable: true,
value: markResourceTiming,
},
now: {
__proto__: null,
configurable: true,
enumerable: false,
value: now,
},
setResourceTimingBufferSize: {
__proto__: null,
configurable: true,
enumerable: false,
value: setResourceTimingBufferSize
},
timerify: {
__proto__: null,
configurable: true,
// Node.js specific extensions.
enumerable: false,
writable: true,
value: timerify,
},
// This would be updated during pre-execution in case
// the process is launched from a snapshot.
// TODO(joyeecheung): we may want to warn about access to
// this during snapshot building.
timeOrigin: {
__proto__: null,
configurable: true,
enumerable: true,
value: getTimeOriginTimestamp(),
},
toJSON: {
__proto__: null,
configurable: true,
enumerable: true,
value: toJSON,
}
});

function refreshTimeOrigin() {
ObjectDefineProperty(Performance.prototype, 'timeOrigin', {
__proto__: null,
configurable: true,
enumerable: true,
value: getTimeOriginTimestamp(),
});
function createPerformance() {
return ReflectConstruct(function Performance() {
initEventTarget(this);
this[kPerformanceBrand] = true;
}, [], Performance);
}

const performance = new InternalPerformance();
const performance = createPerformance();

function dispatchBufferFull(type) {
const event = new Event(type, {
Expand All @@ -246,5 +221,4 @@ setDispatchBufferFull(dispatchBufferFull);
module.exports = {
Performance,
performance,
refreshTimeOrigin
};
2 changes: 1 addition & 1 deletion lib/internal/perf/usertiming.js
Expand Up @@ -94,7 +94,7 @@ class PerformanceMeasure extends InternalPerformanceEntry {
}
}

function mark(name, options = kEmptyObject) {
function mark(name, options) {
const mark = new PerformanceMark(name, options);
enqueue(mark);
bufferUserTiming(mark);
Expand Down
8 changes: 7 additions & 1 deletion lib/internal/perf/utils.js
@@ -1,5 +1,9 @@
'use strict';

const {
Symbol,
} = primordials;

const binding = internalBinding('performance');
const {
milestones,
Expand All @@ -9,6 +13,7 @@ const {
// TODO(joyeecheung): we may want to warn about access to
// this during snapshot building.
let timeOrigin = getTimeOrigin();
const kPerformanceBrand = Symbol('performance');

function now() {
const hr = process.hrtime();
Expand All @@ -29,5 +34,6 @@ function refreshTimeOrigin() {
module.exports = {
now,
getMilestoneTimestamp,
refreshTimeOrigin
refreshTimeOrigin,
kPerformanceBrand,
};

0 comments on commit a94deca

Please sign in to comment.