Notification.cpp (29472B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/Notification.h" 8 9 #include <utility> 10 11 #include "Navigator.h" 12 #include "NotificationUtils.h" 13 #include "mozilla/HoldDropJSObjects.h" 14 #include "mozilla/OwningNonNull.h" 15 #include "mozilla/StaticPrefs_dom.h" 16 #include "mozilla/UseCounter.h" 17 #include "mozilla/dom/BindingUtils.h" 18 #include "mozilla/dom/Document.h" 19 #include "mozilla/dom/Promise-inl.h" 20 #include "mozilla/dom/Promise.h" 21 #include "mozilla/dom/RootedDictionary.h" 22 #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" 23 #include "mozilla/dom/WorkerRunnable.h" 24 #include "mozilla/dom/WorkerScope.h" 25 #include "mozilla/glean/DomNotificationMetrics.h" 26 #include "mozilla/ipc/BackgroundChild.h" 27 #include "mozilla/ipc/BackgroundUtils.h" 28 #include "mozilla/ipc/PBackgroundChild.h" 29 #include "nsContentPermissionHelper.h" 30 #include "nsContentUtils.h" 31 #include "nsGlobalWindowInner.h" 32 #include "nsIContentPermissionPrompt.h" 33 #include "nsIScriptError.h" 34 #include "nsNetUtil.h" 35 #include "nsStructuredCloneContainer.h" 36 37 namespace mozilla::dom { 38 39 using namespace notification; 40 41 class NotificationPermissionRequest : public ContentPermissionRequestBase, 42 public nsIRunnable, 43 public nsINamed { 44 public: 45 NS_DECL_NSIRUNNABLE 46 NS_DECL_ISUPPORTS_INHERITED 47 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(NotificationPermissionRequest, 48 ContentPermissionRequestBase) 49 50 // nsIContentPermissionRequest 51 NS_IMETHOD Cancel(void) override; 52 NS_IMETHOD Allow(JS::Handle<JS::Value> choices) override; 53 54 NotificationPermissionRequest(nsIPrincipal* aPrincipal, 55 nsIPrincipal* aEffectiveStoragePrincipal, 56 nsPIDOMWindowInner* aWindow, Promise* aPromise, 57 NotificationPermissionCallback* aCallback) 58 : ContentPermissionRequestBase(aPrincipal, aWindow, "notification"_ns, 59 "desktop-notification"_ns), 60 mEffectiveStoragePrincipal(aEffectiveStoragePrincipal), 61 mPermission(NotificationPermission::Default), 62 mPromise(aPromise), 63 mCallback(aCallback) { 64 MOZ_ASSERT(aPromise); 65 } 66 67 NS_IMETHOD GetName(nsACString& aName) override { 68 aName.AssignLiteral("NotificationPermissionRequest"); 69 return NS_OK; 70 } 71 72 protected: 73 ~NotificationPermissionRequest() = default; 74 75 MOZ_CAN_RUN_SCRIPT nsresult ResolvePromise(); 76 nsresult DispatchResolvePromise(); 77 nsCOMPtr<nsIPrincipal> mEffectiveStoragePrincipal; 78 NotificationPermission mPermission; 79 RefPtr<Promise> mPromise; 80 RefPtr<NotificationPermissionCallback> mCallback; 81 }; 82 83 namespace { 84 class GetPermissionRunnable final : public WorkerMainThreadRunnable { 85 public: 86 explicit GetPermissionRunnable(WorkerPrivate* aWorker, 87 bool aUseRegularPrincipal, 88 PermissionCheckPurpose aPurpose) 89 : WorkerMainThreadRunnable(aWorker, "Notification :: Get Permission"_ns), 90 mUseRegularPrincipal(aUseRegularPrincipal), 91 mPurpose(aPurpose) {} 92 93 bool MainThreadRun() override { 94 MOZ_ASSERT(mWorkerRef); 95 WorkerPrivate* workerPrivate = mWorkerRef->Private(); 96 nsIPrincipal* principal = workerPrivate->GetPrincipal(); 97 nsIPrincipal* effectiveStoragePrincipal = 98 mUseRegularPrincipal ? principal 99 : workerPrivate->GetPartitionedPrincipal(); 100 mPermission = 101 GetNotificationPermission(principal, effectiveStoragePrincipal, 102 workerPrivate->IsSecureContext(), mPurpose); 103 return true; 104 } 105 106 NotificationPermission GetPermission() { return mPermission; } 107 108 private: 109 NotificationPermission mPermission = NotificationPermission::Denied; 110 bool mUseRegularPrincipal; 111 PermissionCheckPurpose mPurpose; 112 }; 113 114 } // anonymous namespace 115 116 NS_IMPL_CYCLE_COLLECTION_INHERITED(NotificationPermissionRequest, 117 ContentPermissionRequestBase, mCallback) 118 NS_IMPL_ADDREF_INHERITED(NotificationPermissionRequest, 119 ContentPermissionRequestBase) 120 NS_IMPL_RELEASE_INHERITED(NotificationPermissionRequest, 121 ContentPermissionRequestBase) 122 123 NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED( 124 NotificationPermissionRequest, ContentPermissionRequestBase, nsIRunnable, 125 nsINamed) 126 127 NS_IMETHODIMP 128 NotificationPermissionRequest::Run() { 129 if (IsNotificationAllowedFor(mPrincipal)) { 130 mPermission = NotificationPermission::Granted; 131 } else if (IsNotificationForbiddenFor( 132 mPrincipal, mEffectiveStoragePrincipal, 133 mWindow->IsSecureContext(), 134 PermissionCheckPurpose::PermissionRequest, 135 mWindow->GetExtantDoc())) { 136 mPermission = NotificationPermission::Denied; 137 } else if (!StaticPrefs::dom_webnotifications_allowcrossoriginiframe() && 138 !mPrincipal->Subsumes(mTopLevelPrincipal)) { 139 mPermission = NotificationPermission::Denied; 140 } 141 142 // We can't call ShowPrompt() directly here since our logic for determining 143 // whether to display a prompt depends on the checks above as well as the 144 // result of CheckPromptPrefs(). So we have to manually check the prompt 145 // prefs and decide what to do based on that. 146 PromptResult pr = CheckPromptPrefs(); 147 switch (pr) { 148 case PromptResult::Granted: 149 mPermission = NotificationPermission::Granted; 150 break; 151 case PromptResult::Denied: 152 mPermission = NotificationPermission::Denied; 153 break; 154 default: 155 // ignore 156 break; 157 } 158 159 if (!mHasValidTransientUserGestureActivation && 160 !StaticPrefs::dom_webnotifications_requireuserinteraction()) { 161 nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); 162 if (doc) { 163 doc->WarnOnceAbout(Document::eNotificationsRequireUserGestureDeprecation); 164 } 165 } 166 167 if (mPermission != NotificationPermission::Default) { 168 return DispatchResolvePromise(); 169 } 170 171 return nsContentPermissionUtils::AskPermission(this, mWindow); 172 } 173 174 NS_IMETHODIMP 175 NotificationPermissionRequest::Cancel() { 176 // `Cancel` is called if the user denied permission or dismissed the 177 // permission request. To distinguish between the two, we set the 178 // permission to "default" and query the permission manager in 179 // `ResolvePromise`. 180 mPermission = NotificationPermission::Default; 181 return DispatchResolvePromise(); 182 } 183 184 NS_IMETHODIMP 185 NotificationPermissionRequest::Allow(JS::Handle<JS::Value> aChoices) { 186 MOZ_ASSERT(aChoices.isUndefined()); 187 188 mPermission = NotificationPermission::Granted; 189 return DispatchResolvePromise(); 190 } 191 192 inline nsresult NotificationPermissionRequest::DispatchResolvePromise() { 193 nsCOMPtr<nsIRunnable> resolver = 194 NewRunnableMethod("NotificationPermissionRequest::DispatchResolvePromise", 195 this, &NotificationPermissionRequest::ResolvePromise); 196 return nsGlobalWindowInner::Cast(mWindow.get())->Dispatch(resolver.forget()); 197 } 198 199 nsresult NotificationPermissionRequest::ResolvePromise() { 200 nsresult rv = NS_OK; 201 // This will still be "default" if the user dismissed the doorhanger, 202 // or "denied" otherwise. 203 if (mPermission == NotificationPermission::Default) { 204 // When the front-end has decided to deny the permission request 205 // automatically and we are not handling user input, then log a 206 // warning in the current document that this happened because 207 // Notifications require a user gesture. 208 if (!mHasValidTransientUserGestureActivation && 209 StaticPrefs::dom_webnotifications_requireuserinteraction()) { 210 nsCOMPtr<Document> doc = mWindow->GetExtantDoc(); 211 if (doc) { 212 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, 213 doc, nsContentUtils::eDOM_PROPERTIES, 214 "NotificationsRequireUserGesture"); 215 } 216 } 217 218 mPermission = GetRawNotificationPermission(mPrincipal); 219 } 220 if (mCallback) { 221 ErrorResult error; 222 RefPtr<NotificationPermissionCallback> callback(mCallback); 223 callback->Call(mPermission, error); 224 rv = error.StealNSResult(); 225 } 226 mPromise->MaybeResolve(mPermission); 227 return rv; 228 } 229 230 // static 231 bool Notification::PrefEnabled(JSContext* aCx, JSObject* aObj) { 232 return StaticPrefs::dom_webnotifications_enabled(); 233 } 234 235 Notification::Notification(nsIGlobalObject* aGlobal, 236 const IPCNotification& aIPCNotification, 237 const nsAString& aScope) 238 : DOMEventTargetHelper(aGlobal), 239 mIPCNotification(aIPCNotification), 240 mData(JS::NullValue()), 241 mScope(aScope) { 242 KeepAliveIfHasListenersFor(nsGkAtoms::onclick); 243 KeepAliveIfHasListenersFor(nsGkAtoms::onshow); 244 KeepAliveIfHasListenersFor(nsGkAtoms::onerror); 245 KeepAliveIfHasListenersFor(nsGkAtoms::onclose); 246 } 247 248 // May be called on any thread. 249 // static 250 already_AddRefed<Notification> Notification::Constructor( 251 const GlobalObject& aGlobal, const nsAString& aTitle, 252 const NotificationOptions& aOptions, ErrorResult& aRv) { 253 // FIXME(nsm): If the sticky flag is set, throw an error. 254 RefPtr<ServiceWorkerGlobalScope> scope; 255 UNWRAP_OBJECT(ServiceWorkerGlobalScope, aGlobal.Get(), scope); 256 if (scope) { 257 aRv.ThrowTypeError( 258 "Notification constructor cannot be used in ServiceWorkerGlobalScope. " 259 "Use registration.showNotification() instead."); 260 return nullptr; 261 } 262 263 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 264 RefPtr<Notification> notification = ValidateAndCreate( 265 aGlobal.Context(), global, aTitle, aOptions, u""_ns, aRv); 266 if (NS_WARN_IF(aRv.Failed())) { 267 return nullptr; 268 } 269 270 RefPtr<Promise> promise = Promise::CreateInfallible(global); 271 promise->AddCallbacksWithCycleCollectedArgs( 272 [](JSContext*, JS::Handle<JS::Value>, ErrorResult&, 273 Notification* aNotification) { 274 aNotification->DispatchTrustedEvent(u"show"_ns); 275 }, 276 [](JSContext*, JS::Handle<JS::Value> aValue, ErrorResult&, 277 Notification* aNotification) { 278 aNotification->DispatchTrustedEvent(u"error"_ns); 279 aNotification->Deactivate(); 280 }, 281 notification); 282 if (!notification->CreateActor() || !notification->SendShow(promise)) { 283 notification->Deactivate(); 284 return nullptr; 285 } 286 287 notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclick); 288 notification->KeepAliveIfHasListenersFor(nsGkAtoms::onshow); 289 notification->KeepAliveIfHasListenersFor(nsGkAtoms::onerror); 290 notification->KeepAliveIfHasListenersFor(nsGkAtoms::onclose); 291 292 return notification.forget(); 293 } 294 295 // NOTE(krosylight): Maybe move this check to the parent process? 296 Result<Ok, nsresult> ValidateBase64Data(const nsAString& aData) { 297 if (aData.IsEmpty()) { 298 return Ok(); 299 } 300 301 // To and from to ensure it is valid base64. 302 RefPtr<nsStructuredCloneContainer> container = 303 new nsStructuredCloneContainer(); 304 MOZ_TRY(container->InitFromBase64(aData, JS_STRUCTURED_CLONE_VERSION)); 305 306 nsString result; 307 MOZ_TRY(container->GetDataAsBase64(result)); 308 309 return Ok(); 310 } 311 312 // static 313 Result<already_AddRefed<Notification>, nsresult> Notification::ConstructFromIPC( 314 nsIGlobalObject* aGlobal, const IPCNotification& aIPCNotification, 315 const nsAString& aServiceWorkerRegistrationScope) { 316 MOZ_ASSERT(aGlobal); 317 318 MOZ_TRY(ValidateBase64Data(aIPCNotification.options().dataSerialized())); 319 320 RefPtr<Notification> notification = new Notification( 321 aGlobal, aIPCNotification, aServiceWorkerRegistrationScope); 322 323 return notification.forget(); 324 } 325 326 void Notification::MaybeNotifyClose() { 327 if (mIsClosed) { 328 return; 329 } 330 mIsClosed = true; 331 DispatchTrustedEvent(u"close"_ns); 332 } 333 334 static Result<nsString, nsresult> SerializeDataAsBase64( 335 JSContext* aCx, JS::Handle<JS::Value> aData) { 336 if (aData.isNull()) { 337 return nsString(); 338 } 339 RefPtr<nsStructuredCloneContainer> dataObjectContainer = 340 new nsStructuredCloneContainer(); 341 MOZ_TRY(dataObjectContainer->InitFromJSVal(aData, aCx)); 342 343 nsString result; 344 MOZ_TRY(dataObjectContainer->GetDataAsBase64(result)); 345 346 return result; 347 } 348 349 #define SetUseCounterIf(wasUsed, memberName) \ 350 if (wasUsed) { \ 351 if (NS_IsMainThread()) { \ 352 SetUseCounter(aGlobal->GetGlobalJSObject(), \ 353 eUseCounter_NotificationOptions_##memberName); \ 354 } else { \ 355 SetUseCounter(UseCounterWorker::NotificationOptions_##memberName); \ 356 } \ 357 } 358 359 /* static */ 360 // https://notifications.spec.whatwg.org/#create-a-notification 361 already_AddRefed<Notification> Notification::ValidateAndCreate( 362 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aTitle, 363 const NotificationOptions& aOptions, const nsAString& aScope, 364 ErrorResult& aRv) { 365 MOZ_ASSERT(aGlobal); 366 367 SetUseCounterIf(aOptions.mNavigate.WasPassed(), navigate); 368 SetUseCounterIf(aOptions.mImage.WasPassed(), image); 369 SetUseCounterIf(aOptions.mBadge.WasPassed(), badge); 370 SetUseCounterIf(aOptions.mVibrate.WasPassed(), vibrate); 371 SetUseCounterIf(aOptions.mTimestamp.WasPassed(), timestamp); 372 SetUseCounterIf(aOptions.mRenotify, renotify); 373 SetUseCounterIf(aOptions.mRequireInteraction, requireInteraction); 374 SetUseCounterIf(!aOptions.mActions.IsEmpty(), actions); 375 376 // Step 4: Set notification’s data to 377 // StructuredSerializeForStorage(options["data"]). 378 JS::Rooted<JS::Value> data(aCx, aOptions.mData); 379 Result<nsString, nsresult> dataResult = SerializeDataAsBase64(aCx, data); 380 if (dataResult.isErr()) { 381 aRv = dataResult.unwrapErr(); 382 return nullptr; 383 } 384 385 // Step 17: Set notification’s silent preference to options["silent"]. 386 bool silent = false; 387 if (StaticPrefs::dom_webnotifications_silent_enabled()) { 388 silent = aOptions.mSilent; 389 } 390 391 nsTArray<uint32_t> vibrate; 392 if (StaticPrefs::dom_webnotifications_vibrate_enabled() && 393 aOptions.mVibrate.WasPassed()) { 394 // Step 2: If options["silent"] is true and options["vibrate"] exists, then 395 // throw a TypeError. 396 if (silent) { 397 aRv.ThrowTypeError( 398 "Silent notifications must not specify vibration patterns."); 399 return nullptr; 400 } 401 402 // Step 14: If options["vibrate"] exists, then validate and normalize it and 403 // set notification’s vibration pattern to the return value. 404 const OwningUnsignedLongOrUnsignedLongSequence& value = 405 aOptions.mVibrate.Value(); 406 if (value.IsUnsignedLong()) { 407 AutoTArray<uint32_t, 1> array; 408 array.AppendElement(value.GetAsUnsignedLong()); 409 vibrate = SanitizeVibratePattern(array); 410 } else { 411 vibrate = SanitizeVibratePattern(value.GetAsUnsignedLongSequence()); 412 } 413 } 414 415 // Step 12: If options["icon"] exists, then parse it using baseURL, and if 416 // that does not return failure, set notification’s icon URL to the return 417 // value. (Otherwise icon URL is not set.) 418 RefPtr<nsIURI> iconUrl = ResolveIconURL(aGlobal, aOptions.mIcon); 419 420 // Step 19: Set notification’s actions to « ». 421 nsTArray<IPCNotificationAction> actions; 422 if (StaticPrefs::dom_webnotifications_actions_enabled()) { 423 // Step 20: For each entry in options["actions"], up to the maximum number 424 // of actions supported (skip any excess entries): 425 for (const auto& entry : aOptions.mActions) { 426 // Step 20.1: Let action be a new notification action. 427 IPCNotificationAction action; 428 // Step 20.2: Set action’s name to entry["action"]. 429 action.name() = entry.mAction; 430 // Step 20.3: Set action’s title to entry["title"]. 431 action.title() = entry.mTitle; 432 // Step 20.4: (Skipping icon support, see 433 // https://github.com/whatwg/notifications/issues/233) 434 // Step 20.5: Append action to notification’s actions. 435 actions.AppendElement(std::move(action)); 436 if (actions.Length() == kMaxActions) { 437 break; 438 } 439 } 440 } 441 442 IPCNotification ipcNotification( 443 nsString(), IPCNotificationOptions( 444 nsString(aTitle), aOptions.mDir, nsString(aOptions.mLang), 445 nsString(aOptions.mBody), nsString(aOptions.mTag), 446 iconUrl, aOptions.mRequireInteraction, silent, vibrate, 447 nsString(dataResult.unwrap()), actions)); 448 449 RefPtr<Notification> notification = 450 new Notification(aGlobal, ipcNotification, aScope); 451 return notification.forget(); 452 } 453 454 Notification::~Notification() { mozilla::DropJSObjects(this); } 455 456 NS_IMPL_CYCLE_COLLECTION_CLASS(Notification) 457 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Notification, 458 DOMEventTargetHelper) 459 tmp->mData.setUndefined(); 460 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR 461 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 462 463 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Notification, 464 DOMEventTargetHelper) 465 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 466 467 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(Notification, 468 DOMEventTargetHelper) 469 NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData) 470 NS_IMPL_CYCLE_COLLECTION_TRACE_END 471 472 NS_IMPL_ADDREF_INHERITED(Notification, DOMEventTargetHelper) 473 NS_IMPL_RELEASE_INHERITED(Notification, DOMEventTargetHelper) 474 475 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Notification) 476 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 477 478 /* static */ 479 bool Notification::RequestPermissionEnabledForScope(JSContext* aCx, 480 JSObject* /* unused */) { 481 // requestPermission() is not allowed on workers. The calling page should ask 482 // for permission on the worker's behalf. This is to prevent 'which window 483 // should show the browser pop-up'. See discussion: 484 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2013-October/041272.html 485 return NS_IsMainThread(); 486 } 487 488 // static 489 already_AddRefed<Promise> Notification::RequestPermission( 490 const GlobalObject& aGlobal, 491 const Optional<OwningNonNull<NotificationPermissionCallback>>& aCallback, 492 ErrorResult& aRv) { 493 AssertIsOnMainThread(); 494 495 // Get principal from global to make permission request for notifications. 496 nsCOMPtr<nsPIDOMWindowInner> window = 497 do_QueryInterface(aGlobal.GetAsSupports()); 498 nsCOMPtr<nsIScriptObjectPrincipal> sop = 499 do_QueryInterface(aGlobal.GetAsSupports()); 500 if (!sop || !window) { 501 aRv.Throw(NS_ERROR_UNEXPECTED); 502 return nullptr; 503 } 504 505 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); 506 nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal = 507 sop->GetEffectiveStoragePrincipal(); 508 if (!principal || !effectiveStoragePrincipal) { 509 aRv.Throw(NS_ERROR_UNEXPECTED); 510 return nullptr; 511 } 512 513 RefPtr<Promise> promise = Promise::Create(window->AsGlobal(), aRv); 514 if (aRv.Failed()) { 515 return nullptr; 516 } 517 NotificationPermissionCallback* permissionCallback = nullptr; 518 if (aCallback.WasPassed()) { 519 permissionCallback = &aCallback.Value(); 520 } 521 nsCOMPtr<nsIRunnable> request = 522 new NotificationPermissionRequest(principal, effectiveStoragePrincipal, 523 window, promise, permissionCallback); 524 525 window->AsGlobal()->Dispatch(request.forget()); 526 527 return promise.forget(); 528 } 529 530 // static 531 NotificationPermission Notification::GetPermission(const GlobalObject& aGlobal, 532 ErrorResult& aRv) { 533 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 534 return GetPermission(global, PermissionCheckPurpose::PermissionAttribute, 535 aRv); 536 } 537 538 // static 539 NotificationPermission Notification::GetPermission( 540 nsIGlobalObject* aGlobal, PermissionCheckPurpose aPurpose, 541 ErrorResult& aRv) { 542 if (NS_IsMainThread()) { 543 return GetPermissionInternal(aGlobal->GetAsInnerWindow(), aPurpose, aRv); 544 } 545 546 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 547 MOZ_ASSERT(worker); 548 RefPtr<GetPermissionRunnable> r = new GetPermissionRunnable( 549 worker, worker->UseRegularPrincipal(), aPurpose); 550 r->Dispatch(worker, Canceling, aRv); 551 if (aRv.Failed()) { 552 return NotificationPermission::Denied; 553 } 554 555 return r->GetPermission(); 556 } 557 558 /* static */ 559 NotificationPermission Notification::GetPermissionInternal( 560 nsPIDOMWindowInner* aWindow, PermissionCheckPurpose aPurpose, 561 ErrorResult& aRv) { 562 // Get principal from global to check permission for notifications. 563 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow); 564 if (!sop) { 565 aRv.Throw(NS_ERROR_UNEXPECTED); 566 return NotificationPermission::Denied; 567 } 568 569 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); 570 nsCOMPtr<nsIPrincipal> effectiveStoragePrincipal = 571 sop->GetEffectiveStoragePrincipal(); 572 if (!principal || !effectiveStoragePrincipal) { 573 aRv.Throw(NS_ERROR_UNEXPECTED); 574 return NotificationPermission::Denied; 575 } 576 577 return GetNotificationPermission(principal, effectiveStoragePrincipal, 578 aWindow->IsSecureContext(), aPurpose); 579 } 580 581 uint32_t Notification::MaxActions(const GlobalObject& aGlobal) { 582 return kMaxActions; 583 } 584 585 already_AddRefed<nsIURI> Notification::ResolveIconURL( 586 nsIGlobalObject* aGlobal, const nsACString& aIconUrl) { 587 nsresult rv = NS_OK; 588 589 if (aIconUrl.IsEmpty()) { 590 return nullptr; 591 } 592 593 nsCOMPtr<nsIURI> baseUri = aGlobal->GetBaseURI(); 594 if (!baseUri) { 595 return nullptr; 596 } 597 598 nsCOMPtr<nsIURI> srcUri; 599 rv = NS_NewURI(getter_AddRefs(srcUri), aIconUrl, nullptr, baseUri); 600 if (NS_FAILED(rv)) { 601 return nullptr; 602 } 603 604 return srcUri.forget(); 605 } 606 607 JSObject* Notification::WrapObject(JSContext* aCx, 608 JS::Handle<JSObject*> aGivenProto) { 609 return mozilla::dom::Notification_Binding::Wrap(aCx, this, aGivenProto); 610 } 611 612 void Notification::Close() { 613 if (mIsClosed) { 614 return; 615 } 616 if (!mActor) { 617 CreateActor(); 618 } 619 if (mActor) { 620 (void)mActor->SendClose(); 621 } 622 } 623 624 bool Notification::RequireInteraction() const { 625 return mIPCNotification.options().requireInteraction(); 626 } 627 628 bool Notification::Silent() const { 629 return mIPCNotification.options().silent(); 630 } 631 632 void Notification::GetVibrate(nsTArray<uint32_t>& aRetval) const { 633 aRetval = mIPCNotification.options().vibrate().Clone(); 634 } 635 636 void Notification::GetData(JSContext* aCx, 637 JS::MutableHandle<JS::Value> aRetval) { 638 const nsString& dataSerialized = mIPCNotification.options().dataSerialized(); 639 if (mData.isNull() && !dataSerialized.IsEmpty()) { 640 nsresult rv; 641 RefPtr<nsStructuredCloneContainer> container = 642 new nsStructuredCloneContainer(); 643 rv = container->InitFromBase64(dataSerialized, JS_STRUCTURED_CLONE_VERSION); 644 if (NS_WARN_IF(NS_FAILED(rv))) { 645 aRetval.setNull(); 646 return; 647 } 648 649 JS::Rooted<JS::Value> data(aCx); 650 rv = container->DeserializeToJsval(aCx, &data); 651 if (NS_WARN_IF(NS_FAILED(rv))) { 652 aRetval.setNull(); 653 return; 654 } 655 656 if (data.isGCThing()) { 657 mozilla::HoldJSObjects(this); 658 } 659 mData = data; 660 } 661 if (mData.isNull()) { 662 aRetval.setNull(); 663 return; 664 } 665 666 aRetval.set(mData); 667 } 668 669 void Notification::GetActions(nsTArray<NotificationAction>& aRetVal) { 670 aRetVal.Clear(); 671 for (const IPCNotificationAction& entry : 672 mIPCNotification.options().actions()) { 673 RootedDictionary<NotificationAction> action(RootingCx()); 674 action.mAction = entry.name(); 675 action.mTitle = entry.title(); 676 aRetVal.AppendElement(action); 677 } 678 } 679 680 // Steps 2-5 of 681 // https://notifications.spec.whatwg.org/#dom-serviceworkerregistration-shownotification 682 /* static */ 683 already_AddRefed<Promise> Notification::ShowPersistentNotification( 684 JSContext* aCx, nsIGlobalObject* aGlobal, const nsAString& aScope, 685 const nsAString& aTitle, const NotificationOptions& aOptions, 686 const ServiceWorkerRegistrationDescriptor& aDescriptor, ErrorResult& aRv) { 687 MOZ_ASSERT(aGlobal); 688 689 // Step 2: Let promise be a new promise in this’s relevant Realm. 690 RefPtr<Promise> p = Promise::Create(aGlobal, aRv); 691 if (NS_WARN_IF(aRv.Failed())) { 692 return nullptr; 693 } 694 695 // Step 3: If this’s active worker is null, then reject promise with a 696 // TypeError and return promise. 697 if (!aDescriptor.GetActive()) { 698 aRv.ThrowTypeError<MSG_NO_ACTIVE_WORKER>(NS_ConvertUTF16toUTF8(aScope)); 699 return nullptr; 700 } 701 702 // Step 4: Let notification be the result of creating a notification with a 703 // settings object given title, options, and this’s relevant settings object. 704 // If this threw an exception, then reject promise with that exception and 705 // return promise. 706 // 707 // Step 5: Set notification’s service worker registration to this. 708 // 709 // Note: We currently use the scope as the unique identifier for the 710 // registration (and there currently is no durable registration identifier, 711 // so this is necessary), which is why we pass in the scope. See 712 // https://github.com/whatwg/notifications/issues/205 for some scope-related 713 // discussion. 714 // 715 // XXX: We create Notification object almost solely to share the parameter 716 // normalization steps. It would be nice to export that and skip creating 717 // object here. 718 RefPtr<Notification> notification = 719 ValidateAndCreate(aCx, aGlobal, aTitle, aOptions, aScope, aRv); 720 if (NS_WARN_IF(aRv.Failed())) { 721 return nullptr; 722 } 723 724 if (!notification->CreateActor() || !notification->SendShow(p)) { 725 return nullptr; 726 } 727 728 return p.forget(); 729 } 730 731 bool Notification::CreateActor() { 732 mozilla::ipc::PBackgroundChild* backgroundActor = 733 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); 734 735 // Note: We are not using the typical PBackground managed actor here as we 736 // want the actor to be in the main thread of the main process. Instead we 737 // pass the endpoint to PBackground, it dispatches a runnable to the main 738 // thread, and the endpoint is bound there. 739 740 mozilla::ipc::Endpoint<notification::PNotificationParent> parentEndpoint; 741 mozilla::ipc::Endpoint<notification::PNotificationChild> childEndpoint; 742 notification::PNotification::CreateEndpoints(&parentEndpoint, &childEndpoint); 743 744 bool persistent = !mScope.IsEmpty(); 745 RefPtr<nsPIDOMWindowInner> window = GetOwnerWindow(); 746 mActor = new notification::NotificationChild( 747 persistent ? nullptr : this, 748 window ? window->GetWindowGlobalChild() : nullptr); 749 750 nsISerialEventTarget* target = nullptr; 751 nsIPrincipal* principal; 752 nsIPrincipal* effectiveStoragePrincipal; 753 bool isSecureContext; 754 755 // TODO: Should get nsIGlobalObject methods for each method 756 if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) { 757 target = workerPrivate->HybridEventTarget(); 758 principal = workerPrivate->GetPrincipal(); 759 effectiveStoragePrincipal = workerPrivate->GetEffectiveStoragePrincipal(); 760 isSecureContext = workerPrivate->IsSecureContext(); 761 } else { 762 nsGlobalWindowInner* win = GetOwnerWindow(); 763 NS_ENSURE_TRUE(win, false); 764 principal = win->GetPrincipal(); 765 effectiveStoragePrincipal = win->GetEffectiveStoragePrincipal(); 766 isSecureContext = win->IsSecureContext(); 767 } 768 769 if (!childEndpoint.Bind(mActor, target)) { 770 return false; 771 } 772 773 (void)backgroundActor->SendCreateNotificationParent( 774 std::move(parentEndpoint), WrapNotNull(principal), 775 WrapNotNull(effectiveStoragePrincipal), isSecureContext, mScope, 776 mIPCNotification); 777 778 return true; 779 } 780 781 bool Notification::SendShow(Promise* aPromise) { 782 mActor->SendShow()->Then( 783 GetCurrentSerialEventTarget(), __func__, 784 [self = RefPtr{this}, promise = RefPtr(aPromise)]( 785 notification::PNotificationChild::ShowPromise::ResolveOrRejectValue&& 786 aResult) { 787 if (aResult.IsReject()) { 788 promise->MaybeRejectWithUnknownError("Failed to open notification"); 789 self->Deactivate(); 790 return; 791 } 792 793 CopyableErrorResult rv = aResult.ResolveValue(); 794 if (rv.Failed()) { 795 promise->MaybeReject(std::move(rv)); 796 self->Deactivate(); 797 return; 798 } 799 800 if (promise) { 801 promise->MaybeResolveWithUndefined(); 802 } else { 803 self->DispatchTrustedEvent(u"show"_ns); 804 } 805 }); 806 807 return true; 808 } 809 810 void Notification::Deactivate() { 811 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclick); 812 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onshow); 813 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onerror); 814 IgnoreKeepAliveIfHasListenersFor(nsGkAtoms::onclose); 815 mIsClosed = true; 816 if (mActor) { 817 mActor->Close(); 818 mActor = nullptr; 819 } 820 } 821 822 nsresult Notification::DispatchToMainThread( 823 already_AddRefed<nsIRunnable>&& aRunnable) { 824 if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) { 825 return workerPrivate->DispatchToMainThread(std::move(aRunnable)); 826 } 827 AssertIsOnMainThread(); 828 return NS_DispatchToCurrentThread(std::move(aRunnable)); 829 } 830 831 } // namespace mozilla::dom