michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "PCOMContentPermissionRequestChild.h" michael@0: #include "mozilla/dom/Notification.h" michael@0: #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" michael@0: #include "mozilla/dom/OwningNonNull.h" michael@0: #include "mozilla/dom/Promise.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "TabChild.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIAlertsService.h" michael@0: #include "nsIAppsService.h" michael@0: #include "nsIContentPermissionPrompt.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsINotificationStorage.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIUUIDGenerator.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsToolkitCompsCID.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "mozilla/dom/PermissionMessageUtils.h" michael@0: #include "nsContentPermissionHelper.h" michael@0: #ifdef MOZ_B2G michael@0: #include "nsIDOMDesktopNotification.h" michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback michael@0: { michael@0: public: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback) michael@0: michael@0: NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise) michael@0: : mCount(0), michael@0: mGlobal(aGlobal.Get()), michael@0: mWindow(aWindow), michael@0: mPromise(aPromise) michael@0: { michael@0: MOZ_ASSERT(aWindow); michael@0: MOZ_ASSERT(aPromise); michael@0: JSContext* cx = aGlobal.GetContext(); michael@0: JSAutoCompartment ac(cx, mGlobal); michael@0: mNotifications = JS_NewArrayObject(cx, 0); michael@0: HoldData(); michael@0: } michael@0: michael@0: NS_IMETHOD Handle(const nsAString& aID, michael@0: const nsAString& aTitle, michael@0: const nsAString& aDir, michael@0: const nsAString& aLang, michael@0: const nsAString& aBody, michael@0: const nsAString& aTag, michael@0: const nsAString& aIcon, michael@0: JSContext* aCx) michael@0: { michael@0: MOZ_ASSERT(!aID.IsEmpty()); michael@0: michael@0: NotificationOptions options; michael@0: options.mDir = Notification::StringToDirection(nsString(aDir)); michael@0: options.mLang = aLang; michael@0: options.mBody = aBody; michael@0: options.mTag = aTag; michael@0: options.mIcon = aIcon; michael@0: nsRefPtr notification = Notification::CreateInternal(mWindow, michael@0: aID, michael@0: aTitle, michael@0: options); michael@0: JSAutoCompartment ac(aCx, mGlobal); michael@0: JS::Rooted element(aCx, notification->WrapObject(aCx)); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_FAILURE); michael@0: michael@0: if (!JS_DefineElement(aCx, mNotifications, mCount++, michael@0: JS::ObjectValue(*element), nullptr, nullptr, 0)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD Done(JSContext* aCx) michael@0: { michael@0: JSAutoCompartment ac(aCx, mGlobal); michael@0: JS::Rooted result(aCx, JS::ObjectValue(*mNotifications)); michael@0: mPromise->MaybeResolve(aCx, result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: ~NotificationStorageCallback() michael@0: { michael@0: DropData(); michael@0: } michael@0: michael@0: void HoldData() michael@0: { michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: void DropData() michael@0: { michael@0: mGlobal = nullptr; michael@0: mNotifications = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: uint32_t mCount; michael@0: JS::Heap mGlobal; michael@0: nsCOMPtr mWindow; michael@0: nsRefPtr mPromise; michael@0: JS::Heap mNotifications; michael@0: }; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) michael@0: tmp->DropData(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: class NotificationPermissionRequest : public nsIContentPermissionRequest, michael@0: public PCOMContentPermissionRequestChild, michael@0: public nsIRunnable michael@0: { michael@0: public: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_NSICONTENTPERMISSIONREQUEST michael@0: NS_DECL_NSIRUNNABLE michael@0: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest, michael@0: nsIContentPermissionRequest) michael@0: michael@0: NotificationPermissionRequest(nsIPrincipal* aPrincipal, nsPIDOMWindow* aWindow, michael@0: NotificationPermissionCallback* aCallback) michael@0: : mPrincipal(aPrincipal), mWindow(aWindow), michael@0: mPermission(NotificationPermission::Default), michael@0: mCallback(aCallback) {} michael@0: michael@0: virtual ~NotificationPermissionRequest() {} michael@0: michael@0: bool Recv__delete__(const bool& aAllow, michael@0: const InfallibleTArray& choices); michael@0: void IPDLRelease() { Release(); } michael@0: michael@0: protected: michael@0: nsresult CallCallback(); michael@0: nsresult DispatchCallback(); michael@0: nsCOMPtr mPrincipal; michael@0: nsCOMPtr mWindow; michael@0: NotificationPermission mPermission; michael@0: nsRefPtr mCallback; michael@0: }; michael@0: michael@0: class NotificationObserver : public nsIObserver michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: michael@0: NotificationObserver(Notification* aNotification) michael@0: : mNotification(aNotification) {} michael@0: michael@0: virtual ~NotificationObserver() {} michael@0: michael@0: protected: michael@0: nsRefPtr mNotification; michael@0: }; michael@0: michael@0: class NotificationTask : public nsIRunnable michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: enum NotificationAction { michael@0: eShow, michael@0: eClose michael@0: }; michael@0: michael@0: NotificationTask(Notification* aNotification, NotificationAction aAction) michael@0: : mNotification(aNotification), mAction(aAction) {} michael@0: michael@0: virtual ~NotificationTask() {} michael@0: michael@0: protected: michael@0: nsRefPtr mNotification; michael@0: NotificationAction mAction; michael@0: }; michael@0: michael@0: uint32_t Notification::sCount = 0; michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(NotificationPermissionRequest, mWindow) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationPermissionRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRunnable) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationPermissionRequest) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationPermissionRequest) michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::Run() michael@0: { michael@0: if (nsContentUtils::IsSystemPrincipal(mPrincipal)) { michael@0: mPermission = NotificationPermission::Granted; michael@0: } else { michael@0: // File are automatically granted permission. michael@0: nsCOMPtr uri; michael@0: mPrincipal->GetURI(getter_AddRefs(uri)); michael@0: michael@0: if (uri) { michael@0: bool isFile; michael@0: uri->SchemeIs("file", &isFile); michael@0: if (isFile) { michael@0: mPermission = NotificationPermission::Granted; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Grant permission if pref'ed on. michael@0: if (Preferences::GetBool("notification.prompt.testing", false)) { michael@0: if (Preferences::GetBool("notification.prompt.testing.allow", true)) { michael@0: mPermission = NotificationPermission::Granted; michael@0: } else { michael@0: mPermission = NotificationPermission::Denied; michael@0: } michael@0: } michael@0: michael@0: if (mPermission != NotificationPermission::Default) { michael@0: return DispatchCallback(); michael@0: } michael@0: michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: // because owner implements nsITabChild, we can assume that it is michael@0: // the one and only TabChild. michael@0: TabChild* child = TabChild::GetFrom(mWindow->GetDocShell()); michael@0: if (!child) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // Retain a reference so the object isn't deleted without IPDL's knowledge. michael@0: // Corresponding release occurs in DeallocPContentPermissionRequest. michael@0: AddRef(); michael@0: michael@0: nsTArray permArray; michael@0: nsTArray emptyOptions; michael@0: permArray.AppendElement(PermissionRequest( michael@0: NS_LITERAL_CSTRING("desktop-notification"), michael@0: NS_LITERAL_CSTRING("unused"), michael@0: emptyOptions)); michael@0: child->SendPContentPermissionRequestConstructor(this, permArray, michael@0: IPC::Principal(mPrincipal)); michael@0: michael@0: Sendprompt(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr prompt = michael@0: do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); michael@0: if (prompt) { michael@0: prompt->Prompt(this); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::GetPrincipal(nsIPrincipal** aRequestingPrincipal) michael@0: { michael@0: NS_ADDREF(*aRequestingPrincipal = mPrincipal); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow) michael@0: { michael@0: NS_ADDREF(*aRequestingWindow = mWindow); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::GetElement(nsIDOMElement** aElement) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aElement); michael@0: *aElement = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::Cancel() michael@0: { michael@0: mPermission = NotificationPermission::Denied; michael@0: return DispatchCallback(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::Allow(JS::HandleValue aChoices) michael@0: { michael@0: MOZ_ASSERT(aChoices.isUndefined()); michael@0: michael@0: mPermission = NotificationPermission::Granted; michael@0: return DispatchCallback(); michael@0: } michael@0: michael@0: inline nsresult michael@0: NotificationPermissionRequest::DispatchCallback() michael@0: { michael@0: if (!mCallback) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr callbackRunnable = NS_NewRunnableMethod(this, michael@0: &NotificationPermissionRequest::CallCallback); michael@0: return NS_DispatchToMainThread(callbackRunnable); michael@0: } michael@0: michael@0: nsresult michael@0: NotificationPermissionRequest::CallCallback() michael@0: { michael@0: ErrorResult rv; michael@0: mCallback->Call(mPermission, rv); michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationPermissionRequest::GetTypes(nsIArray** aTypes) michael@0: { michael@0: nsTArray emptyOptions; michael@0: return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), michael@0: NS_LITERAL_CSTRING("unused"), michael@0: emptyOptions, michael@0: aTypes); michael@0: } michael@0: michael@0: bool michael@0: NotificationPermissionRequest::Recv__delete__(const bool& aAllow, michael@0: const InfallibleTArray& choices) michael@0: { michael@0: MOZ_ASSERT(choices.IsEmpty(), "Notification doesn't support permission choice"); michael@0: michael@0: if (aAllow) { michael@0: (void) Allow(JS::UndefinedHandleValue); michael@0: } else { michael@0: (void) Cancel(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(NotificationTask, nsIRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationTask::Run() michael@0: { michael@0: switch (mAction) { michael@0: case eShow: michael@0: mNotification->ShowInternal(); michael@0: break; michael@0: case eClose: michael@0: mNotification->CloseInternal(); michael@0: break; michael@0: default: michael@0: MOZ_CRASH("Unexpected action for NotificationTask."); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (!strcmp("alertclickcallback", aTopic)) { michael@0: nsCOMPtr window = mNotification->GetOwner(); michael@0: nsIDocument* doc = window ? window->GetExtantDoc() : nullptr; michael@0: if (doc) { michael@0: nsContentUtils::DispatchChromeEvent(doc, window, michael@0: NS_LITERAL_STRING("DOMWebNotificationClicked"), michael@0: true, true); michael@0: } michael@0: mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("click")); michael@0: } else if (!strcmp("alertfinished", aTopic)) { michael@0: mNotification->mIsClosed = true; michael@0: mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close")); michael@0: } else if (!strcmp("alertshow", aTopic)) { michael@0: mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show")); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody, michael@0: NotificationDirection aDir, const nsAString& aLang, michael@0: const nsAString& aTag, const nsAString& aIconUrl, michael@0: nsPIDOMWindow* aWindow) michael@0: : DOMEventTargetHelper(aWindow), michael@0: mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang), michael@0: mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false) michael@0: { michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: Notification::Constructor(const GlobalObject& aGlobal, michael@0: const nsAString& aTitle, michael@0: const NotificationOptions& aOptions, michael@0: ErrorResult& aRv) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: MOZ_ASSERT(window, "Window should not be null."); michael@0: nsRefPtr notification = CreateInternal(window, michael@0: EmptyString(), michael@0: aTitle, michael@0: aOptions); michael@0: michael@0: // Queue a task to show the notification. michael@0: nsCOMPtr showNotificationTask = michael@0: new NotificationTask(notification, NotificationTask::eShow); michael@0: NS_DispatchToCurrentThread(showNotificationTask); michael@0: michael@0: // Persist the notification. michael@0: nsresult rv; michael@0: nsCOMPtr notificationStorage = michael@0: do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsString origin; michael@0: aRv = GetOrigin(window, origin); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsString id; michael@0: notification->GetID(id); michael@0: aRv = notificationStorage->Put(origin, michael@0: id, michael@0: aTitle, michael@0: DirectionToString(aOptions.mDir), michael@0: aOptions.mLang, michael@0: aOptions.mBody, michael@0: aOptions.mTag, michael@0: aOptions.mIcon); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return notification.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: Notification::CreateInternal(nsPIDOMWindow* aWindow, michael@0: const nsAString& aID, michael@0: const nsAString& aTitle, michael@0: const NotificationOptions& aOptions) michael@0: { michael@0: nsString id; michael@0: if (!aID.IsEmpty()) { michael@0: id = aID; michael@0: } else { michael@0: nsCOMPtr uuidgen = michael@0: do_GetService("@mozilla.org/uuid-generator;1"); michael@0: NS_ENSURE_TRUE(uuidgen, nullptr); michael@0: nsID uuid; michael@0: nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: char buffer[NSID_LENGTH]; michael@0: uuid.ToProvidedString(buffer); michael@0: NS_ConvertASCIItoUTF16 convertedID(buffer); michael@0: id = convertedID; michael@0: } michael@0: michael@0: nsRefPtr notification = new Notification(id, michael@0: aTitle, michael@0: aOptions.mBody, michael@0: aOptions.mDir, michael@0: aOptions.mLang, michael@0: aOptions.mTag, michael@0: aOptions.mIcon, michael@0: aWindow); michael@0: return notification.forget(); michael@0: } michael@0: michael@0: nsIPrincipal* michael@0: Notification::GetPrincipal() michael@0: { michael@0: nsCOMPtr sop = do_QueryInterface(GetOwner()); michael@0: NS_ENSURE_TRUE(sop, nullptr); michael@0: return sop->GetPrincipal(); michael@0: } michael@0: michael@0: void michael@0: Notification::ShowInternal() michael@0: { michael@0: nsCOMPtr alertService = michael@0: do_GetService(NS_ALERTSERVICE_CONTRACTID); michael@0: michael@0: ErrorResult result; michael@0: if (GetPermissionInternal(GetOwner(), result) != michael@0: NotificationPermission::Granted || !alertService) { michael@0: // We do not have permission to show a notification or alert service michael@0: // is not available. michael@0: DispatchTrustedEvent(NS_LITERAL_STRING("error")); michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsAutoString absoluteUrl; michael@0: if (mIconUrl.Length() > 0) { michael@0: // Resolve image URL against document base URI. michael@0: nsIDocument* doc = GetOwner()->GetExtantDoc(); michael@0: if (doc) { michael@0: nsCOMPtr baseUri = doc->GetBaseURI(); michael@0: if (baseUri) { michael@0: nsCOMPtr srcUri; michael@0: rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri), michael@0: mIconUrl, doc, baseUri); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAutoCString src; michael@0: srcUri->GetSpec(src); michael@0: absoluteUrl = NS_ConvertUTF8toUTF16(src); michael@0: } michael@0: } michael@0: michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr observer = new NotificationObserver(this); michael@0: michael@0: nsString alertName; michael@0: rv = GetAlertName(alertName); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: #ifdef MOZ_B2G michael@0: nsCOMPtr appNotifier = michael@0: do_GetService("@mozilla.org/system-alerts-service;1"); michael@0: if (appNotifier) { michael@0: nsCOMPtr window = GetOwner(); michael@0: uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId(); michael@0: michael@0: if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { michael@0: nsCOMPtr appsService = do_GetService("@mozilla.org/AppsService;1"); michael@0: nsString manifestUrl = EmptyString(); michael@0: rv = appsService->GetManifestURLByLocalId(appId, manifestUrl); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mozilla::AutoSafeJSContext cx; michael@0: JS::Rooted val(cx); michael@0: AppNotificationServiceOptions ops; michael@0: ops.mTextClickable = true; michael@0: ops.mManifestURL = manifestUrl; michael@0: ops.mId = alertName; michael@0: ops.mDbId = mID; michael@0: ops.mDir = DirectionToString(mDir); michael@0: ops.mLang = mLang; michael@0: ops.mTag = mTag; michael@0: michael@0: if (!ops.ToObject(cx, &val)) { michael@0: NS_WARNING("Converting dict to object failed!"); michael@0: return; michael@0: } michael@0: michael@0: appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody, michael@0: observer, val); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // In the case of IPC, the parent process uses the cookie to map to michael@0: // nsIObserver. Thus the cookie must be unique to differentiate observers. michael@0: nsString uniqueCookie = NS_LITERAL_STRING("notification:"); michael@0: uniqueCookie.AppendInt(sCount++); michael@0: alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true, michael@0: uniqueCookie, observer, alertName, michael@0: DirectionToString(mDir), mLang, michael@0: GetPrincipal()); michael@0: } michael@0: michael@0: void michael@0: Notification::RequestPermission(const GlobalObject& aGlobal, michael@0: const Optional >& aCallback, michael@0: ErrorResult& aRv) michael@0: { michael@0: // Get principal from global to make permission request for notifications. michael@0: nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: nsCOMPtr sop = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!sop) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: nsCOMPtr principal = sop->GetPrincipal(); michael@0: michael@0: NotificationPermissionCallback* permissionCallback = nullptr; michael@0: if (aCallback.WasPassed()) { michael@0: permissionCallback = &aCallback.Value(); michael@0: } michael@0: nsCOMPtr request = michael@0: new NotificationPermissionRequest(principal, window, permissionCallback); michael@0: michael@0: NS_DispatchToMainThread(request); michael@0: } michael@0: michael@0: NotificationPermission michael@0: Notification::GetPermission(const GlobalObject& aGlobal, ErrorResult& aRv) michael@0: { michael@0: return GetPermissionInternal(aGlobal.GetAsSupports(), aRv); michael@0: } michael@0: michael@0: NotificationPermission michael@0: Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) michael@0: { michael@0: // Get principal from global to check permission for notifications. michael@0: nsCOMPtr sop = do_QueryInterface(aGlobal); michael@0: if (!sop) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return NotificationPermission::Denied; michael@0: } michael@0: michael@0: nsCOMPtr principal = sop->GetPrincipal(); michael@0: if (nsContentUtils::IsSystemPrincipal(principal)) { michael@0: return NotificationPermission::Granted; michael@0: } else { michael@0: // Allow files to show notifications by default. michael@0: nsCOMPtr uri; michael@0: principal->GetURI(getter_AddRefs(uri)); michael@0: if (uri) { michael@0: bool isFile; michael@0: uri->SchemeIs("file", &isFile); michael@0: if (isFile) { michael@0: return NotificationPermission::Granted; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We also allow notifications is they are pref'ed on. michael@0: if (Preferences::GetBool("notification.prompt.testing", false)) { michael@0: if (Preferences::GetBool("notification.prompt.testing.allow", true)) { michael@0: return NotificationPermission::Granted; michael@0: } else { michael@0: return NotificationPermission::Denied; michael@0: } michael@0: } michael@0: michael@0: uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION; michael@0: michael@0: nsCOMPtr permissionManager = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: michael@0: permissionManager->TestPermissionFromPrincipal(principal, michael@0: "desktop-notification", michael@0: &permission); michael@0: michael@0: // Convert the result to one of the enum types. michael@0: switch (permission) { michael@0: case nsIPermissionManager::ALLOW_ACTION: michael@0: return NotificationPermission::Granted; michael@0: case nsIPermissionManager::DENY_ACTION: michael@0: return NotificationPermission::Denied; michael@0: default: michael@0: return NotificationPermission::Default; michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: Notification::Get(const GlobalObject& aGlobal, michael@0: const GetNotificationOptions& aFilter, michael@0: ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr global = michael@0: do_QueryInterface(aGlobal.GetAsSupports()); michael@0: MOZ_ASSERT(global); michael@0: nsCOMPtr window = do_QueryInterface(global); michael@0: MOZ_ASSERT(window); michael@0: nsIDocument* doc = window->GetExtantDoc(); michael@0: if (!doc) { michael@0: aRv.Throw(NS_ERROR_UNEXPECTED); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsString origin; michael@0: aRv = GetOrigin(window, origin); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr notificationStorage = michael@0: do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsRefPtr promise = new Promise(global); michael@0: nsCOMPtr callback = michael@0: new NotificationStorageCallback(aGlobal, window, promise); michael@0: nsString tag = aFilter.mTag.WasPassed() ? michael@0: aFilter.mTag.Value() : michael@0: EmptyString(); michael@0: aRv = notificationStorage->Get(origin, tag, callback); michael@0: if (aRv.Failed()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return promise.forget(); michael@0: } michael@0: michael@0: JSObject* michael@0: Notification::WrapObject(JSContext* aCx) michael@0: { michael@0: return mozilla::dom::NotificationBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: Notification::Close() michael@0: { michael@0: // Queue a task to close the notification. michael@0: nsCOMPtr closeNotificationTask = michael@0: new NotificationTask(this, NotificationTask::eClose); michael@0: NS_DispatchToMainThread(closeNotificationTask); michael@0: } michael@0: michael@0: void michael@0: Notification::CloseInternal() michael@0: { michael@0: if (!mIsClosed) { michael@0: nsresult rv; michael@0: // Don't bail out if notification storage fails, since we still michael@0: // want to send the close event through the alert service. michael@0: nsCOMPtr notificationStorage = michael@0: do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID); michael@0: if (notificationStorage) { michael@0: nsString origin; michael@0: rv = GetOrigin(GetOwner(), origin); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: notificationStorage->Delete(origin, mID); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr alertService = michael@0: do_GetService(NS_ALERTSERVICE_CONTRACTID); michael@0: if (alertService) { michael@0: nsString alertName; michael@0: rv = GetAlertName(alertName); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: alertService->CloseAlert(alertName, GetPrincipal()); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin) michael@0: { michael@0: if (!aWindow) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsresult rv; michael@0: nsIDocument* doc = aWindow->GetExtantDoc(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: nsIPrincipal* principal = doc->NodePrincipal(); michael@0: NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED); michael@0: michael@0: uint16_t appStatus = principal->GetAppStatus(); michael@0: uint32_t appId = principal->GetAppId(); michael@0: michael@0: if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED || michael@0: appId == nsIScriptSecurityManager::NO_APP_ID || michael@0: appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { michael@0: rv = nsContentUtils::GetUTFOrigin(principal, aOrigin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: // If we are in "app code", use manifest URL as unique origin since michael@0: // multiple apps can share the same origin but not same notifications. michael@0: nsCOMPtr appsService = michael@0: do_GetService("@mozilla.org/AppsService;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: appsService->GetManifestURLByLocalId(appId, aOrigin); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Notification::GetAlertName(nsString& aAlertName) michael@0: { michael@0: // Get the notification name that is unique per origin + tag/ID. michael@0: // The name of the alert is of the form origin#tag/ID. michael@0: nsresult rv = GetOrigin(GetOwner(), aAlertName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: aAlertName.AppendLiteral("#"); michael@0: if (!mTag.IsEmpty()) { michael@0: aAlertName.Append(NS_LITERAL_STRING("tag:")); michael@0: aAlertName.Append(mTag); michael@0: } else { michael@0: aAlertName.Append(NS_LITERAL_STRING("notag:")); michael@0: aAlertName.Append(mID); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: