perf_hooks: fix webperf idlharness

1. Enforce receiver checks on IDL interfaces.
2. Avoid prototype manipulation on constructing IDL interfaces with
   `ReflectConstruct`.
3. `defineReplaceableAttribute` should create IDL getter/setter.
4. Corrected `PerformanceResourceTiming` to inherit the public interface
   `PerformanceEntry` instead of the internal interface
   `InternalPerformanceResourceTiming`.
5. `detail` is not a specified attribute on `PerfomanceEntry`. Node.js
   specific extensions are moved to a subclass of `PerformanceEntry` as
   `PerformanceNodeEntry`.

PR-URL: https://github.com/nodejs/node/pull/44483
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
legendecas 2022-08-18 00:46:14 +08:00
parent f529f73bd7
commit 364c0e196c
No known key found for this signature in database
GPG Key ID: CB3C9EC2BC27057C
218 changed files with 8093 additions and 533 deletions

View File

@ -49,6 +49,11 @@ Node.js instance. It is similar to [`window.performance`][] in browsers.
<!-- YAML
added: v8.5.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}
@ -60,6 +65,11 @@ Performance Timeline. If `name` is provided, removes only the named mark.
<!-- YAML
added: v16.7.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}
@ -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.
<!-- YAML
added: v16.7.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.
-->
* Returns: {PerformanceEntry\[]}
@ -160,6 +180,11 @@ performance entries of certain types or that have certain names, see
<!-- YAML
added: v16.7.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}
@ -175,6 +200,11 @@ equal to `name`, and optionally, whose `performanceEntry.entryType` is equal to
<!-- YAML
added: v16.7.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.
-->
* `type` {string}
@ -189,6 +219,10 @@ is equal to `type`.
<!-- YAML
added: v8.5.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.
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/37136
description: Updated to conform to the User Timing Level 3 specification.
@ -244,6 +278,10 @@ Performance Timeline manually with `performance.clearResourceTimings`.
<!-- YAML
added: v8.5.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.
- version: v16.0.0
pr-url: https://github.com/nodejs/node/pull/37136
description: Updated to conform to the User Timing Level 3 specification.
@ -305,6 +343,11 @@ metrics for specific Node.js operational milestones.
<!-- YAML
added: v8.5.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.
-->
* Returns: {number}
@ -316,6 +359,11 @@ the start of the current `node` process.
<!-- YAML
added: v18.8.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.
-->
Sets the global performance resource timing buffer size to the specified number
@ -393,6 +441,11 @@ invoked.
<!-- YAML
added: v16.1.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.
-->
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`
<!-- YAML
added: v16.0.0
-->
* {any}
Additional detail specific to the `entryType`.
The constructor of this class is not exposed to users directly.
### `performanceEntry.duration`
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceEntry` object as the receiver.
-->
* {number}
@ -441,6 +491,11 @@ be meaningful for all Performance Entry types.
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceEntry` object as the receiver.
-->
* {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`
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceEntry` object as the receiver.
-->
* {string}
The name of the performance entry.
### `performanceEntry.startTime`
<!-- YAML
added: v8.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceEntry` object as the receiver.
-->
* {number}
The high resolution millisecond timestamp marking the starting time of the
Performance Entry.
## Class: `PerformanceMark`
<!-- YAML
added:
- v18.2.0
- v16.17.0
-->
* Extends: {PerformanceEntry}
Exposes marks created via the `Performance.mark()` method.
### `performanceMark.detail`
<!-- YAML
added: v16.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceMark` object as the receiver.
-->
* {any}
Additional detail specified when creating with `Performance.mark()` method.
## Class: `PerformanceMeasure`
<!-- YAML
added:
- v18.2.0
- v16.17.0
-->
* Extends: {PerformanceEntry}
Exposes measures created via the `Performance.measure()` method.
The constructor of this class is not exposed to users directly.
### `performanceMeasure.detail`
<!-- YAML
added: v16.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceMeasure` object as the receiver.
-->
* {any}
Additional detail specified when creating with `Performance.measure()` method.
## Class: `PerformanceNodeEntry`
<!-- YAML
added: REPLACEME
-->
* 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`
<!-- YAML
added: v16.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/44483
description: This property getter must be called with the
`PerformanceNodeEntry` object as the receiver.
-->
* {any}
Additional detail specific to the `entryType`.
### `performanceNodeEntry.flags`
<!-- YAML
added:
@ -468,9 +639,9 @@ changes:
when entryType is 'gc'.
-->
* {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`
<!-- YAML
added: v8.5.0
-->
* {string}
The name of the performance entry.
### `performanceEntry.kind`
### `performanceNodeEntry.kind`
<!-- YAML
added: v8.5.0
@ -505,9 +666,9 @@ changes:
when entryType is 'gc'.
-->
* {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`
<!-- YAML
added: v8.5.0
-->
* {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)`

View File

@ -75,7 +75,7 @@ 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',
defineReplaceableAttribute(globalThis, 'performance',
perf_hooks.performance);
function createGlobalConsole() {
@ -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,
});
}

View File

@ -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,

View File

@ -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,17 +69,9 @@ class Performance extends EventTarget {
timeOrigin: this.timeOrigin,
}, opts)}`;
}
}
function toJSON() {
return {
nodeTiming: this.nodeTiming,
timeOrigin: this.timeOrigin,
eventLoopUtilization: this.eventLoopUtilization()
};
}
function clearMarks(name) {
clearMarks(name = undefined) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (name !== undefined) {
name = `${name}`;
}
@ -81,25 +79,29 @@ function clearMarks(name) {
clearEntriesFromBuffer('mark', name);
}
function clearMeasures(name) {
clearMeasures(name = undefined) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (name !== undefined) {
name = `${name}`;
}
clearEntriesFromBuffer('measure', name);
}
function clearResourceTimings(name) {
clearResourceTimings(name = undefined) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (name !== undefined) {
name = `${name}`;
}
clearEntriesFromBuffer('resource', name);
}
function getEntries() {
getEntries() {
validateInternalField(this, kPerformanceBrand, 'Performance');
return filterBufferMapByNameAndType();
}
function getEntriesByName(name) {
getEntriesByName(name) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('name');
}
@ -107,7 +109,8 @@ function getEntriesByName(name) {
return filterBufferMapByNameAndType(name, undefined);
}
function getEntriesByType(type) {
getEntriesByType(type) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('type');
}
@ -115,69 +118,86 @@ function getEntriesByType(type) {
return filterBufferMapByNameAndType(undefined, type);
}
class InternalPerformance extends EventTarget {}
InternalPerformance.prototype.constructor = Performance.prototype.constructor;
ObjectSetPrototypeOf(InternalPerformance.prototype, Performance.prototype);
mark(name, options = kEmptyObject) {
validateInternalField(this, kPerformanceBrand, 'Performance');
if (arguments.length === 0) {
throw new ERR_MISSING_ARGS('name');
}
return mark(name, options);
}
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);
}
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: {
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,
configurable: true,
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
};

View File

@ -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 name() {
validateInternalField(this, kName, 'PerformanceEntry');
return this[kName];
}
get entryType() { return this[kType]; }
get entryType() {
validateInternalField(this, kEntryType, 'PerformanceEntry');
return this[kEntryType];
}
get startTime() { return this[kStart]; }
get startTime() {
validateInternalField(this, kStartTime, 'PerformanceEntry');
return this[kStartTime];
}
get duration() { return this[kDuration]; }
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;
}
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],
};
}
}
class InternalPerformanceEntry {
constructor(name, type, start, duration, detail) {
this[kName] = name;
this[kType] = type;
this[kStart] = start;
this[kDuration] = duration;
function createPerformanceNodeEntry(name, type, start, duration, detail) {
return ReflectConstruct(function PerformanceNodeEntry() {
initPerformanceEntry(this, name, type, start, duration);
this[kDetail] = detail;
}, [], PerformanceNodeEntry);
}
}
InternalPerformanceEntry.prototype.constructor = PerformanceEntry;
ObjectSetPrototypeOf(
InternalPerformanceEntry.prototype,
PerformanceEntry.prototype);
module.exports = {
InternalPerformanceEntry,
initPerformanceEntry,
createPerformanceEntry,
PerformanceEntry,
isPerformanceEntry,
PerformanceNodeEntry,
createPerformanceNodeEntry,
};

View File

@ -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 {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
}
} = require('internal/errors');
const { validateInternalField } = require('internal/validators');
const { kEnumerableProperty } = require('internal/util');
const kCacheMode = Symbol('kCacheMode');
const kRequestedUrl = Symbol('kRequestedUrl');
const kTimingInfo = Symbol('kTimingInfo');
const kInitiatorType = Symbol('kInitiatorType');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
}
} = require('internal/errors');
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;
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,

View File

@ -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,

View File

@ -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 mark(name, options = kEmptyObject) {
function createPerformanceMeasure(name, start, duration, detail) {
return ReflectConstruct(function PerformanceMeasure() {
initPerformanceEntry(this, name, 'measure', start, duration);
this[kDetail] = detail;
}, [], PerformanceMeasure);
}
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;

View File

@ -372,7 +372,6 @@ function setupTraceCategoryState() {
}
function setupPerfHooks() {
require('internal/perf/performance').refreshTimeOrigin();
require('internal/perf/utils').refreshTimeOrigin();
}

View File

@ -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.initScript === null) {
return this.dummyGlobalThisScript;
} else if (this.dummyGlobalThisScript === null) {
if (this.globalThisInitScripts.length === null) {
return this.initScript;
}
return `${this.dummyGlobalThisScript}\n\n//===\n${this.initScript}`;
const globalThisInitScript = this.globalThisInitScripts.join('\n\n//===\n');
if (this.initScript === null) {
return globalThisInitScript;
}
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() {

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;
};

View File

@ -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();
};

View File

@ -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;

View File

@ -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<DOMString, any> CanvasFilterInput;
[Exposed=(Window,Worker,PaintWorklet)]
interface CanvasFilter {
constructor(optional (CanvasFilterInput or sequence<CanvasFilterInput>) 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<Blob> 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<ImageBitmap> createImageBitmap(ImageBitmapSource image, optional ImageBitmapOptions options = {});
Promise<ImageBitmap> 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<DOMString>) 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<object> 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<object> transfer = [];
};
@ -2233,7 +2257,7 @@ interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
[Replaceable] readonly attribute DOMString name;
undefined postMessage(any message, sequence<object> 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<object> 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;
};

View File

@ -10,7 +10,7 @@ partial interface Performance {
};
typedef sequence<PerformanceEntry> 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);

View File

@ -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;
};

View File

