☠☠ backed out by 531f100d2bd8 ☠ ☠ | |
author | Nikhil Marathe <nsm.nikhil@gmail.com> |
Thu, 25 Jun 2015 11:36:53 -0700 | |
changeset 250272 | 61c3f24cc908f0d46008dfa38939298841dfbee2 |
parent 250271 | e7ae00df02ec976f778cb1f2d8ee7a69ce9b504f |
child 250273 | c3b07f0d1a6000e231395e3a2261f980271779e1 |
push id | 28951 |
push user | cbook@mozilla.com |
push date | Fri, 26 Jun 2015 11:19:38 +0000 |
treeherder | mozilla-central@56e207dbb3bd [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | khuey, wchen |
bugs | 916893 |
milestone | 41.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -4,44 +4,52 @@ * 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 "mozilla/dom/Notification.h" #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/OwningNonNull.h" #include "mozilla/dom/Promise.h" +#include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "nsContentUtils.h" #include "nsIAlertsService.h" #include "nsIAppsService.h" #include "nsIContentPermissionPrompt.h" #include "nsIDocument.h" #include "nsINotificationStorage.h" #include "nsIPermissionManager.h" #include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" #include "nsStructuredCloneContainer.h" #include "nsToolkitCompsCID.h" #include "nsGlobalWindow.h" #include "nsDOMJSUtils.h" +#include "nsProxyRelease.h" +#include "nsNetUtil.h" #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/Event.h" #include "mozilla/Services.h" #include "nsContentPermissionHelper.h" #include "nsILoadContext.h" #ifdef MOZ_B2G #include "nsIDOMDesktopNotification.h" #endif +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" + namespace mozilla { namespace dom { +using namespace workers; + class NotificationStorageCallback final : public nsINotificationStorageCallback { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback) NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise) : mCount(0), @@ -73,17 +81,18 @@ public: RootedDictionary<NotificationOptions> options(aCx); options.mDir = Notification::StringToDirection(nsString(aDir)); options.mLang = aLang; options.mBody = aBody; options.mTag = aTag; options.mIcon = aIcon; options.mMozbehavior.Init(aBehavior); nsRefPtr<Notification> notification; - notification = Notification::CreateInternal(mWindow, + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow); + notification = Notification::CreateInternal(global, aID, aTitle, options); ErrorResult rv; notification->InitFromBase64(aCx, aData, rv); if (rv.Failed()) { return rv.StealNSResult(); } @@ -186,49 +195,227 @@ protected: nsresult DispatchCallback(); nsCOMPtr<nsIPrincipal> mPrincipal; nsCOMPtr<nsPIDOMWindow> mWindow; NotificationPermission mPermission; nsRefPtr<NotificationPermissionCallback> mCallback; nsCOMPtr<nsIContentPermissionRequester> mRequester; }; -class NotificationObserver : public nsIObserver +namespace { +class ReleaseNotificationControlRunnable final : public MainThreadWorkerControlRunnable +{ + Notification* mNotification; + +public: + explicit ReleaseNotificationControlRunnable(Notification* aNotification) + : MainThreadWorkerControlRunnable(aNotification->mWorkerPrivate) + , mNotification(aNotification) + { } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mNotification->ReleaseObject(); + return true; + } +}; + +class GetPermissionRunnable final : public WorkerMainThreadRunnable +{ + NotificationPermission mPermission; + +public: + explicit GetPermissionRunnable(WorkerPrivate* aWorker) + : WorkerMainThreadRunnable(aWorker) + , mPermission(NotificationPermission::Denied) + { } + + bool + MainThreadRun() override + { + ErrorResult result; + mPermission = + Notification::GetPermissionInternal(mWorkerPrivate->GetPrincipal(), + result); + return true; + } + + NotificationPermission + GetPermission() + { + return mPermission; + } +}; + +class FocusWindowRunnable final : public nsRunnable +{ + nsMainThreadPtrHandle<nsPIDOMWindow> mWindow; +public: + explicit FocusWindowRunnable(const nsMainThreadPtrHandle<nsPIDOMWindow>& aWindow) + : mWindow(aWindow) + { } + + NS_IMETHOD + Run() + { + AssertIsOnMainThread(); + if (!mWindow->IsCurrentInnerWindow()) { + // Window has been closed, this observer is not valid anymore + return NS_OK; + } + + nsIDocument* doc = mWindow->GetExtantDoc(); + if (doc) { + // Browser UI may use DOMWebNotificationClicked to focus the tab + // from which the event was dispatched. + nsContentUtils::DispatchChromeEvent(doc, mWindow->GetOuterWindow(), + NS_LITERAL_STRING("DOMWebNotificationClicked"), + true, true); + } + + return NS_OK; + } +}; +} // anonymous namespace + +// Subclass that can be directly dispatched to child workers from the main +// thread. +class NotificationWorkerRunnable : public WorkerRunnable +{ +protected: + explicit NotificationWorkerRunnable(WorkerPrivate* aWorkerPrivate) + : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) + { + } + + bool + PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + return true; + } + + void + PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aDispatchResult) override + { + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + aWorkerPrivate->AssertIsOnWorkerThread(); + aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true); + WorkerRunInternal(); + return true; + } + + void + PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + bool aRunResult) override + { + aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false); + } + + virtual void + WorkerRunInternal() = 0; +}; + +// Overrides dispatch and run handlers so we can directly dispatch from main +// thread to child workers. +class NotificationEventWorkerRunnable final : public NotificationWorkerRunnable +{ + Notification* mNotification; + const nsString mEventName; +public: + NotificationEventWorkerRunnable(Notification* aNotification, + const nsString& aEventName) + : NotificationWorkerRunnable(aNotification->mWorkerPrivate) + , mNotification(aNotification) + , mEventName(aEventName) + {} + + void + WorkerRunInternal() override + { + mNotification->DispatchTrustedEvent(mEventName); + } +}; + +class NotificationRef final { + friend class WorkerNotificationObserver; + +private: + Notification* mNotification; + + // Only useful for workers. + void + Forget() + { + mNotification = nullptr; + } + +public: + explicit NotificationRef(Notification* aNotification) + : mNotification(aNotification) + { + MOZ_ASSERT(mNotification); + if (mNotification->mWorkerPrivate) { + mNotification->mWorkerPrivate->AssertIsOnWorkerThread(); + } else { + AssertIsOnMainThread(); + } + + mNotification->AddRefObject(); + } + + ~NotificationRef() + { + if (mNotification) { + if (mNotification->mWorkerPrivate && NS_IsMainThread()) { + nsRefPtr<ReleaseNotificationControlRunnable> r = + new ReleaseNotificationControlRunnable(mNotification); + AutoSafeJSContext cx; + if (!r->Dispatch(cx)) { + MOZ_CRASH("Will leak worker thread Notification!"); + } + } else { + mNotification->AssertIsOnTargetThread(); + mNotification->ReleaseObject(); + } + mNotification = nullptr; + } + } + + // XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of + // a rawptr that the NotificationRef can invalidate? + Notification* + GetNotification() + { + return mNotification; + } +}; + +class NotificationTask : public nsRunnable { public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - - explicit NotificationObserver(Notification* aNotification) - : mNotification(aNotification) {} - -protected: - virtual ~NotificationObserver() {} - - nsRefPtr<Notification> mNotification; -}; - -class NotificationTask : public nsIRunnable -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIRUNNABLE - enum NotificationAction { eShow, eClose }; - NotificationTask(Notification* aNotification, NotificationAction aAction) - : mNotification(aNotification), mAction(aAction) {} + NotificationTask(UniquePtr<NotificationRef> aRef, NotificationAction aAction) + : mNotificationRef(Move(aRef)), mAction(aAction) {} + NS_IMETHOD + Run() override; protected: virtual ~NotificationTask() {} - nsRefPtr<Notification> mNotification; + UniquePtr<NotificationRef> mNotificationRef; NotificationAction mAction; }; uint32_t Notification::sCount = 0; NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest) @@ -348,198 +535,247 @@ NotificationPermissionRequest::GetTypes( { nsTArray<nsString> emptyOptions; return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), NS_LITERAL_CSTRING("unused"), emptyOptions, aTypes); } -NS_IMPL_ISUPPORTS(NotificationTask, nsIRunnable) - -NS_IMETHODIMP -NotificationTask::Run() +class NotificationObserver : public nsIObserver { - switch (mAction) { - case eShow: - mNotification->ShowInternal(); - break; - case eClose: - mNotification->CloseInternal(); - break; - default: - MOZ_CRASH("Unexpected action for NotificationTask."); +public: + UniquePtr<NotificationRef> mNotificationRef; + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit NotificationObserver(UniquePtr<NotificationRef> aRef) + : mNotificationRef(Move(aRef)) + { + AssertIsOnMainThread(); } - return NS_OK; -} + +protected: + virtual ~NotificationObserver() + { + AssertIsOnMainThread(); + } +}; NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver) NS_IMETHODIMP -NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) +NotificationTask::Run() { - nsCOMPtr<nsPIDOMWindow> window = mNotification->GetOwner(); - if (!window || !window->IsCurrentInnerWindow()) { - // Window has been closed, this observer is not valid anymore - return NS_ERROR_FAILURE; + AssertIsOnMainThread(); + + // Get a pointer to notification before the notification takes ownership of + // the ref (it owns itself temporarily, with ShowInternal() and + // CloseInternal() passing on the ownership appropriately.) + Notification* notif = mNotificationRef->GetNotification(); + notif->mTempRef.swap(mNotificationRef); + if (mAction == eShow) { + notif->ShowInternal(); + } else if (mAction == eClose) { + notif->CloseInternal(); + } else { + MOZ_CRASH("Invalid action"); } - if (!strcmp("alertclickcallback", aTopic)) { - nsCOMPtr<nsIDOMEvent> event; - NS_NewDOMEvent(getter_AddRefs(event), mNotification, nullptr, nullptr); - nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true); - NS_ENSURE_SUCCESS(rv, rv); - event->SetTrusted(true); - WantsPopupControlCheck popupControlCheck(event); - bool doDefaultAction = true; - mNotification->DispatchEvent(event, &doDefaultAction); - if (doDefaultAction) { - nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; - if (doc) { - // Browser UI may use DOMWebNotificationClicked to focus the tab - // from which the event was dispatched. - nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(), - NS_LITERAL_STRING("DOMWebNotificationClicked"), - true, true); - } - } - } else if (!strcmp("alertfinished", aTopic)) { - nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); - if (notificationStorage && mNotification->IsStored()) { - nsString origin; - nsresult rv = Notification::GetOrigin(mNotification->GetOwner(), origin); - if (NS_SUCCEEDED(rv)) { - nsString id; - mNotification->GetID(id); - notificationStorage->Delete(origin, id); - } - mNotification->SetStoredState(false); - } - mNotification->mIsClosed = true; - mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close")); - } else if (!strcmp("alertshow", aTopic)) { - mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show")); + MOZ_ASSERT(!mNotificationRef); + return NS_OK; +} + +// static +bool +Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.webnotifications.enabled", false); } - return NS_OK; + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->DOMWorkerNotificationEnabled(); +} + +// static +bool +Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj) +{ + return NS_IsMainThread(); } Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody, NotificationDirection aDir, const nsAString& aLang, const nsAString& aTag, const nsAString& aIconUrl, - const NotificationBehavior& aBehavior, nsPIDOMWindow* aWindow) - : DOMEventTargetHelper(aWindow), + const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal) + : DOMEventTargetHelper(), + mWorkerPrivate(nullptr), mObserver(nullptr), mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang), - mTag(aTag), mIconUrl(aIconUrl), mBehavior(aBehavior), mIsClosed(false), mIsStored(false) + mTag(aTag), mIconUrl(aIconUrl), mBehavior(aBehavior), mIsClosed(false), + mIsStored(false), mTaskCount(0) { nsAutoString alertName; - DebugOnly<nsresult> rv = GetOrigin(GetOwner(), alertName); - MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed"); + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal); + MOZ_ASSERT(window); + BindToOwner(window); + + DebugOnly<nsresult> rv = GetOrigin(window, alertName); + MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed"); + } else { + mWorkerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(mWorkerPrivate); + + DebugOnly<nsresult> rv = GetOriginWorker(alertName); + MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed"); + } // Get the notification name that is unique per origin + tag/ID. // The name of the alert is of the form origin#tag/ID. alertName.Append('#'); if (!mTag.IsEmpty()) { alertName.AppendLiteral("tag:"); alertName.Append(mTag); } else { alertName.AppendLiteral("notag:"); alertName.Append(mID); } mAlertName = alertName; } +// May be called on any thread. // static already_AddRefed<Notification> Notification::Constructor(const GlobalObject& aGlobal, const nsAString& aTitle, const NotificationOptions& aOptions, ErrorResult& aRv) { - MOZ_ASSERT(NS_IsMainThread()); - nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports()); - MOZ_ASSERT(window, "Window should not be null."); - - nsRefPtr<Notification> notification = CreateInternal(window, + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + nsRefPtr<Notification> notification = CreateInternal(global, EmptyString(), aTitle, aOptions); - // Make a structured clone of the aOptions.mData object JS::Rooted<JS::Value> data(aGlobal.Context(), aOptions.mData); notification->InitFromJSVal(aGlobal.Context(), data, aRv); if (aRv.Failed()) { return nullptr; } + auto n = MakeUnique<NotificationRef>(notification); // Queue a task to show the notification. nsCOMPtr<nsIRunnable> showNotificationTask = - new NotificationTask(notification, NotificationTask::eShow); - NS_DispatchToCurrentThread(showNotificationTask); + new NotificationTask(Move(n), NotificationTask::eShow); + nsresult rv = NS_DispatchToMainThread(showNotificationTask); + if (NS_FAILED(rv)) { + notification->DispatchTrustedEvent(NS_LITERAL_STRING("error")); + } - // Persist the notification. + //XXXnsm The persistence service seems like a prime candidate of + //PBackground. + // On workers, persistence is done in the NotificationTask. + if (NS_IsMainThread()) { + nsresult rv = notification->PersistNotification(); + if (NS_FAILED(rv)) { + NS_WARNING("Could not persist main thread Notification"); + } + } + + return notification.forget(); +} + +nsresult +Notification::PersistNotification() +{ + AssertIsOnMainThread(); nsresult rv; nsCOMPtr<nsINotificationStorage> notificationStorage = do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); if (NS_FAILED(rv)) { - aRv.Throw(rv); - return nullptr; + return rv; } nsString origin; - aRv = GetOrigin(window, origin); - if (aRv.Failed()) { - return nullptr; + if (mWorkerPrivate) { + rv = GetOriginWorker(origin); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } else { + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetOwner()); + MOZ_ASSERT(window); + rv = GetOrigin(window, origin); + if (NS_FAILED(rv)) { + return rv; + } } nsString id; - notification->GetID(id); + GetID(id); nsString alertName; - notification->GetAlertName(alertName); + GetAlertName(alertName); nsString dataString; nsCOMPtr<nsIStructuredCloneContainer> scContainer; - scContainer = notification->GetDataCloneContainer(); + scContainer = GetDataCloneContainer(); if (scContainer) { scContainer->GetDataAsBase64(dataString); } nsAutoString behavior; - if (!aOptions.mMozbehavior.ToJSON(behavior)) { - aRv.Throw(NS_ERROR_FAILURE); - return nullptr; + if (!mBehavior.ToJSON(behavior)) { + return NS_ERROR_FAILURE; + } + + rv = notificationStorage->Put(origin, + id, + mTitle, + DirectionToString(mDir), + mLang, + mBody, + mTag, + mIconUrl, + alertName, + dataString, + behavior); + + if (NS_FAILED(rv)) { + return rv; } - aRv = notificationStorage->Put(origin, - id, - aTitle, - DirectionToString(aOptions.mDir), - aOptions.mLang, - aOptions.mBody, - aOptions.mTag, - aOptions.mIcon, - alertName, - dataString, - behavior); + SetStoredState(true); + return NS_OK; +} - if (aRv.Failed()) { - return nullptr; +void +Notification::UnpersistNotification() +{ + AssertIsOnMainThread(); + if (mIsStored) { + nsCOMPtr<nsINotificationStorage> notificationStorage = + do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); + if (notificationStorage) { + nsString origin; + nsresult rv = GetOrigin(GetOwner(), origin); + if (NS_SUCCEEDED(rv)) { + notificationStorage->Delete(origin, mID); + } + } + SetStoredState(false); } - - notification->SetStoredState(true); - - return notification.forget(); } already_AddRefed<Notification> -Notification::CreateInternal(nsPIDOMWindow* aWindow, +Notification::CreateInternal(nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle, const NotificationOptions& aOptions) { nsString id; if (!aID.IsEmpty()) { id = aID; } else { @@ -559,21 +795,26 @@ Notification::CreateInternal(nsPIDOMWind nsRefPtr<Notification> notification = new Notification(id, aTitle, aOptions.mBody, aOptions.mDir, aOptions.mLang, aOptions.mTag, aOptions.mIcon, aOptions.mMozbehavior, - aWindow); + aGlobal); return notification.forget(); } -Notification::~Notification() {} +Notification::~Notification() +{ + AssertIsOnTargetThread(); + MOZ_ASSERT(!mFeature); + MOZ_ASSERT(!mTempRef); +} NS_IMPL_CYCLE_COLLECTION_CLASS(Notification) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_UNLINK(mData) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDataObjectContainer) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification, DOMEventTargetHelper) @@ -585,87 +826,311 @@ NS_IMPL_ADDREF_INHERITED(Notification, D NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Notification) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) nsIPrincipal* Notification::GetPrincipal() { - nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); - NS_ENSURE_TRUE(sop, nullptr); - return sop->GetPrincipal(); + AssertIsOnMainThread(); + MOZ_ASSERT_IF(mWorkerPrivate, mFeature); + if (mWorkerPrivate) { + return mWorkerPrivate->GetPrincipal(); + } else { + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner()); + NS_ENSURE_TRUE(sop, nullptr); + return sop->GetPrincipal(); + } +} + +class WorkerNotificationObserver final : public NotificationObserver +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + + explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef) + : NotificationObserver(Move(aRef)) + { + AssertIsOnMainThread(); + MOZ_ASSERT(mNotificationRef->GetNotification()->mWorkerPrivate); + } + + void + ForgetNotification() + { + AssertIsOnMainThread(); + mNotificationRef->Forget(); + } + +protected: + virtual ~WorkerNotificationObserver() + { + AssertIsOnMainThread(); + + MOZ_ASSERT(mNotificationRef); + Notification* notification = mNotificationRef->GetNotification(); + if (!notification) { + return; + } + + // Try to pass ownership back to the worker. If the dispatch succeeds we + // are guaranteed this runnable will run, and that it will run after queued + // event runnables, so event runnables will have a safe pointer to the + // Notification. + // + // If the dispatch fails, the worker isn't running anymore and the event + // runnables have already run. We can just let the standard NotificationRef + // release routine take over when ReleaseNotificationRunnable gets deleted. + class ReleaseNotificationRunnable final : public NotificationWorkerRunnable + { + UniquePtr<NotificationRef> mNotificationRef; + public: + explicit ReleaseNotificationRunnable(UniquePtr<NotificationRef> aRef) + : NotificationWorkerRunnable(aRef->GetNotification()->mWorkerPrivate) + , mNotificationRef(Move(aRef)) + {} + + void + WorkerRunInternal() override + { + UniquePtr<NotificationRef> ref; + mozilla::Swap(ref, mNotificationRef); + // Gets released at the end of the function. + } + }; + + nsRefPtr<ReleaseNotificationRunnable> r = + new ReleaseNotificationRunnable(Move(mNotificationRef)); + notification = nullptr; + + AutoJSAPI jsapi; + jsapi.Init(); + r->Dispatch(jsapi.cx()); + } +}; + +NS_IMPL_ISUPPORTS_INHERITED0(WorkerNotificationObserver, NotificationObserver) + +bool +Notification::DispatchClickEvent() +{ + AssertIsOnTargetThread(); + nsCOMPtr<nsIDOMEvent> event; + NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); + nsresult rv = event->InitEvent(NS_LITERAL_STRING("click"), false, true); + NS_ENSURE_SUCCESS(rv, false); + event->SetTrusted(true); + WantsPopupControlCheck popupControlCheck(event); + bool doDefaultAction = true; + DispatchEvent(event, &doDefaultAction); + return doDefaultAction; +} + +// Overrides dispatch and run handlers so we can directly dispatch from main +// thread to child workers. +class NotificationClickWorkerRunnable final : public NotificationWorkerRunnable +{ + Notification* mNotification; + // Optional window that gets focused if click event is not + // preventDefault()ed. + nsMainThreadPtrHandle<nsPIDOMWindow> mWindow; +public: + NotificationClickWorkerRunnable(Notification* aNotification, + const nsMainThreadPtrHandle<nsPIDOMWindow>& aWindow) + : NotificationWorkerRunnable(aNotification->mWorkerPrivate) + , mNotification(aNotification) + , mWindow(aWindow) + {} + + void + WorkerRunInternal() override + { + bool doDefaultAction = mNotification->DispatchClickEvent(); + if (doDefaultAction) { + nsRefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow); + NS_DispatchToMainThread(r); + } + } +}; + +NS_IMETHODIMP +NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mNotificationRef); + Notification* notification = mNotificationRef->GetNotification(); + MOZ_ASSERT(notification); + if (!strcmp("alertclickcallback", aTopic)) { + nsCOMPtr<nsPIDOMWindow> window = notification->GetOwner(); + if (!window || !window->IsCurrentInnerWindow()) { + // Window has been closed, this observer is not valid anymore + return NS_ERROR_FAILURE; + } + + bool doDefaultAction = notification->DispatchClickEvent(); + if (doDefaultAction) { + nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; + if (doc) { + // Browser UI may use DOMWebNotificationClicked to focus the tab + // from which the event was dispatched. + nsContentUtils::DispatchChromeEvent(doc, window->GetOuterWindow(), + NS_LITERAL_STRING("DOMWebNotificationClicked"), + true, true); + } + } + } else if (!strcmp("alertfinished", aTopic)) { + notification->UnpersistNotification(); + notification->mIsClosed = true; + notification->DispatchTrustedEvent(NS_LITERAL_STRING("close")); + } else if (!strcmp("alertshow", aTopic)) { + notification->DispatchTrustedEvent(NS_LITERAL_STRING("show")); + } + return NS_OK; +} + +NS_IMETHODIMP +WorkerNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(mNotificationRef); + // For an explanation of why it is OK to pass this rawptr to the event + // runnables, see the Notification class comment. + Notification* notification = mNotificationRef->GetNotification(); + // We can't assert notification here since the feature could've unset it. + if (!notification) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(notification->mWorkerPrivate); + + nsRefPtr<WorkerRunnable> r; + if (!strcmp("alertclickcallback", aTopic)) { + nsCOMPtr<nsPIDOMWindow> window = notification->mWorkerPrivate->GetWindow(); + 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 + // management, we simply hold strongrefs to the window and document. + nsMainThreadPtrHandle<nsPIDOMWindow> windowHandle( + new nsMainThreadPtrHolder<nsPIDOMWindow>(window)); + + r = new NotificationClickWorkerRunnable(notification, windowHandle); + } else if (!strcmp("alertfinished", aTopic)) { + notification->UnpersistNotification(); + notification->mIsClosed = true; + r = new NotificationEventWorkerRunnable(notification, + NS_LITERAL_STRING("close")); + } else if (!strcmp("alertshow", aTopic)) { + r = new NotificationEventWorkerRunnable(notification, + NS_LITERAL_STRING("show")); + } + + MOZ_ASSERT(r); + AutoSafeJSContext cx; + if (!r->Dispatch(cx)) { + NS_WARNING("Could not dispatch event to worker notification"); + } + return NS_OK; } void Notification::ShowInternal() { + AssertIsOnMainThread(); + MOZ_ASSERT(mTempRef, "Notification should take ownership of itself before" + "calling ShowInternal!"); + // A notification can only have one observer and one call to ShowInternal. + MOZ_ASSERT(!mObserver); + + // Transfer ownership to local scope so we can either release it at the end + // of this function or transfer it to the observer. + UniquePtr<NotificationRef> ownership; + mozilla::Swap(ownership, mTempRef); + MOZ_ASSERT(ownership->GetNotification() == this); + + if (mWorkerPrivate) { + // Persist the notification now since we couldn't do it on the worker + // thread. + nsresult rv = PersistNotification(); + if (NS_FAILED(rv)) { + NS_WARNING("Could not persist worker Notification"); + } + } + nsCOMPtr<nsIAlertsService> alertService = do_GetService(NS_ALERTSERVICE_CONTRACTID); ErrorResult result; - if (GetPermissionInternal(GetOwner(), result) != - NotificationPermission::Granted || !alertService) { + NotificationPermission permission = NotificationPermission::Denied; + if (mWorkerPrivate) { + permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result); + } else { + permission = GetPermissionInternal(GetOwner(), result); + } + if (permission != NotificationPermission::Granted || !alertService) { // We do not have permission to show a notification or alert service // is not available. - DispatchTrustedEvent(NS_LITERAL_STRING("error")); + if (mWorkerPrivate) { + nsRefPtr<NotificationEventWorkerRunnable> r = + new NotificationEventWorkerRunnable(this, + NS_LITERAL_STRING("error")); + AutoSafeJSContext cx; + if (!r->Dispatch(cx)) { + NS_WARNING("Could not dispatch event to worker notification"); + } + } else { + DispatchTrustedEvent(NS_LITERAL_STRING("error")); + } return; } - nsresult rv; - nsAutoString absoluteUrl; + nsAutoString iconUrl; nsAutoString soundUrl; - // Resolve image URL against document base URI. - nsIDocument* doc = GetOwner()->GetExtantDoc(); - if (doc) { - nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI(); - if (baseUri) { - if (mIconUrl.Length() > 0) { - nsCOMPtr<nsIURI> srcUri; - rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri), - mIconUrl, doc, baseUri); - if (NS_SUCCEEDED(rv)) { - nsAutoCString src; - srcUri->GetSpec(src); - absoluteUrl = NS_ConvertUTF8toUTF16(src); - } - } - if (mBehavior.mSoundFile.Length() > 0) { - nsCOMPtr<nsIURI> srcUri; - rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri), - mBehavior.mSoundFile, doc, baseUri); - if (NS_SUCCEEDED(rv)) { - nsAutoCString src; - srcUri->GetSpec(src); - soundUrl = NS_ConvertUTF8toUTF16(src); - } - } - } + ResolveIconAndSoundURL(iconUrl, soundUrl); + + // Ownership passed to observer. + nsCOMPtr<nsIObserver> 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; + } else { + observer = new NotificationObserver(Move(ownership)); } - nsCOMPtr<nsIObserver> observer = new NotificationObserver(this); - // mDataObjectContainer might be uninitialized here because the notification // was constructed with an undefined data property. nsString dataStr; if (mDataObjectContainer) { mDataObjectContainer->GetDataAsBase64(dataStr); } #ifdef MOZ_B2G nsCOMPtr<nsIAppNotificationService> appNotifier = do_GetService("@mozilla.org/system-alerts-service;1"); if (appNotifier) { - nsCOMPtr<nsPIDOMWindow> window = GetOwner(); - uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId(); + uint32_t appId = nsIScriptSecurityManager::UNKNOWN_APP_ID; + if (mWorkerPrivate) { + appId = mWorkerPrivate->GetPrincipal()->GetAppId(); + } else { + nsCOMPtr<nsPIDOMWindow> window = GetOwner(); + appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId(); + } if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1"); nsString manifestUrl = EmptyString(); - rv = appsService->GetManifestURLByLocalId(appId, manifestUrl); + nsresult rv = appsService->GetManifestURLByLocalId(appId, manifestUrl); if (NS_SUCCEEDED(rv)) { mozilla::AutoSafeJSContext cx; JS::Rooted<JS::Value> val(cx); AppNotificationServiceOptions ops; ops.mTextClickable = true; ops.mManifestURL = manifestUrl; ops.mId = mAlertName; ops.mDbId = mID; @@ -676,40 +1141,61 @@ Notification::ShowInternal() ops.mMozbehavior = mBehavior; ops.mMozbehavior.mSoundFile = soundUrl; if (!ToJSValue(cx, ops, &val)) { NS_WARNING("Converting dict to object failed!"); return; } - appNotifier->ShowAppNotification(absoluteUrl, mTitle, mBody, + appNotifier->ShowAppNotification(iconUrl, mTitle, mBody, observer, val); return; } } } #endif // In the case of IPC, the parent process uses the cookie to map to // nsIObserver. Thus the cookie must be unique to differentiate observers. nsString uniqueCookie = NS_LITERAL_STRING("notification:"); uniqueCookie.AppendInt(sCount++); + //XXXnsm Should this default to true? bool inPrivateBrowsing = false; + nsIDocument* doc = mWorkerPrivate ? mWorkerPrivate->GetDocument() + : GetOwner()->GetExtantDoc(); if (doc) { nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext(); inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); + } else if (mWorkerPrivate) { + // Not all workers may have a document, but with Bug 1107516 fixed, they + // should all have a loadcontext. + nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup(); + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(nullptr, loadGroup, NS_GET_IID(nsILoadContext), + getter_AddRefs(loadContext)); + inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing(); } - alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true, + alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true, uniqueCookie, observer, mAlertName, DirectionToString(mDir), mLang, dataStr, GetPrincipal(), inPrivateBrowsing); } +/* static */ bool +Notification::RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */) +{ + // requestPermission() is not allowed on workers. The calling page should ask + // for permission on the worker's behalf. This is to prevent 'which window + // should show the browser pop-up'. See discussion: + // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html + return NS_IsMainThread(); +} + void Notification::RequestPermission(const GlobalObject& aGlobal, const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback, ErrorResult& aRv) { // Get principal from global to make permission request for notifications. nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports()); nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal.GetAsSupports()); @@ -727,36 +1213,59 @@ Notification::RequestPermission(const Gl new NotificationPermissionRequest(principal, window, permissionCallback); NS_DispatchToMainThread(request); } NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv) { - return GetPermissionInternal(aGlobal.GetAsSupports(), aRv); + if (NS_IsMainThread()) { + return GetPermissionInternal(aGlobal.GetAsSupports(), aRv); + } else { + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + nsRefPtr<GetPermissionRunnable> r = + new GetPermissionRunnable(worker); + if (!r->Dispatch(worker->GetJSContext())) { + aRv.Throw(NS_ERROR_DOM_ABORT_ERR); + return NotificationPermission::Denied; + } + + return r->GetPermission(); + } } -NotificationPermission +/* static */ NotificationPermission Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) { // Get principal from global to check permission for notifications. nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal); if (!sop) { aRv.Throw(NS_ERROR_UNEXPECTED); return NotificationPermission::Denied; } nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); - if (nsContentUtils::IsSystemPrincipal(principal)) { + return GetPermissionInternal(principal, aRv); +} + +/* static */ NotificationPermission +Notification::GetPermissionInternal(nsIPrincipal* aPrincipal, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + MOZ_ASSERT(aPrincipal); + + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { return NotificationPermission::Granted; } else { // Allow files to show notifications by default. nsCOMPtr<nsIURI> uri; - principal->GetURI(getter_AddRefs(uri)); + aPrincipal->GetURI(getter_AddRefs(uri)); if (uri) { bool isFile; uri->SchemeIs("file", &isFile); if (isFile) { return NotificationPermission::Granted; } } } @@ -770,36 +1279,92 @@ Notification::GetPermissionInternal(nsIS } } uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; nsCOMPtr<nsIPermissionManager> permissionManager = services::GetPermissionManager(); - permissionManager->TestPermissionFromPrincipal(principal, + permissionManager->TestPermissionFromPrincipal(aPrincipal, "desktop-notification", &permission); // Convert the result to one of the enum types. switch (permission) { case nsIPermissionManager::ALLOW_ACTION: return NotificationPermission::Granted; case nsIPermissionManager::DENY_ACTION: return NotificationPermission::Denied; default: return NotificationPermission::Default; } } +nsresult +Notification::ResolveIconAndSoundURL(nsString& iconUrl, nsString& soundUrl) +{ + AssertIsOnMainThread(); + nsresult rv = NS_OK; + + nsCOMPtr<nsIURI> baseUri; + + // XXXnsm If I understand correctly, the character encoding for resolving + // URIs in new specs is dictated by the URL spec, which states that unless + // the URL parser is passed an override encoding, the charset to be used is + // UTF-8. The new Notification icon/sound specification just says to use the + // Fetch API, where the Request constructor defers to URL parsing specifying + // the API base URL and no override encoding. So we've to use UTF-8 on + // workers, but for backwards compat keeping it document charset on main + // thread. + const char* charset = "UTF-8"; + + if (mWorkerPrivate) { + baseUri = mWorkerPrivate->GetBaseURI(); + } else { + nsIDocument* doc = GetOwner()->GetExtantDoc(); + if (doc) { + baseUri = doc->GetBaseURI(); + charset = doc->GetDocumentCharacterSet().get(); + } else { + NS_WARNING("No document found for main thread notification!"); + return NS_ERROR_FAILURE; + } + } + + if (baseUri) { + if (mIconUrl.Length() > 0) { + nsCOMPtr<nsIURI> srcUri; + rv = NS_NewURI(getter_AddRefs(srcUri), mIconUrl, charset, baseUri); + if (NS_SUCCEEDED(rv)) { + nsAutoCString src; + srcUri->GetSpec(src); + iconUrl = NS_ConvertUTF8toUTF16(src); + } + } + if (mBehavior.mSoundFile.Length() > 0) { + nsCOMPtr<nsIURI> srcUri; + rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, charset, baseUri); + if (NS_SUCCEEDED(rv)) { + nsAutoCString src; + srcUri->GetSpec(src); + soundUrl = NS_ConvertUTF8toUTF16(src); + } + } + } + + return rv; +} + already_AddRefed<Promise> Notification::Get(const GlobalObject& aGlobal, const GetNotificationOptions& aFilter, ErrorResult& aRv) { + AssertIsOnMainThread(); nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); MOZ_ASSERT(global); nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global); MOZ_ASSERT(window); nsIDocument* doc = window->GetExtantDoc(); if (!doc) { aRv.Throw(NS_ERROR_UNEXPECTED); @@ -841,39 +1406,40 @@ JSObject* Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return mozilla::dom::NotificationBinding::Wrap(aCx, this, aGivenProto); } void Notification::Close() { - // Queue a task to close the notification. + AssertIsOnTargetThread(); + auto ref = MakeUnique<NotificationRef>(this); + nsCOMPtr<nsIRunnable> closeNotificationTask = - new NotificationTask(this, NotificationTask::eClose); - NS_DispatchToMainThread(closeNotificationTask); + new NotificationTask(Move(ref), NotificationTask::eClose); + nsresult rv = NS_DispatchToMainThread(closeNotificationTask); + + if (NS_FAILED(rv)) { + DispatchTrustedEvent(NS_LITERAL_STRING("error")); + // If dispatch fails, NotificationTask will release the ref when it goes + // out of scope at the end of this function. + } } void Notification::CloseInternal() { - if (mIsStored) { - // Don't bail out if notification storage fails, since we still - // want to send the close event through the alert service. - nsCOMPtr<nsINotificationStorage> notificationStorage = - do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); - if (notificationStorage) { - nsString origin; - nsresult rv = GetOrigin(GetOwner(), origin); - if (NS_SUCCEEDED(rv)) { - notificationStorage->Delete(origin, mID); - } - } - SetStoredState(false); - } + // Transfer ownership (if any) to local scope so we can release it at the end + // of this function. This is relevant when the call is from + // NotificationTask::Run(). + UniquePtr<NotificationRef> ownership; + mozilla::Swap(ownership, mTempRef); + + UnpersistNotification(); if (!mIsClosed) { nsCOMPtr<nsIAlertsService> alertService = do_GetService(NS_ALERTSERVICE_CONTRACTID); if (alertService) { alertService->CloseAlert(mAlertName, GetPrincipal()); } } } @@ -905,16 +1471,24 @@ Notification::GetOrigin(nsPIDOMWindow* a do_GetService("@mozilla.org/AppsService;1", &rv); NS_ENSURE_SUCCESS(rv, rv); appsService->GetManifestURLByLocalId(appId, aOrigin); } return NS_OK; } +nsresult +Notification::GetOriginWorker(nsString& aOrigin) +{ + MOZ_ASSERT(mWorkerPrivate); + aOrigin = mWorkerPrivate->GetLocationInfo().mOrigin; + return NS_OK; +} + nsIStructuredCloneContainer* Notification::GetDataCloneContainer() { return mDataObjectContainer; } void Notification::GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval) @@ -953,11 +1527,118 @@ void Notification::InitFromBase64(JSCont } auto container = new nsStructuredCloneContainer(); aRv = container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION, aCx); mDataObjectContainer = container; } +void +Notification::AddRefObject() +{ + AssertIsOnTargetThread(); + AddRef(); + MOZ_ASSERT_IF(mWorkerPrivate&& !mFeature, mTaskCount == 0); + MOZ_ASSERT_IF(mWorkerPrivate && mFeature, mTaskCount > 0); + if (mWorkerPrivate && !mFeature) { + RegisterFeature(); + } + ++mTaskCount; +} + +void +Notification::ReleaseObject() +{ + AssertIsOnTargetThread(); + MOZ_ASSERT(mTaskCount > 0); + + --mTaskCount; + if (mWorkerPrivate && mFeature && mTaskCount == 0) { + UnregisterFeature(); + } + Release(); +} + +NotificationFeature::NotificationFeature(Notification* aNotification) + : mNotification(aNotification) +{ + MOZ_ASSERT(mNotification->mWorkerPrivate); + mNotification->mWorkerPrivate->AssertIsOnWorkerThread(); +} + +/* + * Called from the worker, runs on main thread, blocks worker. + * + * We can freely access mNotification here because the feature supplied it and + * the Notification owns the feature. + */ +class CloseNotificationRunnable final + : public WorkerMainThreadRunnable +{ + Notification* mNotification; + + public: + explicit CloseNotificationRunnable(Notification* aNotification) + : WorkerMainThreadRunnable(aNotification->mWorkerPrivate) + , mNotification(aNotification) + {} + + bool + MainThreadRun() override + { + if (mNotification->mObserver) { + // The Notify() take's responsibility of releasing the Notification. + mNotification->mObserver->ForgetNotification(); + mNotification->mObserver = nullptr; + } + mNotification->CloseInternal(); + return true; + } +}; + +bool +NotificationFeature::Notify(JSContext* aCx, Status aStatus) +{ + MOZ_ASSERT(aStatus >= Canceling); + + // Dispatched to main thread, blocks on closing the Notification. + nsRefPtr<CloseNotificationRunnable> r = + new CloseNotificationRunnable(mNotification); + r->Dispatch(aCx); + + mNotification->ReleaseObject(); + // From this point we cannot touch properties of this feature because + // ReleaseObject() may have led to the notification going away and the + // notification owns this feature! + return true; +} + +void +Notification::RegisterFeature() +{ + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(!mFeature); + // Only calls to show() and close() should lead to AddFeature(), both of + // which come from JS. So we can assert that AddFeature() should succeed. + mFeature = MakeUnique<NotificationFeature>(this); + bool ok = + mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), + mFeature.get()); + if (!ok) { + MOZ_CRASH("AddFeature failed"); + } +} + +void +Notification::UnregisterFeature() +{ + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(mFeature); + mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), + mFeature.get()); + mFeature = nullptr; +} } // namespace dom } // namespace mozilla
--- a/dom/notification/Notification.h +++ b/dom/notification/Notification.h @@ -3,49 +3,131 @@ /* 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_notification_h__ #define mozilla_dom_notification_h__ #include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/UniquePtr.h" #include "mozilla/dom/NotificationBinding.h" +#include "mozilla/dom/workers/bindings/WorkerFeature.h" #include "nsIObserver.h" #include "nsCycleCollectionParticipant.h" class nsIPrincipal; class nsIStructuredCloneContainer; class nsIVariant; namespace mozilla { namespace dom { - -class NotificationObserver; +class NotificationRef; +class WorkerNotificationObserver; class Promise; +namespace workers { + class WorkerPrivate; +} // namespace workers + +class Notification; +class NotificationFeature final : public workers::WorkerFeature +{ + // Held alive by Notification itself before feature is added, and only + // released after feature is removed. + Notification* mNotification; + +public: + explicit NotificationFeature(Notification* aNotification); + + bool + Notify(JSContext* aCx, workers::Status aStatus) override; +}; + + +/* + * Notifications on workers introduce some liveness issues. The property we + * are trying to satisfy is: + * Whenever a task is dispatched to the main thread to operate on + * a Notification, the Notification should be addrefed on the worker thread + * and a feature should be added to observe the worker lifetime. This main + * thread owner should ensure it properly releases the reference to the + * Notification, additionally removing the feature if necessary. + * + * To enforce the correct addref and release, along with managing the feature, + * we introduce a NotificationRef. Only one object may ever own + * a NotificationRef, so UniquePtr<> is used throughout. The NotificationRef + * constructor calls AddRefObject(). When it is destroyed (on any thread) it + * releases the Notification on the correct thread. + * + * Code should only access the underlying Notification object when it can + * guarantee that it retains ownership of the NotificationRef while doing so. + * + * The one kink in this mechanism is that the worker feature may be Notify()ed + * if the worker stops running script, even if the Notification's corresponding + * UI is still visible to the user. We handle this case with the following + * steps: + * a) Close the notification. This is done by blocking the worker on the main + * thread. + * b) Ask the observer to let go of its NotificationRef's underlying + * Notification without proper cleanup since the feature will handle the + * release. This is only OK because every notification has only one + * associated observer. The NotificationRef itself is still owned by the + * observer and deleted by the UniquePtr, but it doesn't do anything since + * the underlying Notification is null. + * + * To unify code-paths, we use the same NotificationRef in the main + * thread implementation too. + * + * Note that the Notification's JS wrapper does it's standard + * AddRef()/Release() and is not affected by any of this. + * + * There is one case related to the WorkerNotificationObserver having to + * dispatch WorkerRunnables to the worker thread which will use the + * Notification object. We can end up in a situation where an event runnable is + * dispatched to the worker, gets queued in the worker's event queue, but then, + * the worker yields to the main thread. Here the main thread observer is + * destroyed, which frees its NotificationRef. The NotificationRef dispatches + * a ControlRunnable to the worker, which runs before the event runnable, + * leading to the event runnable possibly not having a valid Notification + * reference. + * We solve this problem by having WorkerNotificationObserver's dtor + * dispatching a standard WorkerRunnable to do the release (this guarantees the + * ordering of the release is after the event runnables). All WorkerRunnables + * that get dispatched successfully are guaranteed to run on the worker before + * it shuts down. If that dispatch fails, the standard ControlRunnable based + * shutdown is acceptable since the already dispatched event runnables have + * already run or canceled (the worker is already past Running). + * + */ class Notification : public DOMEventTargetHelper { + friend class CloseNotificationRunnable; friend class NotificationTask; friend class NotificationPermissionRequest; friend class NotificationObserver; friend class NotificationStorageCallback; + friend class WorkerNotificationObserver; public: IMPL_EVENT_HANDLER(click) IMPL_EVENT_HANDLER(show) IMPL_EVENT_HANDLER(error) IMPL_EVENT_HANDLER(close) NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Notification, DOMEventTargetHelper) + static bool PrefEnabled(JSContext* aCx, JSObject* aObj); + // Returns if Notification.get() is allowed for the current global. + static bool IsGetEnabled(JSContext* aCx, JSObject* aObj); + static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal, const nsAString& aTitle, const NotificationOptions& aOption, ErrorResult& aRv); void GetID(nsAString& aRetval) { aRetval = mID; } @@ -86,16 +168,18 @@ public: bool IsStored() { return mIsStored; } nsIStructuredCloneContainer* GetDataCloneContainer(); + static bool RequestPermissionEnabledForScope(JSContext* aCx, JSObject* /* unused */); + static void RequestPermission(const GlobalObject& aGlobal, const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback, ErrorResult& aRv); static NotificationPermission GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv); static already_AddRefed<Promise> Get(const GlobalObject& aGlobal, @@ -112,23 +196,46 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval); void InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData, ErrorResult& aRv); void InitFromBase64(JSContext* aCx, const nsAString& aData, ErrorResult& aRv); + void AssertIsOnTargetThread() const + { + MOZ_ASSERT(IsTargetThread()); + } + + workers::WorkerPrivate* mWorkerPrivate; + // Main thread only. + WorkerNotificationObserver* mObserver; + + // The NotificationTask calls ShowInternal()/CloseInternal() on the + // Notification. At this point the task has ownership of the Notification. It + // passes this on to the Notification itself via mTempRef so that + // ShowInternal()/CloseInternal() may pass it along appropriately (or release + // it). + UniquePtr<NotificationRef> mTempRef; + + void AddRefObject(); + void ReleaseObject(); + + static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal, + ErrorResult& rv); + + bool DispatchClickEvent(); protected: Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody, NotificationDirection aDir, const nsAString& aLang, const nsAString& aTag, const nsAString& aIconUrl, - const NotificationBehavior& aBehavior, nsPIDOMWindow* aWindow); + const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal); - static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow, + static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle, const NotificationOptions& aOptions); void ShowInternal(); void CloseInternal(); static NotificationPermission GetPermissionInternal(nsISupports* aGlobal, @@ -153,50 +260,68 @@ protected: } if (aDirection.EqualsLiteral("rtl")) { return NotificationDirection::Rtl; } return NotificationDirection::Auto; } static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin); + nsresult GetOriginWorker(nsString& aOrigin); void GetAlertName(nsAString& aRetval) { aRetval = mAlertName; } - nsString mID; - nsString mTitle; - nsString mBody; - NotificationDirection mDir; - nsString mLang; - nsString mTag; - nsString mIconUrl; + const nsString mID; + const nsString mTitle; + const nsString mBody; + const NotificationDirection mDir; + const nsString mLang; + const nsString mTag; + const nsString mIconUrl; nsCOMPtr<nsIStructuredCloneContainer> mDataObjectContainer; - NotificationBehavior mBehavior; + const NotificationBehavior mBehavior; // It's null until GetData is first called nsCOMPtr<nsIVariant> mData; nsString mAlertName; + // Main thread only. bool mIsClosed; // We need to make a distinction between the notification being closed i.e. // removed from any pending or active lists, and the notification being // removed from the database. NotificationDB might fail when trying to remove // the notification. bool mIsStored; static uint32_t sCount; private: virtual ~Notification(); nsIPrincipal* GetPrincipal(); + + nsresult PersistNotification(); + void UnpersistNotification(); + + bool IsTargetThread() const + { + return NS_IsMainThread() == !mWorkerPrivate; + } + + void RegisterFeature(); + void UnregisterFeature(); + + nsresult ResolveIconAndSoundURL(nsString&, nsString&); + + UniquePtr<NotificationFeature> mFeature; + uint32_t mTaskCount; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_notification_h__
--- a/dom/notification/moz.build +++ b/dom/notification/moz.build @@ -28,11 +28,12 @@ UNIFIED_SOURCES += [ FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/dom/base', '/dom/ipc', + '/dom/workers', ] XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/dom/webidl/Notification.webidl +++ b/dom/webidl/Notification.webidl @@ -6,27 +6,28 @@ * 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. */ -[Pref="dom.webnotifications.enabled", - Constructor(DOMString title, optional NotificationOptions options), +[Constructor(DOMString title, optional NotificationOptions options), + Exposed=(Window,Worker), + Func="mozilla::dom::Notification::PrefEnabled", UnsafeInPrerendering] interface Notification : EventTarget { [GetterThrows] static readonly attribute NotificationPermission permission; - [Throws] + [Throws, Func="mozilla::dom::Notification::RequestPermissionEnabledForScope"] static void requestPermission(optional NotificationPermissionCallback permissionCallback); - [Throws] + [Throws, Func="mozilla::dom::Notification::IsGetEnabled"] static Promise<sequence<Notification>> get(optional GetNotificationOptions filter); attribute EventHandler onclick; attribute EventHandler onshow; attribute EventHandler onerror;
--- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -153,16 +153,17 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= #define PREF_GCZEAL "gcZeal" #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP)) #define DUMP_CONTROLLED_BY_PREF 1 #define PREF_DOM_WINDOW_DUMP_ENABLED "browser.dom.window.dump.enabled" #endif #define PREF_DOM_CACHES_ENABLED "dom.caches.enabled" +#define PREF_DOM_WORKERNOTIFICATION_ENABLED "dom.webnotifications.workers.enabled" #define PREF_WORKERS_LATEST_JS_VERSION "dom.workers.latestJSVersion" #define PREF_INTL_ACCEPT_LANGUAGES "intl.accept_languages" #define PREF_SERVICEWORKERS_ENABLED "dom.serviceWorkers.enabled" #define PREF_INTERCEPTION_ENABLED "dom.serviceWorkers.interception.enabled" namespace { const uint32_t kNoIndex = uint32_t(-1); @@ -1899,16 +1900,20 @@ RuntimeService::Init() reinterpret_cast<void *>(WORKERPREF_DUMP))) || #endif NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, PREF_DOM_CACHES_ENABLED, reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) || NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, + PREF_DOM_WORKERNOTIFICATION_ENABLED, + reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) || + NS_FAILED(Preferences::RegisterCallbackAndCall( + WorkerPrefChanged, PREF_SERVICEWORKERS_ENABLED, reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) || NS_FAILED(Preferences::RegisterCallbackAndCall( WorkerPrefChanged, PREF_INTERCEPTION_ENABLED, reinterpret_cast<void *>(WORKERPREF_INTERCEPTION_ENABLED))) || NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions, PREF_JS_OPTIONS_PREFIX, @@ -2112,16 +2117,20 @@ RuntimeService::Cleanup() NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, PREF_SERVICEWORKERS_ENABLED, reinterpret_cast<void *>(WORKERPREF_SERVICEWORKERS))) || NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, PREF_DOM_CACHES_ENABLED, reinterpret_cast<void *>(WORKERPREF_DOM_CACHES))) || + NS_FAILED(Preferences::UnregisterCallback( + WorkerPrefChanged, + PREF_DOM_WORKERNOTIFICATION_ENABLED, + reinterpret_cast<void *>(WORKERPREF_DOM_WORKERNOTIFICATION))) || #if DUMP_CONTROLLED_BY_PREF NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, PREF_DOM_WINDOW_DUMP_ENABLED, reinterpret_cast<void *>(WORKERPREF_DUMP))) || #endif #ifdef JS_GC_ZEAL NS_FAILED(Preferences::UnregisterCallback( @@ -2646,38 +2655,36 @@ RuntimeService::WorkerPrefChanged(const AssertIsOnMainThread(); uintptr_t tmp = reinterpret_cast<uintptr_t>(aClosure); MOZ_ASSERT(tmp < WORKERPREF_COUNT); WorkerPreference key = static_cast<WorkerPreference>(tmp); #ifdef DUMP_CONTROLLED_BY_PREF if (key == WORKERPREF_DUMP) { - key = WORKERPREF_DUMP; - sDefaultPreferences[WORKERPREF_DUMP] = + sDefaultPreferences[key] = Preferences::GetBool(PREF_DOM_WINDOW_DUMP_ENABLED, false); } #endif if (key == WORKERPREF_DOM_CACHES) { - key = WORKERPREF_DOM_CACHES; sDefaultPreferences[WORKERPREF_DOM_CACHES] = Preferences::GetBool(PREF_DOM_CACHES_ENABLED, false); + } else if (key == WORKERPREF_DOM_WORKERNOTIFICATION) { + sDefaultPreferences[key] = + Preferences::GetBool(PREF_DOM_WORKERNOTIFICATION_ENABLED, false); } else if (key == WORKERPREF_SERVICEWORKERS) { key = WORKERPREF_SERVICEWORKERS; sDefaultPreferences[WORKERPREF_SERVICEWORKERS] = Preferences::GetBool(PREF_SERVICEWORKERS_ENABLED, false); } else if (key == WORKERPREF_INTERCEPTION_ENABLED) { key = WORKERPREF_INTERCEPTION_ENABLED; sDefaultPreferences[key] = Preferences::GetBool(PREF_INTERCEPTION_ENABLED, false); } - // This function should never be registered as a callback for a preference it - // does not handle. - MOZ_ASSERT(key != WORKERPREF_COUNT); RuntimeService* rts = RuntimeService::GetService(); if (rts) { rts->UpdateAllWorkerPreference(key, sDefaultPreferences[key]); } } void
--- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1240,16 +1240,23 @@ public: } bool InterceptionEnabled() const { AssertIsOnWorkerThread(); return mPreferences[WORKERPREF_INTERCEPTION_ENABLED]; } + + bool + DOMWorkerNotificationEnabled() const + { + AssertIsOnWorkerThread(); + return mPreferences[WORKERPREF_DOM_WORKERNOTIFICATION]; + } bool OnLine() const { AssertIsOnWorkerThread(); return mOnLine; }
--- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -194,16 +194,17 @@ struct JSSettings }; enum WorkerPreference { WORKERPREF_DUMP = 0, // browser.dom.window.dump.enabled WORKERPREF_DOM_CACHES, // dom.caches.enabled WORKERPREF_SERVICEWORKERS, // dom.serviceWorkers.enabled WORKERPREF_INTERCEPTION_ENABLED, // dom.serviceWorkers.interception.enabled + WORKERPREF_DOM_WORKERNOTIFICATION, // dom.webnotifications.workers.enabled WORKERPREF_COUNT }; // Implemented in WorkerPrivate.cpp struct WorkerLoadInfo { // All of these should be released in WorkerPrivateParent::ForgetMainThreadObjects.
--- a/dom/workers/test/mochitest.ini +++ b/dom/workers/test/mochitest.ini @@ -36,16 +36,20 @@ support-files = loadEncoding_worker.js location_worker.js longThread_worker.js multi_sharedWorker_frame.html multi_sharedWorker_sharedWorker.js navigator_languages_worker.js navigator_worker.js newError_worker.js + notification_worker.js + notification_worker_child-child.js + notification_worker_child-parent.js + notification_permission_worker.js onLine_worker.js onLine_worker_child.js onLine_worker_head.js promise_worker.js recursion_worker.js recursiveOnerror_worker.js relativeLoad_import.js relativeLoad_worker.js @@ -153,16 +157,19 @@ skip-if = (toolkit == 'gonk' && debug) # [test_location.html] [test_longThread.html] [test_multi_sharedWorker.html] [test_multi_sharedWorker_lifetimes.html] [test_navigator.html] [test_navigator_languages.html] skip-if = buildapp == 'mulet' [test_newError.html] +[test_notification.html] +[test_notification_child.html] +[test_notification_permission.html] [test_onLine.html] skip-if = (toolkit == 'gonk' && debug) #debug-only failure [test_performance_user_timing.html] [test_promise.html] [test_promise_resolved_with_string.html] [test_recursion.html] [test_recursiveOnerror.html] [test_relativeLoad.html]
new file mode 100644 --- /dev/null +++ b/dom/workers/test/notification_permission_worker.js @@ -0,0 +1,56 @@ +function info(message) { + dump("INFO: " + message + "\n"); +} + +function ok(test, message) { + postMessage({ type: 'ok', test: test, message: message }); +} + +function is(a, b, message) { + postMessage({ type: 'is', test1: a, test2: b, message: message }); +} + +if (self.Notification) { + var steps = [ + function (done) { + info("Test notification permission"); + ok(typeof Notification === "function", "Notification constructor exists"); + ok(Notification.permission === "denied", "Notification.permission is denied."); + + var n = new Notification("Hello"); + n.onerror = function(e) { + ok(true, "error called due to permission denied."); + done(); + } + }, + ]; + + onmessage = function(e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({type: 'finish'}); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (e) { + ok(false, "Test threw exception! " + nextTest + " " + e); + finishTest(); + } + })(steps); + } +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: 'finish' }); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/notification_worker.js @@ -0,0 +1,89 @@ +function ok(test, message) { + postMessage({ type: 'ok', test: test, message: message }); +} + +function is(a, b, message) { + postMessage({ type: 'is', test1: a, test2: b, message: message }); +} + +if (self.Notification) { + var steps = [ + function () { + ok(typeof Notification === "function", "Notification constructor exists"); + ok(Notification.permission, "Notification.permission exists"); + ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist"); + }, + + function (done) { + var options = { + dir: "auto", + lang: "", + body: "This is a notification body", + tag: "sometag", + icon: "icon.png" + }; + var notification = new Notification("This is a title", options); + + ok(notification !== undefined, "Notification exists"); + is(notification.onclick, null, "onclick() should be null"); + is(notification.onshow, null, "onshow() should be null"); + is(notification.onerror, null, "onerror() should be null"); + is(notification.onclose, null, "onclose() should be null"); + is(typeof notification.close, "function", "close() should exist"); + + is(notification.dir, options.dir, "auto should get set"); + is(notification.lang, options.lang, "lang should get set"); + is(notification.body, options.body, "body should get set"); + is(notification.tag, options.tag, "tag should get set"); + is(notification.icon, options.icon, "icon should get set"); + + // store notification in test context + this.notification = notification; + + notification.onshow = function () { + ok(true, "onshow handler should be called"); + done(); + }; + }, + + function (done) { + var notification = this.notification; + + notification.onclose = function () { + ok(true, "onclose handler should be called"); + done(); + }; + + notification.close(); + }, + ]; + + onmessage = function(e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({type: 'finish'}); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (e) { + ok(false, "Test threw exception! " + nextTest + " " + e); + finishTest(); + } + })(steps); + } +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: 'finish' }); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/notification_worker_child-child.js @@ -0,0 +1,92 @@ +function ok(test, message) { + postMessage({ type: 'ok', test: test, message: message }); +} + +function is(a, b, message) { + postMessage({ type: 'is', test1: a, test2: b, message: message }); +} + +if (self.Notification) { + var steps = [ + function () { + ok(typeof Notification === "function", "Notification constructor exists"); + ok(Notification.permission, "Notification.permission exists"); + ok(typeof Notification.requestPermission === "undefined", "Notification.requestPermission should not exist"); + //ok(typeof Notification.get === "function", "Notification.get exists"); + }, + + function (done) { + + var options = { + dir: "auto", + lang: "", + body: "This is a notification body", + tag: "sometag", + icon: "icon.png" + }; + var notification = new Notification("This is a title", options); + + ok(notification !== undefined, "Notification exists"); + is(notification.onclick, null, "onclick() should be null"); + is(notification.onshow, null, "onshow() should be null"); + is(notification.onerror, null, "onerror() should be null"); + is(notification.onclose, null, "onclose() should be null"); + is(typeof notification.close, "function", "close() should exist"); + + is(notification.dir, options.dir, "auto should get set"); + is(notification.lang, options.lang, "lang should get set"); + is(notification.body, options.body, "body should get set"); + is(notification.tag, options.tag, "tag should get set"); + is(notification.icon, options.icon, "icon should get set"); + + // store notification in test context + this.notification = notification; + + notification.onshow = function () { + ok(true, "onshow handler should be called"); + done(); + }; + }, + + function (done) { + var notification = this.notification; + + notification.onclose = function () { + ok(true, "onclose handler should be called"); + done(); + }; + + notification.close(); + }, + ]; + + onmessage = function(e) { + var context = {}; + (function executeRemainingTests(remainingTests) { + if (!remainingTests.length) { + postMessage({type: 'finish'}); + return; + } + + var nextTest = remainingTests.shift(); + var finishTest = executeRemainingTests.bind(null, remainingTests); + var startTest = nextTest.call.bind(nextTest, context, finishTest); + + try { + startTest(); + // if no callback was defined for test function, + // we must manually invoke finish to continue + if (nextTest.length === 0) { + finishTest(); + } + } catch (e) { + ok(false, "Test threw exception! " + nextTest + " " + e); + finishTest(); + } + })(steps); + } +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: 'finish' }); +} +
new file mode 100644 --- /dev/null +++ b/dom/workers/test/notification_worker_child-parent.js @@ -0,0 +1,26 @@ +function ok(test, message) { + postMessage({ type: 'ok', test: test, message: message }); +} + +function is(a, b, message) { + postMessage({ type: 'is', test1: a, test2: b, message: message }); +} + +if (self.Notification) { + var child = new Worker("notification_worker_child-child.js"); + child.onerror = function(e) { + ok(false, "Error loading child worker " + e); + postMessage({ type: 'finish' }); + } + + child.onmessage = function(e) { + postMessage(e.data); + } + + onmessage = function(e) { + child.postMessage('start'); + } +} else { + ok(true, "Notifications are not enabled in workers on the platform."); + postMessage({ type: 'finish' }); +}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/test_notification.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event."); + + function runTest() { + MockServices.register(); + var w = new Worker("notification_worker.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + {"set": [["dom.webnotifications.workers.enabled", true]]}, + runTest + ); +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/test_notification_child.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893 - Test Notifications in child workers.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event."); + function runTest() { + MockServices.register(); + var w = new Worker("notification_worker_child-parent.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + {"set": [["dom.webnotifications.workers.enabled", true]]}, + runTest + ); +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/test_notification_permission.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=916893 +--> +<head> + <title>Bug 916893 - Make sure error is fired on Notification if permission is denied.</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/MockServices.js"></script> + <script type="text/javascript" src="/tests/dom/tests/mochitest/notification/NotificationTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=916893">Bug 916893</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +<script type="text/javascript"> + SimpleTest.requestFlakyTimeout("Mock alert service dispatches show event."); + function runTest() { + MockServices.register(); + var w = new Worker("notification_permission_worker.js"); + w.onmessage = function(e) { + if (e.data.type === 'finish') { + SpecialPowers.setBoolPref("notification.prompt.testing.allow", true); + MockServices.unregister(); + SimpleTest.finish(); + } else if (e.data.type === 'ok') { + ok(e.data.test, e.data.message); + } else if (e.data.type === 'is') { + is(e.data.test1, e.data.test2, e.data.message); + } + } + + SimpleTest.waitForExplicitFinish(); + // turn on testing pref (used by notification.cpp, and mock the alerts + SpecialPowers.setBoolPref("notification.prompt.testing", true); + SpecialPowers.setBoolPref("notification.prompt.testing.allow", false); + w.postMessage('start') + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv( + {"set": [["dom.webnotifications.workers.enabled", true]]}, + runTest + ); +</script> +</body> +</html>
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4318,16 +4318,17 @@ pref("extensions.minCompatiblePlatformVe pref("network.buffer.cache.count", 24); pref("network.buffer.cache.size", 32768); // Desktop Notification pref("notification.feature.enabled", false); // Web Notification pref("dom.webnotifications.enabled", true); +pref("dom.webnotifications.workers.enabled", false); // Alert animation effect, name is disableSlidingEffect for backwards-compat. pref("alerts.disableSlidingEffect", false); // DOM full-screen API. pref("full-screen-api.enabled", false); pref("full-screen-api.allow-trusted-requests-only", true); pref("full-screen-api.pointer-lock.enabled", true);