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: #include "mozilla/dom/DesktopNotification.h" michael@0: #include "mozilla/dom/DesktopNotificationBinding.h" michael@0: #include "mozilla/dom/AppNotificationServiceOptionsBinding.h" michael@0: #include "nsContentPermissionHelper.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "mozilla/dom/PBrowserChild.h" michael@0: #include "nsIDOMDesktopNotification.h" michael@0: #include "TabChild.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsGlobalWindow.h" michael@0: #include "nsIAppsService.h" michael@0: #include "PCOMContentPermissionRequestChild.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "PermissionMessageUtils.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: /* michael@0: * Simple Request michael@0: */ michael@0: class DesktopNotificationRequest : public nsIContentPermissionRequest, michael@0: public nsRunnable, michael@0: public PCOMContentPermissionRequestChild michael@0: michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSICONTENTPERMISSIONREQUEST michael@0: michael@0: DesktopNotificationRequest(DesktopNotification* notification) michael@0: : mDesktopNotification(notification) {} michael@0: michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: nsCOMPtr prompt = michael@0: do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); michael@0: if (prompt) { michael@0: prompt->Prompt(this); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: ~DesktopNotificationRequest() michael@0: { michael@0: } michael@0: michael@0: virtual bool Recv__delete__(const bool& aAllow, michael@0: const InfallibleTArray& choices) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(choices.IsEmpty(), "DesktopNotification doesn't support permission choice"); 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: virtual void IPDLRelease() MOZ_OVERRIDE { Release(); } michael@0: michael@0: nsRefPtr mDesktopNotification; michael@0: }; michael@0: michael@0: /* ------------------------------------------------------------------------ */ michael@0: /* AlertServiceObserver */ michael@0: /* ------------------------------------------------------------------------ */ michael@0: michael@0: NS_IMPL_ISUPPORTS(AlertServiceObserver, nsIObserver) michael@0: michael@0: /* ------------------------------------------------------------------------ */ michael@0: /* DesktopNotification */ michael@0: /* ------------------------------------------------------------------------ */ michael@0: michael@0: uint32_t DesktopNotification::sCount = 0; michael@0: michael@0: nsresult michael@0: DesktopNotification::PostDesktopNotification() michael@0: { michael@0: if (!mObserver) { michael@0: mObserver = new AlertServiceObserver(this); michael@0: } 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: appsService->GetManifestURLByLocalId(appId, manifestUrl); 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: michael@0: if (!ops.ToObject(cx, &val)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return appNotifier->ShowAppNotification(mIconURL, mTitle, mDescription, michael@0: mObserver, val); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr alerts = do_GetService("@mozilla.org/alerts-service;1"); michael@0: if (!alerts) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Generate a unique name (which will also be used as a cookie) because michael@0: // the nsIAlertsService will coalesce notifications with the same name. michael@0: // In the case of IPC, the parent process will use the cookie to map michael@0: // to nsIObservers, thus cookies must be unique to differentiate observers. michael@0: nsString uniqueName = NS_LITERAL_STRING("desktop-notification:"); michael@0: uniqueName.AppendInt(sCount++); michael@0: nsIPrincipal* principal = GetOwner()->GetDoc()->NodePrincipal(); michael@0: return alerts->ShowAlertNotification(mIconURL, mTitle, mDescription, michael@0: true, michael@0: uniqueName, michael@0: mObserver, michael@0: uniqueName, michael@0: NS_LITERAL_STRING("auto"), michael@0: EmptyString(), michael@0: principal); michael@0: } michael@0: michael@0: DesktopNotification::DesktopNotification(const nsAString & title, michael@0: const nsAString & description, michael@0: const nsAString & iconURL, michael@0: nsPIDOMWindow *aWindow, michael@0: nsIPrincipal* principal) michael@0: : DOMEventTargetHelper(aWindow) michael@0: , mTitle(title) michael@0: , mDescription(description) michael@0: , mIconURL(iconURL) michael@0: , mPrincipal(principal) michael@0: , mAllow(false) michael@0: , mShowHasBeenCalled(false) michael@0: { michael@0: if (Preferences::GetBool("notification.disabled", false)) { michael@0: return; michael@0: } michael@0: michael@0: // If we are in testing mode (running mochitests, for example) michael@0: // and we are suppose to allow requests, then just post an allow event. michael@0: if (Preferences::GetBool("notification.prompt.testing", false) && michael@0: Preferences::GetBool("notification.prompt.testing.allow", true)) { michael@0: mAllow = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: DesktopNotification::Init() michael@0: { michael@0: nsRefPtr request = new DesktopNotificationRequest(this); michael@0: michael@0: // if we are in the content process, then remote it to the parent. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: michael@0: // if for some reason mOwner is null, just silently michael@0: // bail. The user will not see a notification, and that michael@0: // is fine. michael@0: if (!GetOwner()) michael@0: return; michael@0: michael@0: // because owner implements nsITabChild, we can assume that it is michael@0: // the one and only TabChild for this docshell. michael@0: TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell()); 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: nsRefPtr copy = request; 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(copy.forget().take(), michael@0: permArray, michael@0: IPC::Principal(mPrincipal)); michael@0: michael@0: request->Sendprompt(); michael@0: return; michael@0: } michael@0: michael@0: // otherwise, dispatch it michael@0: NS_DispatchToMainThread(request); michael@0: } michael@0: michael@0: DesktopNotification::~DesktopNotification() michael@0: { michael@0: if (mObserver) { michael@0: mObserver->Disconnect(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DesktopNotification::DispatchNotificationEvent(const nsString& aName) michael@0: { michael@0: if (NS_FAILED(CheckInnerWindowCorrectness())) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr event; michael@0: nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // it doesn't bubble, and it isn't cancelable michael@0: rv = event->InitEvent(aName, false, false); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: event->SetTrusted(true); michael@0: DispatchDOMEvent(nullptr, event, nullptr, nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: DesktopNotification::SetAllow(bool aAllow) michael@0: { michael@0: mAllow = aAllow; michael@0: michael@0: // if we have called Show() already, lets go ahead and post a notification michael@0: if (mShowHasBeenCalled && aAllow) { michael@0: return PostDesktopNotification(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: DesktopNotification::HandleAlertServiceNotification(const char *aTopic) michael@0: { michael@0: if (NS_FAILED(CheckInnerWindowCorrectness())) { michael@0: return; michael@0: } michael@0: michael@0: if (!strcmp("alertclickcallback", aTopic)) { michael@0: DispatchNotificationEvent(NS_LITERAL_STRING("click")); michael@0: } else if (!strcmp("alertfinished", aTopic)) { michael@0: DispatchNotificationEvent(NS_LITERAL_STRING("close")); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DesktopNotification::Show(ErrorResult& aRv) michael@0: { michael@0: mShowHasBeenCalled = true; michael@0: michael@0: if (!mAllow) { michael@0: return; michael@0: } michael@0: michael@0: aRv = PostDesktopNotification(); michael@0: } michael@0: michael@0: JSObject* michael@0: DesktopNotification::WrapObject(JSContext* aCx) michael@0: { michael@0: return DesktopNotificationBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: /* ------------------------------------------------------------------------ */ michael@0: /* DesktopNotificationCenter */ michael@0: /* ------------------------------------------------------------------------ */ michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(DesktopNotificationCenter) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(DesktopNotificationCenter) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(DesktopNotificationCenter) michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DesktopNotificationCenter) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: already_AddRefed michael@0: DesktopNotificationCenter::CreateNotification(const nsAString& aTitle, michael@0: const nsAString& aDescription, michael@0: const nsAString& aIconURL) michael@0: { michael@0: MOZ_ASSERT(mOwner); michael@0: michael@0: nsRefPtr notification = michael@0: new DesktopNotification(aTitle, michael@0: aDescription, michael@0: aIconURL, michael@0: mOwner, michael@0: mPrincipal); michael@0: notification->Init(); michael@0: return notification.forget(); michael@0: } michael@0: michael@0: JSObject* michael@0: DesktopNotificationCenter::WrapObject(JSContext* aCx) michael@0: { michael@0: return DesktopNotificationCenterBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: /* ------------------------------------------------------------------------ */ michael@0: /* DesktopNotificationRequest */ michael@0: /* ------------------------------------------------------------------------ */ michael@0: michael@0: NS_IMPL_ISUPPORTS(DesktopNotificationRequest, michael@0: nsIContentPermissionRequest, michael@0: nsIRunnable) michael@0: michael@0: NS_IMETHODIMP michael@0: DesktopNotificationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal) michael@0: { michael@0: if (!mDesktopNotification) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: NS_IF_ADDREF(*aRequestingPrincipal = mDesktopNotification->mPrincipal); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DesktopNotificationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow) michael@0: { michael@0: if (!mDesktopNotification) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: NS_IF_ADDREF(*aRequestingWindow = mDesktopNotification->GetOwner()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DesktopNotificationRequest::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: DesktopNotificationRequest::Cancel() michael@0: { michael@0: nsresult rv = mDesktopNotification->SetAllow(false); michael@0: mDesktopNotification = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DesktopNotificationRequest::Allow(JS::HandleValue aChoices) michael@0: { michael@0: MOZ_ASSERT(aChoices.isUndefined()); michael@0: nsresult rv = mDesktopNotification->SetAllow(true); michael@0: mDesktopNotification = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: DesktopNotificationRequest::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: } // namespace dom michael@0: } // namespace mozilla