forked from mirrors/gecko-dev
This unfortunately adds some ASCII-to-UTF16 conversion because: 1. Enums use nsLiteralCString internally 2. nsAlertsService expects nsString. (macOS and Windows expects UTF16 while libnotify expects UTF8) 3. ServiceWorker events currently expect nsString. Fixing this requires touching quite some files and I filed bug 1894240 for that. Differential Revision: https://phabricator.services.mozilla.com/D208920
2374 lines
77 KiB
C++
2374 lines
77 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
|
||
|
||
nsCOMPtr<nsINotificationStorage> GetNotificationStorage(bool isPrivate) {
|
||
return do_GetService(isPrivate ? NS_MEMORY_NOTIFICATION_STORAGE_CONTRACTID
|
||
: NS_NOTIFICATION_STORAGE_CONTRACTID);
|
||
}
|
||
|
||
class NotificationGetRunnable final : public Runnable {
|
||
bool mIsPrivate;
|
||
const nsString mOrigin;
|
||
const nsString mTag;
|
||
nsCOMPtr<nsINotificationStorageCallback> mCallback;
|
||
|
||
public:
|
||
NotificationGetRunnable(const nsAString& aOrigin, const nsAString& aTag,
|
||
nsINotificationStorageCallback* aCallback,
|
||
bool aIsPrivate)
|
||
: Runnable("NotificationGetRunnable"),
|
||
mIsPrivate(aIsPrivate),
|
||
mOrigin(aOrigin),
|
||
mTag(aTag),
|
||
mCallback(aCallback) {}
|
||
|
||
NS_IMETHOD
|
||
Run() override {
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
GetNotificationStorage(mIsPrivate);
|
||
if (NS_WARN_IF(!notificationStorage)) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
nsresult 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 = StringToEnum<NotificationDirection>(aDir).valueOr(
|
||
NotificationDirection::Auto);
|
||
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();
|
||
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
GetNotificationStorage(IsInPrivateBrowsing());
|
||
if (NS_WARN_IF(!notificationStorage)) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
nsString origin;
|
||
nsresult 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, GetEnumString(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 =
|
||
GetNotificationStorage(IsInPrivateBrowsing());
|
||
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, NotificationDirection 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 NotificationDirection 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,
|
||
NS_ConvertASCIItoUTF16(GetEnumString(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,
|
||
NS_ConvertASCIItoUTF16(GetEnumString(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 =
|
||
GetNotificationStorage(mPrincipal->GetPrivateBrowsingId() != 0);
|
||
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,
|
||
NS_ConvertASCIItoUTF16(GetEnumString(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,
|
||
NS_ConvertASCIItoUTF16(GetEnumString(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, 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,
|
||
NS_ConvertASCIItoUTF16(GetEnumString(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, doc->IsInPrivateBrowsing());
|
||
|
||
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();
|
||
|
||
MutexAutoLock lock(mPromiseProxy->Lock());
|
||
if (mPromiseProxy->CleanedUp()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
auto* principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal();
|
||
auto isPrivate = principal->GetPrivateBrowsingId() != 0;
|
||
|
||
nsCOMPtr<nsINotificationStorageCallback> callback =
|
||
new WorkerGetCallback(mPromiseProxy, mScope);
|
||
|
||
nsCOMPtr<nsINotificationStorage> notificationStorage =
|
||
GetNotificationStorage(isPrivate);
|
||
if (NS_WARN_IF(!notificationStorage)) {
|
||
callback->Done();
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
nsString origin;
|
||
nsresult rv = Notification::GetOrigin(principal, 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
|