Skip to content
Permalink
Browse files
Expose the Notification API to dedicated workers
https://bugs.webkit.org/show_bug.cgi?id=245531

Reviewed by Geoffrey Garen.

Expose the Notification API to dedicated workers as per the specification:
- https://notifications.spec.whatwg.org/#api

Blink and Gecko already support this.

We're supposed to expose the API to shared workers as well but I haven't
done so in this patch to decrease patch complexity and size.

* LayoutTests/http/tests/notifications/show-notification-worker.js: Added.
(onmessage):
* LayoutTests/http/tests/notifications/worker-show-notification-expected.txt: Added.
* LayoutTests/http/tests/notifications/worker-show-notification.html: Added.
* LayoutTests/imported/w3c/web-platform-tests/notifications/historical.any.worker-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/notifications/idlharness.https.any.worker-expected.txt:
* Source/WebCore/Modules/notifications/Notification.cpp:
(WebCore::Notification::create):
(WebCore::Notification::Notification):
(WebCore::Notification::~Notification):
(WebCore::Notification::show):
(WebCore::Notification::close):
(WebCore::Notification::stop):
(WebCore::Notification::dispatchShowEvent):
(WebCore::Notification::dispatchClickEvent):
(WebCore::Notification::dispatchCloseEvent):
(WebCore::Notification::dispatchErrorEvent):
(WebCore::Notification::data const):
(WebCore::Notification::ensureOnNotificationThread):
* Source/WebCore/Modules/notifications/Notification.h:
* Source/WebCore/Modules/notifications/Notification.idl:
* Source/WebCore/Modules/notifications/NotificationClient.h:
* Source/WebCore/Modules/notifications/NotificationData.cpp:
(WebCore::NotificationData::isolatedCopy const):
(WebCore::NotificationData::isolatedCopy):
* Source/WebCore/Modules/notifications/NotificationData.h:
(WebCore::NotificationData::isPersistent const):
(WebCore::NotificationData::encode const):
(WebCore::NotificationData::decode):
* Source/WebCore/Modules/notifications/NotificationDataCocoa.mm:
(WebCore::NotificationData::fromDictionary):
(WebCore::NotificationData::dictionaryRepresentation const):
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/workers/DedicatedWorkerGlobalScope.cpp:
(WebCore::DedicatedWorkerGlobalScope::notificationClient):
* Source/WebCore/workers/DedicatedWorkerGlobalScope.h:
* Source/WebCore/workers/WorkerNotificationClient.cpp: Added.
(WebCore::WorkerNotificationClient::create):
(WebCore::WorkerNotificationClient::WorkerNotificationClient):
(WebCore::WorkerNotificationClient::show):
(WebCore::WorkerNotificationClient::cancel):
(WebCore::WorkerNotificationClient::notificationObjectDestroyed):
(WebCore::WorkerNotificationClient::notificationControllerDestroyed):
(WebCore::WorkerNotificationClient::requestPermission):
(WebCore::WorkerNotificationClient::checkPermission):
(WebCore::WorkerNotificationClient::postToMainThread):
(WebCore::WorkerNotificationClient::postToWorkerThread):
* Source/WebCore/workers/WorkerNotificationClient.h: Copied from Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.h.
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.cpp:
(WebKit::sendMessage):
(WebKit::WebNotificationManager::sendNotificationMessage):
(WebKit::WebNotificationManager::sendNotificationMessageWithAsyncReply):
(WebKit::WebNotificationManager::show):
(WebKit::WebNotificationManager::cancel):
(WebKit::WebNotificationManager::didDestroyNotification):
(WebKit::WebNotificationManager::didShowNotification):
(WebKit::WebNotificationManager::didClickNotification):
(WebKit::WebNotificationManager::didCloseNotifications):
* Source/WebKit/WebProcess/Notifications/WebNotificationManager.h:
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.cpp:
(WebKit::WebNotificationClient::show):
(WebKit::WebNotificationClient::cancel):
(WebKit::WebNotificationClient::notificationObjectDestroyed):
* Source/WebKit/WebProcess/WebCoreSupport/WebNotificationClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebNotificationClient.h:
* Source/WebKitLegacy/mac/WebCoreSupport/WebNotificationClient.mm:
(WebNotificationClient::show):
(WebNotificationClient::cancel):
(WebNotificationClient::notificationObjectDestroyed):
(generateNotificationID): Deleted.
(WebNotificationClient::notificationIDForTesting): Deleted.
* Source/WebKitLegacy/mac/WebCoreSupport/WebSecurityOrigin.mm:
(-[WebSecurityOrigin _initWithString:]):
* Source/WebKitLegacy/mac/WebCoreSupport/WebSecurityOriginInternal.h:
* Source/WebKitLegacy/mac/WebView/WebNotification.h:
* Source/WebKitLegacy/mac/WebView/WebNotification.mm:
(-[WebNotification initWithCoreNotification:]):
(-[WebNotification title]):
(-[WebNotification body]):
(-[WebNotification tag]):
(-[WebNotification iconURL]):
(-[WebNotification lang]):
(-[WebNotification dir]):
(-[WebNotification origin]):
(-[WebNotification notificationID]):
(-[WebNotification dispatchShowEvent]):
(-[WebNotification dispatchCloseEvent]):
(-[WebNotification dispatchClickEvent]):
(-[WebNotification dispatchErrorEvent]):
(-[WebNotification finalize]):
(core): Deleted.
(-[WebNotification initWithCoreNotification:notificationID:]): Deleted.
* Source/WebKitLegacy/mac/WebView/WebNotificationInternal.h:
* Source/WebKitLegacy/mac/WebView/WebView.mm:
(-[WebView _notificationDidShow:]):
(-[WebView _notificationDidClick:]):
(-[WebView _notificationIDForTesting:]):
* Source/WebKitLegacy/mac/WebView/WebViewPrivate.h:
* Tools/DumpRenderTree/mac/MockWebNotificationProvider.h:
* Tools/DumpRenderTree/mac/MockWebNotificationProvider.mm:
(-[MockWebNotificationProvider showNotification:fromWebView:]):
(-[MockWebNotificationProvider cancelNotification:]):
(-[MockWebNotificationProvider notificationDestroyed:]):
(-[MockWebNotificationProvider clearNotifications:]):
(-[MockWebNotificationProvider webView:didShowNotification:]):
(-[MockWebNotificationProvider webView:didClickNotification:]):
(-[MockWebNotificationProvider webView:didCloseNotifications:]):
(-[MockWebNotificationProvider simulateWebNotificationClick:]):
* Tools/DumpRenderTree/mac/TestRunnerMac.mm:
(TestRunner::simulateWebNotificationClick):

