1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/src/notification/DesktopNotification.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,373 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 +#include "mozilla/dom/DesktopNotification.h" 1.8 +#include "mozilla/dom/DesktopNotificationBinding.h" 1.9 +#include "mozilla/dom/AppNotificationServiceOptionsBinding.h" 1.10 +#include "nsContentPermissionHelper.h" 1.11 +#include "nsXULAppAPI.h" 1.12 +#include "mozilla/dom/PBrowserChild.h" 1.13 +#include "nsIDOMDesktopNotification.h" 1.14 +#include "TabChild.h" 1.15 +#include "mozilla/Preferences.h" 1.16 +#include "nsGlobalWindow.h" 1.17 +#include "nsIAppsService.h" 1.18 +#include "PCOMContentPermissionRequestChild.h" 1.19 +#include "nsIScriptSecurityManager.h" 1.20 +#include "nsServiceManagerUtils.h" 1.21 +#include "PermissionMessageUtils.h" 1.22 + 1.23 +namespace mozilla { 1.24 +namespace dom { 1.25 + 1.26 +/* 1.27 + * Simple Request 1.28 + */ 1.29 +class DesktopNotificationRequest : public nsIContentPermissionRequest, 1.30 + public nsRunnable, 1.31 + public PCOMContentPermissionRequestChild 1.32 + 1.33 +{ 1.34 +public: 1.35 + NS_DECL_ISUPPORTS 1.36 + NS_DECL_NSICONTENTPERMISSIONREQUEST 1.37 + 1.38 + DesktopNotificationRequest(DesktopNotification* notification) 1.39 + : mDesktopNotification(notification) {} 1.40 + 1.41 + NS_IMETHOD Run() MOZ_OVERRIDE 1.42 + { 1.43 + nsCOMPtr<nsIContentPermissionPrompt> prompt = 1.44 + do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); 1.45 + if (prompt) { 1.46 + prompt->Prompt(this); 1.47 + } 1.48 + return NS_OK; 1.49 + } 1.50 + 1.51 + ~DesktopNotificationRequest() 1.52 + { 1.53 + } 1.54 + 1.55 + virtual bool Recv__delete__(const bool& aAllow, 1.56 + const InfallibleTArray<PermissionChoice>& choices) MOZ_OVERRIDE 1.57 + { 1.58 + MOZ_ASSERT(choices.IsEmpty(), "DesktopNotification doesn't support permission choice"); 1.59 + if (aAllow) { 1.60 + (void) Allow(JS::UndefinedHandleValue); 1.61 + } else { 1.62 + (void) Cancel(); 1.63 + } 1.64 + return true; 1.65 + } 1.66 + virtual void IPDLRelease() MOZ_OVERRIDE { Release(); } 1.67 + 1.68 + nsRefPtr<DesktopNotification> mDesktopNotification; 1.69 +}; 1.70 + 1.71 +/* ------------------------------------------------------------------------ */ 1.72 +/* AlertServiceObserver */ 1.73 +/* ------------------------------------------------------------------------ */ 1.74 + 1.75 +NS_IMPL_ISUPPORTS(AlertServiceObserver, nsIObserver) 1.76 + 1.77 +/* ------------------------------------------------------------------------ */ 1.78 +/* DesktopNotification */ 1.79 +/* ------------------------------------------------------------------------ */ 1.80 + 1.81 +uint32_t DesktopNotification::sCount = 0; 1.82 + 1.83 +nsresult 1.84 +DesktopNotification::PostDesktopNotification() 1.85 +{ 1.86 + if (!mObserver) { 1.87 + mObserver = new AlertServiceObserver(this); 1.88 + } 1.89 + 1.90 +#ifdef MOZ_B2G 1.91 + nsCOMPtr<nsIAppNotificationService> appNotifier = 1.92 + do_GetService("@mozilla.org/system-alerts-service;1"); 1.93 + if (appNotifier) { 1.94 + nsCOMPtr<nsPIDOMWindow> window = GetOwner(); 1.95 + uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId(); 1.96 + 1.97 + if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { 1.98 + nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1"); 1.99 + nsString manifestUrl = EmptyString(); 1.100 + appsService->GetManifestURLByLocalId(appId, manifestUrl); 1.101 + mozilla::AutoSafeJSContext cx; 1.102 + JS::Rooted<JS::Value> val(cx); 1.103 + AppNotificationServiceOptions ops; 1.104 + ops.mTextClickable = true; 1.105 + ops.mManifestURL = manifestUrl; 1.106 + 1.107 + if (!ops.ToObject(cx, &val)) { 1.108 + return NS_ERROR_FAILURE; 1.109 + } 1.110 + 1.111 + return appNotifier->ShowAppNotification(mIconURL, mTitle, mDescription, 1.112 + mObserver, val); 1.113 + } 1.114 + } 1.115 +#endif 1.116 + 1.117 + nsCOMPtr<nsIAlertsService> alerts = do_GetService("@mozilla.org/alerts-service;1"); 1.118 + if (!alerts) { 1.119 + return NS_ERROR_NOT_IMPLEMENTED; 1.120 + } 1.121 + 1.122 + // Generate a unique name (which will also be used as a cookie) because 1.123 + // the nsIAlertsService will coalesce notifications with the same name. 1.124 + // In the case of IPC, the parent process will use the cookie to map 1.125 + // to nsIObservers, thus cookies must be unique to differentiate observers. 1.126 + nsString uniqueName = NS_LITERAL_STRING("desktop-notification:"); 1.127 + uniqueName.AppendInt(sCount++); 1.128 + nsIPrincipal* principal = GetOwner()->GetDoc()->NodePrincipal(); 1.129 + return alerts->ShowAlertNotification(mIconURL, mTitle, mDescription, 1.130 + true, 1.131 + uniqueName, 1.132 + mObserver, 1.133 + uniqueName, 1.134 + NS_LITERAL_STRING("auto"), 1.135 + EmptyString(), 1.136 + principal); 1.137 +} 1.138 + 1.139 +DesktopNotification::DesktopNotification(const nsAString & title, 1.140 + const nsAString & description, 1.141 + const nsAString & iconURL, 1.142 + nsPIDOMWindow *aWindow, 1.143 + nsIPrincipal* principal) 1.144 + : DOMEventTargetHelper(aWindow) 1.145 + , mTitle(title) 1.146 + , mDescription(description) 1.147 + , mIconURL(iconURL) 1.148 + , mPrincipal(principal) 1.149 + , mAllow(false) 1.150 + , mShowHasBeenCalled(false) 1.151 +{ 1.152 + if (Preferences::GetBool("notification.disabled", false)) { 1.153 + return; 1.154 + } 1.155 + 1.156 + // If we are in testing mode (running mochitests, for example) 1.157 + // and we are suppose to allow requests, then just post an allow event. 1.158 + if (Preferences::GetBool("notification.prompt.testing", false) && 1.159 + Preferences::GetBool("notification.prompt.testing.allow", true)) { 1.160 + mAllow = true; 1.161 + } 1.162 +} 1.163 + 1.164 +void 1.165 +DesktopNotification::Init() 1.166 +{ 1.167 + nsRefPtr<DesktopNotificationRequest> request = new DesktopNotificationRequest(this); 1.168 + 1.169 + // if we are in the content process, then remote it to the parent. 1.170 + if (XRE_GetProcessType() == GeckoProcessType_Content) { 1.171 + 1.172 + // if for some reason mOwner is null, just silently 1.173 + // bail. The user will not see a notification, and that 1.174 + // is fine. 1.175 + if (!GetOwner()) 1.176 + return; 1.177 + 1.178 + // because owner implements nsITabChild, we can assume that it is 1.179 + // the one and only TabChild for this docshell. 1.180 + TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell()); 1.181 + 1.182 + // Retain a reference so the object isn't deleted without IPDL's knowledge. 1.183 + // Corresponding release occurs in DeallocPContentPermissionRequest. 1.184 + nsRefPtr<DesktopNotificationRequest> copy = request; 1.185 + 1.186 + nsTArray<PermissionRequest> permArray; 1.187 + nsTArray<nsString> emptyOptions; 1.188 + permArray.AppendElement(PermissionRequest( 1.189 + NS_LITERAL_CSTRING("desktop-notification"), 1.190 + NS_LITERAL_CSTRING("unused"), 1.191 + emptyOptions)); 1.192 + child->SendPContentPermissionRequestConstructor(copy.forget().take(), 1.193 + permArray, 1.194 + IPC::Principal(mPrincipal)); 1.195 + 1.196 + request->Sendprompt(); 1.197 + return; 1.198 + } 1.199 + 1.200 + // otherwise, dispatch it 1.201 + NS_DispatchToMainThread(request); 1.202 +} 1.203 + 1.204 +DesktopNotification::~DesktopNotification() 1.205 +{ 1.206 + if (mObserver) { 1.207 + mObserver->Disconnect(); 1.208 + } 1.209 +} 1.210 + 1.211 +void 1.212 +DesktopNotification::DispatchNotificationEvent(const nsString& aName) 1.213 +{ 1.214 + if (NS_FAILED(CheckInnerWindowCorrectness())) { 1.215 + return; 1.216 + } 1.217 + 1.218 + nsCOMPtr<nsIDOMEvent> event; 1.219 + nsresult rv = NS_NewDOMEvent(getter_AddRefs(event), this, nullptr, nullptr); 1.220 + if (NS_SUCCEEDED(rv)) { 1.221 + // it doesn't bubble, and it isn't cancelable 1.222 + rv = event->InitEvent(aName, false, false); 1.223 + if (NS_SUCCEEDED(rv)) { 1.224 + event->SetTrusted(true); 1.225 + DispatchDOMEvent(nullptr, event, nullptr, nullptr); 1.226 + } 1.227 + } 1.228 +} 1.229 + 1.230 +nsresult 1.231 +DesktopNotification::SetAllow(bool aAllow) 1.232 +{ 1.233 + mAllow = aAllow; 1.234 + 1.235 + // if we have called Show() already, lets go ahead and post a notification 1.236 + if (mShowHasBeenCalled && aAllow) { 1.237 + return PostDesktopNotification(); 1.238 + } 1.239 + 1.240 + return NS_OK; 1.241 +} 1.242 + 1.243 +void 1.244 +DesktopNotification::HandleAlertServiceNotification(const char *aTopic) 1.245 +{ 1.246 + if (NS_FAILED(CheckInnerWindowCorrectness())) { 1.247 + return; 1.248 + } 1.249 + 1.250 + if (!strcmp("alertclickcallback", aTopic)) { 1.251 + DispatchNotificationEvent(NS_LITERAL_STRING("click")); 1.252 + } else if (!strcmp("alertfinished", aTopic)) { 1.253 + DispatchNotificationEvent(NS_LITERAL_STRING("close")); 1.254 + } 1.255 +} 1.256 + 1.257 +void 1.258 +DesktopNotification::Show(ErrorResult& aRv) 1.259 +{ 1.260 + mShowHasBeenCalled = true; 1.261 + 1.262 + if (!mAllow) { 1.263 + return; 1.264 + } 1.265 + 1.266 + aRv = PostDesktopNotification(); 1.267 +} 1.268 + 1.269 +JSObject* 1.270 +DesktopNotification::WrapObject(JSContext* aCx) 1.271 +{ 1.272 + return DesktopNotificationBinding::Wrap(aCx, this); 1.273 +} 1.274 + 1.275 +/* ------------------------------------------------------------------------ */ 1.276 +/* DesktopNotificationCenter */ 1.277 +/* ------------------------------------------------------------------------ */ 1.278 + 1.279 +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(DesktopNotificationCenter) 1.280 +NS_IMPL_CYCLE_COLLECTING_ADDREF(DesktopNotificationCenter) 1.281 +NS_IMPL_CYCLE_COLLECTING_RELEASE(DesktopNotificationCenter) 1.282 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DesktopNotificationCenter) 1.283 + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 1.284 + NS_INTERFACE_MAP_ENTRY(nsISupports) 1.285 +NS_INTERFACE_MAP_END 1.286 + 1.287 +already_AddRefed<DesktopNotification> 1.288 +DesktopNotificationCenter::CreateNotification(const nsAString& aTitle, 1.289 + const nsAString& aDescription, 1.290 + const nsAString& aIconURL) 1.291 +{ 1.292 + MOZ_ASSERT(mOwner); 1.293 + 1.294 + nsRefPtr<DesktopNotification> notification = 1.295 + new DesktopNotification(aTitle, 1.296 + aDescription, 1.297 + aIconURL, 1.298 + mOwner, 1.299 + mPrincipal); 1.300 + notification->Init(); 1.301 + return notification.forget(); 1.302 +} 1.303 + 1.304 +JSObject* 1.305 +DesktopNotificationCenter::WrapObject(JSContext* aCx) 1.306 +{ 1.307 + return DesktopNotificationCenterBinding::Wrap(aCx, this); 1.308 +} 1.309 + 1.310 +/* ------------------------------------------------------------------------ */ 1.311 +/* DesktopNotificationRequest */ 1.312 +/* ------------------------------------------------------------------------ */ 1.313 + 1.314 +NS_IMPL_ISUPPORTS(DesktopNotificationRequest, 1.315 + nsIContentPermissionRequest, 1.316 + nsIRunnable) 1.317 + 1.318 +NS_IMETHODIMP 1.319 +DesktopNotificationRequest::GetPrincipal(nsIPrincipal * *aRequestingPrincipal) 1.320 +{ 1.321 + if (!mDesktopNotification) { 1.322 + return NS_ERROR_NOT_INITIALIZED; 1.323 + } 1.324 + 1.325 + NS_IF_ADDREF(*aRequestingPrincipal = mDesktopNotification->mPrincipal); 1.326 + return NS_OK; 1.327 +} 1.328 + 1.329 +NS_IMETHODIMP 1.330 +DesktopNotificationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow) 1.331 +{ 1.332 + if (!mDesktopNotification) { 1.333 + return NS_ERROR_NOT_INITIALIZED; 1.334 + } 1.335 + 1.336 + NS_IF_ADDREF(*aRequestingWindow = mDesktopNotification->GetOwner()); 1.337 + return NS_OK; 1.338 +} 1.339 + 1.340 +NS_IMETHODIMP 1.341 +DesktopNotificationRequest::GetElement(nsIDOMElement * *aElement) 1.342 +{ 1.343 + NS_ENSURE_ARG_POINTER(aElement); 1.344 + *aElement = nullptr; 1.345 + return NS_OK; 1.346 +} 1.347 + 1.348 +NS_IMETHODIMP 1.349 +DesktopNotificationRequest::Cancel() 1.350 +{ 1.351 + nsresult rv = mDesktopNotification->SetAllow(false); 1.352 + mDesktopNotification = nullptr; 1.353 + return rv; 1.354 +} 1.355 + 1.356 +NS_IMETHODIMP 1.357 +DesktopNotificationRequest::Allow(JS::HandleValue aChoices) 1.358 +{ 1.359 + MOZ_ASSERT(aChoices.isUndefined()); 1.360 + nsresult rv = mDesktopNotification->SetAllow(true); 1.361 + mDesktopNotification = nullptr; 1.362 + return rv; 1.363 +} 1.364 + 1.365 +NS_IMETHODIMP 1.366 +DesktopNotificationRequest::GetTypes(nsIArray** aTypes) 1.367 +{ 1.368 + nsTArray<nsString> emptyOptions; 1.369 + return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), 1.370 + NS_LITERAL_CSTRING("unused"), 1.371 + emptyOptions, 1.372 + aTypes); 1.373 +} 1.374 + 1.375 +} // namespace dom 1.376 +} // namespace mozilla