summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/js-self-profiling
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/js-self-profiling
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/js-self-profiling')
-rw-r--r--testing/web-platform/tests/js-self-profiling/META.yml1
-rw-r--r--testing/web-platform/tests/js-self-profiling/__dir__.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-getter-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/class-setter-names.https.html30
-rw-r--r--testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/external-script.https.html50
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/function-expression-names.https.html24
-rw-r--r--testing/web-platform/tests/js-self-profiling/idlharness.https.html25
-rw-r--r--testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html36
-rw-r--r--testing/web-platform/tests/js-self-profiling/inline-script.html46
-rw-r--r--testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js49
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/__dir__.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/child-frame.html8
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/external-script.js9
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profile-utils.js149
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profiling-script.js26
-rw-r--r--testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers1
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/README.md3
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html37
-rw-r--r--testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers3
-rw-r--r--testing/web-platform/tests/js-self-profiling/time-domain.window.js19
-rw-r--r--testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html16
28 files changed, 717 insertions, 0 deletions
diff --git a/testing/web-platform/tests/js-self-profiling/META.yml b/testing/web-platform/tests/js-self-profiling/META.yml
new file mode 100644
index 0000000000..84e5177d5e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/META.yml
@@ -0,0 +1 @@
+spec: https://wicg.github.io/js-self-profiling/
diff --git a/testing/web-platform/tests/js-self-profiling/__dir__.headers b/testing/web-platform/tests/js-self-profiling/__dir__.headers
new file mode 100644
index 0000000000..93537b44de
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/__dir__.headers
@@ -0,0 +1 @@
+Document-Policy: js-profiling
diff --git a/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html b/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html
new file mode 100644
index 0000000000..46e2ed2748
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-getter-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Getter methods should use `get ${label}` as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(t => ProfileUtils.testFunction(sample => {
+ class SomeClass {
+ get someValue() {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+ instance.someValue;
+ }, {
+ name: 'get someValue',
+ }
+ ), 'class getter names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/class-names.https.html b/testing/web-platform/tests/js-self-profiling/class-names.https.html
new file mode 100644
index 0000000000..9d201b03a1
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Methods should use their label as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(async t => {
+ class SomeClass {
+ method(sample) {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+
+ await ProfileUtils.testFunction(instance.method.bind(instance), {
+ name: 'method',
+ });
+ }, 'class method names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html b/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html
new file mode 100644
index 0000000000..f03a372460
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/class-setter-names.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ // Setter methods should use `set ${label}` as the function/frame name. Source:
+ // https://www.ecma-international.org/ecma-262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+ promise_test(t => ProfileUtils.testFunction(sample => {
+ class SomeClass {
+ set someValue(_) {
+ sample();
+ }
+ }
+ let instance = new SomeClass();
+ instance.someValue = 5;
+ }, {
+ name: 'set someValue',
+ }
+ ), 'class setter names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html b/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html
new file mode 100644
index 0000000000..26f37ec8bd
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/concurrent-profilers.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const profiler_a = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const profiler_b = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const trace_b = await profiler_b.stop();
+ const trace_a = await profiler_a.stop();
+ }, 'concurrent profilers should be supported');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html b/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html
new file mode 100644
index 0000000000..f2b2c8d14e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/cross-origin-script-cors.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script crossorigin src="https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfilingScript.profileBuiltinsInNewTask();
+
+ // Ensure that signal from the external script was gathered.
+ assert_equals(trace.resources.length, 1);
+ assert_equals(trace.resources[0], 'https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js');
+ assert_greater_than(trace.frames.length, 0);
+ assert_greater_than(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+ }, 'cors cross-origin script execution is not observable');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html b/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html
new file mode 100644
index 0000000000..ea6fd3384e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/cross-origin-script-no-cors.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script src="https://{{hosts[alt][]}}:{{ports[https][0]}}/js-self-profiling/resources/profiling-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfilingScript.profileBuiltinsInNewTask();
+
+ // Ensure that no signal from the external script was gathered.
+ assert_equals(trace.resources.length, 0);
+ assert_equals(trace.frames.length, 0);
+ assert_equals(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+ }, 'no-cors cross-origin script execution is not observable');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/external-script.https.html b/testing/web-platform/tests/js-self-profiling/external-script.https.html
new file mode 100644
index 0000000000..9f9438ba6d
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/external-script.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+
+ <script id="external-script" src="resources/external-script.js"></script>
+</head>
+<body>
+ <script>
+ promise_test(async t => {
+ const trace = await ProfileUtils.profileFunction(function trampoline(sample) {
+ externalScriptFunction(sample);
+ });
+
+ const scriptUrl = document.querySelector('#external-script').src;
+ assert_true(ProfileUtils.containsResource(trace, scriptUrl),
+ 'external resource is included');
+
+ const expectedTrampolineFrame = {
+ name: 'trampoline',
+ resourceId: trace.resources.indexOf(
+ location.href,
+ ),
+ };
+ const expectedExternalFrame = {
+ name: 'externalScriptFunction',
+ resourceId: trace.resources.indexOf(scriptUrl),
+ line: EXTERNAL_SCRIPT_FUNCTION_LINE,
+ column: EXTERNAL_SCRIPT_FUNCTION_COLUMN,
+ };
+
+ assert_true(ProfileUtils.containsFrame(trace, expectedTrampolineFrame),
+ 'trampoline function included');
+
+ assert_true(ProfileUtils.containsFrame(trace, expectedExternalFrame),
+ 'external script function included');
+
+ assert_true(ProfileUtils.containsSubstack(trace, [
+ externalScriptFunction,
+ expectedTrampolineFrame,
+ ]),
+ 'stack exists with external script function');
+
+ }, 'external script function details');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html b/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html
new file mode 100644
index 0000000000..fd7fbecc50
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-anonymous-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ const f = function (sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(f, {
+ name: '',
+ });
+ }, 'anonymous function expression names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html b/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html
new file mode 100644
index 0000000000..9cb02cbfc0
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-declaration-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ function namedFunctionDeclaration(sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(namedFunctionDeclaration, {
+ name: 'namedFunctionDeclaration',
+ });
+ }, 'function declaration names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html b/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html
new file mode 100644
index 0000000000..402797402e
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/function-expression-names.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ const f = function namedFunctionExpression(sample) {
+ sample();
+ };
+ await ProfileUtils.testFunction(f, {
+ name: 'namedFunctionExpression',
+ });
+ }, 'function expression names are logged correctly');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/idlharness.https.html b/testing/web-platform/tests/js-self-profiling/idlharness.https.html
new file mode 100644
index 0000000000..3807a6e4fa
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/idlharness.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<body>
+ <script>
+ 'use strict';
+
+ idl_test(
+ ['js-self-profiling'],
+ ['hr-time', 'dom'],
+ async idl_array => {
+ idl_array.add_objects({
+ Profiler: ['profiler'],
+ });
+
+ self.profiler = new Profiler({
+ sampleInterval: 1,
+ maxBufferSize: 1,
+ });
+ }
+ );
+ </script>
diff --git a/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html b/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html
new file mode 100644
index 0000000000..4d26264211
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/iframe-context-filtration.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+<body>
+ <iframe src="resources/child-frame.html"></iframe>
+
+ <script>
+ promise_test(_ => new Promise(res => window.addEventListener('load', res)),
+ 'wait for load event');
+
+ promise_test(async t => {
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ const iframe = frames[0];
+ await ProfileUtils.forceSampleFrame(iframe);
+
+ const trace = await profiler.stop();
+
+ assert_false(ProfileUtils.containsFrame(trace, { name: 'sampleFromMessage' }),
+ 'function from child context not included in trace');
+
+ const childUrl = iframe.src;
+ assert_false(ProfileUtils.containsResource(trace, childUrl),
+ 'child resources are not included');
+ }, 'functions from child frame are not included in profile created by parent frame');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/inline-script.html b/testing/web-platform/tests/js-self-profiling/inline-script.html
new file mode 100644
index 0000000000..56a7b55c6b
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/inline-script.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="resources/profile-utils.js"></script>
+</head>
+<body>
+ <script>
+ // Note: moving these function definitions will change the expected
+ // outcomes below.
+ function nestedInlineScriptFunction(sample) {
+ sample();
+ }
+
+ function inlineScriptFunction(sample) {
+ nestedInlineScriptFunction(sample);
+ }
+ </script>
+
+ <script>
+ promise_test(async t => {
+ const trace = await ProfileUtils.profileFunction(inlineScriptFunction);
+
+ assert_true(ProfileUtils.containsResource(trace, location.href),
+ 'inline script resource is included');
+
+ assert_true(ProfileUtils.containsSubstack(trace, [
+ {
+ name: 'nestedInlineScriptFunction',
+ resourceId: trace.resources.indexOf(location.href),
+ line: 13,
+ column: 40,
+ },
+ {
+ name: 'inlineScriptFunction',
+ resourceId: trace.resources.indexOf(location.href),
+ line: 17,
+ column: 34,
+ },
+ ]));
+ }, 'inline script function details');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js b/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js
new file mode 100644
index 0000000000..659d4cd14d
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/max-buffer-size.window.js
@@ -0,0 +1,49 @@
+// META: script=resources/profile-utils.js
+
+promise_test(async t => {
+ assert_throws_js(TypeError, () => {
+ new Profiler({ sampleInterval: 10 });
+ });
+}, 'max buffer size must be defined');
+
+promise_test(async t => {
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: 2,
+ });
+
+ // Force 3 samples with a max buffer size of 2.
+ for (let i = 0; i < 3; i++) {
+ ProfileUtils.forceSample();
+ }
+
+ const trace = await profiler.stop();
+ assert_equals(trace.samples.length, 2);
+}, 'max buffer size is not exceeded');
+
+promise_test(async t => {
+ const largeBufferProfiler = new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER });
+ const smallBufferProfiler = new Profiler({ sampleInterval: 10, maxBufferSize: 1 });
+
+ const watcher = new EventWatcher(t, smallBufferProfiler, ['samplebufferfull']);
+
+ largeBufferProfiler.addEventListener('samplebufferfull', () => {
+ assert_unreached('samplebufferfull invoked on wrong profiler');
+ largeBufferProfiler.stop();
+ smallBufferProfiler.stop();
+ });
+
+ smallBufferProfiler.addEventListener('samplebufferfull', () => {
+ largeBufferProfiler.stop();
+ smallBufferProfiler.stop();
+ });
+
+ // Force two samples to be taken, which should exceed
+ // |smallBufferProfiler|'s buffer size.
+ for (let i = 0; i < 2; i++) {
+ ProfileUtils.forceSample();
+ }
+
+ // Ensure that |smallBufferProfiler|'s buffer size is exceeded.
+ await watcher.wait_for('samplebufferfull');
+}, 'ensure samplebufferfull is fired on full profiler');
diff --git a/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers b/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers
new file mode 100644
index 0000000000..93537b44de
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/__dir__.headers
@@ -0,0 +1 @@
+Document-Policy: js-profiling
diff --git a/testing/web-platform/tests/js-self-profiling/resources/child-frame.html b/testing/web-platform/tests/js-self-profiling/resources/child-frame.html
new file mode 100644
index 0000000000..7f8125eae7
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/child-frame.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+ <script src="profile-utils.js"></script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/resources/external-script.js b/testing/web-platform/tests/js-self-profiling/resources/external-script.js
new file mode 100644
index 0000000000..6f6bd42b25
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js
@@ -0,0 +1,9 @@
+// NOTE: Modifying the location of functions in this file will cause
+// `external-script.html` to fail! Please update the following constants
+// accordingly.
+const EXTERNAL_SCRIPT_FUNCTION_LINE = 7;
+const EXTERNAL_SCRIPT_FUNCTION_COLUMN = 32;
+
+function externalScriptFunction(sample) {
+ sample();
+}
diff --git a/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers b/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/external-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js b/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js
new file mode 100644
index 0000000000..2c32f34608
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profile-utils.js
@@ -0,0 +1,149 @@
+(function(global) {
+ const TEST_SAMPLE_INTERVAL = 10;
+ const ENSURE_SAMPLE_SPIN_WAIT_MS = 500;
+
+ function forceSample() {
+ // Spin for |TEST_SAMPLE_INTERVAL + 500|ms to ensure that a sample occurs
+ // before this function returns. As periodic sampling is enforced by a
+ // SHOULD clause, it is indeed testable.
+ //
+ // More reliable sampling will be handled in a future testdriver RFC
+ // (https://github.com/web-platform-tests/rfcs/pull/81).
+ for (const deadline = performance.now() + TEST_SAMPLE_INTERVAL +
+ ENSURE_SAMPLE_SPIN_WAIT_MS;
+ performance.now() < deadline;)
+ ;
+ }
+
+ // Creates a new profile that captures the execution of when the given
+ // function calls the `sample` function passed to it.
+ async function profileFunction(func) {
+ const profiler = new Profiler({
+ sampleInterval: TEST_SAMPLE_INTERVAL,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+
+ func(() => forceSample());
+
+ const trace = await profiler.stop();
+
+ // Sanity check ensuring that we captured a sample.
+ assert_greater_than(trace.resources.length, 0);
+ assert_greater_than(trace.frames.length, 0);
+ assert_greater_than(trace.stacks.length, 0);
+ assert_greater_than(trace.samples.length, 0);
+
+ return trace;
+ }
+
+ async function testFunction(func, frame) {
+ const trace = await profileFunction(func);
+ assert_true(containsFrame(trace, frame), 'trace contains frame');
+ }
+
+ function substackMatches(trace, stackId, expectedStack) {
+ if (expectedStack.length === 0) {
+ return true;
+ }
+ if (stackId === undefined) {
+ return false;
+ }
+
+ const stackElem = trace.stacks[stackId];
+ const expectedFrame = expectedStack[0];
+
+ if (!frameMatches(trace.frames[stackElem.frameId], expectedFrame)) {
+ return false;
+ }
+ return substackMatches(trace, stackElem.parentId, expectedStack.slice(1));
+ }
+
+ // Returns true if the trace contains a frame matching the given specification.
+ // We define a "match" as follows: a frame A matches an expectation E if (and
+ // only if) for each field of E, A has the same value.
+ function containsFrame(trace, expectedFrame) {
+ return trace.frames.find(frame => {
+ return frameMatches(frame, expectedFrame);
+ }) !== undefined;
+ }
+
+ // Returns true if a trace contains a substack in one of its samples, ordered
+ // leaf to root.
+ function containsSubstack(trace, expectedStack) {
+ return trace.samples.find(sample => {
+ let stackId = sample.stackId;
+ while (stackId !== undefined) {
+ if (substackMatches(trace, stackId, expectedStack)) {
+ return true;
+ }
+ stackId = trace.stacks[stackId].parentId;
+ }
+ return false;
+ }) !== undefined;
+ }
+
+ function containsResource(trace, expectedResource) {
+ return trace.resources.includes(expectedResource);
+ }
+
+ // Returns true if a trace contains a sample matching the given specification.
+ // We define a "match" as follows: a sample A matches an expectation E if (and
+ // only if) for each field of E, A has the same value.
+ function containsSample(trace, expectedSample) {
+ return trace.samples.find(sample => {
+ return sampleMatches(sample, expectedSample);
+ }) !== undefined;
+ }
+
+ // Compares each set field of `expected` against the given frame `actual`.
+ function sampleMatches(actual, expected) {
+ return (expected.timestamp === undefined ||
+ expected.timestamp === actual.timestamp) &&
+ (expected.stackId === undefined ||
+ expected.stackId === actual.stackId) &&
+ (expected.marker === undefined || expected.marker === actual.marker);
+ }
+
+ // Compares each set field of `expected` against the given frame `actual`.
+ function frameMatches(actual, expected) {
+ return (expected.name === undefined || expected.name === actual.name) &&
+ (expected.resourceId === undefined || expected.resourceId === actual.resourceId) &&
+ (expected.line === undefined || expected.line === actual.line) &&
+ (expected.column === undefined || expected.column === actual.column);
+ }
+
+ function forceSampleFrame(frame) {
+ const channel = new MessageChannel();
+ const replyPromise = new Promise(res => {
+ channel.port1.onmessage = res;
+ });
+ frame.postMessage('', '*', [channel.port2]);
+ return replyPromise;
+ }
+
+ window.addEventListener('message', message => {
+ // Force sample in response to messages received.
+ (function sampleFromMessage() {
+ ProfileUtils.forceSample();
+ message.ports[0].postMessage('');
+ })();
+ });
+
+ global.ProfileUtils = {
+ // Capturing
+ profileFunction,
+ forceSample,
+
+ // Containment checks
+ containsFrame,
+ containsSubstack,
+ containsResource,
+ containsSample,
+
+ // Cross-frame sampling
+ forceSampleFrame,
+
+ // Assertions
+ testFunction,
+ };
+})(this);
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js
new file mode 100644
index 0000000000..9f09e05b34
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js
@@ -0,0 +1,26 @@
+(function(global) {
+ let counter = 0;
+
+ // Spins up a new profiler and performs some work in a new top-level task,
+ // calling some builtins. Returns a promise for the resulting trace.
+ const profileBuiltinsInNewTask = () => {
+ // Run profiling logic in a new task to eliminate the caller stack.
+ return new Promise(resolve => {
+ setTimeout(async () => {
+ const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
+ for (const deadline = performance.now() + 500; performance.now() < deadline;) {
+ // Run a range of builtins to ensure they get included in the trace.
+ // Store this computation in a variable to prevent getting optimized out.
+ counter += Math.random();
+ counter += performance.now();
+ }
+ const trace = await profiler.stop();
+ resolve(trace);
+ });
+ });
+ }
+
+ global.ProfilingScript = {
+ profileBuiltinsInNewTask,
+ }
+})(window);
diff --git a/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers
new file mode 100644
index 0000000000..cb762eff80
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/resources/profiling-script.js.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/README.md b/testing/web-platform/tests/js-self-profiling/tentative/README.md
new file mode 100644
index 0000000000..a6b8d008f2
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/README.md
@@ -0,0 +1,3 @@
+Tests in this directory are for the proposed js-self-profiling API marker extension. This is not yet standardised and browsers should not be expected to pass these tests.
+
+See the explainer at https://github.com/WICG/js-self-profiling/blob/main/markers.md for more information about the API.
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html
new file mode 100644
index 0000000000..07dbe7f0ff
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+ <script src="../resources/profile-utils.js"></script>
+</head>
+
+<body>
+
+ <script>
+ // Note: moving these function definitions will change the expected
+ // outcomes below.
+ function scriptFunction(sample) {
+ sample();
+ }
+ </script>
+
+ <script>
+ promise_test(async t => {
+ // *.headers file should ensure we sesrve COOP and COEP headers.
+ assert_true(self.crossOriginIsolated,
+ "Cross origin isolation is required to surface markers");
+ const trace = await ProfileUtils.profileFunction(scriptFunction);
+
+ assert_true(ProfileUtils.containsSample(trace, {
+ stackId: 7,
+ marker: 'script'
+ }));
+ }, 'markers logged correctly');
+
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers
new file mode 100644
index 0000000000..228047a15a
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/tentative/marker-vm-state.https.html.headers
@@ -0,0 +1,3 @@
+Document-Policy: js-profiling
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin
diff --git a/testing/web-platform/tests/js-self-profiling/time-domain.window.js b/testing/web-platform/tests/js-self-profiling/time-domain.window.js
new file mode 100644
index 0000000000..5791a3de75
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/time-domain.window.js
@@ -0,0 +1,19 @@
+// META: script=resources/profile-utils.js
+
+promise_test(async () => {
+ const start = performance.now();
+
+ const profiler = new Profiler({
+ sampleInterval: 10,
+ maxBufferSize: Number.MAX_SAFE_INTEGER,
+ });
+ ProfileUtils.forceSample();
+ const trace = await profiler.stop();
+
+ const end = performance.now();
+
+ assert_greater_than(trace.samples.length, 0);
+ for (const sample of trace.samples) {
+ assert_between_inclusive(sample.timestamp, start, end);
+ }
+}, 'sample timestamps use the current high-resolution time');
diff --git a/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html b/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html
new file mode 100644
index 0000000000..bff9851263
--- /dev/null
+++ b/testing/web-platform/tests/js-self-profiling/without-document-policy/disabled.https.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ test(t => {
+ assert_throws_dom('NotAllowedError', () => {
+ new Profiler({ sampleInterval: 10, maxBufferSize: Number.MAX_SAFE_INTEGER });
+ });
+ }, 'profiling should throw without passing document policy');
+ </script>
+</body>
+</html>