Bug 1114554 - Patch 2 - ServiceWorkerRegistration.showNotification(). r=wchen,baku

Refactor creation and show dispatch so Notification constructor and showNotification can use it.
Move persistence to ShowInternal.
NotificationStorage calls callback async even when fetching from cache, simply to have similar semantics.
Calls to Notification::Get() are performed async since persistence is now async after being moved to ShowInternal().
Both are in accordance with the spec where the "append to list of notifications" operation is performed in the "show steps" which are performed in parallel from API invocations.
This commit is contained in:
Nikhil Marathe 2015-06-25 18:50:24 -07:00
parent 1c6b24d392
commit e70c925298
10 changed files with 466 additions and 140 deletions

View File

@ -73,3 +73,6 @@ MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN_TYPEERR, "{0} URL {1} must be either ht
MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.")
MSG_DEF(MSG_RESPONSE_HAS_VARY_STAR, 0, JSEXN_TYPEERR, "Invalid Response object with a 'Vary: *' header.")
MSG_DEF(MSG_BAD_FORMDATA, 0, JSEXN_TYPEERR, "Could not parse content as FormData.")
MSG_DEF(MSG_NO_ACTIVE_WORKER, 1, JSEXN_TYPEERR, "No active worker for scope {0}.")
MSG_DEF(MSG_NOTIFICATION_PERMISSION_DENIED, 0, JSEXN_TYPEERR, "Permission to show Notification denied.")
MSG_DEF(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER, 0, JSEXN_TYPEERR, "Notification constructor cannot be used in ServiceWorkerGlobalScope. Use registration.showNotification() instead.")

View File

