Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute Pressure API: Initial implementation without platform support. #27858

Merged
merged 1 commit into from May 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions compute-pressure/README.md
@@ -0,0 +1,2 @@
This directory contains (tentative) tests for the
[Compute Pressure](https://oyiptong.github.io/compute-pressure/) specification.
@@ -0,0 +1,73 @@
'use strict';

for (const property of ['cpuUtilizationThresholds', 'cpuSpeedThresholds']) {
for (const out_of_range_value of [-1.0, 0.0, 1.0, 2.0]) {
test(t => {
const callback = () => {};

const options = {
cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5] };
options[property] = [out_of_range_value];

assert_throws_js(TypeError, () => {
new ComputePressureObserver(callback, options);
});
}, `ComputePressureObserver constructor throws when ${property} ` +
`is [${out_of_range_value}]`);
}

for (const valid_value of [0.05, 0.1, 0.2, 0.5, 0.9, 0.95]) {
test(t => {
const callback = () => {};

const options = {
cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5] };
options[property] = [valid_value];

const observer = new ComputePressureObserver(callback, options);
assert_true(observer instanceof ComputePressureObserver);
}, `ComputePressureObserver constructor accepts ${property} value ` +
`[${valid_value}]`);
}

promise_test(async t => {
const many_thresholds = [0.5];
for (let i = 0.01; i < 0.5; i += 0.0001) {
many_thresholds.push(0.5 + i);
many_thresholds.push(0.5 - i);
}

const options = {
cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5] };
options[property] = many_thresholds;

const update = await new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(resolve, options);
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});

const effective_thresholds = update.options[property];
assert_less_than(effective_thresholds.length, many_thresholds.length,
'only a small number of thresholds are selected');

const expected_thresholds =
many_thresholds.slice(0, effective_thresholds.length);
expected_thresholds.sort();
assert_array_equals(
effective_thresholds, expected_thresholds,
'thresholds are selected in the given order, before sorting');
}, `ComputePressureObserver filters thresholds in ${property}`);
}

test(t => {
const callback = () => {};


assert_throws_js(TypeError, () => {
new ComputePressureObserver(
callback,
{ cpuUtilizationThresholds: [0.5, 0.5], cpuSpeedThresholds: [0.5] });
});
}, 'ComputePressureObserver constructor throws when cpuUtilizationThresholds ' +
'has duplicates');
24 changes: 24 additions & 0 deletions compute-pressure/compute_pressure_basic.tentative.https.window.js
@@ -0,0 +1,24 @@
'use strict';

promise_test(async t => {
// The quantization thresholds and the quantized values that they lead to can
// be represented exactly in floating-point, so === comparison works.

const update = await new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(
resolve, {cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});

assert_equals(typeof update.cpuUtilization, 'number');
assert_greater_than_equal(update.cpuUtilization, 0.0, 'cpuUtilization range');
assert_less_than_equal(update.cpuUtilization, 1.0, 'cpuUtilization range');
assert_in_array(update.cpuUtilization, [0.25, 0.75],
'cpuUtilization quantization');

assert_equals(typeof update.cpuSpeed, 'number');
assert_greater_than_equal(update.cpuSpeed, 0.0, 'cpuSpeed range');
assert_less_than_equal(update.cpuSpeed, 1.0, 'cpuUSpeed range');
assert_in_array(update.cpuSpeed, [0.25, 0.75], 'cpuSpeed quantization');
}, 'An active ComputePressureObserver calls its callback at least once');
@@ -0,0 +1,83 @@
<!doctype html>
<meta charset="utf-8">
<title>ComputePressureObserver on DOMWindow of detached iframe</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
'use strict';

test(() => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const frame_window = iframe.contentWindow;

iframe.remove();
assert_equals(undefined, frame_window.ComputePressureObserver);
}, 'ComputePressureObserver constructor does not exist in detached iframes');

