PushNotifier.cpp (14338B)
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 "PushNotifier.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/Preferences.h" 11 #include "mozilla/Services.h" 12 #include "mozilla/dom/BodyUtil.h" 13 #include "mozilla/dom/ContentChild.h" 14 #include "mozilla/dom/ContentParent.h" 15 #include "mozilla/dom/ServiceWorkerManager.h" 16 #include "nsCOMPtr.h" 17 #include "nsContentUtils.h" 18 #include "nsICategoryManager.h" 19 #include "nsIPushService.h" 20 #include "nsIXULRuntime.h" 21 #include "nsNetUtil.h" 22 #include "nsXPCOM.h" 23 24 namespace mozilla::dom { 25 26 PushNotifier::PushNotifier() = default; 27 28 PushNotifier::~PushNotifier() = default; 29 30 NS_IMPL_CYCLE_COLLECTION_0(PushNotifier) 31 32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushNotifier) 33 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushNotifier) 34 NS_INTERFACE_MAP_ENTRY(nsIPushNotifier) 35 NS_INTERFACE_MAP_END 36 37 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier) 38 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier) 39 40 NS_IMETHODIMP 41 PushNotifier::NotifyPushWithData(const nsACString& aScope, 42 nsIPrincipal* aPrincipal, 43 const nsAString& aMessageId, 44 const nsTArray<uint8_t>& aData) { 45 NS_ENSURE_ARG(aPrincipal); 46 // We still need to do this copying business, if we want the copy to be 47 // fallible. Just passing Some(aData) would do an infallible copy at the 48 // point where the Some() call happens. 49 nsTArray<uint8_t> data; 50 if (!data.AppendElements(aData, fallible)) { 51 return NS_ERROR_OUT_OF_MEMORY; 52 } 53 PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, 54 Some(std::move(data))); 55 return Dispatch(dispatcher); 56 } 57 58 NS_IMETHODIMP 59 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, 60 const nsAString& aMessageId) { 61 NS_ENSURE_ARG(aPrincipal); 62 PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing()); 63 return Dispatch(dispatcher); 64 } 65 66 NS_IMETHODIMP 67 PushNotifier::NotifySubscriptionChange(const nsACString& aScope, 68 nsIPrincipal* aPrincipal, 69 nsIPushSubscription* aOldSubscription) { 70 NS_ENSURE_ARG(aPrincipal); 71 PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal, 72 aOldSubscription); 73 return Dispatch(dispatcher); 74 } 75 76 NS_IMETHODIMP 77 PushNotifier::NotifySubscriptionModified(const nsACString& aScope, 78 nsIPrincipal* aPrincipal) { 79 NS_ENSURE_ARG(aPrincipal); 80 PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal); 81 return Dispatch(dispatcher); 82 } 83 84 NS_IMETHODIMP 85 PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal, 86 const nsAString& aMessage, uint32_t aFlags) { 87 NS_ENSURE_ARG(aPrincipal); 88 PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags); 89 return Dispatch(dispatcher); 90 } 91 92 nsresult PushNotifier::Dispatch(PushDispatcher& aDispatcher) { 93 if (XRE_IsParentProcess()) { 94 // Always notify XPCOM observers in the parent process. 95 (void)NS_WARN_IF(NS_FAILED(aDispatcher.NotifyObservers())); 96 97 // e10s is disabled; notify workers in the parent. 98 return aDispatcher.NotifyWorkers(); 99 } 100 101 // Otherwise, we're in the content process, so e10s must be enabled. Notify 102 // observers and workers, then send a message to notify observers in the 103 // parent. 104 MOZ_ASSERT(XRE_IsContentProcess()); 105 106 nsresult rv = aDispatcher.NotifyObserversAndWorkers(); 107 108 ContentChild* parentActor = ContentChild::GetSingleton(); 109 if (!NS_WARN_IF(!parentActor)) { 110 (void)NS_WARN_IF(!aDispatcher.SendToParent(parentActor)); 111 } 112 113 return rv; 114 } 115 116 PushData::PushData(const nsTArray<uint8_t>& aData) : mData(aData.Clone()) {} 117 118 PushData::~PushData() = default; 119 120 NS_IMPL_CYCLE_COLLECTION_0(PushData) 121 122 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushData) 123 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushData) 124 NS_INTERFACE_MAP_ENTRY(nsIPushData) 125 NS_INTERFACE_MAP_END 126 127 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushData) 128 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushData) 129 130 nsresult PushData::EnsureDecodedText() { 131 if (mData.IsEmpty() || !mDecodedText.IsEmpty()) { 132 return NS_OK; 133 } 134 nsresult rv = BodyUtil::ConsumeText( 135 mData.Length(), reinterpret_cast<uint8_t*>(mData.Elements()), 136 mDecodedText); 137 if (NS_WARN_IF(NS_FAILED(rv))) { 138 mDecodedText.Truncate(); 139 return rv; 140 } 141 return NS_OK; 142 } 143 144 NS_IMETHODIMP 145 PushData::Text(nsAString& aText) { 146 nsresult rv = EnsureDecodedText(); 147 if (NS_WARN_IF(NS_FAILED(rv))) { 148 return rv; 149 } 150 aText = mDecodedText; 151 return NS_OK; 152 } 153 154 NS_IMETHODIMP 155 PushData::Json(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { 156 nsresult rv = EnsureDecodedText(); 157 if (NS_WARN_IF(NS_FAILED(rv))) { 158 return rv; 159 } 160 ErrorResult error; 161 BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error); 162 return error.StealNSResult(); 163 } 164 165 NS_IMETHODIMP 166 PushData::Binary(nsTArray<uint8_t>& aData) { 167 aData = mData.Clone(); 168 return NS_OK; 169 } 170 171 PushMessage::PushMessage(nsIPrincipal* aPrincipal, nsIPushData* aData) 172 : mPrincipal(aPrincipal), mData(aData) {} 173 174 PushMessage::~PushMessage() = default; 175 176 NS_IMPL_CYCLE_COLLECTION(PushMessage, mPrincipal, mData) 177 178 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessage) 179 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPushMessage) 180 NS_INTERFACE_MAP_ENTRY(nsIPushMessage) 181 NS_INTERFACE_MAP_END 182 183 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessage) 184 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessage) 185 186 NS_IMETHODIMP 187 PushMessage::GetPrincipal(nsIPrincipal** aPrincipal) { 188 NS_ENSURE_ARG_POINTER(aPrincipal); 189 190 nsCOMPtr<nsIPrincipal> principal = mPrincipal; 191 principal.forget(aPrincipal); 192 return NS_OK; 193 } 194 195 NS_IMETHODIMP 196 PushMessage::GetData(nsIPushData** aData) { 197 NS_ENSURE_ARG_POINTER(aData); 198 199 nsCOMPtr<nsIPushData> data = mData; 200 data.forget(aData); 201 return NS_OK; 202 } 203 204 PushDispatcher::PushDispatcher(const nsACString& aScope, 205 nsIPrincipal* aPrincipal) 206 : mScope(aScope), mPrincipal(aPrincipal) {} 207 208 PushDispatcher::~PushDispatcher() = default; 209 210 nsresult PushDispatcher::HandleNoChildProcesses() { return NS_OK; } 211 212 nsresult PushDispatcher::NotifyObserversAndWorkers() { 213 (void)NS_WARN_IF(NS_FAILED(NotifyObservers())); 214 return NotifyWorkers(); 215 } 216 217 bool PushDispatcher::ShouldNotifyWorkers() { 218 if (NS_WARN_IF(!mPrincipal)) { 219 return false; 220 } 221 222 // System subscriptions use observer notifications instead of service worker 223 // events. The `testing.notifyWorkers` pref disables worker events for 224 // non-system subscriptions. 225 if (mPrincipal->IsSystemPrincipal() || 226 !Preferences::GetBool("dom.push.testing.notifyWorkers", true)) { 227 return false; 228 } 229 230 // If e10s is off, no need to worry about processes. 231 if (!BrowserTabsRemoteAutostart()) { 232 return true; 233 } 234 235 // We only want to notify in the parent process. 236 bool isContentProcess = XRE_GetProcessType() == GeckoProcessType_Content; 237 return !isContentProcess; 238 } 239 240 nsresult PushDispatcher::DoNotifyObservers(nsISupports* aSubject, 241 const char* aTopic, 242 const nsACString& aScope) { 243 nsCOMPtr<nsIObserverService> obsService = 244 mozilla::services::GetObserverService(); 245 if (!obsService) { 246 return NS_ERROR_FAILURE; 247 } 248 // If there's a service for this push category, make sure it is alive. 249 nsCOMPtr<nsICategoryManager> catMan = 250 do_GetService(NS_CATEGORYMANAGER_CONTRACTID); 251 if (catMan) { 252 nsCString contractId; 253 nsresult rv = catMan->GetCategoryEntry("push", mScope, contractId); 254 if (NS_SUCCEEDED(rv)) { 255 // Ensure the service is created - we don't need to do anything with 256 // it though - we assume the service constructor attaches a listener. 257 nsCOMPtr<nsISupports> service = do_GetService(contractId.get()); 258 } 259 } 260 return obsService->NotifyObservers(aSubject, aTopic, 261 NS_ConvertUTF8toUTF16(mScope).get()); 262 } 263 264 PushMessageDispatcher::PushMessageDispatcher( 265 const nsACString& aScope, nsIPrincipal* aPrincipal, 266 const nsAString& aMessageId, const Maybe<nsTArray<uint8_t>>& aData) 267 : PushDispatcher(aScope, aPrincipal), 268 mMessageId(aMessageId), 269 mData(aData ? Some(aData->Clone()) : Nothing()) {} 270 271 PushMessageDispatcher::~PushMessageDispatcher() = default; 272 273 nsresult PushMessageDispatcher::NotifyObservers() { 274 nsCOMPtr<nsIPushData> data; 275 if (mData) { 276 data = new PushData(mData.ref()); 277 } 278 nsCOMPtr<nsIPushMessage> message = new PushMessage(mPrincipal, data); 279 return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, mScope); 280 } 281 282 nsresult PushMessageDispatcher::NotifyWorkers() { 283 if (!ShouldNotifyWorkers()) { 284 return NS_OK; 285 } 286 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 287 if (!swm) { 288 return NS_ERROR_FAILURE; 289 } 290 nsAutoCString originSuffix; 291 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); 292 if (NS_WARN_IF(NS_FAILED(rv))) { 293 return rv; 294 } 295 return swm->SendPushEvent(originSuffix, mScope, mMessageId, mData); 296 } 297 298 bool PushMessageDispatcher::SendToParent(ContentChild* aParentActor) { 299 if (mData) { 300 return aParentActor->SendNotifyPushObserversWithData( 301 mScope, mPrincipal, mMessageId, mData.ref()); 302 } 303 return aParentActor->SendNotifyPushObservers(mScope, mPrincipal, mMessageId); 304 } 305 306 bool PushMessageDispatcher::SendToChild(ContentParent* aContentActor) { 307 if (mData) { 308 return aContentActor->SendPushWithData(mScope, mPrincipal, mMessageId, 309 mData.ref()); 310 } 311 return aContentActor->SendPush(mScope, mPrincipal, mMessageId); 312 } 313 314 PushSubscriptionChangeDispatcher::PushSubscriptionChangeDispatcher( 315 const nsACString& aScope, nsIPrincipal* aPrincipal, 316 nsIPushSubscription* aOldSubscription) 317 : PushDispatcher(aScope, aPrincipal), mOldSubscription(aOldSubscription) {} 318 319 PushSubscriptionChangeDispatcher::~PushSubscriptionChangeDispatcher() = default; 320 321 nsresult PushSubscriptionChangeDispatcher::NotifyObservers() { 322 return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_CHANGE, 323 mScope); 324 } 325 326 nsresult PushSubscriptionChangeDispatcher::NotifyWorkers() { 327 if (!ShouldNotifyWorkers()) { 328 return NS_OK; 329 } 330 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 331 if (!swm) { 332 return NS_ERROR_FAILURE; 333 } 334 nsAutoCString originSuffix; 335 nsresult rv = mPrincipal->GetOriginSuffix(originSuffix); 336 if (NS_WARN_IF(NS_FAILED(rv))) { 337 return rv; 338 } 339 return swm->SendPushSubscriptionChangeEvent(originSuffix, mScope, 340 mOldSubscription); 341 } 342 343 bool PushSubscriptionChangeDispatcher::SendToParent( 344 ContentChild* aParentActor) { 345 return true; 346 } 347 348 bool PushSubscriptionChangeDispatcher::SendToChild( 349 ContentParent* aContentActor) { 350 return true; 351 } 352 353 PushSubscriptionModifiedDispatcher::PushSubscriptionModifiedDispatcher( 354 const nsACString& aScope, nsIPrincipal* aPrincipal) 355 : PushDispatcher(aScope, aPrincipal) {} 356 357 PushSubscriptionModifiedDispatcher::~PushSubscriptionModifiedDispatcher() = 358 default; 359 360 nsresult PushSubscriptionModifiedDispatcher::NotifyObservers() { 361 return DoNotifyObservers(mPrincipal, OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED, 362 mScope); 363 } 364 365 nsresult PushSubscriptionModifiedDispatcher::NotifyWorkers() { return NS_OK; } 366 367 bool PushSubscriptionModifiedDispatcher::SendToParent( 368 ContentChild* aParentActor) { 369 return aParentActor->SendNotifyPushSubscriptionModifiedObservers(mScope, 370 mPrincipal); 371 } 372 373 bool PushSubscriptionModifiedDispatcher::SendToChild( 374 ContentParent* aContentActor) { 375 return aContentActor->SendNotifyPushSubscriptionModifiedObservers(mScope, 376 mPrincipal); 377 } 378 379 PushErrorDispatcher::PushErrorDispatcher(const nsACString& aScope, 380 nsIPrincipal* aPrincipal, 381 const nsAString& aMessage, 382 uint32_t aFlags) 383 : PushDispatcher(aScope, aPrincipal), mMessage(aMessage), mFlags(aFlags) {} 384 385 PushErrorDispatcher::~PushErrorDispatcher() = default; 386 387 nsresult PushErrorDispatcher::NotifyObservers() { return NS_OK; } 388 389 nsresult PushErrorDispatcher::NotifyWorkers() { 390 if (!ShouldNotifyWorkers() && 391 (!mPrincipal || mPrincipal->IsSystemPrincipal())) { 392 // For system subscriptions, log the error directly to the browser console. 393 return nsContentUtils::ReportToConsoleNonLocalized( 394 mMessage, mFlags, "Push"_ns, nullptr, /* aDocument */ 395 SourceLocation()); 396 } 397 398 // For service worker subscriptions, report the error to all clients. 399 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 400 if (swm) { 401 swm->ReportToAllClients(mScope, mMessage, mScope, /* aFilename */ 402 u""_ns, /* aLine */ 403 0, /* aLineNumber */ 404 0, /* aColumnNumber */ 405 mFlags); 406 } 407 return NS_OK; 408 } 409 410 bool PushErrorDispatcher::SendToParent(ContentChild* aContentActor) { 411 return aContentActor->SendPushError(mScope, mPrincipal, mMessage, mFlags); 412 } 413 414 bool PushErrorDispatcher::SendToChild(ContentParent* aContentActor) { 415 return aContentActor->SendPushError(mScope, mPrincipal, mMessage, mFlags); 416 } 417 418 nsresult PushErrorDispatcher::HandleNoChildProcesses() { 419 // Report to the console if no content processes are active. 420 nsCOMPtr<nsIURI> scopeURI; 421 nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), mScope); 422 if (NS_WARN_IF(NS_FAILED(rv))) { 423 return rv; 424 } 425 return nsContentUtils::ReportToConsoleNonLocalized( 426 mMessage, mFlags, "Push"_ns, /* aDocument = */ nullptr, 427 SourceLocation(scopeURI.get())); 428 } 429 430 } // namespace mozilla::dom