@ -11,6 +11,7 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "mozilla/unused.h"
#include "nsContentUtils.h"
#include "nsIAlertsService.h"
#include "nsIAppsService.h"
@ -29,7 +30,7 @@
#include "nsIScriptSecurityManager.h"
#include "nsIXPConnect.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
#include "mozilla/Services.h"
#include "nsContentPermissionHelper.h"
#include "nsILoadContext.h"
@ -37,6 +38,7 @@
#include "nsIDOMDesktopNotification.h"
#endif
#include "ServiceWorkerManager.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
@ -87,10 +89,10 @@ public:
options.mMozbehavior.Init(aBehavior);
nsRefPtr<Notification> notification;
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
notification = Notification::CreateInternal(global,
aID,
notification = Notification::CreateInternal(aID,
aTitle,
options);
notification->BindToOwner(mWindow);
ErrorResult rv;
notification->InitFromBase64(aCx, aData, rv);
if (rv.Failed()) {
@ -169,6 +171,35 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback)
tmp->DropData();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
class NotificationGetRunnable final : public nsRunnable
{
const nsString mOrigin;
const nsString mTag;
nsCOMPtr<nsINotificationStorageCallback> mCallback;
public:
NotificationGetRunnable(const nsAString& aOrigin,
const nsAString& aTag,
nsINotificationStorageCallback* aCallback)
: mOrigin(aOrigin), mTag(aTag), mCallback(aCallback)
{}
NS_IMETHOD
Run() override
{
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = notificationStorage->Get(mOrigin, mTag, mCallback);
//XXXnsm Is it guaranteed mCallback will be called in case of failure?
unused << NS_WARN_IF(NS_FAILED(rv));
return rv;
}
};
class NotificationPermissionRequest : public nsIContentPermissionRequest,
public nsIRunnable
{
@ -275,7 +306,23 @@ public:
return NS_OK;
}
};
} // namespace
nsresult
CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
nsCOMPtr<nsIURI> scopeURI;
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return aPrincipal->CheckMayLoad(scopeURI, /* report = */ true,
/* allowIfInheritsPrincipal = */ false);
}
} // anonymous namespace
// Subclass that can be directly dispatched to child workers from the main
// thread.
@ -417,7 +464,8 @@ public:
};
NotificationTask(UniquePtr<NotificationRef> aRef, NotificationAction aAction)
: mNotificationRef(Move(aRef)), mAction(aAction) {}
: mNotificationRef(Move(aRef)), mAction(aAction)
{}
NS_IMETHOD
Run() override;
@ -624,28 +672,26 @@ Notification::IsGetEnabled(JSContext* aCx, JSObject* aObj)
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, nsIGlobalObject* aGlobal)
const NotificationBehavior& aBehavior)
: 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), mTaskCount(0)
{
nsAutoString alertName;
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 {
if (!NS_IsMainThread()) {
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(mWorkerPrivate);
DebugOnly<nsresult> rv = GetOriginWorker(alertName);
MOZ_ASSERT(NS_SUCCEEDED(rv), "GetOrigin should not have failed");
}
}
void
Notification::SetAlertName()
{
AssertIsOnMainThread();
nsAutoString alertName;
DebugOnly<nsresult> rv = GetOrigin(GetPrincipal(), 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.
@ -669,45 +715,27 @@ Notification::Constructor(const GlobalObject& aGlobal,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
// FIXME(nsm): If the sticky flag is set, throw an error.
ServiceWorkerGlobalScope* scope = nullptr;
UNWRAP_WORKER_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
if (scope) {
aRv.ThrowTypeError(MSG_NOTIFICATION_NO_CONSTRUCTOR_IN_SERVICEWORKER);
return nullptr;
}
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()) {
nsRefPtr<Notification> notification =
CreateAndShow(global, aTitle, aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
auto ref = MakeUnique<NotificationRef>(notification);
if (!ref->Initialized()) {
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
// Queue a task to show the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(Move(ref), NotificationTask::eShow);
nsresult rv = NS_DispatchToMainThread(showNotificationTask);
if (NS_FAILED(rv)) {
notification->DispatchTrustedEvent(NS_LITERAL_STRING("error"));
}
//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");
}
}
// This is be ok since we are on the worker thread where this function will
// run to completion before the Notification has a chance to go away.
return notification.forget();
}
nsresult
Notification::PersistNotification()
{
@ -720,17 +748,8 @@ Notification::PersistNotification()
}
nsString origin;
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;
}
}
rv = GetOrigin(GetPrincipal(), origin);
MOZ_ASSERT(NS_SUCCEEDED(rv));
nsString id;
GetID(id);
@ -774,12 +793,12 @@ void
Notification::UnpersistNotification()
{
AssertIsOnMainThread();
if (mIsStored) {
if (IsStored()) {
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
if (notificationStorage) {
nsString origin;
nsresult rv = GetOrigin(GetOwner(), origin);
nsresult rv = GetOrigin(GetPrincipal(), origin);
if (NS_SUCCEEDED(rv)) {
notificationStorage->Delete(origin, mID);
}
@ -789,8 +808,7 @@ Notification::UnpersistNotification()
}
already_AddRefed<Notification>
Notification::CreateInternal(nsIGlobalObject* aGlobal,
const nsAString& aID,
Notification::CreateInternal(const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions)
{
@ -818,8 +836,7 @@ Notification::CreateInternal(nsIGlobalObject* aGlobal,
aOptions.mLang,
aOptions.mTag,
aOptions.mIcon,
aOptions.mMozbehavior,
aGlobal);
aOptions.mMozbehavior);
return notification.forget();
}
@ -1075,13 +1092,9 @@ Notification::ShowInternal()
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");
}
nsresult rv = PersistNotification();
if (NS_FAILED(rv)) {
NS_WARNING("Could not persist Notification");
}
nsCOMPtr<nsIAlertsService> alertService =
@ -1095,8 +1108,6 @@ Notification::ShowInternal()
permission = GetPermissionInternal(GetOwner(), result);
}
if (permission != NotificationPermission::Granted || !alertService) {
// We do not have permission to show a notification or alert service
// is not available.
if (mWorkerPrivate) {
nsRefPtr<NotificationEventWorkerRunnable> r =
new NotificationEventWorkerRunnable(this,
@ -1155,7 +1166,7 @@ Notification::ShowInternal()
AppNotificationServiceOptions ops;
ops.mTextClickable = true;
ops.mManifestURL = manifestUrl;
ops.mId = mAlertName;
GetAlertName(ops.mId);
ops.mDbId = mID;
ops.mDir = DirectionToString(mDir);
ops.mLang = mLang;
@ -1197,8 +1208,11 @@ Notification::ShowInternal()
getter_AddRefs(loadContext));
inPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
}
nsAutoString alertName;
GetAlertName(alertName);
alertService->ShowAlertNotification(iconUrl, mTitle, mBody, true,
uniqueCookie, observer, mAlertName,
uniqueCookie, observer, alertName,
DirectionToString(mDir), mLang,
dataStr, GetPrincipal(),
inPrivateBrowsing);
@ -1238,11 +1252,20 @@ Notification::RequestPermission(const GlobalObject& aGlobal,
NS_DispatchToMainThread(request);
}
// static
NotificationPermission
Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
return GetPermission(global, aRv);
}
// static
NotificationPermission
Notification::GetPermission(nsIGlobalObject* aGlobal, ErrorResult& aRv)
{
if (NS_IsMainThread()) {
return GetPermissionInternal(aGlobal.GetAsSupports(), aRv);
return GetPermissionInternal(aGlobal, aRv);
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
@ -1388,34 +1411,32 @@ Notification::Get(const GlobalObject& aGlobal,
MOZ_ASSERT(global);
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
MOZ_ASSERT(window);
nsIDocument* doc = window->GetExtantDoc();
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (!doc) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsString origin;
aRv = GetOrigin(window, origin);
aRv = GetOrigin(doc->NodePrincipal(), origin);
if (aRv.Failed()) {
return nullptr;
}
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
nsCOMPtr<nsINotificationStorageCallback> callback =
new NotificationStorageCallback(aGlobal, window, promise);
aRv = notificationStorage->Get(origin, aFilter.mTag, callback);
if (aRv.Failed()) {
nsRefPtr<NotificationGetRunnable> r =
new NotificationGetRunnable(origin, aFilter.mTag, callback);
aRv = NS_DispatchToMainThread(r);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@ -1462,30 +1483,25 @@ Notification::CloseInternal()
nsCOMPtr<nsIAlertsService> alertService =
do_GetService(NS_ALERTSERVICE_CONTRACTID);
if (alertService) {
alertService->CloseAlert(mAlertName, GetPrincipal());
nsAutoString alertName;
GetAlertName(alertName);
alertService->CloseAlert(alertName, GetPrincipal());
}
}
}
nsresult
Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin)
{
if (!aWindow) {
return NS_ERROR_FAILURE;
}
MOZ_ASSERT(aPrincipal);
uint16_t appStatus = aPrincipal->GetAppStatus();
uint32_t appId = aPrincipal->GetAppId();
nsresult rv;
nsIDocument* doc = aWindow->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsIPrincipal* principal = doc->NodePrincipal();
NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
uint16_t appStatus = principal->GetAppStatus();
uint32_t appId = principal->GetAppId();
if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED ||
appId == nsIScriptSecurityManager::NO_APP_ID ||
appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
rv = nsContentUtils::GetUTFOrigin(aPrincipal, aOrigin);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// If we are in "app code", use manifest URL as unique origin since
@ -1499,14 +1515,6 @@ Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
return NS_OK;
}
nsresult
Notification::GetOriginWorker(nsString& aOrigin)
{
MOZ_ASSERT(mWorkerPrivate);
aOrigin = mWorkerPrivate->GetLocationInfo().mOrigin;
return NS_OK;
}
nsIStructuredCloneContainer* Notification::GetDataCloneContainer()
{
return mDataObjectContainer;
@ -1661,6 +1669,187 @@ Notification::UnregisterFeature()
mFeature = nullptr;
}
/*
* Checks:
* 1) Is aWorker allowed to show a notification for scope?
* 2) Is aWorker an active worker?
*
* If it is not an active worker, Result() will be NS_ERROR_NOT_AVAILABLE.
*/
class CheckLoadRunnable final : public WorkerMainThreadRunnable
{
nsresult mRv;
nsCString mScope;
public:
explicit CheckLoadRunnable(WorkerPrivate* aWorker, const nsACString& aScope)
: WorkerMainThreadRunnable(aWorker)
, mRv(NS_ERROR_DOM_SECURITY_ERR)
, mScope(aScope)
{ }
bool
MainThreadRun() override
{
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
mRv = CheckScope(principal, mScope);
if (NS_FAILED(mRv)) {
return true;
}
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
nsRefPtr<ServiceWorkerRegistrationInfo> registration =
swm->GetRegistration(principal, mScope);
// This is coming from a ServiceWorkerRegistrationWorkerThread.
MOZ_ASSERT(registration);
if (!registration->mActiveWorker ||
registration->mActiveWorker->ID() != mWorkerPrivate->ServiceWorkerID()) {
mRv = NS_ERROR_NOT_AVAILABLE;
}
return true;
}
nsresult
Result()
{
return mRv;
}
};
/* static */
already_AddRefed<Promise>
Notification::ShowPersistentNotification(nsIGlobalObject *aGlobal,
const nsAString& aScope,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
MOZ_ASSERT(aGlobal);
// Validate scope.
// XXXnsm: This may be slow due to blocking the worker and waiting on the main
// thread. On calls from content, we can be sure the scope is valid since
// ServiceWorkerRegistrations have their scope set correctly. Can this be made
// debug only? The problem is that there would be different semantics in
// debug and non-debug builds in such a case.
if (NS_IsMainThread()) {
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aGlobal);
if (NS_WARN_IF(!sop)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsIPrincipal* principal = sop->GetPrincipal();
if (NS_WARN_IF(!principal)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope));
if (NS_WARN_IF(aRv.Failed())) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
nsRefPtr<CheckLoadRunnable> loadChecker =
new CheckLoadRunnable(worker, NS_ConvertUTF16toUTF8(aScope));
if (!loadChecker->Dispatch(worker->GetJSContext())) {
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
aRv.ThrowTypeError(MSG_NO_ACTIVE_WORKER);
} else {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
}
return nullptr;
}
}
nsRefPtr<Promise> p = Promise::Create(aGlobal, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// We check permission here rather than pass the Promise to NotificationTask
// which leads to uglier code.
NotificationPermission permission = GetPermission(aGlobal, aRv);
// "If permission for notifications origin is not "granted", reject promise with a TypeError exception, and terminate these substeps."
if (NS_WARN_IF(aRv.Failed()) || permission == NotificationPermission::Denied) {
ErrorResult result;
result.ThrowTypeError(MSG_NOTIFICATION_PERMISSION_DENIED);
p->MaybeReject(result);
return p.forget();
}
// "Otherwise, resolve promise with undefined."
// The Notification may still not be shown due to other errors, but the spec
// is not concerned with those.
p->MaybeResolve(JS::UndefinedHandleValue);
nsRefPtr<Notification> notification =
CreateAndShow(aGlobal, aTitle, aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return p.forget();
}
/* static */ already_AddRefed<Notification>
Notification::CreateAndShow(nsIGlobalObject* aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
MOZ_ASSERT(aGlobal);
AutoJSAPI jsapi;
jsapi.Init(aGlobal);
JSContext* cx = jsapi.cx();
nsRefPtr<Notification> notification = CreateInternal(EmptyString(),
aTitle,
aOptions);
notification->BindToOwner(aGlobal);
// Make a structured clone of the aOptions.mData object
JS::Rooted<JS::Value> data(cx, aOptions.mData);
notification->InitFromJSVal(cx, data, aRv);
if (aRv.Failed()) {
return nullptr;
}
auto ref = MakeUnique<NotificationRef>(notification);
if (!ref->Initialized()) {
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
// Queue a task to show the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(Move(ref), NotificationTask::eShow);
nsresult rv = NS_DispatchToMainThread(showNotificationTask);
if (NS_WARN_IF(NS_FAILED(rv))) {
notification->DispatchTrustedEvent(NS_LITERAL_STRING("error"));
}
return notification.forget();
}
} // namespace dom
} // namespace mozilla

View File

@ -193,6 +193,15 @@ public:
const GetNotificationOptions& aFilter,
ErrorResult& aRv);
// Notification implementation of
// ServiceWorkerRegistration.showNotification.
static already_AddRefed<Promise>
ShowPersistentNotification(nsIGlobalObject* aGlobal,
const nsAString& aScope,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv);
void Close();
nsPIDOMWindow* GetParentObject()
@ -233,18 +242,22 @@ public:
bool AddRefObject();
void ReleaseObject();
static NotificationPermission GetPermission(nsIGlobalObject* aGlobal,
ErrorResult& aRv);
static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
ErrorResult& rv);
bool DispatchClickEvent();
protected:
// Callers MUST bind the Notification to the correct DOMEventTargetHelper parent using
// BindToOwner().
Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl,
const NotificationBehavior& aBehavior, nsIGlobalObject* aGlobal);
const NotificationBehavior& aBehavior);
static already_AddRefed<Notification> CreateInternal(nsIGlobalObject* aGlobal,
const nsAString& aID,
static already_AddRefed<Notification> CreateInternal(const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions);
@ -277,11 +290,14 @@ protected:
return NotificationDirection::Auto;
}
static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
nsresult GetOriginWorker(nsString& aOrigin);
static nsresult GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin);
void GetAlertName(nsAString& aRetval)
{
workers::AssertIsOnMainThread();
if (mAlertName.IsEmpty()) {
SetAlertName();
}
aRetval = mAlertName;
}
@ -314,11 +330,24 @@ protected:
private:
virtual ~Notification();
// Creates a Notification and shows it. Returns a reference to the
// Notification if result is NS_OK. The lifetime of this Notification is tied
// to an underlying NotificationRef. Do not hold a non-stack raw pointer to
// it. Be careful about thread safety if acquiring a strong reference.
static already_AddRefed<Notification>
CreateAndShow(nsIGlobalObject* aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv);
nsIPrincipal* GetPrincipal();
nsresult PersistNotification();
void UnpersistNotification();
void
SetAlertName();
bool IsTargetThread() const
{
return NS_IsMainThread() == !mWorkerPrivate;

View File

@ -211,23 +211,29 @@ NotificationStorage.prototype = {
}
// Pass each notification back separately.
// The callback is called asynchronously to match the behaviour when
// fetching from the database.
notifications.forEach(function(notification) {
try {
callback.handle(notification.id,
notification.title,
notification.dir,
notification.lang,
notification.body,
notification.tag,
notification.icon,
notification.data,
notification.mozbehavior);
Services.tm.currentThread.dispatch(
callback.handle.bind(callback,
notification.id,
notification.title,
notification.dir,
notification.lang,
notification.body,
notification.tag,
notification.icon,
notification.data,
notification.mozbehavior),
Ci.nsIThread.DISPATCH_NORMAL);
} catch (e) {
if (DEBUG) { debug("Error calling callback handle: " + e); }
}
});
try {
callback.done();
Services.tm.currentThread.dispatch(callback.done,
Ci.nsIThread.DISPATCH_NORMAL);
} catch (e) {
if (DEBUG) { debug("Error calling callback done: " + e); }
}

View File

@ -94,6 +94,8 @@ enum NotificationDirection {
};
partial interface ServiceWorkerRegistration {
[Throws]
Promise<void> showNotification(DOMString title, optional NotificationOptions options);
[Throws]
Promise<sequence<Notification>> getNotifications(optional GetNotificationOptions filter);
};

View File

@ -6,6 +6,7 @@
#include "ServiceWorkerRegistration.h"
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/dom/ServiceWorkerRegistrationBinding.h"
@ -599,14 +600,42 @@ ServiceWorkerRegistrationMainThread::Unregister(ErrorResult& aRv)
already_AddRefed<Promise>
ServiceWorkerRegistrationMainThread::ShowNotification(JSContext* aCx,
const nsAString& aTitle,
const NotificationOptions& aOptions)
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
MOZ_ASSERT(false);
return nullptr;
AssertIsOnMainThread();
MOZ_ASSERT(GetOwner());
nsCOMPtr<nsPIDOMWindow> window = GetOwner();
if (NS_WARN_IF(!window)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (NS_WARN_IF(!doc)) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsRefPtr<workers::ServiceWorker> worker = GetActive();
if (!worker) {
aRv.ThrowTypeError(MSG_NO_ACTIVE_WORKER);
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(window);
nsRefPtr<Promise> p =
Notification::ShowPersistentNotification(global,
mScope, aTitle, aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return p.forget();
}
already_AddRefed<Promise>
ServiceWorkerRegistrationMainThread::GetNotifications(const GetNotificationOptions& aOptions)
ServiceWorkerRegistrationMainThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv)
{
MOZ_ASSERT(false);
return nullptr;
@ -1022,14 +1051,25 @@ WorkerListener::UpdateFound()
already_AddRefed<Promise>
ServiceWorkerRegistrationWorkerThread::ShowNotification(JSContext* aCx,
const nsAString& aTitle,
const NotificationOptions& aOptions)
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
MOZ_ASSERT(false);
return nullptr;
// Until Bug 1131324 exposes ServiceWorkerContainer on workers,
// ShowPersistentNotification() checks for valid active worker while it is
// also verifying scope so that we block the worker on the main thread only
// once.
nsRefPtr<Promise> p =
Notification::ShowPersistentNotification(mWorkerPrivate->GlobalScope(),
mScope, aTitle, aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return p.forget();
}
already_AddRefed<Promise>
ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOptions& aOptions)
ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv)
{
MOZ_ASSERT(false);
return nullptr;

View File

@ -118,10 +118,11 @@ public:
already_AddRefed<Promise>
ShowNotification(JSContext* aCx,
const nsAString& aTitle,
const NotificationOptions& aOptions);
const NotificationOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
GetNotifications(const GetNotificationOptions& aOptions);
GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv);
already_AddRefed<workers::ServiceWorker>
GetInstalling() override;
@ -206,10 +207,11 @@ public:
already_AddRefed<Promise>
ShowNotification(JSContext* aCx,
const nsAString& aTitle,
const NotificationOptions& aOptions);
const NotificationOptions& aOptions,
ErrorResult& aRv);
already_AddRefed<Promise>
GetNotifications(const GetNotificationOptions& aOptions);
GetNotifications(const GetNotificationOptions& aOptions, ErrorResult& aRv);
already_AddRefed<workers::ServiceWorker>
GetInstalling() override;

