diff --git a/doc/api/perf_hooks.md b/doc/api/perf_hooks.md index 157c0e014e042c..e4179a4c5fde7a 100644 --- a/doc/api/perf_hooks.md +++ b/doc/api/perf_hooks.md @@ -49,6 +49,11 @@ Node.js instance. It is similar to [`window.performance`][] in browsers. * `name` {string} @@ -60,6 +65,11 @@ Performance Timeline. If `name` is provided, removes only the named mark. * `name` {string} @@ -73,6 +83,11 @@ Performance Timeline. If `name` is provided, removes only the named measure. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This method must be called with the `performance` object as + the receiver. --> * `name` {string} @@ -147,6 +162,11 @@ are not guaranteed to reflect any correct state of the event loop. * Returns: {PerformanceEntry\[]} @@ -160,6 +180,11 @@ performance entries of certain types or that have certain names, see * `name` {string} @@ -175,6 +200,11 @@ equal to `name`, and optionally, whose `performanceEntry.entryType` is equal to * `type` {string} @@ -189,6 +219,10 @@ is equal to `type`. * Returns: {number} @@ -316,6 +359,11 @@ the start of the current `node` process. Sets the global performance resource timing buffer size to the specified number @@ -393,6 +441,11 @@ invoked. An object which is JSON representation of the `performance` object. It @@ -416,20 +469,17 @@ more entries to be added to the performance timeline buffer. added: v8.5.0 --> -### `performanceEntry.detail` - - - -* {any} - -Additional detail specific to the `entryType`. +The constructor of this class is not exposed to users directly. ### `performanceEntry.duration` * {number} @@ -441,6 +491,11 @@ be meaningful for all Performance Entry types. * {string} @@ -455,7 +510,123 @@ The type of the performance entry. It may be one of: * `'http2'` (Node.js only) * `'http'` (Node.js only) -### `performanceEntry.flags` +### `performanceEntry.name` + + + +* {string} + +The name of the performance entry. + +### `performanceEntry.startTime` + + + +* {number} + +The high resolution millisecond timestamp marking the starting time of the +Performance Entry. + +## Class: `PerformanceMark` + + + +* Extends: {PerformanceEntry} + +Exposes marks created via the `Performance.mark()` method. + +### `performanceMark.detail` + + + +* {any} + +Additional detail specified when creating with `Performance.mark()` method. + +## Class: `PerformanceMeasure` + + + +* Extends: {PerformanceEntry} + +Exposes measures created via the `Performance.measure()` method. + +The constructor of this class is not exposed to users directly. + +### `performanceMeasure.detail` + + + +* {any} + +Additional detail specified when creating with `Performance.measure()` method. + +## Class: `PerformanceNodeEntry` + + + +* Extends: {PerformanceEntry} + +_This class is an extension by Node.js. It is not available in Web browsers._ + +Provides detailed Node.js timing data. + +The constructor of this class is not exposed to users directly. + +### `performanceNodeEntry.detail` + + + +* {any} + +Additional detail specific to the `entryType`. + +### `performanceNodeEntry.flags` -* {number} +> Stability: 0 - Deprecated: Use `performanceNodeEntry.detail` instead. -_This property is an extension by Node.js. It is not available in Web browsers._ +* {number} When `performanceEntry.entryType` is equal to `'gc'`, the `performance.flags` property contains additional information about garbage collection operation. @@ -484,17 +655,7 @@ The value may be one of: * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_ALL_EXTERNAL_MEMORY` * `perf_hooks.constants.NODE_PERFORMANCE_GC_FLAGS_SCHEDULE_IDLE` -### `performanceEntry.name` - - - -* {string} - -The name of the performance entry. - -### `performanceEntry.kind` +### `performanceNodeEntry.kind` -* {number} +> Stability: 0 - Deprecated: Use `performanceNodeEntry.detail` instead. -_This property is an extension by Node.js. It is not available in Web browsers._ +* {number} When `performanceEntry.entryType` is equal to `'gc'`, the `performance.kind` property identifies the type of garbage collection operation that occurred. @@ -518,21 +679,10 @@ The value may be one of: * `perf_hooks.constants.NODE_PERFORMANCE_GC_INCREMENTAL` * `perf_hooks.constants.NODE_PERFORMANCE_GC_WEAKCB` -### `performanceEntry.startTime` - - - -* {number} - -The high resolution millisecond timestamp marking the starting time of the -Performance Entry. - ### Garbage Collection ('gc') Details -When `performanceEntry.type` is equal to `'gc'`, the `performanceEntry.detail` -property will be an {Object} with two properties: +When `performanceEntry.type` is equal to `'gc'`, the +`performanceNodeEntry.detail` property will be an {Object} with two properties: * `kind` {number} One of: * `perf_hooks.constants.NODE_PERFORMANCE_GC_MAJOR` @@ -550,8 +700,9 @@ property will be an {Object} with two properties: ### HTTP ('http') Details -When `performanceEntry.type` is equal to `'http'`, the `performanceEntry.detail` -property will be an {Object} containing additional information. +When `performanceEntry.type` is equal to `'http'`, the +`performanceNodeEntry.detail` property will be an {Object} containing +additional information. If `performanceEntry.name` is equal to `HttpClient`, the `detail` will contain the following properties: `req`, `res`. And the `req` property @@ -569,7 +720,7 @@ diagnostic purposes, not left turned on in production by default. ### HTTP/2 ('http2') Details When `performanceEntry.type` is equal to `'http2'`, the -`performanceEntry.detail` property will be an {Object} containing +`performanceNodeEntry.detail` property will be an {Object} containing additional performance information. If `performanceEntry.name` is equal to `Http2Stream`, the `detail` @@ -610,13 +761,13 @@ contain the following properties: ### Timerify ('function') Details When `performanceEntry.type` is equal to `'function'`, the -`performanceEntry.detail` property will be an {Array} listing +`performanceNodeEntry.detail` property will be an {Array} listing the input arguments to the timed function. ### Net ('net') Details When `performanceEntry.type` is equal to `'net'`, the -`performanceEntry.detail` property will be an {Object} containing +`performanceNodeEntry.detail` property will be an {Object} containing additional information. If `performanceEntry.name` is equal to `connect`, the `detail` @@ -625,7 +776,7 @@ will contain the following properties: `host`, `port`. ### DNS ('dns') Details When `performanceEntry.type` is equal to `'dns'`, the -`performanceEntry.detail` property will be an {Object} containing +`performanceNodeEntry.detail` property will be an {Object} containing additional information. If `performanceEntry.name` is equal to `lookup`, the `detail` @@ -758,6 +909,11 @@ The constructor of this class is not exposed to users directly. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -772,6 +928,11 @@ will always return 0. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -785,6 +946,11 @@ of the fetch which initiates the redirect. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -798,6 +964,11 @@ receiving the last byte of the response of the last redirect. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -811,6 +982,11 @@ to fetch the resource. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -824,6 +1000,11 @@ the domain name lookup for the resource. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -837,6 +1018,11 @@ after the Node.js finished the domain name lookup for the resource. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -851,6 +1037,11 @@ the resource. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -865,6 +1056,11 @@ the resource. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -878,6 +1074,11 @@ before Node.js starts the handshake process to secure the current connection. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -891,6 +1092,11 @@ before Node.js receives the first byte of the response from the server. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -905,6 +1111,11 @@ the transport connection is closed, whichever comes first. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -918,6 +1129,11 @@ includes the response header fields plus the response payload body. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -932,6 +1148,11 @@ content-codings. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This property getter must be called with the + `PerformanceResourceTiming` object as the receiver. --> * {number} @@ -946,12 +1167,17 @@ content-codings. added: - v18.2.0 - v16.17.0 +changes: + - version: REPLACEME + pr-url: https://github.com/nodejs/node/pull/44483 + description: This method must be called with the + `PerformanceResourceTiming` object as the receiver. --> Returns a `object` that is the JSON representation of the `PerformanceResourceTiming` object -## Class: `perf_hooks.PerformanceObserver` +## Class: `PerformanceObserver` ### `new PerformanceObserver(callback)` diff --git a/lib/internal/bootstrap/browser.js b/lib/internal/bootstrap/browser.js index d0c01ca2a512be..92d57688e21115 100644 --- a/lib/internal/bootstrap/browser.js +++ b/lib/internal/bootstrap/browser.js @@ -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 = @@ -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, }); } diff --git a/lib/internal/perf/observe.js b/lib/internal/perf/observe.js index f84bb69bbddae2..2eba4b6c4e2288 100644 --- a/lib/internal/perf/observe.js +++ b/lib/internal/perf/observe.js @@ -16,9 +16,11 @@ const { ObjectDefineProperties, ObjectFreeze, ObjectKeys, + ReflectConstruct, SafeMap, SafeSet, Symbol, + SymbolToStringTag, } = primordials; const { @@ -36,12 +38,13 @@ const { } = internalBinding('performance'); const { - InternalPerformanceEntry, isPerformanceEntry, + createPerformanceNodeEntry, } = require('internal/perf/performance_entry'); const { codes: { + ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_VALUE, ERR_INVALID_ARG_TYPE, ERR_MISSING_ARGS, @@ -51,6 +54,7 @@ const { const { validateFunction, validateObject, + validateInternalField, } = require('internal/validators'); const { @@ -58,6 +62,7 @@ const { deprecate, lazyDOMException, kEmptyObject, + kEnumerableProperty, } = require('internal/util'); const { @@ -68,6 +73,7 @@ const { inspect } = require('util'); const { now } = require('internal/perf/utils'); +const kBuffer = Symbol('kBuffer'); const kDispatch = Symbol('kDispatch'); const kMaybeBuffer = Symbol('kMaybeBuffer'); const kDeprecatedFields = Symbol('kDeprecatedFields'); @@ -167,34 +173,39 @@ function maybeIncrementObserverCount(type) { } class PerformanceObserverEntryList { - #buffer = []; - - constructor(entries) { - this.#buffer = ArrayPrototypeSort(entries, (first, second) => { - return first.startTime - second.startTime; - }); + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); } getEntries() { - return ArrayPrototypeSlice(this.#buffer); + validateInternalField(this, kBuffer, 'PerformanceObserverEntryList'); + return ArrayPrototypeSlice(this[kBuffer]); } getEntriesByType(type) { + validateInternalField(this, kBuffer, 'PerformanceObserverEntryList'); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('type'); + } type = `${type}`; return ArrayPrototypeFilter( - this.#buffer, + this[kBuffer], (entry) => entry.entryType === type); } - getEntriesByName(name, type) { + getEntriesByName(name, type = undefined) { + validateInternalField(this, kBuffer, 'PerformanceObserverEntryList'); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('name'); + } name = `${name}`; if (type != null /** not nullish */) { return ArrayPrototypeFilter( - this.#buffer, + this[kBuffer], (entry) => entry.name === name && entry.entryType === type); } return ArrayPrototypeFilter( - this.#buffer, + this[kBuffer], (entry) => entry.name === name); } @@ -206,9 +217,29 @@ class PerformanceObserverEntryList { depth: options.depth == null ? null : options.depth - 1 }; - return `PerformanceObserverEntryList ${inspect(this.#buffer, opts)}`; + return `PerformanceObserverEntryList ${inspect(this[kBuffer], opts)}`; } } +ObjectDefineProperties(PerformanceObserverEntryList.prototype, { + getEntries: kEnumerableProperty, + getEntriesByType: kEnumerableProperty, + getEntriesByName: kEnumerableProperty, + [SymbolToStringTag]: { + __proto__: null, + writable: false, + enumerable: false, + configurable: true, + value: 'PerformanceObserverEntryList', + }, +}); + +function createPerformanceObserverEntryList(entries) { + return ReflectConstruct(function PerformanceObserverEntryList() { + this[kBuffer] = ArrayPrototypeSort(entries, (first, second) => { + return first.startTime - second.startTime; + }); + }, [], PerformanceObserverEntryList); +} class PerformanceObserver { #buffer = []; @@ -319,7 +350,7 @@ class PerformanceObserver { } [kDispatch]() { - this.#callback(new PerformanceObserverEntryList(this.takeRecords()), + this.#callback(createPerformanceObserverEntryList(this.takeRecords()), this); } @@ -339,6 +370,18 @@ class PerformanceObserver { }, opts)}`; } } +ObjectDefineProperties(PerformanceObserver.prototype, { + observe: kEnumerableProperty, + disconnect: kEnumerableProperty, + takeRecords: kEnumerableProperty, + [SymbolToStringTag]: { + __proto__: null, + writable: false, + enumerable: false, + configurable: true, + value: 'PerformanceObserver', + }, +}); /** * https://www.w3.org/TR/performance-timeline/#dfn-queue-a-performanceentry @@ -485,7 +528,7 @@ function filterBufferMapByNameAndType(name, type) { function observerCallback(name, type, startTime, duration, details) { const entry = - new InternalPerformanceEntry( + createPerformanceNodeEntry( name, type, startTime, @@ -542,7 +585,7 @@ function stopPerf(target, key, context = {}) { return; } const startTime = ctx.startTime; - const entry = new InternalPerformanceEntry( + const entry = createPerformanceNodeEntry( ctx.name, ctx.type, startTime, diff --git a/lib/internal/perf/performance.js b/lib/internal/perf/performance.js index 610523853f86f5..0053ec23a7afcb 100644 --- a/lib/internal/perf/performance.js +++ b/lib/internal/perf/performance.js @@ -1,9 +1,10 @@ 'use strict'; const { - ObjectDefineProperty, ObjectDefineProperties, - ObjectSetPrototypeOf, + ReflectConstruct, + Symbol, + SymbolToStringTag, } = primordials; const { @@ -17,6 +18,8 @@ const { EventTarget, Event, kTrustEvent, + initEventTarget, + defineEventHandler, } = require('internal/event_target'); const { now } = require('internal/perf/utils'); @@ -38,13 +41,16 @@ 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 } = internalBinding('performance'); +const kPerformanceBrand = Symbol('performance'); + class Performance extends EventTarget { constructor() { throw new ERR_ILLEGAL_CONSTRUCTOR(); @@ -63,121 +69,135 @@ class Performance extends EventTarget { timeOrigin: this.timeOrigin, }, opts)}`; } -} -function toJSON() { - return { - nodeTiming: this.nodeTiming, - timeOrigin: this.timeOrigin, - eventLoopUtilization: this.eventLoopUtilization() - }; -} + clearMarks(name = undefined) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (name !== undefined) { + name = `${name}`; + } + clearMarkTimings(name); + clearEntriesFromBuffer('mark', name); + } -function clearMarks(name) { - if (name !== undefined) { - name = `${name}`; + clearMeasures(name = undefined) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (name !== undefined) { + name = `${name}`; + } + clearEntriesFromBuffer('measure', name); } - clearMarkTimings(name); - clearEntriesFromBuffer('mark', name); -} -function clearMeasures(name) { - if (name !== undefined) { - name = `${name}`; + clearResourceTimings(name = undefined) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (name !== undefined) { + name = `${name}`; + } + clearEntriesFromBuffer('resource', name); + } + + getEntries() { + validateInternalField(this, kPerformanceBrand, 'Performance'); + return filterBufferMapByNameAndType(); } - clearEntriesFromBuffer('measure', name); -} -function clearResourceTimings(name) { - if (name !== undefined) { + getEntriesByName(name) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + 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) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + 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) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('name'); + } + 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 = kEmptyObject, endMark = undefined) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('name'); + } + 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) { + validateInternalField(this, kPerformanceBrand, 'Performance'); + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('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 @@ -185,55 +205,30 @@ ObjectDefineProperties(Performance.prototype, { 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, - } }); +defineEventHandler(Performance.prototype, 'resourcetimingbufferfull'); -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, { @@ -246,5 +241,4 @@ setDispatchBufferFull(dispatchBufferFull); module.exports = { Performance, performance, - refreshTimeOrigin }; diff --git a/lib/internal/perf/performance_entry.js b/lib/internal/perf/performance_entry.js index 759f1315d5b6c2..f487e575b42ea0 100644 --- a/lib/internal/perf/performance_entry.js +++ b/lib/internal/perf/performance_entry.js @@ -1,7 +1,8 @@ 'use strict'; const { - ObjectSetPrototypeOf, + ObjectDefineProperties, + ReflectConstruct, Symbol, } = primordials; @@ -13,15 +14,17 @@ const { const { customInspectSymbol: kInspect, + kEnumerableProperty, } = require('internal/util'); +const { validateInternalField } = require('internal/validators'); const { inspect } = require('util'); -const kName = Symbol('kName'); -const kType = Symbol('kType'); -const kStart = Symbol('kStart'); -const kDuration = Symbol('kDuration'); -const kDetail = Symbol('kDetail'); +const kName = Symbol('PerformanceEntry.Name'); +const kEntryType = Symbol('PerformanceEntry.EntryType'); +const kStartTime = Symbol('PerformanceEntry.StartTime'); +const kDuration = Symbol('PerformanceEntry.Duration'); +const kDetail = Symbol('NodePerformanceEntry.Detail'); function isPerformanceEntry(obj) { return obj?.[kName] !== undefined; @@ -32,15 +35,25 @@ class PerformanceEntry { throw new ERR_ILLEGAL_CONSTRUCTOR(); } - get name() { return this[kName]; } - - get entryType() { return this[kType]; } + get name() { + validateInternalField(this, kName, 'PerformanceEntry'); + return this[kName]; + } - get startTime() { return this[kStart]; } + get entryType() { + validateInternalField(this, kEntryType, 'PerformanceEntry'); + return this[kEntryType]; + } - get duration() { return this[kDuration]; } + get startTime() { + validateInternalField(this, kStartTime, 'PerformanceEntry'); + return this[kStartTime]; + } - get detail() { return this[kDetail]; } + get duration() { + validateInternalField(this, kDuration, 'PerformanceEntry'); + return this[kDuration]; + } [kInspect](depth, options) { if (depth < 0) return this; @@ -54,33 +67,69 @@ class PerformanceEntry { } toJSON() { + validateInternalField(this, kName, 'PerformanceEntry'); return { - name: this.name, - entryType: this.entryType, - startTime: this.startTime, - duration: this.duration, - detail: this.detail, + name: this[kName], + entryType: this[kEntryType], + startTime: this[kStartTime], + duration: this[kDuration], }; } } +ObjectDefineProperties(PerformanceEntry.prototype, { + name: kEnumerableProperty, + entryType: kEnumerableProperty, + startTime: kEnumerableProperty, + duration: kEnumerableProperty, + toJSON: kEnumerableProperty, +}); + +function initPerformanceEntry(entry, name, type, start, duration) { + entry[kName] = name; + entry[kEntryType] = type; + entry[kStartTime] = start; + entry[kDuration] = duration; +} -class InternalPerformanceEntry { - constructor(name, type, start, duration, detail) { - this[kName] = name; - this[kType] = type; - this[kStart] = start; - this[kDuration] = duration; - this[kDetail] = detail; +function createPerformanceEntry(name, type, start, duration) { + return ReflectConstruct(function PerformanceEntry() { + initPerformanceEntry(this, name, type, start, duration); + }, [], PerformanceEntry); +} + +/** + * Node.js specific extension to PerformanceEntry. + */ +class PerformanceNodeEntry extends PerformanceEntry { + get detail() { + validateInternalField(this, kDetail, 'NodePerformanceEntry'); + return this[kDetail]; + } + + toJSON() { + validateInternalField(this, kName, 'PerformanceEntry'); + return { + name: this[kName], + entryType: this[kEntryType], + startTime: this[kStartTime], + duration: this[kDuration], + detail: this[kDetail], + }; } } -InternalPerformanceEntry.prototype.constructor = PerformanceEntry; -ObjectSetPrototypeOf( - InternalPerformanceEntry.prototype, - PerformanceEntry.prototype); +function createPerformanceNodeEntry(name, type, start, duration, detail) { + return ReflectConstruct(function PerformanceNodeEntry() { + initPerformanceEntry(this, name, type, start, duration); + this[kDetail] = detail; + }, [], PerformanceNodeEntry); +} module.exports = { - InternalPerformanceEntry, + initPerformanceEntry, + createPerformanceEntry, PerformanceEntry, isPerformanceEntry, + PerformanceNodeEntry, + createPerformanceNodeEntry, }; diff --git a/lib/internal/perf/resource_timing.js b/lib/internal/perf/resource_timing.js index 33e0d9e5ce936b..8c8dd699b440da 100644 --- a/lib/internal/perf/resource_timing.js +++ b/lib/internal/perf/resource_timing.js @@ -1,33 +1,32 @@ 'use strict'; // https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming -const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); -const { SymbolToStringTag } = primordials; +const { + ObjectDefineProperties, + ObjectSetPrototypeOf, + ReflectConstruct, + Symbol, + SymbolToStringTag, +} = primordials; +const { initPerformanceEntry, PerformanceEntry } = require('internal/perf/performance_entry'); const assert = require('internal/assert'); const { enqueue, bufferResourceTiming } = require('internal/perf/observe'); -const { Symbol, ObjectSetPrototypeOf } = primordials; - -const kCacheMode = Symbol('kCacheMode'); -const kRequestedUrl = Symbol('kRequestedUrl'); -const kTimingInfo = Symbol('kTimingInfo'); -const kInitiatorType = Symbol('kInitiatorType'); - const { codes: { ERR_ILLEGAL_CONSTRUCTOR, } } = require('internal/errors'); +const { validateInternalField } = require('internal/validators'); +const { kEnumerableProperty } = require('internal/util'); -class InternalPerformanceResourceTiming extends InternalPerformanceEntry { - constructor(requestedUrl, initiatorType, timingInfo, cacheMode = '') { - super(requestedUrl, 'resource'); - this[kInitiatorType] = initiatorType; - this[kRequestedUrl] = requestedUrl; - // https://fetch.spec.whatwg.org/#fetch-timing-info - // This class is using timingInfo assuming it's already validated. - // The spec doesn't say to validate it in the class construction. - this[kTimingInfo] = timingInfo; - this[kCacheMode] = cacheMode; +const kCacheMode = Symbol('kCacheMode'); +const kRequestedUrl = Symbol('kRequestedUrl'); +const kTimingInfo = Symbol('kTimingInfo'); +const kInitiatorType = Symbol('kInitiatorType'); + +class PerformanceResourceTiming extends PerformanceEntry { + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); } get [SymbolToStringTag]() { @@ -35,84 +34,104 @@ class InternalPerformanceResourceTiming extends InternalPerformanceEntry { } get name() { + validateInternalField(this, kRequestedUrl, 'PerformanceResourceTiming'); return this[kRequestedUrl]; } get startTime() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].startTime; } get duration() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].endTime - this[kTimingInfo].startTime; } get initiatorType() { + validateInternalField(this, kInitiatorType, 'PerformanceResourceTiming'); return this[kInitiatorType]; } get workerStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalServiceWorkerStartTime; } get redirectStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].redirectStartTime; } get redirectEnd() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].redirectEndTime; } get fetchStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].postRedirectStartTime; } get domainLookupStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupStartTime; } get domainLookupEnd() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalConnectionTimingInfo?.domainLookupEndTime; } get connectStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalConnectionTimingInfo?.connectionStartTime; } get connectEnd() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalConnectionTimingInfo?.connectionEndTime; } get secureConnectionStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo] .finalConnectionTimingInfo?.secureConnectionStartTime; } get nextHopProtocol() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo] .finalConnectionTimingInfo?.ALPNNegotiatedProtocol; } get requestStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalNetworkRequestStartTime; } get responseStart() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].finalNetworkResponseStartTime; } get responseEnd() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].endTime; } get encodedBodySize() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].encodedBodySize; } get decodedBodySize() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); return this[kTimingInfo].decodedBodySize; } get transferSize() { + validateInternalField(this, kTimingInfo, 'PerformanceResourceTiming'); if (this[kCacheMode] === 'local') return 0; if (this[kCacheMode] === 'validated') return 300; @@ -120,6 +139,7 @@ class InternalPerformanceResourceTiming extends InternalPerformanceEntry { } toJSON() { + validateInternalField(this, kInitiatorType, 'PerformanceResourceTiming'); return { name: this.name, entryType: this.entryType, @@ -146,10 +166,38 @@ class InternalPerformanceResourceTiming extends InternalPerformanceEntry { } } -class PerformanceResourceTiming extends InternalPerformanceResourceTiming { - constructor() { - throw new ERR_ILLEGAL_CONSTRUCTOR(); - } +ObjectDefineProperties(PerformanceResourceTiming.prototype, { + initiatorType: kEnumerableProperty, + nextHopProtocol: kEnumerableProperty, + workerStart: kEnumerableProperty, + redirectStart: kEnumerableProperty, + redirectEnd: kEnumerableProperty, + fetchStart: kEnumerableProperty, + domainLookupStart: kEnumerableProperty, + domainLookupEnd: kEnumerableProperty, + connectStart: kEnumerableProperty, + connectEnd: kEnumerableProperty, + secureConnectionStart: kEnumerableProperty, + requestStart: kEnumerableProperty, + responseStart: kEnumerableProperty, + responseEnd: kEnumerableProperty, + transferSize: kEnumerableProperty, + encodedBodySize: kEnumerableProperty, + decodedBodySize: kEnumerableProperty, + toJSON: kEnumerableProperty, +}); + +function createPerformanceResourceTiming(requestedUrl, initiatorType, timingInfo, cacheMode = '') { + return ReflectConstruct(function PerformanceResourceTiming() { + initPerformanceEntry(this, requestedUrl, 'resource'); + this[kInitiatorType] = initiatorType; + this[kRequestedUrl] = requestedUrl; + // https://fetch.spec.whatwg.org/#fetch-timing-info + // This class is using timingInfo assuming it's already validated. + // The spec doesn't say to validate it in the class construction. + this[kTimingInfo] = timingInfo; + this[kCacheMode] = cacheMode; + }, [], PerformanceResourceTiming); } // https://w3c.github.io/resource-timing/#dfn-mark-resource-timing @@ -165,7 +213,7 @@ function markResourceTiming( cacheMode === '' || cacheMode === 'local', 'cache must be an empty string or \'local\'', ); - const resource = new InternalPerformanceResourceTiming( + const resource = createPerformanceResourceTiming( requestedUrl, initiatorType, timingInfo, diff --git a/lib/internal/perf/timerify.js b/lib/internal/perf/timerify.js index e4c97e70353977..5dce317be76303 100644 --- a/lib/internal/perf/timerify.js +++ b/lib/internal/perf/timerify.js @@ -8,7 +8,7 @@ const { ReflectConstruct, } = primordials; -const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); +const { createPerformanceNodeEntry } = require('internal/perf/performance_entry'); const { now } = require('internal/perf/utils'); const { @@ -39,7 +39,7 @@ function processComplete(name, start, args, histogram) { if (histogram !== undefined) histogram.record(MathCeil(duration * 1e6)); const entry = - new InternalPerformanceEntry( + createPerformanceNodeEntry( name, 'function', start, diff --git a/lib/internal/perf/usertiming.js b/lib/internal/perf/usertiming.js index c61be708829db1..4544c507752036 100644 --- a/lib/internal/perf/usertiming.js +++ b/lib/internal/perf/usertiming.js @@ -1,13 +1,17 @@ 'use strict'; const { + ObjectDefineProperties, + ObjectSetPrototypeOf, SafeMap, SafeSet, SafeArrayIterator, + Symbol, SymbolToStringTag, + ReflectConstruct, } = primordials; -const { InternalPerformanceEntry } = require('internal/perf/performance_entry'); +const { initPerformanceEntry, PerformanceEntry } = require('internal/perf/performance_entry'); const { now } = require('internal/perf/utils'); const { enqueue, bufferUserTiming } = require('internal/perf/observe'); const nodeTiming = require('internal/perf/nodetiming'); @@ -16,11 +20,14 @@ const { validateNumber, validateObject, validateString, + validateInternalField, } = require('internal/validators'); const { codes: { + ERR_ILLEGAL_CONSTRUCTOR, ERR_INVALID_ARG_VALUE, + ERR_MISSING_ARGS, ERR_PERFORMANCE_INVALID_TIMESTAMP, ERR_PERFORMANCE_MEASURE_INVALID_OPTIONS, }, @@ -30,8 +37,11 @@ const { structuredClone } = require('internal/structured_clone'); const { kEmptyObject, lazyDOMException, + kEnumerableProperty, } = require('internal/util'); +const kDetail = Symbol('kDetail'); + const markTimings = new SafeMap(); const nodeTimingReadOnlyAttributes = new SafeSet(new SafeArrayIterator([ @@ -59,12 +69,15 @@ function getMark(name) { return ts; } -class PerformanceMark extends InternalPerformanceEntry { - constructor(name, options) { +class PerformanceMark { + constructor(name, options = kEmptyObject) { + if (arguments.length === 0) { + throw new ERR_MISSING_ARGS('name'); + } name = `${name}`; + options ??= kEmptyObject; if (nodeTimingReadOnlyAttributes.has(name)) throw new ERR_INVALID_ARG_VALUE('name', name); - options ??= kEmptyObject; validateObject(options, 'options'); const startTime = options.startTime ?? now(); validateNumber(startTime, 'startTime'); @@ -76,25 +89,61 @@ class PerformanceMark extends InternalPerformanceEntry { detail = detail != null ? structuredClone(detail) : null; - super(name, 'mark', startTime, 0, detail); + initPerformanceEntry(this, name, 'mark', startTime, 0); + this[kDetail] = detail; + } + + get detail() { + validateInternalField(this, kDetail, 'PerformanceMark'); + return this[kDetail]; } get [SymbolToStringTag]() { return 'PerformanceMark'; } + + toJSON() { + return { + name: this.name, + entryType: this.entryType, + startTime: this.startTime, + duration: this.duration, + detail: this[kDetail], + }; + } } +ObjectSetPrototypeOf(PerformanceMark, PerformanceEntry); +ObjectSetPrototypeOf(PerformanceMark.prototype, PerformanceEntry.prototype); +ObjectDefineProperties(PerformanceMark.prototype, { + detail: kEnumerableProperty, +}); + +class PerformanceMeasure extends PerformanceEntry { + constructor() { + throw new ERR_ILLEGAL_CONSTRUCTOR(); + } -class PerformanceMeasure extends InternalPerformanceEntry { - constructor(name, start, duration, detail) { - super(name, 'measure', start, duration, detail); + get detail() { + validateInternalField(this, kDetail, 'PerformanceMeasure'); + return this[kDetail]; } get [SymbolToStringTag]() { return 'PerformanceMeasure'; } } +ObjectDefineProperties(PerformanceMeasure.prototype, { + detail: kEnumerableProperty, +}); + +function createPerformanceMeasure(name, start, duration, detail) { + return ReflectConstruct(function PerformanceMeasure() { + initPerformanceEntry(this, name, 'measure', start, duration); + this[kDetail] = detail; + }, [], PerformanceMeasure); +} -function mark(name, options = kEmptyObject) { +function mark(name, options) { const mark = new PerformanceMark(name, options); enqueue(mark); bufferUserTiming(mark); @@ -160,7 +209,7 @@ function measure(name, startOrMeasureOptions, endMark) { } = calculateStartDuration(startOrMeasureOptions, endMark); let detail = startOrMeasureOptions?.detail; detail = detail != null ? structuredClone(detail) : null; - const measure = new PerformanceMeasure(name, start, duration, detail); + const measure = createPerformanceMeasure(name, start, duration, detail); enqueue(measure); bufferUserTiming(measure); return measure; diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 6ad5502b61ded1..71ec2979041b10 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -372,7 +372,6 @@ function setupTraceCategoryState() { } function setupPerfHooks() { - require('internal/perf/performance').refreshTimeOrigin(); require('internal/perf/utils').refreshTimeOrigin(); } diff --git a/test/common/wpt.js b/test/common/wpt.js index 01f937fafce841..b3ec2b369b3dcc 100644 --- a/test/common/wpt.js +++ b/test/common/wpt.js @@ -298,7 +298,7 @@ class WPTRunner { this.resource = new ResourceLoader(path); this.flags = []; - this.dummyGlobalThisScript = null; + this.globalThisInitScripts = []; this.initScript = null; this.status = new StatusLoader(path); @@ -340,17 +340,17 @@ class WPTRunner { } get fullInitScript() { - if (this.initScript === null && this.dummyGlobalThisScript === null) { - return null; + if (this.globalThisInitScripts.length === null) { + return this.initScript; } + const globalThisInitScript = this.globalThisInitScripts.join('\n\n//===\n'); + if (this.initScript === null) { - return this.dummyGlobalThisScript; - } else if (this.dummyGlobalThisScript === null) { - return this.initScript; + return globalThisInitScript; } - return `${this.dummyGlobalThisScript}\n\n//===\n${this.initScript}`; + return `${globalThisInitScript}\n\n//===\n${this.initScript}`; } /** @@ -361,8 +361,9 @@ class WPTRunner { pretendGlobalThisAs(name) { switch (name) { case 'Window': { - this.dummyGlobalThisScript = - 'global.Window = Object.getPrototypeOf(globalThis).constructor;'; + this.globalThisInitScripts.push( + `global.Window = Object.getPrototypeOf(globalThis).constructor; + self.GLOBAL.isWorker = () => false;`); break; } @@ -376,6 +377,36 @@ class WPTRunner { } } + brandCheckGlobalScopeAttribute(name) { + // TODO(legendecas): idlharness GlobalScope attribute receiver validation. + const script = ` + const desc = Object.getOwnPropertyDescriptor(globalThis, '${name}'); + function getter() { + // Mimic GlobalScope instance brand check. + if (this !== globalThis) { + throw new TypeError('Illegal invocation'); + } + return desc.get(); + } + Object.defineProperty(getter, 'name', { value: 'get ${name}' }); + + function setter(value) { + // Mimic GlobalScope instance brand check. + if (this !== globalThis) { + throw new TypeError('Illegal invocation'); + } + desc.set(value); + } + Object.defineProperty(setter, 'name', { value: 'set ${name}' }); + + Object.defineProperty(globalThis, '${name}', { + get: getter, + set: setter, + }); + `; + this.globalThisInitScripts.push(script); + } + // TODO(joyeecheung): work with the upstream to port more tests in .html // to .js. async runJsTests() { diff --git a/test/fixtures/wpt/README.md b/test/fixtures/wpt/README.md index 63175ead2a9c2d..dd6136c16abe4f 100644 --- a/test/fixtures/wpt/README.md +++ b/test/fixtures/wpt/README.md @@ -21,8 +21,9 @@ Last update: - html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing - html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone - html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers -- interfaces: https://github.com/web-platform-tests/wpt/tree/fc086c82d5/interfaces +- interfaces: https://github.com/web-platform-tests/wpt/tree/df731dab88/interfaces - performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline +- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing - resources: https://github.com/web-platform-tests/wpt/tree/c5b428f15a/resources - streams: https://github.com/web-platform-tests/wpt/tree/9e5ef42bd3/streams - url: https://github.com/web-platform-tests/wpt/tree/0e5b126cd0/url diff --git a/test/fixtures/wpt/interfaces/WebCryptoAPI.idl b/test/fixtures/wpt/interfaces/WebCryptoAPI.idl index d7fce62d5b0003..0e68ea82f59443 100644 --- a/test/fixtures/wpt/interfaces/WebCryptoAPI.idl +++ b/test/fixtures/wpt/interfaces/WebCryptoAPI.idl @@ -11,6 +11,7 @@ partial interface mixin WindowOrWorkerGlobalScope { interface Crypto { [SecureContext] readonly attribute SubtleCrypto subtle; ArrayBufferView getRandomValues(ArrayBufferView array); + [SecureContext] DOMString randomUUID(); }; typedef (object or DOMString) AlgorithmIdentifier; @@ -29,7 +30,7 @@ enum KeyType { "public", "private", "secret" }; enum KeyUsage { "encrypt", "decrypt", "sign", "verify", "deriveKey", "deriveBits", "wrapKey", "unwrapKey" }; -[SecureContext,Exposed=(Window,Worker)] +[SecureContext,Exposed=(Window,Worker),Serializable] interface CryptoKey { readonly attribute KeyType type; readonly attribute boolean extractable; diff --git a/test/fixtures/wpt/interfaces/console.idl b/test/fixtures/wpt/interfaces/console.idl index 7cd73a61764fc1..fdf1d0df978378 100644 --- a/test/fixtures/wpt/interfaces/console.idl +++ b/test/fixtures/wpt/interfaces/console.idl @@ -3,7 +3,7 @@ // (https://github.com/w3c/webref) // Source: Console Standard (https://console.spec.whatwg.org/) -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] namespace console { // but see namespace object requirements below // Logging undefined assert(optional boolean condition = false, any... data); diff --git a/test/fixtures/wpt/interfaces/dom.idl b/test/fixtures/wpt/interfaces/dom.idl index 4a57b2e1eb4bce..96acfc6a717fb2 100644 --- a/test/fixtures/wpt/interfaces/dom.idl +++ b/test/fixtures/wpt/interfaces/dom.idl @@ -3,7 +3,7 @@ // (https://github.com/w3c/webref) // Source: DOM Standard (https://dom.spec.whatwg.org/) -[Exposed=(Window,Worker,AudioWorklet)] +[Exposed=*] interface Event { constructor(DOMString type, optional EventInit eventInitDict = {}); @@ -46,7 +46,7 @@ partial interface Window { [Replaceable] readonly attribute (Event or undefined) event; // legacy }; -[Exposed=(Window,Worker)] +[Exposed=*] interface CustomEvent : Event { constructor(DOMString type, optional CustomEventInit eventInitDict = {}); @@ -59,7 +59,7 @@ dictionary CustomEventInit : EventInit { any detail = null; }; -[Exposed=(Window,Worker,AudioWorklet)] +[Exposed=*] interface EventTarget { constructor(); @@ -77,25 +77,28 @@ dictionary EventListenerOptions { }; dictionary AddEventListenerOptions : EventListenerOptions { - boolean passive = false; + boolean passive; boolean once = false; AbortSignal signal; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface AbortController { constructor(); [SameObject] readonly attribute AbortSignal signal; - undefined abort(); + undefined abort(optional any reason); }; -[Exposed=(Window,Worker)] +[Exposed=*] interface AbortSignal : EventTarget { - [NewObject] static AbortSignal abort(); + [NewObject] static AbortSignal abort(optional any reason); + [Exposed=(Window,Worker), NewObject] static AbortSignal timeout([EnforceRange] unsigned long long milliseconds); readonly attribute boolean aborted; + readonly attribute any reason; + undefined throwIfAborted(); attribute EventHandler onabort; }; diff --git a/test/fixtures/wpt/interfaces/encoding.idl b/test/fixtures/wpt/interfaces/encoding.idl index 7585cb3717c9aa..a8cbe4431a2dc7 100644 --- a/test/fixtures/wpt/interfaces/encoding.idl +++ b/test/fixtures/wpt/interfaces/encoding.idl @@ -18,7 +18,7 @@ dictionary TextDecodeOptions { boolean stream = false; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface TextDecoder { constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); @@ -35,7 +35,7 @@ dictionary TextEncoderEncodeIntoResult { unsigned long long written; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface TextEncoder { constructor(); @@ -44,14 +44,14 @@ interface TextEncoder { }; TextEncoder includes TextEncoderCommon; -[Exposed=(Window,Worker)] +[Exposed=*] interface TextDecoderStream { constructor(optional DOMString label = "utf-8", optional TextDecoderOptions options = {}); }; TextDecoderStream includes TextDecoderCommon; TextDecoderStream includes GenericTransformStream; -[Exposed=(Window,Worker)] +[Exposed=*] interface TextEncoderStream { constructor(); }; diff --git a/test/fixtures/wpt/interfaces/hr-time.idl b/test/fixtures/wpt/interfaces/hr-time.idl index 93537d64ad18d7..13aa109b6c764c 100644 --- a/test/fixtures/wpt/interfaces/hr-time.idl +++ b/test/fixtures/wpt/interfaces/hr-time.idl @@ -5,7 +5,9 @@ typedef double DOMHighResTimeStamp; -[Exposed=(Window,Worker)] +typedef unsigned long long EpochTimeStamp; + +[Exposed=*] interface Performance : EventTarget { DOMHighResTimeStamp now(); readonly attribute DOMHighResTimeStamp timeOrigin; diff --git a/test/fixtures/wpt/interfaces/html.idl b/test/fixtures/wpt/interfaces/html.idl index d878cba3367cc6..d9068b4ad1e550 100644 --- a/test/fixtures/wpt/interfaces/html.idl +++ b/test/fixtures/wpt/interfaces/html.idl @@ -43,6 +43,7 @@ interface DOMStringList { }; enum DocumentReadyState { "loading", "interactive", "complete" }; +enum DocumentVisibilityState { "visible", "hidden" }; typedef (HTMLScriptElement or SVGScriptElement) HTMLOrSVGScriptElement; [LegacyOverrideBuiltIns] @@ -87,9 +88,12 @@ partial interface Document { boolean queryCommandState(DOMString commandId); boolean queryCommandSupported(DOMString commandId); DOMString queryCommandValue(DOMString commandId); + readonly attribute boolean hidden; + readonly attribute DocumentVisibilityState visibilityState; // special event handler IDL attributes that only apply to Document objects [LegacyLenientThis] attribute EventHandler onreadystatechange; + attribute EventHandler onvisibilitychange; // also has obsolete members }; @@ -111,7 +115,8 @@ interface HTMLElement : Element { [CEReactions] attribute DOMString dir; // user interaction - [CEReactions] attribute boolean hidden; + [CEReactions] attribute (boolean or unrestricted double or DOMString)? hidden; + [CEReactions] attribute boolean inert; undefined click(); [CEReactions] attribute DOMString accessKey; readonly attribute DOMString accessKeyLabel; @@ -187,7 +192,7 @@ interface HTMLLinkElement : HTMLElement { [CEReactions] attribute USVString href; [CEReactions] attribute DOMString? crossOrigin; [CEReactions] attribute DOMString rel; - [CEReactions] attribute DOMString as; // (default "") + [CEReactions] attribute DOMString as; [SameObject, PutForwards=value] readonly attribute DOMTokenList relList; [CEReactions] attribute DOMString media; [CEReactions] attribute DOMString integrity; @@ -197,6 +202,7 @@ interface HTMLLinkElement : HTMLElement { [CEReactions] attribute USVString imageSrcset; [CEReactions] attribute DOMString imageSizes; [CEReactions] attribute DOMString referrerPolicy; + [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; [CEReactions] attribute boolean disabled; // also has obsolete members @@ -210,6 +216,7 @@ interface HTMLMetaElement : HTMLElement { [CEReactions] attribute DOMString name; [CEReactions] attribute DOMString httpEquiv; [CEReactions] attribute DOMString content; + [CEReactions] attribute DOMString media; // also has obsolete members }; @@ -218,7 +225,9 @@ interface HTMLMetaElement : HTMLElement { interface HTMLStyleElement : HTMLElement { [HTMLConstructor] constructor(); + attribute boolean disabled; [CEReactions] attribute DOMString media; + [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; // also has obsolete members }; @@ -487,16 +496,6 @@ interface HTMLObjectElement : HTMLElement { // also has obsolete members }; -[Exposed=Window] -interface HTMLParamElement : HTMLElement { - [HTMLConstructor] constructor(); - - [CEReactions] attribute DOMString name; - [CEReactions] attribute DOMString value; - - // also has obsolete members -}; - [Exposed=Window] interface HTMLVideoElement : HTMLMediaElement { [HTMLConstructor] constructor(); @@ -928,6 +927,8 @@ interface HTMLInputElement : HTMLElement { undefined setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve"); undefined setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction); + undefined showPicker(); + // also has obsolete members }; @@ -972,7 +973,7 @@ interface HTMLSelectElement : HTMLElement { [SameObject] readonly attribute HTMLOptionsCollection options; [CEReactions] attribute unsigned long length; - getter Element? item(unsigned long index); + getter HTMLOptionElement? item(unsigned long index); HTMLOptionElement? namedItem(DOMString name); [CEReactions] undefined add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null); [CEReactions] undefined remove(); // ChildNode overload @@ -1214,6 +1215,9 @@ interface HTMLScriptElement : HTMLElement { [CEReactions] attribute DOMString text; [CEReactions] attribute DOMString integrity; [CEReactions] attribute DOMString referrerPolicy; + [SameObject, PutForwards=value] readonly attribute DOMTokenList blocking; + + static boolean supports(DOMString type); // also has obsolete members }; @@ -1239,7 +1243,7 @@ dictionary AssignedNodesOptions { boolean flatten = false; }; -typedef (CanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext) RenderingContext; +typedef (CanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext or GPUCanvasContext) RenderingContext; [Exposed=Window] interface HTMLCanvasElement : HTMLElement { @@ -1264,7 +1268,8 @@ typedef (HTMLOrSVGImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap or - OffscreenCanvas) CanvasImageSource; + OffscreenCanvas or + VideoFrame) CanvasImageSource; enum PredefinedColorSpace { "srgb", "display-p3" }; @@ -1274,6 +1279,7 @@ dictionary CanvasRenderingContext2DSettings { boolean alpha = true; boolean desynchronized = false; PredefinedColorSpace colorSpace = "srgb"; + boolean willReadFrequently = false; }; enum ImageSmoothingQuality { "low", "medium", "high" }; @@ -1307,6 +1313,7 @@ interface mixin CanvasState { undefined save(); // push state on state stack undefined restore(); // pop state stack and restore state undefined reset(); // reset the rendering context to its default state + boolean isContextLost(); // return whether context is lost }; interface mixin CanvasTransform { @@ -1326,7 +1333,7 @@ interface mixin CanvasTransform { interface mixin CanvasCompositing { // compositing attribute unrestricted double globalAlpha; // (default 1.0) - attribute DOMString globalCompositeOperation; // (default source-over) + attribute DOMString globalCompositeOperation; // (default "source-over") }; interface mixin CanvasImageSmoothing { @@ -1357,7 +1364,14 @@ interface mixin CanvasShadowStyles { interface mixin CanvasFilters { // filters - attribute DOMString filter; // (default "none") + attribute (DOMString or CanvasFilter) filter; // (default "none") +}; + +typedef record CanvasFilterInput; + +[Exposed=(Window,Worker,PaintWorklet)] +interface CanvasFilter { + constructor(optional (CanvasFilterInput or sequence) filters); }; interface mixin CanvasRect { @@ -1441,12 +1455,12 @@ interface mixin CanvasTextDrawingStyles { attribute CanvasTextAlign textAlign; // (default: "start") attribute CanvasTextBaseline textBaseline; // (default: "alphabetic") attribute CanvasDirection direction; // (default: "inherit") - attribute double textLetterSpacing; // (default: 0) - attribute double textWordSpacing; // (default: 0) + attribute DOMString letterSpacing; // (default: "0px") attribute CanvasFontKerning fontKerning; // (default: "auto") attribute CanvasFontStretch fontStretch; // (default: "normal") attribute CanvasFontVariantCaps fontVariantCaps; // (default: "normal") - attribute CanvasTextRendering textRendering; // (default: "normal") + attribute CanvasTextRendering textRendering; // (default: "auto") + attribute DOMString wordSpacing; // (default: "0px") }; interface mixin CanvasPath { @@ -1458,6 +1472,7 @@ interface mixin CanvasPath { undefined bezierCurveTo(unrestricted double cp1x, unrestricted double cp1y, unrestricted double cp2x, unrestricted double cp2y, unrestricted double x, unrestricted double y); undefined arcTo(unrestricted double x1, unrestricted double y1, unrestricted double x2, unrestricted double y2, unrestricted double radius); undefined rect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h); + undefined roundRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h, optional (unrestricted double or DOMPointInit or sequence<(unrestricted double or DOMPointInit)>) radii = 0); undefined arc(unrestricted double x, unrestricted double y, unrestricted double radius, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); undefined ellipse(unrestricted double x, unrestricted double y, unrestricted double radiusX, unrestricted double radiusY, unrestricted double rotation, unrestricted double startAngle, unrestricted double endAngle, optional boolean counterclockwise = false); }; @@ -1527,14 +1542,14 @@ dictionary ImageBitmapRenderingContextSettings { boolean alpha = true; }; -typedef (OffscreenCanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext) OffscreenRenderingContext; +typedef (OffscreenCanvasRenderingContext2D or ImageBitmapRenderingContext or WebGLRenderingContext or WebGL2RenderingContext or GPUCanvasContext) OffscreenRenderingContext; dictionary ImageEncodeOptions { DOMString type = "image/png"; unrestricted double quality; }; -enum OffscreenRenderingContextId { "2d", "bitmaprenderer", "webgl", "webgl2" }; +enum OffscreenRenderingContextId { "2d", "bitmaprenderer", "webgl", "webgl2", "webgpu" }; [Exposed=(Window,Worker), Transferable] interface OffscreenCanvas : EventTarget { @@ -1546,6 +1561,9 @@ interface OffscreenCanvas : EventTarget { OffscreenRenderingContext? getContext(OffscreenRenderingContextId contextId, optional any options = null); ImageBitmap transferToImageBitmap(); Promise convertToBlob(optional ImageEncodeOptions options = {}); + + attribute EventHandler oncontextlost; + attribute EventHandler oncontextrestored; }; [Exposed=(Window,Worker)] @@ -1724,6 +1742,7 @@ interface Window : EventTarget { // the user agent readonly attribute Navigator navigator; + readonly attribute Navigator clientInformation; // legacy alias of .navigator readonly attribute boolean originAgentCluster; // user prompts @@ -1741,7 +1760,7 @@ interface Window : EventTarget { Window includes GlobalEventHandlers; Window includes WindowEventHandlers; -dictionary WindowPostMessageOptions : PostMessageOptions { +dictionary WindowPostMessageOptions : StructuredSerializeOptions { USVString targetOrigin = "/"; }; @@ -1823,7 +1842,7 @@ interface BeforeUnloadEvent : Event { attribute DOMString returnValue; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface ErrorEvent : Event { constructor(DOMString type, optional ErrorEventInit eventInitDict = {}); @@ -1839,10 +1858,10 @@ dictionary ErrorEventInit : EventInit { USVString filename = ""; unsigned long lineno = 0; unsigned long colno = 0; - any error = null; + any error; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface PromiseRejectionEvent : Event { constructor(DOMString type, PromiseRejectionEventInit eventInitDict); @@ -1870,6 +1889,8 @@ typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler; interface mixin GlobalEventHandlers { attribute EventHandler onabort; attribute EventHandler onauxclick; + attribute EventHandler onbeforeinput; + attribute EventHandler onbeforematch; attribute EventHandler onblur; attribute EventHandler oncancel; attribute EventHandler oncanplay; @@ -1877,7 +1898,9 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; + attribute EventHandler oncontextrestored; attribute EventHandler oncuechange; attribute EventHandler ondblclick; attribute EventHandler ondrag; @@ -1968,15 +1991,17 @@ interface mixin WindowOrWorkerGlobalScope { readonly attribute boolean isSecureContext; readonly attribute boolean crossOriginIsolated; + undefined reportError(any e); + // base64 utility methods DOMString btoa(DOMString data); ByteString atob(DOMString data); // timers long setTimeout(TimerHandler handler, optional long timeout = 0, any... arguments); - undefined clearTimeout(optional long handle = 0); + undefined clearTimeout(optional long id = 0); long setInterval(TimerHandler handler, optional long timeout = 0, any... arguments); - undefined clearInterval(optional long handle = 0); + undefined clearInterval(optional long id = 0); // microtask queuing undefined queueMicrotask(VoidFunction callback); @@ -1984,6 +2009,9 @@ interface mixin WindowOrWorkerGlobalScope { // ImageBitmap Promise createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {}); Promise createImageBitmap(ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options = {}); + + // structured cloning + any structuredClone(any value, optional StructuredSerializeOptions options = {}); }; Window includes WindowOrWorkerGlobalScope; WorkerGlobalScope includes WindowOrWorkerGlobalScope; @@ -2050,6 +2078,49 @@ interface mixin NavigatorCookies { readonly attribute boolean cookieEnabled; }; +interface mixin NavigatorPlugins { + [SameObject] readonly attribute PluginArray plugins; + [SameObject] readonly attribute MimeTypeArray mimeTypes; + boolean javaEnabled(); + readonly attribute boolean pdfViewerEnabled; +}; + +[Exposed=Window, + LegacyUnenumerableNamedProperties] +interface PluginArray { + undefined refresh(); + readonly attribute unsigned long length; + getter Plugin? item(unsigned long index); + getter Plugin? namedItem(DOMString name); +}; + +[Exposed=Window, + LegacyUnenumerableNamedProperties] +interface MimeTypeArray { + readonly attribute unsigned long length; + getter MimeType? item(unsigned long index); + getter MimeType? namedItem(DOMString name); +}; + +[Exposed=Window, + LegacyUnenumerableNamedProperties] +interface Plugin { + readonly attribute DOMString name; + readonly attribute DOMString description; + readonly attribute DOMString filename; + readonly attribute unsigned long length; + getter MimeType? item(unsigned long index); + getter MimeType? namedItem(DOMString name); +}; + +[Exposed=Window] +interface MimeType { + readonly attribute DOMString type; + readonly attribute DOMString description; + readonly attribute DOMString suffixes; + readonly attribute Plugin enabledPlugin; +}; + [Exposed=(Window,Worker), Serializable, Transferable] interface ImageBitmap { readonly attribute unsigned long width; @@ -2131,53 +2202,6 @@ dictionary EventSourceInit { boolean withCredentials = false; }; -enum BinaryType { "blob", "arraybuffer" }; -[Exposed=(Window,Worker)] -interface WebSocket : EventTarget { - constructor(USVString url, optional (DOMString or sequence) protocols = []); - - readonly attribute USVString url; - - // ready state - const unsigned short CONNECTING = 0; - const unsigned short OPEN = 1; - const unsigned short CLOSING = 2; - const unsigned short CLOSED = 3; - readonly attribute unsigned short readyState; - readonly attribute unsigned long long bufferedAmount; - - // networking - attribute EventHandler onopen; - attribute EventHandler onerror; - attribute EventHandler onclose; - readonly attribute DOMString extensions; - readonly attribute DOMString protocol; - undefined close(optional [Clamp] unsigned short code, optional USVString reason); - - // messaging - attribute EventHandler onmessage; - attribute BinaryType binaryType; - undefined send(USVString data); - undefined send(Blob data); - undefined send(ArrayBuffer data); - undefined send(ArrayBufferView data); -}; - -[Exposed=(Window,Worker)] -interface CloseEvent : Event { - constructor(DOMString type, optional CloseEventInit eventInitDict = {}); - - readonly attribute boolean wasClean; - readonly attribute unsigned short code; - readonly attribute USVString reason; -}; - -dictionary CloseEventInit : EventInit { - boolean wasClean = false; - unsigned short code = 0; - USVString reason = ""; -}; - [Exposed=(Window,Worker)] interface MessageChannel { constructor(); @@ -2189,7 +2213,7 @@ interface MessageChannel { [Exposed=(Window,Worker,AudioWorklet), Transferable] interface MessagePort : EventTarget { undefined postMessage(any message, sequence transfer); - undefined postMessage(any message, optional PostMessageOptions options = {}); + undefined postMessage(any message, optional StructuredSerializeOptions options = {}); undefined start(); undefined close(); @@ -2198,7 +2222,7 @@ interface MessagePort : EventTarget { attribute EventHandler onmessageerror; }; -dictionary PostMessageOptions { +dictionary StructuredSerializeOptions { sequence transfer = []; }; @@ -2233,7 +2257,7 @@ interface DedicatedWorkerGlobalScope : WorkerGlobalScope { [Replaceable] readonly attribute DOMString name; undefined postMessage(any message, sequence transfer); - undefined postMessage(any message, optional PostMessageOptions options = {}); + undefined postMessage(any message, optional StructuredSerializeOptions options = {}); undefined close(); @@ -2261,7 +2285,7 @@ interface Worker : EventTarget { undefined terminate(); undefined postMessage(any message, sequence transfer); - undefined postMessage(any message, optional PostMessageOptions options = {}); + undefined postMessage(any message, optional StructuredSerializeOptions options = {}); attribute EventHandler onmessage; attribute EventHandler onmessageerror; }; @@ -2560,7 +2584,12 @@ partial interface HTMLParagraphElement { [CEReactions] attribute DOMString align; }; -partial interface HTMLParamElement { +[Exposed=Window] +interface HTMLParamElement : HTMLElement { + [HTMLConstructor] constructor(); + + [CEReactions] attribute DOMString name; + [CEReactions] attribute DOMString value; [CEReactions] attribute DOMString type; [CEReactions] attribute DOMString valueType; }; @@ -2656,42 +2685,3 @@ interface External { undefined AddSearchProvider(); undefined IsSearchProviderInstalled(); }; - -interface mixin NavigatorPlugins { - [SameObject] readonly attribute PluginArray plugins; - [SameObject] readonly attribute MimeTypeArray mimeTypes; - boolean javaEnabled(); -}; - -[Exposed=Window] -interface PluginArray { - undefined refresh(); - readonly attribute unsigned long length; - getter object? item(unsigned long index); - object? namedItem(DOMString name); -}; - -[Exposed=Window] -interface MimeTypeArray { - readonly attribute unsigned long length; - getter object? item(unsigned long index); - object? namedItem(DOMString name); -}; - -[Exposed=Window] -interface Plugin { - readonly attribute undefined name; - readonly attribute undefined description; - readonly attribute undefined filename; - readonly attribute undefined length; - getter undefined item(unsigned long index); - undefined namedItem(DOMString name); -}; - -[Exposed=Window] -interface MimeType { - readonly attribute undefined type; - readonly attribute undefined description; - readonly attribute undefined suffixes; - readonly attribute undefined enabledPlugin; -}; diff --git a/test/fixtures/wpt/interfaces/performance-timeline.idl b/test/fixtures/wpt/interfaces/performance-timeline.idl index d3a5a278055eba..9f6cc5e2e902dc 100644 --- a/test/fixtures/wpt/interfaces/performance-timeline.idl +++ b/test/fixtures/wpt/interfaces/performance-timeline.idl @@ -10,7 +10,7 @@ partial interface Performance { }; typedef sequence PerformanceEntryList; -[Exposed=(Window,Worker)] +[Exposed=*] interface PerformanceEntry { readonly attribute DOMString name; readonly attribute DOMString entryType; @@ -22,7 +22,7 @@ interface PerformanceEntry { callback PerformanceObserverCallback = undefined (PerformanceObserverEntryList entries, PerformanceObserver observer, optional PerformanceObserverCallbackOptions options = {}); -[Exposed=(Window,Worker)] +[Exposed=*] interface PerformanceObserver { constructor(PerformanceObserverCallback callback); undefined observe (optional PerformanceObserverInit options = {}); @@ -41,7 +41,7 @@ dictionary PerformanceObserverInit { boolean buffered; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface PerformanceObserverEntryList { PerformanceEntryList getEntries(); PerformanceEntryList getEntriesByType (DOMString type); diff --git a/test/fixtures/wpt/interfaces/resource-timing.idl b/test/fixtures/wpt/interfaces/resource-timing.idl new file mode 100644 index 00000000000000..235963b804bf9a --- /dev/null +++ b/test/fixtures/wpt/interfaces/resource-timing.idl @@ -0,0 +1,32 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Resource Timing Level 2 (https://w3c.github.io/resource-timing/) + +[Exposed=(Window,Worker)] +interface PerformanceResourceTiming : PerformanceEntry { + readonly attribute DOMString initiatorType; + readonly attribute ByteString nextHopProtocol; + readonly attribute DOMHighResTimeStamp workerStart; + readonly attribute DOMHighResTimeStamp redirectStart; + readonly attribute DOMHighResTimeStamp redirectEnd; + readonly attribute DOMHighResTimeStamp fetchStart; + readonly attribute DOMHighResTimeStamp domainLookupStart; + readonly attribute DOMHighResTimeStamp domainLookupEnd; + readonly attribute DOMHighResTimeStamp connectStart; + readonly attribute DOMHighResTimeStamp connectEnd; + readonly attribute DOMHighResTimeStamp secureConnectionStart; + readonly attribute DOMHighResTimeStamp requestStart; + readonly attribute DOMHighResTimeStamp responseStart; + readonly attribute DOMHighResTimeStamp responseEnd; + readonly attribute unsigned long long transferSize; + readonly attribute unsigned long long encodedBodySize; + readonly attribute unsigned long long decodedBodySize; + [Default] object toJSON(); +}; + +partial interface Performance { + undefined clearResourceTimings (); + undefined setResourceTimingBufferSize (unsigned long maxSize); + attribute EventHandler onresourcetimingbufferfull; +}; diff --git a/test/fixtures/wpt/interfaces/streams.idl b/test/fixtures/wpt/interfaces/streams.idl index 99c3a5dee91f69..fd5420f16a0967 100644 --- a/test/fixtures/wpt/interfaces/streams.idl +++ b/test/fixtures/wpt/interfaces/streams.idl @@ -3,7 +3,7 @@ // (https://github.com/w3c/webref) // Source: Streams Standard (https://streams.spec.whatwg.org/) -[Exposed=(Window,Worker,Worklet), Transferable] +[Exposed=*, Transferable] interface ReadableStream { constructor(optional object underlyingSource, optional QueuingStrategy strategy = {}); @@ -64,35 +64,30 @@ interface mixin ReadableStreamGenericReader { Promise cancel(optional any reason); }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ReadableStreamDefaultReader { constructor(ReadableStream stream); - Promise read(); + Promise read(); undefined releaseLock(); }; ReadableStreamDefaultReader includes ReadableStreamGenericReader; -dictionary ReadableStreamDefaultReadResult { - any value; - boolean done; +dictionary ReadableStreamReadResult { + any value; + boolean done; }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ReadableStreamBYOBReader { constructor(ReadableStream stream); - Promise read(ArrayBufferView view); + Promise read(ArrayBufferView view); undefined releaseLock(); }; ReadableStreamBYOBReader includes ReadableStreamGenericReader; -dictionary ReadableStreamBYOBReadResult { - ArrayBufferView value; - boolean done; -}; - -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ReadableStreamDefaultController { readonly attribute unrestricted double? desiredSize; @@ -101,7 +96,7 @@ interface ReadableStreamDefaultController { undefined error(optional any e); }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ReadableByteStreamController { readonly attribute ReadableStreamBYOBRequest? byobRequest; readonly attribute unrestricted double? desiredSize; @@ -111,7 +106,7 @@ interface ReadableByteStreamController { undefined error(optional any e); }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ReadableStreamBYOBRequest { readonly attribute ArrayBufferView? view; @@ -119,7 +114,7 @@ interface ReadableStreamBYOBRequest { undefined respondWithNewView(ArrayBufferView view); }; -[Exposed=(Window,Worker,Worklet), Transferable] +[Exposed=*, Transferable] interface WritableStream { constructor(optional object underlyingSink, optional QueuingStrategy strategy = {}); @@ -143,7 +138,7 @@ callback UnderlyingSinkWriteCallback = Promise (any chunk, WritableSt callback UnderlyingSinkCloseCallback = Promise (); callback UnderlyingSinkAbortCallback = Promise (optional any reason); -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface WritableStreamDefaultWriter { constructor(WritableStream stream); @@ -157,12 +152,13 @@ interface WritableStreamDefaultWriter { Promise write(optional any chunk); }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface WritableStreamDefaultController { + readonly attribute AbortSignal signal; undefined error(optional any e); }; -[Exposed=(Window,Worker,Worklet), Transferable] +[Exposed=*, Transferable] interface TransformStream { constructor(optional object transformer, optional QueuingStrategy writableStrategy = {}, @@ -184,7 +180,7 @@ callback TransformerStartCallback = any (TransformStreamDefaultController contro callback TransformerFlushCallback = Promise (TransformStreamDefaultController controller); callback TransformerTransformCallback = Promise (any chunk, TransformStreamDefaultController controller); -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface TransformStreamDefaultController { readonly attribute unrestricted double? desiredSize; @@ -198,13 +194,13 @@ dictionary QueuingStrategy { QueuingStrategySize size; }; -callback QueuingStrategySize = unrestricted double (optional any chunk); +callback QueuingStrategySize = unrestricted double (any chunk); dictionary QueuingStrategyInit { required unrestricted double highWaterMark; }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface ByteLengthQueuingStrategy { constructor(QueuingStrategyInit init); @@ -212,7 +208,7 @@ interface ByteLengthQueuingStrategy { readonly attribute Function size; }; -[Exposed=(Window,Worker,Worklet)] +[Exposed=*] interface CountQueuingStrategy { constructor(QueuingStrategyInit init); diff --git a/test/fixtures/wpt/interfaces/url.idl b/test/fixtures/wpt/interfaces/url.idl index b00ab4b83bc60b..360c9adcfa1104 100644 --- a/test/fixtures/wpt/interfaces/url.idl +++ b/test/fixtures/wpt/interfaces/url.idl @@ -3,7 +3,7 @@ // (https://github.com/w3c/webref) // Source: URL Standard (https://url.spec.whatwg.org/) -[Exposed=(Window,Worker), +[Exposed=*, LegacyWindowAlias=webkitURL] interface URL { constructor(USVString url, optional USVString base); @@ -24,7 +24,7 @@ interface URL { USVString toJSON(); }; -[Exposed=(Window,Worker)] +[Exposed=*] interface URLSearchParams { constructor(optional (sequence> or record or USVString) init = ""); diff --git a/test/fixtures/wpt/interfaces/user-timing.idl b/test/fixtures/wpt/interfaces/user-timing.idl index 28ee8aac2b19a6..a0b8f94710ec33 100644 --- a/test/fixtures/wpt/interfaces/user-timing.idl +++ b/test/fixtures/wpt/interfaces/user-timing.idl @@ -22,13 +22,13 @@ partial interface Performance { undefined clearMeasures(optional DOMString measureName); }; -[Exposed=(Window,Worker)] +[Exposed=*] interface PerformanceMark : PerformanceEntry { constructor(DOMString markName, optional PerformanceMarkOptions markOptions = {}); readonly attribute any detail; }; -[Exposed=(Window,Worker)] +[Exposed=*] interface PerformanceMeasure : PerformanceEntry { readonly attribute any detail; }; diff --git a/test/fixtures/wpt/interfaces/webidl.idl b/test/fixtures/wpt/interfaces/webidl.idl new file mode 100644 index 00000000000000..43748c5ac4c889 --- /dev/null +++ b/test/fixtures/wpt/interfaces/webidl.idl @@ -0,0 +1,50 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Web IDL Standard (https://webidl.spec.whatwg.org/) + +typedef (Int8Array or Int16Array or Int32Array or + Uint8Array or Uint16Array or Uint32Array or Uint8ClampedArray or + BigInt64Array or BigUint64Array or + Float32Array or Float64Array or DataView) ArrayBufferView; + +typedef (ArrayBufferView or ArrayBuffer) BufferSource; +[Exposed=(Window,Worker), + Serializable] +interface DOMException { // but see below note about ECMAScript binding + constructor(optional DOMString message = "", optional DOMString name = "Error"); + readonly attribute DOMString name; + readonly attribute DOMString message; + readonly attribute unsigned short code; + + const unsigned short INDEX_SIZE_ERR = 1; + const unsigned short DOMSTRING_SIZE_ERR = 2; + const unsigned short HIERARCHY_REQUEST_ERR = 3; + const unsigned short WRONG_DOCUMENT_ERR = 4; + const unsigned short INVALID_CHARACTER_ERR = 5; + const unsigned short NO_DATA_ALLOWED_ERR = 6; + const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7; + const unsigned short NOT_FOUND_ERR = 8; + const unsigned short NOT_SUPPORTED_ERR = 9; + const unsigned short INUSE_ATTRIBUTE_ERR = 10; + const unsigned short INVALID_STATE_ERR = 11; + const unsigned short SYNTAX_ERR = 12; + const unsigned short INVALID_MODIFICATION_ERR = 13; + const unsigned short NAMESPACE_ERR = 14; + const unsigned short INVALID_ACCESS_ERR = 15; + const unsigned short VALIDATION_ERR = 16; + const unsigned short TYPE_MISMATCH_ERR = 17; + const unsigned short SECURITY_ERR = 18; + const unsigned short NETWORK_ERR = 19; + const unsigned short ABORT_ERR = 20; + const unsigned short URL_MISMATCH_ERR = 21; + const unsigned short QUOTA_EXCEEDED_ERR = 22; + const unsigned short TIMEOUT_ERR = 23; + const unsigned short INVALID_NODE_TYPE_ERR = 24; + const unsigned short DATA_CLONE_ERR = 25; +}; + +callback Function = any (any... arguments); +callback VoidFunction = undefined (); + +typedef unsigned long long DOMTimeStamp; diff --git a/test/fixtures/wpt/resource-timing/304-response-recorded.html b/test/fixtures/wpt/resource-timing/304-response-recorded.html new file mode 100644 index 00000000000000..9e1bb3045cd548 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/304-response-recorded.html @@ -0,0 +1,53 @@ + + + + +Resource Timing - cached resources generate performance entries + + + + + + + + +

