Skip to content

Commit

Permalink
[WIP] wake lock: Adapt to the version of the spec in PR #201
Browse files Browse the repository at this point in the history
This CL includes an implementation of the Wake Lock spec after
https://github.com/w3c/wake-lock/pull/201 [this will be updated once pending
clean-up PRs are merged too].

The spec has undergone significant changes since the latest update to the
implementation, and multiple features were added and removed to the spec in
the meantime.

From a Web API user perspective, the Wake Lock API surface is now pretty
small:
* While the Wake Lock interface still exists, it only contains two static
  methods, requestPermission() and request(). requestPermission() needs
  pending work on the permissions side in //content and //chrome, so this CL
  only implements request().
* navigator.getWakeLock() and WakeLock.createRequest() have been removed and
  replaced by a single call to WakeLock.request().
* A consequence of the two items above is that it is no longer possible to
  actually hold a WakeLock object (and all attributes and event listeners
  are gone).
* WakeLock.request() returns a promise that never resolves when it works,
  and rejects with either NotAllowedError or AbortError in case something
  did not work or the lock was cancelled.
* We now use the AbortSignal mechanism from the DOM spec (just like other
  specs such as Fetch) for cancelling a Wake Lock.

From a Blink perspective:
* wake_lock.cc just performs the validation steps in WakeLock.request(), and
  delegates the act of acquiring or releasing a wake lock to
  WakeLockController.
* WakeLockController is similar to other controllers (like the ones in
  device_orientation/) and tracks per-Document wake lock state. It also does
  not acquire or release wake locks itself, but rather delegates it to its
  per-wake-lock-type instances of WakeLockStateRecord. It also handles
  per-document visibility and activity changes.