Canonical link: https://commits.webkit.org/254805@main
  • Loading branch information
cdumez committed Sep 23, 2022
1 parent 676cef4 commit aab63a24c2e8666462d8ceb81083c54a102964bc
Show file tree
Hide file tree
Showing 35 changed files with 642 additions and 270 deletions.
@@ -0,0 +1,20 @@
onmessage = (e) => {
if (e.data == "check-initial-permission" || e.data == "check-permission-granted") {
self.postMessage(Notification.permission);
return;
}
if (e.data == "show-notification") {
notification = new Notification("foo");
timeoutHandle = setTimeout(() => {
self.postMessage("timeout");
}, 10000);
notification.onshow = (e) => {
clearTimeout(timeoutHandle);
self.postMessage("shown");
};
notification.onerror = (e) => {
clearTimeout(timeoutHandle);
self.postMessage("error");
};
}
};
@@ -0,0 +1,12 @@
Tests checking notification permission and constructing a notification from a dedicated worker.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS initialPermissionState is "default"
PASS updatedPermissionState is "granted"
PASS showResult is "shown"
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<body>
<script src="/js-test-resources/js-test.js"></script>
<script>
description("Tests checking notification permission and constructing a notification from a dedicated worker.");
jsTestIsAsync = true;

let state = "check-initial-permission";
let worker = new Worker("show-notification-worker.js");
worker.onmessage = (event) => {
if (state == "check-initial-permission") {
// Permission should initially be default.
initialPermissionState = event.data;
shouldBeEqualToString("initialPermissionState", "default");

if (window.testRunner)
testRunner.grantWebNotificationPermission(self.origin);

state = "check-permission-granted";
worker.postMessage(state);
return;
}
if (state == "check-permission-granted") {
// Permission should now be granted.
updatedPermissionState = event.data;
shouldBeEqualToString("updatedPermissionState", "granted");

state = "show-notification"
worker.postMessage(state);
return;
}
if (state == "show-notification") {
showResult = event.data;
shouldBeEqualToString("showResult", "shown");
finishJSTest();
return;
}
};
worker.postMessage(state);
</script>
</body>
</html>
@@ -1,3 +1,3 @@