Description

+

This test validates that a 304 Not Modified resource appears in the +Performance Timeline.

+ + + diff --git a/test/fixtures/wpt/resource-timing/CodingConventions.md b/test/fixtures/wpt/resource-timing/CodingConventions.md new file mode 100644 index 00000000000000..39b8d13435ef49 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/CodingConventions.md @@ -0,0 +1,77 @@ +For [Resource Timing][1] tests, we want to have a consistent and clear coding +style. The goals of this style are to: +* Make it easier for new contributors to find their way around +* Help improve readability and maintainability +* Help us understand which parts of the spec are tested or not +Lots of the following rules are arbitrary but the value is realized in +consistency instead of adhering to the 'perfect' style. + +We want the test suite to be navigable. Developers should be able to easily +find the file or test that is relevant to their work. +* Tests should be arranged in files according to which piece of the spec they + test +* Files should be named using a consistent pattern +* HTML files should include useful meta tags + * `` for controlling labels in results pages + * `<link rel="help">` to point at the relevant piece of the spec + +We want the test suite to run consistently. Flaky tests are counterproductive. +* Prefer `promise_test` to `async_test` + * Note that there’s [still potential for some concurrency][2]; use + `add_cleanup()` if needed + +We want the tests to be readable. Tests should be written in a modern style +with recurring patterns. +* 80 character line limits where we can +* Consistent use of anonymous functions + * prefer + ``` + const func1 = param1 => { + body(); + } + const func2 = (param1, param2) => { + body(); + } + fn(param => { + body(); + }); + ``` + + over + + ``` + function func1(param1) { + body(); + } + function func2(param1, param2) { + body(); + } + fn(function(param) { + body(); + }); + ``` + +* Prefer `const` (or, if needed, `let`) to `var` +* Contain use of ‘.sub’ in filenames to known helper utilities where possible + * E.g. prefer use of get-host-info.sub.js to `{{host}}` or `{{ports[0]}}` + expressions +* Avoid use of webperftestharness[extension].js as it’s a layer of cognitive + overhead between test content and test intent + * Helper .js files are still encouraged where it makes sense but we want + to avoid a testing framework that is specific to Resource Timing (or + web performance APIs, in general). +* Prefer [`fetch_tests_from_window`][3] to collect test results from embedded + iframes instead of hand-rolled `postMessage` approaches +* Use the [`assert_*`][4] family of functions to check conformance to the spec + but throw exceptions explicitly when the test itself is broken. + * A failed assert indicates "the implementation doesn't conform to the + spec" + * Other uncaught exceptions indicate "the test case itself has a bug" +* Where possible, we want tests to be scalable - adding another test case + should be as simple as calling the tests with new parameters, rather than + copying an existing test and modifying it. + +[1]: https://www.w3.org/TR/resource-timing-2/ +[2]: https://web-platform-tests.org/writing-tests/testharness-api.html#promise-tests +[3]: https://web-platform-tests.org/writing-tests/testharness-api.html#consolidating-tests-from-other-documents +[4]: https://web-platform-tests.org/writing-tests/testharness-api.html#list-of-assertions diff --git a/test/fixtures/wpt/resource-timing/META.yml b/test/fixtures/wpt/resource-timing/META.yml new file mode 100644 index 00000000000000..662c42cb664219 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/META.yml @@ -0,0 +1,6 @@ +spec: https://w3c.github.io/resource-timing/ +suggested_reviewers: + - plehegar + - zqzhang + - igrigorik + - yoavweiss diff --git a/test/fixtures/wpt/resource-timing/SO-XO-SO-redirect-chain-tao.https.html b/test/fixtures/wpt/resource-timing/SO-XO-SO-redirect-chain-tao.https.html new file mode 100644 index 00000000000000..e6568910e4fe7b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/SO-XO-SO-redirect-chain-tao.https.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8" /> +<title>This test validates resource timing information for a same-origin=>cross-origin=>same-origin redirect chain without Timing-Allow-Origin. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/TAO-match.html b/test/fixtures/wpt/resource-timing/TAO-match.html new file mode 100644 index 00000000000000..dc0e2f744347b2 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/TAO-match.html @@ -0,0 +1,82 @@ + + + + +Resource Timing TAO tests + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/TAO-port-mismatch-means-crossorigin.html b/test/fixtures/wpt/resource-timing/TAO-port-mismatch-means-crossorigin.html new file mode 100644 index 00000000000000..f1218d17a0a44f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/TAO-port-mismatch-means-crossorigin.html @@ -0,0 +1,46 @@ + + + + +TAO - port mismatch must fail the check + + + + + + + + + +

Description

+

This test validates that for a cross origin resource with different ports, +the timing allow check algorithm will fail when the value of +Timing-Allow-Origin value has the right host but the wrong port in it.

+ + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-add-after-full-event.html b/test/fixtures/wpt/resource-timing/buffer-full-add-after-full-event.html new file mode 100644 index 00000000000000..43dc3d84fd3dd1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-add-after-full-event.html @@ -0,0 +1,27 @@ + + + + + + +This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level. + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html b/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html new file mode 100644 index 00000000000000..b00185c5b6c881 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback-that-drop.html @@ -0,0 +1,29 @@ + + + + +This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback.html b/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback.html new file mode 100644 index 00000000000000..d5883d33d5d780 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-add-entries-during-callback.html @@ -0,0 +1,28 @@ + + + + +This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-add-then-clear.html b/test/fixtures/wpt/resource-timing/buffer-full-add-then-clear.html new file mode 100644 index 00000000000000..5617c30b8850d8 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-add-then-clear.html @@ -0,0 +1,31 @@ + + + + +This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html b/test/fixtures/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html new file mode 100644 index 00000000000000..3091fcf4269192 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-decrease-buffer-during-callback.html @@ -0,0 +1,26 @@ + + + + +This test validates that decreasing the buffer size in onresourcetimingbufferfull callback does not result in extra entries being dropped. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-eventually.html b/test/fixtures/wpt/resource-timing/buffer-full-eventually.html new file mode 100644 index 00000000000000..6e9d5db4839903 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-eventually.html @@ -0,0 +1,31 @@ + + + + + +This test validates that resource timing implementations have a finite + number of entries in their buffer. + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html b/test/fixtures/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html new file mode 100644 index 00000000000000..dd12dd7afa9be1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-increase-buffer-during-callback.html @@ -0,0 +1,26 @@ + + + + +This test validates increasing the buffer size in onresourcetimingbufferfull callback of resource timing. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html b/test/fixtures/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html new file mode 100644 index 00000000000000..d5cc8e6ecd8d11 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-inspect-buffer-during-callback.html @@ -0,0 +1,30 @@ + + + + +This test validates the buffer doesn't contain more entries than it should inside onresourcetimingbufferfull callback. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-set-to-current-buffer.html b/test/fixtures/wpt/resource-timing/buffer-full-set-to-current-buffer.html new file mode 100644 index 00000000000000..dc527b9a32f514 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-set-to-current-buffer.html @@ -0,0 +1,34 @@ + + + + +This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level. + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html b/test/fixtures/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html new file mode 100644 index 00000000000000..3ea0577256c85b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-store-and-clear-during-callback.html @@ -0,0 +1,36 @@ + + + + +This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-then-decreased.html b/test/fixtures/wpt/resource-timing/buffer-full-then-decreased.html new file mode 100644 index 00000000000000..21912d978b294d --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-then-decreased.html @@ -0,0 +1,29 @@ + + + + +This test validates that reducing the buffer size after entries were + queued does not drop those entries, nor does it call the + resourcetimingbufferfull event callback. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-then-increased.html b/test/fixtures/wpt/resource-timing/buffer-full-then-increased.html new file mode 100644 index 00000000000000..de517bf405c66e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-then-increased.html @@ -0,0 +1,28 @@ + + + + +This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffer-full-when-populate-entries.html b/test/fixtures/wpt/resource-timing/buffer-full-when-populate-entries.html new file mode 100644 index 00000000000000..f4b1a2e7e7abba --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffer-full-when-populate-entries.html @@ -0,0 +1,30 @@ + + + + +This test validates the functionality of onresourcetimingbufferfull in resource timing. + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/buffered-flag.any.js b/test/fixtures/wpt/resource-timing/buffered-flag.any.js new file mode 100644 index 00000000000000..b46fd00e69ddfc --- /dev/null +++ b/test/fixtures/wpt/resource-timing/buffered-flag.any.js @@ -0,0 +1,18 @@ +async_test(t => { + performance.clearResourceTimings(); + // First observer creates second in callback to ensure the entry has been dispatched by the time + // the second observer begins observing. + new PerformanceObserver(() => { + // Second observer requires 'buffered: true' to see an entry. + new PerformanceObserver(t.step_func_done(list => { + const entries = list.getEntries(); + assert_equals(entries.length, 1, 'There should be 1 resource entry.'); + assert_equals(entries[0].entryType, 'resource'); + assert_greater_than(entries[0].startTime, 0); + assert_greater_than(entries[0].responseEnd, entries[0].startTime); + assert_greater_than(entries[0].duration, 0); + assert_true(entries[0].name.endsWith('resources/empty.js')); + })).observe({'type': 'resource', buffered: true}); + }).observe({'entryTypes': ['resource']}); + fetch('resources/empty.js'); +}, 'PerformanceObserver with buffered flag sees previous resource entries.'); diff --git a/test/fixtures/wpt/resource-timing/cached-image-gets-single-entry.html b/test/fixtures/wpt/resource-timing/cached-image-gets-single-entry.html new file mode 100644 index 00000000000000..2d8c4e2e83944d --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cached-image-gets-single-entry.html @@ -0,0 +1,67 @@ + + + + +Resource Timing: test behavior for cached resources + + + + + + + +

