mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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:
parent
b043678543
commit
d7c89d1814
@ -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.")
|
||||
|
@ -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);
|
||||
rv = GetOrigin(GetPrincipal(), 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;
|
||||
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");
|
||||
}
|
||||
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 notification’s 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
|
||||
|
||||
|
@ -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;
|
||||
|
@ -211,9 +211,13 @@ 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,
|
||||
Services.tm.currentThread.dispatch(
|
||||
callback.handle.bind(callback,
|
||||
notification.id,
|
||||
notification.title,
|
||||
notification.dir,
|
||||
notification.lang,
|
||||
@ -221,13 +225,15 @@ NotificationStorage.prototype = {
|
||||
notification.tag,
|
||||
notification.icon,
|
||||
notification.data,
|
||||
notification.mozbehavior);
|
||||
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); }
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
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);
|
||||
// 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;
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -0,0 +1 @@
|
||||
new Notification("Hi there");
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user