From 40af10ec0bc4c6f66b0a362fdf296829928ae28d Mon Sep 17 00:00:00 2001 From: Nikhil Marathe Date: Thu, 25 Jun 2015 18:50:25 -0700 Subject: [PATCH] Bug 1114554 - Patch 3 - Fire notificationclick event on ServiceWorkerGlobalScope. r=wchen,baku Bug 1114554 - Patch 3.1 - ServiceWorker principal fixes. r=baku Bug 1162088 introduced origin attributes that ServiceWorkerManager callers have to use. This patch updates notificationclick events to work. Folded: Hide NotificationEvent behind pref --- .../base/nsIServiceWorkerManager.idl | 13 +- .../notification/nsINotificationStorage.idl | 16 +- dom/notification/Notification.cpp | 256 ++++++++++++++++-- dom/notification/Notification.h | 39 +++ dom/notification/NotificationEvent.cpp | 26 ++ dom/notification/NotificationEvent.h | 74 +++++ dom/notification/NotificationStorage.js | 23 +- dom/notification/moz.build | 2 + .../mochitest/notification/MockServices.js | 5 +- dom/webidl/NotificationEvent.webidl | 26 ++ dom/webidl/moz.build | 1 + dom/workers/ServiceWorkerManager.cpp | 112 ++++++++ dom/workers/test/serviceworkers/mochitest.ini | 3 + .../serviceworkers/notificationclick.html | 27 ++ .../test/serviceworkers/notificationclick.js | 15 + .../test_notificationclick.html | 56 ++++ .../test_serviceworker_interfaces.js | 2 + dom/workers/test/test_notification.html | 2 +- modules/libpref/init/all.js | 2 +- 19 files changed, 675 insertions(+), 25 deletions(-) create mode 100644 dom/notification/NotificationEvent.cpp create mode 100644 dom/notification/NotificationEvent.h create mode 100644 dom/webidl/NotificationEvent.webidl create mode 100644 dom/workers/test/serviceworkers/notificationclick.html create mode 100644 dom/workers/test/serviceworkers/notificationclick.js create mode 100644 dom/workers/test/serviceworkers/test_notificationclick.html diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl index aa6a1c0afd2..3cd33f84654 100644 --- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -33,7 +33,7 @@ interface nsIServiceWorkerInfo : nsISupports readonly attribute DOMString waitingCacheName; }; -[scriptable, builtinclass, uuid(e9abb123-0099-4d9e-85db-c8cd0aff19e6)] +[scriptable, builtinclass, uuid(ed1cbbf2-0400-4caa-8eb2-b09d21a94e20)] interface nsIServiceWorkerManager : nsISupports { /** @@ -126,6 +126,17 @@ interface nsIServiceWorkerManager : nsISupports in nsIServiceWorkerUnregisterCallback aCallback, in DOMString aScope); + void sendNotificationClickEvent(in ACString aOriginSuffix, + in ACString scope, + in AString aID, + in AString aTitle, + in AString aDir, + in AString aLang, + in AString aBody, + in AString aTag, + in AString aIcon, + in AString aData, + in AString aBehavior); void sendPushEvent(in ACString aOriginAttributes, in ACString aScope, in DOMString aData); diff --git a/dom/interfaces/notification/nsINotificationStorage.idl b/dom/interfaces/notification/nsINotificationStorage.idl index 2362bfbd58b..d8338f8eeb0 100644 --- a/dom/interfaces/notification/nsINotificationStorage.idl +++ b/dom/interfaces/notification/nsINotificationStorage.idl @@ -41,7 +41,7 @@ interface nsINotificationStorageCallback : nsISupports /** * Interface for notification persistence layer. */ -[scriptable, uuid(cac01fb0-c2eb-4252-b2f4-5b1fac933bd4)] +[scriptable, uuid(2f8f84b7-70b5-4673-98d8-fd3f9f8e0e5c)] interface nsINotificationStorage : nsISupports { @@ -86,6 +86,20 @@ interface nsINotificationStorage : nsISupports in DOMString tag, in nsINotificationStorageCallback aCallback); + /** + * Retrieve a notification by ID. + * + * @param origin: the origin/app for which to fetch notifications. + * @param id: the id of the notification. + * @param callback: nsINotificationStorageCallback whose Handle method will + * be called *at most once* if the notification with that ID is found. Not + * called if that ID is not found. Done() will be called right after + * Handle(). + */ + void getByID(in DOMString origin, + in DOMString id, + in nsINotificationStorageCallback aCallback); + /** * Remove a notification from storage. * diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index d52bb4dc815..a04e0491463 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -19,6 +19,7 @@ #include "nsIDocument.h" #include "nsINotificationStorage.h" #include "nsIPermissionManager.h" +#include "nsIServiceWorkerManager.h" #include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" #include "nsStructuredCloneContainer.h" @@ -29,8 +30,9 @@ #include "nsNetUtil.h" #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" -#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" +#include "mozilla/dom/NotificationEvent.h" +#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/Services.h" #include "nsContentPermissionHelper.h" #include "nsILoadContext.h" @@ -41,6 +43,7 @@ #include "ServiceWorkerManager.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" +#include "WorkerScope.h" namespace mozilla { namespace dom { @@ -735,6 +738,48 @@ Notification::Constructor(const GlobalObject& aGlobal, return notification.forget(); } +// static +already_AddRefed +Notification::ConstructFromFields( + nsIGlobalObject* aGlobal, + const nsAString& aID, + const nsAString& aTitle, + const nsAString& aDir, + const nsAString& aLang, + const nsAString& aBody, + const nsAString& aTag, + const nsAString& aIcon, + const nsAString& aData, + ErrorResult& aRv) +{ + MOZ_ASSERT(aGlobal); + + AutoJSAPI jsapi; + DebugOnly ok = jsapi.Init(aGlobal); + MOZ_ASSERT(ok); + + RootedDictionary options(jsapi.cx()); + options.mDir = Notification::StringToDirection(nsString(aDir)); + options.mLang = aLang; + options.mBody = aBody; + options.mTag = aTag; + options.mIcon = aIcon; + nsRefPtr notification; + notification = Notification::CreateInternal(aID, + aTitle, + options); + + if (NS_IsMainThread()) { + notification->BindToOwner(aGlobal); + } + + notification->InitFromBase64(jsapi.cx(), aData, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return notification.forget(); +} nsresult Notification::PersistNotification() @@ -946,6 +991,63 @@ protected: NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver) +class ServiceWorkerNotificationObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ServiceWorkerNotificationObserver(const nsAString& aScope, + nsIPrincipal* aPrincipal, + const nsAString& aID) + : mScope(aScope), mID(aID), mPrincipal(aPrincipal) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aPrincipal); + } + +private: + ~ServiceWorkerNotificationObserver() + {} + + const nsString mScope; + const nsString mID; + nsCOMPtr mPrincipal; +}; + +NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver) + +// For ServiceWorkers. +bool +Notification::DispatchNotificationClickEvent() +{ + MOZ_ASSERT(mWorkerPrivate); + MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); + mWorkerPrivate->AssertIsOnWorkerThread(); + + NotificationEventInit options; + options.mNotification = this; + + ErrorResult result; + nsRefPtr target = mWorkerPrivate->GlobalScope(); + nsRefPtr event = + NotificationEvent::Constructor(target, + NS_LITERAL_STRING("notificationclick"), + options, + result); + if (NS_WARN_IF(result.Failed())) { + return false; + } + + event->SetTrusted(true); + WantsPopupControlCheck popupControlCheck(event); + target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); + // We always return false since in case of dispatching on the serviceworker, + // there is no well defined window to focus. The script may use the + // Client.focus() API if it wishes. + return false; +} + bool Notification::DispatchClickEvent() { @@ -975,12 +1077,15 @@ public: : NotificationWorkerRunnable(aNotification->mWorkerPrivate) , mNotification(aNotification) , mWindow(aWindow) - {} + { + MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !mWindow); + } void WorkerRunInternal() override { bool doDefaultAction = mNotification->DispatchClickEvent(); + MOZ_ASSERT_IF(mWorkerPrivate->IsServiceWorker(), !doDefaultAction); if (doDefaultAction) { nsRefPtr r = new FocusWindowRunnable(mWindow); NS_DispatchToMainThread(r); @@ -1042,15 +1147,18 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, nsRefPtr r; if (!strcmp("alertclickcallback", aTopic)) { - WorkerPrivate* top = notification->mWorkerPrivate; - while (top->GetParent()) { - top = top->GetParent(); - } + nsPIDOMWindow* window = nullptr; + if (!notification->mWorkerPrivate->IsServiceWorker()) { + WorkerPrivate* top = notification->mWorkerPrivate; + while (top->GetParent()) { + top = top->GetParent(); + } - nsPIDOMWindow* window = top->GetWindow(); - if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) { - // Window has been closed, this observer is not valid anymore - return NS_ERROR_FAILURE; + window = top->GetWindow(); + if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) { + // Window has been closed, this observer is not valid anymore + return NS_ERROR_FAILURE; + } } // Instead of bothering with adding features and other worker lifecycle @@ -1077,6 +1185,107 @@ WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } +class NotificationClickEventCallback final : public nsINotificationStorageCallback +{ +public: + NS_DECL_ISUPPORTS + + NotificationClickEventCallback(nsIPrincipal* aPrincipal, + const nsAString& aScope) + : mPrincipal(aPrincipal), mScope(aScope) + { + MOZ_ASSERT(aPrincipal); + } + + NS_IMETHOD Handle(const nsAString& aID, + const nsAString& aTitle, + const nsAString& aDir, + const nsAString& aLang, + const nsAString& aBody, + const nsAString& aTag, + const nsAString& aIcon, + const nsAString& aData, + const nsAString& aBehavior, + JSContext* aCx) override + { + MOZ_ASSERT(!aID.IsEmpty()); + + AssertIsOnMainThread(); + + nsAutoCString originSuffix; + nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr swm = + mozilla::services::GetServiceWorkerManager(); + + if (swm) { + swm->SendNotificationClickEvent(originSuffix, + NS_ConvertUTF16toUTF8(mScope), + aID, + aTitle, + aDir, + aLang, + aBody, + aTag, + aIcon, + aData, + aBehavior); + } + return NS_OK; + } + + NS_IMETHOD Done(JSContext* aCx) override + { + return NS_OK; + } + +private: + ~NotificationClickEventCallback() + { + } + + nsCOMPtr mPrincipal; + nsString mScope; +}; + +NS_IMPL_ISUPPORTS(NotificationClickEventCallback, nsINotificationStorageCallback) + +NS_IMETHODIMP +ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + // Persistent notifications only care about the click event. + if (!strcmp("alertclickcallback", aTopic)) { + nsresult rv; + nsCOMPtr notificationStorage = + do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr callback = + new NotificationClickEventCallback(mPrincipal, mScope); + + nsAutoString origin; + rv = Notification::GetOrigin(mPrincipal, origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = notificationStorage->GetByID(origin, mID, callback); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + void Notification::ShowInternal() { @@ -1126,16 +1335,27 @@ Notification::ShowInternal() nsAutoString soundUrl; ResolveIconAndSoundURL(iconUrl, soundUrl); - // Ownership passed to observer. nsCOMPtr observer; - if (mWorkerPrivate) { - // Keep a pointer so that the feature can tell the observer not to release - // the notification. - mObserver = new WorkerNotificationObserver(Move(ownership)); - observer = mObserver; + if (mScope.IsEmpty()) { + // Ownership passed to observer. + if (mWorkerPrivate) { + // Scope better be set on ServiceWorker initiated requests. + MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker()); + // Keep a pointer so that the feature can tell the observer not to release + // the notification. + mObserver = new WorkerNotificationObserver(Move(ownership)); + observer = mObserver; + } else { + observer = new NotificationObserver(Move(ownership)); + } } else { - observer = new NotificationObserver(Move(ownership)); + // This observer does not care about the Notification. It will be released + // at the end of this function. + // + // The observer is wholly owned by the alerts service. + observer = new ServiceWorkerNotificationObserver(mScope, GetPrincipal(), mID); } + MOZ_ASSERT(observer); // mDataObjectContainer might be uninitialized here because the notification // was constructed with an undefined data property. @@ -1805,6 +2025,8 @@ Notification::ShowPersistentNotification(nsIGlobalObject *aGlobal, return nullptr; } + notification->SetScope(aScope); + return p.forget(); } diff --git a/dom/notification/Notification.h b/dom/notification/Notification.h index 0cf7d649b46..beb0fb41fb2 100644 --- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -115,6 +115,7 @@ class Notification : public DOMEventTargetHelper friend class NotificationPermissionRequest; friend class NotificationObserver; friend class NotificationStorageCallback; + friend class ServiceWorkerNotificationObserver; friend class WorkerNotificationObserver; public: @@ -134,6 +135,30 @@ public: const nsAString& aTitle, const NotificationOptions& aOption, ErrorResult& aRv); + + /** + * Used when dispatching the ServiceWorkerEvent. + * + * Does not initialize the Notification's behavior. + * This is because: + * 1) The Notification is not shown to the user and so the behavior + * parameters don't matter. + * 2) The default binding requires main thread for parsing the JSON from the + * string behavior. + */ + static already_AddRefed + ConstructFromFields( + nsIGlobalObject* aGlobal, + const nsAString& aID, + const nsAString& aTitle, + const nsAString& aDir, + const nsAString& aLang, + const nsAString& aBody, + const nsAString& aTag, + const nsAString& aIcon, + const nsAString& aData, + ErrorResult& aRv); + void GetID(nsAString& aRetval) { aRetval = mID; } @@ -249,6 +274,7 @@ public: ErrorResult& rv); bool DispatchClickEvent(); + bool DispatchNotificationClickEvent(); protected: // Callers MUST bind the Notification to the correct DOMEventTargetHelper parent using // BindToOwner(). @@ -301,6 +327,18 @@ protected: aRetval = mAlertName; } + void GetScope(nsAString& aScope) + { + aScope = mScope; + } + + void + SetScope(const nsAString& aScope) + { + MOZ_ASSERT(mScope.IsEmpty()); + mScope = aScope; + } + const nsString mID; const nsString mTitle; const nsString mBody; @@ -315,6 +353,7 @@ protected: nsCOMPtr mData; nsString mAlertName; + nsString mScope; // Main thread only. bool mIsClosed; diff --git a/dom/notification/NotificationEvent.cpp b/dom/notification/NotificationEvent.cpp new file mode 100644 index 00000000000..765dd68cb18 --- /dev/null +++ b/dom/notification/NotificationEvent.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "NotificationEvent.h" + +using namespace mozilla::dom; + +BEGIN_WORKERS_NAMESPACE + +NotificationEvent::NotificationEvent(EventTarget* aOwner) + : ExtendableEvent(aOwner) +{ +} + +NS_IMPL_ADDREF_INHERITED(NotificationEvent, ExtendableEvent) +NS_IMPL_RELEASE_INHERITED(NotificationEvent, ExtendableEvent) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(NotificationEvent) +NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent) + +NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationEvent, ExtendableEvent, mNotification) + +END_WORKERS_NAMESPACE diff --git a/dom/notification/NotificationEvent.h b/dom/notification/NotificationEvent.h new file mode 100644 index 00000000000..f49873f763a --- /dev/null +++ b/dom/notification/NotificationEvent.h @@ -0,0 +1,74 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_workers_notificationevent_h__ +#define mozilla_dom_workers_notificationevent_h__ + +#include "mozilla/dom/Event.h" +#include "mozilla/dom/NotificationEventBinding.h" +#include "mozilla/dom/ServiceWorkerEvents.h" +#include "mozilla/dom/workers/Workers.h" + +BEGIN_WORKERS_NAMESPACE + +class ServiceWorker; +class ServiceWorkerClient; + +class NotificationEvent final : public ExtendableEvent +{ +protected: + explicit NotificationEvent(EventTarget* aOwner); + ~NotificationEvent() + {} + +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationEvent, ExtendableEvent) + NS_FORWARD_TO_EVENT + + virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle aGivenProto) override + { + return NotificationEventBinding::Wrap(aCx, this, aGivenProto); + } + + static already_AddRefed + Constructor(mozilla::dom::EventTarget* aOwner, + const nsAString& aType, + const NotificationEventInit& aOptions, + ErrorResult& aRv) + { + nsRefPtr e = new NotificationEvent(aOwner); + bool trusted = e->Init(aOwner); + e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable); + e->SetTrusted(trusted); + e->mNotification = aOptions.mNotification; + e->SetWantsPopupControlCheck(e->IsTrusted()); + return e.forget(); + } + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const NotificationEventInit& aOptions, + ErrorResult& aRv) + { + nsCOMPtr owner = do_QueryInterface(aGlobal.GetAsSupports()); + return Constructor(owner, aType, aOptions, aRv); + } + + already_AddRefed + Notification_() + { + nsRefPtr n = mNotification; + return n.forget(); + } + +private: + nsRefPtr mNotification; +}; + +END_WORKERS_NAMESPACE +#endif /* mozilla_dom_workers_notificationevent_h__ */ + diff --git a/dom/notification/NotificationStorage.js b/dom/notification/NotificationStorage.js index 85f581d031d..89452a0897d 100644 --- a/dom/notification/NotificationStorage.js +++ b/dom/notification/NotificationStorage.js @@ -4,7 +4,7 @@ "use strict"; -const DEBUG = false; +const DEBUG = true; function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); } const Cc = Components.classes; @@ -85,7 +85,7 @@ NotificationStorage.prototype = { put: function(origin, id, title, dir, lang, body, tag, icon, alertName, data, behavior) { - if (DEBUG) { debug("PUT: " + id + ": " + title); } + if (DEBUG) { debug("PUT: " + origin + " " + id + ": " + title); } var notification = { id: id, title: title, @@ -134,6 +134,25 @@ NotificationStorage.prototype = { } }, + getByID: function(origin, id, callback) { + if (DEBUG) { debug("GETBYID: " + origin + " " + id); } + var GetByIDProxyCallback = function(id, originalCallback) { + this.searchID = id; + this.originalCallback = originalCallback; + var self = this; + this.handle = function(id, title, dir, lang, body, tag, icon, data, behavior) { + if (id == this.searchID) { + self.originalCallback.handle(id, title, dir, lang, body, tag, icon, data, behavior); + } + }; + this.done = function() { + self.originalCallback.done(); + }; + }; + + return this.get(origin, "", new GetByIDProxyCallback(id, callback)); + }, + delete: function(origin, id) { if (DEBUG) { debug("DELETE: " + id); } var notification = this._notifications[id]; diff --git a/dom/notification/moz.build b/dom/notification/moz.build index 25d219ae390..6a080126e27 100644 --- a/dom/notification/moz.build +++ b/dom/notification/moz.build @@ -18,11 +18,13 @@ EXTRA_JS_MODULES += [ EXPORTS.mozilla.dom += [ 'DesktopNotification.h', 'Notification.h', + 'NotificationEvent.h', ] UNIFIED_SOURCES += [ 'DesktopNotification.cpp', 'Notification.cpp', + 'NotificationEvent.cpp', ] FAIL_ON_WARNINGS = True diff --git a/dom/tests/mochitest/notification/MockServices.js b/dom/tests/mochitest/notification/MockServices.js index e8794b8014b..b47194b5c60 100644 --- a/dom/tests/mochitest/notification/MockServices.js +++ b/dom/tests/mochitest/notification/MockServices.js @@ -43,9 +43,10 @@ var MockServices = (function () { setTimeout(function () { listener.observe(null, "alertshow", cookie); }, 100); + setTimeout(function () { + listener.observe(null, "alertclickcallback", cookie); + }, 100); } - - // ?? SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie); }, showAppNotification: function(aImageUrl, aTitle, aText, aAlertListener, aDetails) { diff --git a/dom/webidl/NotificationEvent.webidl b/dom/webidl/NotificationEvent.webidl new file mode 100644 index 00000000000..e92e33a1913 --- /dev/null +++ b/dom/webidl/NotificationEvent.webidl @@ -0,0 +1,26 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://notifications.spec.whatwg.org/ + * + * Copyright: + * To the extent possible under law, the editors have waived all copyright and + * related or neighboring rights to this work. + */ + +[Constructor(DOMString type, optional NotificationEventInit eventInitDict), + Exposed=ServiceWorker,Func="mozilla::dom::Notification::PrefEnabled"] +interface NotificationEvent : ExtendableEvent { + readonly attribute Notification notification; +}; + +dictionary NotificationEventInit : ExtendableEventInit { + required Notification notification; +}; + +partial interface ServiceWorkerGlobalScope { + attribute EventHandler onnotificationclick; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e0c02ee32f0..fc340e7946b 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -335,6 +335,7 @@ WEBIDL_FILES = [ 'NodeIterator.webidl', 'NodeList.webidl', 'Notification.webidl', + 'NotificationEvent.webidl', 'NotifyPaintEvent.webidl', 'OfflineAudioCompletionEvent.webidl', 'OfflineAudioContext.webidl', diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 4eeaaa3fc17..97e798913db 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -39,6 +39,7 @@ #include "mozilla/dom/indexedDB/IDBFactory.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/Navigator.h" +#include "mozilla/dom/NotificationEvent.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/RootedDictionary.h" @@ -2282,6 +2283,117 @@ ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginA #endif } +class SendNotificationClickEventRunnable final : public WorkerRunnable +{ + nsMainThreadPtrHandle mServiceWorker; + const nsString mID; + const nsString mTitle; + const nsString mDir; + const nsString mLang; + const nsString mBody; + const nsString mTag; + const nsString mIcon; + const nsString mData; + const nsString mBehavior; + +public: + SendNotificationClickEventRunnable( + WorkerPrivate* aWorkerPrivate, + nsMainThreadPtrHandle& aServiceWorker, + const nsAString& aID, + const nsAString& aTitle, + const nsAString& aDir, + const nsAString& aLang, + const nsAString& aBody, + const nsAString& aTag, + const nsAString& aIcon, + const nsAString& aData, + const nsAString& aBehavior) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mServiceWorker(aServiceWorker) + , mID(aID) + , mTitle(aTitle) + , mDir(aDir) + , mLang(aLang) + , mBody(aBody) + , mTag(aTag) + , mIcon(aIcon) + , mData(aData) + , mBehavior(aBehavior) + { + AssertIsOnMainThread(); + MOZ_ASSERT(aWorkerPrivate); + MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + + nsRefPtr target = do_QueryObject(aWorkerPrivate->GlobalScope()); + + ErrorResult result; + nsRefPtr notification = + Notification::ConstructFromFields(aWorkerPrivate->GlobalScope(), mID, mTitle, mDir, mLang, mBody, mTag, mIcon, mData, result); + if (NS_WARN_IF(result.Failed())) { + return false; + } + + NotificationEventInit nei; + nei.mNotification = notification; + nei.mBubbles = false; + nei.mCancelable = true; + + nsRefPtr event = + NotificationEvent::Constructor(target, NS_LITERAL_STRING("notificationclick"), nei, result); + if (NS_WARN_IF(result.Failed())) { + return false; + } + + event->SetTrusted(true); + target->DispatchDOMEvent(nullptr, event, nullptr, nullptr); + return true; + } +}; + +NS_IMETHODIMP +ServiceWorkerManager::SendNotificationClickEvent(const nsACString& aOriginSuffix, + const nsACString& aScope, + const nsAString& aID, + const nsAString& aTitle, + const nsAString& aDir, + const nsAString& aLang, + const nsAString& aBody, + const nsAString& aTag, + const nsAString& aIcon, + const nsAString& aData, + const nsAString& aBehavior) +{ + OriginAttributes attrs; + if (!attrs.PopulateFromSuffix(aOriginSuffix)) { + return NS_ERROR_INVALID_ARG; + } + + nsRefPtr serviceWorker = CreateServiceWorkerForScope(attrs, aScope); + if (!serviceWorker) { + return NS_ERROR_FAILURE; + } + nsMainThreadPtrHandle serviceWorkerHandle( + new nsMainThreadPtrHolder(serviceWorker)); + + nsRefPtr r = + new SendNotificationClickEventRunnable(serviceWorker->GetWorkerPrivate(), serviceWorkerHandle, aID, aTitle, aDir, aLang, aBody, aTag, aIcon, aData, aBehavior); + + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + NS_IMETHODIMP ServiceWorkerManager::GetReadyPromise(nsIDOMWindow* aWindow, nsISupports** aPromise) diff --git a/dom/workers/test/serviceworkers/mochitest.ini b/dom/workers/test/serviceworkers/mochitest.ini index 36c0c91cb2b..d5e30ab103e 100644 --- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -93,6 +93,8 @@ support-files = client_focus_worker.js bug1151916_worker.js bug1151916_driver.html + notificationclick.html + notificationclick.js worker_updatefoundevent.js worker_updatefoundevent2.js updatefoundevent.html @@ -218,6 +220,7 @@ skip-if = !debug [test_request_context_xslt.html] [test_scopes.html] [test_sandbox_intercept.html] +[test_notificationclick.html] [test_notification_constructor_error.html] [test_sanitize.html] [test_sanitize_domain.html] diff --git a/dom/workers/test/serviceworkers/notificationclick.html b/dom/workers/test/serviceworkers/notificationclick.html new file mode 100644 index 00000000000..80531b85bb9 --- /dev/null +++ b/dom/workers/test/serviceworkers/notificationclick.html @@ -0,0 +1,27 @@ + + + + + Bug 1114554 - controlled page + + + + + + diff --git a/dom/workers/test/serviceworkers/notificationclick.js b/dom/workers/test/serviceworkers/notificationclick.js new file mode 100644 index 00000000000..63ab71c35bd --- /dev/null +++ b/dom/workers/test/serviceworkers/notificationclick.js @@ -0,0 +1,15 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +onnotificationclick = function(e) { + self.clients.matchAll().then(function(clients) { + if (clients.length === 0) { + dump("********************* CLIENTS LIST EMPTY! Test will timeout! ***********************\n"); + return; + } + + clients.forEach(function(client) { + client.postMessage("done"); + }); + }); +} diff --git a/dom/workers/test/serviceworkers/test_notificationclick.html b/dom/workers/test/serviceworkers/test_notificationclick.html new file mode 100644 index 00000000000..258736137f0 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_notificationclick.html @@ -0,0 +1,56 @@ + + + + + Bug 1114554 - Test ServiceWorkerGlobalScope.notificationclick event. + + + + + + +Bug 1114554 +

+ +
+
+ + + diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js index 1b0c4d188b3..2b7e360c7cd 100644 --- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js @@ -158,6 +158,8 @@ var interfaceNamesInGlobalScope = "MessagePort", // IMPORTANT: Do not change this list without review from a DOM peer! "Notification", +// IMPORTANT: Do not change this list without review from a DOM peer! + "NotificationEvent", // IMPORTANT: Do not change this list without review from a DOM peer! "Performance", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/workers/test/test_notification.html b/dom/workers/test/test_notification.html index a680fef67ca..409d9dfd141 100644 --- a/dom/workers/test/test_notification.html +++ b/dom/workers/test/test_notification.html @@ -18,7 +18,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=916893