@ -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<undefined> cancel(optional any reason);
};
[Exposed=(Window,Worker,Worklet)]
[Exposed=*]
interface ReadableStreamDefaultReader {
constructor(ReadableStream stream);
Promise<ReadableStreamDefaultReadResult> read();
Promise<ReadableStreamReadResult> read();
undefined releaseLock();
};
ReadableStreamDefaultReader includes ReadableStreamGenericReader;
dictionary ReadableStreamDefaultReadResult {
dictionary ReadableStreamReadResult {
any value;
boolean done;
};
[Exposed=(Window,Worker,Worklet)]
[Exposed=*]
interface ReadableStreamBYOBReader {
constructor(ReadableStream stream);
Promise<ReadableStreamBYOBReadResult> read(ArrayBufferView view);
Promise<ReadableStreamReadResult> 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<undefined> (any chunk, WritableSt
callback UnderlyingSinkCloseCallback = Promise<undefined> ();
callback UnderlyingSinkAbortCallback = Promise<undefined> (optional any reason);
[Exposed=(Window,Worker,Worklet)]
[Exposed=*]
interface WritableStreamDefaultWriter {
constructor(WritableStream stream);
@ -157,12 +152,13 @@ interface WritableStreamDefaultWriter {
Promise<undefined> 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<undefined> (TransformStreamDefaultController controller);
callback TransformerTransformCallback = Promise<undefined> (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);

View File

@ -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<sequence<USVString>> or record<USVString, USVString> or USVString) init = "");

View File

@ -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;
};

50
test/fixtures/wpt/interfaces/webidl.idl vendored Normal file
View File

@ -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;

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing - cached resources generate performance entries</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
</head>
<body>
<h1>Description</h1>
<p>This test validates that a 304 Not Modified resource appears in the
Performance Timeline.</p>
<script>
// Need to fetch the same resource twice; the first will get a 200 response but
// the second request should be cached and get a 304.
promise_test(async () => {
performance.clearResourceTimings();
const unique = Math.random();
const path = `resources/fake_responses.py?tag=${unique}`;
await load.xhr_sync(path);
await load.xhr_sync(path, {"If-None-Match": `${unique}`});
const entries = await new Promise(resolve => {
const accumulator = [];
new PerformanceObserver(entry_list => {
entry_list.getEntries().forEach(entry => {
accumulator.push(entry);
});
if (accumulator.length >= 2) {
resolve(accumulator);
}
}).observe({'type': 'resource', 'buffered': true});
});
if (entries.length != 2) {
throw new Error(`Expecting 2 but got ${entries.length} entries`);
}
assert_equals(entries[0].name, entries[1].name,
"Both entries should have the same name");
invariants.assert_tao_pass_no_redirect_http(entries[0]);
invariants.assert_tao_pass_304_not_modified_http(entries[1]);
}, "304 responses should still show up in the PerformanceTimeline");
</script>
</body>
</html>

View File

@ -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
* `<title>` 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 theres [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 its 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

View File

@ -0,0 +1,6 @@
spec: https://w3c.github.io/resource-timing/
suggested_reviewers:
- plehegar
- zqzhang
- igrigorik
- yoavweiss

View File

@ -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.</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-cross-origin-resources"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
</head>
<body>
<script>
const {HTTPS_REMOTE_ORIGIN} = get_host_info();
const SAME_ORIGIN = location.origin;
// Same-Origin => Cross-Origin => Same-Origin => Same-Origin redirect chain
let destUrl = `${SAME_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
destUrl += `page_origin=${SAME_ORIGIN}`;
destUrl += `&cross_origin=${HTTPS_REMOTE_ORIGIN}`;
destUrl += `&final_resource=/resource-timing/resources/blank_page_green.htm`;
// No TAO in the redirect chain
attribute_test(
load.iframe, destUrl,
invariants.assert_cross_origin_redirected_resource,
"Verify that cross origin resources' timings are not exposed when " +
"same-origin=>cross-origin=>same-origin redirects have no " +
"`Timing-Allow-Origin:` headers.");
// Partial TAO in the redirect chain
destUrl += '&tao_steps=2';
attribute_test(
load.iframe, destUrl,
invariants.assert_cross_origin_redirected_resource,
"Verify that cross origin resources' timings are not exposed when " +
"same-origin=>cross-origin=>same-origin redirects have " +
"`Timing-Allow-Origin:` headers only on some of the responses.");
// Cross-origin => Cross-Origin => Same-Origin => Same-Origin redirect chain.
destUrl = `${HTTPS_REMOTE_ORIGIN}/resource-timing/resources/multi_redirect.py?`;
destUrl += `page_origin=${SAME_ORIGIN}`;
destUrl += `&cross_origin=${HTTPS_REMOTE_ORIGIN}`;
destUrl += `&final_resource=/resource-timing/resources/blue-with-tao.png`;
destUrl += `&tao_steps=3`;
// Full redirect chain with `TAO: *`.
attribute_test(
load.image, destUrl,
invariants.assert_tao_enabled_cross_origin_redirected_resource,
"Verify that cross origin resources' timings are exposed when cross-origin " +
"redirects have `Timing-Allow-Origin: *` headers");
// TAO with a specific origin
destUrl += `&tao_value=${SAME_ORIGIN}`;
attribute_test(
load.image, destUrl,
invariants.assert_cross_origin_redirected_resource,
"Verify that cross origin resources' timings are not exposed when " +
"same-origin=>cross-origin=>same-origin redirects have " +
"`Timing-Allow-Origin:` headers with a specific origin.");
</script>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing TAO tests</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/common/custom-cors-response.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/tao-response.js"></script>
<body>
<script>
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
const run_test = (loader, resource_type) => {
attribute_test(loader, remote_tao_response(ORIGIN),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value contains only the origin. (${resource_type})`);
attribute_test(loader, remote_tao_response('*'),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value contains only a wildcard. (${resource_type})`);
attribute_test(loader, remote_tao_response(`${ORIGIN},fake`),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value list contains a case-sensitive match. (${resource_type})`);
attribute_test(loader, remote_tao_response(`${ORIGIN},*`),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value list contains the origin and a wildcard. (${resource_type})`);
attribute_test(loader, remote_tao_response('fake,*'),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value list contains a wildcard. (${resource_type})`);
attribute_test(loader, remote_tao_response('null'),
invariants.assert_tao_failure_resource,
`The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
`header value list contains a null origin. (${resource_type})`);
attribute_test(loader, remote_tao_response('*,*'),
invariants.assert_tao_pass_no_redirect_http,
`The timing allow check algorithm will pass when the Timing-Allow-Origin ` +
`header value list contains multiple wildcards. (${resource_type})`);
attribute_test(loader, remote_tao_response(ORIGIN.toUpperCase()),
invariants.assert_tao_failure_resource,
`The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
`header value contains only the uppercased origin. (${resource_type})`);
attribute_test(loader, remote_tao_response(`${ORIGIN} *`),
invariants.assert_tao_failure_resource,
`The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
`header value contains the origin, a space, then a wildcard. ` +
`(${resource_type})`);
attribute_test(loader, custom_cors_response({}, REMOTE_ORIGIN),
invariants.assert_tao_failure_resource,
`The timing allow check algorithm will fail when the Timing-Allow-Origin ` +
`header is not present. (${resource_type})`);
};
run_test(load.font, "font");
run_test(load.iframe, "iframe");
run_test(load.image, "image");
run_test(load.script, "script");
run_test(load.stylesheet, "stylesheet");
run_test(load.xhr_sync, "XMLHttpRequest");
</script>
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TAO - port mismatch must fail the check</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-timing-allow-origin"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script>
const {ORIGINAL_HOST, PORT, PORT2} = get_host_info();
// The main page is being requested on the default port (PORT), while the
// subresource will be requested on a separate port (PORT2). The response will
// have a Timing-Allow-Origin header value with the second port so this page's
// origin should not be a match.
const port_mismatch_url = `${location.protocol}//${ORIGINAL_HOST}:${PORT2}` +
`/resource-timing/resources/TAOResponse.py?` +
`tao=origin_port_${PORT2}`;
attribute_test(
fetch, port_mismatch_url, invariants.assert_tao_failure_resource,
"A port mismatch must fail the TAO check");
// The same URL as above except the Timing-Allow-Origin header will have the
// same port as this page's origin. Therefore, this page's origin will match
// the Timing-Allow-Origin header's value. Therefore, the subresource's timings
// must be exposed.
const port_match_url = `${location.protocol}//${ORIGINAL_HOST}:${PORT2}` +
`/resource-timing/resources/TAOResponse.py?` +
`tao=origin_port_${PORT}`;
attribute_test(
fetch, port_match_url, invariants.assert_tao_pass_no_redirect_http,
"An identical port must pass the TAO check");
</script>
</head>
<body>
<h1>Description</h1>
<p>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.</p>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize">
<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async t => {
await forceBufferFullEvent();
performance.clearResourceTimings();
return new Promise(resolve => {
new PerformanceObserver(t.step_func(() => {
assert_equals(performance.getEntriesByType('resource').length, 1,
'The entry should be available in the performance timeline!');
resolve();
})).observe({type: 'resource'});
load.script(scriptResources[2]);
});
}, "Test that entry was added to the buffer after a buffer full event");
</script>

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
await fillUpTheBufferWithSingleResource();
performance.addEventListener('resourcetimingbufferfull', () => {
performance.setResourceTimingBufferSize(2);
// The sync entry is added to the secondary buffer, so will be the last one there and eventually dropped.
load.xhr_sync(scriptResources[2]);
});
// This resource overflows the entry buffer, and goes into the secondary buffer.
load.script(scriptResources[1]);
await bufferFullFirePromise;
checkEntries(2);
}, "Test that entries synchronously added to the buffer during the callback are dropped");
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
await fillUpTheBufferWithSingleResource();
performance.addEventListener('resourcetimingbufferfull', () => {
performance.setResourceTimingBufferSize(3);
load.xhr_sync(scriptResources[2]);
});
// This resource overflows the entry buffer, and goes into the secondary buffer.
load.script(scriptResources[1]);
await bufferFullFirePromise;
checkEntries(3);
}, "Test that entries synchronously added to the buffer during the callback don't get dropped if the buffer is increased");
</script>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async t => {
addAssertUnreachedBufferFull(t);
await fillUpTheBufferWithSingleResource('resources/empty.js?willbelost');
// These resources overflow the entry buffer, and go into the secondary buffer.
load.xhr_sync(scriptResources[0]);
load.xhr_sync(scriptResources[1]);
performance.clearResourceTimings();
performance.setResourceTimingBufferSize(3);
load.xhr_sync(scriptResources[2]);
const entriesAfterAddition = performance.getEntriesByType('resource');
await waitForNextTask();
checkEntries(3);
assert_equals(entriesAfterAddition.length, 0, "No entries should have been added to the primary buffer before the task to 'fire a buffer full event'.");
}, "Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one");
</script>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that decreasing the buffer size in onresourcetimingbufferfull callback does not result in extra entries being dropped.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
performance.addEventListener('resourcetimingbufferfull', () => {
performance.setResourceTimingBufferSize(1);
});
await fillUpTheBufferWithTwoResources();
load.script(scriptResources[2]);
await bufferFullFirePromise;
checkEntries(2);
}, "Test that decreasing the buffer limit during the callback does not drop entries");
</script>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<link rel="help"
href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<title>This test validates that resource timing implementations have a finite
number of entries in their buffer.</title>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(t => {
return new Promise(resolve => {
let counter = 0;
performance.onresourcetimingbufferfull = resolve;
const loadImagesRecursively = () => {
// Load an image.
(new Image()).src = "resources/blue.png?" + counter;
++counter;
// Yield to enable queueing an entry, then recursively load another image.
t.step_timeout(loadImagesRecursively, 0);
};
loadImagesRecursively();
});
}, "Finite resource timing entries buffer size");
</script>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates increasing the buffer size in onresourcetimingbufferfull callback of resource timing.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
await fillUpTheBufferWithSingleResource();
performance.addEventListener('resourcetimingbufferfull', () => {
performance.setResourceTimingBufferSize(2);
});
await load.script(scriptResources[1]);
await bufferFullFirePromise;
checkEntries(2);
}, "Test that increasing the buffer during the callback is enough for entries not to be dropped");
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates the buffer doesn't contain more entries than it should inside onresourcetimingbufferfull callback.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async t => {
performance.addEventListener('resourcetimingbufferfull', t.step_func(() => {
assert_equals(performance.getEntriesByType("resource").length, 1,
"resource timing buffer in resourcetimingbufferfull is the size of the limit");
load.xhr_sync(scriptResources[2]);
performance.setResourceTimingBufferSize(3);
assert_equals(performance.getEntriesByType("resource").length, 1,
"A sync request must not be added to the primary buffer just yet, because it is full");
}));
await forceBufferFullEvent();
await waitForNextTask();
checkEntries(3);
}, "Test that entries in the secondary buffer are not exposed during the callback and before they are copied to the primary buffer");
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>This test validates that setResourceTimingBufferFull behaves appropriately when set to the current buffer level.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
let result = '';
performance.addEventListener('resourcetimingbufferfull', () => {
result += 'Event Fired with ' +
performance.getEntriesByType('resource').length + ' entries.';
performance.clearResourceTimings();
});
result += 'Before adding entries. ';
await fillUpTheBufferWithTwoResources();
result += 'After adding entries. ';
load.script(scriptResources[2]);
await bufferFullFirePromise;
assert_equals(result, 'Before adding entries. After adding entries. Event Fired with 2 entries.');
const entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 1,
'Number of entries in resource timing buffer is unexpected');
assert_true(entries[0].name.includes(scriptResources[2]),
'The entry must correspond to the last resource loaded.')
}, "Test that adding entries and firing the buffer full event happen in the right order.");
</script>

