Skip to content
Permalink
Browse files
Add support for Notification objects with custom data
https://bugs.webkit.org/show_bug.cgi?id=240153

Reviewed by Chris Dumez.

This adds support for the data attribute in Notification. This holds an arbitrary structured
cloneable object passed through the constructor. The accessor is marked SameObject, which we
implement via CachedAttribute. The wire form of the data is serialized and deserialized in
NotificationData so that persistent notifications properly support this property.

* Modules/notifications/Notification.cpp:
(WebCore::createSerializedNotificationData):
(WebCore::Notification::create):
(WebCore::Notification::createForServiceWorker):
(WebCore::Notification::Notification):
(WebCore::Notification::dataForBindings):
* Modules/notifications/Notification.h:
* Modules/notifications/Notification.idl:
* Modules/notifications/NotificationData.cpp:
(WebCore::NotificationData::isolatedCopy const):
(WebCore::NotificationData::isolatedCopy):
* Modules/notifications/NotificationData.h:
(WebCore::NotificationData::encode const):
(WebCore::NotificationData::decode):
* Modules/notifications/NotificationDataCocoa.mm:
(WebCore::NotificationData::fromDictionary):
(WebCore::NotificationData::dictionaryRepresentation const):
* Modules/notifications/NotificationOptions.idl:
* workers/service/ServiceWorkerRegistration.cpp:
(WebCore::ServiceWorkerRegistration::showNotification):

LayoutTests/imported/w3c:

* web-platform-tests/notifications/idlharness.https.any-expected.txt:
* web-platform-tests/notifications/idlharness.https.any.serviceworker-expected.txt:
* web-platform-tests/wasm/serialization/module/serialization-via-notifications-api.any-expected.txt:

LayoutTests:

* http/tests/notifications/notification-expected.txt:
* http/tests/notifications/notification.html:
* http/tests/workers/service/getnotifications-expected.txt:
* http/tests/workers/service/getnotifications-stop-expected.txt:
* http/tests/workers/service/getnotifications-stop.html:
* http/tests/workers/service/getnotifications.html:
* http/tests/workers/service/openwindow-from-notification-click.html:
* http/tests/workers/service/resources/shownotification-openwindow-worker.js:
(async tryShow):
(async getNotes):
* http/tests/workers/service/resources/shownotification-worker.js:
(async event):
(async tryShow):
(async tryShowInvalidData.try.data):
(async tryShowInvalidData):
(async getNotes):
* http/tests/workers/service/shownotification-allowed-document-expected.txt:
* http/tests/workers/service/shownotification-allowed-document.html:
* http/tests/workers/service/shownotification-allowed.html:
* http/tests/workers/service/shownotification-invalid-data-expected.txt: Added.
* http/tests/workers/service/shownotification-invalid-data.html:

Canonical link: https://commits.webkit.org/250368@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@293921 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
bnham committed May 6, 2022
1 parent 715fd77 commit f10a012babd59d5d9b17d02cb1e50355bb566fcd
Showing 28 changed files with 308 additions and 63 deletions.
@@ -1,3 +1,35 @@
2022-05-05 Ben Nham <nham@apple.com>

Add support for Notification objects with custom data
https://bugs.webkit.org/show_bug.cgi?id=240153

Reviewed by Chris Dumez.

Test that Notification objects with custom data can be created and shown by both documents
and service workers.

* http/tests/notifications/notification-expected.txt:
* http/tests/notifications/notification.html:
* http/tests/workers/service/getnotifications-expected.txt:
* http/tests/workers/service/getnotifications-stop-expected.txt:
* http/tests/workers/service/getnotifications-stop.html:
* http/tests/workers/service/getnotifications.html:
* http/tests/workers/service/openwindow-from-notification-click.html:
* http/tests/workers/service/resources/shownotification-openwindow-worker.js:
(async tryShow):
(async getNotes):
* http/tests/workers/service/resources/shownotification-worker.js:
(async event):
(async tryShow):
(async tryShowInvalidData.try.data):
(async tryShowInvalidData):
(async getNotes):
* http/tests/workers/service/shownotification-allowed-document-expected.txt:
* http/tests/workers/service/shownotification-allowed-document.html:
* http/tests/workers/service/shownotification-allowed.html:
* http/tests/workers/service/shownotification-invalid-data-expected.txt: Added.
* http/tests/workers/service/shownotification-invalid-data.html: Added.

2022-05-06 Brent Fulgham <bfulgham@apple.com>

Remove the viewportFitEnabled WKPreference now that it is always on
@@ -2,5 +2,7 @@
PASS The Notification constructor requires at least one argument.
PASS The Notification object initializes its properties to their default values from the NotificationOptions dictionary if it is not provided.
PASS The Notification object initializes its properties to the values from NotificationOptions dictionary if it is provided.
PASS The Notification constructor should throw if it is passed a non-cloneable data option.
PASS The NotificationOptions dictionary only accepts valid NotificationDirection values.
PASS The Notification data property returns the same object after GC.

