import { primordials } from "ext:core/mod.js";
import { op_now, op_time_origin } from "ext:core/ops";
const {
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
queueMicrotask,
ReflectHas,
Symbol,
SymbolFor,
TypeError,
TypedArrayPrototypeGetBuffer,
Uint8Array,
Uint32Array,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { structuredClone } from "./02_structured_clone.js";
import { createFilteredInspectProxy } from "./01_console.js";
import { EventTarget } from "./02_event.js";
import { DOMException } from "./01_dom_exception.js";
const illegalConstructorKey = Symbol("illegalConstructorKey");
let performanceEntries = [];
let timeOrigin;
const performanceObservers = [];
const hrU8 = new Uint8Array(8);
const hr = new Uint32Array(TypedArrayPrototypeGetBuffer(hrU8));
function setTimeOrigin() {
op_time_origin(hrU8);
timeOrigin = hr[0] * 1000 + hr[1] / 1e6;
}
function now() {
op_now(hrU8);
return hr[0] * 1000 + hr[1] / 1e6;
}
webidl.converters["PerformanceMarkOptions"] = webidl
.createDictionaryConverter(
"PerformanceMarkOptions",
[
{
key: "detail",
converter: webidl.converters.any,
},
{
key: "startTime",
converter: webidl.converters.DOMHighResTimeStamp,
},
],
);
webidl.converters["DOMString or DOMHighResTimeStamp"] = (
V,
prefix,
context,
opts,
) => {
if (webidl.type(V) === "Number" && V !== null) {
return webidl.converters.DOMHighResTimeStamp(V, prefix, context, opts);
}
return webidl.converters.DOMString(V, prefix, context, opts);
};
webidl.converters["PerformanceMeasureOptions"] = webidl
.createDictionaryConverter(
"PerformanceMeasureOptions",
[
{
key: "detail",
converter: webidl.converters.any,
},
{
key: "start",
converter: webidl.converters["DOMString or DOMHighResTimeStamp"],
},
{
key: "duration",
converter: webidl.converters.DOMHighResTimeStamp,
},
{
key: "end",
converter: webidl.converters["DOMString or DOMHighResTimeStamp"],
},
],
);
webidl.converters["DOMString or PerformanceMeasureOptions"] = (
V,
prefix,
context,
opts,
) => {
if (webidl.type(V) === "Object" && V !== null) {
return webidl.converters["PerformanceMeasureOptions"](
V,
prefix,
context,
opts,
);
}
return webidl.converters.DOMString(V, prefix, context, opts);
};
function findMostRecent(
name,
type,
) {
for (let i = performanceEntries.length - 1; i >= 0; --i) {
const entry = performanceEntries[i];
if (entry.name === name && entry.entryType === type) {
return entry;
}
}
}
function convertMarkToTimestamp(mark) {
if (typeof mark === "string") {
const entry = findMostRecent(mark, "mark");
if (!entry) {
throw new DOMException(
`Cannot find mark: "${mark}"`,
"SyntaxError",
);
}
return entry.startTime;
}
if (mark < 0) {
throw new TypeError(`Mark cannot be negative: received ${mark}`);
}
return mark;
}
function filterByNameType(
name,
type,
) {
return ArrayPrototypeFilter(
performanceEntries,
(entry) =>
(name ? entry.name === name : true) &&
(type ? entry.entryType === type : true),
);
}
const _name = Symbol("[[name]]");
const _entryType = Symbol("[[entryType]]");
const _startTime = Symbol("[[startTime]]");
const _duration = Symbol("[[duration]]");
class PerformanceEntry {
[_name] = "";
[_entryType] = "";
[_startTime] = 0;
[_duration] = 0;
get name() {
webidl.assertBranded(this, PerformanceEntryPrototype);
return this[_name];
}
get entryType() {
webidl.assertBranded(this, PerformanceEntryPrototype);
return this[_entryType];
}
get startTime() {
webidl.assertBranded(this, PerformanceEntryPrototype);
return this[_startTime];
}
get duration() {
webidl.assertBranded(this, PerformanceEntryPrototype);
return this[_duration];
}
constructor(
name = null,
entryType = null,
startTime = null,
duration = null,
key = undefined,
) {
if (key !== illegalConstructorKey) {
webidl.illegalConstructor();
}
this[webidl.brand] = webidl.brand;
this[_name] = name;
this[_entryType] = entryType;
this[_startTime] = startTime;
this[_duration] = duration;
}
toJSON() {
webidl.assertBranded(this, PerformanceEntryPrototype);
return {
name: this[_name],
entryType: this[_entryType],
startTime: this[_startTime],
duration: this[_duration],
};
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(
PerformanceEntryPrototype,
this,
),
keys: [
"name",
"entryType",
"startTime",
"duration",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(PerformanceEntry);
const PerformanceEntryPrototype = PerformanceEntry.prototype;
const _detail = Symbol("[[detail]]");
class PerformanceMark extends PerformanceEntry {
[_detail] = null;
get detail() {
webidl.assertBranded(this, PerformanceMarkPrototype);
return this[_detail];
}
get entryType() {
webidl.assertBranded(this, PerformanceMarkPrototype);
return "mark";
}
constructor(
name,
options = { __proto__: null },
) {
const prefix = "Failed to construct 'PerformanceMark'";
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters.DOMString(name, prefix, "Argument 1");
options = webidl.converters.PerformanceMarkOptions(
options,
prefix,
"Argument 2",
);
const { detail = null, startTime = now() } = options;
super(name, "mark", startTime, 0, illegalConstructorKey);
this[webidl.brand] = webidl.brand;
if (startTime < 0) {
throw new TypeError(
`Cannot construct PerformanceMark: startTime cannot be negative, received ${startTime}`,
);
}
this[_detail] = structuredClone(detail);
}
toJSON() {
webidl.assertBranded(this, PerformanceMarkPrototype);
return {
name: this.name,
entryType: this.entryType,
startTime: this.startTime,
duration: this.duration,
detail: this.detail,
};
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(PerformanceMarkPrototype, this),
keys: [
"name",
"entryType",
"startTime",
"duration",
"detail",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(PerformanceMark);
const PerformanceMarkPrototype = PerformanceMark.prototype;
class PerformanceMeasure extends PerformanceEntry {
[_detail] = null;
get detail() {
webidl.assertBranded(this, PerformanceMeasurePrototype);
return this[_detail];
}
get entryType() {
webidl.assertBranded(this, PerformanceMeasurePrototype);
return "measure";
}
constructor(
name = null,
startTime = null,
duration = null,
detail = null,
key = undefined,
) {
if (key !== illegalConstructorKey) {
webidl.illegalConstructor();
}
super(name, "measure", startTime, duration, key);
this[webidl.brand] = webidl.brand;
this[_detail] = structuredClone(detail);
}
toJSON() {
webidl.assertBranded(this, PerformanceMeasurePrototype);
return {
name: this.name,
entryType: this.entryType,
startTime: this.startTime,
duration: this.duration,
detail: this.detail,
};
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(
PerformanceMeasurePrototype,
this,
),
keys: [
"name",
"entryType",
"startTime",
"duration",
"detail",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(PerformanceMeasure);
const PerformanceMeasurePrototype = PerformanceMeasure.prototype;
function queuePerformanceEntry(entry) {
for (let i = 0; i < performanceObservers.length; i++) {
const observer = performanceObservers[i];
if (ArrayPrototypeIncludes(observer[_entryTypes], entry.entryType)) {
ArrayPrototypePush(observer[_buffer], entry);
if (!observer[_scheduled]) {
observer[_scheduled] = true;
queueMicrotask(() => {
observer[_scheduled] = false;
const entries = observer[_buffer];
observer[_buffer] = [];
if (entries.length > 0) {
const entryList = new PerformanceObserverEntryList(
entries,
illegalConstructorKey,
);
observer[_callback](entryList, observer);
}
});
}
}
}
}
const _entries = Symbol("[[entries]]");
class PerformanceObserverEntryList {
[_entries] = [];
constructor(entries, key = undefined) {
if (key !== illegalConstructorKey) {
webidl.illegalConstructor();
}
this[webidl.brand] = webidl.brand;
this[_entries] = entries;
}
getEntries() {
webidl.assertBranded(this, PerformanceObserverEntryListPrototype);
return ArrayPrototypeSlice(this[_entries]);
}
getEntriesByType(type) {
webidl.assertBranded(this, PerformanceObserverEntryListPrototype);
const prefix =
"Failed to execute 'getEntriesByType' on 'PerformanceObserverEntryList'";
webidl.requiredArguments(arguments.length, 1, prefix);
type = webidl.converters.DOMString(type, prefix, "Argument 1");
return ArrayPrototypeFilter(
this[_entries],
(entry) => entry.entryType === type,
);
}
getEntriesByName(name, type = undefined) {
webidl.assertBranded(this, PerformanceObserverEntryListPrototype);
const prefix =
"Failed to execute 'getEntriesByName' on 'PerformanceObserverEntryList'";
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters.DOMString(name, prefix, "Argument 1");
if (type !== undefined) {
type = webidl.converters.DOMString(type, prefix, "Argument 2");
}
return ArrayPrototypeFilter(
this[_entries],
(entry) =>
entry.name === name && (type === undefined || entry.entryType === type),
);
}
}
webidl.configureInterface(PerformanceObserverEntryList);
const PerformanceObserverEntryListPrototype =
PerformanceObserverEntryList.prototype;
const _callback = Symbol("[[callback]]");
const _entryTypes = Symbol("[[entryTypes]]");
const _buffer = Symbol("[[buffer]]");
const _scheduled = Symbol("[[scheduled]]");
class PerformanceObserver {
static get supportedEntryTypes() {
return ["mark", "measure"];
}
[_callback] = null;
[_entryTypes] = [];
[_buffer] = [];
[_scheduled] = false;
constructor(callback) {
const prefix = "Failed to construct 'PerformanceObserver'";
webidl.requiredArguments(arguments.length, 1, prefix);
if (typeof callback !== "function") {
throw new TypeError(
`${prefix}: The callback provided as parameter 1 is not a function.`,
);
}
this[webidl.brand] = webidl.brand;
this[_callback] = callback;
}
observe(options = { __proto__: null }) {
webidl.assertBranded(this, PerformanceObserverPrototype);
const prefix = "Failed to execute 'observe' on 'PerformanceObserver'";
if (options === undefined || options === null) {
throw new TypeError(
`${prefix}: 1 argument required, but only 0 present.`,
);
}
const { entryTypes, type } = options;
if (entryTypes !== undefined && type !== undefined) {
throw new TypeError(
`${prefix}: Cannot specify both 'entryTypes' and 'type'.`,
);
}
if (entryTypes === undefined && type === undefined) {
throw new TypeError(
`${prefix}: Either 'entryTypes' or 'type' must be specified.`,
);
}
let types;
if (entryTypes !== undefined) {
if (!ArrayIsArray(entryTypes)) {
throw new TypeError(`${prefix}: 'entryTypes' must be an array.`);
}
types = ArrayPrototypeFilter(
entryTypes,
(t) =>
ArrayPrototypeIncludes(PerformanceObserver.supportedEntryTypes, t),
);
if (types.length === 0) {
return;
}
} else {
if (
!ArrayPrototypeIncludes(PerformanceObserver.supportedEntryTypes, type)
) {
return;
}
types = [type];
}
this[_entryTypes] = types;
this[_buffer] = [];
if (!ArrayPrototypeIncludes(performanceObservers, this)) {
ArrayPrototypePush(performanceObservers, this);
}
}
disconnect() {
webidl.assertBranded(this, PerformanceObserverPrototype);
const index = ArrayPrototypeIndexOf(performanceObservers, this);
if (index !== -1) {
ArrayPrototypeSplice(performanceObservers, index, 1);
}
this[_entryTypes] = [];
this[_buffer] = [];
}
takeRecords() {
webidl.assertBranded(this, PerformanceObserverPrototype);
const records = this[_buffer];
this[_buffer] = [];
return records;
}
}
webidl.configureInterface(PerformanceObserver);
const PerformanceObserverPrototype = PerformanceObserver.prototype;
class Performance extends EventTarget {
constructor(key = null) {
if (key != illegalConstructorKey) {
webidl.illegalConstructor();
}
super();
this[webidl.brand] = webidl.brand;
}
get timeOrigin() {
webidl.assertBranded(this, PerformancePrototype);
return timeOrigin;
}
clearMarks(markName = undefined) {
webidl.assertBranded(this, PerformancePrototype);
if (markName !== undefined) {
markName = webidl.converters.DOMString(
markName,
"Failed to execute 'clearMarks' on 'Performance'",
"Argument 1",
);
performanceEntries = ArrayPrototypeFilter(
performanceEntries,
(entry) => !(entry.name === markName && entry.entryType === "mark"),
);
} else {
performanceEntries = ArrayPrototypeFilter(
performanceEntries,
(entry) => entry.entryType !== "mark",
);
}
}
clearMeasures(measureName = undefined) {
webidl.assertBranded(this, PerformancePrototype);
if (measureName !== undefined) {
measureName = webidl.converters.DOMString(
measureName,
"Failed to execute 'clearMeasures' on 'Performance'",
"Argument 1",
);
performanceEntries = ArrayPrototypeFilter(
performanceEntries,
(entry) =>
!(entry.name === measureName && entry.entryType === "measure"),
);
} else {
performanceEntries = ArrayPrototypeFilter(
performanceEntries,
(entry) => entry.entryType !== "measure",
);
}
}
clearResourceTimings() {
webidl.assertBranded(this, PerformancePrototype);
performanceEntries = ArrayPrototypeFilter(
performanceEntries,
(entry) => entry.entryType !== "resource",
);
}
setResourceTimingBufferSize(_maxSize) {
webidl.assertBranded(this, PerformancePrototype);
webidl.requiredArguments(
arguments.length,
1,
"Failed to execute 'setResourceTimingBufferSize' on 'Performance'",
);
}
getEntries() {
webidl.assertBranded(this, PerformancePrototype);
return filterByNameType();
}
getEntriesByName(
name,
type = undefined,
) {
webidl.assertBranded(this, PerformancePrototype);
const prefix = "Failed to execute 'getEntriesByName' on 'Performance'";
webidl.requiredArguments(arguments.length, 1, prefix);
name = webidl.converters.DOMString(name, prefix, "Argument 1");
if (type !== undefined) {
type = webidl.converters.DOMString(type, prefix, "Argument 2");
}
return filterByNameType(name, type);
}
getEntriesByType(type) {
webidl.assertBranded(this, PerformancePrototype);
const prefix = "Failed to execute 'getEntriesByName' on 'Performance'";
webidl.requiredArguments(arguments.length, 1, prefix);
type = webidl.converters.DOMString(type, prefix, "Argument 1");
return filterByNameType(undefined, type);
}
mark(
markName,
markOptions = { __proto__: null },
) {
webidl.assertBranded(this, PerformancePrototype);
const prefix = "Failed to execute 'mark' on 'Performance'";
webidl.requiredArguments(arguments.length, 1, prefix);
markName = webidl.converters.DOMString(markName, prefix, "Argument 1");
markOptions = webidl.converters.PerformanceMarkOptions(
markOptions,
prefix,
"Argument 2",
);
const entry = new PerformanceMark(markName, markOptions);
ArrayPrototypePush(performanceEntries, entry);
queuePerformanceEntry(entry);
return entry;
}
measure(
measureName,
startOrMeasureOptions = { __proto__: null },
endMark = undefined,
) {
webidl.assertBranded(this, PerformancePrototype);
const prefix = "Failed to execute 'measure' on 'Performance'";
webidl.requiredArguments(arguments.length, 1, prefix);
measureName = webidl.converters.DOMString(
measureName,
prefix,
"Argument 1",
);
startOrMeasureOptions = webidl.converters
["DOMString or PerformanceMeasureOptions"](
startOrMeasureOptions,
prefix,
"Argument 2",
);
if (endMark !== undefined) {
endMark = webidl.converters.DOMString(endMark, prefix, "Argument 3");
}
if (
startOrMeasureOptions && typeof startOrMeasureOptions === "object" &&
ObjectKeys(startOrMeasureOptions).length > 0
) {
if (endMark) {
throw new TypeError('Options cannot be passed with "endMark"');
}
if (
ReflectHas(startOrMeasureOptions, "start") &&
ReflectHas(startOrMeasureOptions, "duration") &&
ReflectHas(startOrMeasureOptions, "end")
) {
throw new TypeError(
'Cannot specify "start", "end", and "duration" together in options',
);
}
}
let endTime;
if (endMark) {
endTime = convertMarkToTimestamp(endMark);
} else if (
typeof startOrMeasureOptions === "object" &&
ReflectHas(startOrMeasureOptions, "end")
) {
endTime = convertMarkToTimestamp(startOrMeasureOptions.end);
} else if (
typeof startOrMeasureOptions === "object" &&
ReflectHas(startOrMeasureOptions, "start") &&
ReflectHas(startOrMeasureOptions, "duration")
) {
const start = convertMarkToTimestamp(startOrMeasureOptions.start);
const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
endTime = start + duration;
} else {
endTime = now();
}
let startTime;
if (
typeof startOrMeasureOptions === "object" &&
ReflectHas(startOrMeasureOptions, "start")
) {
startTime = convertMarkToTimestamp(startOrMeasureOptions.start);
} else if (
typeof startOrMeasureOptions === "object" &&
ReflectHas(startOrMeasureOptions, "end") &&
ReflectHas(startOrMeasureOptions, "duration")
) {
const end = convertMarkToTimestamp(startOrMeasureOptions.end);
const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
startTime = end - duration;
} else if (typeof startOrMeasureOptions === "string") {
startTime = convertMarkToTimestamp(startOrMeasureOptions);
} else {
startTime = 0;
}
const entry = new PerformanceMeasure(
measureName,
startTime,
endTime - startTime,
typeof startOrMeasureOptions === "object"
? startOrMeasureOptions.detail ?? null
: null,
illegalConstructorKey,
);
ArrayPrototypePush(performanceEntries, entry);
queuePerformanceEntry(entry);
return entry;
}
now() {
webidl.assertBranded(this, PerformancePrototype);
return now();
}
toJSON() {
webidl.assertBranded(this, PerformancePrototype);
return {
timeOrigin: this.timeOrigin,
};
}
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(PerformancePrototype, this),
keys: ["timeOrigin"],
}),
inspectOptions,
);
}
}
webidl.configureInterface(Performance);
const PerformancePrototype = Performance.prototype;
webidl.converters["Performance"] = webidl.createInterfaceConverter(
"Performance",
PerformancePrototype,
);
const performance = new Performance(illegalConstructorKey);
export {
Performance,
performance,
PerformanceEntry,
PerformanceMark,
PerformanceMeasure,
PerformanceObserver,
PerformanceObserverEntryList,
setTimeOrigin,
};