View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates the behavior of read and clear operation in onresourcetimingbufferfull callback of resource timing.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
await fillUpTheBufferWithSingleResource();
const entryBuffer = [];
performance.addEventListener('resourcetimingbufferfull', () => {
entryBuffer.push(...performance.getEntriesByType('resource'));
performance.clearResourceTimings();
});
load.script(scriptResources[1]);
await bufferFullFirePromise;
const entries = performance.getEntriesByType('resource');
assert_equals(entries.length, 1,
"Only the last entry should be stored in resource timing buffer since it's cleared once it overflows.");
assert_true(entries[0].name.includes(scriptResources[1]),
scriptResources[1] + " is in the entries buffer");
assert_equals(entryBuffer.length, 1,
'1 resource timing entry should be moved to entryBuffer.');
assert_true(entryBuffer[0].name.includes(scriptResources[0]),
scriptResources[0] + ' is in the entryBuffer');
}, "Test that entries overflowing the buffer trigger the buffer full event, can be stored, and make their way to the primary buffer after it's cleared in the buffer full event.");
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>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.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async t => {
addAssertUnreachedBufferFull(t);
await fillUpTheBufferWithTwoResources();
performance.setResourceTimingBufferSize(1);
await waitForNextTask();
checkEntries(2);
}, "Test that if the buffer is reduced after entries were added to it, those" +
" entries don't get cleared, nor is the resourcetimingbufferfull event" +
" being called.");
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE HTML>
<html>
<head onload>
<meta charset="utf-8" />
<title>This test validates that synchronously adding entries in onresourcetimingbufferfull callback results in these entries being properly handled.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async t => {
addAssertUnreachedBufferFull(t);
await fillUpTheBufferWithSingleResource();
// These resources overflow the entry buffer, and go into the secondary buffer.
load.xhr_sync(scriptResources[1]);
load.xhr_sync(scriptResources[2]);
// Immediately increase the size: the bufferfull event should not be fired.
performance.setResourceTimingBufferSize(3);
await waitForNextTask();
checkEntries(3);
}, "Test that overflowing the buffer and immediately increasing its limit does not trigger the resourcetimingbufferfull event");
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates the functionality of onresourcetimingbufferfull in resource timing.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performance-onresourcetimingbufferfull"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/buffer-full-utilities.js"></script>
</head>
<body>
<script>
promise_test(async () => {
let bufferFullCount = 0;
performance.addEventListener('resourcetimingbufferfull', e => {
assert_equals(e.bubbles, false, "Event bubbles attribute is false");
bufferFullCount++;
});
await fillUpTheBufferWithTwoResources();
// Overflow the buffer
await load.script(scriptResources[2]);
await waitForNextTask();
checkEntries(2);
assert_equals(bufferFullCount, 1, 'onresourcetimingbufferfull should have been invoked once.');
}, "Test that a buffer full event does not bubble and that resourcetimingbufferfull is called only once per overflow");
</script>
</body>
</html>

View File

@ -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.');

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing: test behavior for cached resources</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/observe-entry.js"></script>
</head>
<body>
<h1>Description</h1>
<p>Test that a reused resource only appears in the buffer once.</p>
<script>
// Need our own image loading helper because the one in resource-loaders.js
// is desgined to always side-step the HTTP cache but this test relies on the
// second request being resolved from the cache.
const load_image = path => new Promise(resolve => {
const img = document.createElement('img');
img.onload = img.onerror = () => resolve();
img.src = path;
document.body.append(img);
});
promise_test(async () => {
const blue = "resources/blue.png";
// First request. Should appear in the timeline.
await load_image(blue + "?cacheable");
// Second request. Should not appear in the timeline.
await load_image(blue + "?cacheable");
// Third request. When this request shows up in the timeline, we know that, if
// the second request would generate an entry, that entry would have already
// shown up in the timeline. Without this, we'd need to guess at how long to
// wait which tends to be flaky.
await load_image(blue + "?avoid-cache");
const entries = await new Promise(resolve => {
const accumulator = [];
new PerformanceObserver(entry_list => {
entry_list.getEntries().forEach(entry => {
if (!entry.name.includes("blue.png")) {
// Ignore resources other than blue images.
return;
}
accumulator.push(entry);
// Once we see the 'canary' resource, we don't need to wait anymore.
if (entry.name.endsWith('avoid-cache')) {
resolve(accumulator);
}
});
}).observe({'type': 'resource', 'buffered': true});
});
assert_equals(entries.length, 2, "There must be exactly 2 entries in the " +
"Performance Timeline");
assert_true(entries[0].name.endsWith("blue.png?cacheable"));
assert_true(entries[1].name.endsWith("blue.png?avoid-cache"));
}, "When a resource is resolved from cache, there must not be a " +
"corresponding entry in the Performance Timeline");
</script>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates the functionality of clearResourceTimings method
in resource timing.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#dom-performance-clearresourcetimings">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
test(() => {
assert_equals(performance.getEntriesByType("resource").length, 2,
"Resource timing entries exist");
performance.clearResourceTimings();
assert_equals(performance.getEntriesByType("resource").length, 0,
"Resource timing entries are cleared");
}, "Test that clearResourceTimings() clears the performance timeline buffer");
</script>
</head>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing connection reuse</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/connection-reuse-test.js"></script>
<script>
const {HTTPS_ORIGIN} = get_host_info();
// Fetches the given subresource a couple times with the same connection.
const http_path = "resources/fake_responses.py";
connection_reuse_test(http_path,
{
'on_200': invariants.assert_tao_pass_no_redirect_http,
'on_304': invariants.assert_tao_pass_304_not_modified_http,
}, "Reuse HTTP connection");
// Like above, but the subresource is fetched over HTTPS while this page is
// fetched over HTTP.
const https_url = `${HTTPS_ORIGIN}/resource-timing/${http_path}`;
connection_reuse_test(https_url,
{
'on_200': invariants.assert_tao_pass_no_redirect_https,
'on_304': invariants.assert_tao_pass_304_not_modified_https,
}, "Reuse HTTPS connection from HTTP page");
// Like the above mixed-content test but the final resource is behind an HTTP
// redirect response.
const redirect_path = (() => {
// The resource behind the redirect is the same fake_responses.py handler
// on the HTTPS origin. Pass it through encodeURIComponent so that it can
// be passed through a query-parameter.
const redirect_url = encodeURIComponent(https_url)
// The request is made to the HTTPS origin with a query parameter that will
// cause a 302 response.
return `${https_url}?redirect=${redirect_url}`;
})();
connection_reuse_test(redirect_path,
{
'on_200': invariants.assert_tao_enabled_cross_origin_redirected_resource,
'on_304': invariants.assert_tao_enabled_cross_origin_redirected_resource,
}, "Reuse HTTPS connection with redirects from an HTTP page");
</script>
</head>
<body>
<h1>Description</h1>
<p>See <a href="resources/connection-reuse-test.js">the included test
script</a></p>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing connection reuse</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/connection-reuse-test.js"></script>
<script>
connection_reuse_test("resources/fake_responses.py",
{
'on_200': invariants.assert_tao_pass_no_redirect_https,
'on_304': invariants.assert_tao_pass_304_not_modified_https,
}, "Reuse an HTTPS connection");
</script>
</head>
<body>
<h1>Description</h1>
<p>See <a href="resources/connection-reuse-test.js">the included test
script</a></p>
</body>
</html>

View File

@ -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');

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test ResourceTiming reporting for cross-origin iframe.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/observe-entry.js"></script>
</head>
<body>
<body>
<script>
const {REMOTE_ORIGIN} = get_host_info();
promise_test(async t => {
const iframe = document.createElement('iframe');
t.add_cleanup(() => iframe.remove());
iframe.src = `${REMOTE_ORIGIN}/resource-timing/resources/green.html`;
document.body.appendChild(iframe);
const entry = await observe_entry(iframe.src);
invariants.assert_tao_failure_resource(entry);
}, "A cross-origin iframe should report an opaque RT entry");
promise_test(async t => {
const iframe = document.createElement('iframe');
t.add_cleanup(() => iframe.remove());
iframe.src = `${REMOTE_ORIGIN}/resource-timing/resources/TAOResponse.py?tao=wildcard`;
document.body.appendChild(iframe);
const entry = await observe_entry(iframe.src);
invariants.assert_tao_pass_no_redirect_http(entry);
}, "A cross-origin iframe with TAO enabled should report a full RT entry");
</script>

