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( ...@@ -127,6 +127,10 @@ blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfoOnUI(
if (!render_frame_host) if (!render_frame_host)
return nullptr; 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 // 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 // for a frame that is actually being navigated and isn't exactly what we are
// expecting. // expecting.
...@@ -139,6 +143,9 @@ blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfoOnUI( ...@@ -139,6 +143,9 @@ blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfoOnUI(
: network::mojom::RequestContextFrameType::kTopLevel, : network::mojom::RequestContextFrameType::kTopLevel,
client_uuid, blink::mojom::ServiceWorkerClientType::kWindow, page_hidden, client_uuid, blink::mojom::ServiceWorkerClientType::kWindow, page_hidden,
render_frame_host->IsFocused(), 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); render_frame_host->frame_tree_node()->last_focus_time(), create_time);
} }
...@@ -343,11 +350,15 @@ void AddNonWindowClient(const ServiceWorkerProviderHost* host, ...@@ -343,11 +350,15 @@ void AddNonWindowClient(const ServiceWorkerProviderHost* host,
if (!host->is_execution_ready()) if (!host->is_execution_ready())
return; return;
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New( auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
host->url(), network::mojom::RequestContextFrameType::kNone, host->url(), network::mojom::RequestContextFrameType::kNone,
host->client_uuid(), host_client_type, host->client_uuid(), host_client_type,
/*page_hidden=*/true, /*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)); out_clients->push_back(std::move(client_info));
} }
...@@ -356,6 +367,7 @@ void OnGetWindowClientsOnUI( ...@@ -356,6 +367,7 @@ void OnGetWindowClientsOnUI(
const std::vector<std::tuple<int, int, base::TimeTicks, std::string>>& const std::vector<std::tuple<int, int, base::TimeTicks, std::string>>&
clients_info, clients_info,
const GURL& script_url, const GURL& script_url,
blink::mojom::ServiceWorkerClientLifecycleStateQuery lifecycle_state,
ClientsCallback callback, ClientsCallback callback,
std::unique_ptr<ServiceWorkerClientPtrs> out_clients) { std::unique_ptr<ServiceWorkerClientPtrs> out_clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
...@@ -378,6 +390,19 @@ void OnGetWindowClientsOnUI( ...@@ -378,6 +390,19 @@ void OnGetWindowClientsOnUI(
if (info->url.GetOrigin() != script_url.GetOrigin()) if (info->url.GetOrigin() != script_url.GetOrigin())
continue; 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)); out_clients->push_back(std::move(info));
} }
...@@ -481,10 +506,12 @@ void GetWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller, ...@@ -481,10 +506,12 @@ void GetWindowClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
return; return;
} }
blink::mojom::ServiceWorkerClientLifecycleStateQuery lifecycle_state =
options->lifecycle_state;
RunOrPostTaskOnThread( RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI, FROM_HERE, BrowserThread::UI,
base::BindOnce(&OnGetWindowClientsOnUI, clients_info, base::BindOnce(&OnGetWindowClientsOnUI, clients_info,
controller->script_url(), controller->script_url(), lifecycle_state,
base::BindOnce(&DidGetWindowClients, controller, base::BindOnce(&DidGetWindowClients, controller,
std::move(options), std::move(callback)), std::move(options), std::move(callback)),
std::move(clients))); std::move(clients)));
...@@ -619,11 +646,15 @@ void GetClient(const ServiceWorkerProviderHost* provider_host, ...@@ -619,11 +646,15 @@ void GetClient(const ServiceWorkerProviderHost* provider_host,
return; return;
} }
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New( auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
provider_host->url(), network::mojom::RequestContextFrameType::kNone, provider_host->url(), network::mojom::RequestContextFrameType::kNone,
provider_host->client_uuid(), provider_host->client_type(), provider_host->client_uuid(), provider_host->client_type(),
/*page_hidden=*/true, /*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( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(client_info))); FROM_HERE, base::BindOnce(std::move(callback), std::move(client_info)));
} }
......
...@@ -18,6 +18,21 @@ enum ServiceWorkerClientType { ...@@ -18,6 +18,21 @@ enum ServiceWorkerClientType {
kAll, 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. // Indicates the options for the service worker clients matching operation.
// https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions. // https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions.
struct ServiceWorkerClientQueryOptions { struct ServiceWorkerClientQueryOptions {
...@@ -26,6 +41,9 @@ struct ServiceWorkerClientQueryOptions { ...@@ -26,6 +41,9 @@ struct ServiceWorkerClientQueryOptions {
// ClientQueryOptions#type // ClientQueryOptions#type
ServiceWorkerClientType client_type = ServiceWorkerClientType.kWindow; ServiceWorkerClientType client_type = ServiceWorkerClientType.kWindow;
// ClientQueryOptions#lifecycleState
ServiceWorkerClientLifecycleStateQuery lifecycle_state = ServiceWorkerClientLifecycleStateQuery.kActive;
}; };
// Holds the information related to a service worker client. // Holds the information related to a service worker client.
...@@ -55,6 +73,9 @@ struct ServiceWorkerClientInfo { ...@@ -55,6 +73,9 @@ struct ServiceWorkerClientInfo {
// Set to |false| if it's a non-window client. // Set to |false| if it's a non-window client.
bool is_focused = false; 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 // The time this window was last focused. Set to base::TimeTicks() if it's
// a non-window client. // a non-window client.
mojo_base.mojom.TimeTicks last_focus_time; mojo_base.mojom.TimeTicks last_focus_time;
......
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 // https://w3c.github.io/ServiceWorker/#client-interface
[ [
Exposed=ServiceWorker, Exposed=ServiceWorker,
...@@ -15,4 +21,6 @@ ...@@ -15,4 +21,6 @@
// FIXME: frameType is non-standard, see https://crbug.com/697110 // FIXME: frameType is non-standard, see https://crbug.com/697110
[CallWith=ScriptState] readonly attribute ContextFrameType frameType; [CallWith=ScriptState] readonly attribute ContextFrameType frameType;
[RuntimeEnabled=ServiceWorkerFilterFrozen] readonly attribute ClientLifecycleState lifecycleState;
}; };
...@@ -10,8 +10,16 @@ enum ClientType { ...@@ -10,8 +10,16 @@ enum ClientType {
"all" "all"
}; };
// https://wicg.github.io/page-lifecycle/#serviceworker-clients-dfn
enum ClientLifecycleStateQuery {
"active",
"frozen",
"all"
};
// https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions // https://w3c.github.io/ServiceWorker/#dictdef-clientqueryoptions
dictionary ClientQueryOptions { dictionary ClientQueryOptions {
boolean includeUncontrolled = false; boolean includeUncontrolled = false;
[RuntimeEnabled=ServiceWorkerFilterFrozen] ClientLifecycleStateQuery lifecycleState = "active";
ClientType type = "window"; ClientType type = "window";
}; };
...@@ -32,7 +32,8 @@ ServiceWorkerClient::ServiceWorkerClient( ...@@ -32,7 +32,8 @@ ServiceWorkerClient::ServiceWorkerClient(
: uuid_(info.client_uuid), : uuid_(info.client_uuid),
url_(info.url.GetString()), url_(info.url.GetString()),
type_(info.client_type), type_(info.client_type),
frame_type_(info.frame_type) {} frame_type_(info.frame_type),
lifecycle_state_(info.lifecycle_state) {}
ServiceWorkerClient::~ServiceWorkerClient() = default; ServiceWorkerClient::~ServiceWorkerClient() = default;
...@@ -71,6 +72,18 @@ String ServiceWorkerClient::frameType(ScriptState* script_state) const { ...@@ -71,6 +72,18 @@ String ServiceWorkerClient::frameType(ScriptState* script_state) const {
return String(); 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, void ServiceWorkerClient::postMessage(ScriptState* script_state,
const ScriptValue& message, const ScriptValue& message,
Vector<ScriptValue>& transfer, Vector<ScriptValue>& transfer,
......
...@@ -33,6 +33,7 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable { ...@@ -33,6 +33,7 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable {
String type() const; String type() const;
String frameType(ScriptState*) const; String frameType(ScriptState*) const;
String id() const { return uuid_; } String id() const { return uuid_; }
String lifecycleState() const;
void postMessage(ScriptState*, void postMessage(ScriptState*,
const ScriptValue& message, const ScriptValue& message,
Vector<ScriptValue>& transfer, Vector<ScriptValue>& transfer,
...@@ -46,10 +47,11 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable { ...@@ -46,10 +47,11 @@ class MODULES_EXPORT ServiceWorkerClient : public ScriptWrappable {
String Uuid() const { return uuid_; } String Uuid() const { return uuid_; }
private: private:
String uuid_; const String uuid_;
String url_; const String url_;
mojom::ServiceWorkerClientType type_; const mojom::ServiceWorkerClientType type_;
network::mojom::RequestContextFrameType frame_type_; const network::mojom::RequestContextFrameType frame_type_;
const mojom::ServiceWorkerClientLifecycleState lifecycle_state_;
}; };
} // namespace blink } // namespace blink
......
...@@ -39,6 +39,18 @@ mojom::ServiceWorkerClientType GetClientType(const String& type) { ...@@ -39,6 +39,18 @@ mojom::ServiceWorkerClientType GetClientType(const String& type) {
return mojom::ServiceWorkerClientType::kWindow; 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, void DidGetClient(ScriptPromiseResolver* resolver,
mojom::blink::ServiceWorkerClientInfoPtr info) { mojom::blink::ServiceWorkerClientInfoPtr info) {
if (!resolver->GetExecutionContext() || if (!resolver->GetExecutionContext() ||
...@@ -137,7 +149,8 @@ ScriptPromise ServiceWorkerClients::matchAll( ...@@ -137,7 +149,8 @@ ScriptPromise ServiceWorkerClients::matchAll(
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
global_scope->GetServiceWorkerHost()->GetClients( global_scope->GetServiceWorkerHost()->GetClients(
mojom::blink::ServiceWorkerClientQueryOptions::New( mojom::blink::ServiceWorkerClientQueryOptions::New(
options->includeUncontrolled(), GetClientType(options->type())), options->includeUncontrolled(), GetClientType(options->type()),
GetLifecycleStateQueryType(options->lifecycleState())),
WTF::Bind(&DidGetClients, WrapPersistent(resolver))); WTF::Bind(&DidGetClients, WrapPersistent(resolver)));
return resolver->Promise(); return resolver->Promise();
} }
......
...@@ -1474,6 +1474,10 @@ ...@@ -1474,6 +1474,10 @@
name: "Serial", name: "Serial",
status: {"Android": "", "default": "experimental"}, status: {"Android": "", "default": "experimental"},
}, },
{
name: "ServiceWorkerFilterFrozen",
status: "experimental",
},
{ {
name: "SetRootScroller", name: "SetRootScroller",
status: "experimental", 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) { ...@@ -13,11 +13,20 @@ self.onmessage = function(e) {
// In that case, just pretend it's top-level! // In that case, just pretend it's top-level!
frame_type = 'top-level'; frame_type = 'top-level';
} }
message.push([client.visibilityState, if (e.data.includeLifecycleState) {
client.focused, message.push({visibilityState: client.visibilityState,
client.url, focused: client.focused,
client.type, url: client.url,
frame_type]); 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 // Sort by url
if (!e.data.disableSort) { if (!e.data.disableSort) {
......
...@@ -124,6 +124,7 @@ interface Client ...@@ -124,6 +124,7 @@ interface Client
attribute @@toStringTag attribute @@toStringTag
getter frameType getter frameType
getter id getter id
getter lifecycleState
getter type getter type
getter url getter url
method constructor 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