Description

+

Test that a reused resource only appears in the buffer once.

+ + + diff --git a/test/fixtures/wpt/resource-timing/clear-resource-timings.html b/test/fixtures/wpt/resource-timing/clear-resource-timings.html new file mode 100644 index 00000000000000..7508f8432e66f5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/clear-resource-timings.html @@ -0,0 +1,22 @@ + + + + +This test validates the functionality of clearResourceTimings method +in resource timing. + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/connection-reuse.html b/test/fixtures/wpt/resource-timing/connection-reuse.html new file mode 100644 index 00000000000000..a1bc927cfd3f2b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/connection-reuse.html @@ -0,0 +1,56 @@ + + + + +Resource Timing connection reuse + + + + + + + + + + +

Description

+

See the included test + script

+ + diff --git a/test/fixtures/wpt/resource-timing/connection-reuse.https.html b/test/fixtures/wpt/resource-timing/connection-reuse.https.html new file mode 100644 index 00000000000000..3461eed47249df --- /dev/null +++ b/test/fixtures/wpt/resource-timing/connection-reuse.https.html @@ -0,0 +1,25 @@ + + + + +Resource Timing connection reuse + + + + + + + + + +

Description

+

See the included test + script

+ + diff --git a/test/fixtures/wpt/resource-timing/cors-preflight.any.js b/test/fixtures/wpt/resource-timing/cors-preflight.any.js new file mode 100644 index 00000000000000..4b980e7d0af89e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cors-preflight.any.js @@ -0,0 +1,49 @@ +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js + +// Because apache decrements the Keep-Alive max value on each request, the +// transferSize will vary slightly between requests for the same resource. +const fuzzFactor = 3; // bytes + +const {HTTP_REMOTE_ORIGIN} = get_host_info(); +const url = new URL('/resource-timing/resources/preflight.py', + HTTP_REMOTE_ORIGIN).href; + +// The header bytes are expected to be > |minHeaderSize| and +// < |maxHeaderSize|. If they are outside this range the test will fail. +const minHeaderSize = 100; +const maxHeaderSize = 1024; + +promise_test(async () => { + const checkCorsAllowed = response => response.arrayBuffer(); + const requirePreflight = {headers: {'X-Require-Preflight' : '1'}}; + const collectEntries = new Promise(resolve => { + let entriesSeen = []; + new PerformanceObserver(entryList => { + entriesSeen = entriesSeen.concat(entryList.getEntries()); + if (entriesSeen.length > 2) { + throw new Error(`Saw too many PerformanceResourceTiming entries ` + + `(${entriesSeen.length})`); + } + if (entriesSeen.length == 2) { + resolve(entriesSeen); + } + }).observe({"type": "resource"}); + }); + + // Although this fetch doesn't send a pre-flight request, the server response + // will allow cross-origin requests explicitly with the + // Access-Control-Allow-Origin header. + await fetch(url).then(checkCorsAllowed); + + // This fetch will send a pre-flight request to do the CORS handshake + // explicitly. + await fetch(url, requirePreflight).then(checkCorsAllowed); + + const entries = await collectEntries; + assert_greater_than(entries[0].transferSize, 0, 'No-preflight transferSize'); + const lowerBound = entries[0].transferSize - fuzzFactor; + const upperBound = entries[0].transferSize + fuzzFactor; + assert_between_exclusive(entries[1].transferSize, lowerBound, upperBound, + 'Preflighted transferSize'); +}, 'PerformanceResourceTiming sizes fetch with preflight test'); diff --git a/test/fixtures/wpt/resource-timing/cross-origin-iframe.html b/test/fixtures/wpt/resource-timing/cross-origin-iframe.html new file mode 100644 index 00000000000000..69daebffaf2a24 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cross-origin-iframe.html @@ -0,0 +1,33 @@ + + +Test ResourceTiming reporting for cross-origin iframe. + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/cross-origin-redirects.html b/test/fixtures/wpt/resource-timing/cross-origin-redirects.html new file mode 100644 index 00000000000000..0bdc0547e577f6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cross-origin-redirects.html @@ -0,0 +1,102 @@ + + + + +This test validates the values in resource timing for cross-origin +redirects. + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/cross-origin-start-end-time-with-redirects.html b/test/fixtures/wpt/resource-timing/cross-origin-start-end-time-with-redirects.html new file mode 100644 index 00000000000000..1b107d3aef7764 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cross-origin-start-end-time-with-redirects.html @@ -0,0 +1,32 @@ + + + + +This test validates the values in resource timing for cross-origin +redirects. + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/cross-origin-status-codes.html b/test/fixtures/wpt/resource-timing/cross-origin-status-codes.html new file mode 100644 index 00000000000000..197a7663396ef6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/cross-origin-status-codes.html @@ -0,0 +1,70 @@ + + + +Resource Timing: PerformanceResourceTiming attributes shouldn't change + if the HTTP status code changes + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/document-domain-no-impact-opener.html b/test/fixtures/wpt/resource-timing/document-domain-no-impact-opener.html new file mode 100644 index 00000000000000..69df2f27fa5b19 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/document-domain-no-impact-opener.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/entries-for-network-errors.sub.https.html b/test/fixtures/wpt/resource-timing/entries-for-network-errors.sub.https.html new file mode 100644 index 00000000000000..95849d282621ff --- /dev/null +++ b/test/fixtures/wpt/resource-timing/entries-for-network-errors.sub.https.html @@ -0,0 +1,35 @@ + + + + +This test validates that a failed cross-origin fetch creates an opaque network timing entry. + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/entry-attributes.html b/test/fixtures/wpt/resource-timing/entry-attributes.html new file mode 100644 index 00000000000000..94f219f229107e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/entry-attributes.html @@ -0,0 +1,39 @@ + + + + +Resource Timing: PerformanceResourceTiming attributes + + + + + + + + + +

Description

+

This test validates that PerformanceResourceTiming entries' attributes are +populated with the correct values.

+ + diff --git a/test/fixtures/wpt/resource-timing/event-source-timing.html b/test/fixtures/wpt/resource-timing/event-source-timing.html new file mode 100644 index 00000000000000..917e7c34952cd6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/event-source-timing.html @@ -0,0 +1,32 @@ + + + + + +Resource Timing: EventSource timing behavior + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/fetch-cross-origin-redirect.https.html b/test/fixtures/wpt/resource-timing/fetch-cross-origin-redirect.https.html new file mode 100644 index 00000000000000..4193422653a595 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/fetch-cross-origin-redirect.https.html @@ -0,0 +1,33 @@ + + +Test cross-origin fetch redirects have the right values. + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/font-timestamps.html b/test/fixtures/wpt/resource-timing/font-timestamps.html new file mode 100644 index 00000000000000..56ecb5c4bfddb6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/font-timestamps.html @@ -0,0 +1,62 @@ + + +Test cross-origin fetch redirects have the right values. + + + + + diff --git a/test/fixtures/wpt/resource-timing/frameset-timing.html b/test/fixtures/wpt/resource-timing/frameset-timing.html new file mode 100644 index 00000000000000..1a6facbfc4d623 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/frameset-timing.html @@ -0,0 +1,6 @@ + + +Test the sequence of events when reporting timing for frames. + + + diff --git a/test/fixtures/wpt/resource-timing/idlharness.any.js b/test/fixtures/wpt/resource-timing/idlharness.any.js new file mode 100644 index 00000000000000..aa860d3dd16a71 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/idlharness.any.js @@ -0,0 +1,24 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +// https://w3c.github.io/resource-timing/ + +idl_test( + ['resource-timing'], + ['performance-timeline', 'hr-time', 'dom', 'html'], + idl_array => { + try { + self.resource = performance.getEntriesByType('resource')[0]; + } catch (e) { + // Will be surfaced when resource is undefined below. + } + + idl_array.add_objects({ + Performance: ['performance'], + PerformanceResourceTiming: ['resource'] + }); + } +); diff --git a/test/fixtures/wpt/resource-timing/iframe-failed-commit.html b/test/fixtures/wpt/resource-timing/iframe-failed-commit.html new file mode 100644 index 00000000000000..1da207d2fbe05e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/iframe-failed-commit.html @@ -0,0 +1,108 @@ + + + + +Resource Timing - test that unsuccessful iframes create entries + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/iframe-non-html.html b/test/fixtures/wpt/resource-timing/iframe-non-html.html new file mode 100644 index 00000000000000..a5df3b034899d1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/iframe-non-html.html @@ -0,0 +1,23 @@ + + +Test the sequence of events when reporting iframe timing. + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/iframe-redirect-without-location.html b/test/fixtures/wpt/resource-timing/iframe-redirect-without-location.html new file mode 100644 index 00000000000000..bae5f3112fe291 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/iframe-redirect-without-location.html @@ -0,0 +1,17 @@ + + +Test the sequence of events when reporting iframe timing. + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/iframe-sequence-of-events.html b/test/fixtures/wpt/resource-timing/iframe-sequence-of-events.html new file mode 100644 index 00000000000000..5f99a5cab2de6b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/iframe-sequence-of-events.html @@ -0,0 +1,12 @@ + + +Test the sequence of events when reporting iframe timing. + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/iframe-with-download.html b/test/fixtures/wpt/resource-timing/iframe-with-download.html new file mode 100644 index 00000000000000..9583024dd8faa9 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/iframe-with-download.html @@ -0,0 +1,24 @@ + + +Test the sequence of events when reporting iframe timing. + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/image-sequence-of-events.html b/test/fixtures/wpt/resource-timing/image-sequence-of-events.html new file mode 100644 index 00000000000000..630fed78c964bd --- /dev/null +++ b/test/fixtures/wpt/resource-timing/image-sequence-of-events.html @@ -0,0 +1,29 @@ + + +Test the sequence of events when reporting image timing. + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type-for-script.html b/test/fixtures/wpt/resource-timing/initiator-type-for-script.html new file mode 100644 index 00000000000000..72173398d56aa4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type-for-script.html @@ -0,0 +1,67 @@ + + + + +This test validates that the initiatorType information for various +Resource Timing entries is accurate for scripts. + + + + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/audio.html b/test/fixtures/wpt/resource-timing/initiator-type/audio.html new file mode 100644 index 00000000000000..f09fc618478fd6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/audio.html @@ -0,0 +1,34 @@ + + + + +Resource Timing initiator type: audio + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/dynamic-insertion.html b/test/fixtures/wpt/resource-timing/initiator-type/dynamic-insertion.html new file mode 100644 index 00000000000000..8ce05b3cfeddd6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/dynamic-insertion.html @@ -0,0 +1,41 @@ + + + + +Resource Timing - initiatorType with dynamic insertion + + + + + + + + + +

Description

+

This test validates that the initiatorType field is correct even when an +element is dynamically inserted.

+ + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/embed.html b/test/fixtures/wpt/resource-timing/initiator-type/embed.html new file mode 100644 index 00000000000000..c7a505afac90c3 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/embed.html @@ -0,0 +1,20 @@ + + + + +Resource Timing initiator type: embed + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/frameset.html b/test/fixtures/wpt/resource-timing/initiator-type/frameset.html new file mode 100644 index 00000000000000..697549a14dd97a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/frameset.html @@ -0,0 +1,22 @@ + + + + +Resource Timing initiator type: frameset + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/iframe.html b/test/fixtures/wpt/resource-timing/initiator-type/iframe.html new file mode 100644 index 00000000000000..0becd86894be23 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/iframe.html @@ -0,0 +1,19 @@ + + + + +Resource Timing initiator type: iframe + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/img-srcset.html b/test/fixtures/wpt/resource-timing/initiator-type/img-srcset.html new file mode 100644 index 00000000000000..b8c81fbb251d5a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/img-srcset.html @@ -0,0 +1,21 @@ + + + + +Resource Timing initiator type: img with srcset attribute + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/img.html b/test/fixtures/wpt/resource-timing/initiator-type/img.html new file mode 100644 index 00000000000000..8e2d305048ea51 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/img.html @@ -0,0 +1,19 @@ + + + + +Resource Timing initiator type: img + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/input.html b/test/fixtures/wpt/resource-timing/initiator-type/input.html new file mode 100644 index 00000000000000..a46d416671c1c6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/input.html @@ -0,0 +1,19 @@ + + + + +Resource Timing initiator type: input + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/link.html b/test/fixtures/wpt/resource-timing/initiator-type/link.html new file mode 100644 index 00000000000000..c49576a8e650bc --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/link.html @@ -0,0 +1,38 @@ + + + + +Resource Timing initiator type: link + + + + + + + + + + + + + + + +
    This content forces a font to get fetched
+ + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/misc.html b/test/fixtures/wpt/resource-timing/initiator-type/misc.html new file mode 100644 index 00000000000000..02d01a16336c93 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/misc.html @@ -0,0 +1,31 @@ + + + + +Resource Timing initiator type: miscellaneous elements + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/picture.html b/test/fixtures/wpt/resource-timing/initiator-type/picture.html new file mode 100644 index 00000000000000..e384b9e97dbe8b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/picture.html @@ -0,0 +1,39 @@ + + + + +Resource Timing initiator type: picture + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/resources/initiator-type-test.js b/test/fixtures/wpt/resource-timing/initiator-type/resources/initiator-type-test.js new file mode 100644 index 00000000000000..2b1f844376c34f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/resources/initiator-type-test.js @@ -0,0 +1,15 @@ + +if (observe_entry === undefined) { + throw new Error("You must include resource-timing/resources/observe-entry.js " + + "before including this script."); +} + +// Asserts that, for the given name, there is/will-be a +// PerformanceResourceTiming entry that has the given 'initiatorType'. The test +// is labeled according to the given descriptor. +const initiator_type_test = (entry_name, expected_initiator, descriptor) => { + promise_test(async () => { + const entry = await observe_entry(entry_name); + assert_equals(entry.initiatorType, expected_initiator); + }, `The initiator type for ${descriptor} must be '${expected_initiator}'`); +}; diff --git a/test/fixtures/wpt/resource-timing/initiator-type/script.html b/test/fixtures/wpt/resource-timing/initiator-type/script.html new file mode 100644 index 00000000000000..dbd6a131decae1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/script.html @@ -0,0 +1,26 @@ + + + + +Resource Timing initiator type: script + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/style.html b/test/fixtures/wpt/resource-timing/initiator-type/style.html new file mode 100644 index 00000000000000..051496b766f91b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/style.html @@ -0,0 +1,45 @@ + + + + +Resource Timing initiator type: style + + + + + + + + + + +
    +
  • This content forces the 'list-style-image' resource to be fetched.
  • +
+
This content forces the '@font-face' resource to be fetched.
+ + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/svg.html b/test/fixtures/wpt/resource-timing/initiator-type/svg.html new file mode 100644 index 00000000000000..d92f5935d807ee --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/svg.html @@ -0,0 +1,23 @@ + + + + +Resource Timing initiator type: svg + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/video.html b/test/fixtures/wpt/resource-timing/initiator-type/video.html new file mode 100644 index 00000000000000..16f3b3dea5f1e8 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/video.html @@ -0,0 +1,32 @@ + + + + +Resource Timing initiator type: video + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/initiator-type/workers.html b/test/fixtures/wpt/resource-timing/initiator-type/workers.html new file mode 100644 index 00000000000000..3a23ad71a31555 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/initiator-type/workers.html @@ -0,0 +1,23 @@ + + + + +Resource Timing initiatorType: worker resources + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/input-sequence-of-events.html b/test/fixtures/wpt/resource-timing/input-sequence-of-events.html new file mode 100644 index 00000000000000..446e24a0bca8a4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/input-sequence-of-events.html @@ -0,0 +1,21 @@ + + +Test the sequence of events when reporting input timing. + + + + diff --git a/test/fixtures/wpt/resource-timing/link-sequence-of-events.html b/test/fixtures/wpt/resource-timing/link-sequence-of-events.html new file mode 100644 index 00000000000000..be9db32cd92968 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/link-sequence-of-events.html @@ -0,0 +1,30 @@ + + +Test the sequence of events when reporting link timing. + + + + + diff --git a/test/fixtures/wpt/resource-timing/load-from-mem-cache-transfer-size.html b/test/fixtures/wpt/resource-timing/load-from-mem-cache-transfer-size.html new file mode 100644 index 00000000000000..3d2d32d6e63bf4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/load-from-mem-cache-transfer-size.html @@ -0,0 +1,65 @@ + + + + + + This tests transfer size of resource timing when loaded from memory cache. + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/nested-context-navigations-embed.html b/test/fixtures/wpt/resource-timing/nested-context-navigations-embed.html new file mode 100644 index 00000000000000..f804adbb8a99e4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/nested-context-navigations-embed.html @@ -0,0 +1,36 @@ + + + + + +Resource Timing embed navigate - back button navigation + + + + + + diff --git a/test/fixtures/wpt/resource-timing/nested-context-navigations-iframe.html b/test/fixtures/wpt/resource-timing/nested-context-navigations-iframe.html new file mode 100644 index 00000000000000..32ab21633c669c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/nested-context-navigations-iframe.html @@ -0,0 +1,32 @@ + + + + + +Resource Timing embed navigate - back button navigation + + + + + + diff --git a/test/fixtures/wpt/resource-timing/nested-context-navigations-object.html b/test/fixtures/wpt/resource-timing/nested-context-navigations-object.html new file mode 100644 index 00000000000000..1508d8829fd269 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/nested-context-navigations-object.html @@ -0,0 +1,37 @@ + + + + + +Resource Timing embed navigate - back button navigation + + + + + + diff --git a/test/fixtures/wpt/resource-timing/nextHopProtocol-is-tao-protected.https.html b/test/fixtures/wpt/resource-timing/nextHopProtocol-is-tao-protected.https.html new file mode 100644 index 00000000000000..b16ff7af75dc7a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/nextHopProtocol-is-tao-protected.https.html @@ -0,0 +1,49 @@ + + + + +Resource Timing - Check that nextHopProtocol is TAO protected + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html b/test/fixtures/wpt/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html new file mode 100644 index 00000000000000..63f9e06e19083a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/no-entries-for-cross-origin-css-fetched.sub.html @@ -0,0 +1,39 @@ + + +Make sure that resources fetched by cross origin CSS are not in the timeline. + + + + + + +
    Some content