promise_test(async t => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const frame_window = iframe.contentWindow;

const observer = new frame_window.ComputePressureObserver(
() => {},
{cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
const iframe_DOMException = frame_window.DOMException;

iframe.remove();

// Calling observe() from a detached iframe should fail but not crash.
await promise_rejects_dom(t, 'InvalidStateError', iframe_DOMException,
observer.observe());
}, 'ComputePressureObserver.observe() on detached frame rejects');

promise_test(async t => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const frame_window = iframe.contentWindow;

const observer = new frame_window.ComputePressureObserver(
() => {},
{cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});

await observer.observe();

iframe.remove();

// Calling stop() from a detached iframe should not crash.
observer.stop();
}, 'ComputePressureObserver.stop() on detached frame returns');

promise_test(async t => {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const frame_window = iframe.contentWindow;

const observer = new frame_window.ComputePressureObserver(
() => {},
{cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
const iframe_DOMException = frame_window.DOMException;

// await is intentionally not used here. We want to remove the iframe while
// the returned Promise settles.
const observe_promise = observer.observe();
iframe.remove();

// Establish an observer and wait for an update in the main frame. This should
// keep the test running long enough to catch any crash from the observe()
// call in the removed iframe's ComputePressureObserver.
const update = await new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(
resolve, {cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});
assert_in_array(update.cpuUtilization, [0.25, 0.75],
'cpuUtilization quantization');
assert_in_array(update.cpuSpeed, [0.25, 0.75], 'cpuSpeed quantization')
}, 'Detaching frame while ComputePressureObserver.observe() settles');

</script>
</body>
@@ -0,0 +1,79 @@
'use strict';

promise_test(async t => {
const observer1_updates = [];
const observer1 = new ComputePressureObserver(
update => { observer1_updates.push(update); },
{cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer1.stop());
// Ensure that observer1's quantization scheme gets registered as the origin's
// scheme before observer2 starts.
await observer1.observe();

const observer2_updates = [];
await new Promise((resolve, reject) => {
const observer2 = new ComputePressureObserver(
update => {
observer2_updates.push(update);
resolve();
},
{cpuUtilizationThresholds: [0.25], cpuSpeedThresholds: [0.75]});
t.add_cleanup(() => observer2.stop());
observer2.observe().catch(reject);
});

// observer2 uses a different quantization scheme than observer1. After
// observer2.observe() completes, observer1 should no longer be active.
//
// The check below assumes that observer2.observe() completes before the
// browser dispatches any update for observer1. This assumption is highly
// likely to be true, because there shold be a 1-second delay between
// observer1.observe() and the first update that observer1 would receive.
assert_equals(
observer1_updates.length, 0,
'observer2.observe() should have stopped observer1; the two observers ' +
'have different quantization schemes');

assert_equals(observer2_updates.length, 1);
assert_in_array(observer2_updates[0].cpuUtilization, [0.125, 0.625],
'cpuUtilization quantization');
assert_in_array(observer2_updates[0].cpuSpeed, [0.375, 0.875],
'cpuSpeed quantization');

// Go through one more update cycle so any (incorrect) update for observer1
// makes it through the IPC queues.
observer1_updates.length = 0;
observer2_updates.length = 0;

const observer3_updates = [];
await new Promise((resolve, reject) => {
const observer3 = new ComputePressureObserver(
update => {
observer3_updates.push(update);
resolve();
},
{cpuUtilizationThresholds: [0.75], cpuSpeedThresholds: [0.25]});
t.add_cleanup(() => observer3.stop());
observer3.observe().catch(reject);
});

assert_equals(
observer1_updates.length, 0,
'observer2.observe() should have stopped observer1; the two observers ' +
'have different quantization schemes');

// observer3 uses a different quantization scheme than observer2. So,
// observer3.observe() should stop observer2.
assert_equals(
observer2_updates.length, 0,
'observer3.observe() should have stopped observer2; the two observers ' +
'have different quantization schemes');

assert_equals(observer3_updates.length, 1);
assert_in_array(observer3_updates[0].cpuUtilization, [0.375, 0.875],
'cpuUtilization quantization');
assert_in_array(observer3_updates[0].cpuSpeed, [0.125, 0.625],
'cpuSpeed quantization');

}, 'ComputePressureObserver with a new quantization schema stops all ' +
'other active observers');
@@ -0,0 +1,87 @@
'use strict';

promise_test(async t => {
const observer1_updates = [];
const observer1 = new ComputePressureObserver(
update => { observer1_updates.push(update); },
{cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer1.stop());
// Ensure that observer1's quantization scheme gets registered as the origin's
// scheme before observer2 starts.
await observer1.observe();

// iframe numbers are aligned with observer numbers. The first observer is in
// the main frame, so there is no iframe1.
const iframe2 = document.createElement('iframe');
document.body.appendChild(iframe2);

const observer2_updates = [];
await new Promise((resolve, reject) => {
const observer2 = new iframe2.contentWindow.ComputePressureObserver(
update => {
observer2_updates.push(update);
resolve();
},
{cpuUtilizationThresholds: [0.25], cpuSpeedThresholds: [0.75]});
t.add_cleanup(() => observer2.stop());
observer2.observe().catch(reject);
});

// observer2 uses a different quantization scheme than observer1. After
// observer2.observe() completes, observer1 should no longer be active.
//
// The check below assumes that observer2.observe() completes before the
// browser dispatches any update for observer1. This assumption is highly
// likely to be true, because there shold be a 1-second delay between
// observer1.observe() and the first update that observer1 would receive.
assert_equals(
observer1_updates.length, 0,
'observer2.observe() should have stopped observer1; the two observers ' +
'have different quantization schemes');

assert_equals(observer2_updates.length, 1);
assert_in_array(observer2_updates[0].cpuUtilization, [0.125, 0.625],
'cpuUtilization quantization');
assert_in_array(observer2_updates[0].cpuSpeed, [0.375, 0.875],
'cpuSpeed quantization');

// Go through one more update cycle so any (incorrect) update for observer1
// makes it through the IPC queues.
observer1_updates.length = 0;
observer2_updates.length = 0;

const iframe3 = document.createElement('iframe');
document.body.appendChild(iframe3);

const observer3_updates = [];
await new Promise((resolve, reject) => {
const observer3 = new iframe3.contentWindow.ComputePressureObserver(
update => {
observer3_updates.push(update);
resolve();
},
{cpuUtilizationThresholds: [0.75], cpuSpeedThresholds: [0.25]});
t.add_cleanup(() => observer3.stop());
observer3.observe().catch(reject);
});

assert_equals(
observer1_updates.length, 0,
'observer2.observe() should have stopped observer1; the two observers ' +
'have different quantization schemes');

// observer3 uses a different quantization scheme than observer2. So,
// observer3.observe() should stop observer2.
assert_equals(
observer2_updates.length, 0,
'observer3.observe() should have stopped observer2; the two observers ' +
'have different quantization schemes');

assert_equals(observer3_updates.length, 1);
assert_in_array(observer3_updates[0].cpuUtilization, [0.375, 0.875],
'cpuUtilization quantization');
assert_in_array(observer3_updates[0].cpuSpeed, [0.125, 0.625],
'cpuSpeed quantization');

}, 'ComputePressureObserver with a new quantization schema stops all ' +
'other active observers');
@@ -0,0 +1,34 @@
'use strict';

promise_test(async t => {
const update1_promise = new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(
resolve, {cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});

const update2_promise = new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(
resolve, {cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});

const update3_promise = new Promise((resolve, reject) => {
const observer = new ComputePressureObserver(
resolve, {cpuUtilizationThresholds: [0.5], cpuSpeedThresholds: [0.5]});
t.add_cleanup(() => observer.stop());
observer.observe().catch(reject);
});

const [update1, update2, update3] =
await Promise.all([update1_promise, update2_promise, update3_promise]);

for (const update of [update1, update2, update3]) {
assert_in_array(update.cpuUtilization, [0.25, 0.75],
'cpuUtilization quantization');
assert_in_array(update.cpuSpeed, [0.25, 0.75], 'cpuSpeed quantization');
}
}, 'Three ComputePressureObserver instances with the same quantization ' +
'schema receive updates');