NotificationHandler.cpp (5770B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "NotificationHandler.h" 8 9 #include "NotificationUtils.h" 10 #include "mozilla/ClearOnShutdown.h" 11 #include "mozilla/dom/ClientIPCTypes.h" 12 #include "mozilla/dom/ClientOpenWindowUtils.h" 13 #include "mozilla/dom/DOMTypes.h" 14 #include "mozilla/dom/Promise-inl.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/dom/ServiceWorkerManager.h" 17 #include "mozilla/ipc/PBackgroundSharedTypes.h" 18 #include "xpcprivate.h" 19 20 namespace mozilla::dom::notification { 21 22 nsresult RespondOnClick(nsIPrincipal* aPrincipal, const nsAString& aScope, 23 const IPCNotification& aNotification, 24 const nsAString& aActionName) { 25 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 26 if (!swm) { 27 return NS_ERROR_FAILURE; 28 } 29 30 nsAutoCString originSuffix; 31 MOZ_TRY(aPrincipal->GetOriginSuffix(originSuffix)); 32 33 nsresult rv = swm->SendNotificationClickEvent(originSuffix, aScope, 34 aNotification, aActionName); 35 if (NS_FAILED(rv)) { 36 // No active service worker, let's do the last resort 37 // TODO(krosylight): We should prevent entering this path as much as 38 // possible and ultimately remove this. See bug 1972120. 39 return OpenWindowFor(aPrincipal); 40 } 41 return NS_OK; 42 } 43 44 nsresult OpenWindowFor(nsIPrincipal* aPrincipal) { 45 nsAutoCString origin; 46 MOZ_TRY(aPrincipal->GetOriginNoSuffix(origin)); 47 48 // XXX: We should be able to just pass nsIPrincipal directly 49 mozilla::ipc::PrincipalInfo info{}; 50 MOZ_TRY(PrincipalToPrincipalInfo(aPrincipal, &info)); 51 52 (void)ClientOpenWindow(nullptr, 53 ClientOpenWindowArgs(info, Nothing(), ""_ns, origin)); 54 return NS_OK; 55 } 56 57 NS_IMPL_ISUPPORTS(NotificationHandler, nsINotificationHandler); 58 59 StaticRefPtr<NotificationHandler> sHandler; 60 61 already_AddRefed<NotificationHandler> NotificationHandler::GetSingleton() { 62 if (!sHandler) { 63 sHandler = new NotificationHandler(); 64 ClearOnShutdown(&sHandler); 65 } 66 67 return do_AddRef(sHandler); 68 } 69 70 struct NotificationActionComparator { 71 bool Equals(const IPCNotificationAction& aAction, 72 const nsAString& aActionName) const { 73 return aAction.name() == aActionName; 74 } 75 }; 76 77 NS_IMETHODIMP NotificationHandler::RespondOnClick( 78 nsIPrincipal* aPrincipal, const nsAString& aNotificationId, 79 const nsAString& aActionName, bool aAutoClosed, Promise** aResult) { 80 if (aPrincipal->IsSystemPrincipal()) { 81 // This function is only designed for web notifications. 82 return NS_ERROR_INVALID_ARG; 83 } 84 85 nsAutoCString origin; 86 MOZ_TRY(aPrincipal->GetOrigin(origin)); 87 if (!StringBeginsWith(origin, "https://"_ns)) { 88 // We expect only secure context origins for web notifications. 89 // (Simple https check is sufficient for this case, as we do not expect 90 // chrome script nor webextensions to hit this path as they are expected to 91 // use different APIs that do not involve service workers.) 92 return NS_ERROR_INVALID_ARG; 93 } 94 95 bool isPrivate = aPrincipal->GetIsInPrivateBrowsing(); 96 nsCOMPtr<nsINotificationStorage> storage = GetNotificationStorage(isPrivate); 97 98 RefPtr<Promise> promise; 99 storage->GetById(origin, aNotificationId, getter_AddRefs(promise)); 100 101 if (aAutoClosed) { 102 // The system already closed the notification, let's purge the entry here. 103 // 104 // It is guaranteed that Delete will happen only immediately after GetById 105 // as NotificationDB manages each request with an internal job queue. 106 // 107 // XXX(krosylight): We should use AUTF8String for all NotificationStorage 108 // methods. 109 storage->Delete(NS_ConvertUTF8toUTF16(origin), aNotificationId); 110 } 111 112 RefPtr<Promise> result = MOZ_TRY(promise->ThenWithoutCycleCollection( 113 [actionName = nsString(aActionName), principal = nsCOMPtr(aPrincipal)]( 114 JSContext* aCx, JS::Handle<JS::Value> aValue, 115 ErrorResult& aRv) mutable -> already_AddRefed<Promise> { 116 auto tryable = [&]() -> nsresult { 117 if (aValue.isUndefined()) { 118 // No storage entry, open a new window as a fallback 119 return OpenWindowFor(principal); 120 } 121 122 MOZ_ASSERT(aValue.isObject()); 123 JSObject* obj = &aValue.toObject(); 124 125 nsCOMPtr<nsINotificationStorageEntry> entry; 126 MOZ_TRY(nsXPConnect::XPConnect()->WrapJS( 127 aCx, obj, NS_GET_IID(nsINotificationStorageEntry), 128 getter_AddRefs(entry))); 129 if (!entry) { 130 return NS_ERROR_FAILURE; 131 } 132 133 nsAutoString scope; 134 MOZ_TRY(entry->GetServiceWorkerRegistrationScope(scope)); 135 136 IPCNotification notification = 137 MOZ_TRY(NotificationStorageEntry::ToIPC(*entry)); 138 139 if (!actionName.IsEmpty()) { 140 bool contains = notification.options().actions().Contains( 141 actionName, NotificationActionComparator()); 142 if (!contains) { 143 // Invalid action, so pretend it had no action 144 actionName.Truncate(); 145 } 146 } 147 148 return notification::RespondOnClick(principal, scope, notification, 149 actionName); 150 }; 151 152 nsresult rv = tryable(); 153 if (NS_FAILED(rv)) { 154 aRv.Throw(rv); 155 return nullptr; 156 } 157 158 return nullptr; 159 })); 160 161 if (aResult) { 162 result.forget(aResult); 163 } 164 165 return NS_OK; 166 } 167 168 } // namespace mozilla::dom::notification