@@ -2,6 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<script src="/js-test-resources/gc.js"></script>
<script src="/js-test-resources/testharness.js"></script>
<script src="/js-test-resources/testharnessreport.js"></script>
</head>
@@ -24,6 +25,7 @@
assert_equals(notification.body, "");
assert_equals(notification.tag, "");
assert_equals(notification.icon, "");
assert_equals(notification.data, null);
}, "The Notification object initializes its properties to their default values from the NotificationOptions dictionary if it is not provided.");

test(function() {
@@ -32,7 +34,8 @@
lang: "en",
body: "test body",
tag: "test tag",
icon: "foo.png"
icon: "foo.png",
data: {foo: 'bar'}
});

assert_equals(notification.title, "test title");
@@ -41,8 +44,13 @@
assert_equals(notification.body, "test body");
assert_equals(notification.tag, "test tag");
assert_equals(notification.icon, "http://127.0.0.1:8000/notifications/foo.png");
assert_equals(JSON.stringify(notification.data), '{"foo":"bar"}')
}, "The Notification object initializes its properties to the values from NotificationOptions dictionary if it is provided.");

test(function() {
assert_throws_dom('DataCloneError', function() { new Notification("title", { data: function() { } }); });
}, "The Notification constructor should throw if it is passed a non-cloneable data option.");

test(function() {
let notification1 = new Notification("test title", { dir: "auto" });
assert_equals(notification1.dir, "auto");
@@ -56,6 +64,17 @@
assert_throws_js(TypeError, function() { new Notification("test title", { dir: "foo" }) });
}, "The NotificationOptions dictionary only accepts valid NotificationDirection values.");

promise_test(async function() {
let notification = new Notification("test title", { data: { foo: 'bar' } });
let data = notification.data;
assert_equals(JSON.stringify(data), '{"foo":"bar"}')
gc();
await new Promise(resolve => setTimeout(resolve, 10));
gc();
await new Promise(resolve => setTimeout(resolve, 10));
assert_equals(data, notification.data);
}, "The Notification data property returns the same object after GC.");

</script>
</body>
</html>
@@ -15,32 +15,38 @@ There are 3 notifications
Title: Hello
Body: Body1
Tag: tag-a
Data: null
Title: There
Body: Body2
Tag: tag-b
Data: null
Title: Buddy
Body: Body3
Tag: tag-b
Data: Data3

Got notifications
There are 1 notifications
Title: Hello
Body: Body1
Tag: tag-a
Data: null

Got notifications
There are 2 notifications
Title: There
Body: Body2
Tag: tag-b
Data: null
Title: Buddy
Body: Body3
Tag: tag-b
Data: Data3

Retrieving notifications from page registration object - 3
Notification: Hello / Body1 / tag-a
Notification: There / Body2 / tag-b
Notification: Buddy / Body3 / tag-b
Notification: Hello / Body1 / tag-a / null
Notification: There / Body2 / tag-b / null
Notification: Buddy / Body3 / tag-b / Data3
Retrieving notifications from page registration object - end
PASS successfullyParsed is true

@@ -15,35 +15,41 @@ There are 3 notifications
Title: Hello
Body: Body1
Tag: tag-a
Data: null
Title: There
Body: Body2
Tag: tag-b
Data: null
Title: Buddy
Body: Body3
Tag: tag-b
Data: Data3

Got notifications
There are 1 notifications
Title: Hello
Body: Body1
Tag: tag-a
Data: null

Got notifications
There are 2 notifications
Title: There
Body: Body2
Tag: tag-b
Data: null
Title: Buddy
Body: Body3
Tag: tag-b
Data: Data3

Loading iframe
Get notifications from iframe
Remove iframes to stop notifications
Retrieving notifications from page registration object - 3
Notification: Hello / Body1 / tag-a
Notification: There / Body2 / tag-b
Notification: Buddy / Body3 / tag-b
Notification: Hello / Body1 / tag-a / null
Notification: There / Body2 / tag-b / null
Notification: Buddy / Body3 / tag-b / Data3
Retrieving notifications from page registration object - end
PASS successfullyParsed is true