View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates the values in resource timing for cross-origin
redirects.</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/custom-cors-response.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/tao-response.js"></script>
</head>
<body>
<script>
const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
const HTTP_SO_to_XO_redirect_url = url => {
// Make an initial request to a same-domain resource that will return a 302
// redirect to the given (possibly cross-origin) url.
return `/resource-timing/resources/redirect-cors.py?location=${url}`;
};
const HTTP_SO_resource = () => {
if (location.protocol != "http:") {
throw new Error("Can only make an HTTP SO request if this page was " +
"served over HTTP.");
}
return tao_response("*", ORIGIN);
};
const HTTP_XO_redirect = (url, tao) => {
const ret = new URL(
`${REMOTE_ORIGIN}/resource-timing/resources/redirect-cors.py`);
ret.searchParams.append("location", url);
ret.searchParams.append("allow_origin", "*");
ret.searchParams.append("timing_allow_origin", tao);
return ret.href;
};
attribute_test(
load.iframe, HTTP_SO_to_XO_redirect_url(custom_cors_response({},
REMOTE_ORIGIN)),
invariants.assert_http_to_cross_origin_redirected_resource,
"Verify that cross-origin resources' timings aren't exposed through HTTP " +
"redirects.");
attribute_test(
load.iframe, HTTP_SO_to_XO_redirect_url(remote_tao_response("no-match")),
invariants.assert_cross_origin_redirected_resource,
"Verify that a redirected cross-origin resources' timings aren't exposed " +
"when the TAO check fails.");
attribute_test(
load.iframe, HTTP_SO_to_XO_redirect_url(remote_tao_response("*")),
invariants.assert_http_to_tao_enabled_cross_origin_https_redirected_resource,
"Verify that cross-origin resources' timings are exposed when the TAO " +
"check succeeds. Also verify that secureConnectionStart is 0 since the " +
"original request was over HTTP.");
attribute_test(
load.iframe, HTTP_XO_redirect(HTTP_XO_redirect(HTTP_SO_resource(), "*"), "*"),
invariants.assert_http_to_tao_enabled_cross_origin_https_redirected_resource,
"Verify that a redirect chain through cross-origin resources have their " +
"timings exposed when all TAO checks succeed. Also verify that " +
"secureConnectionStart is 0 since the original request was over HTTP.");
const failure_permutations = [
["fail", "fail", "fail"],
["fail", "fail", "*" ],
["fail", "*", "fail"],
["fail", "*", "*" ],
["*", "fail", "fail"],
["*", "fail", "*" ],
["*", "*", "fail"],
];
const test_case = (so_tao, xo1_tao, xo2_tao) => {
return HTTP_XO_redirect(HTTP_XO_redirect(HTTP_SO_resource(
so_tao), xo2_tao), xo1_tao);
};
const test_label = perm => {
return perm.map(x => {
if (x == "*" ) return "PASS";
if (x == "fail" ) return "FAIL";
throw new Error(`unexpected element ${x}`);
}).join(" -> ");
};
for (const permutation of failure_permutations) {
attribute_test(
load.iframe, test_case.apply(permutation),
invariants.assert_tao_failure_resource,
`Verify that a redirect chain through cross-origin resources do not have ` +
`their timings exposed when any of the TAO checks fail. ` +
`(${test_label(permutation)})`);
}
</script>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates the values in resource timing for cross-origin
redirects.</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
</head>
<body>
<script>
const {REMOTE_ORIGIN} = get_host_info();
const delay = 2
const blank_page = `/resource-timing/resources/blank_page_green.htm`;
const destUrl = `/common/slow-redirect.py?delay=${delay}&location=${REMOTE_ORIGIN}/${blank_page}`;
const timeBefore = performance.now()
attribute_test(load.iframe, destUrl, entry => {
assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
assert_greater_than(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
// See https://github.com/w3c/resource-timing/issues/264
assert_less_than(Math.round(entry.startTime - timeBefore), delay * 1000, 'startTime should not expose redirect delays');
}, "Verify that cross-origin resources don't implicitly expose their redirect timings")
</script>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<title>Resource Timing: PerformanceResourceTiming attributes shouldn't change
if the HTTP status code changes</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
</head>
<body>
<img id="img_200">
<img id="img_307">
<img id="img_404">
<img id="img_502">
<script id="script_200"></script>
<script id="script_307"></script>
<script id="script_404"></script>
<script id="script_502"></script>
<script>
const listenForPerformanceEntries = num_expected => {
return new Promise(resolve => {
let results = [];
new PerformanceObserver(entryList => {
entryList.getEntries().forEach(entry => {
if (!entry.name.includes("status-code"))
return;
results.push(entry);
if (results.length == num_expected) {
resolve(results);
}
});
}).observe({entryTypes: ['resource']});
});
}
promise_test(async t => {
const destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/';
const statusCodes = ['200', '307', '404', '502'];
let expected_entry_count = 0;
statusCodes.forEach(status => {
document.getElementById(`img_${status}`).src = `${destUrl}status-code.py?status=${status}`;
document.getElementById(`script_${status}`).src = `${destUrl}status-code.py?status=${status}&script=1`;
expected_entry_count += 2;
});
const entries = await listenForPerformanceEntries(expected_entry_count);
// We will check that the non-timestamp values of the entry match for all
// entries.
const keys = [
'entryType',
'nextHopProtocol',
'transferSize',
'encodedBodySize',
'decodedBodySize',
];
const first = entries[0];
entries.slice(1).forEach(entry => {
keys.forEach(attribute => {
assert_equals(entry[attribute], first[attribute],
`There must be no discernible difference for the ${attribute} ` +
`attribute but found a difference for the ${entry.name} resource.`);
})});
}, "Make sure cross origin resource fetch failures with different status codes are indistinguishable");
</script>

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script>
// Open a document on one of hosts on the web-platform test domain, so that
// document.domain will set a valid domain, turning the frame into a
// cross-origin frame.
const {OTHER_ORIGIN} = get_host_info();
const openee = window.open(OTHER_ORIGIN +
"/resource-timing/resources/document-domain-no-impact.html");
fetch_tests_from_window(openee);
</script>

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates that a failed cross-origin fetch creates an opaque network timing entry.
</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
</head>
<body>
<script>
const validDataURL = 'data:,Hello%2C%20World%21'
const {REMOTE_ORIGIN, ORIGINAL_HOST, HTTP_PORT} = get_host_info();
const validXmlUrl = '/common/dummy.xml';
network_error_entry_test(
`${REMOTE_ORIGIN}${validXmlUrl}`, null, `failed cross-origin requests`);
network_error_entry_test(`/common/redirect.py?location=${validDataURL}`, null, "non-HTTP redirect");
network_error_entry_test('//{{hosts[][nonexistent]}}/common/dummy.xml', null, "DNS failure");
network_error_entry_test(`http://${ORIGINAL_HOST}:${HTTP_PORT}/commo/dummy.xml`, null, "Mixed content");
network_error_entry_test('/common/dummy.xml', {cache: 'only-if-cached', mode: 'same-origin'},
"only-if-cached resource that was not cached");
network_error_entry_test(
`/element-timing/resources/multiple-redirects.py?redirect_count=22&final_resource=${validXmlUrl}`,
null, "too many redirects");
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing: PerformanceResourceTiming attributes</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
<script>
attribute_test(
load.image, "resources/fake_responses.py#hash=1",
entry => {
assert_true(entry.name.includes('#hash=1'),
"There should be a hash in the resource name");
invariants.assert_tao_pass_no_redirect_http(entry);
},
"Image resources should generate conformant entries");
attribute_test(
load.font, "/fonts/Ahem.ttf",
invariants.assert_tao_pass_no_redirect_http,
"Font resources should generate conformant entries");
attribute_test(
load.image, "/common/redirect.py?location=resources/fake_responses.py",
invariants.assert_same_origin_redirected_resource,
"Same-origin redirects should populate redirectStart/redirectEnd");
</script>
</head>
<body>
<h1>Description</h1>
<p>This test validates that PerformanceResourceTiming entries' attributes are
populated with the correct values.</p>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="timeout" content="long">
<title>Resource Timing: EventSource timing behavior</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
</head>
</script>
<script>
async_test(t => {
const repetitions = 2;
const url = new URL(`/eventsource/resources/message.py`, location.href);
const eventSource = new EventSource(url);
let messages = 0;
t.add_cleanup(() => eventSource.close());
eventSource.addEventListener('message', () => {
++messages;
})
new PerformanceObserver(() => {
const entries = performance.getEntriesByName(url);
assert_greater_than_equal(entries.length, messages - 1);
if (entries.length === repetitions)
t.done();
}).observe({type: 'resource'});
}, "ResourceTiming for EventSource should reflect number of re-connections to source");
</script>
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test cross-origin fetch redirects have the right values.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
<script>
const {REMOTE_ORIGIN, ORIGIN} = get_host_info();
const redirect = "/common/redirect.py?" +
"location=/resource-timing/resources/green.html";
const cross_origin_redirect = REMOTE_ORIGIN + redirect;
const same_origin_redirect = ORIGIN + redirect;
attribute_test(
url => fetch(url, {mode: "no-cors", credentials: "include"}),
new URL(cross_origin_redirect).href,
invariants.assert_cross_origin_redirected_resource,
"Test fetching through a cross-origin redirect URL"
);
attribute_test(
url => fetch(url, {mode: "no-cors", credentials: "include"}),
new URL(same_origin_redirect).href,
invariants.assert_same_origin_redirected_resource,
"Test fetching through a same-origin redirect URL"
);
</script>

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test cross-origin fetch redirects have the right values.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>
<script>
const load_font = url => {
document.body.innerHTML = `
<style>
@font-face {
font-family: ahem;
src: url('${url}');
}
</style>
<div style="font-family: ahem;">This fetches ahem font.</div>
`;
return document.fonts.ready;
};
const run_test = async (t, url) => {
// Set up PerformanceObserver
const href = new URL(url).href;
const setPerformanceObserver = new Promise(resolve => {
const po = new PerformanceObserver(resolve);
po.observe({type: "resource"});
});
// Load the font resource and wait for it to be fetched.
await load_font(href);
// Wait for an entry
const timeout = new Promise(resolve => t.step_timeout(resolve, 3000));
const list = await Promise.race([setPerformanceObserver, timeout]);
assert_equals(typeof(list), "object", "No iframe entry was fired");
const entries = list.getEntriesByName(url);
assert_equals(entries.length, 1);
// Test entry values
const entry = entries[0];
assert_greater_than(entry.fetchStart, 0, "fetchStart should be greater than 0 in redirects.");
assert_greater_than_equal(entry.domainLookupStart, entry.fetchStart, "domainLookupStart should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.domainLookupEnd, entry.domainLookupStart, "domainLookupEnd should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.connectStart, entry.domainLookupEnd, "connectStart should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.secureConnectionStart, entry.connectStart, "secureConnectionStart should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.connectEnd, entry.secureConnectionStart, "connectEnd should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.requestStart, entry.connectEnd, "requestStart should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.responseStart, entry.requestStart, "responseStart should be more than 0 in same-origin redirect.");
assert_greater_than_equal(entry.responseEnd, entry.responseStart, "responseEnd should be greater than 0 in redirects.");
assert_greater_than_equal(entry.duration, 0, "duration should be greater than 0 in redirects.");
}
const {HTTPS_REMOTE_ORIGIN} = get_host_info();
promise_test(t => {
return run_test(t, HTTPS_REMOTE_ORIGIN + "/fonts/Ahem.ttf");
}, "Test a font's timestamps");
promise_test(t => {
return run_test(t, HTTPS_REMOTE_ORIGIN + "/resource-timing/resources/cors-ahem.py?pipe=trickle(d1)");
}, "Test a font's timestamps with delays");
</script>

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting timing for frames.</title>
<frameset>
<frame src="resources/frameset-timing-frame.html" />
</frameset>

View File

@ -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']
});
}
);