* WakeLockStateRecord implements the spec's concept of "state record" and
  actually requests and cancels wake locks (by talking to //content and
  //services).

Major pending Wake Lock features present in the spec:
* Workers support
* Permission handling

Bug: 257511
Change-Id: I3dced3a16711b720a96b1a0d5c008d49e3b38c6d
  • Loading branch information
rakuco authored and chromium-wpt-export-bot committed May 15, 2019
1 parent 0385bbf commit ac8c95e
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 159 deletions.
27 changes: 20 additions & 7 deletions feature-policy/resources/feature-policy-wakelock.html
@@ -1,12 +1,25 @@
<script>
"use strict";

Promise.resolve().then(() => {
try {
const wakeLock = new WakeLock("screen");
window.parent.postMessage({ enabled: true }, "*");
} catch (e) {
window.parent.postMessage({ enabled: false }, "*");
}
Promise.resolve().then(async () => {
// On success, WakeLock.request() returns a promise that never resolves. To
// prevent a timeout, abort it with an AbortController and use the different
// DOMExceptions we get to determine if this worked or not.
const controller = new AbortController();
const wakeLock = WakeLock.request("screen", { signal: controller.signal });
wakeLock.catch(e => {
if (e.name == "AbortError") {
// We stopped due to the call to AbortController.abort(), so we did manage
// to get the lock.
window.parent.postMessage({ enabled: true }, "*");
} else if (e.name == "NotAllowedError") {
// This means requesting the lock failed.
window.parent.postMessage({ enabled: false }, "*");
} else {
// We should not really hit this branch.
window.parent.postMessage({ enabled: false }, "*");
}
});
controller.abort();
});
</script>
9 changes: 3 additions & 6 deletions interfaces/wake-lock.idl
Expand Up @@ -9,13 +9,10 @@ dictionary WakeLockPermissionDescriptor : PermissionDescriptor {

enum WakeLockType { "screen", "system" };

[Constructor(WakeLockType type), SecureContext, Exposed=(DedicatedWorker,Window)]
interface WakeLock : EventTarget {
[SecureContext, Exposed=(DedicatedWorker,Window)]
interface WakeLock {
[Exposed=Window] static Promise<PermissionState> requestPermission(WakeLockType type);
readonly attribute WakeLockType type;
readonly attribute boolean active;
attribute EventHandler onactivechange;
Promise<void> request(optional WakeLockRequestOptions options);
static Promise<void> request(WakeLockType type, optional WakeLockRequestOptions options);
};

dictionary WakeLockRequestOptions {
Expand Down
2 changes: 1 addition & 1 deletion wake-lock/idlharness.https.any.js
Expand Up @@ -10,7 +10,7 @@ idl_test(
['dom', 'html', 'permissions'],
idl_array => {
idl_array.add_objects({
WakeLock: ['new WakeLock("screen")']
WakeLock: []
});
}
);
1 change: 1 addition & 0 deletions wake-lock/resources/page1.html
@@ -0,0 +1 @@
<meta charset="utf-8">
1 change: 1 addition & 0 deletions wake-lock/resources/page2.html
@@ -0,0 +1 @@
<meta charset="utf-8">
31 changes: 31 additions & 0 deletions wake-lock/wakelock-abortsignal-set.https.html
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>WakeLock: passing an AbortSignal already set aborts</title>
<link rel="help" href="https://w3c.github.io/wake-lock/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
promise_test(t => {
const abortController = new AbortController();
const abortSignal = abortController.signal;
abortController.abort();
assert_true(abortSignal.aborted);

return promise_rejects(t, "AbortError", WakeLock.request('screen', { signal: abortSignal }));
}, "A WakeLock request with an AbortSignal whose abort flag is set always aborts");

promise_test(async t => {
const abortController = new AbortController();
const abortSignal = abortController.signal;
abortController.abort();
assert_true(abortSignal.aborted);

const lock1 = WakeLock.request('screen', { signal: abortSignal });
const lock2 = WakeLock.request('screen', { signal: abortSignal });
const lock3 = WakeLock.request('system', { signal: abortSignal });

await promise_rejects(t, "AbortError", lock1);
await promise_rejects(t, "AbortError", lock2);
await promise_rejects(t, "AbortError", lock3);
}, "The same AbortSignal can be used to cause multiple wake locks to abort");
</script>
81 changes: 81 additions & 0 deletions wake-lock/wakelock-active-document.https.window.js
@@ -0,0 +1,81 @@
function getWakeLockObject(iframe, url) {
return new Promise(resolve => {
iframe.addEventListener(
"load",
() => {
const { WakeLock } = iframe.contentWindow;
resolve(WakeLock);
},
{ once: true }
);
iframe.src = url;
});
}

promise_test(async t => {
const iframe = document.createElement("iframe");
document.body.appendChild(iframe);
// We first got to page1.html, grab a WakeLock object.
const wakeLock1 = await getWakeLockObject(
iframe,
"/wake-lock/resources/page1.html"
);
// We navigate the iframe again, putting wakeLock1's document into an inactive state.
const wakeLock2 = await getWakeLockObject(
iframe,
"/wake-lock/resources/page2.html"
);
// Now, wakeLock1's relevant global object's document is no longer active.
// So, call .request(), and make sure it rejects appropriately.
await promise_rejects(
t,
"NotAllowedError",
wakeLock1.request('screen'),
"Inactive document, so must throw NotAllowedError"
);
// We are done, so clean up.
iframe.remove();
}, "WakeLock.request() aborts if the document is not active.");

promise_test(async t => {
// We nest two iframes and wait for them to load.
const outerIframe = document.createElement("iframe");
document.body.appendChild(outerIframe);
// Load the outer iframe (we don't care about the awaited request)
await getWakeLockObject(
outerIframe,
"/wake-lock/resources/page1.html"
);

// Now we create the inner iframe
const innerIframe = outerIframe.contentDocument.createElement("iframe");

// nest them
outerIframe.contentDocument.body.appendChild(innerIframe);

// load innerIframe, and get the PaymentRequest instance
const wakeLock = await getWakeLockObject(
innerIframe,
"/wake-lock/resources/page2.html"
);

// Navigate the outer iframe to a new location.
// Wait for the load event to fire.
await new Promise(resolve => {
outerIframe.addEventListener("load", resolve);
outerIframe.src = "/wake-lock/resources/page2.html";
});

// Now, request's relevant global object's document is still active
// (it is the active document of the inner iframe), but is not fully active
// (since the parent of the inner iframe is itself no longer active).
// So, call request.show() and make sure it rejects appropriately.
await promise_rejects(
t,
"NotAllowedError",
wakeLock.request('screen'),
"Active, but not fully active, so must throw NotAllowedError"
);
// We are done, so clean up.
outerIframe.remove();
}, "WakeLock.request() aborts if the document is active, but not fully active.");
59 changes: 0 additions & 59 deletions wake-lock/wakelock-applicability-manual.https.html

This file was deleted.

8 changes: 4 additions & 4 deletions wake-lock/wakelock-disabled-by-feature-policy.https.sub.html
Expand Up @@ -11,13 +11,13 @@
const cross_origin_src =
"https://{{domains[www]}}:{{ports[https][0]}}" + same_origin_src;

test(() => {
assert_throws("NotAllowedError", () => new WakeLock("screen"));
promise_test(t => {
return promise_rejects(t, "NotAllowedError", WakeLock.request("screen"));
}, 'Feature-Policy header {"wake-lock" : []} disallows the top-level document.');

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
same_origin_src,
expect_feature_unavailable_default
Expand All @@ -26,7 +26,7 @@

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
cross_origin_src,
expect_feature_unavailable_default
Expand Down
38 changes: 38 additions & 0 deletions wake-lock/wakelock-document-hidden-manual.https.html
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="timeout" content="long">
<title>Screen wake locks respect page visibility changes</title>
<link rel="help" href="https://w3c.github.io/wake-lock/#dfn-requesting-the-wake-lock">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

promise_test(async t => {
const controller = new AbortController();
const screenWakeLock = WakeLock.request('screen', { signal: controller.signal });
const systemWakeLock = WakeLock.request('system', { signal: controller.signal });
const systemWakeLockPromise = new Promise((resolve, reject) => {
systemWakeLock.catch(error => {
assert_equals("AbortError", error.name, "systemWakeLock must have been aborted");
assert_false(document.hidden, "systemWakeLock must have been aborted after the page is visible again");
resolve();
});
});

const eventWatcher = new EventWatcher(t, document, "visibilitychange");
await eventWatcher.wait_for("visibilitychange");
assert_true(document.hidden, "document is hidden after the visibilitychange event");
await promise_rejects(t, "AbortError", screenWakeLock, "existing screen locks are aborted");
await promise_rejects(t, "NotAllowedError", WakeLock.request('screen'),
"new screen locks are not allowed when the page is not visible");

await eventWatcher.wait_for("visibilitychange");
assert_false(document.hidden, "document is no longer hidden after the visibilitychange event");
controller.abort();

return systemWakeLockPromise;
}, "Test screen locks respect page visibility changes and system locks are unchanged");

</script>

<p>Switch the page to the background, then switch back to it.</p>
31 changes: 0 additions & 31 deletions wake-lock/wakelock-document-hidden.https.html

This file was deleted.

Expand Up @@ -14,7 +14,7 @@

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
same_origin_src,
expect_feature_available_default,
Expand All @@ -24,7 +24,7 @@

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
cross_origin_src,
expect_feature_unavailable_default,
Expand Down
Expand Up @@ -13,7 +13,7 @@

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
same_origin_src,
expect_feature_available_default,
Expand All @@ -23,7 +23,7 @@

async_test(t => {
test_feature_availability(
'new WakeLock("screen")',
'WakeLock.request("screen")',
t,
cross_origin_src,
expect_feature_available_default,
Expand Down

0 comments on commit ac8c95e

Please sign in to comment.