forked from mirrors/gecko-dev
2360 lines
76 KiB
C++
2360 lines
76 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
||
#include "mozilla/dom/Notification.h"
|
||
|
||
#include <utility>
|
||
|
||
#include "mozilla/BasePrincipal.h"
|
||
#include "mozilla/Components.h"
|
||
#include "mozilla/Encoding.h"
|
||
#include "mozilla/EventStateManager.h"
|
||
#include "mozilla/HoldDropJSObjects.h"
|
||
#include "mozilla/JSONStringWriteFuncs.h"
|
||
#include "mozilla/OwningNonNull.h"
|
||
#include "mozilla/Preferences.h"
|
||
#include "mozilla/StaticPrefs_dom.h"
|
||
#include "mozilla/Unused.h"
|
||
#include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
|
||
#include "mozilla/dom/BindingUtils.h"
|
||
#include "mozilla/dom/ContentChild.h"
|
||
#include "mozilla/dom/Document.h"
|
||
#include "mozilla/dom/Promise.h"
|
||
#include "mozilla/dom/PromiseWorkerProxy.h"
|
||
#include "mozilla/dom/QMResult.h"
|
||
#include "mozilla/dom/RootedDictionary.h"
|
||
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
|
||
#include "mozilla/dom/ServiceWorkerUtils.h"
|
||
#include "mozilla/dom/WorkerRunnable.h"
|
||
#include "mozilla/dom/WorkerScope.h"
|
||
#include "mozilla/dom/quota/ResultExtensions.h"
|
||
#include "Navigator.h"
|
||
#include "nsComponentManagerUtils.h"
|
||
#include "nsContentPermissionHelper.h"
|
||
#include "nsContentUtils.h"
|
||
#include "nsFocusManager.h"
|
||
#include "nsIAlertsService.h"
|
||
#include "nsIContentPermissionPrompt.h"
|
||
#include "nsILoadContext.h"
|
||
#include "nsINotificationStorage.h"
|
||
#include "nsIPermission.h"
|
||
#include "nsIPermissionManager.h"
|
||
#include "nsIPushService.h"
|
||
#include "nsIScriptError.h"
|
||
#include "nsIServiceWorkerManager.h"
|
||
#include "nsIUUIDGenerator.h"
|
||
#include "nsNetUtil.h"
|
||
#include "nsProxyRelease.h"
|
||
#include "nsServiceManagerUtils.h"
|
||
#include "nsStructuredCloneContainer.h"
|
||
#include "nsThreadUtils.h"
|
||
#include "nsXULAppAPI.h"
|
||
|
||
namespace mozilla::dom {
|
||
|
||
struct NotificationStrings {
|
||
const nsString mID;
|
||
const nsString mTitle;
|
||
const nsString mDir;
|
||
const nsString mLang;
|
||
const nsString mBody;
|
||
const nsString mTag;
|
||
const nsString mIcon;
|
||
const nsString mData;
|
||
const nsString mBehavior;
|
||
const nsString mServiceWorkerRegistrationScope;
|
||
};
|
||
|
||
class ScopeCheckingGetCallback : public nsINotificationStorageCallback {
|
||
const nsString mScope;
|
||
|
||
public:
|
||
explicit ScopeCheckingGetCallback(const nsAString& aScope) : mScope(aScope) {}
|
||
|
||
NS_IMETHOD Handle(const nsAString& aID, const nsAString& aTitle,
|
||
const nsAString& aDir, const nsAString& aLang,
|
||
const nsAString& aBody, const nsAString& aTag,
|
||
const nsAString& aIcon, const nsAString& aData,
|
||
const nsAString& aBehavior,
|
||
const nsAString& aServiceWorkerRegistrationScope) final {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(!aID.IsEmpty());
|
||
|
||
// Skip scopes that don't match when called from getNotifications().
|
||
if (!mScope.IsEmpty() && !mScope.Equals(aServiceWorkerRegistrationScope)) {
|
||
return NS_OK;
|
||
}
|
||
|
||
NotificationStrings strings = {
|
||
nsString(aID), nsString(aTitle),
|
||
nsString(aDir), nsString(aLang),
|
||
nsString(aBody), nsString(aTag),
|
||
nsString(aIcon), nsString(aData),
|
||
nsString(aBehavior), nsString(aServiceWorkerRegistrationScope),
|
||
};
|
||
|
||
mStrings.AppendElement(std::move(strings));
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHOD Done() override = 0;
|
||
|
||
protected:
|
||
virtual ~ScopeCheckingGetCallback() = default;
|
||
|
||
nsTArray<NotificationStrings> mStrings;
|
||
};
|
||
|
||
class NotificationStorageCallback final : public ScopeCheckingGetCallback {
|
||
public:
|
||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||
NS_DECL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
|
||
|
||
NotificationStorageCallback(nsIGlobalObject* aWindow, const nsAString& aScope,
|
||
Promise* aPromise)
|
||
: ScopeCheckingGetCallback(aScope), mWindow(aWindow), mPromise(aPromise) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aWindow);
|
||
MOZ_ASSERT(aPromise);
|
||
}
|
||
|
||
NS_IMETHOD Done() final {
|
||
AutoTArray<RefPtr<Notification>, 5> notifications;
|
||
|
||
for (uint32_t i = 0; i < mStrings.Length(); ++i) {
|
||
auto result = Notification::ConstructFromFields(
|
||
mWindow, mStrings[i].mID, mStrings[i].mTitle, mStrings[i].mDir,
|
||
mStrings[i].mLang, mStrings[i].mBody, mStrings[i].mTag,
|
||
mStrings[i].mIcon, mStrings[i].mData,
|
||
/* mStrings[i].mBehavior, not
|
||
* supported */
|
||
mStrings[i].mServiceWorkerRegistrationScope);
|
||
if (result.isErr()) {
|
||
continue;
|
||
}
|
||
RefPtr<Notification> n = result.unwrap();
|
||
n->SetStoredState(true);
|
||
notifications.AppendElement(n.forget());
|
||
}
|
||
|
||
mPromise->MaybeResolve(notifications);
|
||
return NS_OK;
|
||
}
|
||
|
||
private:
|
||
virtual ~NotificationStorageCallback() = default;
|
||
|
||
nsCOMPtr<nsIGlobalObject> mWindow;
|
||
RefPtr<Promise> mPromise;
|
||
const nsString mScope;
|
||
};
|
||
|
||
NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
|
||
NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
|
||
NS_IMPL_CYCLE_COLLECTION(NotificationStorageCallback, mWindow, mPromise);
|
||
|
||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
|
||
NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
|
||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||
NS_INTERFACE_MAP_END
|
||
|
||
class NotificationGetRunnable final : public Runnable {
|
||
const nsString mOrigin;
|
||
const nsString mTag;
|
||
nsCOMPtr<nsINotificationStorageCallback> mCallback;
|
||
|
||
public:
|
||
NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
|
||
nsINotificationStorageCallback* aCallback)
|
||
: Runnable("NotificationGetRunnable"),
|
||
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 ContentPermissionRequestBase,
|
||
public nsIRunnable,
|
||
public nsINamed {
|
||
public:
|
||
NS_DECL_NSIRUNNABLE
|
||
NS_DECL_ISUPPORTS_INHERITED
|
||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest,
|
||
ContentPermissionRequestBase)
|
||
|
||
// nsIContentPermissionRequest
|
||
NS_IMETHOD Cancel(void) override;
|
||
NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override;
|
||
|
||
NotificationPermissionRequest(nsIPrincipal* aPrincipal,
|
||
nsPIDOMWindowInner* aWindow, Promise* aPromise,
|
||
NotificationPermissionCallback* aCallback)
|
||
: ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns,
|
||
"desktop-notification"_ns),
|
||
mPermission(NotificationPermission::Default),
|
||
mPromise(aPromise),
|
||
mCallback(aCallback) {
|
||
MOZ_ASSERT(aPromise);
|
||
}
|
||
|
||
NS_IMETHOD GetName(nsACString& aName) override {
|
||
aName.AssignLiteral("NotificationPermissionRequest");
|
||
return NS_OK;
|
||
}
|
||
|
||
protected:
|
||
~NotificationPermissionRequest() = default;
|
||
|
||
MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise();
|
||
nsresult DispatchResolvePromise();
|
||
NotificationPermission mPermission;
|
||
RefPtr<Promise> mPromise;
|
||
RefPtr<NotificationPermissionCallback> mCallback;
|
||
};
|
||
|
||
namespace {
|
||
class ReleaseNotificationControlRunnable final
|
||
: public MainThreadWorkerControlRunnable {
|
||
Notification* mNotification;
|
||
|
||
public:
|
||
explicit ReleaseNotificationControlRunnable(Notification* aNotification)
|
||
: MainThreadWorkerControlRunnable("ReleaseNotificationControlRunnable"),
|
||
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, "Notification :: Get Permission"_ns),
|
||
mPermission(NotificationPermission::Denied) {}
|
||
|
||
bool MainThreadRun() override {
|
||
ErrorResult result;
|
||
mPermission = Notification::GetPermissionInternal(
|
||
mWorkerPrivate->GetPrincipal(), result);
|
||
return true;
|
||
}
|
||
|
||
NotificationPermission GetPermission() { return mPermission; }
|
||
};
|
||
|
||
class FocusWindowRunnable final : public Runnable {
|
||
nsMainThreadPtrHandle<nsPIDOMWindowInner> mWindow;
|
||
|
||
public:
|
||
explicit FocusWindowRunnable(
|
||
const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
|
||
: Runnable("FocusWindowRunnable"), mWindow(aWindow) {}
|
||
|
||
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
|
||
// bug 1535398.
|
||
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
|
||
AssertIsOnMainThread();
|
||
if (!mWindow->IsCurrentInnerWindow()) {
|
||
// Window has been closed, this observer is not valid anymore
|
||
return NS_OK;
|
||
}
|
||
|
||
nsCOMPtr<nsPIDOMWindowOuter> outerWindow = mWindow->GetOuterWindow();
|
||
nsFocusManager::FocusWindow(outerWindow, CallerType::System);
|
||
return NS_OK;
|
||
}
|
||
};
|
||
|
||
nsresult CheckScope(nsIPrincipal* aPrincipal, const nsACString& aScope,
|
||
uint64_t aWindowID) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aPrincipal);
|
||
|
||
nsCOMPtr<nsIURI> scopeURI;
|
||
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return rv;
|
||
}
|
||
|
||
return aPrincipal->CheckMayLoadWithReporting(
|
||
scopeURI,
|
||
/* allowIfInheritsPrincipal = */ false, aWindowID);
|
||
}
|
||
} // anonymous namespace
|
||
|
||
// Subclass that can be directly dispatched to child workers from the main
|
||
// thread.
|
||
class NotificationWorkerRunnable : public MainThreadWorkerRunnable {
|
||
protected:
|
||
explicit NotificationWorkerRunnable(
|
||
WorkerPrivate* aWorkerPrivate,
|
||
const char* aName = "NotificationWorkerRunnable")
|
||
: MainThreadWorkerRunnable(aName) {}
|
||
|
||
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
|
||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||
// WorkerScope might start dying at the moment. And WorkerRunInternal()
|
||
// should not be executed once WorkerScope is dying, since
|
||
// WorkerRunInternal() might access resources which already been freed
|
||
// during WorkerRef::Notify().
|
||
if (aWorkerPrivate->GlobalScope() &&
|
||
!aWorkerPrivate->GlobalScope()->IsDying()) {
|
||
WorkerRunInternal(aWorkerPrivate);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
virtual void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) = 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,
|
||
"NotificationEventWorkerRunnable"),
|
||
mNotification(aNotification),
|
||
mEventName(aEventName) {}
|
||
|
||
void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
|
||
mNotification->DispatchTrustedEvent(mEventName);
|
||
}
|
||
};
|
||
|
||
class ReleaseNotificationRunnable final : public NotificationWorkerRunnable {
|
||
Notification* mNotification;
|
||
|
||
public:
|
||
explicit ReleaseNotificationRunnable(Notification* aNotification)
|
||
: NotificationWorkerRunnable(aNotification->mWorkerPrivate,
|
||
"ReleaseNotificationRunnable"),
|
||
mNotification(aNotification) {}
|
||
|
||
bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
|
||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||
// ReleaseNotificationRunnable is only used in StrongWorkerRef's shutdown
|
||
// callback. At the moment, it is supposed to executing
|
||
// mNotification->ReleaseObject() safely even though the corresponding
|
||
// WorkerScope::IsDying() is true. It is unlike other
|
||
// NotificationWorkerRunnable.
|
||
WorkerRunInternal(aWorkerPrivate);
|
||
return true;
|
||
}
|
||
|
||
void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
|
||
mNotification->ReleaseObject();
|
||
}
|
||
|
||
nsresult Cancel() override {
|
||
mNotification->ReleaseObject();
|
||
return NS_OK;
|
||
}
|
||
};
|
||
|
||
// Create one whenever you require ownership of the notification. Use with
|
||
// UniquePtr<>. See Notification.h for details.
|
||
class NotificationRef final {
|
||
friend class WorkerNotificationObserver;
|
||
|
||
private:
|
||
Notification* mNotification;
|
||
bool mInited;
|
||
|
||
// 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();
|
||
}
|
||
|
||
mInited = mNotification->AddRefObject();
|
||
}
|
||
|
||
// This is only required because Gecko runs script in a worker's onclose
|
||
// handler (non-standard, Bug 790919) where calls to HoldWorker() will
|
||
// fail. Due to non-standardness and added complications if we decide to
|
||
// support this, attempts to create a Notification in onclose just throw
|
||
// exceptions.
|
||
bool Initialized() { return mInited; }
|
||
|
||
~NotificationRef() {
|
||
if (Initialized() && mNotification) {
|
||
Notification* notification = mNotification;
|
||
mNotification = nullptr;
|
||
if (notification->mWorkerPrivate && NS_IsMainThread()) {
|
||
// 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 or been canceled. We can use a control
|
||
// runnable to release the reference.
|
||
RefPtr<ReleaseNotificationRunnable> r =
|
||
new ReleaseNotificationRunnable(notification);
|
||
|
||
if (!r->Dispatch(notification->mWorkerPrivate)) {
|
||
RefPtr<ReleaseNotificationControlRunnable> r =
|
||
new ReleaseNotificationControlRunnable(notification);
|
||
MOZ_ALWAYS_TRUE(r->Dispatch(notification->mWorkerPrivate));
|
||
}
|
||
} else {
|
||
notification->AssertIsOnTargetThread();
|
||
notification->ReleaseObject();
|
||
}
|
||
}
|
||
}
|
||
|
||
// XXXnsm, is it worth having some sort of WeakPtr like wrapper instead of
|
||
// a rawptr that the NotificationRef can invalidate?
|
||
Notification* GetNotification() {
|
||
MOZ_ASSERT(Initialized());
|
||
return mNotification;
|
||
}
|
||
};
|
||
|
||
class NotificationTask : public Runnable {
|
||
public:
|
||
enum NotificationAction { eShow, eClose };
|
||
|
||
NotificationTask(const char* aName, UniquePtr<NotificationRef> aRef,
|
||
NotificationAction aAction)
|
||
: Runnable(aName), mNotificationRef(std::move(aRef)), mAction(aAction) {}
|
||
|
||
NS_IMETHOD
|
||
Run() override;
|
||
|
||
protected:
|
||
virtual ~NotificationTask() = default;
|
||
|
||
UniquePtr<NotificationRef> mNotificationRef;
|
||
NotificationAction mAction;
|
||
};
|
||
|
||
uint32_t Notification::sCount = 0;
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest,
|
||
ContentPermissionRequestBase, mCallback)
|
||
NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest,
|
||
ContentPermissionRequestBase)
|
||
NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest,
|
||
ContentPermissionRequestBase)
|
||
|
||
NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(
|
||
NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable,
|
||
nsINamed)
|
||
|
||
NS_IMETHODIMP
|
||
NotificationPermissionRequest::Run() {
|
||
bool isSystem = mPrincipal->IsSystemPrincipal();
|
||
bool blocked = false;
|
||
if (isSystem) {
|
||
mPermission = NotificationPermission::Granted;
|
||
} else if (
|
||
mPrincipal->GetPrivateBrowsingId() != 0 &&
|
||
!StaticPrefs::
|
||
dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
|
||
mPermission = NotificationPermission::Denied;
|
||
blocked = true;
|
||
} else {
|
||
// File are automatically granted permission.
|
||
|
||
if (mPrincipal->SchemeIs("file")) {
|
||
mPermission = NotificationPermission::Granted;
|
||
} else if (!mWindow->IsSecureContext()) {
|
||
mPermission = NotificationPermission::Denied;
|
||
blocked = true;
|
||
nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
|
||
if (doc) {
|
||
nsContentUtils::ReportToConsole(
|
||
nsIScriptError::errorFlag, "DOM"_ns, doc,
|
||
nsContentUtils::eDOM_PROPERTIES,
|
||
"NotificationsInsecureRequestIsForbidden");
|
||
}
|
||
}
|
||
}
|
||
|
||
// We can't call ShowPrompt() directly here since our logic for determining
|
||
// whether to display a prompt depends on the checks above as well as the
|
||
// result of CheckPromptPrefs(). So we have to manually check the prompt
|
||
// prefs and decide what to do based on that.
|
||
PromptResult pr = CheckPromptPrefs();
|
||
switch (pr) {
|
||
case PromptResult::Granted:
|
||
mPermission = NotificationPermission::Granted;
|
||
break;
|
||
case PromptResult::Denied:
|
||
mPermission = NotificationPermission::Denied;
|
||
break;
|
||
default:
|
||
// ignore
|
||
break;
|
||
}
|
||
|
||
if (!mHasValidTransientUserGestureActivation &&
|
||
!StaticPrefs::dom_webnotifications_requireuserinteraction()) {
|
||
nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
|
||
if (doc) {
|
||
doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation);
|
||
}
|
||
}
|
||
|
||
// Check this after checking the prompt prefs to make sure this pref overrides
|
||
// those. We rely on this for testing purposes.
|
||
if (!isSystem && !blocked &&
|
||
!StaticPrefs::dom_webnotifications_allowcrossoriginiframe() &&
|
||
!mPrincipal->Subsumes(mTopLevelPrincipal)) {
|
||
mPermission = NotificationPermission::Denied;
|
||
blocked = true;
|
||
nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
|
||
if (doc) {
|
||
nsContentUtils::ReportToConsole(
|
||
nsIScriptError::errorFlag, "DOM"_ns, doc,
|
||
nsContentUtils::eDOM_PROPERTIES,
|
||
"NotificationsCrossOriginIframeRequestIsForbidden");
|
||
}
|
||
}
|
||
|
||
if (mPermission != NotificationPermission::Default) {
|
||
return DispatchResolvePromise();
|
||
}
|
||
|
||
return nsContentPermissionUtils::AskPermission(this, mWindow);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
NotificationPermissionRequest::Cancel() {
|
||
// `Cancel` is called if the user denied permission or dismissed the
|
||
// permission request. To distinguish between the two, we set the
|
||
// permission to "default" and query the permission manager in
|
||
// `ResolvePromise`.
|
||
mPermission = NotificationPermission::Default;
|
||
return DispatchResolvePromise();
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) {
|
||
MOZ_ASSERT(aChoices.isUndefined());
|
||
|
||
mPermission = NotificationPermission::Granted;
|
||
return DispatchResolvePromise();
|
||
}
|
||
|
||
inline nsresult NotificationPermissionRequest::DispatchResolvePromise() {
|
||
nsCOMPtr<nsIRunnable> resolver =
|
||
NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise",
|
||
this, &NotificationPermissionRequest::ResolvePromise);
|
||
return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget());
|
||
}
|
||
|
||
nsresult NotificationPermissionRequest::ResolvePromise() {
|
||
nsresult rv = NS_OK;
|
||
// This will still be "default" if the user dismissed the doorhanger,
|
||
// or "denied" otherwise.
|
||
if (mPermission == NotificationPermission::Default) {
|
||
// When the front-end has decided to deny the permission request
|
||
// automatically and we are not handling user input, then log a
|
||
// warning in the current document that this happened because
|
||
// Notifications require a user gesture.
|
||
if (!mHasValidTransientUserGestureActivation &&
|
||
StaticPrefs::dom_webnotifications_requireuserinteraction()) {
|
||
nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
|
||
if (doc) {
|
||
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns,
|
||
doc, nsContentUtils::eDOM_PROPERTIES,
|
||
"NotificationsRequireUserGesture");
|
||
}
|
||
}
|
||
|
||
mPermission = Notification::TestPermission(mPrincipal);
|
||
}
|
||
if (mCallback) {
|
||
ErrorResult error;
|
||
RefPtr<NotificationPermissionCallback> callback(mCallback);
|
||
callback->Call(mPermission, error);
|
||
rv = error.StealNSResult();
|
||
}
|
||
mPromise->MaybeResolve(mPermission);
|
||
return rv;
|
||
}
|
||
|
||
// Observer that the alert service calls to do common tasks and/or dispatch to
|
||
// the specific observer for the context e.g. main thread, worker, or service
|
||
// worker.
|
||
class NotificationObserver final : public nsIObserver {
|
||
public:
|
||
nsCOMPtr<nsIObserver> mObserver;
|
||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||
bool mInPrivateBrowsing;
|
||
NS_DECL_ISUPPORTS
|
||
NS_DECL_NSIOBSERVER
|
||
|
||
NotificationObserver(nsIObserver* aObserver, nsIPrincipal* aPrincipal,
|
||
bool aInPrivateBrowsing)
|
||
: mObserver(aObserver),
|
||
mPrincipal(aPrincipal),
|
||
mInPrivateBrowsing(aInPrivateBrowsing) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(mObserver);
|
||
MOZ_ASSERT(mPrincipal);
|
||
}
|
||
|
||
protected:
|
||
virtual ~NotificationObserver() { AssertIsOnMainThread(); }
|
||
|
||
nsresult AdjustPushQuota(const char* aTopic);
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
|
||
|
||
class MainThreadNotificationObserver : public nsIObserver {
|
||
public:
|
||
UniquePtr<NotificationRef> mNotificationRef;
|
||
NS_DECL_ISUPPORTS
|
||
NS_DECL_NSIOBSERVER
|
||
|
||
explicit MainThreadNotificationObserver(UniquePtr<NotificationRef> aRef)
|
||
: mNotificationRef(std::move(aRef)) {
|
||
AssertIsOnMainThread();
|
||
}
|
||
|
||
protected:
|
||
virtual ~MainThreadNotificationObserver() { AssertIsOnMainThread(); }
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS(MainThreadNotificationObserver, nsIObserver)
|
||
|
||
NS_IMETHODIMP
|
||
NotificationTask::Run() {
|
||
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");
|
||
}
|
||
|
||
MOZ_ASSERT(!mNotificationRef);
|
||
return NS_OK;
|
||
}
|
||
|
||
// static
|
||
bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) {
|
||
return StaticPrefs::dom_webnotifications_enabled();
|
||
}
|
||
|
||
Notification::Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
|
||
const nsAString& aTitle, const nsAString& aBody,
|
||
NotificationDirection aDir, const nsAString& aLang,
|
||
const nsAString& aTag, const nsAString& aIconUrl,
|
||
bool aRequireInteraction, bool aSilent,
|
||
nsTArray<uint32_t>&& aVibrate,
|
||
const NotificationBehavior& aBehavior)
|
||
: DOMEventTargetHelper(aGlobal),
|
||
mWorkerPrivate(nullptr),
|
||
mObserver(nullptr),
|
||
mID(aID),
|
||
mTitle(aTitle),
|
||
mBody(aBody),
|
||
mDir(aDir),
|
||
mLang(aLang),
|
||
mTag(aTag),
|
||
mIconUrl(aIconUrl),
|
||
mRequireInteraction(aRequireInteraction),
|
||
mSilent(aSilent),
|
||
mVibrate(std::move(aVibrate)),
|
||
mBehavior(aBehavior),
|
||
mData(JS::NullValue()),
|
||
mIsClosed(false),
|
||
mIsStored(false),
|
||
mTaskCount(0) {
|
||
if (!NS_IsMainThread()) {
|
||
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
|
||
MOZ_ASSERT(mWorkerPrivate);
|
||
}
|
||
}
|
||
|
||
nsresult Notification::MaybeObserveWindowFrozenOrDestroyed() {
|
||
// NOTE: Non-persistent notifications can also be opened from workers, but we
|
||
// don't care and nobody else cares. And it's not clear whether we even should
|
||
// do this for window at all, see
|
||
// https://github.com/whatwg/notifications/issues/204.
|
||
// TODO: Somehow extend GlobalTeardownObserver to deal with FROZEN_TOPIC?
|
||
if (!mWorkerPrivate) {
|
||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||
NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
|
||
|
||
nsresult rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, true);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
rv = obs->AddObserver(this, DOM_WINDOW_FROZEN_TOPIC, true);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
void Notification::SetAlertName() {
|
||
AssertIsOnMainThread();
|
||
if (!mAlertName.IsEmpty()) {
|
||
return;
|
||
}
|
||
|
||
nsAutoString alertName;
|
||
nsresult rv = GetOrigin(GetPrincipal(), alertName);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return;
|
||
}
|
||
|
||
// 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) {
|
||
// FIXME(nsm): If the sticky flag is set, throw an error.
|
||
RefPtr<ServiceWorkerGlobalScope> scope;
|
||
UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope);
|
||
if (scope) {
|
||
aRv.ThrowTypeError(
|
||
"Notification constructor cannot be used in ServiceWorkerGlobalScope. "
|
||
"Use registration.showNotification() instead.");
|
||
return nullptr;
|
||
}
|
||
|
||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
||
RefPtr<Notification> notification =
|
||
CreateAndShow(aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return nullptr;
|
||
}
|
||
if (NS_WARN_IF(
|
||
NS_FAILED(notification->MaybeObserveWindowFrozenOrDestroyed()))) {
|
||
return nullptr;
|
||
}
|
||
|
||
// 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();
|
||
}
|
||
|
||
// static
|
||
Result<already_AddRefed<Notification>, QMResult>
|
||
Notification::ConstructFromFields(
|
||
nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
|
||
const nsAString& aDir, const nsAString& aLang, const nsAString& aBody,
|
||
const nsAString& aTag, const nsAString& aIcon, const nsAString& aData,
|
||
const nsAString& aServiceWorkerRegistrationScope) {
|
||
MOZ_ASSERT(aGlobal);
|
||
|
||
RootedDictionary<NotificationOptions> options(RootingCx());
|
||
options.mDir = Notification::StringToDirection(nsString(aDir));
|
||
options.mLang = aLang;
|
||
options.mBody = aBody;
|
||
options.mTag = aTag;
|
||
options.mIcon = aIcon;
|
||
IgnoredErrorResult rv;
|
||
RefPtr<Notification> notification =
|
||
CreateInternal(aGlobal, aID, aTitle, options, rv);
|
||
if (NS_WARN_IF(rv.Failed())) {
|
||
return Err(ToQMResult(NS_ERROR_FAILURE));
|
||
}
|
||
|
||
QM_TRY(notification->InitFromBase64(aData));
|
||
|
||
notification->SetScope(aServiceWorkerRegistrationScope);
|
||
|
||
return notification.forget();
|
||
}
|
||
|
||
nsresult Notification::PersistNotification() {
|
||
AssertIsOnMainThread();
|
||
nsresult rv;
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
nsString origin;
|
||
rv = GetOrigin(GetPrincipal(), origin);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return rv;
|
||
}
|
||
|
||
nsString id;
|
||
GetID(id);
|
||
|
||
nsString alertName;
|
||
GetAlertName(alertName);
|
||
|
||
nsAutoString behavior;
|
||
if (!mBehavior.ToJSON(behavior)) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
rv = notificationStorage->Put(origin, id, mTitle, DirectionToString(mDir),
|
||
mLang, mBody, mTag, mIconUrl, alertName,
|
||
mDataAsBase64, behavior, mScope);
|
||
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
SetStoredState(true);
|
||
return NS_OK;
|
||
}
|
||
|
||
void Notification::UnpersistNotification() {
|
||
AssertIsOnMainThread();
|
||
if (IsStored()) {
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
|
||
if (notificationStorage) {
|
||
nsString origin;
|
||
nsresult rv = GetOrigin(GetPrincipal(), origin);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
notificationStorage->Delete(origin, mID);
|
||
}
|
||
}
|
||
SetStoredState(false);
|
||
}
|
||
}
|
||
|
||
already_AddRefed<Notification> Notification::CreateInternal(
|
||
nsIGlobalObject* aGlobal, const nsAString& aID, const nsAString& aTitle,
|
||
const NotificationOptions& aOptions, ErrorResult& aRv) {
|
||
nsresult rv;
|
||
nsString id;
|
||
if (!aID.IsEmpty()) {
|
||
id = aID;
|
||
} else {
|
||
nsCOMPtr<nsIUUIDGenerator> uuidgen =
|
||
do_GetService("@mozilla.org/uuid-generator;1");
|
||
NS_ENSURE_TRUE(uuidgen, nullptr);
|
||
nsID uuid;
|
||
rv = uuidgen->GenerateUUIDInPlace(&uuid);
|
||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||
|
||
char buffer[NSID_LENGTH];
|
||
uuid.ToProvidedString(buffer);
|
||
NS_ConvertASCIItoUTF16 convertedID(buffer);
|
||
id = convertedID;
|
||
}
|
||
|
||
bool silent = false;
|
||
if (StaticPrefs::dom_webnotifications_silent_enabled()) {
|
||
silent = aOptions.mSilent;
|
||
}
|
||
|
||
nsTArray<uint32_t> vibrate;
|
||
if (StaticPrefs::dom_webnotifications_vibrate_enabled() &&
|
||
aOptions.mVibrate.WasPassed()) {
|
||
if (silent) {
|
||
aRv.ThrowTypeError(
|
||
"Silent notifications must not specify vibration patterns.");
|
||
return nullptr;
|
||
}
|
||
|
||
const OwningUnsignedLongOrUnsignedLongSequence& value =
|
||
aOptions.mVibrate.Value();
|
||
if (value.IsUnsignedLong()) {
|
||
AutoTArray<uint32_t, 1> array;
|
||
array.AppendElement(value.GetAsUnsignedLong());
|
||
vibrate = SanitizeVibratePattern(array);
|
||
} else {
|
||
vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence());
|
||
}
|
||
}
|
||
|
||
RefPtr<Notification> notification = new Notification(
|
||
aGlobal, id, aTitle, aOptions.mBody, aOptions.mDir, aOptions.mLang,
|
||
aOptions.mTag, aOptions.mIcon, aOptions.mRequireInteraction, silent,
|
||
std::move(vibrate), aOptions.mMozbehavior);
|
||
return notification.forget();
|
||
}
|
||
|
||
Notification::~Notification() {
|
||
mozilla::DropJSObjects(this);
|
||
AssertIsOnTargetThread();
|
||
MOZ_ASSERT(!mWorkerRef);
|
||
MOZ_ASSERT(!mTempRef);
|
||
}
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_CLASS(Notification)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification,
|
||
DOMEventTargetHelper)
|
||
tmp->mData.setUndefined();
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification,
|
||
DOMEventTargetHelper)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification,
|
||
DOMEventTargetHelper)
|
||
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
|
||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||
|
||
NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper)
|
||
NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper)
|
||
|
||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification)
|
||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
|
||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||
|
||
nsIPrincipal* Notification::GetPrincipal() {
|
||
AssertIsOnMainThread();
|
||
if (mWorkerPrivate) {
|
||
return mWorkerPrivate->GetPrincipal();
|
||
} else {
|
||
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetOwner());
|
||
NS_ENSURE_TRUE(sop, nullptr);
|
||
return sop->GetPrincipal();
|
||
}
|
||
}
|
||
|
||
class WorkerNotificationObserver final : public MainThreadNotificationObserver {
|
||
public:
|
||
NS_INLINE_DECL_REFCOUNTING_INHERITED(WorkerNotificationObserver,
|
||
MainThreadNotificationObserver)
|
||
NS_DECL_NSIOBSERVER
|
||
|
||
explicit WorkerNotificationObserver(UniquePtr<NotificationRef> aRef)
|
||
: MainThreadNotificationObserver(std::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) {
|
||
notification->mObserver = nullptr;
|
||
}
|
||
}
|
||
};
|
||
|
||
class ServiceWorkerNotificationObserver final : public nsIObserver {
|
||
public:
|
||
NS_DECL_ISUPPORTS
|
||
NS_DECL_NSIOBSERVER
|
||
|
||
ServiceWorkerNotificationObserver(
|
||
const nsAString& aScope, nsIPrincipal* aPrincipal, const nsAString& aID,
|
||
const nsAString& aTitle, const nsAString& aDir, const nsAString& aLang,
|
||
const nsAString& aBody, const nsAString& aTag, const nsAString& aIcon,
|
||
const nsAString& aData, const nsAString& aBehavior)
|
||
: mScope(aScope),
|
||
mID(aID),
|
||
mPrincipal(aPrincipal),
|
||
mTitle(aTitle),
|
||
mDir(aDir),
|
||
mLang(aLang),
|
||
mBody(aBody),
|
||
mTag(aTag),
|
||
mIcon(aIcon),
|
||
mData(aData),
|
||
mBehavior(aBehavior) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aPrincipal);
|
||
}
|
||
|
||
private:
|
||
~ServiceWorkerNotificationObserver() = default;
|
||
|
||
const nsString mScope;
|
||
const nsString mID;
|
||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||
const nsString mTitle;
|
||
const nsString mDir;
|
||
const nsString mLang;
|
||
const nsString mBody;
|
||
const nsString mTag;
|
||
const nsString mIcon;
|
||
const nsString mData;
|
||
const nsString mBehavior;
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS(ServiceWorkerNotificationObserver, nsIObserver)
|
||
|
||
bool Notification::DispatchClickEvent() {
|
||
AssertIsOnTargetThread();
|
||
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
|
||
event->InitEvent(u"click"_ns, false, true);
|
||
event->SetTrusted(true);
|
||
WantsPopupControlCheck popupControlCheck(event);
|
||
return DispatchEvent(*event, CallerType::System, IgnoreErrors());
|
||
}
|
||
|
||
// 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<nsPIDOMWindowInner> mWindow;
|
||
|
||
public:
|
||
NotificationClickWorkerRunnable(
|
||
Notification* aNotification,
|
||
const nsMainThreadPtrHandle<nsPIDOMWindowInner>& aWindow)
|
||
: NotificationWorkerRunnable(aNotification->mWorkerPrivate,
|
||
"NotificationClickWorkerRunnable"),
|
||
mNotification(aNotification),
|
||
mWindow(aWindow) {
|
||
MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(), !mWindow);
|
||
}
|
||
|
||
void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
|
||
bool doDefaultAction = mNotification->DispatchClickEvent();
|
||
MOZ_ASSERT_IF(mNotification->mWorkerPrivate->IsServiceWorker(),
|
||
!doDefaultAction);
|
||
if (doDefaultAction) {
|
||
RefPtr<FocusWindowRunnable> r = new FocusWindowRunnable(mWindow);
|
||
mNotification->mWorkerPrivate->DispatchToMainThread(r.forget());
|
||
}
|
||
}
|
||
};
|
||
|
||
NS_IMETHODIMP
|
||
NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
|
||
const char16_t* aData) {
|
||
AssertIsOnMainThread();
|
||
|
||
if (!strcmp("alertdisablecallback", aTopic)) {
|
||
if (XRE_IsParentProcess()) {
|
||
return Notification::RemovePermission(mPrincipal);
|
||
}
|
||
// Permissions can't be removed from the content process. Send a message
|
||
// to the parent; `ContentParent::RecvDisableNotifications` will call
|
||
// `RemovePermission`.
|
||
ContentChild::GetSingleton()->SendDisableNotifications(mPrincipal);
|
||
return NS_OK;
|
||
} else if (!strcmp("alertsettingscallback", aTopic)) {
|
||
if (XRE_IsParentProcess()) {
|
||
return Notification::OpenSettings(mPrincipal);
|
||
}
|
||
// `ContentParent::RecvOpenNotificationSettings` notifies observers in the
|
||
// parent process.
|
||
ContentChild::GetSingleton()->SendOpenNotificationSettings(mPrincipal);
|
||
return NS_OK;
|
||
} else if (!strcmp("alertshow", aTopic) || !strcmp("alertfinished", aTopic)) {
|
||
Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
|
||
}
|
||
|
||
return mObserver->Observe(aSubject, aTopic, aData);
|
||
}
|
||
|
||
nsresult NotificationObserver::AdjustPushQuota(const char* aTopic) {
|
||
nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
|
||
do_GetService("@mozilla.org/push/Service;1");
|
||
if (!pushQuotaManager) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
nsAutoCString origin;
|
||
nsresult rv = mPrincipal->GetOrigin(origin);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
if (!strcmp("alertshow", aTopic)) {
|
||
return pushQuotaManager->NotificationForOriginShown(origin.get());
|
||
}
|
||
return pushQuotaManager->NotificationForOriginClosed(origin.get());
|
||
}
|
||
|
||
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
|
||
// bug 1539845.
|
||
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
|
||
MainThreadNotificationObserver::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<nsPIDOMWindowInner> window = notification->GetOwner();
|
||
if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
|
||
// Window has been closed, this observer is not valid anymore
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
bool doDefaultAction = notification->DispatchClickEvent();
|
||
if (doDefaultAction) {
|
||
nsCOMPtr<nsPIDOMWindowOuter> outerWindow = window->GetOuterWindow();
|
||
nsFocusManager::FocusWindow(outerWindow, CallerType::System);
|
||
}
|
||
} else if (!strcmp("alertfinished", aTopic)) {
|
||
notification->UnpersistNotification();
|
||
notification->mIsClosed = true;
|
||
notification->DispatchTrustedEvent(u"close"_ns);
|
||
} else if (!strcmp("alertshow", aTopic)) {
|
||
notification->DispatchTrustedEvent(u"show"_ns);
|
||
}
|
||
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 (NS_WARN_IF(!notification)) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
MOZ_ASSERT(notification->mWorkerPrivate);
|
||
|
||
RefPtr<WorkerThreadRunnable> r;
|
||
if (!strcmp("alertclickcallback", aTopic)) {
|
||
nsPIDOMWindowInner* window = nullptr;
|
||
if (!notification->mWorkerPrivate->IsServiceWorker()) {
|
||
WorkerPrivate* top = notification->mWorkerPrivate;
|
||
while (top->GetParent()) {
|
||
top = top->GetParent();
|
||
}
|
||
|
||
window = top->GetWindow();
|
||
if (NS_WARN_IF(!window || !window->IsCurrentInnerWindow())) {
|
||
// Window has been closed, this observer is not valid anymore
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
}
|
||
|
||
// Instead of bothering with adding features and other worker lifecycle
|
||
// management, we simply hold strongrefs to the window and document.
|
||
nsMainThreadPtrHandle<nsPIDOMWindowInner> windowHandle(
|
||
new nsMainThreadPtrHolder<nsPIDOMWindowInner>(
|
||
"WorkerNotificationObserver::Observe::nsPIDOMWindowInner", window));
|
||
|
||
r = new NotificationClickWorkerRunnable(notification, windowHandle);
|
||
} else if (!strcmp("alertfinished", aTopic)) {
|
||
notification->UnpersistNotification();
|
||
notification->mIsClosed = true;
|
||
r = new NotificationEventWorkerRunnable(notification, u"close"_ns);
|
||
} else if (!strcmp("alertshow", aTopic)) {
|
||
r = new NotificationEventWorkerRunnable(notification, u"show"_ns);
|
||
}
|
||
|
||
MOZ_ASSERT(r);
|
||
if (!r->Dispatch(notification->mWorkerPrivate)) {
|
||
NS_WARNING("Could not dispatch event to worker notification");
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
ServiceWorkerNotificationObserver::Observe(nsISupports* aSubject,
|
||
const char* aTopic,
|
||
const char16_t* aData) {
|
||
AssertIsOnMainThread();
|
||
|
||
nsAutoCString originSuffix;
|
||
nsresult rv = mPrincipal->GetOriginSuffix(originSuffix);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return rv;
|
||
}
|
||
|
||
if (!strcmp("alertclickcallback", aTopic)) {
|
||
if (XRE_IsParentProcess()) {
|
||
nsCOMPtr<nsIServiceWorkerManager> swm =
|
||
mozilla::components::ServiceWorkerManager::Service();
|
||
if (NS_WARN_IF(!swm)) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
rv = swm->SendNotificationClickEvent(
|
||
originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
|
||
mBody, mTag, mIcon, mData, mBehavior);
|
||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||
} else {
|
||
auto* cc = ContentChild::GetSingleton();
|
||
NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
|
||
mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
|
||
mData, mBehavior);
|
||
Unused << cc->SendNotificationEvent(u"click"_ns, data);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
if (!strcmp("alertfinished", aTopic)) {
|
||
nsString origin;
|
||
nsresult rv = Notification::GetOrigin(mPrincipal, origin);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return rv;
|
||
}
|
||
|
||
// Remove closed or dismissed persistent notifications.
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
|
||
if (notificationStorage) {
|
||
notificationStorage->Delete(origin, mID);
|
||
}
|
||
|
||
if (XRE_IsParentProcess()) {
|
||
nsCOMPtr<nsIServiceWorkerManager> swm =
|
||
mozilla::components::ServiceWorkerManager::Service();
|
||
if (NS_WARN_IF(!swm)) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
rv = swm->SendNotificationCloseEvent(
|
||
originSuffix, NS_ConvertUTF16toUTF8(mScope), mID, mTitle, mDir, mLang,
|
||
mBody, mTag, mIcon, mData, mBehavior);
|
||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||
} else {
|
||
auto* cc = ContentChild::GetSingleton();
|
||
NotificationEventData data(originSuffix, NS_ConvertUTF16toUTF8(mScope),
|
||
mID, mTitle, mDir, mLang, mBody, mTag, mIcon,
|
||
mData, mBehavior);
|
||
Unused << cc->SendNotificationEvent(u"close"_ns, data);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
bool Notification::IsInPrivateBrowsing() {
|
||
AssertIsOnMainThread();
|
||
|
||
Document* doc = nullptr;
|
||
|
||
if (mWorkerPrivate) {
|
||
doc = mWorkerPrivate->GetDocument();
|
||
} else if (GetOwner()) {
|
||
doc = GetOwner()->GetExtantDoc();
|
||
}
|
||
|
||
if (doc) {
|
||
nsCOMPtr<nsILoadContext> loadContext = doc->GetLoadContext();
|
||
return loadContext && loadContext->UsePrivateBrowsing();
|
||
}
|
||
|
||
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));
|
||
return loadContext && loadContext->UsePrivateBrowsing();
|
||
}
|
||
|
||
// XXXnsm Should this default to true?
|
||
return false;
|
||
}
|
||
|
||
// Step 4 of
|
||
// https://notifications.spec.whatwg.org/#dom-notification-notification
|
||
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;
|
||
std::swap(ownership, mTempRef);
|
||
MOZ_ASSERT(ownership->GetNotification() == this);
|
||
|
||
nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
|
||
|
||
// Step 4.1: If the result of getting the notifications permission state is
|
||
// not "granted", then queue a task to fire an event named error on this, and
|
||
// abort these steps.
|
||
//
|
||
// XXX(krosylight): But this function is also triggered by
|
||
// Notification::ShowPersistentNotification which already does its own
|
||
// permission check. Can we split this?
|
||
ErrorResult result;
|
||
NotificationPermission permission = NotificationPermission::Denied;
|
||
if (mWorkerPrivate) {
|
||
permission = GetPermissionInternal(mWorkerPrivate->GetPrincipal(), result);
|
||
} else {
|
||
permission = GetPermissionInternal(GetOwner(), result);
|
||
}
|
||
// We rely on GetPermissionInternal returning Denied on all failure codepaths.
|
||
MOZ_ASSERT_IF(result.Failed(), permission == NotificationPermission::Denied);
|
||
result.SuppressException();
|
||
if (permission != NotificationPermission::Granted || !alertService) {
|
||
if (mWorkerPrivate) {
|
||
RefPtr<NotificationEventWorkerRunnable> r =
|
||
new NotificationEventWorkerRunnable(this, u"error"_ns);
|
||
if (!r->Dispatch(mWorkerPrivate)) {
|
||
NS_WARNING("Could not dispatch event to worker notification");
|
||
}
|
||
} else {
|
||
DispatchTrustedEvent(u"error"_ns);
|
||
}
|
||
mIsClosed = true;
|
||
return;
|
||
}
|
||
|
||
// Preparing for Step 4.2 the fetch steps. The actual work happens in
|
||
// nsIAlertNotification::LoadImage
|
||
nsAutoString iconUrl;
|
||
nsAutoString soundUrl;
|
||
ResolveIconAndSoundURL(iconUrl, soundUrl);
|
||
|
||
// Step 4.3 the show steps, which are almost all about processing `tag` and
|
||
// then displaying the notification. Both are handled by
|
||
// nsIAlertsService::ShowAlert/PersistentNotification. The below is all about
|
||
// constructing the observer (for show and close events) right and ultimately
|
||
// call the alerts service function.
|
||
|
||
// XXX(krosylight): Non-persistent notifications probably don't need this
|
||
nsresult rv = PersistNotification();
|
||
if (NS_FAILED(rv)) {
|
||
NS_WARNING("Could not persist Notification");
|
||
}
|
||
|
||
bool isPersistent = false;
|
||
nsCOMPtr<nsIObserver> observer;
|
||
if (mScope.IsEmpty()) {
|
||
// Ownership passed to observer.
|
||
if (mWorkerPrivate) {
|
||
// Scope better be set on ServiceWorker initiated requests.
|
||
MOZ_ASSERT(!mWorkerPrivate->IsServiceWorker());
|
||
// Keep a pointer so that the feature can tell the observer not to release
|
||
// the notification.
|
||
mObserver = new WorkerNotificationObserver(std::move(ownership));
|
||
observer = mObserver;
|
||
} else {
|
||
observer = new MainThreadNotificationObserver(std::move(ownership));
|
||
}
|
||
} else {
|
||
isPersistent = true;
|
||
// This observer does not care about the Notification. It will be released
|
||
// at the end of this function.
|
||
//
|
||
// The observer is wholly owned by the NotificationObserver passed to the
|
||
// alert service.
|
||
nsAutoString behavior;
|
||
if (NS_WARN_IF(!mBehavior.ToJSON(behavior))) {
|
||
behavior.Truncate();
|
||
}
|
||
observer = new ServiceWorkerNotificationObserver(
|
||
mScope, GetPrincipal(), mID, mTitle, DirectionToString(mDir), mLang,
|
||
mBody, mTag, iconUrl, mDataAsBase64, behavior);
|
||
}
|
||
MOZ_ASSERT(observer);
|
||
nsCOMPtr<nsIObserver> alertObserver =
|
||
new NotificationObserver(observer, GetPrincipal(), IsInPrivateBrowsing());
|
||
|
||
// 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 = u"notification:"_ns;
|
||
uniqueCookie.AppendInt(sCount++);
|
||
bool inPrivateBrowsing = IsInPrivateBrowsing();
|
||
|
||
bool requireInteraction = mRequireInteraction;
|
||
if (!StaticPrefs::dom_webnotifications_requireinteraction_enabled()) {
|
||
requireInteraction = false;
|
||
}
|
||
|
||
nsAutoString alertName;
|
||
GetAlertName(alertName);
|
||
nsCOMPtr<nsIAlertNotification> alert =
|
||
do_CreateInstance(ALERT_NOTIFICATION_CONTRACTID);
|
||
NS_ENSURE_TRUE_VOID(alert);
|
||
nsIPrincipal* principal = GetPrincipal();
|
||
rv =
|
||
alert->Init(alertName, iconUrl, mTitle, mBody, true, uniqueCookie,
|
||
DirectionToString(mDir), mLang, mDataAsBase64, GetPrincipal(),
|
||
inPrivateBrowsing, requireInteraction, mSilent, mVibrate);
|
||
NS_ENSURE_SUCCESS_VOID(rv);
|
||
|
||
if (isPersistent) {
|
||
JSONStringWriteFunc<nsAutoCString> persistentData;
|
||
JSONWriter w(persistentData);
|
||
w.Start();
|
||
|
||
nsAutoString origin;
|
||
Notification::GetOrigin(principal, origin);
|
||
w.StringProperty("origin", NS_ConvertUTF16toUTF8(origin));
|
||
|
||
w.StringProperty("id", NS_ConvertUTF16toUTF8(mID));
|
||
|
||
nsAutoCString originSuffix;
|
||
principal->GetOriginSuffix(originSuffix);
|
||
w.StringProperty("originSuffix", originSuffix);
|
||
|
||
w.End();
|
||
|
||
alertService->ShowPersistentNotification(
|
||
NS_ConvertUTF8toUTF16(persistentData.StringCRef()), alert,
|
||
alertObserver);
|
||
} else {
|
||
alertService->ShowAlert(alert, alertObserver);
|
||
}
|
||
}
|
||
|
||
/* 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();
|
||
}
|
||
|
||
// static
|
||
already_AddRefed<Promise> Notification::RequestPermission(
|
||
const GlobalObject& aGlobal,
|
||
const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
|
||
ErrorResult& aRv) {
|
||
AssertIsOnMainThread();
|
||
|
||
// Get principal from global to make permission request for notifications.
|
||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||
do_QueryInterface(aGlobal.GetAsSupports());
|
||
nsCOMPtr<nsIScriptObjectPrincipal> sop =
|
||
do_QueryInterface(aGlobal.GetAsSupports());
|
||
if (!sop || !window) {
|
||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||
return nullptr;
|
||
}
|
||
|
||
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
||
if (!principal) {
|
||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
NotificationPermissionCallback* permissionCallback = nullptr;
|
||
if (aCallback.WasPassed()) {
|
||
permissionCallback = &aCallback.Value();
|
||
}
|
||
nsCOMPtr<nsIRunnable> request = new NotificationPermissionRequest(
|
||
principal, window, promise, permissionCallback);
|
||
|
||
window->AsGlobal()->Dispatch(request.forget());
|
||
|
||
return promise.forget();
|
||
}
|
||
|
||
// 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->GetAsInnerWindow(), aRv);
|
||
} else {
|
||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||
MOZ_ASSERT(worker);
|
||
RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable(worker);
|
||
r->Dispatch(Canceling, aRv);
|
||
if (aRv.Failed()) {
|
||
return NotificationPermission::Denied;
|
||
}
|
||
|
||
return r->GetPermission();
|
||
}
|
||
}
|
||
|
||
/* static */
|
||
NotificationPermission Notification::GetPermissionInternal(
|
||
nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
|
||
// Get principal from global to check permission for notifications.
|
||
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
|
||
if (!sop) {
|
||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||
return NotificationPermission::Denied;
|
||
}
|
||
|
||
nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
|
||
if (!principal) {
|
||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||
return NotificationPermission::Denied;
|
||
}
|
||
|
||
if (principal->GetPrivateBrowsingId() != 0 &&
|
||
!StaticPrefs::
|
||
dom_webnotifications_privateBrowsing_enableDespiteLimitations()) {
|
||
return NotificationPermission::Denied;
|
||
}
|
||
// Disallow showing notification if our origin is not the same origin as the
|
||
// toplevel one, see https://github.com/whatwg/notifications/issues/177.
|
||
if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe()) {
|
||
nsCOMPtr<nsIScriptObjectPrincipal> topSop =
|
||
do_QueryInterface(aWindow->GetBrowsingContext()->Top()->GetDOMWindow());
|
||
nsIPrincipal* topPrincipal = topSop ? topSop->GetPrincipal() : nullptr;
|
||
if (!topPrincipal || !principal->Subsumes(topPrincipal)) {
|
||
return NotificationPermission::Denied;
|
||
}
|
||
}
|
||
|
||
return GetPermissionInternal(principal, aRv);
|
||
}
|
||
|
||
/* static */
|
||
NotificationPermission Notification::GetPermissionInternal(
|
||
nsIPrincipal* aPrincipal, ErrorResult& aRv) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aPrincipal);
|
||
|
||
if (aPrincipal->IsSystemPrincipal()) {
|
||
return NotificationPermission::Granted;
|
||
} else {
|
||
// Allow files to show notifications by default.
|
||
if (aPrincipal->SchemeIs("file")) {
|
||
return NotificationPermission::Granted;
|
||
}
|
||
}
|
||
|
||
// We also allow notifications is they are pref'ed on.
|
||
if (Preferences::GetBool("notification.prompt.testing", false)) {
|
||
if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
|
||
return NotificationPermission::Granted;
|
||
} else {
|
||
return NotificationPermission::Denied;
|
||
}
|
||
}
|
||
|
||
return TestPermission(aPrincipal);
|
||
}
|
||
|
||
/* static */
|
||
NotificationPermission Notification::TestPermission(nsIPrincipal* aPrincipal) {
|
||
AssertIsOnMainThread();
|
||
|
||
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
|
||
|
||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||
components::PermissionManager::Service();
|
||
if (!permissionManager) {
|
||
return NotificationPermission::Default;
|
||
}
|
||
|
||
permissionManager->TestExactPermissionFromPrincipal(
|
||
aPrincipal, "desktop-notification"_ns, &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;
|
||
|
||
nsIURI* baseUri = nullptr;
|
||
|
||
// 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.
|
||
auto encoding = UTF_8_ENCODING;
|
||
|
||
if (mWorkerPrivate) {
|
||
baseUri = mWorkerPrivate->GetBaseURI();
|
||
} else {
|
||
Document* doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
|
||
if (doc) {
|
||
baseUri = doc->GetBaseURI();
|
||
encoding = doc->GetDocumentCharacterSet();
|
||
} 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, encoding, baseUri);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
nsAutoCString src;
|
||
srcUri->GetSpec(src);
|
||
CopyUTF8toUTF16(src, iconUrl);
|
||
}
|
||
}
|
||
if (mBehavior.mSoundFile.Length() > 0) {
|
||
nsCOMPtr<nsIURI> srcUri;
|
||
rv = NS_NewURI(getter_AddRefs(srcUri), mBehavior.mSoundFile, encoding,
|
||
baseUri);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
nsAutoCString src;
|
||
srcUri->GetSpec(src);
|
||
CopyUTF8toUTF16(src, soundUrl);
|
||
}
|
||
}
|
||
}
|
||
|
||
return rv;
|
||
}
|
||
|
||
already_AddRefed<Promise> Notification::Get(
|
||
nsPIDOMWindowInner* aWindow, const GetNotificationOptions& aFilter,
|
||
const nsAString& aScope, ErrorResult& aRv) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aWindow);
|
||
|
||
nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
|
||
if (!doc) {
|
||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||
return nullptr;
|
||
}
|
||
|
||
nsString origin;
|
||
aRv = GetOrigin(doc->NodePrincipal(), origin);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<Promise> promise = Promise::Create(aWindow->AsGlobal(), aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
nsCOMPtr<nsINotificationStorageCallback> callback =
|
||
new NotificationStorageCallback(aWindow->AsGlobal(), aScope, promise);
|
||
|
||
RefPtr<NotificationGetRunnable> r =
|
||
new NotificationGetRunnable(origin, aFilter.mTag, callback);
|
||
|
||
aRv = aWindow->AsGlobal()->Dispatch(r.forget());
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return nullptr;
|
||
}
|
||
|
||
return promise.forget();
|
||
}
|
||
|
||
class WorkerGetResultRunnable final : public NotificationWorkerRunnable {
|
||
RefPtr<PromiseWorkerProxy> mPromiseProxy;
|
||
const nsTArray<NotificationStrings> mStrings;
|
||
|
||
public:
|
||
WorkerGetResultRunnable(WorkerPrivate* aWorkerPrivate,
|
||
PromiseWorkerProxy* aPromiseProxy,
|
||
nsTArray<NotificationStrings>&& aStrings)
|
||
: NotificationWorkerRunnable(aWorkerPrivate, "WorkerGetResultRunnable"),
|
||
mPromiseProxy(aPromiseProxy),
|
||
mStrings(std::move(aStrings)) {}
|
||
|
||
void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) override {
|
||
RefPtr<Promise> workerPromise = mPromiseProxy->GetWorkerPromise();
|
||
// Once Worker had already started shutdown, workerPromise would be nullptr
|
||
if (!workerPromise) {
|
||
return;
|
||
}
|
||
|
||
AutoTArray<RefPtr<Notification>, 5> notifications;
|
||
for (uint32_t i = 0; i < mStrings.Length(); ++i) {
|
||
auto result = Notification::ConstructFromFields(
|
||
aWorkerPrivate->GlobalScope(), mStrings[i].mID, mStrings[i].mTitle,
|
||
mStrings[i].mDir, mStrings[i].mLang, mStrings[i].mBody,
|
||
mStrings[i].mTag, mStrings[i].mIcon, mStrings[i].mData,
|
||
/* mStrings[i].mBehavior, not
|
||
* supported */
|
||
mStrings[i].mServiceWorkerRegistrationScope);
|
||
if (result.isErr()) {
|
||
continue;
|
||
}
|
||
RefPtr<Notification> n = result.unwrap();
|
||
n->SetStoredState(true);
|
||
notifications.AppendElement(n.forget());
|
||
}
|
||
|
||
workerPromise->MaybeResolve(notifications);
|
||
mPromiseProxy->CleanUp();
|
||
}
|
||
};
|
||
|
||
class WorkerGetCallback final : public ScopeCheckingGetCallback {
|
||
RefPtr<PromiseWorkerProxy> mPromiseProxy;
|
||
|
||
public:
|
||
NS_DECL_ISUPPORTS
|
||
|
||
WorkerGetCallback(PromiseWorkerProxy* aProxy, const nsAString& aScope)
|
||
: ScopeCheckingGetCallback(aScope), mPromiseProxy(aProxy) {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(aProxy);
|
||
}
|
||
|
||
NS_IMETHOD Done() final {
|
||
AssertIsOnMainThread();
|
||
MOZ_ASSERT(mPromiseProxy, "Was Done() called twice?");
|
||
|
||
RefPtr<PromiseWorkerProxy> proxy = std::move(mPromiseProxy);
|
||
MutexAutoLock lock(proxy->Lock());
|
||
if (proxy->CleanedUp()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
RefPtr<WorkerGetResultRunnable> r = new WorkerGetResultRunnable(
|
||
proxy->GetWorkerPrivate(), proxy, std::move(mStrings));
|
||
|
||
r->Dispatch(proxy->GetWorkerPrivate());
|
||
return NS_OK;
|
||
}
|
||
|
||
private:
|
||
~WorkerGetCallback() = default;
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS(WorkerGetCallback, nsINotificationStorageCallback)
|
||
|
||
class WorkerGetRunnable final : public Runnable {
|
||
RefPtr<PromiseWorkerProxy> mPromiseProxy;
|
||
const nsString mTag;
|
||
const nsString mScope;
|
||
|
||
public:
|
||
WorkerGetRunnable(PromiseWorkerProxy* aProxy, const nsAString& aTag,
|
||
const nsAString& aScope)
|
||
: Runnable("WorkerGetRunnable"),
|
||
mPromiseProxy(aProxy),
|
||
mTag(aTag),
|
||
mScope(aScope) {
|
||
MOZ_ASSERT(mPromiseProxy);
|
||
}
|
||
|
||
NS_IMETHOD
|
||
Run() override {
|
||
AssertIsOnMainThread();
|
||
nsCOMPtr<nsINotificationStorageCallback> callback =
|
||
new WorkerGetCallback(mPromiseProxy, mScope);
|
||
|
||
nsresult rv;
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
callback->Done();
|
||
return rv;
|
||
}
|
||
|
||
MutexAutoLock lock(mPromiseProxy->Lock());
|
||
if (mPromiseProxy->CleanedUp()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
nsString origin;
|
||
rv = Notification::GetOrigin(
|
||
mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), origin);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
callback->Done();
|
||
return rv;
|
||
}
|
||
|
||
rv = notificationStorage->Get(origin, mTag, callback);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
callback->Done();
|
||
return rv;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
private:
|
||
~WorkerGetRunnable() = default;
|
||
};
|
||
|
||
// static
|
||
already_AddRefed<Promise> Notification::WorkerGet(
|
||
WorkerPrivate* aWorkerPrivate, const GetNotificationOptions& aFilter,
|
||
const nsAString& aScope, ErrorResult& aRv) {
|
||
MOZ_ASSERT(aWorkerPrivate);
|
||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||
RefPtr<Promise> p = Promise::Create(aWorkerPrivate->GlobalScope(), aRv);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<PromiseWorkerProxy> proxy =
|
||
PromiseWorkerProxy::Create(aWorkerPrivate, p);
|
||
if (!proxy) {
|
||
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<WorkerGetRunnable> r =
|
||
new WorkerGetRunnable(proxy, aFilter.mTag, aScope);
|
||
// Since this is called from script via
|
||
// ServiceWorkerRegistration::GetNotifications, we can assert dispatch.
|
||
MOZ_ALWAYS_SUCCEEDS(aWorkerPrivate->DispatchToMainThread(r.forget()));
|
||
return p.forget();
|
||
}
|
||
|
||
JSObject* Notification::WrapObject(JSContext* aCx,
|
||
JS::Handle<JSObject*> aGivenProto) {
|
||
return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto);
|
||
}
|
||
|
||
void Notification::Close() {
|
||
AssertIsOnTargetThread();
|
||
auto ref = MakeUnique<NotificationRef>(this);
|
||
if (!ref->Initialized()) {
|
||
return;
|
||
}
|
||
|
||
nsCOMPtr<nsIRunnable> closeNotificationTask = new NotificationTask(
|
||
"Notification::Close", std::move(ref), NotificationTask::eClose);
|
||
nsresult rv = DispatchToMainThread(closeNotificationTask.forget());
|
||
|
||
if (NS_FAILED(rv)) {
|
||
DispatchTrustedEvent(u"error"_ns);
|
||
// If dispatch fails, NotificationTask will release the ref when it goes
|
||
// out of scope at the end of this function.
|
||
}
|
||
}
|
||
|
||
void Notification::CloseInternal(bool aContextClosed) {
|
||
AssertIsOnMainThread();
|
||
// 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;
|
||
std::swap(ownership, mTempRef);
|
||
|
||
SetAlertName();
|
||
UnpersistNotification();
|
||
if (!mIsClosed) {
|
||
nsCOMPtr<nsIAlertsService> alertService = components::Alerts::Service();
|
||
if (alertService) {
|
||
nsAutoString alertName;
|
||
GetAlertName(alertName);
|
||
alertService->CloseAlert(alertName, aContextClosed);
|
||
}
|
||
}
|
||
}
|
||
|
||
nsresult Notification::GetOrigin(nsIPrincipal* aPrincipal, nsString& aOrigin) {
|
||
if (!aPrincipal) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
nsresult rv =
|
||
nsContentUtils::GetWebExposedOriginSerialization(aPrincipal, aOrigin);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
bool Notification::RequireInteraction() const { return mRequireInteraction; }
|
||
|
||
bool Notification::Silent() const { return mSilent; }
|
||
|
||
void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const {
|
||
aRetval = mVibrate.Clone();
|
||
}
|
||
|
||
void Notification::GetData(JSContext* aCx,
|
||
JS::MutableHandle<JS::Value> aRetval) {
|
||
if (mData.isNull() && !mDataAsBase64.IsEmpty()) {
|
||
nsresult rv;
|
||
RefPtr<nsStructuredCloneContainer> container =
|
||
new nsStructuredCloneContainer();
|
||
rv = container->InitFromBase64(mDataAsBase64, JS_STRUCTURED_CLONE_VERSION);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
aRetval.setNull();
|
||
return;
|
||
}
|
||
|
||
JS::Rooted<JS::Value> data(aCx);
|
||
rv = container->DeserializeToJsval(aCx, &data);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
aRetval.setNull();
|
||
return;
|
||
}
|
||
|
||
if (data.isGCThing()) {
|
||
mozilla::HoldJSObjects(this);
|
||
}
|
||
mData = data;
|
||
}
|
||
if (mData.isNull()) {
|
||
aRetval.setNull();
|
||
return;
|
||
}
|
||
|
||
aRetval.set(mData);
|
||
}
|
||
|
||
void Notification::InitFromJSVal(JSContext* aCx, JS::Handle<JS::Value> aData,
|
||
ErrorResult& aRv) {
|
||
if (!mDataAsBase64.IsEmpty() || aData.isNull()) {
|
||
return;
|
||
}
|
||
RefPtr<nsStructuredCloneContainer> dataObjectContainer =
|
||
new nsStructuredCloneContainer();
|
||
aRv = dataObjectContainer->InitFromJSVal(aData, aCx);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return;
|
||
}
|
||
|
||
aRv = dataObjectContainer->GetDataAsBase64(mDataAsBase64);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
Result<Ok, QMResult> Notification::InitFromBase64(const nsAString& aData) {
|
||
MOZ_ASSERT(mDataAsBase64.IsEmpty());
|
||
if (aData.IsEmpty()) {
|
||
// No data; skipping
|
||
return Ok();
|
||
}
|
||
|
||
// To and fro to ensure it is valid base64.
|
||
RefPtr<nsStructuredCloneContainer> container =
|
||
new nsStructuredCloneContainer();
|
||
QM_TRY(QM_TO_RESULT(
|
||
container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION)));
|
||
QM_TRY(QM_TO_RESULT(container->GetDataAsBase64(mDataAsBase64)));
|
||
|
||
return Ok();
|
||
}
|
||
|
||
bool Notification::AddRefObject() {
|
||
AssertIsOnTargetThread();
|
||
MOZ_ASSERT_IF(mWorkerPrivate && !mWorkerRef, mTaskCount == 0);
|
||
MOZ_ASSERT_IF(mWorkerPrivate && mWorkerRef, mTaskCount > 0);
|
||
if (mWorkerPrivate && !mWorkerRef) {
|
||
if (!CreateWorkerRef()) {
|
||
return false;
|
||
}
|
||
}
|
||
AddRef();
|
||
++mTaskCount;
|
||
return true;
|
||
}
|
||
|
||
void Notification::ReleaseObject() {
|
||
AssertIsOnTargetThread();
|
||
MOZ_ASSERT(mTaskCount > 0);
|
||
MOZ_ASSERT_IF(mWorkerPrivate, mWorkerRef);
|
||
|
||
--mTaskCount;
|
||
if (mWorkerPrivate && mTaskCount == 0) {
|
||
MOZ_ASSERT(mWorkerRef);
|
||
mWorkerRef = nullptr;
|
||
}
|
||
Release();
|
||
}
|
||
|
||
/*
|
||
* 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;
|
||
bool mHadObserver;
|
||
|
||
public:
|
||
explicit CloseNotificationRunnable(Notification* aNotification)
|
||
: WorkerMainThreadRunnable(aNotification->mWorkerPrivate,
|
||
"Notification :: Close Notification"_ns),
|
||
mNotification(aNotification),
|
||
mHadObserver(false) {}
|
||
|
||
bool MainThreadRun() override {
|
||
if (mNotification->mObserver) {
|
||
// The Notify() take's responsibility of releasing the Notification.
|
||
mNotification->mObserver->ForgetNotification();
|
||
mNotification->mObserver = nullptr;
|
||
mHadObserver = true;
|
||
}
|
||
mNotification->CloseInternal();
|
||
return true;
|
||
}
|
||
|
||
bool HadObserver() { return mHadObserver; }
|
||
};
|
||
|
||
bool Notification::CreateWorkerRef() {
|
||
MOZ_ASSERT(mWorkerPrivate);
|
||
mWorkerPrivate->AssertIsOnWorkerThread();
|
||
MOZ_ASSERT(!mWorkerRef);
|
||
|
||
RefPtr<Notification> self = this;
|
||
mWorkerRef =
|
||
StrongWorkerRef::Create(mWorkerPrivate, "Notification", [self]() {
|
||
// CloseNotificationRunnable blocks the worker by pushing a sync event
|
||
// loop on the stack. Meanwhile, WorkerControlRunnables dispatched to
|
||
// the worker can still continue running. One of these is
|
||
// ReleaseNotificationControlRunnable that releases the notification,
|
||
// invalidating the notification and this feature. We hold this
|
||
// reference to keep the notification valid until we are done with it.
|
||
//
|
||
// An example of when the control runnable could get dispatched to the
|
||
// worker is if a Notification is created and the worker is immediately
|
||
// closed, but there is no permission to show it so that the main thread
|
||
// immediately drops the NotificationRef. In this case, this function
|
||
// blocks on the main thread, but the main thread dispatches the control
|
||
// runnable, invalidating mNotification.
|
||
|
||
// Dispatched to main thread, blocks on closing the Notification.
|
||
RefPtr<CloseNotificationRunnable> r =
|
||
new CloseNotificationRunnable(self);
|
||
ErrorResult rv;
|
||
r->Dispatch(Killing, rv);
|
||
// XXXbz I'm told throwing and returning false from here is pointless
|
||
// (and also that doing sync stuff from here is really weird), so I
|
||
// guess we just suppress the exception on rv, if any.
|
||
rv.SuppressException();
|
||
|
||
// Only call ReleaseObject() to match the observer's NotificationRef
|
||
// ownership (since CloseNotificationRunnable asked the observer to drop
|
||
// the reference to the notification).
|
||
if (r->HadObserver()) {
|
||
self->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!
|
||
});
|
||
|
||
if (NS_WARN_IF(!mWorkerRef)) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 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;
|
||
ServiceWorkerRegistrationDescriptor mDescriptor;
|
||
|
||
public:
|
||
explicit CheckLoadRunnable(
|
||
WorkerPrivate* aWorker, const nsACString& aScope,
|
||
const ServiceWorkerRegistrationDescriptor& aDescriptor)
|
||
: WorkerMainThreadRunnable(aWorker, "Notification :: Check Load"_ns),
|
||
mRv(NS_ERROR_DOM_SECURITY_ERR),
|
||
mScope(aScope),
|
||
mDescriptor(aDescriptor) {}
|
||
|
||
bool MainThreadRun() override {
|
||
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
||
mRv = CheckScope(principal, mScope, mWorkerPrivate->WindowID());
|
||
|
||
if (NS_FAILED(mRv)) {
|
||
return true;
|
||
}
|
||
|
||
auto activeWorker = mDescriptor.GetActive();
|
||
|
||
if (!activeWorker ||
|
||
activeWorker.ref().Id() != mWorkerPrivate->ServiceWorkerID()) {
|
||
mRv = NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
nsresult Result() { return mRv; }
|
||
};
|
||
|
||
// Step 2, 5, 6 of
|
||
// https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification
|
||
/* static */
|
||
already_AddRefed<Promise> Notification::ShowPersistentNotification(
|
||
JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope,
|
||
const nsAString& aTitle, const NotificationOptions& aOptions,
|
||
const ServiceWorkerRegistrationDescriptor& aDescriptor, 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;
|
||
}
|
||
|
||
uint64_t windowID = 0;
|
||
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal);
|
||
if (win) {
|
||
windowID = win->WindowID();
|
||
}
|
||
|
||
aRv = CheckScope(principal, NS_ConvertUTF16toUTF8(aScope), windowID);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||
return nullptr;
|
||
}
|
||
} else {
|
||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||
MOZ_ASSERT(worker);
|
||
worker->AssertIsOnWorkerThread();
|
||
|
||
RefPtr<CheckLoadRunnable> loadChecker = new CheckLoadRunnable(
|
||
worker, NS_ConvertUTF16toUTF8(aScope), aDescriptor);
|
||
loadChecker->Dispatch(Canceling, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
if (NS_WARN_IF(NS_FAILED(loadChecker->Result()))) {
|
||
if (loadChecker->Result() == NS_ERROR_NOT_AVAILABLE) {
|
||
aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope));
|
||
} else {
|
||
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
|
||
}
|
||
return nullptr;
|
||
}
|
||
}
|
||
|
||
// Step 2: Let promise be a new promise in this’s relevant Realm.
|
||
RefPtr<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.
|
||
// XXX: GetPermission is a synchronous blocking function on workers.
|
||
NotificationPermission permission = GetPermission(aGlobal, aRv);
|
||
|
||
// Step 6.1: If the result of getting the notifications permission state is
|
||
// not "granted", then queue a global task on the DOM manipulation task source
|
||
// given global to reject promise with a TypeError, and abort these steps.
|
||
if (NS_WARN_IF(aRv.Failed()) ||
|
||
permission != NotificationPermission::Granted) {
|
||
p->MaybeRejectWithTypeError("Permission to show Notification denied.");
|
||
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->MaybeResolveWithUndefined();
|
||
|
||
// Step 5: Let notification be the result of creating a notification given
|
||
// title, options, this’s relevant settings object, and
|
||
// serviceWorkerRegistration. If this threw an exception, then reject promise
|
||
// with that exception and return promise.
|
||
//
|
||
// XXX: This should happen before the permission check per the spec, as this
|
||
// can throw errors too. This should be split into create and show.
|
||
RefPtr<Notification> notification =
|
||
CreateAndShow(aCx, aGlobal, aTitle, aOptions, aScope, aRv);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return nullptr;
|
||
}
|
||
|
||
return p.forget();
|
||
}
|
||
|
||
/* static */
|
||
already_AddRefed<Notification> Notification::CreateAndShow(
|
||
JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle,
|
||
const NotificationOptions& aOptions, const nsAString& aScope,
|
||
ErrorResult& aRv) {
|
||
MOZ_ASSERT(aGlobal);
|
||
|
||
RefPtr<Notification> notification =
|
||
CreateInternal(aGlobal, u""_ns, aTitle, aOptions, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
// Make a structured clone of the aOptions.mData object
|
||
JS::Rooted<JS::Value> data(aCx, aOptions.mData);
|
||
notification->InitFromJSVal(aCx, data, aRv);
|
||
if (NS_WARN_IF(aRv.Failed())) {
|
||
return nullptr;
|
||
}
|
||
|
||
notification->SetScope(aScope);
|
||
|
||
auto ref = MakeUnique<NotificationRef>(notification);
|
||
if (NS_WARN_IF(!ref->Initialized())) {
|
||
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
|
||
return nullptr;
|
||
}
|
||
|
||
// Queue a task to show the notification.
|
||
nsCOMPtr<nsIRunnable> showNotificationTask = new NotificationTask(
|
||
"Notification::CreateAndShow", std::move(ref), NotificationTask::eShow);
|
||
|
||
nsresult rv =
|
||
notification->DispatchToMainThread(showNotificationTask.forget());
|
||
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
notification->DispatchTrustedEvent(u"error"_ns);
|
||
}
|
||
|
||
return notification.forget();
|
||
}
|
||
|
||
/* static */
|
||
nsresult Notification::RemovePermission(nsIPrincipal* aPrincipal) {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
nsCOMPtr<nsIPermissionManager> permissionManager =
|
||
mozilla::components::PermissionManager::Service();
|
||
if (!permissionManager) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
permissionManager->RemoveFromPrincipal(aPrincipal, "desktop-notification"_ns);
|
||
return NS_OK;
|
||
}
|
||
|
||
/* static */
|
||
nsresult Notification::OpenSettings(nsIPrincipal* aPrincipal) {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||
if (!obs) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
// Notify other observers so they can show settings UI.
|
||
obs->NotifyObservers(aPrincipal, "notifications-open-settings", nullptr);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
Notification::Observe(nsISupports* aSubject, const char* aTopic,
|
||
const char16_t* aData) {
|
||
AssertIsOnMainThread();
|
||
|
||
if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) ||
|
||
!strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC)) {
|
||
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
|
||
if (SameCOMIdentity(aSubject, window)) {
|
||
nsCOMPtr<nsIObserverService> obs =
|
||
mozilla::services::GetObserverService();
|
||
if (obs) {
|
||
obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC);
|
||
obs->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC);
|
||
}
|
||
|
||
CloseInternal(true);
|
||
}
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult Notification::DispatchToMainThread(
|
||
already_AddRefed<nsIRunnable>&& aRunnable) {
|
||
if (mWorkerPrivate) {
|
||
return mWorkerPrivate->DispatchToMainThread(std::move(aRunnable));
|
||
}
|
||
AssertIsOnMainThread();
|
||
return NS_DispatchToCurrentThread(std::move(aRunnable));
|
||
}
|
||
|
||
} // namespace mozilla::dom
|