View File

@ -103,6 +103,7 @@ support-files =
periodic/register.html
periodic/wait_for_update.html
periodic/unregister.html
notification_constructor_error.js
sanitize/frame.html
sanitize/register.html
sanitize/example_check_and_unregister.html
@ -217,6 +218,7 @@ skip-if = !debug
[test_request_context_xslt.html]
[test_scopes.html]
[test_sandbox_intercept.html]
[test_notification_constructor_error.html]
[test_sanitize.html]
[test_sanitize_domain.html]
[test_service_worker_allowed.html]

View File

@ -0,0 +1 @@
new Notification("Hi there");

View File

@ -0,0 +1,52 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug XXXXXXX - Check that Notification constructor throws in ServiceWorkerGlobalScope</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>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
function simpleRegister() {
return navigator.serviceWorker.register("notification_constructor_error.js", { scope: "notification_constructor_error/" }).then(function(swr) {
ok(false, "Registration should fail.");
}, function(e) {
ok(e.message.indexOf("Notification") != -1, "Registration should fail.");
});
}
function runTest() {
MockServices.register();
simpleRegister()
.then(function() {
MockServices.unregister();
SimpleTest.finish();
}).catch(function(e) {
ok(false, "Some test failed with error " + e);
MockServices.unregister();
SimpleTest.finish();
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
["notification.prompt.testing", true],
]}, runTest);
</script>
</pre>
</body>
</html>