+ + diff --git a/test/fixtures/wpt/resource-timing/object-not-found-adds-entry.html b/test/fixtures/wpt/resource-timing/object-not-found-adds-entry.html new file mode 100644 index 00000000000000..d11823dd9d07f5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/object-not-found-adds-entry.html @@ -0,0 +1,37 @@ + + + + +This test validates that object resource emit resource timing entries. + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html b/test/fixtures/wpt/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html new file mode 100644 index 00000000000000..d0dad9381ba42b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html @@ -0,0 +1,47 @@ + + + + +This test validates resource timing information for a timing allowed cross-origin redirect chain. + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/object-not-found-after-cross-origin-redirect.html b/test/fixtures/wpt/resource-timing/object-not-found-after-cross-origin-redirect.html new file mode 100644 index 00000000000000..278c78e320e98c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/object-not-found-after-cross-origin-redirect.html @@ -0,0 +1,35 @@ + + + + +This test validates the values in resource timing for cross-origin +redirects. + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/opaque-origin.html b/test/fixtures/wpt/resource-timing/opaque-origin.html new file mode 100644 index 00000000000000..598ee50a59278f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/opaque-origin.html @@ -0,0 +1,46 @@ + + + + +Resource Timing TAO - "null" and opaque origin + + + + + + + +

Description

+

This test validates that, for a cross origin resource, the timing allow +check algorithm will correctly distinguish between 'null' and 'Null' values in +the Timing-Allow-Origin header. An opaque origin's serialization is the string +"null" and the timing allow origin check needs to do a case-sensitive comparison +to the Timing-Allow-Origin header. +

+ + + + diff --git a/test/fixtures/wpt/resource-timing/ping-rt-entries.html b/test/fixtures/wpt/resource-timing/ping-rt-entries.html new file mode 100644 index 00000000000000..34dad10b9f28b6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/ping-rt-entries.html @@ -0,0 +1,29 @@ + + + + +Resource Timing Entry For hyperlink audit (ping) + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/redirects.html b/test/fixtures/wpt/resource-timing/redirects.html new file mode 100644 index 00000000000000..ba69907a5f6e46 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/redirects.html @@ -0,0 +1,61 @@ + + + + +Resource Timing: resources fetched through same-origin redirects + + + + + + + + + + +

Description

+

This test validates that, when a fetching resources that encounter +same-origin redirects, attributes of the PerformanceResourceTiming entry +conform to the specification.