View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing - test that unsuccessful iframes create entries</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href=
"https://www.w3.org/TR/resource-timing-2/#resources-included-in-the-performanceresourcetiming-interface"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
<body>
<script>
// Like load.iframe but fetches the iframe under a "default-src 'none'"
// Content-Security-Policy.
const load_iframe_with_csp = async path => {
return load.iframe_with_attrs(path, {"csp": "default-src 'none'"});
};
// Runs a test (labeled by the given label) to verify that loading an iframe
// with the given URL generates a PerformanceResourceTiming entry and that the
// entry does not expose sensitive timing attributes.
const masked_entry_test = (url, label) => {
return attribute_test(load.iframe, url,
invariants.assert_tao_failure_resource, label);
};
// Runs a test (labeled by the given label) to verify that loading an iframe
// with the given URL generates a PerformanceResourceTiming entry and that the
// entry does expose sensitive timing attributes.
const unmasked_entry_with_csp_test = (url, label) => {
return attribute_test(load_iframe_with_csp, url,
invariants.assert_tao_pass_no_redirect_http, label);
};
// Runs a test (labeled by the given label) to verify that loading an iframe
// with the given URL under a "default-src 'none' Content-Security-Policy
// generates a PerformanceResourceTiming entry and that the entry does not
// expose sensitive timing attributes.
const masked_entry_with_csp_test = (url, label) => {
return attribute_test(load_iframe_with_csp, url,
invariants.assert_tao_failure_resource, label);
};
// Runs a test (labeled by the given label) to verify that loading an iframe
// with the given URL, an empty response body and under a "default-src 'none'
// Content-Security-Policy generates a PerformanceResourceTiming entry and that
// the entry does expose sensitive timing attributes.
const empty_unmasked_entry_with_csp_test = (url, label) => {
return attribute_test(load_iframe_with_csp, url,
invariants.assert_tao_pass_no_redirect_http_empty, label);
};
const {REMOTE_ORIGIN, ORIGINAL_HOST, HTTPS_PORT} = get_host_info();
const unhosted_url = `https://nonexistent.${ORIGINAL_HOST}:${HTTPS_PORT}/`;
masked_entry_test(
unhosted_url,
"Test iframe from non-existent host gets reported");
masked_entry_test(
"/resource-timing/resources/fake_responses.py?redirect=" + unhosted_url,
"Test iframe redirecting to non-existent host gets reported");
unmasked_entry_with_csp_test("/resource-timing/resources/csp-default-none.html",
"Same-origin iframe that complies with CSP attribute gets reported");
unmasked_entry_with_csp_test("/resource-timing/resources/green-frame.html",
"Same-origin iframe that doesn't comply with CSP attribute gets reported");
masked_entry_with_csp_test(
new URL("/resource-timing/resources/csp-default-none.html", REMOTE_ORIGIN),
"Cross-origin iframe that complies with CSP attribute gets reported");
masked_entry_with_csp_test(
new URL("/resource-timing/resources/green-frame.html", REMOTE_ORIGIN),
"Cross-origin iframe that doesn't comply with CSP attribute gets reported");
empty_unmasked_entry_with_csp_test(
"/resource-timing/resources/200_empty.asis",
"Same-origin empty iframe with a 200 status gets reported");
masked_entry_with_csp_test(
new URL("/resource-timing/resources/200_empty.asis", REMOTE_ORIGIN),
"Cross-origin empty iframe with a 200 status gets reported");
unmasked_entry_with_csp_test(
new URL("/resource-timing/resources/204_empty.asis"),
"Same-origin empty iframe with a 204 status gets reported");
unmasked_entry_with_csp_test(
new URL("/resource-timing/resources/205_empty.asis"),
"Same-origin empty iframe with a 205 status gets reported");
masked_entry_with_csp_test(
new URL("/resource-timing/resources/204_empty.asis", REMOTE_ORIGIN),
"Cross-origin empty iframe with a 204 status gets reported");
masked_entry_with_csp_test(
new URL("/resource-timing/resources/205_empty.asis", REMOTE_ORIGIN),
"Cross-origin empty iframe with a 205 status gets reported");
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting iframe timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<body>
<script>
function test(href, type) {
promise_test(async t => {
await load.iframe(href);
const entries = performance.getEntriesByType('resource').filter(({name}) => name.includes(href));
assert_equals(entries.length, 1);
assert_equals(entries[0].initiatorType, 'iframe');
}, `Iframes should report resource timing for ${type} iframes`);
}
test('/common/square.png', 'image');
test('/common/dummy.xhtml', 'xhtml');
test('/common/dummy.xml', 'xml');
test('/common/text-plain.txt', 'text');
</script>
</body>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting iframe timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<body>
<script>
promise_test(async t => {
const href = new URL('resources/redirect-without-location.py', location.href);
await load.iframe(href);
const entries = performance.getEntriesByType('resource').filter(({name}) => name.startsWith(href));
assert_equals(entries.length, 1);
assert_equals(entries[0].initiatorType, 'iframe');
}, 'Iframes should report resource timing for redirect responses without a location');
</script>
</body>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting iframe timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/frame-timing.js"></script>
<body>
<script>
test_frame_timing_before_load_event('iframe');
test_frame_timing_change_src('iframe');
</script>
</body>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting iframe timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/resource-loaders.js"></script>
<body>
<script>
promise_test(async t => {
const href = new URL('resources/download.asis', location.href);
const iframe = document.createElement('iframe');
iframe.src = href;
const errored = new Promise(resolve => iframe.addEventListener('error', resolve));
const loaded = new Promise(resolve => iframe.addEventListener('load', resolve));
document.body.appendChild(iframe);
const timeout = 2000;
t.add_cleanup(() => iframe.remove());
const expired = new Promise(resolve => t.step_timeout(resolve, timeout));
await Promise.any([loaded, expired, errored]);
const entries = performance.getEntriesByType('resource').filter(({name}) => name.startsWith(href));
assert_equals(entries.length, 0);
}, 'Iframes should not report resource timing for non-handled mime-types (downloads)');
</script>
</body>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting image timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
function test_image_sequence(src, event, t) {
const image = document.createElement('img');
const absoluteURL = new URL(src, location.href).toString();
document.body.appendChild(image);
t.add_cleanup(() => image.remove());
return new Promise(resolve => {
image.addEventListener(event, t.step_func(() => {
assert_equals(performance.getEntriesByName(absoluteURL).length, 1);
resolve();
}));
image.src = src;
});
}
promise_test(t => test_image_sequence('resources/blue.png', 'load', t),
"An image should receive its load event after the ResourceTiming entry is available");
promise_test(t => test_image_sequence('resources/nothing-at-all.png', 'error', t),
"A non-existent (404) image should receive its error event after the ResourceTiming entry is available");
promise_test(t => test_image_sequence('resources/invalid.png', 'error', t),
"An invalid image should receive its error event after the ResourceTiming entry is available");
</script>

View File

@ -0,0 +1,67 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates that the initiatorType information for various
Resource Timing entries is accurate for scripts.</title>
<link rel="help"
href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
<!-- Tested resources -->
<script src="resources/empty_script.js?id=blocking"></script>
<script src="resources/empty_script.js?id=async" async></script>
<script src="resources/empty_script.js?id=async_false" async=false></script>
<script src="resources/empty_script.js?id=defer" defer></script>
<script>
document.write("<script src='resources/empty_script.js?id=doc_written'></scr"
+ "ipt>");
const head = document.getElementsByTagName("head")[0];
const s1 = document.createElement("script");
s1.src = "empty_script.js?id=appended";
head.appendChild(s1);
const s2 = document.createElement("script");
s2.src = "empty_script.js?id=appended_async";
s2.async = true;
head.appendChild(s2);
const s3 = document.createElement("script");
s3.src = "empty_script.js?id=appended_aync_false";
s3.async = false;
head.appendChild(s3);
const s4 = document.createElement("script");
s4.src = "empty_script.js?id=appended_defer";
s4.defer = true;
head.appendChild(s4);
</script>
</head>
<body>
<script>
const wait_for_onload = () => {
return new Promise(resolve => {
window.addEventListener("load", resolve);
})};
promise_test(
async () => {
await wait_for_onload();
const entry_list = performance.getEntriesByType("resource");
for (entry of entry_list) {
if (entry.name.includes("empty_script.js")) {
assert_equals(entry.initiatorType, "script",
"initiatorType should be 'script' for " + entry.name);
}
}
}, "Validate initiatorType for scripts is 'script'");
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: audio</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<audio src="/resource-timing/resources/empty.py?id=src"></audio>
<audio>
<source src="/resource-timing/resources/empty.py?id=source-wav"
type="audio/wav" />
</audio>
<audio>
<source src="/resource-timing/resources/empty.py?id=source-mpeg"
type="audio/mpeg" />
</audio>
<audio>
<source src="/resource-timing/resources/empty.py?id=source-ogg"
type="audio/ogg" />
</audio>
<script>
initiator_type_test("empty.py?id=src", "audio", "<audio src> without 'type' attribute");
initiator_type_test("empty.py?id=source-wav", "audio", "<source src> with type 'audio/wav'");
initiator_type_test("empty.py?id=source-mpeg", "audio", "<source src> with type 'audio/mpeg'");
initiator_type_test("empty.py?id=source-ogg", "audio", "<source src> with type 'audio/ogg'");
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing - initiatorType with dynamic insertion</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/entry-invariants.js"></script>
<script src="/resource-timing/resources/resource-loaders.js"></script>
<script>
const dynamic_initiator_type_test = (loader, path, expected_type,
resource_type) => {
attribute_test(loader, path, entry => {
assert_equals(entry.initiatorType, expected_type);
}, `A ${resource_type} should have the '${expected_type}' initiator type.`);
};
dynamic_initiator_type_test(load.image, "resources/resource_timing_test0.png",
"img", "image");
// Note that, to download a font, 'load.font' uses a <style> element to
// construct a font-face that is then applied to a <div>. Since it's a <style>
// element requesting the resource, the initiator type is 'css', not 'font'.
dynamic_initiator_type_test(load.font, "/fonts/Ahem.ttf", "css", "font");
dynamic_initiator_type_test(load.stylesheet,
"resources/resource_timing_test0.css", "link", "stylesheet");
dynamic_initiator_type_test(load.iframe, "resources/green.html", "iframe",
"iframe");
dynamic_initiator_type_test(load.script, "resources/empty.js", "script",
"script");
dynamic_initiator_type_test(load.xhr_sync, "resources/empty.py",
"xmlhttprequest", "XMLHttpRequest");
</script>
</head>
<body>
<h1>Description</h1>
<p>This test validates that the initiatorType field is correct even when an
element is dynamically inserted.</p>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: embed</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<embed src="/resource-timing/resources/resource_timing_test0.css"
type="text/css">
<script>
initiator_type_test("resource_timing_test0.css", "embed", "<embed>");
</script>
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: frameset</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
<script>
initiator_type_test("green.html", "frame", "<frame> in a <frameset>");
</script>
</head>
<!-- Although framesets were deprecated in HTML5, we still want to make sure
Resource Timing is emitting entries for the underlying resources' requests.
-->
<frameset>
<frame src="/resource-timing/resources/green.html">
</frameset>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: iframe</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<iframe src="/resource-timing/resources/green.html"></iframe>
<script>
initiator_type_test("green.html", "iframe", "<iframe>");
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: img with srcset attribute</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<img src="/resource-timing/resources/resource_timing_test0.png"
srcset="/resource-timing/resources/resource_timing_test0.png?id=srcset 67w"
sizes="67px"></img>
<script>
initiator_type_test("resource_timing_test0.png?id=srcset", "img", "<img srcset>");
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: img</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<img src="/resource-timing/resources/resource_timing_test0.png"></img>
<script>
initiator_type_test("resource_timing_test0.png", "img", "<img>");
</script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: input</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<input type="image" src="/resource-timing/resources/resource_timing_test0.png">
<script>
initiator_type_test("resource_timing_test0.png", "input", "<input type=image>");
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: link</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<link rel="stylesheet" href="/resource-timing/resources/nested.css">
<link rel="prefetch"
href="/resource-timing/resources/resource_timing_test0.css?id=prefetch">
<link rel="preload" as="style"
href="/resource-timing/resources/resource_timing_test0.css?id=preload">
<link rel="prerender" href="/resource-timing/resources/green.html?id=prerender">
<link rel="manifest" href="/resource-timing/resources/manifest.json">
<link rel="modulepreload" href="resources/empty.js?id=modulePreload">
<script>
initiator_type_test("nested.css", "link", "<link>");
// Verify there are enries for each of nested.css' nested resources.
initiator_type_test("resource_timing_test0.css?id=n1", "css", "css resources embedded in css");
initiator_type_test("fonts/Ahem.ttf?id=n1", "css", "font resources embedded in css");
initiator_type_test("blue.png?id=n1", "css", "image resources embedded in css");
initiator_type_test("resource_timing_test0.css?id=prefetch", "link", "<link prefetch>");
initiator_type_test("resource_timing_test0.css?id=preload", "link", "<link preload>");
initiator_type_test("green.html?id=prerender", "link", "<link prerender>");
initiator_type_test("manifest.json", "link", "<link manifest>");
initiator_type_test("resources/empty.js?id=modulePreload", "other", "module preload");
</script>
<ol>This content forces a font to get fetched</ol>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: miscellaneous elements</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body background="/resource-timing/resources/blue.png?id=body">
<input type="image" src="/resource-timing/resources/blue.png?id=input">
<object type="image/png" data="/resource-timing/resources/blue.png?id=object">
</object>
<script>
navigator.sendBeacon('/resource-timing/resources/empty.py?id=beacon');
fetch('/resource-timing/resources/empty.py?id=fetch');
const evtSource = new EventSource('/resource-timing/resources/eventsource.py?id=eventsource');
</script>
<script>
initiator_type_test("blue.png?id=body", "body", "<body background>");
initiator_type_test("blue.png?id=input", "input", "<input type='image'>");
initiator_type_test("blue.png?id=object", "object", "<object type='image/png'>");
initiator_type_test("empty.py?id=beacon", "beacon", "sendBeacon()");
initiator_type_test("empty.py?id=fetch", "fetch", "for fetch()");
initiator_type_test("eventsource.py?id=eventsource", "other", "new EventSource()");
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: picture</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<picture>
<source srcset="blue.png?id=picture-source" type="image/png" />
<img src="blue.png?id=picture-img" />
</picture>
<picture>
<source srcset="blue.png?id=picture-notsupported-source" type="image/notsupported" />
<img src="blue.png?id=picture-notsupported-img" />
</picture>
<picture>
<img src="blue.png?id=picture-img-src"
srcset="blue.png?id=picture-img-srcset"
sizes="67px"></img>
</picture>
<picture>
<img src="blue.png?id=picture-99x-img-src"
srcset="blue.png?id=picture-99x-img-srcset 99x"
sizes="67px"></img>
</picture>
<script>
initiator_type_test("blue.png?id=picture-source", "img", "<source> in a <picture>");
initiator_type_test("blue.png?id=picture-notsupported-img", "img", "<img> in a <picture>");
initiator_type_test("blue.png?id=picture-img-srcset", "img", "<img srcset> in a <picture>");
initiator_type_test("blue.png?id=picture-99x-img-src", "img", "<img src> in a <picture>");
</script>
</body>
</html>

