Commit 8ebf51ef authored by Dave Tapuska's avatar Dave Tapuska Committed by Commit Bot

Add ability for service worker to filter out frozen windows.

Add code to support handling of frozen clients. Frozen clients do not
run their event loop, so postMessage to them just causes problems. To
allow service workers to continue working with frozen windows we expose
includeFrozen in the matchAll and the frozen attribute on the Client. If
a service worker calls focus on a client it will unfreeze the window when
it is moved to have focus. This feature is currently marked as
experimental and an intent to ship will be sent.

This is specified in https://wicg.github.io/page-lifecycle/ and
https://github.com/w3c/ServiceWorker/pull/1442

BUG=957597

Change-Id: I6abe1882e88c65dac99250db5bb7fa8d3a4b2b1d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1677065
Commit-Queue: Ken Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#689558}
parent 6cc1d3cc
......@@ -127,6 +127,10 @@ blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfoOnUI(
if (!render_frame_host)
return nullptr;
// Treat items in backforward cache as not existing.
if (render_frame_host->is_in_back_forward_cache())
return nullptr;
// TODO(mlamouri,michaeln): it is possible to end up collecting information
// for a frame that is actually being navigated and isn't exactly what we are
// expecting.
......@@ -139,6 +143,9 @@ blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfoOnUI(
: network::mojom::RequestContextFrameType::kTopLevel,
client_uuid, blink::mojom::ServiceWorkerClientType::kWindow, page_hidden,
render_frame_host->IsFocused(),
render_frame_host->IsFrozen()
? blink::mojom::ServiceWorkerClientLifecycleState::kFrozen
: blink::mojom::ServiceWorkerClientLifecycleState::kActive,
render_frame_host->frame_tree_node()->last_focus_time(), create_time);
}
......@@ -343,11 +350,15 @@ void AddNonWindowClient(const ServiceWorkerProviderHost* host,
if (!host->is_execution_ready())
return;
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
host->url(), network::mojom::RequestContextFrameType::kNone,
host->client_uuid(), host_client_type,
/*page_hidden=*/true,
/*is_focused=*/false, base::TimeTicks(), host->create_time());
/*is_focused=*/false,
blink::mojom::ServiceWorkerClientLifecycleState::kActive,
base::TimeTicks(), host->create_time());
out_clients->push_back(std::move(client_info));
}
......@@ -356,6 +367,7 @@ void OnGetWindowClientsOnUI(
const std::vector<std::tuple<int, int, base::TimeTicks, std::string>>&
clients_info,
const GURL& script_url,
blink::mojom::ServiceWorkerClientLifecycleStateQuery lifecycle_state,
ClientsCallback callback,
std::unique_ptr<ServiceWorkerClientPtrs> out_clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
......@@ -378,6 +390,19 @@ void OnGetWindowClientsOnUI(
if (info->url.GetOrigin() != script_url.GetOrigin())
continue;
// Skip frozen clients if asked to be excluded.
if (lifecycle_state !=
blink::mojom::ServiceWorkerClientLifecycleStateQuery::kAll &&
((lifecycle_state ==
blink::mojom::ServiceWorkerClientLifecycleStateQuery::kActive &&
info->lifecycle_state !=
blink::mojom::ServiceWorkerClientLifecycleState::kActive) ||
(lifecycle_state ==
blink::mojom::ServiceWorkerClientLifecycleStateQuery::kFrozen &&
info->lifecycle_state !=
blink::mojom::ServiceWorkerClientLifecycleState::kFrozen)))
continue;
out_clients->push_back(std::move(info));
}
......@@ -481,10 +506,12 @@ void GetWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
return;
}
blink::mojom::ServiceWorkerClientLifecycleStateQuery lifecycle_state =
options->lifecycle_state;
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(&OnGetWindowClientsOnUI, clients_info,
controller->script_url(),
controller->script_url(), lifecycle_state,
base::BindOnce(&DidGetWindowClients, controller,
std::move(options), std::move(callback)),
std::move(clients)));
......@@ -619,11 +646,15 @@ void GetClient(const ServiceWorkerProviderHost* provider_host,
return;
}
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
provider_host->url(), network::mojom::RequestContextFrameType::kNone,
provider_host->client_uuid(), provider_host->client_type(),
/*page_hidden=*/true,
/*is_focused=*/false, base::TimeTicks(), provider_host->create_time());
/*is_focused=*/false,
blink::mojom::ServiceWorkerClientLifecycleState::kActive,
base::TimeTicks(), provider_host->create_time());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(client_info)));
}
......
......@@ -18,6 +18,21 @@ enum ServiceWorkerClientType {
kAll,
};
// Indicates the service worker client lifecycle state.
// https://wicg.github.io/page-lifecycle/#serviceworker-client-dfn
enum ServiceWorkerClientLifecycleState {
kActive,
kFrozen,
};
// Indicates the query mode for service worker lifecycle state.
// https://wicg.github.io/page-lifecycle/#serviceworker-clients-dfn
enum ServiceWorkerClientLifecycleStateQuery {
kActive,
kFrozen,
kAll,
};
// Indicates the options for the service worker clients matching operation.
// https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions.
struct ServiceWorkerClientQueryOptions {
......@@ -26,6 +41,9 @@ struct ServiceWorkerClientQueryOptions {
// ClientQueryOptions#type
ServiceWorkerClientType client_type = ServiceWorkerClientType.kWindow;
// ClientQueryOptions#lifecycleState
ServiceWorkerClientLifecycleStateQuery lifecycle_state = ServiceWorkerClientLifecycleStateQuery.kActive;
};
// Holds the information related to a service worker client.
......@@ -55,6 +73,9 @@ struct ServiceWorkerClientInfo {
// Set to |false| if it's a non-window client.
bool is_focused = false;
// Client#lifecycleState
ServiceWorkerClientLifecycleState lifecycle_state = ServiceWorkerClientLifecycleState.kActive;
// The time this window was last focused. Set to base::TimeTicks() if it's
// a non-window client.
mojo_base.mojom.TimeTicks last_focus_time;
......
......@@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// https://wicg.github.io/page-lifecycle/#serviceworker-clients-dfn
enum ClientLifecycleState {
"active",
"frozen"
};
// https://w3c.github.io/ServiceWorker/#client-interface
[
Exposed=ServiceWorker,
......@@ -15,4 +21,6 @@
// FIXME: frameType is non-standard, see https://crbug.com/697110
[CallWith=ScriptState] readonly attribute ContextFrameType frameType;
[RuntimeEnabled=ServiceWorkerFilterFrozen] readonly attribute ClientLifecycleState lifecycleState;
};
......@@ -10,8 +10,16 @@ enum ClientType {
"all"
};
// https://wicg.github.io/page-lifecycle/#serviceworker-clients-dfn
enum ClientLifecycleStateQuery {
"active",
"frozen",
"all"
};
// https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions
dictionary ClientQueryOptions {
boolean includeUncontrolled = false;
[RuntimeEnabled=ServiceWorkerFilterFrozen] ClientLifecycleStateQuery lifecycleState = "active";
ClientType type = "window";
};
......@@ -32,7 +32,8 @@ ServiceWorkerClient::ServiceWorkerClient(
: uuid_(info.client_uuid),
url_(info.url.GetString()),
type_(info.client_type),
frame_type_(info.frame_type) {}
frame_type_(info.frame_type),
lifecycle_state_(info.lifecycle_state) {}
ServiceWorkerClient::~ServiceWorkerClient() = default;
......@@ -71,6 +72,18 @@ String ServiceWorkerClient::frameType(ScriptState* script_state) const {
return String();
}
String ServiceWorkerClient::lifecycleState() const {
switch (lifecycle_state_) {
case mojom::ServiceWorkerClientLifecycleState::kActive:
return "active";
case mojom::ServiceWorkerClientLifecycleState::kFrozen:
return "frozen";
}
NOTREACHED();
return String();
}
void ServiceWorkerClient::postMessage(ScriptState* script_state,
const ScriptValue& message,
Vector<ScriptValue>& transfer,
......
......@@ -33,6 +33,7 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable {
String type() const;
String frameType(ScriptState*) const;
String id() const { return uuid_; }
String lifecycleState() const;
void postMessage(ScriptState*,
const ScriptValue& message,
Vector<ScriptValue>& transfer,
......@@ -46,10 +47,11 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable {
String Uuid() const { return uuid_; }
private:
String uuid_;
String url_;
mojom::ServiceWorkerClientType type_;
network::mojom::RequestContextFrameType frame_type_;
const String uuid_;
const String url_;
const mojom::ServiceWorkerClientType type_;
const network::mojom::RequestContextFrameType frame_type_;
const mojom::ServiceWorkerClientLifecycleState lifecycle_state_;
};
} // namespace blink
......
......@@ -39,6 +39,18 @@ mojom::ServiceWorkerClientType GetClientType(const String& type) {
return mojom::ServiceWorkerClientType::kWindow;
}
mojom::ServiceWorkerClientLifecycleStateQuery GetLifecycleStateQueryType(
const String& type) {
if (type == "active")
return mojom::ServiceWorkerClientLifecycleStateQuery::kActive;
if (type == "frozen")
return mojom::ServiceWorkerClientLifecycleStateQuery::kFrozen;
if (type == "all")
return mojom::ServiceWorkerClientLifecycleStateQuery::kAll;
NOTREACHED();
return mojom::ServiceWorkerClientLifecycleStateQuery::kActive;
}
void DidGetClient(ScriptPromiseResolver* resolver,
mojom::blink::ServiceWorkerClientInfoPtr info) {
if (!resolver->GetExecutionContext() ||
......@@ -137,7 +149,8 @@ ScriptPromise ServiceWorkerClients::matchAll(
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
global_scope->GetServiceWorkerHost()->GetClients(
mojom::blink::ServiceWorkerClientQueryOptions::New(
options->includeUncontrolled(), GetClientType(options->type())),
options->includeUncontrolled(), GetClientType(options->type()),
GetLifecycleStateQueryType(options->lifecycleState())),
WTF::Bind(&DidGetClients, WrapPersistent(resolver)));
return resolver->Promise();
}
......
......@@ -1474,6 +1474,10 @@
name: "Serial",
status: {"Android": "", "default": "experimental"},
},
{
name: "ServiceWorkerFilterFrozen",
status: "experimental",
},
{
name: "SetRootScroller",
status: "experimental",
......
<!DOCTYPE html>
<title>Service Worker: Clients.matchAll</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/test-helpers.sub.js"></script>
<script>
var scope = 'resources/clients-frame-freeze.html';
var windows = [];
var expected_window_1 =
{visibilityState: 'visible', focused: false, lifecycleState: "frozen", url: new URL(scope + '#1', location).toString(), type: 'window', frameType: 'top-level'};
var expected_window_2 =
{visibilityState: 'visible', focused: false, lifecycleState: "active", url: new URL(scope + '#2', location).toString(), type: 'window', frameType: 'top-level'};
function with_window(url, name) {
return new Promise(function(resolve) {
var child = window.open(url, name);
window.onmessage = () => {resolve(child)};
});
}
promise_test(function(t) {
return service_worker_unregister_and_register(
t, 'resources/clients-matchall-worker.js', scope)
.then(function(registration) {
t.add_cleanup(function() {
return service_worker_unregister(t, scope);
});
return wait_for_state(t, registration.installing, 'activated');
})
.then(function() { return with_window(scope + '#1', 'Child 1'); })
.then(function(window1) {
windows.push(window1);
return with_window(scope + '#2', 'Child 2');
})
.then(function(window2) {
windows.push(window2);
return new Promise(function(resolve) {
window.onmessage = resolve;
windows[0].postMessage('freeze');
});
})
.then(function() {
var channel = new MessageChannel();
return new Promise(function(resolve) {
channel.port1.onmessage = resolve;
windows[1].navigator.serviceWorker.controller.postMessage(
{port:channel.port2, includeLifecycleState: true}, [channel.port2]);
});
})
.then(function(e) {
assert_equals(e.data.length, 1);
assert_object_equals(e.data[0], expected_window_2);
})
.then(function() {
var channel = new MessageChannel();
return new Promise(function(resolve) {
channel.port1.onmessage = resolve;
windows[1].navigator.serviceWorker.controller.postMessage(
{port:channel.port2, options: {lifecycleState: "all"}, includeLifecycleState: true}, [channel.port2]);
});
})
.then(function(e) {
assert_equals(e.data.length, 2);
// No specific order is required, so support inversion.
if (e.data[0][3] == new URL(scope + '#2', location)) {
assert_object_equals(e.data[0], expected_window_2);
assert_object_equals(e.data[1], expected_window_1);
} else {
assert_object_equals(e.data[0], expected_window_1);
assert_object_equals(e.data[1], expected_window_2);
}
})
.then(function() {
var channel = new MessageChannel();
return new Promise(function(resolve) {
channel.port1.onmessage = resolve;
windows[1].navigator.serviceWorker.controller.postMessage(
{port:channel.port2, options: {lifecycleState: "frozen"}, includeLifecycleState: true}, [channel.port2]);
});
})
.then(function(e) {
assert_equals(e.data.length, 1);
assert_object_equals(e.data[0], expected_window_1);
})
.then(function() {
var channel = new MessageChannel();
return new Promise(function(resolve) {
channel.port1.onmessage = resolve;
windows[1].navigator.serviceWorker.controller.postMessage(
{port:channel.port2, options: {lifecycleState: "active"}, includeLifecycleState: true}, [channel.port2]);
});
})
.then(function(e) {
assert_equals(e.data.length, 1);
assert_object_equals(e.data[0], expected_window_2);
});
}, 'Test Clients.matchAll()');
</script>
<!DOCTYPE html>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
document.addEventListener('freeze', () => {
opener.postMessage('frozen', "*");
});
window.onmessage = (e) => {
if (e.data == 'freeze') {
test_driver.freeze();
}
};
opener.postMessage('loaded', '*');
</script>
......@@ -13,11 +13,20 @@ self.onmessage = function(e) {
// In that case, just pretend it's top-level!
frame_type = 'top-level';
}
message.push([client.visibilityState,
client.focused,
client.url,
client.type,
frame_type]);
if (e.data.includeLifecycleState) {
message.push({visibilityState: client.visibilityState,
focused: client.focused,
url: client.url,
lifecycleState: client.lifecycleState,
type: client.type,
frameType: frame_type});
} else {
message.push([client.visibilityState,
client.focused,
client.url,
client.type,
frame_type]);
}
});
// Sort by url
if (!e.data.disableSort) {
......
......@@ -124,6 +124,7 @@ interface Client
attribute @@toStringTag
getter frameType
getter id
getter lifecycleState
getter type
getter url
method constructor
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment