From 220104f577c3c079454b902a413fae98060595ec Mon Sep 17 00:00:00 2001 From: Leo K Date: Mon, 5 Jul 2021 13:17:11 +0200 Subject: [PATCH] fix: spec conformance for performance API (#10887) --- cli/tests/unit/performance_test.ts | 6 +- extensions/timers/02_performance.js | 313 +++++++++++++++++++++------- extensions/webidl/00_webidl.js | 1 + tools/wpt/expectation.json | 18 +- 4 files changed, 238 insertions(+), 100 deletions(-) diff --git a/cli/tests/unit/performance_test.ts b/cli/tests/unit/performance_test.ts index 229b38bb8c..156841165b 100644 --- a/cli/tests/unit/performance_test.ts +++ b/cli/tests/unit/performance_test.ts @@ -82,12 +82,12 @@ unitTest(function performanceMeasure() { }); unitTest(function performanceIllegalConstructor() { - assertThrows(() => new Performance(), TypeError, "Illegal constructor."); + assertThrows(() => new Performance(), TypeError, "Illegal constructor"); assertEquals(Performance.length, 0); }); unitTest(function performanceEntryIllegalConstructor() { - assertThrows(() => new PerformanceEntry(), TypeError, "Illegal constructor."); + assertThrows(() => new PerformanceEntry(), TypeError, "Illegal constructor"); assertEquals(PerformanceEntry.length, 0); }); @@ -95,6 +95,6 @@ unitTest(function performanceMeasureIllegalConstructor() { assertThrows( () => new PerformanceMeasure(), TypeError, - "Illegal constructor.", + "Illegal constructor", ); }); diff --git a/extensions/timers/02_performance.js b/extensions/timers/02_performance.js index baa1676a29..56f82b42ec 100644 --- a/extensions/timers/02_performance.js +++ b/extensions/timers/02_performance.js @@ -10,6 +10,58 @@ const customInspect = Symbol.for("Deno.customInspect"); let performanceEntries = []; + webidl.converters["PerformanceMarkOptions"] = webidl + .createDictionaryConverter( + "PerformanceMarkOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "startTime", + converter: webidl.converters.DOMHighResTimeStamp, + }, + ], + ); + + webidl.converters["DOMString or DOMHighResTimeStamp"] = (V, opts) => { + if (webidl.type(V) === "Number" && V !== null) { + return webidl.converters.DOMHighResTimeStamp(V, opts); + } + return webidl.converters.DOMString(V, opts); + }; + + webidl.converters["PerformanceMeasureOptions"] = webidl + .createDictionaryConverter( + "PerformanceMeasureOptions", + [ + { + key: "detail", + converter: webidl.converters.any, + }, + { + key: "start", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + { + key: "duration", + converter: webidl.converters.DOMHighResTimeStamp, + }, + { + key: "end", + converter: webidl.converters["DOMString or DOMHighResTimeStamp"], + }, + ], + ); + + webidl.converters["DOMString or PerformanceMeasureOptions"] = (V, opts) => { + if (webidl.type(V) === "Object" && V !== null) { + return webidl.converters["PerformanceMeasureOptions"](V, opts); + } + return webidl.converters.DOMString(V, opts); + }; + function findMostRecent( name, type, @@ -50,26 +102,34 @@ const now = opNow; + const _name = Symbol("[[name]]"); + const _entryType = Symbol("[[entryType]]"); + const _startTime = Symbol("[[startTime]]"); + const _duration = Symbol("[[duration]]"); class PerformanceEntry { - #name = ""; - #entryType = ""; - #startTime = 0; - #duration = 0; + [_name] = ""; + [_entryType] = ""; + [_startTime] = 0; + [_duration] = 0; get name() { - return this.#name; + webidl.assertBranded(this, PerformanceEntry); + return this[_name]; } get entryType() { - return this.#entryType; + webidl.assertBranded(this, PerformanceEntry); + return this[_entryType]; } get startTime() { - return this.#startTime; + webidl.assertBranded(this, PerformanceEntry); + return this[_startTime]; } get duration() { - return this.#duration; + webidl.assertBranded(this, PerformanceEntry); + return this[_duration]; } constructor( @@ -77,41 +137,48 @@ entryType = null, startTime = null, duration = null, - key = null, + key = undefined, ) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } - this.#name = name; - this.#entryType = entryType; - this.#startTime = startTime; - this.#duration = duration; + this[webidl.brand] = webidl.brand; + + this[_name] = name; + this[_entryType] = entryType; + this[_startTime] = startTime; + this[_duration] = duration; } toJSON() { + webidl.assertBranded(this, PerformanceEntry); return { - name: this.#name, - entryType: this.#entryType, - startTime: this.#startTime, - duration: this.#duration, + name: this[_name], + entryType: this[_entryType], + startTime: this[_startTime], + duration: this[_duration], }; } - [customInspect]() { - return `${this.constructor.name} { name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; + [customInspect](inspect) { + return `${this.constructor.name} ${inspect(this.toJSON())}`; } } + webidl.configurePrototype(PerformanceEntry); + const _detail = Symbol("[[detail]]"); class PerformanceMark extends PerformanceEntry { [Symbol.toStringTag] = "PerformanceMark"; - #detail = null; + [_detail] = null; get detail() { - return this.#detail; + webidl.assertBranded(this, PerformanceMark); + return this[_detail]; } get entryType() { + webidl.assertBranded(this, PerformanceMark); return "mark"; } @@ -122,28 +189,28 @@ const prefix = "Failed to construct 'PerformanceMark'"; webidl.requiredArguments(arguments.length, 1, { prefix }); - // ensure options is object-ish, or null-ish - switch (typeof options) { - case "object": // includes null - case "function": - case "undefined": { - break; - } - default: { - throw new TypeError("Invalid options"); - } - } + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); - const { detail = null, startTime = now() } = options ?? {}; + options = webidl.converters.PerformanceMarkOptions(options, { + prefix, + context: "Argument 2", + }); + + const { detail = null, startTime = now() } = options; super(name, "mark", startTime, 0, illegalConstructorKey); + this[webidl.brand] = webidl.brand; if (startTime < 0) { throw new TypeError("startTime cannot be negative"); } - this.#detail = structuredClone(detail); + this[_detail] = structuredClone(detail); } toJSON() { + webidl.assertBranded(this, PerformanceMark); return { name: this.name, entryType: this.entryType, @@ -153,43 +220,45 @@ }; } - [customInspect]() { - return this.detail - ? `${this.constructor.name} {\n detail: ${ - JSON.stringify(this.detail, null, 2) - },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}` - : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; + [customInspect](inspect) { + return `${this.constructor.name} ${inspect(this.toJSON())}`; } } + webidl.configurePrototype(PerformanceMark); class PerformanceMeasure extends PerformanceEntry { [Symbol.toStringTag] = "PerformanceMeasure"; - #detail = null; + [_detail] = null; get detail() { - return this.#detail; + webidl.assertBranded(this, PerformanceMeasure); + return this[_detail]; } get entryType() { + webidl.assertBranded(this, PerformanceMeasure); return "measure"; } constructor( - name, - startTime, - duration, + name = null, + startTime = null, + duration = null, detail = null, - key, + key = undefined, ) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); + if (key !== illegalConstructorKey) { + webidl.illegalConstructor(); } - super(name, "measure", startTime, duration, illegalConstructorKey); - this.#detail = structuredClone(detail); + + super(name, "measure", startTime, duration, key); + this[webidl.brand] = webidl.brand; + this[_detail] = structuredClone(detail); } toJSON() { + webidl.assertBranded(this, PerformanceMeasure); return { name: this.name, entryType: this.entryType, @@ -199,70 +268,117 @@ }; } - [customInspect]() { - return this.detail - ? `${this.constructor.name} {\n detail: ${ - JSON.stringify(this.detail, null, 2) - },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}` - : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`; + [customInspect](inspect) { + return `${this.constructor.name} ${inspect(this.toJSON())}`; } } + webidl.configurePrototype(PerformanceMeasure); class Performance { - constructor(key = null) { - if (key != illegalConstructorKey) { - throw new TypeError("Illegal constructor."); - } + constructor() { + webidl.illegalConstructor(); } - clearMarks(markName) { - if (markName == null) { - performanceEntries = performanceEntries.filter( - (entry) => entry.entryType !== "mark", - ); - } else { + clearMarks(markName = undefined) { + webidl.assertBranded(this, Performance); + if (markName !== undefined) { + markName = webidl.converters.DOMString(markName, { + prefix: "Failed to execute 'clearMarks' on 'Performance'", + context: "Argument 1", + }); + performanceEntries = performanceEntries.filter( (entry) => !(entry.name === markName && entry.entryType === "mark"), ); + } else { + performanceEntries = performanceEntries.filter( + (entry) => entry.entryType !== "mark", + ); } } - clearMeasures(measureName) { - if (measureName == null) { - performanceEntries = performanceEntries.filter( - (entry) => entry.entryType !== "measure", - ); - } else { + clearMeasures(measureName = undefined) { + webidl.assertBranded(this, Performance); + if (measureName !== undefined) { + measureName = webidl.converters.DOMString(measureName, { + prefix: "Failed to execute 'clearMeasures' on 'Performance'", + context: "Argument 1", + }); + performanceEntries = performanceEntries.filter( (entry) => !(entry.name === measureName && entry.entryType === "measure"), ); + } else { + performanceEntries = performanceEntries.filter( + (entry) => entry.entryType !== "measure", + ); } } getEntries() { + webidl.assertBranded(this, Performance); return filterByNameType(); } getEntriesByName( name, - type, + type = undefined, ) { + webidl.assertBranded(this, Performance); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + name = webidl.converters.DOMString(name, { + prefix, + context: "Argument 1", + }); + + if (type !== undefined) { + type = webidl.converters.DOMString(type, { + prefix, + context: "Argument 2", + }); + } + return filterByNameType(name, type); } getEntriesByType(type) { + webidl.assertBranded(this, Performance); + const prefix = "Failed to execute 'getEntriesByName' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + type = webidl.converters.DOMString(type, { + prefix, + context: "Argument 1", + }); + return filterByNameType(undefined, type); } mark( markName, - options = {}, + markOptions = {}, ) { + webidl.assertBranded(this, Performance); + const prefix = "Failed to execute 'mark' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + markName = webidl.converters.DOMString(markName, { + prefix, + context: "Argument 1", + }); + + markOptions = webidl.converters.PerformanceMarkOptions(markOptions, { + prefix, + context: "Argument 2", + }); + // 3.1.1.1 If the global object is a Window object and markName uses the // same name as a read only attribute in the PerformanceTiming interface, // throw a SyntaxError. - not implemented - const entry = new PerformanceMark(markName, options); + const entry = new PerformanceMark(markName, markOptions); // 3.1.1.7 Queue entry - not implemented performanceEntries.push(entry); return entry; @@ -271,8 +387,30 @@ measure( measureName, startOrMeasureOptions = {}, - endMark, + endMark = undefined, ) { + webidl.assertBranded(this, Performance); + const prefix = "Failed to execute 'measure' on 'Performance'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + + measureName = webidl.converters.DOMString(measureName, { + prefix, + context: "Argument 1", + }); + + startOrMeasureOptions = webidl.converters + ["DOMString or PerformanceMeasureOptions"](startOrMeasureOptions, { + prefix, + context: "Argument 2", + }); + + if (endMark !== undefined) { + endMark = webidl.converters.DOMString(endMark, { + prefix, + context: "Argument 3", + }); + } + if ( startOrMeasureOptions && typeof startOrMeasureOptions === "object" && Object.keys(startOrMeasureOptions).length > 0 @@ -350,17 +488,30 @@ } now() { + webidl.assertBranded(this, Performance); return now(); } - } - const performance = new Performance(illegalConstructorKey); + toJSON() { + webidl.assertBranded(this, Performance); + return {}; + } + + [customInspect](inspect) { + return `${this.constructor.name} ${inspect(this.toJSON())}`; + } + + get [Symbol.toStringTag]() { + return "Performance"; + } + } + webidl.configurePrototype(Performance); window.__bootstrap.performance = { PerformanceEntry, PerformanceMark, PerformanceMeasure, Performance, - performance, + performance: webidl.createBranded(Performance), }; })(this); diff --git a/extensions/webidl/00_webidl.js b/extensions/webidl/00_webidl.js index 07c9f8e31f..16a6ab4a90 100644 --- a/extensions/webidl/00_webidl.js +++ b/extensions/webidl/00_webidl.js @@ -612,6 +612,7 @@ }; converters.DOMTimeStamp = converters["unsigned long long"]; + converters.DOMHighResTimeStamp = converters["double"]; converters.Function = convertCallbackFunction; diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 70876c164b..5cc1f1b66c 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -283,12 +283,8 @@ "idlharness.any.html": [ "Performance interface: existence and properties of interface object", "Performance interface: existence and properties of interface prototype object", - "Performance interface: operation now()", "Performance interface: attribute timeOrigin", - "Performance interface: operation toJSON()", - "Stringification of performance", "Performance interface: performance must inherit property \"timeOrigin\" with the proper type", - "Performance interface: performance must inherit property \"toJSON()\" with the proper type", "Performance interface: default toJSON operation on performance", "Window interface: attribute performance" ], @@ -507,17 +503,7 @@ "clear_one_mark.any.html": true, "clear_one_measure.any.html": true, "entry_type.any.html": true, - "idlharness.any.html": [ - "PerformanceMark interface: attribute detail", - "PerformanceMeasure interface object length", - "PerformanceMeasure interface: attribute detail", - "Performance interface: operation mark(DOMString, optional PerformanceMarkOptions)", - "Performance interface: operation clearMarks(optional DOMString)", - "Performance interface: operation measure(DOMString, optional (DOMString or PerformanceMeasureOptions), optional DOMString)", - "Performance interface: operation clearMeasures(optional DOMString)", - "Performance interface: calling mark(DOMString, optional PerformanceMarkOptions) on performance with too few arguments must throw TypeError", - "Performance interface: calling measure(DOMString, optional (DOMString or PerformanceMeasureOptions), optional DOMString) on performance with too few arguments must throw TypeError" - ], + "idlharness.any.html": true, "mark-entry-constructor.any.html": true, "mark-errors.any.html": true, "mark-l3.any.html": false, @@ -1303,4 +1289,4 @@ "eventhandlers.any.html?wss": false, "referrer.any.html": true } -} \ No newline at end of file +}