View File

@ -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}'`);
};

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: script</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<script src="/resource-timing/resources/empty_script.js"></script>
<script>
const async_xhr = new XMLHttpRequest;
async_xhr.open('GET', '/resource-timing/resources/blue.png?id=async_xhr',
true);
async_xhr.send();
</script>
<script>
initiator_type_test("empty_script.js", "script", "<script>");
initiator_type_test("blue.png?id=async_xhr", "xmlhttprequest", "an asynchronous XmlHTTPRequest");
</script>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: style</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<style>
iframe {
background: url('/resource-timing/resources/blue.png?id=background');
}
body {
cursor: url('/resource-timing/resources/blue.png?id=cursor'), pointer;
}
ul {
list-style-image: url('/resource-timing/resources/blue.png?id=list-style');
}
@font-face {
font-family: remoteFontAhem;
src: url('/fonts/Ahem.ttf');
}
.ahem {
font-family: remoteFontAhem;
}
</style>
<iframe>This iframe forces the 'background' resource to be fetched.</iframe>
<ul>
<li>This content forces the 'list-style-image' resource to be fetched.</li>
</ul>
<div class="ahem">This content forces the '@font-face' resource to be fetched.</div>
<script>
initiator_type_test("blue.png?id=background", "css", "'background' attributes in <style> elements");
initiator_type_test("blue.png?id=cursor", "css", "'cursor' attributes in <style> elements");
initiator_type_test("blue.png?id=list-style", "css", "'list-style-image' attributes in <style> elements");
initiator_type_test("fonts/Ahem.ttf", "css", "'@font-face' resources");
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: svg</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<svg width=200 height=200
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image href="/resource-timing/resources/blue.png" height="200" width="200"/>
</svg>
<script>
initiator_type_test("blue.png", "image", "<image> in an <svg>");
</script>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiator type: video</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<body>
<video poster="/resource-timing/resources/blue.png?id=poster"></video>
<video src="/media/test.mp4?id=src" autoplay="true"></video>
<video autoplay="true">
<source src="/media/test.mp4?id=source-mp4" type="video/mp4">
<track kind="subtitles" srclang="en" default
src="/resource-timing/resources/empty.py?id=track">
</video>
<video autoplay="true">
<source src="/media/test.ogv?id=source-ogv" type="video/ogg">
</video>
<script>
initiator_type_test("blue.png?id=poster", "video", "<video poster>");
initiator_type_test("media/test.mp4?id=src", "video", "<video src>");
initiator_type_test("media/test.mp4?id=source-mp4", "video", "<source src> with type=\"video/mp4\"");
initiator_type_test("empty.py?id=track", "track", "<track src>");
initiator_type_test("media/test.ogv?id=source-ogv", "video", "<source src> with type=\"video/ogg\"");
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing initiatorType: worker resources</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-initiatortype"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
<script src="resources/initiator-type-test.js"></script>
</head>
<script>
const moduleWorkerURL = 'resources/empty.js?moduleWorker';
const workerURL = 'resources/empty.js?worker';
new Worker(moduleWorkerURL, {type: "module"});
new Worker(workerURL, {type: "classic"});
initiator_type_test(workerURL, "other", "classic worker");
initiator_type_test(moduleWorkerURL, "other", "module worker");
</script>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting input timing.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
async_test(t => {
const input = document.createElement('input');
input.type = "image";
const absoluteURL = new URL('resources/blue.png', location.href).toString();
t.add_cleanup(() => input.remove());
input.addEventListener('load', t.step_func(() => {
assert_equals(performance.getEntriesByName(absoluteURL).length, 1);
t.done();
}));
input.src = absoluteURL;
document.body.appendChild(input);
}, "An image input element should receive its load event after the ResourceTiming entry is available");
</script>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Test the sequence of events when reporting link timing.</title>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
promise_test(async t => {
const link = document.createElement('link');
const delay = 500;
const src = `./resources/import.sub.css?delay=${delay}`
const absoluteURL = new URL(src, location.href).toString();
new PerformanceObserver(t.step_func(() => {
const allPerformanceEntries = performance.getEntriesByType('resource');
const linkEntry = allPerformanceEntries.find(e => e.name.includes('import.sub.css'));
const importEntry = allPerformanceEntries.find(e => e.name.includes('delay-css'));
if (!linkEntry || !importEntry)
return;
const linkEndTime = linkEntry.startTime + linkEntry.duration;
const importEndTime = importEntry.startTime + importEntry.duration;
assert_greater_than_equal(importEndTime, linkEndTime + delay, "link load should be done before import load");
t.done();
})).observe({type: 'resource'});
link.href = src;
link.rel = 'stylesheet';
document.head.appendChild(link);
}, "test that @imports don't affect link resource timings");
</script>

View File

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This tests transfer size of resource timing when loaded from memory cache.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/entry-invariants.js"></script>
</head>
<body>
<script>
function getScript(url) {
const script = document.createElement("script");
const loaded = new Promise(resolve => {
script.onload = script.onerror = resolve;
});
script.src = url;
document.body.appendChild(script);
return loaded;
}
function add_iframe(url) {
return new Promise(function (resolve) {
var frame = document.createElement('iframe');
frame.src = url;
frame.onload = function () { resolve(frame); };
document.body.appendChild(frame);
});
}
promise_test(async t => {
// Add unique token to url so that each run the url is different to avoid
// flakiness.
let url = 'resources/resource_timing_test0.js?unique=' +
Math.random().toString().substr(2);
let frame;
return add_iframe('resources/iframe-load-from-mem-cache-transfer-size.html')
.then((f) => {
frame = f;
// Load script onto iframe in order to get it into the memory cache.
return frame.contentWindow.getScript(url.split('/')[1])
})
.then(() => {
// Verify that the transferSize in case of normal load is greater than
// 0.
assert_positive_(
frame.contentWindow.performance.getEntriesByType('resource')
.filter(e => e.name.includes(url))[0], ['transferSize']);
// Load the same script onto the parent document. This time the script
// is coming from memory cache.
return getScript(url);
})
.then(() => {
// Verify that the transferSize in case of memory cache load is 0.
assert_zeroed_(
window.performance.getEntriesByType('resource')
.filter(e => e.name.includes(url))[0], ['transferSize']);
});
}, "The transferSize of resource timing entries should be 0 when resource \
is loaded from memory cache.");
</script>
</body>
</html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name=timeout content=long>
<title>Resource Timing embed navigate - back button navigation</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/nested-contexts.js"></script>
<script>
open_test_window("resources/embed-navigate-back.html",
"Test that embed navigations are not observable by the parent, even " +
"after history navigations by the parent");
open_test_window("resources/embed-navigate-back.html?crossorigin",
"Test that crossorigin embed navigations are not observable by the " +
"parent, even after history navigations by the parent");
open_test_window("resources/embed-navigate-back.html?cross-site",
"Test that cross-site embed navigations are not observable by the " +
"parent, even after history navigations by the parent");
open_test_window("resources/embed-navigate.html",
"Test that embed navigations are not observable by the parent");
open_test_window("resources/embed-navigate.html?crossorigin",
"Test that crossorigin embed navigations are not observable by the parent");
open_test_window("resources/embed-navigate.html?cross-site",
"Test that cross-site embed navigations are not observable by the parent");
open_test_window("resources/embed-refresh.html",
"Test that embed refreshes are not observable by the parent");
open_test_window("resources/embed-refresh.html?crossorigin",
"Test that crossorigin embed refreshes are not observable by the parent");
open_test_window("resources/embed-refresh.html?cross-site",
"Test that cross-site embed refreshes are not observable by the parent");
</script>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name=timeout content=long>
<title>Resource Timing embed navigate - back button navigation</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/nested-contexts.js"></script>
<script>
open_test_window("resources/iframe-navigate-back.html",
"Test that iframe navigations are not observable by the parent, even after history navigations by the parent");
open_test_window("resources/iframe-navigate-back.html?crossorigin",
"Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent");
open_test_window("resources/iframe-navigate-back.html?cross-site",
"Test that cross-site iframe navigations are not observable by the parent, even after history navigations by the parent");
open_test_window("resources/iframe-navigate.html",
"Test that iframe navigations are not observable by the parent");
open_test_window("resources/iframe-navigate.html?crossorigin",
"Test that crossorigin iframe navigations are not observable by the parent");
open_test_window("resources/iframe-navigate.html?cross-site",
"Test that cross-site iframe navigations are not observable by the parent");
open_test_window("resources/iframe-refresh.html",
"Test that iframe refreshes are not observable by the parent");
open_test_window("resources/iframe-refresh.html?crossorigin",
"Test that crossorigin iframe refreshes are not observable by the parent");
open_test_window("resources/iframe-refresh.html?cross-site",
"Test that cross-site iframe refreshes are not observable by the parent");
</script>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name=timeout content=long>
<title>Resource Timing embed navigate - back button navigation</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/nested-contexts.js"></script>
<script>
open_test_window("resources/object-navigate-back.html",
"Test that object navigations are not observable by the parent, even " +
"after history navigations by the parent");
open_test_window("resources/object-navigate-back.html?crossorigin",
"Test that crossorigin object navigations are not observable by the " +
"parent, even after history navigations by the parent");
open_test_window("resources/object-navigate-back.html?cross-site",
"Test that cross-site object navigations are not observable by the " +
"parent, even after history navigations by the parent");
open_test_window("resources/object-navigate.html",
"Test that object navigations are not observable by the parent");
open_test_window("resources/object-navigate.html?crossorigin",
"Test that crossorigin object navigations are not observable by the " +
"parent");
open_test_window("resources/object-navigate.html?cross-site",
"Test that cross-site object navigations are not observable by the " +
"parent");
open_test_window("resources/object-refresh.html",
"Test that object refreshes are not observable by the parent");
open_test_window("resources/object-refresh.html?crossorigin",
"Test that crossorigin object refreshes are not observable by the parent");
open_test_window("resources/object-refresh.html?cross-site",
"Test that cross-site object refreshes are not observable by the parent");
</script>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing - Check that nextHopProtocol is TAO protected</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/custom-cors-response.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/tao-response.js"></script>
</head>
<body>
<script>
const {HTTPS_REMOTE_ORIGIN} = get_host_info();
const tao_protected_next_hop_test = (loader, item) => {
attribute_test(
loader, custom_cors_response({}, HTTPS_REMOTE_ORIGIN),
entry => assert_equals(entry.nextHopProtocol, "",
"nextHopProtocol should be the empty string."),
`Fetch TAO-less ${item} from remote origin. Make sure nextHopProtocol ` +
"is the empty string."
);
attribute_test(
loader, remote_tao_response('*'),
entry => assert_not_equals(entry.nextHopProtocol, "",
"nextHopProtocol should not be the empty string."),
`Fetch TAO'd ${item} from remote origin. Make sure nextHopProtocol ` +
"is not the empty string."
);
}
tao_protected_next_hop_test(load.font, "font");
tao_protected_next_hop_test(load.iframe, "iframe");
tao_protected_next_hop_test(load.image, "image");
tao_protected_next_hop_test(path => load.object(path, "text/plain"), "object");
tao_protected_next_hop_test(load.script, "script");
tao_protected_next_hop_test(load.stylesheet, "stylesheet");
tao_protected_next_hop_test(load.xhr_sync, "synchronous xhr");
tao_protected_next_hop_test(load.xhr_async, "asynchronous xhr");
</script>
</body>
</html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<meta charset=utf-8>
<title>Make sure that resources fetched by cross origin CSS are not in the timeline.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>
<script>
const host = get_host_info();
const link = document.createElement("LINK");
link.rel = "stylesheet";
link.id = "cross_origin_style";
/*
This stylesheet is fetched from one of:
//www1.webplatform.test:64941/resource-timing/resources/nested.css
//127.0.0.1:64941/resource-timing/resources/nested.css
*/
link.href = "//" + host.REMOTE_HOST + ":{{ports[http][1]}}{{location[path]}}/../resources/nested.css"
document.currentScript.parentNode.insertBefore(link, document.currentScript);
</script>
<script>
const t = async_test("Make sure that resources fetched by cross origin CSS are not in the timeline.");
window.addEventListener("load", function() {
// A timeout is needed as entries are not guaranteed to be in the timeline before onload triggers.
t.step_timeout(function() {
const url = (new URL(document.getElementById("cross_origin_style").href));
const prefix = url.protocol + "//" + url.host;
assert_equals(performance.getEntriesByName(prefix + "/resource-timing/resources/resource_timing_test0.css?id=n1").length, 0, "Import should not be in timeline");
assert_equals(performance.getEntriesByName(prefix + "/fonts/Ahem.ttf?id=n1").length, 0, "Font should not be in timeline");
assert_equals(performance.getEntriesByName(prefix + "/resource-timing/resources/blue.png?id=n1").length, 0, "Image should not be in timeline");
t.done();
}, 200);
});
</script>
<ol>Some content</ol>
</body>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates that object resource emit resource timing entries.</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/entry-invariants.js"></script>
<script src="resources/resource-loaders.js"></script>
</head>
<body>
<script>
const load_image_object = async path => {
return load.object(path, "image/png");
}
const load_null_object = async path => {
return load.object(path, null);
}
attribute_test(
load_null_object, "resources/status-code.py?status=200&type=none",
invariants.assert_tao_pass_no_redirect_http,
"Verify that a 200 null-typed object emits an entry.");
attribute_test(
load_null_object, "resources/status-code.py?status=404&type=none",
invariants.assert_tao_pass_no_redirect_http,
"Verify that a 404 null-typed object emits an entry.");
attribute_test(
load_image_object, "resources/status-code.py?status=404&type=img",
invariants.assert_tao_pass_no_redirect_http,
"Verify that a 404 img-typed object emits an entry.");
</script>