+ + diff --git a/test/fixtures/wpt/resource-timing/render-blocking-status-link.html b/test/fixtures/wpt/resource-timing/render-blocking-status-link.html new file mode 100644 index 00000000000000..8c6544db48664e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/render-blocking-status-link.html @@ -0,0 +1,222 @@ + + + +This test validates the render blocking status of resources. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/render-blocking-status-script.html b/test/fixtures/wpt/resource-timing/render-blocking-status-script.html new file mode 100644 index 00000000000000..bcd55b89944fc6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/render-blocking-status-script.html @@ -0,0 +1,196 @@ + + + +This test validates the render blocking status of resources. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resource-ignore-data-url.html b/test/fixtures/wpt/resource-timing/resource-ignore-data-url.html new file mode 100644 index 00000000000000..a7056a80807967 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource-ignore-data-url.html @@ -0,0 +1,39 @@ + + + + +Resource Timing ignores resources with data: URIs + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resource-reload-TAO.html b/test/fixtures/wpt/resource-timing/resource-reload-TAO.html new file mode 100644 index 00000000000000..83a1e921bb4f65 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource-reload-TAO.html @@ -0,0 +1,18 @@ + + + + +Resource Timing - TAO on reload + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resource-timing-level1.js b/test/fixtures/wpt/resource-timing/resource-timing-level1.js new file mode 100644 index 00000000000000..95b5cdfb1ed0ca --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource-timing-level1.js @@ -0,0 +1,517 @@ +"use strict"; + +window.onload = + function () { + setup({ explicit_timeout: true }); + + /** Number of milliseconds to delay when the server injects pauses into the response. + + This should be large enough that we can distinguish it from noise with high confidence, + but small enough that tests complete quickly. */ + var serverStepDelay = 250; + + var mimeHtml = "text/html"; + var mimeText = "text/plain"; + var mimePng = "image/png"; + var mimeScript = "application/javascript"; + var mimeCss = "text/css"; + + /** Hex encoding of a a 150x50px green PNG. */ + var greenPng = "0x89504E470D0A1A0A0000000D494844520000006400000032010300000090FBECFD00000003504C544500FF00345EC0A80000000F49444154281563601805A36068020002BC00011BDDE3900000000049454E44AE426082"; + + /** Array containing test cases to run. Initially, it contains the one-off 'about:blank" test, + but additional cases are pushed below by expanding templates. */ + var testCases = [ + { + description: "No timeline entry for about:blank", + test: + function (test) { + // Insert an empty IFrame. + var frame = document.createElement("iframe"); + + // Wait for the IFrame to load and ensure there is no resource entry for it on the timeline. + // + // We use the 'createOnloadCallbackFn()' helper which is normally invoked by 'initiateFetch()' + // to avoid setting the IFrame's src. It registers a test step for us, finds our entry on the + // resource timeline, and wraps our callback function to automatically vet invariants. + frame.onload = createOnloadCallbackFn(test, frame, "about:blank", + function (initiator, entry) { + assert_equals(entry, undefined, "Inserting an IFrame with a src of 'about:blank' must not add an entry to the timeline."); + assertInvariants( + test, + function () { + test.done(); + }); + }); + + document.body.appendChild(frame); + + // Paranoid check that the new IFrame has loaded about:blank. + assert_equals( + frame.contentWindow.location.href, + "about:blank", + "'Src' of new + + diff --git a/test/fixtures/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html b/test/fixtures/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html new file mode 100644 index 00000000000000..a46d14c9f159ad --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_connection_reuse_mixed_content_redirect.html @@ -0,0 +1,55 @@ + + + + +Resource Timing connection reuse + + + + + + + + +

Description

+

This test validates that connectStart and connectEnd are the same when a connection is reused (e.g. when a persistent connection is used).

+
+ + + diff --git a/test/fixtures/wpt/resource-timing/resource_dedicated_worker.html b/test/fixtures/wpt/resource-timing/resource_dedicated_worker.html new file mode 100644 index 00000000000000..6d27245ab9cc9a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_dedicated_worker.html @@ -0,0 +1,28 @@ + + + + +Resource Timing in dedicated workers + + + + + + + + + + +

Description

+

This test validates that resources requested by dedicated workers don't appear in the main document.

+ + diff --git a/test/fixtures/wpt/resource-timing/resource_nested_dedicated_worker.worker.js b/test/fixtures/wpt/resource-timing/resource_nested_dedicated_worker.worker.js new file mode 100644 index 00000000000000..2c9f5f95428aca --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_nested_dedicated_worker.worker.js @@ -0,0 +1,17 @@ +importScripts("/resources/testharness.js"); + +async_test(function() { + const worker = new Worker('resources/worker_with_images.js'); + worker.onmessage = this.step_func_done((event) => { + const childNumEntries = event.data; + assert_equals(2, childNumEntries, + "There should be two resource timing entries: 2 image XHRs"); + + const parentNumEntries = performance.getEntries().length; + assert_equals(2, parentNumEntries, + "There should be two resource timing entries: " + + "one is for importScripts() and the another is for a nested worker"); + worker.terminate(); + }); +}, "Resource timing for nested dedicated workers"); +done(); diff --git a/test/fixtures/wpt/resource-timing/resource_reparenting.html b/test/fixtures/wpt/resource-timing/resource_reparenting.html new file mode 100644 index 00000000000000..7d4947fa7703d1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_reparenting.html @@ -0,0 +1,53 @@ + + + + +Resource Timing reparenting elements + + + + + + + + + +

Description

+

This test validates that reparenting an element doesn't change the initiator document.

+
+ + + diff --git a/test/fixtures/wpt/resource-timing/resource_subframe_self_navigation.html b/test/fixtures/wpt/resource-timing/resource_subframe_self_navigation.html new file mode 100644 index 00000000000000..5843f88307233e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_subframe_self_navigation.html @@ -0,0 +1,53 @@ + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resource_timing.worker.js b/test/fixtures/wpt/resource-timing/resource_timing.worker.js new file mode 100644 index 00000000000000..dafd2e9af602ed --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_timing.worker.js @@ -0,0 +1,64 @@ +importScripts("/resources/testharness.js"); + +function check(initiatorType, protocol) { + let entries = performance.getEntries(); + assert_equals(entries.length, 1); + + assert_true(entries[0] instanceof PerformanceEntry); + assert_equals(entries[0].entryType, "resource"); + assert_true(entries[0].startTime > 0); + assert_true(entries[0].duration > 0); + + assert_true(entries[0] instanceof PerformanceResourceTiming); + assert_equals(entries[0].initiatorType, initiatorType); + assert_equals(entries[0].nextHopProtocol, protocol); +} + +async_test(t => { + performance.clearResourceTimings(); + + // Fetch + fetch("resources/empty.js") + .then(r => r.blob()) + .then(blob => { + check("fetch", "http/1.1"); + }) + + // XMLHttpRequest + .then(() => { + return new Promise(resolve => { + performance.clearResourceTimings(); + let xhr = new XMLHttpRequest(); + xhr.onload = () => { + check("xmlhttprequest", "http/1.1"); + resolve(); + }; + xhr.open("GET", "resources/empty.js"); + xhr.send(); + }); + }) + + // Sync XMLHttpREquest + .then(() => { + performance.clearResourceTimings(); + let xhr = new XMLHttpRequest(); + xhr.open("GET", "resources/empty.js", false); + xhr.send(); + + check("xmlhttprequest", "http/1.1"); + }) + + // ImportScripts + .then(() => { + performance.clearResourceTimings(); + importScripts(["resources/empty.js"]); + check("other", "http/1.1"); + }) + + // All done. + .then(() => { + t.done(); + }); +}, "Performance Resource Entries in workers"); + +done(); diff --git a/test/fixtures/wpt/resource-timing/resource_timing_content_length.html b/test/fixtures/wpt/resource-timing/resource_timing_content_length.html new file mode 100644 index 00000000000000..32bd8a97e07341 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resource_timing_content_length.html @@ -0,0 +1,35 @@ + + + + +This test validates the value of encodedBodySize in certain situations. + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/200.https.asis b/test/fixtures/wpt/resource-timing/resources/200.https.asis new file mode 100644 index 00000000000000..5b7c25f4ca03a2 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/200.https.asis @@ -0,0 +1,5 @@ +HTTP/1.0 200 OK +Content-Length: 0 +Timing-Allow-Origin: * + + diff --git a/test/fixtures/wpt/resource-timing/resources/200_empty.asis b/test/fixtures/wpt/resource-timing/resources/200_empty.asis new file mode 100644 index 00000000000000..b5d10bda324528 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/200_empty.asis @@ -0,0 +1,3 @@ +HTTP/1.0 200 OK +Content-Length: 0 + diff --git a/test/fixtures/wpt/resource-timing/resources/204_empty.asis b/test/fixtures/wpt/resource-timing/resources/204_empty.asis new file mode 100644 index 00000000000000..3d9151326b3544 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/204_empty.asis @@ -0,0 +1,3 @@ +HTTP/1.0 204 OK +Content-Length: 0 + diff --git a/test/fixtures/wpt/resource-timing/resources/205_empty.asis b/test/fixtures/wpt/resource-timing/resources/205_empty.asis new file mode 100644 index 00000000000000..2c06998c1eb19e --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/205_empty.asis @@ -0,0 +1,3 @@ +HTTP/1.0 205 OK +Content-Length: 0 + diff --git a/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html b/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html new file mode 100644 index 00000000000000..b8a1947b77e25a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html @@ -0,0 +1,10 @@ + + + + + Green Test Page + + +

Placeholder

+ + diff --git a/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html.headers b/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html.headers new file mode 100644 index 00000000000000..7296361df3d1c5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/blank-with-tao.html.headers @@ -0,0 +1 @@ +Timing-Allow-Origin: * diff --git a/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png b/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png new file mode 100644 index 00000000000000..820f8cace2143b Binary files /dev/null and b/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png differ diff --git a/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png.headers b/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png.headers new file mode 100644 index 00000000000000..7296361df3d1c5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/blue-with-tao.png.headers @@ -0,0 +1 @@ +Timing-Allow-Origin: * diff --git a/test/fixtures/wpt/resource-timing/resources/blue.png b/test/fixtures/wpt/resource-timing/resources/blue.png new file mode 100644 index 00000000000000..820f8cace2143b Binary files /dev/null and b/test/fixtures/wpt/resource-timing/resources/blue.png differ diff --git a/test/fixtures/wpt/resource-timing/resources/buffer-full-utilities.js b/test/fixtures/wpt/resource-timing/resources/buffer-full-utilities.js new file mode 100644 index 00000000000000..6cb1753b2e9fb8 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/buffer-full-utilities.js @@ -0,0 +1,75 @@ +// This script relies on resources/resource-loaders.js. Include it before in order for the below +// methods to work properly. + +// The resources used to trigger new entries. +const scriptResources = [ + 'resources/empty.js', + 'resources/empty_script.js', + 'resources/empty.js?id' +]; + +const waitForNextTask = () => { + return new Promise(resolve => { + step_timeout(resolve, 0); + }); +}; + +const clearBufferAndSetSize = size => { + performance.clearResourceTimings(); + performance.setResourceTimingBufferSize(size); +} + +const forceBufferFullEvent = async () => { + clearBufferAndSetSize(1); + return new Promise(async resolve => { + performance.addEventListener('resourcetimingbufferfull', resolve); + // Load 2 resources to ensure onresourcetimingbufferfull is fired. + // Load them in order in order to get the entries in that order! + await load.script(scriptResources[0]); + await load.script(scriptResources[1]); + }); +}; + +const fillUpTheBufferWithSingleResource = async (src = scriptResources[0]) => { + clearBufferAndSetSize(1); + await load.script(src); +}; + +const fillUpTheBufferWithTwoResources = async () => { + clearBufferAndSetSize(2); + // Load them in order in order to get the entries in that order! + await load.script(scriptResources[0]); + await load.script(scriptResources[1]); +}; + +const addAssertUnreachedBufferFull = t => { + performance.addEventListener('resourcetimingbufferfull', t.step_func(() => { + assert_unreached("resourcetimingbufferfull should not fire") + })); +}; + +const checkEntries = numEntries => { + const entries = performance.getEntriesByType('resource'); + assert_equals(entries.length, numEntries, + 'Number of entries does not match the expected value.'); + assert_true(entries[0].name.includes(scriptResources[0]), + scriptResources[0] + " is in the entries buffer"); + if (entries.length > 1) { + assert_true(entries[1].name.includes(scriptResources[1]), + scriptResources[1] + " is in the entries buffer"); + } + if (entries.length > 2) { + assert_true(entries[2].name.includes(scriptResources[2]), + scriptResources[2] + " is in the entries buffer"); + } +} + +const bufferFullFirePromise = new Promise(resolve => { + performance.addEventListener('resourcetimingbufferfull', async () => { + // Wait for the next task just to ensure that all bufferfull events have fired, and to ensure + // that the secondary buffer is copied (as this is an event, there may be microtask checkpoints + // right after running an event handler). + await waitForNextTask(); + resolve(); + }); +}); diff --git a/test/fixtures/wpt/resource-timing/resources/close.html b/test/fixtures/wpt/resource-timing/resources/close.html new file mode 100644 index 00000000000000..02c275f37b66e8 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/close.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/connection-reuse-test.js b/test/fixtures/wpt/resource-timing/resources/connection-reuse-test.js new file mode 100644 index 00000000000000..453fbd34051067 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/connection-reuse-test.js @@ -0,0 +1,63 @@ +// This script is loaded in HTTP and HTTPS contexts to validate +// PerformanceResourceTiming entries' attributes when reusing connections. +// +// Note: to ensure that we reuse the connection to fetch multiple resources, we +// use the same XMLHttpRequest object throughout an individual test. Although +// it doesn't seem to be specified, each browser tested by WPT will reuse the +// underlying TCP connection with this approach. Pre-establishing the XHR's +// connection helps us to test connection reuse also in browsers that may key +// their connections on the related request's credentials mode. + +const connection_reuse_test = (path, follow_on_assertions, test_label) => { + const {on_200, on_304} = follow_on_assertions; + + // Make the first request before calling 'attribute_test' so that only the + // second request's PerformanceResourceTiming entry will be interrogated. We + // don't check the first request's PerformanceResourceTiming entry because + // that's not what this test is trying to validate. + const client = new XMLHttpRequest(); + const identifier = Math.random(); + path = `${path}?tag=${identifier}`; + client.open("GET", path, false); + client.send(); + + attribute_test( + async () => { + client.open("GET", path + "&same_resource=false", false); + client.send(); + + // We expect to get a 200 Ok response because we've requested a different + // resource than previous requests. + if (client.status != 200) { + throw new Error(`Got something other than a 200 response. ` + + `client.status: ${client.status}`); + } + }, path, entry => { + invariants.assert_connection_reused(entry); + on_200(entry); + }, + `PerformanceResrouceTiming entries need to conform to the spec when a ` + + `distinct resource is fetched over a persistent connection ` + + `(${test_label})`); + + attribute_test( + async () => { + client.open("GET", path, false); + client.setRequestHeader("If-None-Match", identifier); + client.send(); + + // We expect to get a 304 Not Modified response because we've used a + // matching 'identifier' for the If-None-Match header. + if (client.status != 304) { + throw new Error(`Got something other than a 304 response. ` + + `client.status: ${client.status}, response: ` + + `'${client.responseText}'`); + } + }, path, entry => { + invariants.assert_connection_reused(entry); + on_304(entry); + }, + `PerformanceResrouceTiming entries need to conform to the spec when the ` + + `resource is cache-revalidated over a persistent connection ` + + `(${test_label})`); +} diff --git a/test/fixtures/wpt/resource-timing/resources/csp-default-none.html b/test/fixtures/wpt/resource-timing/resources/csp-default-none.html new file mode 100644 index 00000000000000..1f59d8c2250f7f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/csp-default-none.html @@ -0,0 +1,3 @@ + + +empty page diff --git a/test/fixtures/wpt/resource-timing/resources/csp-default-none.html.headers b/test/fixtures/wpt/resource-timing/resources/csp-default-none.html.headers new file mode 100644 index 00000000000000..d66f886dd23d44 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/csp-default-none.html.headers @@ -0,0 +1,2 @@ +Content-Security-Policy: default-src 'none' + diff --git a/test/fixtures/wpt/resource-timing/resources/document-domain-no-impact.html b/test/fixtures/wpt/resource-timing/resources/document-domain-no-impact.html new file mode 100644 index 00000000000000..64cdd8a8701eb4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/document-domain-no-impact.html @@ -0,0 +1,28 @@ + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/document-navigated.html b/test/fixtures/wpt/resource-timing/resources/document-navigated.html new file mode 100644 index 00000000000000..bedae770824a3c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/document-navigated.html @@ -0,0 +1,12 @@ + + + + + + + Navigated document! + + + diff --git a/test/fixtures/wpt/resource-timing/resources/document-refreshed.html b/test/fixtures/wpt/resource-timing/resources/document-refreshed.html new file mode 100644 index 00000000000000..568f7f27c7d950 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/document-refreshed.html @@ -0,0 +1,12 @@ + + + + + + + Refreshed document! + + + diff --git a/test/fixtures/wpt/resource-timing/resources/document-that-navigates.html b/test/fixtures/wpt/resource-timing/resources/document-that-navigates.html new file mode 100644 index 00000000000000..a59e9f3ab2b783 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/document-that-navigates.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/document-that-refreshes.html b/test/fixtures/wpt/resource-timing/resources/document-that-refreshes.html new file mode 100644 index 00000000000000..659513a642f382 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/document-that-refreshes.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/download.asis b/test/fixtures/wpt/resource-timing/resources/download.asis new file mode 100644 index 00000000000000..167386d7a52d7c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/download.asis @@ -0,0 +1,6 @@ +HTTP/1.0 200 OK +Content-Type: application/octet-stream + +12312313 + + diff --git a/test/fixtures/wpt/resource-timing/resources/embed-navigate-back.html b/test/fixtures/wpt/resource-timing/resources/embed-navigate-back.html new file mode 100644 index 00000000000000..c9c7340f5307da --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/embed-navigate-back.html @@ -0,0 +1,18 @@ + + + + +Resource Timing embed navigate - back button navigation + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/embed-navigate.html b/test/fixtures/wpt/resource-timing/resources/embed-navigate.html new file mode 100644 index 00000000000000..24c9d3c462730f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/embed-navigate.html @@ -0,0 +1,18 @@ + + + + +Resource Timing embed navigate + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/embed-refresh.html b/test/fixtures/wpt/resource-timing/resources/embed-refresh.html new file mode 100644 index 00000000000000..bd4b5a14de9779 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/embed-refresh.html @@ -0,0 +1,18 @@ + + + + +Resource Timing embed refresh + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/empty.js b/test/fixtures/wpt/resource-timing/resources/empty.js new file mode 100644 index 00000000000000..3b44754e301ded --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/empty.js @@ -0,0 +1 @@ +/* Nothing here */ diff --git a/test/fixtures/wpt/resource-timing/resources/empty_script.js b/test/fixtures/wpt/resource-timing/resources/empty_script.js new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/fixtures/wpt/resource-timing/resources/empty_style.css b/test/fixtures/wpt/resource-timing/resources/empty_style.css new file mode 100644 index 00000000000000..eb90b432e8421f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/empty_style.css @@ -0,0 +1 @@ +/*Nothing here*/ diff --git a/test/fixtures/wpt/resource-timing/resources/entry-invariants.js b/test/fixtures/wpt/resource-timing/resources/entry-invariants.js new file mode 100644 index 00000000000000..4bef9496103ca6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/entry-invariants.js @@ -0,0 +1,507 @@ +// Asserts that the given attributes are present in 'entry' and hold equal +// values. +const assert_all_equal_ = (entry, attributes) => { + let first = attributes[0]; + attributes.slice(1).forEach(other => { + assert_equals(entry[first], entry[other], + `${first} should be equal to ${other}`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold values +// that are sorted in the same order as given in 'attributes'. +const assert_ordered_ = (entry, attributes) => { + let before = attributes[0]; + attributes.slice(1).forEach(after => { + assert_greater_than_equal(entry[after], entry[before], + `${after} should be greater than ${before}`); + before = after; + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0. +const assert_zeroed_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_equals(entry[attribute], 0, `${attribute} should be 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0 or more. +const assert_not_negative_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than_equal(entry[attribute], 0, + `${attribute} should be greater than or equal to 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value +// greater than 0. +const assert_positive_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than(entry[attribute], 0, + `${attribute} should be greater than 0`); + }); +} + +const invariants = { + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP without + // redirects but passing the Timing-Allow-Origin checks. + assert_tao_pass_no_redirect_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + "encodedBodySize", + "decodedBodySize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for empty response bodies. + assert_tao_pass_no_redirect_http_empty: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + "encodedBodySize", + "decodedBodySize", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for resources fetched over HTTPS + assert_tao_pass_no_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + "encodedBodySize", + "decodedBodySize", + ]); + }, + + // Like assert_tao_pass_no_redirect_https but for resources that did encounter + // at least one HTTP redirect. + assert_tao_pass_with_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + "encodedBodySize", + "decodedBodySize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but, since the resource's bytes + // won't be retransmitted, the encoded and decoded sizes must be zero. + assert_tao_pass_304_not_modified_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + "encodedBodySize", + "decodedBodySize", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_304_not_modified_http but for resources fetched over + // HTTPS. + assert_tao_pass_304_not_modified_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + "encodedBodySize", + "decodedBodySize", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource subsequently fetched over a + // persistent connection. When this happens, we expect that certain + // attributes describing transport layer behaviour will be equal. + assert_connection_reused: entry => { + assert_all_equal_(entry, [ + "fetchStart", + "connectStart", + "connectEnd", + "domainLookupStart", + "domainLookupEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP through an HTTP + // redirect. + assert_same_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + + assert_equals(entry.redirectStart, entry.startTime, + "redirectStart should be equal to startTime"); + + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTPS through a + // cross-origin redirect. + // (e.g. GET http://remote.com/foo => 302 Location: https://remote.com/foo) + assert_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 does not set a matching TAO header. + assert_http_to_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTPS request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_tao_enabled_cross_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "secureConnectionStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_http_to_tao_enabled_cross_origin_https_redirected_resource: entry => { + assert_zeroed_(entry, [ + // Note that, according to the spec, the secureConnectionStart attribute + // should describe the connection for the first resource request when + // there are redirects. Since the initial request is over HTTP, + // secureConnectionStart must be 0. + "secureConnectionStart", + ]); + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + assert_same_origin_redirected_from_cross_origin_resource: entry => { + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + "encodedBodySize", + "decodedBodySize", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_equals(entry.fetchStart, entry.startTime, + "fetchStart must equal startTime"); + }, + + assert_tao_failure_resource: entry => { + assert_equals(entry.entryType, "resource", "entryType must always be 'resource'"); + + assert_positive_(entry, [ + "startTime", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + "encodedBodySize", + "decodedBodySize", + ]); + } + +}; + +const attribute_test_internal = (loader, path, validator, run_test, test_label) => { + promise_test( + async () => { + let loaded_entry = new Promise((resolve, reject) => { + new PerformanceObserver((entry_list, self) => { + try { + const name_matches = entry_list.getEntries().forEach(entry => { + if (entry.name.includes(path)) { + resolve(entry); + } + }); + } catch(e) { + // By surfacing exceptions through the Promise interface, tests can + // fail fast with a useful message instead of timing out. + reject(e); + } + }).observe({"type": "resource"}); + }); + + await loader(path, validator); + const entry = await(loaded_entry); + run_test(entry); + }, test_label); +}; + +// Given a resource-loader, a path (a relative path or absolute URL), and a +// PerformanceResourceTiming test, applies the loader to the resource path +// and tests the resulting PerformanceResourceTiming entry. +const attribute_test = (loader, path, run_test, test_label) => { + attribute_test_internal(loader, path, () => {}, run_test, test_label); +}; + +// Similar to attribute test, but on top of that, validates the added element, +// to ensure the test does what it intends to do. +const attribute_test_with_validator = (loader, path, validator, run_test, test_label) => { + attribute_test_internal(loader, path, validator, run_test, test_label); +}; + +const network_error_entry_test = (originalURL, args, label) => { + const url = new URL(originalURL, location.href); + const search = new URLSearchParams(url.search.substr(1)); + const timeBefore = performance.now(); + loader = () => new Promise(resolve => fetch(url, args).catch(resolve)); + + attribute_test( + loader, url, + () => { + const timeAfter = performance.now(); + const names = performance.getEntriesByType('resource').filter(e => e.initiatorType === 'fetch').map(e => e.name); + const entries = performance.getEntriesByName(url.toString()); + assert_equals(entries.length, 1, 'resource timing entry for network error'); + const entry = entries[0] + assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal'); + assert_greater_than_equal(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching'); + assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch'); + invariants.assert_tao_failure_resource(entry); + }, `A ResourceTiming entry should be created for network error of type ${label}`); +} diff --git a/test/fixtures/wpt/resource-timing/resources/fake_responses.html b/test/fixtures/wpt/resource-timing/resources/fake_responses.html new file mode 100644 index 00000000000000..52cad6c41570e1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/fake_responses.html @@ -0,0 +1,19 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/fake_responses_https.sub.html b/test/fixtures/wpt/resource-timing/resources/fake_responses_https.sub.html new file mode 100644 index 00000000000000..21f1f02a6747f1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/fake_responses_https.sub.html @@ -0,0 +1,18 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html b/test/fixtures/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html new file mode 100644 index 00000000000000..2ee92b2a5511c0 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/fake_responses_https_redirect.sub.html @@ -0,0 +1,20 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/frame-timing.js b/test/fixtures/wpt/resource-timing/resources/frame-timing.js new file mode 100644 index 00000000000000..e0c364e9b2c3e2 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/frame-timing.js @@ -0,0 +1,48 @@ +function test_frame_timing_before_load_event(type) { + promise_test(async t => { + const {document, performance} = type === 'frame' ? window.parent : window; + const delay = 500; + const frame = document.createElement(type); + t.add_cleanup(() => frame.remove()); + await new Promise(resolve => { + frame.addEventListener('load', resolve); + frame.src = `resources/iframe-with-delay.sub.html?delay=${delay}`; + (type === 'frame' ? document.querySelector('frameset') : document.body).appendChild(frame); + }); + + const entries = performance.getEntriesByName(frame.src); + const navigationEntry = frame.contentWindow.performance.getEntriesByType('navigation')[0]; + assert_equals(entries.length, 1); + assert_equals(entries[0].initiatorType, type); + assert_greater_than(performance.now(), entries[0].responseEnd + delay); + const domContentLoadedEventAbsoluteTime = navigationEntry.domContentLoadedEventStart + frame.contentWindow.performance.timeOrigin; + const frameResponseEndAbsoluteTime = entries[0].responseEnd + performance.timeOrigin; + assert_greater_than(domContentLoadedEventAbsoluteTime, frameResponseEndAbsoluteTime); + }, `A ${type} should report its RT entry when the response is done and before it is completely loaded`); +} + + +function test_frame_timing_change_src(type) { + promise_test(async t => { + const {document, performance} = type === 'frame' ? window.parent : window; + const frame = document.createElement(type); + t.add_cleanup(() => frame.remove()); + await new Promise(resolve => { + const done = () => { + resolve(); + frame.removeEventListener('load', done); + } + frame.addEventListener('load', done); + frame.src = 'resources/green.html?1'; + (type === 'frame' ? document.querySelector('frameset') : document.body).appendChild(frame); + }); + + await new Promise(resolve => { + frame.addEventListener('load', resolve); + frame.src = 'resources/green.html?2'; + }); + + const entries = performance.getEntries().filter(e => e.name.includes('green.html')); + assert_equals(entries.length, 2); + }, `A ${type} should report separate RT entries if its src changed from the outside`); +} \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/frameset-timing-frame.html b/test/fixtures/wpt/resource-timing/resources/frameset-timing-frame.html new file mode 100644 index 00000000000000..e260f575268e6c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/frameset-timing-frame.html @@ -0,0 +1,8 @@ + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/green-frame.html b/test/fixtures/wpt/resource-timing/resources/green-frame.html new file mode 100644 index 00000000000000..9613240ae38789 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/green-frame.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/green.html b/test/fixtures/wpt/resource-timing/resources/green.html new file mode 100644 index 00000000000000..b8a1947b77e25a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/green.html @@ -0,0 +1,10 @@ + + + + + Green Test Page + + +

Placeholder

+ + diff --git a/test/fixtures/wpt/resource-timing/resources/green.html.headers b/test/fixtures/wpt/resource-timing/resources/green.html.headers new file mode 100644 index 00000000000000..cb762eff806849 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/green.html.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: * diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html b/test/fixtures/wpt/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html new file mode 100644 index 00000000000000..97d77fcc58a3bb --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-TAO-crossorigin-port.sub.html @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html b/test/fixtures/wpt/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html new file mode 100644 index 00000000000000..6f37a33e8c2e83 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-load-from-mem-cache-transfer-size.html @@ -0,0 +1,24 @@ + + + + + + transfer size of resource timing when loaded from memory cache. + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-navigate-back.html b/test/fixtures/wpt/resource-timing/resources/iframe-navigate-back.html new file mode 100644 index 00000000000000..f944b633e2f207 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-navigate-back.html @@ -0,0 +1,18 @@ + + + + +Resource Timing iframe navigate - back button navigation + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-navigate.html b/test/fixtures/wpt/resource-timing/resources/iframe-navigate.html new file mode 100644 index 00000000000000..02868840218953 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-navigate.html @@ -0,0 +1,18 @@ + + + + +Resource Timing iframe navigate + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-refresh.html b/test/fixtures/wpt/resource-timing/resources/iframe-refresh.html new file mode 100644 index 00000000000000..862b96da9dae68 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-refresh.html @@ -0,0 +1,18 @@ + + + + +Resource Timing iframe refresh + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-reload-TAO.html b/test/fixtures/wpt/resource-timing/resources/iframe-reload-TAO.html new file mode 100644 index 00000000000000..461f43bf8e27b4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-reload-TAO.html @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-setdomain.sub.html b/test/fixtures/wpt/resource-timing/resources/iframe-setdomain.sub.html new file mode 100644 index 00000000000000..4a2f609aa469f1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-setdomain.sub.html @@ -0,0 +1,14 @@ + + + + domain: {{domains[]}} + + + + The resource-timings-level1.sub.html test loads this document into an IFrame to vet that setting + 'document.domain' does not effect the timing allowed. + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe-with-delay.sub.html b/test/fixtures/wpt/resource-timing/resources/iframe-with-delay.sub.html new file mode 100644 index 00000000000000..fe50aa7e471deb --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe-with-delay.sub.html @@ -0,0 +1,3 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/iframe_TAO_match_origin.html b/test/fixtures/wpt/resource-timing/resources/iframe_TAO_match_origin.html new file mode 100644 index 00000000000000..cf68aade7954e6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/iframe_TAO_match_origin.html @@ -0,0 +1,21 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/import.sub.css b/test/fixtures/wpt/resource-timing/resources/import.sub.css new file mode 100644 index 00000000000000..618c568d2a1d7b --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/import.sub.css @@ -0,0 +1 @@ +@import "delay-css.py?delay={{GET[delay]}}" \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/importer.css b/test/fixtures/wpt/resource-timing/resources/importer.css new file mode 100644 index 00000000000000..771204cdd15402 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/importer.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported' \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/importer.js b/test/fixtures/wpt/resource-timing/resources/importer.js new file mode 100644 index 00000000000000..e73d45da29fb29 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/importer.js @@ -0,0 +1,2 @@ +import './fake_responses.py?url=empty_script.js?script-head-import-defer'; +import('./fake_responses.py?url=empty_script.js?script-head-import-defer-dynamic').then(module => {}); \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/importer_async.js b/test/fixtures/wpt/resource-timing/resources/importer_async.js new file mode 100644 index 00000000000000..4b1cd4ddaef46d --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/importer_async.js @@ -0,0 +1,2 @@ +import './fake_responses.py?url=empty_script.js?script-head-import-async'; +import('./fake_responses.py?url=empty_script.js?script-head-import-async-dynamic').then(module => {}); \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/importer_dynamic.css b/test/fixtures/wpt/resource-timing/resources/importer_dynamic.css new file mode 100644 index 00000000000000..f0ba069f0c98ab --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/importer_dynamic.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported-dynamic' \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/importer_print.css b/test/fixtures/wpt/resource-timing/resources/importer_print.css new file mode 100644 index 00000000000000..aac191635ed520 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/importer_print.css @@ -0,0 +1 @@ +@import 'empty_style.css?stylesheet-imported-print' \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/inject_resource_test.html b/test/fixtures/wpt/resource-timing/resources/inject_resource_test.html new file mode 100644 index 00000000000000..44d09675d38e26 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/inject_resource_test.html @@ -0,0 +1,7 @@ + + + diff --git a/test/fixtures/wpt/resource-timing/resources/invalid.jpg b/test/fixtures/wpt/resource-timing/resources/invalid.jpg new file mode 100644 index 00000000000000..81c545efebe5f5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/invalid.jpg @@ -0,0 +1 @@ +1234 diff --git a/test/fixtures/wpt/resource-timing/resources/manifest.json b/test/fixtures/wpt/resource-timing/resources/manifest.json new file mode 100644 index 00000000000000..e107c044d5d8a8 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/manifest.json @@ -0,0 +1,4 @@ +{ + "name": "Dummy manifest", + "start_url": "/start.html" +} diff --git a/test/fixtures/wpt/resource-timing/resources/navigate_back.html b/test/fixtures/wpt/resource-timing/resources/navigate_back.html new file mode 100644 index 00000000000000..345eee1fccca5c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/navigate_back.html @@ -0,0 +1,7 @@ + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/nested-contexts.js b/test/fixtures/wpt/resource-timing/resources/nested-contexts.js new file mode 100644 index 00000000000000..c0822943e86a68 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/nested-contexts.js @@ -0,0 +1,110 @@ +let destination = location; + +if (location.search == "?cross-site") { + const https = destination.protocol.startsWith("https"); + destination = get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN']; +} else if (location.search == "?crossorigin") { + destination = get_host_info().REMOTE_ORIGIN; +} + +const pre_navigate_url = + new URL("/resource-timing/resources/document-that-navigates.html", + destination).href; +const post_navigate_url = + new URL("/resource-timing/resources/document-navigated.html", + destination).href; +const pre_refresh_url = + new URL("/resource-timing/resources/document-that-refreshes.html", + destination).href; +const post_refresh_url = + new URL("/resource-timing/resources/document-refreshed.html", + destination).href; + +const setup_navigate_or_refresh = (type, pre, post) => { + const verify_document_navigate_not_observable = () => { + const entries = performance.getEntriesByType("resource"); + let found_first_document = false; + for (entry of entries) { + if (entry.name == pre) { + found_first_document = true; + } + if (entry.name == post) { + opener.postMessage(`FAIL - ${type} document should not be observable`, + `*`); + return; + } + } + if (!found_first_document) { + opener.postMessage("FAIL - initial document should be observable", "*"); + return; + } + opener.postMessage("PASS", "*"); + } + window.addEventListener("message", e => { + if (e.data == type) { + verify_document_navigate_not_observable(); + } + }); +} + +const setup_navigate_test = () => { + setup_navigate_or_refresh("navigated", pre_navigate_url, post_navigate_url); +} + +const setup_refresh_test = () => { + setup_navigate_or_refresh("refreshed", pre_refresh_url, post_refresh_url); +} + +const setup_back_navigation = pushed_url => { + const verify_document_navigate_not_observable = navigated_back => { + const entries = performance.getEntriesByType("resource"); + let found_first_document = false; + for (entry of entries) { + if (entry.name == pre_navigate_url) { + found_first_document = true; + } + if (entry.name == post_navigate_url) { + opener.postMessage("FAIL - navigated document exposed", "*"); + return; + } + } + if (!found_first_document) { + opener.postMessage(`FAIL - first document not exposed. navigated_back ` + + `is ${navigated_back}`, "*"); + return; + } + if (navigated_back) { + opener.postMessage("PASS", "*"); + } + } + window.addEventListener("message", e => { + if (e.data == "navigated") { + verify_document_navigate_not_observable(sessionStorage.navigated); + if (sessionStorage.navigated) { + delete sessionStorage.navigated; + } else { + sessionStorage.navigated = true; + setTimeout(() => { + history.pushState({}, "", pushed_url); + location.href="navigate_back.html"; + }, 0); + } + } + }); +} + +const open_test_window = (url, message) => { + promise_test(() => { + return new Promise((resolve, reject) => { + const openee = window.open(url); + addEventListener("message", e => { + openee.close(); + if (e.data == "PASS") { + resolve(); + } else { + reject(e.data); + } + }); + }); + }, message); +} diff --git a/test/fixtures/wpt/resource-timing/resources/nested.css b/test/fixtures/wpt/resource-timing/resources/nested.css new file mode 100644 index 00000000000000..90d61b04acc444 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/nested.css @@ -0,0 +1,10 @@ +@import "resource_timing_test0.css?id=n1"; + +@font-face { + font-family: remoteFont; + src: url('/fonts/Ahem.ttf?id=n1'); +} +ol { + font-family: remoteFont; + list-style-image: url('blue.png?id=n1'); +} diff --git a/test/fixtures/wpt/resource-timing/resources/notify_parent.html b/test/fixtures/wpt/resource-timing/resources/notify_parent.html new file mode 100644 index 00000000000000..c104f3c8f0695a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/notify_parent.html @@ -0,0 +1,4 @@ + + diff --git a/test/fixtures/wpt/resource-timing/resources/object-navigate-back.html b/test/fixtures/wpt/resource-timing/resources/object-navigate-back.html new file mode 100644 index 00000000000000..a746947818ffb4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/object-navigate-back.html @@ -0,0 +1,18 @@ + + + + +Resource Timing object navigate - back button navigation + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/object-navigate.html b/test/fixtures/wpt/resource-timing/resources/object-navigate.html new file mode 100644 index 00000000000000..6b4bb3128ee6f1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/object-navigate.html @@ -0,0 +1,18 @@ + + + + +Resource Timing object navigate + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/object-refresh.html b/test/fixtures/wpt/resource-timing/resources/object-refresh.html new file mode 100644 index 00000000000000..5c5f60fb06851a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/object-refresh.html @@ -0,0 +1,18 @@ + + + + +Resource Timing object refresh + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/resources/observe-entry.js b/test/fixtures/wpt/resource-timing/resources/observe-entry.js new file mode 100644 index 00000000000000..260b5929a5bb78 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/observe-entry.js @@ -0,0 +1,25 @@ +// Given a resource name, returns a promise that will resolve to the +// corresponding PerformanceResourceTiming entry. The promise will reject, +// however, if the PerformanceResourceTiming entry isn't observed within ~2 +// seconds (scaled according to WPT timeout scaling). +const observe_entry = entry_name => { + const entry = new Promise(resolve => { + new PerformanceObserver((entry_list, observer) => { + for (const entry of entry_list.getEntries()) { + if (entry.name.endsWith(entry_name)) { + resolve(entry); + observer.disconnect(); + return; + } + } + }).observe({"type": "resource", "buffered": true}); + }); + const timeout = new Promise((resolve, reject) => { + step_timeout(() => { + reject(new Error("observe_entry: timeout")); + }, 2000); + }); + // If the entry isn't observed within 2 seconds, assume it will never show + // up. + return Promise.race([entry, timeout]); +}; diff --git a/test/fixtures/wpt/resource-timing/resources/resource-loaders.js b/test/fixtures/wpt/resource-timing/resources/resource-loaders.js new file mode 100644 index 00000000000000..99a2c28d2b3b27 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource-loaders.js @@ -0,0 +1,133 @@ +const load = { + _cache_bust_value: Math.random().toString().substr(2), + + cache_bust: path => { + let url = new URL(path, location.origin); + url.href += (url.href.includes("?")) ? '&' : '?'; + url.href += "unique=" + load._cache_bust_value++ + return url.href; + }, + + // Returns a promise that settles once the given path has been fetched as an + // image resource. + image: path => { + return new Promise(resolve => { + const img = new Image(); + img.onload = img.onerror = resolve; + img.src = load.cache_bust(path); + }); + }, + + // Returns a promise that settles once the given path has been fetched as a + // font resource. + font: path => { + const div = document.createElement('div'); + div.innerHTML = ` + +
This fetches ahem font.
+ `; + document.body.appendChild(div); + return document.fonts.ready.then(() => { + document.body.removeChild(div); + }); + }, + + // Returns a promise that settles once the given path has been fetched as a + // stylesheet resource. + stylesheet: async path => { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = load.cache_bust(path); + + const loaded = new Promise(resolve => { + link.onload = link.onerror = resolve; + }); + + document.head.appendChild(link); + await loaded; + document.head.removeChild(link); + }, + + iframe_with_attrs: async (path, attribute_map, validator) => { + const frame = document.createElement("iframe"); + if (attribute_map instanceof Object) { + for (const [key, value] of Object.entries(attribute_map)) { + frame[key] = value; + } + } + const loaded = new Promise(resolve => { + frame.onload = frame.onerror = resolve; + }); + frame.src = load.cache_bust(path); + document.body.appendChild(frame); + await loaded; + if (validator instanceof Function) { + validator(frame); + } + document.body.removeChild(frame); + }, + + // Returns a promise that settles once the given path has been fetched as an + // iframe. + iframe: async (path, validator) => { + return load.iframe_with_attrs(path, undefined, validator); + }, + + // Returns a promise that settles once the given path has been fetched as a + // script. + script: async path => { + const script = document.createElement("script"); + const loaded = new Promise(resolve => { + script.onload = script.onerror = resolve; + }); + script.src = load.cache_bust(path); + document.body.appendChild(script); + await loaded; + document.body.removeChild(script); + }, + + // Returns a promise that settles once the given path has been fetched as an + // object. + object: async (path, type) => { + const object = document.createElement("object"); + const loaded = new Promise(resolve => { + object.onload = object.onerror = resolve; + }); + object.data = load.cache_bust(path); + if (type) { + object.type = type; + } + object.style = "width: 0px; height: 0px"; + document.body.appendChild(object); + await loaded; + document.body.removeChild(object); + }, + + // Returns a promise that settles once the given path has been fetched + // through a synchronous XMLHttpRequest. + xhr_sync: async (path, headers) => { + const xhr = new XMLHttpRequest; + xhr.open("GET", path, /* async = */ false); + if (headers instanceof Object) { + for (const [key, value] of Object.entries(headers)) { + xhr.setRequestHeader(key, value); + } + } + xhr.send(); + }, + + xhr_async: path => { + const xhr = new XMLHttpRequest(); + xhr.open("GET", path) + xhr.send(); + return new Promise(resolve => { + xhr.onload = xhr.onerror = resolve; + }); + } +}; diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.css b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.css new file mode 100644 index 00000000000000..8bc8326ba675ad --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.css @@ -0,0 +1,4 @@ +div#resource_link_css +{ + color:hotpink; +} \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.html b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.html new file mode 100644 index 00000000000000..167c65c5c45d11 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.html @@ -0,0 +1,15 @@ + + + + + Child Frame + + + + +

+ Child Document +

+ + + \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js new file mode 100644 index 00000000000000..cf1c1df3920002 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js @@ -0,0 +1,3 @@ +// This is a test script for purposes of testing the +// script initiator type in the Resource Timing feature +var testDummyValue = 0; diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js.headers b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js.headers new file mode 100644 index 00000000000000..308bee94d0e47a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.js.headers @@ -0,0 +1,2 @@ +Content-Type: text/javascript +Cache-Control: max-age=36000 diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.png b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.png new file mode 100644 index 00000000000000..be211bc3771803 Binary files /dev/null and b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.png differ diff --git a/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.xml b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.xml new file mode 100644 index 00000000000000..91cd676be6e6f1 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/resource_timing_test0.xml @@ -0,0 +1,6 @@ + + + + Test XML Data + + diff --git a/test/fixtures/wpt/resource-timing/resources/self_navigation.html b/test/fixtures/wpt/resource-timing/resources/self_navigation.html new file mode 100644 index 00000000000000..beb12f5da55178 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/self_navigation.html @@ -0,0 +1 @@ + diff --git a/test/fixtures/wpt/resource-timing/resources/shared-worker.js b/test/fixtures/wpt/resource-timing/resources/shared-worker.js new file mode 100644 index 00000000000000..f3ef3feb964cb7 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/shared-worker.js @@ -0,0 +1,3 @@ +self.onconnect = e => { + e.ports[0].postMessage(performance.timeOrigin); +} \ No newline at end of file diff --git a/test/fixtures/wpt/resource-timing/resources/sizes-helper.js b/test/fixtures/wpt/resource-timing/resources/sizes-helper.js new file mode 100644 index 00000000000000..86336686b32902 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/sizes-helper.js @@ -0,0 +1,16 @@ +// Header size is a fixed constant. +// https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-transfersize +const headerSize = 300; + +const cacheBustUrl = url => { + return url + '&unique=' + Math.random().toString().substring(2); +} + +const checkSizeFields = (entry, bodySize, transferSize) => { + assert_equals(entry.decodedBodySize, bodySize, + 'decodedBodySize'); + assert_equals(entry.encodedBodySize, bodySize, + 'encodedBodySize'); + assert_equals(entry.transferSize, transferSize, + 'transferSize'); +} diff --git a/test/fixtures/wpt/resource-timing/resources/sw-install.html b/test/fixtures/wpt/resource-timing/resources/sw-install.html new file mode 100644 index 00000000000000..3d1407e83188ae --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/sw-install.html @@ -0,0 +1,58 @@ + + diff --git a/test/fixtures/wpt/resource-timing/resources/sw.js b/test/fixtures/wpt/resource-timing/resources/sw.js new file mode 100644 index 00000000000000..4e4fe1e1f068fd --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/sw.js @@ -0,0 +1,3 @@ +self.addEventListener('fetch', function(event) { + event.respondWith(fetch(event.request)); +}); diff --git a/test/fixtures/wpt/resource-timing/resources/tao-response.js b/test/fixtures/wpt/resource-timing/resources/tao-response.js new file mode 100644 index 00000000000000..2194c5d43a5413 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/tao-response.js @@ -0,0 +1,13 @@ +const tao_response = (tao_value, base_url) => { + const payload = { + 'headers': { + 'Timing-Allow-Origin': tao_value + } + }; + return custom_cors_response(payload, base_url); +}; + +const remote_tao_response = tao_value => { + const {REMOTE_ORIGIN} = get_host_info(); + return tao_response(tao_value, REMOTE_ORIGIN); +}; diff --git a/test/fixtures/wpt/resource-timing/resources/webperftestharness.js b/test/fixtures/wpt/resource-timing/resources/webperftestharness.js new file mode 100644 index 00000000000000..869ef3d74f77b5 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/webperftestharness.js @@ -0,0 +1,166 @@ +/* +author: W3C http://www.w3.org/ +help: http://www.w3.org/TR/navigation-timing/#sec-window.performance-attribute +*/ +// +// Helper Functions for ResourceTiming W3C tests +// + +var performanceNamespace = window.performance; +var timingAttributes = [ + 'connectEnd', + 'connectStart', + 'domComplete', + 'domContentLoadedEventEnd', + 'domContentLoadedEventStart', + 'domInteractive', + 'domLoading', + 'domainLookupEnd', + 'domainLookupStart', + 'fetchStart', + 'loadEventEnd', + 'loadEventStart', + 'navigationStart', + 'redirectEnd', + 'redirectStart', + 'requestStart', + 'responseEnd', + 'responseStart', + 'unloadEventEnd', + 'unloadEventStart' +]; + +var namespace_check = false; + +// +// All test() functions in the WebPerf test suite should use wp_test() instead. +// +// wp_test() validates the window.performance namespace exists prior to running tests and +// immediately shows a single failure if it does not. +// + +function wp_test(func, msg, properties) +{ + // only run the namespace check once + if (!namespace_check) + { + namespace_check = true; + + if (performanceNamespace === undefined || performanceNamespace == null) + { + // show a single error that window.performance is undefined + // The window.performance attribute provides a hosting area for performance related attributes. + test(function() { assert_true(performanceNamespace !== undefined && performanceNamespace != null, "window.performance is defined and not null"); }, "window.performance is defined and not null."); + } + } + + test(func, msg, properties); +} + +function test_namespace(child_name, skip_root) +{ + if (skip_root === undefined) { + var msg = 'window.performance is defined'; + // The window.performance attribute provides a hosting area for performance related attributes. + wp_test(function () { assert_not_equals(performanceNamespace, undefined, msg); }, msg); + } + + if (child_name !== undefined) { + var msg2 = 'window.performance.' + child_name + ' is defined'; + // The window.performance attribute provides a hosting area for performance related attributes. + wp_test(function() { assert_not_equals(performanceNamespace[child_name], undefined, msg2); }, msg2); + } +} + +function test_attribute_exists(parent_name, attribute_name, properties) +{ + var msg = 'window.performance.' + parent_name + '.' + attribute_name + ' is defined.'; + wp_test(function() { assert_not_equals(performanceNamespace[parent_name][attribute_name], undefined, msg); }, msg, properties); +} + +function test_enum(parent_name, enum_name, value, properties) +{ + var msg = 'window.performance.' + parent_name + '.' + enum_name + ' is defined.'; + wp_test(function() { assert_not_equals(performanceNamespace[parent_name][enum_name], undefined, msg); }, msg, properties); + + msg = 'window.performance.' + parent_name + '.' + enum_name + ' = ' + value; + wp_test(function() { assert_equals(performanceNamespace[parent_name][enum_name], value, msg); }, msg, properties); +} + +function test_timing_order(attribute_name, greater_than_attribute, properties) +{ + // ensure it's not 0 first + var msg = "window.performance.timing." + attribute_name + " > 0"; + wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] > 0, msg); }, msg, properties); + + // ensure it's in the right order + msg = "window.performance.timing." + attribute_name + " >= window.performance.timing." + greater_than_attribute; + wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] >= performanceNamespace.timing[greater_than_attribute], msg); }, msg, properties); +} + +function test_timing_greater_than(attribute_name, greater_than, properties) +{ + var msg = "window.performance.timing." + attribute_name + " > " + greater_than; + test_greater_than(performanceNamespace.timing[attribute_name], greater_than, msg, properties); +} + +function test_timing_equals(attribute_name, equals, msg, properties) +{ + var test_msg = msg || "window.performance.timing." + attribute_name + " == " + equals; + test_equals(performanceNamespace.timing[attribute_name], equals, test_msg, properties); +} + +// +// Non-test related helper functions +// + +function sleep_milliseconds(n) +{ + var start = new Date().getTime(); + while (true) { + if ((new Date().getTime() - start) >= n) break; + } +} + +// +// Common helper functions +// + +function test_true(value, msg, properties) +{ + wp_test(function () { assert_true(value, msg); }, msg, properties); +} + +function test_equals(value, equals, msg, properties) +{ + wp_test(function () { assert_equals(value, equals, msg); }, msg, properties); +} + +function test_greater_than(value, greater_than, msg, properties) +{ + wp_test(function () { assert_true(value > greater_than, msg); }, msg, properties); +} + +function test_greater_or_equals(value, greater_than, msg, properties) +{ + wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties); +} + +function test_not_equals(value, notequals, msg, properties) +{ + wp_test(function() { assert_not_equals(value, notequals, msg); }, msg, properties); +} + +function test_tao_pass(entry) { + test_greater_than(entry.redirectStart, 0, 'redirectStart > 0 in cross-origin redirect with Timing-Allow-Origin.'); + test_greater_than(entry.redirectEnd, 0, 'redirectEnd > 0 in cross-origin redirect with Timing-Allow-Origin.'); + test_greater_than(entry.fetchStart, 0, 'fetchStart > 0 in cross-origin redirect with Timing-Allow-Origin.'); + test_greater_than(entry.fetchStart, entry.startTime, 'startTime < fetchStart in cross-origin redirect with Timing-Allow-Origin.'); +} + +function test_tao_fail(entry) { + test_equals(entry.redirectStart, 0, 'redirectStart == 0 in cross-origin redirect with failing Timing-Allow-Origin.'); + test_equals(entry.redirectEnd, 0, 'redirectEnd == 0 in cross-origin redirect with failing Timing-Allow-Origin.'); + test_greater_than(entry.fetchStart, 0, 'fetchStart > 0 in cross-origin redirect with failing Timing-Allow-Origin.'); + test_equals(entry.fetchStart, entry.startTime, 'startTime == fetchStart in cross-origin redirect with failing Timing-Allow-Origin.'); +} diff --git a/test/fixtures/wpt/resource-timing/resources/webperftestharnessextension.js b/test/fixtures/wpt/resource-timing/resources/webperftestharnessextension.js new file mode 100644 index 00000000000000..dc02c075b3d07f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/webperftestharnessextension.js @@ -0,0 +1,188 @@ +// +// Helper functions for Resource Timing tests +// + +var mark_names = [ + '', + '1', + 'abc', +]; + +var measures = [ + [''], + ['2', 1], + ['aaa', 'navigationStart', ''], +]; + +function test_method_exists(method, method_name, properties) +{ + var msg; + if (typeof method === 'function') + msg = 'performance.' + method.name + ' is supported!'; + else + msg = 'performance.' + method_name + ' is supported!'; + wp_test(function() { assert_equals(typeof method, 'function', msg); }, msg, properties); +} + +function test_noless_than(value, greater_than, msg, properties) +{ + wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties); +} + +function test_fail(msg, properties) +{ + wp_test(function() { assert_unreached(); }, msg, properties); +} + +function test_resource_entries(entries, expected_entries) +{ + test(function() { + // This is slightly convoluted so that we can sort the output. + var actual_entries = {}; + var origin = window.location.protocol + "//" + window.location.host; + + for (var i = 0; i < entries.length; ++i) { + var entry = entries[i]; + var found = false; + for (var expected_entry in expected_entries) { + if (entry.name == origin + expected_entry) { + found = true; + if (expected_entry in actual_entries) { + assert_unreached(expected_entry + ' is not expected to have duplicate entries'); + } + actual_entries[expected_entry] = entry; + break; + } + } + if (!found) { + assert_unreached(entries[i].name + ' is not expected to be in the Resource Timing buffer'); + } + } + + sorted_urls = []; + for (var i in actual_entries) { + sorted_urls.push(i); + } + sorted_urls.sort(); + for (var i in sorted_urls) { + var url = sorted_urls[i]; + assert_equals(actual_entries[url].initiatorType, + expected_entries[url], + origin + url + ' is expected to have initiatorType ' + expected_entries[url]); + } + for (var j in expected_entries) { + if (!(j in actual_entries)) { + assert_unreached(origin + j + ' is expected to be in the Resource Timing buffer'); + } + } + }, "Testing resource entries"); +} + +function performance_entrylist_checker(type) +{ + var entryType = type; + + function entry_check(entry, expectedNames) + { + var msg = 'Entry \"' + entry.name + '\" should be one that we have set.'; + wp_test(function() { assert_in_array(entry.name, expectedNames, msg); }, msg); + test_equals(entry.entryType, entryType, 'entryType should be \"' + entryType + '\".'); + if (type === "measure") { + test_true(isFinite(entry.startTime), 'startTime should be a number.'); + test_true(isFinite(entry.duration), 'duration should be a number.'); + } else if (type === "mark") { + test_greater_than(entry.startTime, 0, 'startTime should greater than 0.'); + test_equals(entry.duration, 0, 'duration of mark should be 0.'); + } + } + + function entrylist_order_check(entryList) + { + var inOrder = true; + for (var i = 0; i < entryList.length - 1; ++i) + { + if (entryList[i + 1].startTime < entryList[i].startTime) { + inOrder = false; + break; + } + } + return inOrder; + } + + function entrylist_check(entryList, expectedLength, expectedNames) + { + test_equals(entryList.length, expectedLength, 'There should be ' + expectedLength + ' entries.'); + test_true(entrylist_order_check(entryList), 'Entries in entrylist should be in order.'); + for (var i = 0; i < entryList.length; ++i) + { + entry_check(entryList[i], expectedNames); + } + } + + return{"entrylist_check":entrylist_check}; +} + +function PerformanceContext(context) +{ + this.performanceContext = context; +} + +PerformanceContext.prototype = { + initialMeasures: function(item, index, array) + { + this.performanceContext.measure.apply(this.performanceContext, item); + }, + + mark: function() + { + this.performanceContext.mark.apply(this.performanceContext, arguments); + }, + + measure: function() + { + this.performanceContext.measure.apply(this.performanceContext, arguments); + }, + + clearMarks: function() + { + this.performanceContext.clearMarks.apply(this.performanceContext, arguments); + + }, + + clearMeasures: function() + { + this.performanceContext.clearMeasures.apply(this.performanceContext, arguments); + + }, + + getEntries: function() + { + return this.performanceContext.getEntries.apply(this.performanceContext, arguments); + }, + + getEntriesByType: function() + { + return this.performanceContext.getEntriesByType.apply(this.performanceContext, arguments); + }, + + getEntriesByName: function() + { + return this.performanceContext.getEntriesByName.apply(this.performanceContext, arguments); + }, + + setResourceTimingBufferSize: function() + { + return this.performanceContext.setResourceTimingBufferSize.apply(this.performanceContext, arguments); + }, + + registerResourceTimingBufferFullCallback: function(func) + { + this.performanceContext.onresourcetimingbufferfull = func; + }, + + clearResourceTimings: function() + { + this.performanceContext.clearResourceTimings.apply(this.performanceContext, arguments); + } + +}; diff --git a/test/fixtures/wpt/resource-timing/resources/worker_with_images.js b/test/fixtures/wpt/resource-timing/resources/worker_with_images.js new file mode 100644 index 00000000000000..1fa4893201ee00 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/resources/worker_with_images.js @@ -0,0 +1,22 @@ +let numComplete = 0; + +function checkDone() { + ++numComplete; + if (numComplete == 2) { + const numEntries = performance.getEntries().length; + postMessage(numEntries); + } +} + +function makeRequest(request) { + var xhr = new XMLHttpRequest; + xhr.open('get', request, true); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + checkDone(); + } + } + xhr.send(); +} +makeRequest('blue.png'); +makeRequest('resource_timing_test0.png'); diff --git a/test/fixtures/wpt/resource-timing/same-origin-from-cross-origin-redirect.html b/test/fixtures/wpt/resource-timing/same-origin-from-cross-origin-redirect.html new file mode 100644 index 00000000000000..8740b81b13fd8a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/same-origin-from-cross-origin-redirect.html @@ -0,0 +1,31 @@ + + + + +This test validates resource timing information for a same-origin +resource fetched through a cross-origin redirect chain. + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/script-rt-entries.html b/test/fixtures/wpt/resource-timing/script-rt-entries.html new file mode 100644 index 00000000000000..cdd72bd1e25199 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/script-rt-entries.html @@ -0,0 +1,37 @@ + + + + +Resource Timing Entry Sequence of Events for Scripts + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/secure-iframe-in-insecure-context.html b/test/fixtures/wpt/resource-timing/secure-iframe-in-insecure-context.html new file mode 100644 index 00000000000000..87f4711146ce9a --- /dev/null +++ b/test/fixtures/wpt/resource-timing/secure-iframe-in-insecure-context.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/shared-worker-rt-entry.html b/test/fixtures/wpt/resource-timing/shared-worker-rt-entry.html new file mode 100644 index 00000000000000..194500a09503a4 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/shared-worker-rt-entry.html @@ -0,0 +1,26 @@ + + + + +Resource Timing Entry for Shared Workers + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/sizes-cache.any.js b/test/fixtures/wpt/resource-timing/sizes-cache.any.js new file mode 100644 index 00000000000000..af70e5a6ded3e6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/sizes-cache.any.js @@ -0,0 +1,55 @@ +// META: global=window,worker +// META: script=/resource-timing/resources/sizes-helper.js +// META: script=/resource-timing/resources/resource-loaders.js + +let url = new URL( + '/resource-timing/resources/cacheable-and-validated.py' + + '?content=loremipsumblablabla', + location.href).href; +const bodySize = 19; + +const accumulateEntries = () => { + return new Promise(resolve => { + const po = new PerformanceObserver(list => { + resolve(list); + }); + po.observe({type: "resource", buffered: true}); + }); +}; + +const checkResourceSizes = list => { + const entries = list.getEntriesByName(url); + assert_equals(entries.length, 3, 'Wrong number of entries'); + let seenCount = 0; + for (let entry of entries) { + if (seenCount === 0) { + // 200 response + checkSizeFields(entry, bodySize, bodySize + headerSize); + } else if (seenCount === 1) { + // from cache + checkSizeFields(entry, bodySize, 0); + } else if (seenCount === 2) { + // 304 response + checkSizeFields(entry, bodySize, headerSize); + } else { + assert_unreached('Too many matching entries'); + } + ++seenCount; + } +}; + +promise_test(() => { + // Use a different URL every time so that the cache behaviour does not + // depend on execution order. + url = load.cache_bust(url); + const eatBody = response => response.arrayBuffer(); + const mustRevalidate = {headers: {'Cache-Control': 'max-age=0'}}; + return fetch(url) + .then(eatBody) + .then(() => fetch(url)) + .then(eatBody) + .then(() => fetch(url, mustRevalidate)) + .then(eatBody) + .then(accumulateEntries) + .then(checkResourceSizes); +}, 'PerformanceResourceTiming sizes caching test'); diff --git a/test/fixtures/wpt/resource-timing/sizes-redirect-img.html b/test/fixtures/wpt/resource-timing/sizes-redirect-img.html new file mode 100644 index 00000000000000..786018d0c4634c --- /dev/null +++ b/test/fixtures/wpt/resource-timing/sizes-redirect-img.html @@ -0,0 +1,57 @@ + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/sizes-redirect.any.js b/test/fixtures/wpt/resource-timing/sizes-redirect.any.js new file mode 100644 index 00000000000000..e483a4d409c6c2 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/sizes-redirect.any.js @@ -0,0 +1,62 @@ +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=/resource-timing/resources/sizes-helper.js + +const baseUrl = + new URL('/resource-timing/resources/TAOResponse.py?tao=wildcard', location.href).href; +const expectedSize = 4; + +const hostInfo = get_host_info(); +performance.clearResourceTimings(); + +const accumulateEntry = () => { + return new Promise(resolve => { + const po = new PerformanceObserver(list => { + resolve(list); + }); + po.observe({type: "resource", buffered: true}); + }); +}; + +const checkResourceSizes = () => { + const entries = performance.getEntriesByType('resource'); + for (let entry of entries) { + checkSizeFields(entry, expectedSize, expectedSize + headerSize); + } +} + +const redirectUrl = (redirectSourceOrigin, allowOrigin, targetUrl) => { + return redirectSourceOrigin + + '/resource-timing/resources/redirect-cors.py?allow_origin=' + + encodeURIComponent(allowOrigin) + + '&timing_allow_origin=*' + + '&location=' + encodeURIComponent(targetUrl); +} + +promise_test(() => { + // Use a different URL every time so that the cache behaviour does not + // depend on execution order. + const directUrl = cacheBustUrl(baseUrl); + const sameOriginRedirect = redirectUrl(hostInfo.ORIGIN, '*', directUrl); + const crossOriginRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN, + hostInfo.ORIGIN, directUrl); + const mixedRedirect = redirectUrl(hostInfo.REMOTE_ORIGIN, + hostInfo.ORIGIN, sameOriginRedirect); + const complexRedirect = redirectUrl(hostInfo.ORIGIN, + hostInfo.REMOTE_ORIGIN, mixedRedirect); + let eatBody = response => response.arrayBuffer(); + return fetch(directUrl) + .then(eatBody) + .then(() => fetch(sameOriginRedirect)) + .then(eatBody) + .then(() => fetch(crossOriginRedirect)) + .then(eatBody) + .then(() => fetch(mixedRedirect)) + .then(eatBody) + .then(() => fetch(complexRedirect)) + .then(eatBody) + .then(accumulateEntry) + .then(checkResourceSizes); +}, 'PerformanceResourceTiming sizes Fetch with redirect test'); + +done(); diff --git a/test/fixtures/wpt/resource-timing/status-codes-create-entry.html b/test/fixtures/wpt/resource-timing/status-codes-create-entry.html new file mode 100644 index 00000000000000..cc0cd8ccb88250 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/status-codes-create-entry.html @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/supported_resource_type.any.js b/test/fixtures/wpt/resource-timing/supported_resource_type.any.js new file mode 100644 index 00000000000000..31e40096ddb2c3 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/supported_resource_type.any.js @@ -0,0 +1,24 @@ +test(() => { + if (typeof PerformanceObserver.supportedEntryTypes === "undefined") + assert_unreached("supportedEntryTypes is not supported."); + assert_true(PerformanceObserver.supportedEntryTypes.includes("resource"), + "There should be an entry 'resource' in PerformanceObserver.supportedEntryTypes"); +}, "supportedEntryTypes contains 'resource'."); + +if (typeof PerformanceObserver.supportedEntryTypes !== "undefined") { + const entryType = "resource"; + if (PerformanceObserver.supportedEntryTypes.includes(entryType)) { + promise_test(async() => { + await new Promise((resolve) => { + new PerformanceObserver(function (list, observer) { + observer.disconnect(); + resolve(); + }).observe({entryTypes: [entryType]}); + + // Force the PerformanceEntry. + // Use `self` for Workers. + fetch(self.location.href + "?" + Math.random()); + }) + }, `'${entryType}' entries should be observable.`) + } +} diff --git a/test/fixtures/wpt/resource-timing/test_resource_timing.html b/test/fixtures/wpt/resource-timing/test_resource_timing.html new file mode 100644 index 00000000000000..f4e851abb203ac --- /dev/null +++ b/test/fixtures/wpt/resource-timing/test_resource_timing.html @@ -0,0 +1,24 @@ + + + + + window.performance Resource Timing Entries exist + + + + + + + +

Description

+

+ NOTE: Due to caching behavior in the browser, it is possible that when revisiting this page, some resources + may not have to be fetched from the network. As a result, the performance timeline will not contain entries + for these resources. This test will fail if any entries are missing to ensure that all resources are fetched + from the network and entries for these resources exist in the Performance Timeline. If revisiting this page, + please either perform a full reload of the page or clear the cache between visits. +

+ +
+ + diff --git a/test/fixtures/wpt/resource-timing/test_resource_timing.https.html b/test/fixtures/wpt/resource-timing/test_resource_timing.https.html new file mode 100644 index 00000000000000..f4e851abb203ac --- /dev/null +++ b/test/fixtures/wpt/resource-timing/test_resource_timing.https.html @@ -0,0 +1,24 @@ + + + + + window.performance Resource Timing Entries exist + + + + + + + +

Description

+

+ NOTE: Due to caching behavior in the browser, it is possible that when revisiting this page, some resources + may not have to be fetched from the network. As a result, the performance timeline will not contain entries + for these resources. This test will fail if any entries are missing to ensure that all resources are fetched + from the network and entries for these resources exist in the Performance Timeline. If revisiting this page, + please either perform a full reload of the page or clear the cache between visits. +

+ +
+ + diff --git a/test/fixtures/wpt/resource-timing/test_resource_timing.js b/test/fixtures/wpt/resource-timing/test_resource_timing.js new file mode 100644 index 00000000000000..598a727bf88e7f --- /dev/null +++ b/test/fixtures/wpt/resource-timing/test_resource_timing.js @@ -0,0 +1,228 @@ +var TEST_ALLOWED_TIMING_DELTA = 20; + +var waitTimer; +var expectedEntries = {}; + +var initiatorTypes = ["iframe", "img", "link", "script", "xmlhttprequest"]; + +var tests = {}; +setup(function() { + for (var i in initiatorTypes) { + var type = initiatorTypes[i]; + tests[type] = { + "entry": async_test("window.performance.getEntriesByName() and window.performance.getEntriesByNameType() return same data (" + type + ")"), + "simple_attrs": async_test("PerformanceEntry has correct name, initiatorType, startTime, and duration (" + type + ")"), + "timing_attrs": async_test("PerformanceEntry has correct order of timing attributes (" + type + ")"), + "network_attrs": async_test("PerformanceEntry has correct network transfer attributes (" + type + ")"), + "protocol": async_test("PerformanceEntry has correct protocol attribute (" + type + ")") + }; + } +}); + +function resolve(path) { + var a = document.createElement("a"); + a.href = path; + return a.href; +} + +onload = function() +{ + // check that the Performance Timeline API exists + test(function() { + assert_idl_attribute(window.performance, "getEntriesByName", + "window.performance.getEntriesByName() is defined"); + }); + test(function() { + assert_idl_attribute(window.performance, "getEntriesByType", + "window.performance.getEntriesByType() is defined"); + }); + test(function() { + assert_idl_attribute(window.performance, "getEntries", + "window.performance.getEntries() is defined"); + }); + + var expected_entry; + var url; + var type; + var startTime; + var element; + var encodedBodySize; + var decodedBodySize; + for (var i in initiatorTypes) { + startTime = window.performance.now(); + type = initiatorTypes[i]; + if (type != "xmlhttprequest") { + element = document.createElement(type); + } else { + element = null; + } + switch (type) { + case "iframe": + url = resolve("resources/resource_timing_test0.html"); + element.src = url; + encodedBodySize = 215; + decodedBodySize = 215; + break; + case "img": + url = resolve("resources/resource_timing_test0.png"); + element.src = url; + encodedBodySize = 249; + decodedBodySize = 249; + break; + case "link": + element.rel = "stylesheet"; + url = resolve("resources/resource_timing_test0.css"); + element.href = url; + encodedBodySize = 44; + decodedBodySize = 44; + break; + case "script": + element.type = "text/javascript"; + url = resolve("resources/resource_timing_test0.js"); + element.src = url; + encodedBodySize = 133; + decodedBodySize = 133; + break; + case "xmlhttprequest": + var xmlhttp = new XMLHttpRequest(); + url = resolve("resources/gzip_xml.py"); + xmlhttp.open('GET', url, true); + xmlhttp.send(); + encodedBodySize = 112; + decodedBodySize = 125; + break; + } + + expected_entry = {name:url, + startTime: startTime, + initiatorType: type, + encodedBodySize: encodedBodySize, + decodedBodySize: decodedBodySize + }; + + switch (type) { + case "link": + poll_for_stylesheet_load(expected_entry); + document.body.appendChild(element); + break; + case "xmlhttprequest": + xmlhttp.onload = (function(entry) { + return function (event) { + resource_load(entry); + }; + })(expected_entry); + break; + default: + element.onload = (function(entry) { + return function (event) { + resource_load(entry); + }; + })(expected_entry); + document.body.appendChild(element); + } + + } +}; + +function poll_for_stylesheet_load(expected_entry) { + var t = tests[expected_entry.initiatorType]; + + function inner() { + for(var i=0; i 0; + } catch(e) { + hasRules = false; + } + if (hasRules) { + t["entry"].step_timeout(function() { + resource_load(expected_entry); + }, 200); + return; + } + } + } + t["entry"].step_timeout(inner, 100); + } + inner(); +} + +function resource_load(expected) +{ + var t = tests[expected.initiatorType]; + + t["entry"].step(function() { + var entries_by_name = window.performance.getEntriesByName(expected.name); + assert_equals(entries_by_name.length, 1, "should have a single entry for each resource (without type)"); + var entries_by_name_type = window.performance.getEntriesByName(expected.name, "resource"); + assert_equals(entries_by_name_type.length, 1, "should have a single entry for each resource (with type)"); + assert_not_equals(entries_by_name, entries_by_name_type, "values should be copies"); + for (p in entries_by_name[0]) { + var assertMethod = assert_equals + if (Array.isArray(entries_by_name[0][p]) && Array.isArray(entries_by_name_type[0][p])) { + assertMethod = assert_array_equals + } + assertMethod(entries_by_name[0][p], entries_by_name_type[0][p], "Property " + p + " should match"); + } + this.done(); + }); + + t["simple_attrs"].step(function() { + var actual = window.performance.getEntriesByName(expected.name)[0]; + var expected_type = expected.initiatorType; + assert_equals(actual.name, expected.name); + assert_equals(actual.initiatorType, expected_type); + assert_equals(actual.entryType, "resource"); + assert_greater_than_equal(actual.startTime, expected.startTime, "startTime is after the script to initiate the load ran"); + assert_equals(actual.duration, (actual.responseEnd - actual.startTime)); + this.done(); + }); + + t["timing_attrs"].step(function test() { + const entries = window.performance.getEntriesByName(expected.name); + assert_equals(entries.length, 1, 'There should be a single matching entry'); + const actual = entries[0]; + if (window.location.protocol == "http:") { + assert_equals(actual.secureConnectionStart, 0, 'secureConnectionStart should be 0 in http'); + } else { + assert_greater_than(actual.secureConnectionStart, 0, 'secureConnectionStart should not be 0 in https'); + } + + assert_equals(actual.redirectStart, 0, 'redirectStart should be 0'); + assert_equals(actual.redirectEnd, 0, 'redirectEnd should be 0'); + assert_equals(actual.fetchStart, actual.startTime, 'fetchStart is equal to startTime'); + assert_greater_than_equal(actual.domainLookupStart, actual.fetchStart, 'domainLookupStart after fetchStart'); + assert_greater_than_equal(actual.domainLookupEnd, actual.domainLookupStart, 'domainLookupEnd after domainLookupStart'); + assert_greater_than_equal(actual.connectStart, actual.domainLookupEnd, 'connectStart after domainLookupEnd'); + assert_greater_than_equal(actual.connectEnd, actual.connectStart, 'connectEnd after connectStart'); + assert_true(actual.secureConnectionStart == 0 || actual.secureConnectionStart <= actual.requestStart, + "secureConnectionStart should be either 0 or smaller than/equals to requestStart") + assert_greater_than_equal(actual.requestStart, actual.connectEnd, 'requestStart after connectEnd'); + assert_greater_than_equal(actual.responseStart, actual.requestStart, 'responseStart after requestStart'); + assert_greater_than_equal(actual.responseEnd, actual.responseStart, 'responseEnd after responseStart'); + this.done(); + }); + + t["network_attrs"].step(function test() { + var actual = window.performance.getEntriesByName(expected.name)[0]; + assert_equals(actual.encodedBodySize, expected.encodedBodySize, "encodedBodySize size"); + assert_equals(actual.decodedBodySize, expected.decodedBodySize, "decodedBodySize size"); + + // Transfer size will vary from browser to browser based on default headers, etc. This + // test verifies that transferSize for uncached resources is greater than on-the-wire + // body size. + assert_greater_than(actual.transferSize, actual.encodedBodySize, "transferSize size"); + this.done(); + }); + + t["protocol"].step(function() { + var actual = window.performance.getEntriesByName(expected.name)[0]; + assert_equals(actual.nextHopProtocol, "http/1.1", "expected protocol"); + this.done(); + }); + +} diff --git a/test/fixtures/wpt/resource-timing/tojson.html b/test/fixtures/wpt/resource-timing/tojson.html new file mode 100644 index 00000000000000..7a6187d3d55bcd --- /dev/null +++ b/test/fixtures/wpt/resource-timing/tojson.html @@ -0,0 +1,69 @@ + + + + +This test validates that PerformanceResourceTiming's toJSON method + contains all of the entry's attributes. + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/workerStart-tao-protected.https.html b/test/fixtures/wpt/resource-timing/workerStart-tao-protected.https.html new file mode 100644 index 00000000000000..f54c0f2756eac3 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/workerStart-tao-protected.https.html @@ -0,0 +1,76 @@ + + + + +Resource Timing - Check that workerStart is TAO protected + + + + + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/worklet-rt-entries.https.html b/test/fixtures/wpt/resource-timing/worklet-rt-entries.https.html new file mode 100644 index 00000000000000..8ed280be1706b6 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/worklet-rt-entries.https.html @@ -0,0 +1,19 @@ + + + + +Resource Timing Entry Sequence of Events for Worklets + + + + + + + + diff --git a/test/fixtures/wpt/resource-timing/xhr-resource-timing.html b/test/fixtures/wpt/resource-timing/xhr-resource-timing.html new file mode 100644 index 00000000000000..6f8f3331861062 --- /dev/null +++ b/test/fixtures/wpt/resource-timing/xhr-resource-timing.html @@ -0,0 +1,29 @@ + + + + +This test validates that a failed cross-origin fetch creates an opaque network timing entry. + + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/wpt/versions.json b/test/fixtures/wpt/versions.json index 3e223cd7d93b35..bda346e3ccf210 100644 --- a/test/fixtures/wpt/versions.json +++ b/test/fixtures/wpt/versions.json @@ -44,13 +44,17 @@ "path": "html/webappapis/timers" }, "interfaces": { - "commit": "fc086c82d5a7e9b01a684a23336d6d1f9e79e303", + "commit": "df731dab88a1a25c04eb7e6238c11dc28fda0801", "path": "interfaces" }, "performance-timeline": { "commit": "17ebc3aea0d6321e69554067c39ab5855e6fb67e", "path": "performance-timeline" }, + "resource-timing": { + "commit": "22d38586d04c1d22b64db36f439c6bb84f03db7d", + "path": "resource-timing" + }, "resources": { "commit": "c5b428f15acfb17fe59b5a6f04a21c288a76ed36", "path": "resources" diff --git a/test/parallel/test-perf-hooks-resourcetiming.js b/test/parallel/test-perf-hooks-resourcetiming.js index 056924c4eb8c6f..889e1ad02af5ce 100644 --- a/test/parallel/test-perf-hooks-resourcetiming.js +++ b/test/parallel/test-perf-hooks-resourcetiming.js @@ -7,18 +7,15 @@ const { PerformanceObserver, PerformanceEntry, PerformanceResourceTiming, - performance: { - clearResourceTimings, - markResourceTiming, - }, + performance, } = require('perf_hooks'); assert(PerformanceObserver); assert(PerformanceEntry); assert.throws(() => new PerformanceEntry(), { code: 'ERR_ILLEGAL_CONSTRUCTOR' }); assert(PerformanceResourceTiming); -assert(clearResourceTimings); -assert(markResourceTiming); +assert(performance.clearResourceTimings); +assert(performance.markResourceTiming); function createTimingInfo({ startTime = 0, @@ -78,7 +75,7 @@ function createTimingInfo({ const requestedUrl = 'http://localhost:8080'; const cacheMode = 'local'; const initiatorType = 'fetch'; - const resource = markResourceTiming( + const resource = performance.markResourceTiming( timingInfo, requestedUrl, initiatorType, @@ -107,7 +104,7 @@ function createTimingInfo({ assert(entries[0] instanceof PerformanceResourceTiming); } - clearResourceTimings(); + performance.clearResourceTimings(); assert.strictEqual(performance.getEntries().length, 0); } @@ -120,7 +117,7 @@ function createTimingInfo({ const requestedUrl = 'http://localhost:8080'; const cacheMode = 'local'; const initiatorType = 'fetch'; - const resource = markResourceTiming( + const resource = performance.markResourceTiming( timingInfo, requestedUrl, initiatorType, @@ -228,7 +225,7 @@ function createTimingInfo({ assert(resource instanceof PerformanceEntry); assert(resource instanceof PerformanceResourceTiming); - clearResourceTimings(); + performance.clearResourceTimings(); const entries = performance.getEntries(); assert.strictEqual(entries.length, 0); } @@ -244,7 +241,7 @@ function createTimingInfo({ const requestedUrl = 'http://localhost:8080'; const cacheMode = ''; const initiatorType = 'fetch'; - const resource = markResourceTiming( + const resource = performance.markResourceTiming( timingInfo, requestedUrl, initiatorType, @@ -267,7 +264,7 @@ function createTimingInfo({ assert(resource instanceof PerformanceEntry); assert(resource instanceof PerformanceResourceTiming); - clearResourceTimings(); + performance.clearResourceTimings(); const entries = performance.getEntries(); assert.strictEqual(entries.length, 0); } @@ -299,7 +296,7 @@ function createTimingInfo({ const requestedUrl = 'http://localhost:8080'; const cacheMode = 'local'; const initiatorType = 'fetch'; - const resource = markResourceTiming( + const resource = performance.markResourceTiming( timingInfo, requestedUrl, initiatorType, @@ -310,7 +307,7 @@ function createTimingInfo({ assert(resource instanceof PerformanceEntry); assert(resource instanceof PerformanceResourceTiming); - clearResourceTimings(); + performance.clearResourceTimings(); const entries = performance.getEntries(); assert.strictEqual(entries.length, 0); } diff --git a/test/parallel/test-perf-hooks-usertiming.js b/test/parallel/test-perf-hooks-usertiming.js index 71cc28c0f9c3a3..c6fd664fdce902 100644 --- a/test/parallel/test-perf-hooks-usertiming.js +++ b/test/parallel/test-perf-hooks-usertiming.js @@ -6,22 +6,20 @@ const { PerformanceObserver, PerformanceEntry, PerformanceMark, + performance, performance: { nodeTiming, - mark, - measure, - clearMarks, }, } = require('perf_hooks'); assert(PerformanceObserver); assert(PerformanceEntry); assert(PerformanceMark); -assert(mark); -assert(measure); +assert(performance.mark); +assert(performance.measure); [undefined, 'a', 'null', 1, true].forEach((i) => { - const m = mark(i); + const m = performance.mark(i); assert(m instanceof PerformanceEntry); assert(m instanceof PerformanceMark); @@ -32,20 +30,20 @@ assert(measure); assert.strictEqual(m.detail, null); }); -clearMarks(); +performance.clearMarks(); -assert.throws(() => mark(Symbol('a')), { +assert.throws(() => performance.mark(Symbol('a')), { message: /Cannot convert a Symbol value to a string/ }); [undefined, null].forEach((detail) => { - const m = mark('a', { detail }); + const m = performance.mark('a', { detail }); assert.strictEqual(m.name, 'a'); assert.strictEqual(m.entryType, 'mark'); assert.strictEqual(m.detail, null); }); [1, 'any', {}, [], /a/].forEach((detail) => { - const m = mark('a', { detail }); + const m = performance.mark('a', { detail }); assert.strictEqual(m.name, 'a'); assert.strictEqual(m.entryType, 'mark'); // Value of detail is structured cloned. @@ -55,31 +53,31 @@ assert.throws(() => mark(Symbol('a')), { } }); -clearMarks(); +performance.clearMarks(); { - const m = mark('a', { startTime: 1 }); + const m = performance.mark('a', { startTime: 1 }); assert.strictEqual(m.startTime, 1); } -assert.throws(() => mark('a', { startTime: 'a' }), { +assert.throws(() => performance.mark('a', { startTime: 'a' }), { code: 'ERR_INVALID_ARG_TYPE' }); -clearMarks(); -clearMarks(1); -clearMarks(null); +performance.clearMarks(); +performance.clearMarks(1); +performance.clearMarks(null); -assert.throws(() => clearMarks(Symbol('foo')), { +assert.throws(() => performance.clearMarks(Symbol('foo')), { message: /Cannot convert a Symbol value to a string/ }); { - mark('a', { startTime: 0 }); - mark('b', { startTime: 10 }); + performance.mark('a', { startTime: 0 }); + performance.mark('b', { startTime: 10 }); { - const m3 = measure('foo', 'a', 'b'); + const m3 = performance.measure('foo', 'a', 'b'); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, 0); @@ -87,7 +85,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', 'a'); + const m3 = performance.measure('foo', 'a'); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, 0); @@ -95,7 +93,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', { start: 'a' }); + const m3 = performance.measure('foo', { start: 'a' }); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, 0); @@ -103,7 +101,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', { end: 'b' }); + const m3 = performance.measure('foo', { end: 'b' }); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, 0); @@ -111,7 +109,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', { duration: 11, end: 'b' }); + const m3 = performance.measure('foo', { duration: 11, end: 'b' }); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, -1); @@ -119,7 +117,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', { duration: 11, start: 'b' }); + const m3 = performance.measure('foo', { duration: 11, start: 'b' }); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, 10); @@ -127,7 +125,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', 'nodeStart'); + const m3 = performance.measure('foo', 'nodeStart'); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, nodeTiming.nodeStart); @@ -135,7 +133,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', 'nodeStart', 'bootstrapComplete'); + const m3 = performance.measure('foo', 'nodeStart', 'bootstrapComplete'); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, nodeTiming.nodeStart); @@ -145,14 +143,14 @@ assert.throws(() => clearMarks(Symbol('foo')), { } { - const m3 = measure('foo', { start: 'nodeStart', duration: 10 }); + const m3 = performance.measure('foo', { start: 'nodeStart', duration: 10 }); assert.strictEqual(m3.name, 'foo'); assert.strictEqual(m3.entryType, 'measure'); assert.strictEqual(m3.startTime, nodeTiming.nodeStart); assert.strictEqual(m3.duration, 10); } - clearMarks(); + performance.clearMarks(); } { @@ -184,7 +182,7 @@ assert.throws(() => clearMarks(Symbol('foo')), { obs.disconnect(); })); obs.observe({ entryTypes: ['mark', 'measure'] }); - mark('a'); - mark('b'); - measure('a to b', 'a', 'b'); + performance.mark('a'); + performance.mark('b'); + performance.measure('a to b', 'a', 'b'); } diff --git a/test/sequential/test-worker-eventlooputil.js b/test/sequential/test-worker-eventlooputil.js index 7e012cb2b02e7a..55a3995c3d11c1 100644 --- a/test/sequential/test-worker-eventlooputil.js +++ b/test/sequential/test-worker-eventlooputil.js @@ -9,7 +9,8 @@ const { MessagePort, parentPort, } = require('worker_threads'); -const { eventLoopUtilization, now } = require('perf_hooks').performance; +const { performance } = require('perf_hooks'); +const { eventLoopUtilization } = require('perf_hooks').performance; // Use argv to detect whether we're running as a Worker called by this test vs. // this test also being called as a Worker. @@ -35,8 +36,8 @@ function workerOnMetricsMsg(msg) { if (msg.cmd === 'spin') { const elu = eventLoopUtilization(); - const t = now(); - while (now() - t < msg.dur); + const t = performance.now(); + while (performance.now() - t < msg.dur); return this.postMessage(eventLoopUtilization(elu)); } } diff --git a/test/wpt/status/hr-time.json b/test/wpt/status/hr-time.json index b23a5a4e96a6a4..973e32b298a557 100644 --- a/test/wpt/status/hr-time.json +++ b/test/wpt/status/hr-time.json @@ -1,7 +1,4 @@ { - "idlharness.any.js": { - "skip": "TODO: update IDL parser" - }, "window-worker-timeOrigin.window.js": { "skip": "depends on URL.createObjectURL(blob)" } diff --git a/test/wpt/status/performance-timeline.json b/test/wpt/status/performance-timeline.json index 207f2287746e96..9a297e641437df 100644 --- a/test/wpt/status/performance-timeline.json +++ b/test/wpt/status/performance-timeline.json @@ -1,16 +1,12 @@ { "case-sensitivity.any.js": { "fail": { - "note": "resource entry type not supported", + "note": "self.location is not available", "expected": [ - "getEntriesByType values are case sensitive", "getEntriesByName values are case sensitive" ] } }, - "idlharness.any.js": { - "skip": "idlharness cannot recognize Node.js environment" - }, "webtiming-resolution.any.js": { "skip": "flaky" } diff --git a/test/wpt/status/resource-timing.json b/test/wpt/status/resource-timing.json new file mode 100644 index 00000000000000..22f21cfe48d7fe --- /dev/null +++ b/test/wpt/status/resource-timing.json @@ -0,0 +1,23 @@ +{ + "cors-preflight.any.js": { + "skip": "Browser-specific test" + }, + "resource_nested_dedicated_worker.worker.js": { + "skip": "Browser-specific test" + }, + "resource_timing.worker.js": { + "skip": "Browser-specific test" + }, + "sizes-cache.any.js": { + "skip": "Browser-specific test" + }, + "sizes-redirect.any.js": { + "skip": "Browser-specific test" + }, + "supported_resource_type.any.js": { + "skip": "Browser-specific test" + }, + "buffered-flag.any.js": { + "skip": "Browser-specific test" + } +} diff --git a/test/wpt/status/user-timing.json b/test/wpt/status/user-timing.json index b1110e6a5e798d..6e2b6e276ccda0 100644 --- a/test/wpt/status/user-timing.json +++ b/test/wpt/status/user-timing.json @@ -4,8 +4,5 @@ }, "performance-measure-invalid.worker.js": { "skip": "importScripts not supported" - }, - "idlharness.any.js": { - "skip": "idlharness cannot recognize Node.js environment" } } diff --git a/test/wpt/test-hr-time.js b/test/wpt/test-hr-time.js index 36fdde8036c0cc..8900e8617bbc1d 100644 --- a/test/wpt/test-hr-time.js +++ b/test/wpt/test-hr-time.js @@ -5,12 +5,7 @@ const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('hr-time'); -runner.setInitScript(` - const { Blob } = require('buffer'); - global.Blob = Blob; - - const { PerformanceObserver } = require('perf_hooks'); - global.PerformanceObserver = PerformanceObserver; -`); +runner.pretendGlobalThisAs('Window'); +runner.brandCheckGlobalScopeAttribute('performance'); runner.runJsTests(); diff --git a/test/wpt/test-performance-timeline.js b/test/wpt/test-performance-timeline.js index 12f1abd3a52a32..1d5a259c769d99 100644 --- a/test/wpt/test-performance-timeline.js +++ b/test/wpt/test-performance-timeline.js @@ -4,26 +4,55 @@ const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('performance-timeline'); -// Needed to access to DOMException. -runner.setFlags(['--expose-internals']); - +runner.pretendGlobalThisAs('Window'); +runner.brandCheckGlobalScopeAttribute('performance'); runner.setInitScript(` const { - PerformanceMark, - PerformanceMeasure, + PerformanceEntry, PerformanceObserver, PerformanceObserverEntryList, - performance, } = require('perf_hooks'); - global.PerformanceMark = performance; - global.PerformanceMeasure = performance; - global.PerformanceObserver = PerformanceObserver; - global.PerformanceObserverEntryList = PerformanceObserverEntryList; - global.performance = performance; + Object.defineProperty(global, 'PerformanceEntry', { + value: PerformanceEntry, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceObserver', { + value: PerformanceObserver, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceObserverEntryList', { + value: PerformanceObserverEntryList, + enumerable: false, + writable: true, + configurable: true, + }); - const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); - global.DOMException = DOMException; + // Create a dummy resource timing entry to mimic how the browser would + // record the initial page load. + performance.markResourceTiming({ + startTime: 0, + endTime: 0, + finalServiceWorkerStartTime: 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: 0, + finalConnectionTimingInfo: { + domainLookupStartTime: 0, + domainLookupEndTime: 0, + connectionStartTime: 0, + connectionEndTime: 0, + secureConnectionStartTime: 0, + ALPNNegotiatedProtocol: '', + }, + finalNetworkRequestStartTime: 0, + finalNetworkResponseStartTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + }, 'https://nodejs.org', '', global, ''); `); runner.runJsTests(); diff --git a/test/wpt/test-resource-timing.js b/test/wpt/test-resource-timing.js new file mode 100644 index 00000000000000..ea383f73b6d881 --- /dev/null +++ b/test/wpt/test-resource-timing.js @@ -0,0 +1,48 @@ +'use strict'; +require('../common'); +const { WPTRunner } = require('../common/wpt'); + +const runner = new WPTRunner('resource-timing'); + +runner.pretendGlobalThisAs('Window'); +runner.setInitScript(` + const { + PerformanceEntry, + PerformanceResourceTiming, + } = require('perf_hooks'); + Object.defineProperty(global, 'PerformanceEntry', { + value: PerformanceEntry, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceResourceTiming', { + value: PerformanceResourceTiming, + enumerable: false, + writable: true, + configurable: true, + }); + + global.resource = performance.markResourceTiming({ + startTime: 0, + endTime: 0, + finalServiceWorkerStartTime: 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: 0, + finalConnectionTimingInfo: { + domainLookupStartTime: 0, + domainLookupEndTime: 0, + connectionStartTime: 0, + connectionEndTime: 0, + secureConnectionStartTime: 0, + ALPNNegotiatedProtocol: '', + }, + finalNetworkRequestStartTime: 0, + finalNetworkResponseStartTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + }, 'https://nodejs.org', '', global, ''); +`); + +runner.runJsTests(); diff --git a/test/wpt/test-user-timing.js b/test/wpt/test-user-timing.js index 36d13297ba57cc..f82d2411c3ece2 100644 --- a/test/wpt/test-user-timing.js +++ b/test/wpt/test-user-timing.js @@ -1,27 +1,43 @@ 'use strict'; + require('../common'); const { WPTRunner } = require('../common/wpt'); const runner = new WPTRunner('user-timing'); -// Needed to access to DOMException. -runner.setFlags(['--expose-internals']); - +runner.pretendGlobalThisAs('Window'); +runner.brandCheckGlobalScopeAttribute('performance'); runner.setInitScript(` const { + PerformanceEntry, PerformanceMark, PerformanceMeasure, PerformanceObserver, - performance, } = require('perf_hooks'); - global.PerformanceMark = performance; - global.PerformanceMeasure = performance; - global.PerformanceObserver = PerformanceObserver; - global.performance = performance; - - const { internalBinding } = require('internal/test/binding'); - const { DOMException } = internalBinding('messaging'); - global.DOMException = DOMException; + Object.defineProperty(global, 'PerformanceEntry', { + value: PerformanceEntry, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceMark', { + value: PerformanceMark, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceMeasure', { + value: PerformanceMeasure, + enumerable: false, + writable: true, + configurable: true, + }); + Object.defineProperty(global, 'PerformanceObserver', { + value: PerformanceObserver, + enumerable: false, + writable: true, + configurable: true, + }); `); runner.runJsTests(); diff --git a/tools/doc/type-parser.mjs b/tools/doc/type-parser.mjs index 84a06541671648..9ed35a10eb1c67 100644 --- a/tools/doc/type-parser.mjs +++ b/tools/doc/type-parser.mjs @@ -179,7 +179,7 @@ const customTypesMap = { 'PerformanceNodeTiming': 'perf_hooks.html#class-performancenodetiming', 'PerformanceObserver': - 'perf_hooks.html#class-perf_hooksperformanceobserver', + 'perf_hooks.html#class-performanceobserver', 'PerformanceObserverEntryList': 'perf_hooks.html#class-performanceobserverentrylist',