@@ -66,7 +66,7 @@
if (numberGot == 1) {
event.source.postMessage("tryshow|Hello|Body1|tag-a");
event.source.postMessage("tryshow|There|Body2|tag-b");
event.source.postMessage("tryshow|Buddy|Body3|tag-b");
event.source.postMessage("tryshow|Buddy|Body3|tag-b|Data3");
} else if (numberGot == 4) {
debug("Loading iframe");
const frame = await with_iframe("resources/getNotifications-iframe.html");
@@ -79,7 +79,7 @@

debug("Retrieving notifications from page registration object - " + notifications.length);
notifications.forEach(notification => {
debug("Notification: " + notification.title + " / " + notification.body + " / " + notification.tag);
debug("Notification: " + notification.title + " / " + notification.body + " / " + notification.tag + " / " + notification.data);
});
debug("Retrieving notifications from page registration object - end");

@@ -56,12 +56,12 @@
if (numberGot == 1) {
event.source.postMessage("tryshow|Hello|Body1|tag-a");
event.source.postMessage("tryshow|There|Body2|tag-b");
event.source.postMessage("tryshow|Buddy|Body3|tag-b");
event.source.postMessage("tryshow|Buddy|Body3|tag-b|Data3");
} else if (numberGot == 4) {
const notifications = await registration.getNotifications({ tag: "" });
debug("Retrieving notifications from page registration object - " + notifications.length);
notifications.forEach(notification => {
debug("Notification: " + notification.title + " / " + notification.body + " / " + notification.tag);
debug("Notification: " + notification.title + " / " + notification.body + " / " + notification.tag + " / " + notification.data);
});
debug("Retrieving notifications from page registration object - end");

@@ -37,7 +37,7 @@

installingWorker.addEventListener("statechange", () => {
if (installingWorker.state === "activated") {
installingWorker.postMessage("tryshow");
installingWorker.postMessage("tryshow|title|body|tag|foobar");
}
});
}
@@ -55,7 +55,7 @@
} else if (event.data == "shown") {
if (testRunner)
testRunner.simulateWebNotificationClickForServiceWorkerNotifications();
} else if (event.data == "clicked") {
} else if (event.data == "clicked|data:foobar") {
shouldBeFalse("gotClicked");
shouldBeFalse("gotClosed");
gotClicked = true;
@@ -17,7 +17,7 @@ clients.openWindow('http://127.0.0.1:8000/workers/service/resources/openwindow-c
});

self.addEventListener('notificationclick', async function(event) {
await messageClients("clicked");
await messageClients("clicked|data:" + event.notification.data);
event.notification.close();

// Should fail because about:blank is not an allowable URL
@@ -49,21 +49,22 @@ self.addEventListener('notificationclose', async function(event) {

async function tryShow(message)
{
var title, body, tag;
var command, title, body, tag, data;
var components = message.split('|');

if (components.length == 1) {
title = "This is a notification";
} else {
title = components[1];
body = components[2];
tag = components[3];
} else if (components.length == 4) {
[command, title, body, tag] = components;
} else if (components.length == 5) {
[command, title, body, tag, data] = components;
}

try {
await registration.showNotification(title, {
body: body,
tag: tag
tag: tag,
data: data
});
} catch(error) {
await messageClients("showFailed");
@@ -93,6 +94,7 @@ async function getNotes(message)
reply += "Title: " + notification.title + "|";
reply += "Body: " + notification.body + "|";
reply += "Tag: " + notification.tag + "|";
reply += "Data: " + notification.data + "|";
}
await messageClients(reply);
}
@@ -9,7 +9,7 @@ let messageClients = async function(msg) {
}

self.addEventListener('notificationclick', async function(event) {
await messageClients("clicked");
await messageClients("clicked|data:" + event.notification.data);
event.notification.close();
});

@@ -19,15 +19,15 @@ self.addEventListener('notificationclose', async function(event) {

async function tryShow(message)
{
var title, body, tag;
var command, title, body, tag, data;
var components = message.split('|');

if (components.length == 1) {
title = "This is a notification";
} else {
title = components[1];
body = components[2];
tag = components[3];
} else if (components.length == 4) {
[command, title, body, tag] = components;
} else if (components.length == 5) {
[command, title, body, tag, data] = components;
}

if (!self.Notification) {
@@ -37,7 +37,8 @@ async function tryShow(message)
try {
new Notification(title, {
body: body,
tag: tag
tag: tag,
data: data
});
await messageClients("showFailed due to Notification created from constructor");
return;
@@ -51,7 +52,8 @@ async function tryShow(message)
try {
await registration.showNotification(title, {
body: body,
tag: tag
tag: tag,
data: data
});
} catch(error) {
await messageClients("showFailed");
@@ -61,6 +63,21 @@ async function tryShow(message)
await messageClients("shown");
}

async function tryShowInvalidData()
{
let error = null;
try {
await registration.showNotification("Invalid notification", { data: function() { } });
} catch (e) {
error = e;
}

if (error)
await messageClients("showFailed: threw " + error.name);
else if (error0)
await messageClients("shown");
}

var seenNotes = new Set();

async function getNotes(message)
@@ -81,6 +98,7 @@ async function getNotes(message)
reply += "Title: " + notification.title + "|";
reply += "Body: " + notification.body + "|";
reply += "Tag: " + notification.tag + "|";
reply += "Data: " + notification.data + "|";
}
await messageClients(reply);
}
@@ -89,6 +107,8 @@ self.addEventListener('message', async function(event) {
var messageName = event.data.split('|')[0];
if (messageName == "tryshow")
await tryShow(event.data);
if (messageName == "tryshowinvaliddata")
await tryShowInvalidData();
if (messageName == "getnotes")
await getNotes(event.data);
});
@@ -4,6 +4,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE


PASS Notification.permission is "granted"
PASS threwDataCloneError is true
PASS gotClicked is false
PASS gotClosed is false
PASS gotClicked is true

0 comments on commit f10a012

Please sign in to comment.