View File

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates resource timing information for a timing allowed cross-origin redirect chain.</title>
<link rel="help" href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src=/common/get-host-info.sub.js></script>
<script src="resources/webperftestharness.js"></script>
<script src="resources/webperftestharnessextension.js"></script>
<script>
setup({explicit_done: true});
test_namespace('getEntriesByName');
function onload_test()
{
const context = new PerformanceContext(performance);
const entries = context.getEntriesByName(document.querySelector('object').data, 'resource');
test_equals(entries.length, 1, 'There should be one entry.');
const entry = entries[0];
test_greater_than(entry.redirectStart, 0, 'redirectStart > 0 in timing allowed cross-origin redirect.');
test_equals(entry.redirectStart, entry.startTime, 'redirectStart == startTime in timing allowed cross-origin redirect.');
test_greater_than(entry.redirectEnd, entry.redirectStart, 'redirectEnd > redirectStart in timing allowed cross-origin redirect.');
test_greater_or_equals(entry.fetchStart, entry.redirectEnd, 'fetchStart >= redirectEnd in timing allowed cross-origin redirect.');
done();
}
</script>
</head>
<body>
<script>
let destUrl = get_host_info().HTTP_REMOTE_ORIGIN + '/resource-timing/resources/multi_redirect.py?';
destUrl += 'page_origin=' + 'http://' + document.location.host;
destUrl += '&cross_origin=' + get_host_info().HTTP_REMOTE_ORIGIN;
destUrl += '&final_resource=' + encodeURIComponent("/resource-timing/resources/status-code.py?status=404&tao_value=*");
destUrl += '&tao_steps=3';
const objElement = document.createElement('object');
objElement.style = 'width: 0px; height: 0px;';
objElement.data = destUrl;
objElement.onerror = onload_test;
document.body.appendChild(objElement);
</script>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<title>This test validates the values in resource timing for cross-origin
redirects.</title>
<link rel="author" title="Noam Rosenthal" href="noam@webkit.org">
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
</head>
<body>
<script>
const {REMOTE_ORIGIN} = get_host_info();
const delay = 2
const not_found_page = encodeURIComponent("/resource-timing/resources/status-code.py?status=404");
const load_null_object = async path => {
return load.object(path, null);
}
const destUrl = `/common/slow-redirect.py?delay=${delay}&location=${REMOTE_ORIGIN}/${not_found_page}`;
const timeBefore = performance.now()
attribute_test(load_null_object, destUrl, entry => {
assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal');
assert_greater_than(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching');
// See https://github.com/w3c/resource-timing/issues/264
assert_less_than(Math.round(entry.startTime - timeBefore), delay * 1000, 'startTime should not expose redirect delays');
}, "Verify that cross-origin object resources don't implicitly expose their redirect timings")
</script>
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing TAO - "null" and opaque origin</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#timing-allow-origin"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<h1>Description</h1>
<p>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.
</p>
<iframe id="frameContext"></iframe>
<script>
const {ORIGIN} = get_host_info();
const url = `${ORIGIN}/resource-timing/resources/TAOResponse.py`;
const frame_content = `data:text/html;utf8,<body>
<script src="${ORIGIN}/resources/testharness.js"></` + `script>
<script src="${ORIGIN}/resource-timing/resources/entry-invariants.js">
</` + `script>
<script>
attribute_test(fetch, "${url}?tao=null",
invariants.assert_tao_pass_no_redirect_http,
"An opaque origin should be authorized to see resource timings when the" +
"TAO header is the string 'null'");
attribute_test(fetch, "${url}?tao=Null",
invariants.assert_tao_failure_resource,
"An opaque origin must not be authorized to see resource timings when " +
"the TAO header is the string 'Null'. (The check for 'null' must be " +
"case-sensitive)");
</` + `script>
</body>`;
frameContext.style = "display:none";
frameContext.src = frame_content;
fetch_tests_from_window(frameContext.contentWindow);
</script>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing Entry For hyperlink audit (ping)</title>
<link rel="help" href="https://w3c.github.io/resource-timing/"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resource-timing/resources/observe-entry.js"></script>
</head>
<body>
<script>
promise_test(async t => {
const link = document.createElement('a');
const delay = 500;
const ping = `/xhr/resources/delay.py?ms=${delay}`;
link.setAttribute('href', 'resources/close.html');
link.setAttribute('target', '_blank');
link.setAttribute('ping', ping);
link.innerText = 'Link';
document.body.appendChild(link);
link.click();
const entry = await observe_entry(ping);
assert_equals(entry.initiatorType, 'ping');
assert_greater_than(entry.duration, delay);
}, "Hyperlink auditing (<a ping>) should have a resource timing entry");
</script>
</body>
</html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Resource Timing: resources fetched through same-origin redirects</title>
<link rel="author" title="Google" href="http://www.google.com/" />
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="resources/resource-loaders.js"></script>
<script src="resources/entry-invariants.js"></script>
<script>
const {HTTPS_NOTSAMESITE_ORIGIN} = get_host_info();
const redirect_url = `/common/redirect.py`;
const url_prefix = `${redirect_url}?location=/resource-timing/resources/`;
const https_url_prefix = `${redirect_url}?location=${HTTPS_NOTSAMESITE_ORIGIN}/resource-timing/resources/`;
attribute_test(
load.stylesheet, url_prefix + "resource_timing_test0.css",
invariants.assert_same_origin_redirected_resource,
"Verify attributes of a redirected stylesheet's PerformanceResourceTiming");
attribute_test(
load.image, url_prefix + "blue.png",
invariants.assert_same_origin_redirected_resource,
"Verify attributes of a redirected image's PerformanceResourceTiming");
attribute_test(
load.iframe, url_prefix + "green.html",
invariants.assert_same_origin_redirected_resource,
"Verify attributes of a redirected iframe's PerformanceResourceTiming");
attribute_test(
load.script, url_prefix + "empty_script.js",
invariants.assert_same_origin_redirected_resource,
"Verify attributes of a redirected script's PerformanceResourceTiming");
attribute_test(
load.xhr_sync, url_prefix + "green.html?id=xhr",
invariants.assert_same_origin_redirected_resource,
"Verify attributes of a redirected synchronous XMLHttpRequest's " +
"PerformanceResourceTiming");
attribute_test(
load.xhr_sync, https_url_prefix + "green.html?id=xhr",
invariants.assert_cross_origin_redirected_resource,
"Verify attributes of a synchronous XMLHttpRequest's " +
"PerformanceResourceTiming where the initial HTTP request is redirected " +
"to a cross-origin HTTPS resource."
);
</script>
</head>
<body>
<h1>Description</h1>
<p>This test validates that, when a fetching resources that encounter
same-origin redirects, attributes of the PerformanceResourceTiming entry
conform to the specification.</p>
</body>
</html>

View File

@ -0,0 +1,222 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>This test validates the render blocking status of resources.</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Start of test cases -->
<script>
// Dynamic style using document.write in head
document.write(`
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-head-dynamic-docWrite'>
`);
document.write(`
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-head-dynamic-docWrite-print'
media=print>
`);
</script>
<link rel=stylesheet href="resources/empty_style.css?stylesheet-head">
<link rel=stylesheet href="resources/empty_style.css?stylesheet-head-media-print"
media=print>
<link rel="alternate stylesheet"
href="resources/empty_style.css?stylesheet-head-alternate">
<link rel=preload as=style href="resources/empty_style.css?link-style-head-preload">
<link rel=preload as=style href="resources/empty_style.css?link-style-preload-used">
<link rel=stylesheet href="resources/importer.css?stylesheet-importer-head">
<link rel=stylesheet id="link-head-remove-attr" blocking="render"
href="resources/empty_style.css?stylesheet-head-blocking-render-remove-attr">
<link rel=modulepreload href="resources/empty_script.js?link-head-modulepreload">
<style>@import url(resources/empty_style.css?stylesheet-inline-imported);</style>
<style media=print>
@import url(resources/empty_style.css?stylesheet-inline-imported-print);
</style>
</head>
<body>
<link rel=stylesheet href="resources/empty_style.css?stylesheet-body">
<link rel=stylesheet href="resources/importer.css?stylesheet-importer-body">
<link rel=stylesheet href="resources/empty_style.css?stylesheet-body-media-print"
media=print>
<link rel=stylesheet blocking="render"
href="resources/empty_style.css?stylesheet-body-blocking-render">
<!-- https://html.spec.whatwg.org/multipage/urls-and-fetching.html#blocking-attributes
mentions that an element is potentially render-blocking if its blocking
tokens set contains "render", or if it is implicitly potentially
render-blocking. By default, an element is not implicitly potentially
render-blocking.
https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
specifies that a link element of type stylesheet is implicitly potentially
render-blocking only if the element was created by its node document's parser. -->
<script>
// Dynamic style using document.write in body
document.write(`
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-body-dynamic-docWrite'>
`);
document.write(`
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-body-dynamic-docWrite-print'
media=print>
`);
// Dynamic style using innerHTML
document.head.innerHTML += `
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-head-dynamic-innerHTML'>
`;
document.head.innerHTML += `
<link rel=stylesheet
href='resources/empty_style.css?stylesheet-head-dynamic-innerHTML-print'
media=print>
`;
document.head.innerHTML += `
<link rel=stylesheet blocking=render
href='resources/empty_style.css?stylesheet-head-blocking-render-dynamic-innerHTML'>
`;
// Dynamic style using DOM API
var link = document.createElement("link");
link.href = "resources/empty_style.css?stylesheet-head-dynamic-dom";
link.rel = "stylesheet";
document.head.appendChild(link);
// Add a dynamic render-blocking style with DOM API
link = document.createElement("link");
link.href = "resources/empty_style.css?stylesheet-head-blocking-render-dynamic-dom";
link.rel = "stylesheet";
link.blocking = "render";
document.head.appendChild(link);
// Dynamic style preload using DOM API
link = document.createElement("link");
link.href = "resources/empty_style.css?link-style-head-preload-dynamic-dom";
link.rel = "preload";
link.as = "style";
document.head.appendChild(link);
// Dynamic module via modulepreload using DOM API
link = document.createElement("link");
link.href = "resources/empty_script.js?link-head-modulepreload-dynamic-dom";
link.rel = "modulepreload";
document.head.appendChild(link);
// Add a style preload with DOM API to be used later
link = document.createElement("link");
link.href = "resources/empty_style.css?link-style-preload-used-dynamic";
link.rel = "preload";
link.as = "style";
document.head.appendChild(link);
// Use the preload
link = document.createElement("link");
link.href = "resources/empty_style.css?link-style-preload-used-dynamic";
link.rel = "stylesheet";
document.head.appendChild(link);
// Dynamic inline CSS
// Add an inline CSS importer
document.write(`
<style>
@import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-docwrite')
</style>
`);
document.write(`
<style media=print>
@import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-docwrite-print')
</style>
`);
// Add a dynamic inline CSS importer using DOM API
let style = document.createElement("style");
style.textContent = "@import url('resources/empty_style.css?stylesheet-inline-imported-dynamic-dom')";
document.head.appendChild(style);
// Add a dynamic render-blocking inline CSS importer
style = document.createElement("style");
style.textContent = "@import url('resources/empty_style.css?stylesheet-inline-imported-blocking-render-dynamic-dom')";
style.blocking = "render";
document.head.appendChild(style);
// Dynamic CSS importer
document.write(`
<link rel=stylesheet href='resources/importer_dynamic.css'>
`);
document.write(`
<link rel=stylesheet href='resources/importer_print.css' media=print>
`);
// Removing blocking render attribute after request is made
const sheet = document.getElementById("link-head-remove-attr");
sheet.blocking = "";
</script>
<link rel=stylesheet href="resources/empty_style.css?link-style-preload-used">
<script>
const wait_for_onload = () => {
return new Promise(resolve => {
window.addEventListener("load", resolve);
})};
promise_test(
async () => {
const expectedRenderBlockingStatus = {
'stylesheet-head-dynamic-docWrite': 'blocking',
'stylesheet-head-dynamic-docWrite-print': 'non-blocking',
'stylesheet-head': 'blocking',
'stylesheet-head-media-print' : 'non-blocking',
'stylesheet-head-alternate' : 'non-blocking',
'link-style-head-preload' : 'non-blocking',
'stylesheet-importer-head' : 'blocking',
'stylesheet-head-blocking-render-remove-attr' : 'blocking',
'link-head-modulepreload' : 'non-blocking',
'stylesheet-inline-imported' : 'blocking',
'stylesheet-inline-imported-print' : 'non-blocking',
'stylesheet-body': 'non-blocking',
'stylesheet-importer-body' : 'non-blocking',
'stylesheet-body-media-print' : 'non-blocking',
'stylesheet-body-blocking-render' : 'non-blocking',
'stylesheet-body-dynamic-docWrite' : 'non-blocking',
'stylesheet-body-dynamic-docWrite-print': 'non-blocking',
'stylesheet-head-dynamic-innerHTML' : 'non-blocking',
'stylesheet-head-dynamic-innerHTML-print' : 'non-blocking',
'stylesheet-head-blocking-render-dynamic-innerHTML' : 'blocking',
'stylesheet-head-dynamic-dom' : 'non-blocking',
'stylesheet-head-blocking-render-dynamic-dom' : 'blocking',
'link-style-head-preload-dynamic-dom' : 'non-blocking',
'link-head-modulepreload-dynamic-dom' : 'non-blocking',
'link-style-preload-used' : 'non-blocking',
'link-style-preload-used-dynamic' : 'non-blocking',
'stylesheet-inline-imported-dynamic-docwrite': 'blocking',
'stylesheet-inline-imported-dynamic-docwrite-print' : 'non-blocking',
'stylesheet-inline-imported-dynamic-dom' : 'non-blocking',
'stylesheet-inline-imported-blocking-render-dynamic-dom' : 'blocking',
'stylesheet-imported' : 'blocking',
'stylesheet-imported-print' : 'non-blocking',
'stylesheet-imported-dynamic' : 'non-blocking'
};
await wait_for_onload();
const entry_list = performance.getEntriesByType("resource");
for (entry of entry_list) {
if (entry.name.includes("empty_style.css") ||
entry.name.includes("importer.css") ||
entry.name.includes("empty_script.js")) {
key = entry.name.split("?").pop();
expectedStatus = expectedRenderBlockingStatus[key];
assert_equals(entry.renderBlockingStatus, expectedStatus,
`render blocking status for ${entry.name} should be ${expectedStatus}`);
}
}
}, "Validate render blocking status of link resources in PerformanceResourceTiming");
</script>

View File

@ -0,0 +1,196 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8" />
<title>This test validates the render blocking status of resources.</title>
<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<!-- Start of test cases -->
<script src="resources/empty_script.js?script-head"></script>
<script type="module" src="resources/empty_script.js?script-head-module"></script>
<script async type=module
src="resources/empty_script.js?script-head-async-module">
</script>
<script async src="resources/empty_script.js?script-head-async"></script>
<script defer src="resources/empty_script.js?script-head-defer"></script>
<script blocking=render
src="resources/empty_script.js?script-head-blocking-render">
</script>
<script async blocking=render
src="resources/empty_script.js?script-head-async-blocking-render">
</script>
<script type=module blocking=render
src="resources/empty_script.js?script-head-module-blocking-render">
</script>
<script async type=module blocking=render
src="resources/empty_script.js?script-head-async-module-blocking-render">
</script>
<script defer blocking=render
src="resources/empty_script.js?script-head-defer-blocking-render">
</script>
<script id="script-head-remove-attr" blocking=render
src="resources/empty_script.js?script-head-blocking-render-remove-attr">
</script>
<script>
document.write(`
<script defer
src="resources/empty_script.js?script-head-defer-dynamic-docwrite">
<\/script>`);
</script>
</head>
<body>
<script src="resources/empty_script.js?script-body"></script>
<script type="module" src="resources/empty_script.js?script-body-module"></script>
<script async type=module
src="resources/empty_script.js?script-body-async-module">
</script>
<script async src="resources/empty_script.js?script-body-async"></script>
<script defer src="resources/empty_script.js?script-body-defer"></script>
<script>
const script = document.createElement("script");
script.src = "resources/empty_script.js?script-head-dynamic-dom";
document.head.appendChild(script);
// Dynamic explicitly async script
const async_script = document.createElement("script");
async_script.src = "resources/empty_script.js?script-head-async-dynamic-dom";
async_script.async = true;
document.head.appendChild(async_script);
// Dynamic non-async script
// https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model
// mentions that a script element has to be parser-inserted to be
// implicitly potentially render-blocking
const non_async_script = document.createElement("script");
non_async_script.src = "resources/empty_script.js?script-head-non-async-dynamic-dom";
non_async_script.async = false;
document.head.appendChild(non_async_script);
// Dynamic defer script
const defer_script = document.createElement("script");
defer_script.src = "resources/empty_script.js?script-head-defer-dynamic-dom";
defer_script.defer = true;
document.head.appendChild(defer_script);
// Dynamic explicitly render-blocking script
const blocking_script = document.createElement("script");
blocking_script.src = "resources/empty_script.js?script-head-blocking-render-dynamic-dom";
blocking_script.blocking = "render";
document.head.appendChild(blocking_script);
// Dynamic explicitly render-blocking module script
const blocking_module_script = document.createElement("script");
blocking_module_script.src = "resources/empty_script.js?script-head-module-blocking-render-dynamic-dom";
blocking_module_script.type = "module";
blocking_module_script.blocking = "render";
document.head.appendChild(blocking_module_script);
// Dynamic async module script
const async_module_script = document.createElement("script");
async_module_script.src = "resources/empty_script.js?script-head-async-module-dynamic-dom";
async_module_script.type = "module";
async_module_script.async = true;
document.head.appendChild(async_module_script);
// Dynamic async render-blocking module script
const async_blocking_module_script = document.createElement("script");
async_blocking_module_script.src = "resources/empty_script.js?script-head-async-module-blocking-render-dynamic-dom";
async_blocking_module_script.type = "module";
async_blocking_module_script.async = true;
async_blocking_module_script.blocking = "render"
document.head.appendChild(async_blocking_module_script);
// Add a module that imports more modules
const importer_script = document.createElement("script");
importer_script.src = "resources/fake_responses.py?url=importer.js";
importer_script.type = "module";
document.head.appendChild(importer_script);
// Add an async module that imports more modules
const importer_async_script = document.createElement("script");
importer_async_script.src = "resources/fake_responses.py?url=importer_async.js";
importer_async_script.type = "module";
importer_async_script.async = true;
document.head.appendChild(importer_async_script);
// Removing blocking render attribute after request is made
const script_element = document.getElementById("script-head-remove-attr");
script_element.blocking = "";
</script>
<script>
const wait_for_onload = () => {
return new Promise(resolve => {
window.addEventListener("load", resolve);
})};
promise_test(
async () => {
const expectedRenderBlockingStatus = {
'script-head': 'blocking',
'script-head-module' : 'non-blocking',
'script-head-async-module' : 'non-blocking',
'script-head-async' : 'non-blocking',
'script-head-defer' : 'non-blocking',
'script-head-blocking-render' : 'blocking',
'script-head-async-blocking-render' : 'blocking',
'script-head-module-blocking-render' : 'blocking',
'script-head-async-module-blocking-render' : 'blocking',
'script-head-defer-blocking-render' : 'blocking',
'script-head-blocking-render-remove-attr' : 'blocking',
'script-head-defer-dynamic-docwrite' : 'non-blocking',
'script-body' : 'non-blocking',
'script-body-module' : 'non-blocking',
'script-body-async-module' : 'non-blocking',
'script-body-async' : 'non-blocking',
'script-body-defer' : 'non-blocking',
'script-head-dynamic-dom': 'non-blocking',
'script-head-async-dynamic-dom' : 'non-blocking',
'script-head-non-async-dynamic-dom': 'non-blocking',
'script-head-defer-dynamic-dom' : 'non-blocking',
'script-head-blocking-render-dynamic-dom' : 'blocking',
'script-head-module-blocking-render-dynamic-dom' : 'blocking',
'script-head-async-module-dynamic-dom' : 'non-blocking',
'script-head-async-module-blocking-render-dynamic-dom' : 'blocking',
'script-head-import-defer' : 'non-blocking',
'script-head-import-defer-dynamic' : 'non-blocking',
'script-head-import-async' : 'non-blocking',
'script-head-import-async-dynamic' : 'non-blocking',
'script-importer' : 'non-blocking',
'script-importer-async' : 'non-blocking'
};
await wait_for_onload();
const entry_list = performance.getEntriesByType("resource");
for (entry of entry_list) {
if (entry.name.includes("empty_script.js")) {
key = entry.name.split("?").pop();
expectedStatus = expectedRenderBlockingStatus[key];
assert_equals(entry.renderBlockingStatus, expectedStatus,
`render blocking status for ${entry.name} should be ${expectedStatus}`);
}
else if (entry.name.includes("importer.js")){
key = 'script-importer';
expectedStatus = expectedRenderBlockingStatus[key];
assert_equals(entry.renderBlockingStatus, expectedStatus,
`render blocking status for ${entry.name} should be ${expectedStatus}`);
}
else if (entry.name.includes("importer_async.js")){
key = 'script-importer-async';
expectedStatus = expectedRenderBlockingStatus[key];
assert_equals(entry.renderBlockingStatus, expectedStatus,
`render blocking status for ${entry.name} should be ${expectedStatus}`);
}
}
}, "Validate render blocking status of script resources in PerformanceResourceTiming");
</script>

Some files were not shown because too many files have changed in this diff Show More