Skip to content

Commit 6dd5523

Browse files
committed
feat(performance): structured-clone mark/measure detail per spec
detail is cloned once at entry creation, so entries hold snapshots and an uncloneable detail throws the DataCloneError-named error. The clone goes through structuredClone captured at builtin init, with an identity fallback that keeps performance.js portable to a runtime shipping the Performance API before structuredClone. Removes the by-reference deviation from the docs and bumps the shared tests to the suite that asserts both contracts.
1 parent bddaacb commit 6dd5523

3 files changed

Lines changed: 19 additions & 13 deletions

File tree

NativeScript/runtime/js/performance.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
// against an equivalent bag.
99
//
1010
// Deliberate deviations from the specs:
11-
// - mark/measure `detail` is held by reference (this runtime has no
12-
// structuredClone), so entries retain whatever the caller passed until
13-
// clearMarks()/clearMeasures(); the user-timing buffers are unbounded.
1411
// - Observer callbacks are delivered from a microtask rather than a queued
1512
// task. Delivery is still asynchronous relative to mark()/measure(), but it
1613
// precedes timer callbacks scheduled in the same turn.
@@ -42,6 +39,18 @@ var g = globalThis;
4239
const EventTarget = g.EventTarget;
4340
const enqueueMicrotask = g.queueMicrotask;
4441
const reportException = g.reportError;
42+
// mark/measure `detail` is structured-cloned per spec, so an entry holds a
43+
// snapshot and an uncloneable detail throws DataCloneError. The identity
44+
// fallback keeps this file portable to a runtime that ships the Performance
45+
// API before structuredClone — there, detail degrades to by-reference; the
46+
// user-timing buffers are unbounded either way, so entries retain their
47+
// detail until clearMarks()/clearMeasures().
48+
const cloneDetail =
49+
typeof g.structuredClone === "function"
50+
? g.structuredClone
51+
: function (value) {
52+
return value;
53+
};
4554

4655
// Construction token: interfaces whose constructors the spec marks as not
4756
// user-invocable accept instances only from factories inside this module.
@@ -135,7 +144,7 @@ class PerformanceMark extends PerformanceEntry {
135144
}
136145
}
137146
if (markOptions.detail !== undefined && markOptions.detail !== null) {
138-
detail = markOptions.detail;
147+
detail = cloneDetail(markOptions.detail);
139148
}
140149
}
141150
super(kInternal, name, "mark", startTime === undefined ? now() : startTime, 0);
@@ -516,7 +525,7 @@ class Performance extends EventTarget {
516525
startTime = 0;
517526
}
518527
if (o.detail !== undefined && o.detail !== null) {
519-
detail = o.detail;
528+
detail = cloneDetail(o.detail);
520529
}
521530
} else {
522531
endTime = endMark !== undefined ? convertMarkToTimestamp(endMark) : now();

TestRunner/app/shared

docs/performance.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,10 @@ shares `performance.timeOrigin` as its base.
5656

5757
## Deviations from the specs
5858

59-
- **`detail` is held by reference.** The runtime has no `structuredClone`, so
60-
`mark`/`measure` `detail` values are stored as-is. Mutating the object later
61-
is visible through the entry, and entries retain whatever `detail`
62-
references until `clearMarks()`/`clearMeasures()`.
63-
- **Buffers are unbounded.** Per spec for user timing, but combined with
64-
by-reference `detail` it means a long-lived app marking in a loop should
65-
clear entries periodically.
59+
- **Buffers are unbounded.** Per spec for user timing. `detail` is
60+
structured-cloned at entry creation (per spec — an uncloneable `detail`
61+
throws the `DataCloneError`-named error), so entries hold snapshots, but a
62+
long-lived app marking in a loop should still clear entries periodically.
6663
- **Observer callbacks run from a microtask**, not a queued task: delivery is
6764
asynchronous relative to `mark()`/`measure()` but precedes timer callbacks
6865
scheduled in the same turn. Callback exceptions are routed to

0 commit comments

Comments
 (0)