ServiceWorkerRegistrar.cpp (55515B)
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 "ServiceWorkerRegistrar.h" 8 9 #include "MainThreadUtils.h" 10 #include "ServiceWorkerUtils.h" 11 #include "mozilla/ClearOnShutdown.h" 12 #include "mozilla/CycleCollectedJSContext.h" 13 #include "mozilla/ErrorNames.h" 14 #include "mozilla/ModuleUtils.h" 15 #include "mozilla/Services.h" 16 #include "mozilla/StaticPrefs_dom.h" 17 #include "mozilla/StaticPtr.h" 18 #include "mozilla/dom/CookieStoreSubscriptionService.h" 19 #include "mozilla/dom/DOMException.h" 20 #include "mozilla/dom/StorageActivityService.h" 21 #include "mozilla/glean/DomServiceworkersMetrics.h" 22 #include "mozilla/ipc/BackgroundChild.h" 23 #include "mozilla/ipc/BackgroundParent.h" 24 #include "mozilla/ipc/PBackgroundChild.h" 25 #include "nsAppDirectoryServiceDefs.h" 26 #include "nsComponentManagerUtils.h" 27 #include "nsContentUtils.h" 28 #include "nsDirectoryServiceUtils.h" 29 #include "nsIEventTarget.h" 30 #include "nsIInputStream.h" 31 #include "nsILineInputStream.h" 32 #include "nsIObserverService.h" 33 #include "nsIOutputStream.h" 34 #include "nsISafeOutputStream.h" 35 #include "nsIServiceWorkerManager.h" 36 #include "nsIURI.h" 37 #include "nsIWritablePropertyBag2.h" 38 #include "nsNetCID.h" 39 #include "nsNetUtil.h" 40 #include "nsServiceManagerUtils.h" 41 #include "nsThreadUtils.h" 42 #include "nsXULAppAPI.h" 43 44 using namespace mozilla::ipc; 45 46 namespace mozilla::dom { 47 48 namespace { 49 50 static const uint32_t gSupportedRegistrarVersions[] = { 51 SERVICEWORKERREGISTRAR_VERSION, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2}; 52 53 static const uint32_t kInvalidGeneration = static_cast<uint32_t>(-1); 54 55 StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar; 56 57 nsresult GetOriginAndBaseDomain(const nsACString& aURL, nsACString& aOrigin, 58 nsACString& aBaseDomain) { 59 nsCOMPtr<nsIURI> url; 60 nsresult rv = NS_NewURI(getter_AddRefs(url), aURL); 61 if (NS_WARN_IF(NS_FAILED(rv))) { 62 return rv; 63 } 64 65 OriginAttributes attrs; 66 nsCOMPtr<nsIPrincipal> principal = 67 BasePrincipal::CreateContentPrincipal(url, attrs); 68 if (!principal) { 69 return NS_ERROR_NULL_POINTER; 70 } 71 72 rv = principal->GetOriginNoSuffix(aOrigin); 73 if (NS_WARN_IF(NS_FAILED(rv))) { 74 return rv; 75 } 76 77 rv = principal->GetBaseDomain(aBaseDomain); 78 if (NS_WARN_IF(NS_FAILED(rv))) { 79 return rv; 80 } 81 82 return NS_OK; 83 } 84 85 nsresult ReadLine(nsILineInputStream* aStream, nsACString& aValue) { 86 bool hasMoreLines; 87 nsresult rv = aStream->ReadLine(aValue, &hasMoreLines); 88 if (NS_WARN_IF(NS_FAILED(rv))) { 89 return rv; 90 } 91 92 if (NS_WARN_IF(!hasMoreLines)) { 93 return NS_ERROR_FAILURE; 94 } 95 96 return NS_OK; 97 } 98 99 nsresult CreatePrincipalInfo(nsILineInputStream* aStream, 100 ServiceWorkerRegistrationData& aEntry, 101 bool aSkipSpec = false) { 102 nsAutoCString suffix; 103 nsresult rv = ReadLine(aStream, suffix); 104 if (NS_WARN_IF(NS_FAILED(rv))) { 105 return rv; 106 } 107 108 OriginAttributes attrs; 109 if (!attrs.PopulateFromSuffix(suffix)) { 110 return NS_ERROR_INVALID_ARG; 111 } 112 113 if (aSkipSpec) { 114 nsAutoCString unused; 115 nsresult rv = ReadLine(aStream, unused); 116 if (NS_WARN_IF(NS_FAILED(rv))) { 117 return rv; 118 } 119 } 120 121 rv = ReadLine(aStream, aEntry.scope()); 122 if (NS_WARN_IF(NS_FAILED(rv))) { 123 return rv; 124 } 125 126 nsCString origin; 127 nsCString baseDomain; 128 rv = GetOriginAndBaseDomain(aEntry.scope(), origin, baseDomain); 129 if (NS_WARN_IF(NS_FAILED(rv))) { 130 return rv; 131 } 132 133 aEntry.principal() = mozilla::ipc::ContentPrincipalInfo( 134 attrs, origin, aEntry.scope(), Nothing(), baseDomain); 135 136 return NS_OK; 137 } 138 139 MOZ_RUNINIT const IPCNavigationPreloadState 140 gDefaultNavigationPreloadState(false, "true"_ns); 141 142 } // namespace 143 144 NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar, nsIObserver, nsIAsyncShutdownBlocker) 145 146 void ServiceWorkerRegistrar::Initialize() { 147 MOZ_ASSERT(!gServiceWorkerRegistrar); 148 149 if (!XRE_IsParentProcess()) { 150 return; 151 } 152 153 gServiceWorkerRegistrar = new ServiceWorkerRegistrar(); 154 ClearOnShutdown(&gServiceWorkerRegistrar); 155 156 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); 157 if (obs) { 158 DebugOnly<nsresult> rv = obs->AddObserver(gServiceWorkerRegistrar, 159 "profile-after-change", false); 160 MOZ_ASSERT(NS_SUCCEEDED(rv)); 161 } 162 } 163 164 /* static */ 165 already_AddRefed<ServiceWorkerRegistrar> ServiceWorkerRegistrar::Get() { 166 MOZ_ASSERT(XRE_IsParentProcess()); 167 168 MOZ_ASSERT(gServiceWorkerRegistrar); 169 RefPtr<ServiceWorkerRegistrar> service = gServiceWorkerRegistrar.get(); 170 return service.forget(); 171 } 172 173 ServiceWorkerRegistrar::ServiceWorkerRegistrar() 174 : mMonitor("ServiceWorkerRegistrar.mMonitor"), 175 mDataLoaded(false), 176 mDataGeneration(kInvalidGeneration), 177 mFileGeneration(kInvalidGeneration), 178 mRetryCount(0), 179 mShuttingDown(false), 180 mSaveDataRunnableDispatched(false) { 181 MOZ_ASSERT(NS_IsMainThread()); 182 183 mExpandoHandlers.AppendElement(ExpandoHandler{ 184 nsCString("cookie-store"), 185 CookieStoreSubscriptionService::ServiceWorkerLoaded, 186 CookieStoreSubscriptionService::ServiceWorkerUpdated, 187 CookieStoreSubscriptionService::ServiceWorkerUnregistered}); 188 } 189 190 ServiceWorkerRegistrar::~ServiceWorkerRegistrar() { 191 MOZ_ASSERT(!mSaveDataRunnableDispatched); 192 } 193 194 void ServiceWorkerRegistrar::GetRegistrations( 195 nsTArray<ServiceWorkerRegistrationData>& aValues) { 196 MOZ_ASSERT(NS_IsMainThread()); 197 MOZ_ASSERT(aValues.IsEmpty()); 198 199 MonitorAutoLock lock(mMonitor); 200 201 // If we don't have the profile directory, profile is not started yet (and 202 // probably we are in a utest). 203 if (!mProfileDir) { 204 return; 205 } 206 207 // We care just about the first execution because this can be blocked by 208 // loading data from disk. 209 static bool firstTime = true; 210 TimeStamp startTime; 211 212 if (firstTime) { 213 startTime = TimeStamp::NowLoRes(); 214 } 215 216 // Waiting for data loaded. 217 mMonitor.AssertCurrentThreadOwns(); 218 while (!mDataLoaded) { 219 mMonitor.Wait(); 220 } 221 222 for (const ServiceWorkerData& data : mData) { 223 aValues.AppendElement(data.mRegistration); 224 } 225 226 MaybeResetGeneration(); 227 MOZ_DIAGNOSTIC_ASSERT(mDataGeneration != kInvalidGeneration); 228 MOZ_DIAGNOSTIC_ASSERT(mFileGeneration != kInvalidGeneration); 229 230 if (firstTime) { 231 firstTime = false; 232 glean::service_worker::registration_loading.AccumulateRawDuration( 233 TimeStamp::Now() - startTime); 234 } 235 } 236 237 namespace { 238 239 bool Equivalent(const ServiceWorkerRegistrationData& aLeft, 240 const ServiceWorkerRegistrationData& aRight) { 241 MOZ_ASSERT(aLeft.principal().type() == 242 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); 243 MOZ_ASSERT(aRight.principal().type() == 244 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); 245 246 const auto& leftPrincipal = aLeft.principal().get_ContentPrincipalInfo(); 247 const auto& rightPrincipal = aRight.principal().get_ContentPrincipalInfo(); 248 249 // Only compare the attributes, not the spec part of the principal. 250 // The scope comparison above already covers the origin and codebase 251 // principals include the full path in their spec which is not what 252 // we want here. 253 return aLeft.scope() == aRight.scope() && 254 leftPrincipal.attrs() == rightPrincipal.attrs(); 255 } 256 257 } // anonymous namespace 258 259 void ServiceWorkerRegistrar::RegisterServiceWorker( 260 const ServiceWorkerRegistrationData& aData) { 261 AssertIsOnBackgroundThread(); 262 263 if (mShuttingDown) { 264 NS_WARNING("Failed to register a serviceWorker during shutting down."); 265 return; 266 } 267 268 { 269 MonitorAutoLock lock(mMonitor); 270 MOZ_ASSERT(mDataLoaded); 271 RegisterServiceWorkerInternal(aData); 272 } 273 274 MaybeScheduleSaveData(); 275 StorageActivityService::SendActivity(aData.principal()); 276 } 277 278 void ServiceWorkerRegistrar::UnregisterServiceWorker( 279 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope) { 280 AssertIsOnBackgroundThread(); 281 282 if (mShuttingDown) { 283 NS_WARNING("Failed to unregister a serviceWorker during shutting down."); 284 return; 285 } 286 287 bool deleted = false; 288 289 { 290 MonitorAutoLock lock(mMonitor); 291 MOZ_ASSERT(mDataLoaded); 292 293 ServiceWorkerRegistrationData tmp; 294 tmp.principal() = aPrincipalInfo; 295 tmp.scope() = aScope; 296 297 for (uint32_t i = 0; i < mData.Length(); ++i) { 298 if (Equivalent(tmp, mData[i].mRegistration)) { 299 UnregisterExpandoCallbacks(CopyableTArray<ServiceWorkerData>{mData[i]}); 300 301 mData.RemoveElementAt(i); 302 mDataGeneration = GetNextGeneration(); 303 deleted = true; 304 break; 305 } 306 } 307 } 308 309 if (deleted) { 310 MaybeScheduleSaveData(); 311 StorageActivityService::SendActivity(aPrincipalInfo); 312 } 313 } 314 315 void ServiceWorkerRegistrar::StoreServiceWorkerExpandoOnMainThread( 316 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope, 317 const nsACString& aKey, const nsACString& aValue) { 318 MOZ_ASSERT(NS_IsMainThread()); 319 MOZ_ASSERT(!aValue.Contains('\n'), "Invalid chars in the value"); 320 321 nsCOMPtr<nsISerialEventTarget> backgroundThread = 322 BackgroundParent::GetBackgroundThread(); 323 if (NS_WARN_IF(!backgroundThread)) { 324 // Probably we are shutting down. Unfortunately this expando data will not 325 // be stored. 326 return; 327 } 328 329 backgroundThread->Dispatch(NS_NewRunnableFunction( 330 __func__, 331 [self = RefPtr(this), aPrincipalInfo, aScope = nsCString(aScope), 332 aKey = nsCString(aKey), aValue = nsCString(aValue)]() { 333 if (self->mShuttingDown) { 334 NS_WARNING( 335 "Failed to store an expando to a serviceWorker during shutting " 336 "down."); 337 return; 338 } 339 340 const ExpandoHandler* expandoHandler = nullptr; 341 342 for (const ExpandoHandler& handler : self->mExpandoHandlers) { 343 if (handler.mKey == aKey) { 344 expandoHandler = &handler; 345 break; 346 } 347 } 348 349 if (!expandoHandler) { 350 NS_WARNING("Unsupported handler"); 351 return; 352 } 353 354 bool saveNeeded = false; 355 356 { 357 MonitorAutoLock lock(self->mMonitor); 358 MOZ_ASSERT(self->mDataLoaded); 359 360 ServiceWorkerRegistrationData tmp; 361 tmp.principal() = aPrincipalInfo; 362 tmp.scope() = aScope; 363 364 for (uint32_t i = 0; i < self->mData.Length(); ++i) { 365 if (Equivalent(tmp, self->mData[i].mRegistration)) { 366 bool found = false; 367 for (ExpandoData& expando : self->mData[i].mExpandos) { 368 if (expando.mKey == aKey) { 369 MOZ_ASSERT(expando.mHandler == expandoHandler); 370 expando.mValue = aValue; 371 found = true; 372 break; 373 } 374 } 375 376 if (!found) { 377 self->mData[i].mExpandos.AppendElement(ExpandoData{ 378 nsCString(aKey), nsCString(aValue), expandoHandler}); 379 } 380 381 self->mDataGeneration = self->GetNextGeneration(); 382 saveNeeded = true; 383 break; 384 } 385 } 386 } 387 388 if (saveNeeded) { 389 self->MaybeScheduleSaveData(); 390 StorageActivityService::SendActivity(aPrincipalInfo); 391 } 392 })); 393 } 394 395 void ServiceWorkerRegistrar::UnstoreServiceWorkerExpandoOnMainThread( 396 const PrincipalInfo& aPrincipalInfo, const nsACString& aScope, 397 const nsACString& aKey) { 398 MOZ_ASSERT(NS_IsMainThread()); 399 400 nsCOMPtr<nsISerialEventTarget> backgroundThread = 401 BackgroundParent::GetBackgroundThread(); 402 if (NS_WARN_IF(!backgroundThread)) { 403 // Probably we are shutting down. Unfortunately this expando data will not 404 // be stored. 405 return; 406 } 407 408 backgroundThread->Dispatch(NS_NewRunnableFunction( 409 __func__, [self = RefPtr(this), aPrincipalInfo, 410 aScope = nsCString(aScope), aKey = nsCString(aKey)]() { 411 if (self->mShuttingDown) { 412 NS_WARNING( 413 "Failed to unstore an expando from a serviceWorker during " 414 "shutting down."); 415 return; 416 } 417 418 bool saveNeeded = false; 419 420 { 421 MonitorAutoLock lock(self->mMonitor); 422 MOZ_ASSERT(self->mDataLoaded); 423 424 ServiceWorkerRegistrationData tmp; 425 tmp.principal() = aPrincipalInfo; 426 tmp.scope() = aScope; 427 428 for (ServiceWorkerData& data : self->mData) { 429 if (Equivalent(tmp, data.mRegistration)) { 430 for (uint32_t i = 0; i < data.mExpandos.Length(); ++i) { 431 if (data.mExpandos[i].mKey == aKey) { 432 data.mExpandos.RemoveElementAt(i); 433 self->mDataGeneration = self->GetNextGeneration(); 434 saveNeeded = true; 435 break; 436 } 437 } 438 439 break; 440 } 441 } 442 } 443 444 if (saveNeeded) { 445 self->MaybeScheduleSaveData(); 446 StorageActivityService::SendActivity(aPrincipalInfo); 447 } 448 })); 449 } 450 451 void ServiceWorkerRegistrar::RemoveAll() { 452 AssertIsOnBackgroundThread(); 453 454 if (mShuttingDown) { 455 NS_WARNING("Failed to remove all the serviceWorkers during shutting down."); 456 return; 457 } 458 459 bool deleted = false; 460 461 nsTArray<ServiceWorkerRegistrationData> data; 462 nsTArray<ServiceWorkerData> registrationsWithExpandos; 463 { 464 MonitorAutoLock lock(mMonitor); 465 MOZ_ASSERT(mDataLoaded); 466 467 // Let's take a copy in order to inform StorageActivityService. 468 for (const ServiceWorkerData& i : mData) { 469 data.AppendElement(i.mRegistration); 470 471 if (!i.mExpandos.IsEmpty()) { 472 registrationsWithExpandos.AppendElement(i); 473 } 474 } 475 476 deleted = !mData.IsEmpty(); 477 mData.Clear(); 478 479 mDataGeneration = GetNextGeneration(); 480 } 481 482 if (!deleted) { 483 return; 484 } 485 486 if (!registrationsWithExpandos.IsEmpty()) { 487 UnregisterExpandoCallbacks(registrationsWithExpandos); 488 } 489 490 MaybeScheduleSaveData(); 491 492 for (uint32_t i = 0, len = data.Length(); i < len; ++i) { 493 StorageActivityService::SendActivity(data[i].principal()); 494 } 495 } 496 497 void ServiceWorkerRegistrar::LoadData() { 498 MOZ_ASSERT(!NS_IsMainThread()); 499 #ifdef DEBUG 500 { 501 MonitorAutoLock lock(mMonitor); 502 MOZ_ASSERT(!mDataLoaded); 503 } 504 #endif 505 506 nsresult rv = ReadData(); 507 508 if (NS_WARN_IF(NS_FAILED(rv))) { 509 DeleteData(); 510 // Also if the reading failed we have to notify what is waiting for data. 511 } 512 513 MonitorAutoLock lock(mMonitor); 514 MOZ_ASSERT(!mDataLoaded); 515 mDataLoaded = true; 516 mMonitor.Notify(); 517 } 518 519 bool ServiceWorkerRegistrar::ReloadDataForTest() { 520 if (NS_WARN_IF(!StaticPrefs::dom_serviceWorkers_testing_enabled())) { 521 return false; 522 } 523 524 MOZ_ASSERT(NS_IsMainThread()); 525 MonitorAutoLock lock(mMonitor); 526 mData.Clear(); 527 mDataLoaded = false; 528 529 nsCOMPtr<nsIEventTarget> target = 530 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 531 MOZ_ASSERT(target, "Must have stream transport service"); 532 533 nsCOMPtr<nsIRunnable> runnable = 534 NewRunnableMethod("dom::ServiceWorkerRegistrar::LoadData", this, 535 &ServiceWorkerRegistrar::LoadData); 536 nsresult rv = target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 537 if (NS_FAILED(rv)) { 538 NS_WARNING("Failed to dispatch the LoadDataRunnable."); 539 return false; 540 } 541 542 mMonitor.AssertCurrentThreadOwns(); 543 while (!mDataLoaded) { 544 mMonitor.Wait(); 545 } 546 547 return mDataLoaded; 548 } 549 550 nsresult ServiceWorkerRegistrar::ReadData() { 551 // We cannot assert about the correct thread because normally this method 552 // runs on a IO thread, but in gTests we call it from the main-thread. 553 554 nsCOMPtr<nsIFile> file; 555 556 { 557 MonitorAutoLock lock(mMonitor); 558 559 if (!mProfileDir) { 560 return NS_ERROR_FAILURE; 561 } 562 563 nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); 564 if (NS_WARN_IF(NS_FAILED(rv))) { 565 return rv; 566 } 567 } 568 569 nsresult rv = file->Append(nsLiteralString(SERVICEWORKERREGISTRAR_FILE)); 570 if (NS_WARN_IF(NS_FAILED(rv))) { 571 return rv; 572 } 573 574 bool exists; 575 rv = file->Exists(&exists); 576 if (NS_WARN_IF(NS_FAILED(rv))) { 577 return rv; 578 } 579 580 if (!exists) { 581 return NS_OK; 582 } 583 584 nsCOMPtr<nsIInputStream> stream; 585 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); 586 if (NS_WARN_IF(NS_FAILED(rv))) { 587 return rv; 588 } 589 590 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream); 591 MOZ_ASSERT(lineInputStream); 592 593 nsAutoCString versionStr; 594 bool hasMoreLines; 595 rv = lineInputStream->ReadLine(versionStr, &hasMoreLines); 596 if (NS_WARN_IF(NS_FAILED(rv))) { 597 return rv; 598 } 599 600 uint32_t version = versionStr.ToUnsignedInteger(&rv); 601 if (NS_WARN_IF(NS_FAILED(rv))) { 602 return rv; 603 } 604 605 if (!IsSupportedVersion(version)) { 606 nsContentUtils::LogMessageToConsole( 607 nsPrintfCString("Unsupported service worker registrar version: %s", 608 versionStr.get()) 609 .get()); 610 return NS_ERROR_FAILURE; 611 } 612 613 nsTArray<ServiceWorkerData> tmpData; 614 615 bool overwrite = false; 616 bool dedupe = false; 617 while (hasMoreLines) { 618 ServiceWorkerData* entry = tmpData.AppendElement(); 619 620 #define GET_LINE(x) \ 621 rv = lineInputStream->ReadLine(x, &hasMoreLines); \ 622 if (NS_WARN_IF(NS_FAILED(rv))) { \ 623 return rv; \ 624 } \ 625 if (NS_WARN_IF(!hasMoreLines)) { \ 626 return NS_ERROR_FAILURE; \ 627 } 628 629 // baseSchemaVersion represents the version where major schema changes 630 // happened and requires a different reading strategy as done below in the 631 // switch statement. Version 9 is the latest major schema version, versions 632 // 10 and 11 are just extensions to version 9 and that's why gets processed 633 // under the same block. 634 auto baseSchemaVersion = version >= 9 ? 9 : version; 635 636 nsAutoCString line; 637 switch (baseSchemaVersion) { 638 case 9: { 639 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 640 if (NS_WARN_IF(NS_FAILED(rv))) { 641 return rv; 642 } 643 644 GET_LINE(entry->mRegistration.currentWorkerURL()); 645 646 nsAutoCString fetchFlag; 647 GET_LINE(fetchFlag); 648 if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && 649 !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { 650 return NS_ERROR_INVALID_ARG; 651 } 652 entry->mRegistration.currentWorkerHandlesFetch() = 653 fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE); 654 655 nsAutoCString cacheName; 656 GET_LINE(cacheName); 657 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 658 659 nsAutoCString updateViaCache; 660 GET_LINE(updateViaCache); 661 entry->mRegistration.updateViaCache() = 662 updateViaCache.ToInteger(&rv, 16); 663 if (NS_WARN_IF(NS_FAILED(rv))) { 664 return rv; 665 } 666 if (entry->mRegistration.updateViaCache() > 667 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE) { 668 return NS_ERROR_INVALID_ARG; 669 } 670 671 nsAutoCString installedTimeStr; 672 GET_LINE(installedTimeStr); 673 int64_t installedTime = installedTimeStr.ToInteger64(&rv); 674 if (NS_WARN_IF(NS_FAILED(rv))) { 675 return rv; 676 } 677 entry->mRegistration.currentWorkerInstalledTime() = installedTime; 678 679 nsAutoCString activatedTimeStr; 680 GET_LINE(activatedTimeStr); 681 int64_t activatedTime = activatedTimeStr.ToInteger64(&rv); 682 if (NS_WARN_IF(NS_FAILED(rv))) { 683 return rv; 684 } 685 entry->mRegistration.currentWorkerActivatedTime() = activatedTime; 686 687 nsAutoCString lastUpdateTimeStr; 688 GET_LINE(lastUpdateTimeStr); 689 int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv); 690 if (NS_WARN_IF(NS_FAILED(rv))) { 691 return rv; 692 } 693 entry->mRegistration.lastUpdateTime() = lastUpdateTime; 694 695 nsAutoCString navigationPreloadEnabledStr; 696 GET_LINE(navigationPreloadEnabledStr); 697 bool navigationPreloadEnabled = 698 navigationPreloadEnabledStr.ToInteger(&rv); 699 if (NS_WARN_IF(NS_FAILED(rv))) { 700 return rv; 701 } 702 entry->mRegistration.navigationPreloadState().enabled() = 703 navigationPreloadEnabled; 704 705 GET_LINE(entry->mRegistration.navigationPreloadState().headerValue()); 706 707 if (version >= 10) { 708 nsAutoCString expandoCountStr; 709 GET_LINE(expandoCountStr); 710 uint32_t expandoCount = expandoCountStr.ToInteger(&rv, 16); 711 if (NS_WARN_IF(NS_FAILED(rv))) { 712 return rv; 713 } 714 715 for (uint32_t expandoId = 0; expandoId < expandoCount; ++expandoId) { 716 nsAutoCString key; 717 GET_LINE(key); 718 719 nsAutoCString value; 720 GET_LINE(value); 721 722 for (const ExpandoHandler& handler : mExpandoHandlers) { 723 if (handler.mKey == key) { 724 entry->mExpandos.AppendElement( 725 ExpandoData{key, value, &handler}); 726 break; 727 } 728 } 729 } 730 } 731 732 if (version >= 11) { 733 nsAutoCString numberOfAttemptedActivationsStr; 734 GET_LINE(numberOfAttemptedActivationsStr); 735 int64_t numberOfAttemptedActivations = 736 numberOfAttemptedActivationsStr.ToInteger64(&rv); 737 if (NS_WARN_IF(NS_FAILED(rv))) { 738 return rv; 739 } 740 entry->mRegistration.numberOfAttemptedActivations() = 741 numberOfAttemptedActivations; 742 nsAutoCString isRegistrationBrokenStr; 743 GET_LINE(isRegistrationBrokenStr); 744 int64_t isBroken = isRegistrationBrokenStr.ToInteger64(&rv); 745 if (NS_WARN_IF(NS_FAILED(rv))) { 746 return rv; 747 } 748 entry->mRegistration.isBroken() = (isBroken != 0); 749 nsAutoCString cacheAPIIdStr; 750 GET_LINE(cacheAPIIdStr); 751 int64_t cacheAPIId = cacheAPIIdStr.ToInteger64(&rv); 752 if (NS_WARN_IF(NS_FAILED(rv))) { 753 return rv; 754 } 755 entry->mRegistration.cacheAPIId() = cacheAPIId; 756 } 757 758 // if we are on latest version, get service worker type 759 if (version == SERVICEWORKERREGISTRAR_VERSION) { 760 nsAutoCString serviceWorkerTypeStr; 761 GET_LINE(serviceWorkerTypeStr); 762 uint32_t serviceWorkerType = 763 serviceWorkerTypeStr.ToUnsignedInteger(&rv); 764 if (NS_WARN_IF(NS_FAILED(rv))) { 765 return rv; 766 } 767 if (serviceWorkerType > static_cast<uint32_t>(WorkerType::Module)) { 768 return NS_ERROR_INVALID_ARG; 769 } 770 entry->mRegistration.type() = 771 static_cast<WorkerType>(serviceWorkerType); 772 } 773 break; 774 } 775 776 case 8: { 777 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 778 if (NS_WARN_IF(NS_FAILED(rv))) { 779 return rv; 780 } 781 782 GET_LINE(entry->mRegistration.currentWorkerURL()); 783 784 nsAutoCString fetchFlag; 785 GET_LINE(fetchFlag); 786 if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && 787 !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { 788 return NS_ERROR_INVALID_ARG; 789 } 790 entry->mRegistration.currentWorkerHandlesFetch() = 791 fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE); 792 793 nsAutoCString cacheName; 794 GET_LINE(cacheName); 795 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 796 797 nsAutoCString updateViaCache; 798 GET_LINE(updateViaCache); 799 entry->mRegistration.updateViaCache() = 800 updateViaCache.ToInteger(&rv, 16); 801 if (NS_WARN_IF(NS_FAILED(rv))) { 802 return rv; 803 } 804 if (entry->mRegistration.updateViaCache() > 805 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE) { 806 return NS_ERROR_INVALID_ARG; 807 } 808 809 nsAutoCString installedTimeStr; 810 GET_LINE(installedTimeStr); 811 int64_t installedTime = installedTimeStr.ToInteger64(&rv); 812 if (NS_WARN_IF(NS_FAILED(rv))) { 813 return rv; 814 } 815 entry->mRegistration.currentWorkerInstalledTime() = installedTime; 816 817 nsAutoCString activatedTimeStr; 818 GET_LINE(activatedTimeStr); 819 int64_t activatedTime = activatedTimeStr.ToInteger64(&rv); 820 if (NS_WARN_IF(NS_FAILED(rv))) { 821 return rv; 822 } 823 entry->mRegistration.currentWorkerActivatedTime() = activatedTime; 824 825 nsAutoCString lastUpdateTimeStr; 826 GET_LINE(lastUpdateTimeStr); 827 int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv); 828 if (NS_WARN_IF(NS_FAILED(rv))) { 829 return rv; 830 } 831 entry->mRegistration.lastUpdateTime() = lastUpdateTime; 832 833 entry->mRegistration.navigationPreloadState() = 834 gDefaultNavigationPreloadState; 835 break; 836 } 837 838 case 7: { 839 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 840 if (NS_WARN_IF(NS_FAILED(rv))) { 841 return rv; 842 } 843 844 GET_LINE(entry->mRegistration.currentWorkerURL()); 845 846 nsAutoCString fetchFlag; 847 GET_LINE(fetchFlag); 848 if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && 849 !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { 850 return NS_ERROR_INVALID_ARG; 851 } 852 entry->mRegistration.currentWorkerHandlesFetch() = 853 fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE); 854 855 nsAutoCString cacheName; 856 GET_LINE(cacheName); 857 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 858 859 nsAutoCString loadFlags; 860 GET_LINE(loadFlags); 861 entry->mRegistration.updateViaCache() = 862 loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL 863 ? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL 864 : nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 865 866 if (NS_WARN_IF(NS_FAILED(rv))) { 867 return rv; 868 } 869 870 nsAutoCString installedTimeStr; 871 GET_LINE(installedTimeStr); 872 int64_t installedTime = installedTimeStr.ToInteger64(&rv); 873 if (NS_WARN_IF(NS_FAILED(rv))) { 874 return rv; 875 } 876 entry->mRegistration.currentWorkerInstalledTime() = installedTime; 877 878 nsAutoCString activatedTimeStr; 879 GET_LINE(activatedTimeStr); 880 int64_t activatedTime = activatedTimeStr.ToInteger64(&rv); 881 if (NS_WARN_IF(NS_FAILED(rv))) { 882 return rv; 883 } 884 entry->mRegistration.currentWorkerActivatedTime() = activatedTime; 885 886 nsAutoCString lastUpdateTimeStr; 887 GET_LINE(lastUpdateTimeStr); 888 int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv); 889 if (NS_WARN_IF(NS_FAILED(rv))) { 890 return rv; 891 } 892 entry->mRegistration.lastUpdateTime() = lastUpdateTime; 893 894 entry->mRegistration.navigationPreloadState() = 895 gDefaultNavigationPreloadState; 896 break; 897 } 898 899 case 6: { 900 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 901 if (NS_WARN_IF(NS_FAILED(rv))) { 902 return rv; 903 } 904 905 GET_LINE(entry->mRegistration.currentWorkerURL()); 906 907 nsAutoCString fetchFlag; 908 GET_LINE(fetchFlag); 909 if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && 910 !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { 911 return NS_ERROR_INVALID_ARG; 912 } 913 entry->mRegistration.currentWorkerHandlesFetch() = 914 fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE); 915 916 nsAutoCString cacheName; 917 GET_LINE(cacheName); 918 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 919 920 nsAutoCString loadFlags; 921 GET_LINE(loadFlags); 922 entry->mRegistration.updateViaCache() = 923 loadFlags.ToInteger(&rv, 16) == nsIRequest::LOAD_NORMAL 924 ? nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL 925 : nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 926 927 if (NS_WARN_IF(NS_FAILED(rv))) { 928 return rv; 929 } 930 931 entry->mRegistration.currentWorkerInstalledTime() = 0; 932 entry->mRegistration.currentWorkerActivatedTime() = 0; 933 entry->mRegistration.lastUpdateTime() = 0; 934 935 entry->mRegistration.navigationPreloadState() = 936 gDefaultNavigationPreloadState; 937 break; 938 } 939 940 case 5: { 941 overwrite = true; 942 dedupe = true; 943 944 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 945 if (NS_WARN_IF(NS_FAILED(rv))) { 946 return rv; 947 } 948 949 GET_LINE(entry->mRegistration.currentWorkerURL()); 950 951 nsAutoCString fetchFlag; 952 GET_LINE(fetchFlag); 953 if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) && 954 !fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) { 955 return NS_ERROR_INVALID_ARG; 956 } 957 entry->mRegistration.currentWorkerHandlesFetch() = 958 fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE); 959 960 nsAutoCString cacheName; 961 GET_LINE(cacheName); 962 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 963 964 entry->mRegistration.updateViaCache() = 965 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 966 967 entry->mRegistration.currentWorkerInstalledTime() = 0; 968 entry->mRegistration.currentWorkerActivatedTime() = 0; 969 entry->mRegistration.lastUpdateTime() = 0; 970 971 entry->mRegistration.navigationPreloadState() = 972 gDefaultNavigationPreloadState; 973 break; 974 } 975 976 case 4: { 977 overwrite = true; 978 dedupe = true; 979 980 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration); 981 if (NS_WARN_IF(NS_FAILED(rv))) { 982 return rv; 983 } 984 985 GET_LINE(entry->mRegistration.currentWorkerURL()); 986 987 // default handlesFetch flag to Enabled 988 entry->mRegistration.currentWorkerHandlesFetch() = true; 989 990 nsAutoCString cacheName; 991 GET_LINE(cacheName); 992 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 993 994 entry->mRegistration.updateViaCache() = 995 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 996 997 entry->mRegistration.currentWorkerInstalledTime() = 0; 998 entry->mRegistration.currentWorkerActivatedTime() = 0; 999 entry->mRegistration.lastUpdateTime() = 0; 1000 1001 entry->mRegistration.navigationPreloadState() = 1002 gDefaultNavigationPreloadState; 1003 break; 1004 } 1005 1006 case 3: { 1007 overwrite = true; 1008 dedupe = true; 1009 1010 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration, true); 1011 if (NS_WARN_IF(NS_FAILED(rv))) { 1012 return rv; 1013 } 1014 1015 GET_LINE(entry->mRegistration.currentWorkerURL()); 1016 1017 // default handlesFetch flag to Enabled 1018 entry->mRegistration.currentWorkerHandlesFetch() = true; 1019 1020 nsAutoCString cacheName; 1021 GET_LINE(cacheName); 1022 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 1023 1024 entry->mRegistration.updateViaCache() = 1025 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 1026 1027 entry->mRegistration.currentWorkerInstalledTime() = 0; 1028 entry->mRegistration.currentWorkerActivatedTime() = 0; 1029 entry->mRegistration.lastUpdateTime() = 0; 1030 1031 entry->mRegistration.navigationPreloadState() = 1032 gDefaultNavigationPreloadState; 1033 break; 1034 } 1035 1036 case 2: { 1037 overwrite = true; 1038 dedupe = true; 1039 1040 rv = CreatePrincipalInfo(lineInputStream, entry->mRegistration, true); 1041 if (NS_WARN_IF(NS_FAILED(rv))) { 1042 return rv; 1043 } 1044 1045 // scriptSpec is no more used in latest version. 1046 nsAutoCString unused; 1047 GET_LINE(unused); 1048 1049 GET_LINE(entry->mRegistration.currentWorkerURL()); 1050 1051 // default handlesFetch flag to Enabled 1052 entry->mRegistration.currentWorkerHandlesFetch() = true; 1053 1054 nsAutoCString cacheName; 1055 GET_LINE(cacheName); 1056 CopyUTF8toUTF16(cacheName, entry->mRegistration.cacheName()); 1057 1058 // waitingCacheName is no more used in latest version. 1059 GET_LINE(unused); 1060 1061 entry->mRegistration.updateViaCache() = 1062 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS; 1063 1064 entry->mRegistration.currentWorkerInstalledTime() = 0; 1065 entry->mRegistration.currentWorkerActivatedTime() = 0; 1066 entry->mRegistration.lastUpdateTime() = 0; 1067 1068 entry->mRegistration.navigationPreloadState() = 1069 gDefaultNavigationPreloadState; 1070 break; 1071 } 1072 1073 default: 1074 MOZ_ASSERT_UNREACHABLE("Should never get here!"); 1075 } 1076 1077 #undef GET_LINE 1078 1079 rv = lineInputStream->ReadLine(line, &hasMoreLines); 1080 if (NS_WARN_IF(NS_FAILED(rv))) { 1081 return rv; 1082 } 1083 1084 if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) { 1085 return NS_ERROR_FAILURE; 1086 } 1087 } 1088 1089 stream->Close(); 1090 1091 // We currently only call this at startup where we block the main thread 1092 // preventing further operation until it completes, however take the lock 1093 // in case that changes 1094 1095 nsTArray<ServiceWorkerData> registrationsWithExpandos; 1096 1097 { 1098 MonitorAutoLock lock(mMonitor); 1099 // Copy data over to mData. 1100 for (uint32_t i = 0; i < tmpData.Length(); ++i) { 1101 // Older versions could sometimes write out empty, useless entries. 1102 // Prune those here. 1103 if (!ServiceWorkerRegistrationDataIsValid(tmpData[i].mRegistration)) { 1104 continue; 1105 } 1106 1107 bool match = false; 1108 if (dedupe) { 1109 MOZ_ASSERT(overwrite); 1110 // If this is an old profile, then we might need to deduplicate. In 1111 // theory this can be removed in the future (Bug 1248449) 1112 for (uint32_t j = 0; j < mData.Length(); ++j) { 1113 // Use same comparison as RegisterServiceWorker. Scope contains 1114 // basic origin information. Combine with any principal attributes. 1115 if (Equivalent(tmpData[i].mRegistration, mData[j].mRegistration)) { 1116 // Last match wins, just like legacy loading used to do in 1117 // the ServiceWorkerManager. 1118 mData[j].mRegistration = tmpData[i].mRegistration; 1119 mData[j].mExpandos.Clear(); 1120 // Dupe found, so overwrite file with reduced list. 1121 match = true; 1122 break; 1123 } 1124 } 1125 } else { 1126 #ifdef DEBUG 1127 // Otherwise assert no duplications in debug builds. 1128 for (uint32_t j = 0; j < mData.Length(); ++j) { 1129 MOZ_ASSERT( 1130 !Equivalent(tmpData[i].mRegistration, mData[j].mRegistration)); 1131 } 1132 #endif 1133 } 1134 if (!match) { 1135 mData.AppendElement(tmpData[i]); 1136 1137 if (!tmpData[i].mExpandos.IsEmpty()) { 1138 registrationsWithExpandos.AppendElement(tmpData[i]); 1139 } 1140 } 1141 } 1142 } 1143 1144 if (!registrationsWithExpandos.IsEmpty()) { 1145 LoadExpandoCallbacks(registrationsWithExpandos); 1146 } 1147 1148 // Overwrite previous version. 1149 // Cannot call SaveData directly because gtest uses main-thread. 1150 1151 // XXX NOTE: if we could be accessed multi-threaded here, we would need to 1152 // find a way to lock around access to mData. Since we can't, suppress the 1153 // thread-safety warnings. 1154 MOZ_PUSH_IGNORE_THREAD_SAFETY 1155 if (overwrite && NS_FAILED(WriteData(mData))) { 1156 NS_WARNING("Failed to write data for the ServiceWorker Registations."); 1157 DeleteData(); 1158 } 1159 MOZ_POP_THREAD_SAFETY 1160 1161 return NS_OK; 1162 } 1163 1164 void ServiceWorkerRegistrar::DeleteData() { 1165 // We cannot assert about the correct thread because normally this method 1166 // runs on a IO thread, but in gTests we call it from the main-thread. 1167 1168 nsCOMPtr<nsIFile> file; 1169 1170 { 1171 MonitorAutoLock lock(mMonitor); 1172 mData.Clear(); 1173 1174 if (!mProfileDir) { 1175 return; 1176 } 1177 1178 nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); 1179 if (NS_WARN_IF(NS_FAILED(rv))) { 1180 return; 1181 } 1182 } 1183 1184 nsresult rv = file->Append(nsLiteralString(SERVICEWORKERREGISTRAR_FILE)); 1185 if (NS_WARN_IF(NS_FAILED(rv))) { 1186 return; 1187 } 1188 1189 rv = file->Remove(false); 1190 if (rv == NS_ERROR_FILE_NOT_FOUND) { 1191 return; 1192 } 1193 1194 if (NS_WARN_IF(NS_FAILED(rv))) { 1195 return; 1196 } 1197 } 1198 1199 void ServiceWorkerRegistrar::RegisterServiceWorkerInternal( 1200 const ServiceWorkerRegistrationData& aData) { 1201 bool found = false; 1202 for (uint32_t i = 0, len = mData.Length(); i < len; ++i) { 1203 if (Equivalent(aData, mData[i].mRegistration)) { 1204 UpdateExpandoCallbacks(mData[i]); 1205 1206 found = true; 1207 mData[i].mRegistration = aData; 1208 mData[i].mExpandos.Clear(); 1209 break; 1210 } 1211 } 1212 1213 if (!found) { 1214 MOZ_ASSERT(ServiceWorkerRegistrationDataIsValid(aData)); 1215 mData.AppendElement(ServiceWorkerData{aData, nsTArray<ExpandoData>()}); 1216 } 1217 1218 mDataGeneration = GetNextGeneration(); 1219 } 1220 1221 class ServiceWorkerRegistrarSaveDataRunnable final : public Runnable { 1222 nsCOMPtr<nsIEventTarget> mEventTarget; 1223 const nsTArray<ServiceWorkerRegistrar::ServiceWorkerData> mData; 1224 const uint32_t mGeneration; 1225 1226 public: 1227 ServiceWorkerRegistrarSaveDataRunnable( 1228 nsTArray<ServiceWorkerRegistrar::ServiceWorkerData>&& aData, 1229 uint32_t aGeneration) 1230 : Runnable("dom::ServiceWorkerRegistrarSaveDataRunnable"), 1231 mEventTarget(GetCurrentSerialEventTarget()), 1232 mData(std::move(aData)), 1233 mGeneration(aGeneration) { 1234 AssertIsOnBackgroundThread(); 1235 MOZ_DIAGNOSTIC_ASSERT(mGeneration != kInvalidGeneration); 1236 } 1237 1238 NS_IMETHOD 1239 Run() override { 1240 RefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get(); 1241 MOZ_ASSERT(service); 1242 1243 uint32_t fileGeneration = kInvalidGeneration; 1244 1245 if (NS_SUCCEEDED(service->SaveData(mData))) { 1246 fileGeneration = mGeneration; 1247 } 1248 1249 RefPtr<Runnable> runnable = NewRunnableMethod<uint32_t>( 1250 "ServiceWorkerRegistrar::DataSaved", service, 1251 &ServiceWorkerRegistrar::DataSaved, fileGeneration); 1252 MOZ_ALWAYS_SUCCEEDS( 1253 mEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL)); 1254 1255 return NS_OK; 1256 } 1257 }; 1258 1259 void ServiceWorkerRegistrar::MaybeScheduleSaveData() { 1260 AssertIsOnBackgroundThread(); 1261 MOZ_ASSERT(!mShuttingDown); 1262 1263 if (mShuttingDown || mSaveDataRunnableDispatched || 1264 mDataGeneration <= mFileGeneration) { 1265 return; 1266 } 1267 1268 nsCOMPtr<nsIEventTarget> target = 1269 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 1270 MOZ_ASSERT(target, "Must have stream transport service"); 1271 1272 uint32_t generation = kInvalidGeneration; 1273 nsTArray<ServiceWorkerData> data; 1274 1275 { 1276 MonitorAutoLock lock(mMonitor); 1277 generation = mDataGeneration; 1278 data.AppendElements(mData); 1279 } 1280 1281 RefPtr<Runnable> runnable = 1282 new ServiceWorkerRegistrarSaveDataRunnable(std::move(data), generation); 1283 nsresult rv = target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 1284 NS_ENSURE_SUCCESS_VOID(rv); 1285 1286 mSaveDataRunnableDispatched = true; 1287 } 1288 1289 void ServiceWorkerRegistrar::ShutdownCompleted() { 1290 MOZ_ASSERT(NS_IsMainThread()); 1291 1292 DebugOnly<nsresult> rv = GetShutdownPhase()->RemoveBlocker(this); 1293 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1294 } 1295 1296 nsresult ServiceWorkerRegistrar::SaveData( 1297 const nsTArray<ServiceWorkerData>& aData) { 1298 MOZ_ASSERT(!NS_IsMainThread()); 1299 1300 nsresult rv = WriteData(aData); 1301 if (NS_FAILED(rv)) { 1302 NS_WARNING("Failed to write data for the ServiceWorker Registations."); 1303 // Don't touch the file or in-memory state. Writing files can 1304 // sometimes fail due to virus scanning, etc. We should just leave 1305 // things as is so the next save operation can pick up any changes 1306 // without losing data. 1307 } 1308 return rv; 1309 } 1310 1311 void ServiceWorkerRegistrar::DataSaved(uint32_t aFileGeneration) { 1312 AssertIsOnBackgroundThread(); 1313 MOZ_ASSERT(mSaveDataRunnableDispatched); 1314 1315 mSaveDataRunnableDispatched = false; 1316 1317 // Check for shutdown before possibly triggering any more saves 1318 // runnables. 1319 MaybeScheduleShutdownCompleted(); 1320 if (mShuttingDown) { 1321 return; 1322 } 1323 1324 // If we got a valid generation, then the save was successful. 1325 if (aFileGeneration != kInvalidGeneration) { 1326 // Update the file generation. We also check to see if we 1327 // can reset the generation back to zero if the file and data 1328 // are now in sync. This allows us to avoid dealing with wrap 1329 // around of the generation count. 1330 mFileGeneration = aFileGeneration; 1331 MaybeResetGeneration(); 1332 1333 // Successful write resets the retry count. 1334 mRetryCount = 0; 1335 1336 // Possibly schedule another save operation if more data 1337 // has come in while processing this one. 1338 MaybeScheduleSaveData(); 1339 1340 return; 1341 } 1342 1343 // Otherwise, the save failed since the generation is invalid. We 1344 // want to retry the save, but only a limited number of times. 1345 static const uint32_t kMaxRetryCount = 2; 1346 if (mRetryCount >= kMaxRetryCount) { 1347 return; 1348 } 1349 1350 mRetryCount += 1; 1351 MaybeScheduleSaveData(); 1352 } 1353 1354 void ServiceWorkerRegistrar::MaybeScheduleShutdownCompleted() { 1355 AssertIsOnBackgroundThread(); 1356 1357 if (mSaveDataRunnableDispatched || !mShuttingDown) { 1358 return; 1359 } 1360 1361 RefPtr<Runnable> runnable = 1362 NewRunnableMethod("dom::ServiceWorkerRegistrar::ShutdownCompleted", this, 1363 &ServiceWorkerRegistrar::ShutdownCompleted); 1364 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget())); 1365 } 1366 1367 uint32_t ServiceWorkerRegistrar::GetNextGeneration() { 1368 uint32_t ret = mDataGeneration + 1; 1369 if (ret == kInvalidGeneration) { 1370 ret += 1; 1371 } 1372 return ret; 1373 } 1374 1375 void ServiceWorkerRegistrar::MaybeResetGeneration() { 1376 if (mDataGeneration != mFileGeneration) { 1377 return; 1378 } 1379 mDataGeneration = mFileGeneration = 0; 1380 } 1381 1382 bool ServiceWorkerRegistrar::IsSupportedVersion(uint32_t aVersion) const { 1383 uint32_t numVersions = std::size(gSupportedRegistrarVersions); 1384 for (uint32_t i = 0; i < numVersions; i++) { 1385 if (aVersion == gSupportedRegistrarVersions[i]) { 1386 return true; 1387 } 1388 } 1389 return false; 1390 } 1391 1392 nsresult ServiceWorkerRegistrar::WriteData( 1393 const nsTArray<ServiceWorkerData>& aData) { 1394 // We cannot assert about the correct thread because normally this method 1395 // runs on a IO thread, but in gTests we call it from the main-thread. 1396 1397 nsCOMPtr<nsIFile> file; 1398 1399 { 1400 MonitorAutoLock lock(mMonitor); 1401 1402 if (!mProfileDir) { 1403 return NS_ERROR_FAILURE; 1404 } 1405 1406 nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); 1407 if (NS_WARN_IF(NS_FAILED(rv))) { 1408 return rv; 1409 } 1410 } 1411 1412 nsresult rv = file->Append(nsLiteralString(SERVICEWORKERREGISTRAR_FILE)); 1413 if (NS_WARN_IF(NS_FAILED(rv))) { 1414 return rv; 1415 } 1416 1417 nsCOMPtr<nsIOutputStream> stream; 1418 rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file); 1419 if (NS_WARN_IF(NS_FAILED(rv))) { 1420 return rv; 1421 } 1422 1423 nsAutoCString buffer; 1424 buffer.AppendInt(static_cast<uint32_t>(SERVICEWORKERREGISTRAR_VERSION)); 1425 buffer.Append('\n'); 1426 1427 uint32_t count; 1428 rv = stream->Write(buffer.Data(), buffer.Length(), &count); 1429 if (NS_WARN_IF(NS_FAILED(rv))) { 1430 return rv; 1431 } 1432 1433 if (count != buffer.Length()) { 1434 return NS_ERROR_UNEXPECTED; 1435 } 1436 1437 for (const ServiceWorkerData& data : aData) { 1438 // We have an assertion further up the stack, but as a last 1439 // resort avoid writing out broken entries here. 1440 if (!ServiceWorkerRegistrationDataIsValid(data.mRegistration)) { 1441 continue; 1442 } 1443 1444 const mozilla::ipc::PrincipalInfo& info = data.mRegistration.principal(); 1445 1446 MOZ_ASSERT(info.type() == 1447 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); 1448 1449 const mozilla::ipc::ContentPrincipalInfo& cInfo = 1450 info.get_ContentPrincipalInfo(); 1451 1452 nsAutoCString suffix; 1453 cInfo.attrs().CreateSuffix(suffix); 1454 1455 buffer.Truncate(); 1456 1457 buffer.Append(suffix.get()); 1458 buffer.Append('\n'); 1459 1460 buffer.Append(data.mRegistration.scope()); 1461 buffer.Append('\n'); 1462 1463 buffer.Append(data.mRegistration.currentWorkerURL()); 1464 buffer.Append('\n'); 1465 1466 buffer.Append(data.mRegistration.currentWorkerHandlesFetch() 1467 ? SERVICEWORKERREGISTRAR_TRUE 1468 : SERVICEWORKERREGISTRAR_FALSE); 1469 buffer.Append('\n'); 1470 1471 buffer.Append(NS_ConvertUTF16toUTF8(data.mRegistration.cacheName())); 1472 buffer.Append('\n'); 1473 1474 buffer.AppendInt(data.mRegistration.updateViaCache(), 16); 1475 buffer.Append('\n'); 1476 MOZ_DIAGNOSTIC_ASSERT( 1477 data.mRegistration.updateViaCache() == 1478 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS || 1479 data.mRegistration.updateViaCache() == 1480 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL || 1481 data.mRegistration.updateViaCache() == 1482 nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE); 1483 1484 static_assert(nsIRequest::LOAD_NORMAL == 0, 1485 "LOAD_NORMAL matches serialized value."); 1486 static_assert(nsIRequest::VALIDATE_ALWAYS == (1 << 11), 1487 "VALIDATE_ALWAYS matches serialized value"); 1488 1489 buffer.AppendInt(data.mRegistration.currentWorkerInstalledTime()); 1490 buffer.Append('\n'); 1491 1492 buffer.AppendInt(data.mRegistration.currentWorkerActivatedTime()); 1493 buffer.Append('\n'); 1494 1495 buffer.AppendInt(data.mRegistration.lastUpdateTime()); 1496 buffer.Append('\n'); 1497 1498 buffer.AppendInt(static_cast<int32_t>( 1499 data.mRegistration.navigationPreloadState().enabled())); 1500 buffer.Append('\n'); 1501 1502 buffer.Append(data.mRegistration.navigationPreloadState().headerValue()); 1503 buffer.Append('\n'); 1504 1505 buffer.AppendInt(static_cast<uint32_t>(data.mExpandos.Length()), 16); 1506 buffer.Append('\n'); 1507 1508 for (const ExpandoData& expando : data.mExpandos) { 1509 buffer.Append(expando.mKey); 1510 buffer.Append('\n'); 1511 buffer.Append(expando.mValue); 1512 buffer.Append('\n'); 1513 } 1514 1515 buffer.AppendInt(static_cast<int32_t>( 1516 data.mRegistration.numberOfAttemptedActivations())); 1517 buffer.Append('\n'); 1518 1519 buffer.AppendInt(static_cast<int32_t>(data.mRegistration.isBroken())); 1520 buffer.Append('\n'); 1521 1522 buffer.AppendInt(static_cast<int32_t>(data.mRegistration.cacheAPIId())); 1523 buffer.Append('\n'); 1524 1525 buffer.AppendInt(static_cast<uint32_t>(data.mRegistration.type())); 1526 buffer.Append('\n'); 1527 1528 buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR); 1529 buffer.Append('\n'); 1530 1531 rv = stream->Write(buffer.Data(), buffer.Length(), &count); 1532 if (NS_WARN_IF(NS_FAILED(rv))) { 1533 return rv; 1534 } 1535 1536 if (count != buffer.Length()) { 1537 return NS_ERROR_UNEXPECTED; 1538 } 1539 } 1540 1541 nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream); 1542 MOZ_ASSERT(safeStream); 1543 1544 rv = safeStream->Finish(); 1545 if (NS_WARN_IF(NS_FAILED(rv))) { 1546 return rv; 1547 } 1548 1549 return NS_OK; 1550 } 1551 1552 void ServiceWorkerRegistrar::ProfileStarted() { 1553 MOZ_ASSERT(NS_IsMainThread()); 1554 1555 MonitorAutoLock lock(mMonitor); 1556 MOZ_DIAGNOSTIC_ASSERT(!mProfileDir); 1557 1558 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1559 getter_AddRefs(mProfileDir)); 1560 if (NS_WARN_IF(NS_FAILED(rv))) { 1561 return; 1562 } 1563 1564 nsAutoString blockerName; 1565 MOZ_ALWAYS_SUCCEEDS(GetName(blockerName)); 1566 1567 rv = GetShutdownPhase()->AddBlocker( 1568 this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, blockerName); 1569 if (NS_WARN_IF(NS_FAILED(rv))) { 1570 return; 1571 } 1572 1573 nsCOMPtr<nsIEventTarget> target = 1574 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 1575 MOZ_ASSERT(target, "Must have stream transport service"); 1576 1577 nsCOMPtr<nsIRunnable> runnable = 1578 NewRunnableMethod("dom::ServiceWorkerRegistrar::LoadData", this, 1579 &ServiceWorkerRegistrar::LoadData); 1580 rv = target->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 1581 if (NS_FAILED(rv)) { 1582 NS_WARNING("Failed to dispatch the LoadDataRunnable."); 1583 } 1584 } 1585 1586 void ServiceWorkerRegistrar::ProfileStopped() { 1587 MOZ_ASSERT(NS_IsMainThread()); 1588 1589 MonitorAutoLock lock(mMonitor); 1590 1591 if (!mProfileDir) { 1592 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 1593 getter_AddRefs(mProfileDir)); 1594 if (NS_WARN_IF(NS_FAILED(rv))) { 1595 // If we do not have a profile directory, we are somehow screwed. 1596 MOZ_DIAGNOSTIC_ASSERT( 1597 false, 1598 "NS_GetSpecialDirectory for NS_APP_USER_PROFILE_50_DIR failed!"); 1599 } 1600 } 1601 1602 // Mutations to the ServiceWorkerRegistrar happen on the PBackground thread, 1603 // issued by the ServiceWorkerManagerService, so the appropriate place to 1604 // trigger shutdown is on that thread. 1605 // 1606 // However, it's quite possible that the PBackground thread was not brought 1607 // into existence for xpcshell tests. We don't cause it to be created 1608 // ourselves for any reason, for example. 1609 // 1610 // In this scenario, we know that: 1611 // - We will receive exactly one call to ourself from BlockShutdown() and 1612 // BlockShutdown() will be called (at most) once. 1613 // - The only way our Shutdown() method gets called is via 1614 // BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() being 1615 // invoked, which only happens if we get to that send below here that we 1616 // can't get to. 1617 // - All Shutdown() does is set mShuttingDown=true (essential for 1618 // invariants) and invoke MaybeScheduleShutdownCompleted(). 1619 // - Since there is no PBackground thread, mSaveDataRunnableDispatched must 1620 // be false because only MaybeScheduleSaveData() set it and it only runs 1621 // on the background thread, so it cannot have run. And so we would 1622 // expect MaybeScheduleShutdownCompleted() to schedule an invocation of 1623 // ShutdownCompleted on the main thread. 1624 PBackgroundChild* child = BackgroundChild::GetForCurrentThread(); 1625 if (mProfileDir && child) { 1626 if (child->SendShutdownServiceWorkerRegistrar()) { 1627 // Normal shutdown sequence has been initiated, go home. 1628 return; 1629 } 1630 // If we get here, the PBackground thread has probably gone nuts and we 1631 // want to know it. 1632 MOZ_DIAGNOSTIC_ASSERT( 1633 false, "Unable to send the ShutdownServiceWorkerRegistrar message."); 1634 } 1635 1636 // On any error it's appropriate to set mShuttingDown=true (as Shutdown 1637 // would do) and directly invoke ShutdownCompleted() (as Shutdown would 1638 // indirectly do via MaybeScheduleShutdownCompleted) in order to unblock 1639 // shutdown. 1640 mShuttingDown = true; 1641 ShutdownCompleted(); 1642 } 1643 1644 // Async shutdown blocker methods 1645 1646 NS_IMETHODIMP 1647 ServiceWorkerRegistrar::BlockShutdown(nsIAsyncShutdownClient* aClient) { 1648 ProfileStopped(); 1649 return NS_OK; 1650 } 1651 1652 NS_IMETHODIMP 1653 ServiceWorkerRegistrar::GetName(nsAString& aName) { 1654 aName = u"ServiceWorkerRegistrar: Flushing data"_ns; 1655 return NS_OK; 1656 } 1657 1658 NS_IMETHODIMP 1659 ServiceWorkerRegistrar::GetState(nsIPropertyBag** aBagOut) { 1660 nsCOMPtr<nsIWritablePropertyBag2> propertyBag = 1661 do_CreateInstance("@mozilla.org/hash-property-bag;1"); 1662 1663 MOZ_TRY(propertyBag->SetPropertyAsBool(u"shuttingDown"_ns, mShuttingDown)); 1664 1665 MOZ_TRY(propertyBag->SetPropertyAsBool(u"saveDataRunnableDispatched"_ns, 1666 mSaveDataRunnableDispatched)); 1667 1668 propertyBag.forget(aBagOut); 1669 1670 return NS_OK; 1671 } 1672 1673 #define RELEASE_ASSERT_SUCCEEDED(rv, name) \ 1674 do { \ 1675 if (NS_FAILED(rv)) { \ 1676 if ((rv) == NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS) { \ 1677 if (auto* context = CycleCollectedJSContext::Get()) { \ 1678 if (RefPtr<Exception> exn = context->GetPendingException()) { \ 1679 MOZ_CRASH_UNSAFE_PRINTF("Failed to get " name ": %s", \ 1680 exn->GetMessageMoz().get()); \ 1681 } \ 1682 } \ 1683 } \ 1684 \ 1685 nsAutoCString errorName; \ 1686 GetErrorName(rv, errorName); \ 1687 MOZ_CRASH_UNSAFE_PRINTF("Failed to get " name ": %s", errorName.get()); \ 1688 } \ 1689 } while (0) 1690 1691 nsCOMPtr<nsIAsyncShutdownClient> ServiceWorkerRegistrar::GetShutdownPhase() 1692 const { 1693 nsresult rv; 1694 nsCOMPtr<nsIAsyncShutdownService> svc = 1695 do_GetService("@mozilla.org/async-shutdown-service;1", &rv); 1696 // If this fails, something is very wrong on the JS side (or we're out of 1697 // memory), and there's no point in continuing startup. Include as much 1698 // information as possible in the crash report. 1699 RELEASE_ASSERT_SUCCEEDED(rv, "async shutdown service"); 1700 1701 nsCOMPtr<nsIAsyncShutdownClient> client; 1702 rv = svc->GetProfileBeforeChange(getter_AddRefs(client)); 1703 RELEASE_ASSERT_SUCCEEDED(rv, "profileBeforeChange shutdown blocker"); 1704 return client; 1705 } 1706 1707 #undef RELEASE_ASSERT_SUCCEEDED 1708 1709 void ServiceWorkerRegistrar::Shutdown() { 1710 AssertIsOnBackgroundThread(); 1711 MOZ_ASSERT(!mShuttingDown); 1712 1713 mShuttingDown = true; 1714 MaybeScheduleShutdownCompleted(); 1715 } 1716 1717 NS_IMETHODIMP 1718 ServiceWorkerRegistrar::Observe(nsISupports* aSubject, const char* aTopic, 1719 const char16_t* aData) { 1720 MOZ_ASSERT(NS_IsMainThread()); 1721 1722 if (!strcmp(aTopic, "profile-after-change")) { 1723 nsCOMPtr<nsIObserverService> observerService = 1724 services::GetObserverService(); 1725 observerService->RemoveObserver(this, "profile-after-change"); 1726 1727 // The profile is fully loaded, now we can proceed with the loading of data 1728 // from disk. 1729 ProfileStarted(); 1730 1731 return NS_OK; 1732 } 1733 1734 MOZ_ASSERT(false, "ServiceWorkerRegistrar got unexpected topic!"); 1735 return NS_ERROR_UNEXPECTED; 1736 } 1737 1738 void ServiceWorkerRegistrar::LoadExpandoCallbacks( 1739 const CopyableTArray<ServiceWorkerData>& aData) { 1740 if (NS_IsMainThread()) { 1741 for (const ServiceWorkerData& data : aData) { 1742 for (const ExpandoData& expando : data.mExpandos) { 1743 MOZ_ASSERT(expando.mHandler); 1744 expando.mHandler->mServiceWorkerLoaded(data.mRegistration, 1745 expando.mValue); 1746 } 1747 } 1748 1749 return; 1750 } 1751 1752 NS_DispatchToMainThread(NS_NewRunnableFunction( 1753 __func__, 1754 [self = RefPtr{this}, aData] { self->LoadExpandoCallbacks(aData); })); 1755 } 1756 1757 void ServiceWorkerRegistrar::UpdateExpandoCallbacks( 1758 const ServiceWorkerData& aData) { 1759 if (NS_IsMainThread()) { 1760 for (const ExpandoData& expando : aData.mExpandos) { 1761 MOZ_ASSERT(expando.mHandler); 1762 expando.mHandler->mServiceWorkerUpdated(aData.mRegistration); 1763 } 1764 1765 return; 1766 } 1767 1768 NS_DispatchToMainThread(NS_NewRunnableFunction( 1769 __func__, 1770 [self = RefPtr{this}, aData] { self->UpdateExpandoCallbacks(aData); })); 1771 } 1772 1773 void ServiceWorkerRegistrar::UnregisterExpandoCallbacks( 1774 const CopyableTArray<ServiceWorkerData>& aData) { 1775 if (NS_IsMainThread()) { 1776 for (const ServiceWorkerData& data : aData) { 1777 for (const ExpandoData& expando : data.mExpandos) { 1778 MOZ_ASSERT(expando.mHandler); 1779 expando.mHandler->mServiceWorkerUnregistered(data.mRegistration); 1780 } 1781 } 1782 1783 return; 1784 } 1785 1786 NS_DispatchToMainThread( 1787 NS_NewRunnableFunction(__func__, [self = RefPtr{this}, aData] { 1788 self->UnregisterExpandoCallbacks(aData); 1789 })); 1790 } 1791 1792 } // namespace mozilla::dom