FAIL Notification.get is obsolete Can't find variable: Notification
PASS Notification.get is obsolete

Large diffs are not rendered by default.

@@ -1,12 +1,4 @@

FAIL WebAssembly.Module cloning via the Notifications API's data member: basic case assert_throws_dom: function "() => {
new Notification("Bob: Hi", { data: createEmptyWasmModule() });
}" threw object "ReferenceError: Can't find variable: Notification" that is not a DOMException DataCloneError: property "code" is equal to undefined, expected 25
FAIL WebAssembly.Module cloning via the Notifications API's data member: is interleaved correctly assert_throws_dom: function "() => {
new Notification("Bob: Hi", { data: [
{ get x() { getter1Called = true; return 5; } },
createEmptyWasmModule(),
{ get x() { getter2Called = true; return 5; } }
]});
}" threw object "ReferenceError: Can't find variable: Notification" that is not a DOMException DataCloneError: property "code" is equal to undefined, expected 25
PASS WebAssembly.Module cloning via the Notifications API's data member: basic case
PASS WebAssembly.Module cloning via the Notifications API's data member: is interleaved correctly

@@ -36,6 +36,7 @@
#include "Notification.h"

#include "DOMWindow.h"
#include "DedicatedWorkerGlobalScope.h"
#include "Event.h"
#include "EventNames.h"
#include "FrameDestructionObserverInlines.h"
@@ -57,6 +58,23 @@ namespace WebCore {

WTF_MAKE_ISO_ALLOCATED_IMPL(Notification);

static Lock nonPersistentNotificationMapLock;
static HashMap<UUID, Notification*>& nonPersistentNotificationMap() WTF_REQUIRES_LOCK(nonPersistentNotificationMapLock)
{
static NeverDestroyed<HashMap<UUID, Notification*>> map;
return map;
}

static void addNotificationToMapIfNecessary(Notification& notification)
{
if (notification.isPersistent())
return;

Locker locker { nonPersistentNotificationMapLock };
ASSERT(!nonPersistentNotificationMap().contains(notification.identifier()));
nonPersistentNotificationMap().add(notification.identifier(), &notification);
}

static ExceptionOr<Ref<SerializedScriptValue>> createSerializedScriptValue(ScriptExecutionContext& context, JSC::JSValue value)
{
auto globalObject = context.globalObject();
@@ -78,6 +96,7 @@ ExceptionOr<Ref<Notification>> Notification::create(ScriptExecutionContext& cont

auto notification = adoptRef(*new Notification(context, UUID::createVersion4(), WTFMove(title), WTFMove(options), dataResult.releaseReturnValue()));
notification->suspendIfNeeded();
addNotificationToMapIfNecessary(notification);
notification->showSoon();
return notification;
}
@@ -91,6 +110,7 @@ ExceptionOr<Ref<Notification>> Notification::createForServiceWorker(ScriptExecut
auto notification = adoptRef(*new Notification(context, UUID::createVersion4(), WTFMove(title), WTFMove(options), dataResult.releaseReturnValue()));
notification->m_serviceWorkerRegistrationURL = serviceWorkerRegistrationURL;
notification->suspendIfNeeded();
addNotificationToMapIfNecessary(notification);
return notification;
}

@@ -101,6 +121,7 @@ Ref<Notification> Notification::create(ScriptExecutionContext& context, Notifica
auto notification = adoptRef(*new Notification(context, data.notificationID, WTFMove(data.title), WTFMove(options), SerializedScriptValue::createFromWireBytes(WTFMove(data.data))));
notification->suspendIfNeeded();
notification->m_serviceWorkerRegistrationURL = WTFMove(data.serviceWorkerRegistrationURL);
addNotificationToMapIfNecessary(notification);
return notification;
}

@@ -114,12 +135,13 @@ Notification::Notification(ScriptExecutionContext& context, UUID identifier, Str
, m_tag(WTFMove(options.tag).isolatedCopy())
, m_dataForBindings(WTFMove(dataForBindings))
, m_state(Idle)
, m_contextIdentifier(context.identifier())
{
if (context.isDocument())
m_notificationSource = NotificationSource::Document;
else if (context.isServiceWorkerGlobalScope())
m_notificationSource = NotificationSource::ServiceWorker;
else if (is<DedicatedWorkerGlobalScope>(context))
m_notificationSource = NotificationSource::DedicatedWorker;
else
RELEASE_ASSERT_NOT_REACHED();

@@ -132,6 +154,12 @@ Notification::Notification(ScriptExecutionContext& context, UUID identifier, Str

Notification::~Notification()
{
if (!isPersistent()) {
Locker locker { nonPersistentNotificationMapLock };
ASSERT(nonPersistentNotificationMap().contains(identifier()));
nonPersistentNotificationMap().remove(identifier());
}

stopResourcesLoader();
}

@@ -174,6 +202,7 @@ void Notification::show(CompletionHandler<void()>&& callback)

if (client->checkPermission(context) != Permission::Granted) {
switch (m_notificationSource) {
case NotificationSource::DedicatedWorker:
case NotificationSource::Document:
dispatchErrorEvent();
break;
@@ -191,8 +220,10 @@ void Notification::show(CompletionHandler<void()>&& callback)
m_resourcesLoader->start([this, client, callback = scope.release()](RefPtr<NotificationResources>&& resources) mutable {
CompletionHandlerCallingScope scope { WTFMove(callback) };

auto* context = scriptExecutionContext();

m_resources = WTFMove(resources);
if (m_state == Idle && client->show(*this, scope.release()))
if (m_state == Idle && context && client->show(*context, data(), this->resources(), scope.release()))
m_state = Showing;
m_resourcesLoader = nullptr;
});
@@ -206,7 +237,7 @@ void Notification::close()
break;
case Showing:
if (auto* client = clientFromContext())
client->cancel(*this);
client->cancel(data());
break;
case Closed:
break;
@@ -235,7 +266,7 @@ void Notification::stop()
return;

if (auto* client = clientFromContext())
client->notificationObjectDestroyed(*this);
client->notificationObjectDestroyed(data());
}

void Notification::suspend(ReasonForSuspension)
@@ -252,19 +283,27 @@ void Notification::finalize()

void Notification::dispatchShowEvent()
{
ASSERT(isMainThread());
auto* context = scriptExecutionContext();
if (!context)
return;

ASSERT(context->isContextThread());
ASSERT(!isPersistent());

if (m_notificationSource != NotificationSource::Document)
if (m_notificationSource == NotificationSource::ServiceWorker)
return;

queueTaskToDispatchEvent(*this, TaskSource::UserInteraction, Event::create(eventNames().showEvent, Event::CanBubble::No, Event::IsCancelable::No));
}

void Notification::dispatchClickEvent()
{
ASSERT(isMainThread());
ASSERT(m_notificationSource == NotificationSource::Document);
auto* context = scriptExecutionContext();
if (!context)
return;

ASSERT(context->isContextThread());
ASSERT(m_notificationSource != NotificationSource::ServiceWorker);
ASSERT(!isPersistent());

queueTaskKeepingObjectAlive(*this, TaskSource::UserInteraction, [this] {
@@ -275,8 +314,12 @@ void Notification::dispatchClickEvent()

void Notification::dispatchCloseEvent()
{
ASSERT(isMainThread());
ASSERT(m_notificationSource == NotificationSource::Document);
auto* context = scriptExecutionContext();
if (!context)
return;

ASSERT(context->isContextThread());
ASSERT(m_notificationSource != NotificationSource::ServiceWorker);
ASSERT(!isPersistent());

queueTaskToDispatchEvent(*this, TaskSource::UserInteraction, Event::create(eventNames().closeEvent, Event::CanBubble::No, Event::IsCancelable::No));
@@ -285,8 +328,12 @@ void Notification::dispatchCloseEvent()

void Notification::dispatchErrorEvent()
{
ASSERT(isMainThread());
ASSERT(m_notificationSource == NotificationSource::Document);
auto* context = scriptExecutionContext();
if (!context)
return;

ASSERT(context->isContextThread());
ASSERT(m_notificationSource != NotificationSource::ServiceWorker);
ASSERT(!isPersistent());

queueTaskToDispatchEvent(*this, TaskSource::UserInteraction, Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
@@ -360,21 +407,39 @@ NotificationData Notification::data() const
RELEASE_ASSERT(sessionID);

return {
m_title.isolatedCopy(),
m_body.isolatedCopy(),
m_icon.string().isolatedCopy(),
m_title,
m_body,
m_icon.string(),
m_tag,
m_lang,
m_direction,
scriptExecutionContext()->securityOrigin()->toString().isolatedCopy(),
m_serviceWorkerRegistrationURL.isolatedCopy(),
scriptExecutionContext()->securityOrigin()->toString(),
m_serviceWorkerRegistrationURL,
identifier(),
context.identifier(),
*sessionID,
MonotonicTime::now(),
m_dataForBindings->wireBytes()
};
}

void Notification::ensureOnNotificationThread(ScriptExecutionContextIdentifier contextIdentifier, UUID notificationIdentifier, Function<void(Notification*)>&& task)
{
ScriptExecutionContext::ensureOnContextThread(contextIdentifier, [notificationIdentifier = notificationIdentifier, task = WTFMove(task)](auto&) mutable {
RefPtr<Notification> notification;
{
Locker locker { nonPersistentNotificationMapLock };
notification = nonPersistentNotificationMap().get(notificationIdentifier);
}
task(notification.get());
});
}

void Notification::ensureOnNotificationThread(const NotificationData& notification, Function<void(Notification*)>&& task)
{
ensureOnNotificationThread(notification.contextIdentifier, notification.notificationID, WTFMove(task));
}

} // namespace WebCore

#endif // ENABLE(NOTIFICATIONS)
@@ -55,7 +55,7 @@ class NotificationResourcesLoader;

struct NotificationData;

class Notification final : public ThreadSafeRefCounted<Notification>, public ActiveDOMObject, public EventTarget {
class Notification final : public RefCounted<Notification>, public ActiveDOMObject, public EventTarget {
WTF_MAKE_ISO_ALLOCATED_EXPORT(Notification, WEBCORE_EXPORT);
public:
using Permission = NotificationPermission;
@@ -105,8 +105,8 @@ class Notification final : public ThreadSafeRefCounted<Notification>, public Act
WEBCORE_EXPORT NotificationData data() const;
RefPtr<NotificationResources> resources() const { return m_resources; }

using ThreadSafeRefCounted::ref;
using ThreadSafeRefCounted::deref;
using RefCounted::ref;
using RefCounted::deref;

void markAsShown();
void showSoon();
@@ -115,6 +115,9 @@ class Notification final : public ThreadSafeRefCounted<Notification>, public Act

bool isPersistent() const { return !m_serviceWorkerRegistrationURL.isNull(); }

WEBCORE_EXPORT static void ensureOnNotificationThread(ScriptExecutionContextIdentifier, UUID notificationIdentifier, Function<void(Notification*)>&&);
WEBCORE_EXPORT static void ensureOnNotificationThread(const NotificationData&, Function<void(Notification*)>&&);

private:
Notification(ScriptExecutionContext&, UUID, String&& title, Options&&, Ref<SerializedScriptValue>&&);

@@ -148,12 +151,12 @@ class Notification final : public ThreadSafeRefCounted<Notification>, public Act
State m_state { Idle };
bool m_hasRelevantEventListener { false };

enum class NotificationSource : bool {
enum class NotificationSource : uint8_t {
DedicatedWorker,
Document,
ServiceWorker,
};
NotificationSource m_notificationSource;
ScriptExecutionContextIdentifier m_contextIdentifier;
URL m_serviceWorkerRegistrationURL;
std::unique_ptr<NotificationResourcesLoader> m_resourcesLoader;
RefPtr<NotificationResources> m_resources;
@@ -29,14 +29,14 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// FIXME: Notification should be exposed to all Worker contexts, not just ServiceWorker.
// FIXME: Notification should be exposed to all Worker contexts, not just ServiceWorker and DedicatedWorker.
// https://notifications.spec.whatwg.org/#notification
[
Conditional=NOTIFICATIONS,
ActiveDOMObject,
EnabledBySetting=NotificationsEnabled,
ExportMacro=WEBCORE_EXPORT,
Exposed=(Window,ServiceWorker)
Exposed=(Window,DedicatedWorker,ServiceWorker)
] interface Notification : EventTarget {
[CallWith=CurrentScriptExecutionContext] constructor(DOMString title, optional NotificationOptions options);

0 comments on commit aab63a2

Please sign in to comment.