diff options
Diffstat (limited to 'dom/workers/ServiceWorkerRegistrar.cpp')
-rw-r--r-- | dom/workers/ServiceWorkerRegistrar.cpp | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/dom/workers/ServiceWorkerRegistrar.cpp b/dom/workers/ServiceWorkerRegistrar.cpp new file mode 100644 index 000000000..a4757ea54 --- /dev/null +++ b/dom/workers/ServiceWorkerRegistrar.cpp @@ -0,0 +1,884 @@ + +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerRegistrarTypes.h" + +#include "nsIEventTarget.h" +#include "nsIInputStream.h" +#include "nsILineInputStream.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" + +#include "MainThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/PBackgroundChild.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsContentUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace dom { + +namespace { + +static const char* gSupportedRegistrarVersions[] = { + SERVICEWORKERREGISTRAR_VERSION, + "3", + "2" +}; + +StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar; + +} // namespace + +NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar, + nsIObserver) + +void +ServiceWorkerRegistrar::Initialize() +{ + MOZ_ASSERT(!gServiceWorkerRegistrar); + + if (!XRE_IsParentProcess()) { + return; + } + + gServiceWorkerRegistrar = new ServiceWorkerRegistrar(); + ClearOnShutdown(&gServiceWorkerRegistrar); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + DebugOnly<nsresult> rv = obs->AddObserver(gServiceWorkerRegistrar, + "profile-after-change", false); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + rv = obs->AddObserver(gServiceWorkerRegistrar, "profile-before-change", + false); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } +} + +/* static */ already_AddRefed<ServiceWorkerRegistrar> +ServiceWorkerRegistrar::Get() +{ + MOZ_ASSERT(XRE_IsParentProcess()); + + MOZ_ASSERT(gServiceWorkerRegistrar); + RefPtr<ServiceWorkerRegistrar> service = gServiceWorkerRegistrar.get(); + return service.forget(); +} + +ServiceWorkerRegistrar::ServiceWorkerRegistrar() + : mMonitor("ServiceWorkerRegistrar.mMonitor") + , mDataLoaded(false) + , mShuttingDown(false) + , mShutdownCompleteFlag(nullptr) + , mRunnableCounter(0) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +ServiceWorkerRegistrar::~ServiceWorkerRegistrar() +{ + MOZ_ASSERT(!mRunnableCounter); +} + +void +ServiceWorkerRegistrar::GetRegistrations( + nsTArray<ServiceWorkerRegistrationData>& aValues) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aValues.IsEmpty()); + + MonitorAutoLock lock(mMonitor); + + // If we don't have the profile directory, profile is not started yet (and + // probably we are in a utest). + if (!mProfileDir) { + return; + } + + // We care just about the first execution because this can be blocked by + // loading data from disk. + static bool firstTime = true; + TimeStamp startTime; + + if (firstTime) { + startTime = TimeStamp::NowLoRes(); + } + + // Waiting for data loaded. + mMonitor.AssertCurrentThreadOwns(); + while (!mDataLoaded) { + mMonitor.Wait(); + } + + aValues.AppendElements(mData); + + if (firstTime) { + firstTime = false; + Telemetry::AccumulateTimeDelta( + Telemetry::SERVICE_WORKER_REGISTRATION_LOADING, + startTime); + } +} + +namespace { + +bool Equivalent(const ServiceWorkerRegistrationData& aLeft, + const ServiceWorkerRegistrationData& aRight) +{ + MOZ_ASSERT(aLeft.principal().type() == + mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); + MOZ_ASSERT(aRight.principal().type() == + mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); + + const auto& leftPrincipal = aLeft.principal().get_ContentPrincipalInfo(); + const auto& rightPrincipal = aRight.principal().get_ContentPrincipalInfo(); + + // Only compare the attributes, not the spec part of the principal. + // The scope comparison above already covers the origin and codebase + // principals include the full path in their spec which is not what + // we want here. + return aLeft.scope() == aRight.scope() && + leftPrincipal.attrs() == rightPrincipal.attrs(); +} + +} // anonymous namespace + +void +ServiceWorkerRegistrar::RegisterServiceWorker( + const ServiceWorkerRegistrationData& aData) +{ + AssertIsOnBackgroundThread(); + + if (mShuttingDown) { + NS_WARNING("Failed to register a serviceWorker during shutting down."); + return; + } + + { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mDataLoaded); + RegisterServiceWorkerInternal(aData); + } + + ScheduleSaveData(); +} + +void +ServiceWorkerRegistrar::UnregisterServiceWorker( + const PrincipalInfo& aPrincipalInfo, + const nsACString& aScope) +{ + AssertIsOnBackgroundThread(); + + if (mShuttingDown) { + NS_WARNING("Failed to unregister a serviceWorker during shutting down."); + return; + } + + bool deleted = false; + + { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mDataLoaded); + + ServiceWorkerRegistrationData tmp; + tmp.principal() = aPrincipalInfo; + tmp.scope() = aScope; + + for (uint32_t i = 0; i < mData.Length(); ++i) { + if (Equivalent(tmp, mData[i])) { + mData.RemoveElementAt(i); + deleted = true; + break; + } + } + } + + if (deleted) { + ScheduleSaveData(); + } +} + +void +ServiceWorkerRegistrar::RemoveAll() +{ + AssertIsOnBackgroundThread(); + + if (mShuttingDown) { + NS_WARNING("Failed to remove all the serviceWorkers during shutting down."); + return; + } + + bool deleted = false; + + { + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(mDataLoaded); + + deleted = !mData.IsEmpty(); + mData.Clear(); + } + + if (deleted) { + ScheduleSaveData(); + } +} + +void +ServiceWorkerRegistrar::LoadData() +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!mDataLoaded); + + nsresult rv = ReadData(); + + if (NS_WARN_IF(NS_FAILED(rv))) { + DeleteData(); + // Also if the reading failed we have to notify what is waiting for data. + } + + MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mDataLoaded); + mDataLoaded = true; + mMonitor.Notify(); +} + +nsresult +ServiceWorkerRegistrar::ReadData() +{ + // We cannot assert about the correct thread because normally this method + // runs on a IO thread, but in gTests we call it from the main-thread. + + nsCOMPtr<nsIFile> file; + + { + MonitorAutoLock lock(mMonitor); + + if (!mProfileDir) { + return NS_ERROR_FAILURE; + } + + nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + return NS_OK; + } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream); + MOZ_ASSERT(lineInputStream); + + nsAutoCString version; + bool hasMoreLines; + rv = lineInputStream->ReadLine(version, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!IsSupportedVersion(version)) { + nsContentUtils::LogMessageToConsole(nsPrintfCString( + "Unsupported service worker registrar version: %s", version.get()).get()); + return NS_ERROR_FAILURE; + } + + nsTArray<ServiceWorkerRegistrationData> tmpData; + + bool overwrite = false; + bool dedupe = false; + while (hasMoreLines) { + ServiceWorkerRegistrationData* entry = tmpData.AppendElement(); + +#define GET_LINE(x) \ + rv = lineInputStream->ReadLine(x, &hasMoreLines); \ + if (NS_WARN_IF(NS_FAILED(rv))) { \ + return rv; \ + } \ + if (NS_WARN_IF(!hasMoreLines)) { \ + return NS_ERROR_FAILURE; \ + } + + nsAutoCString line; + nsAutoCString unused; + if (version.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) { + nsAutoCString suffix; + GET_LINE(suffix); + + PrincipalOriginAttributes attrs; + if (!attrs.PopulateFromSuffix(suffix)) { + return NS_ERROR_INVALID_ARG; + } + + GET_LINE(entry->scope()); + + entry->principal() = + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); + + GET_LINE(entry->currentWorkerURL()); + + nsAutoCString cacheName; + GET_LINE(cacheName); + CopyUTF8toUTF16(cacheName, entry->cacheName()); + } else if (version.EqualsLiteral("3")) { + overwrite = true; + dedupe = true; + + nsAutoCString suffix; + GET_LINE(suffix); + + PrincipalOriginAttributes attrs; + if (!attrs.PopulateFromSuffix(suffix)) { + return NS_ERROR_INVALID_ARG; + } + + // principal spec is no longer used; we use scope directly instead + GET_LINE(unused); + + GET_LINE(entry->scope()); + + entry->principal() = + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); + + GET_LINE(entry->currentWorkerURL()); + + nsAutoCString cacheName; + GET_LINE(cacheName); + CopyUTF8toUTF16(cacheName, entry->cacheName()); + } else if (version.EqualsLiteral("2")) { + overwrite = true; + dedupe = true; + + nsAutoCString suffix; + GET_LINE(suffix); + + PrincipalOriginAttributes attrs; + if (!attrs.PopulateFromSuffix(suffix)) { + return NS_ERROR_INVALID_ARG; + } + + // principal spec is no longer used; we use scope directly instead + GET_LINE(unused); + + GET_LINE(entry->scope()); + + entry->principal() = + mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), entry->scope()); + + // scriptSpec is no more used in latest version. + GET_LINE(unused); + + GET_LINE(entry->currentWorkerURL()); + + nsAutoCString cacheName; + GET_LINE(cacheName); + CopyUTF8toUTF16(cacheName, entry->cacheName()); + + // waitingCacheName is no more used in latest version. + GET_LINE(unused); + } else { + MOZ_ASSERT_UNREACHABLE("Should never get here!"); + } + +#undef GET_LINE + + rv = lineInputStream->ReadLine(line, &hasMoreLines); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) { + return NS_ERROR_FAILURE; + } + } + + stream->Close(); + + // Copy data over to mData. + for (uint32_t i = 0; i < tmpData.Length(); ++i) { + bool match = false; + if (dedupe) { + MOZ_ASSERT(overwrite); + // If this is an old profile, then we might need to deduplicate. In + // theory this can be removed in the future (Bug 1248449) + for (uint32_t j = 0; j < mData.Length(); ++j) { + // Use same comparison as RegisterServiceWorker. Scope contains + // basic origin information. Combine with any principal attributes. + if (Equivalent(tmpData[i], mData[j])) { + // Last match wins, just like legacy loading used to do in + // the ServiceWorkerManager. + mData[j] = tmpData[i]; + // Dupe found, so overwrite file with reduced list. + match = true; + break; + } + } + } else { +#ifdef DEBUG + // Otherwise assert no duplications in debug builds. + for (uint32_t j = 0; j < mData.Length(); ++j) { + MOZ_ASSERT(!Equivalent(tmpData[i], mData[j])); + } +#endif + } + if (!match) { + mData.AppendElement(tmpData[i]); + } + } + + // Overwrite previous version. + // Cannot call SaveData directly because gtest uses main-thread. + if (overwrite && NS_FAILED(WriteData())) { + NS_WARNING("Failed to write data for the ServiceWorker Registations."); + DeleteData(); + } + + return NS_OK; +} + +void +ServiceWorkerRegistrar::DeleteData() +{ + // We cannot assert about the correct thread because normally this method + // runs on a IO thread, but in gTests we call it from the main-thread. + + nsCOMPtr<nsIFile> file; + + { + MonitorAutoLock lock(mMonitor); + mData.Clear(); + + if (!mProfileDir) { + return; + } + + nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + rv = file->Remove(false); + if (rv == NS_ERROR_FILE_NOT_FOUND) { + return; + } + + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } +} + +void +ServiceWorkerRegistrar::RegisterServiceWorkerInternal(const ServiceWorkerRegistrationData& aData) +{ + bool found = false; + for (uint32_t i = 0, len = mData.Length(); i < len; ++i) { + if (Equivalent(aData, mData[i])) { + mData[i] = aData; + found = true; + break; + } + } + + if (!found) { + mData.AppendElement(aData); + } +} + +class ServiceWorkerRegistrarSaveDataRunnable final : public Runnable +{ +public: + ServiceWorkerRegistrarSaveDataRunnable() + : mThread(do_GetCurrentThread()) + { + AssertIsOnBackgroundThread(); + } + + NS_IMETHOD + Run() override + { + RefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get(); + MOZ_ASSERT(service); + + service->SaveData(); + + RefPtr<Runnable> runnable = + NewRunnableMethod(service, &ServiceWorkerRegistrar::DataSaved); + nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + +private: + nsCOMPtr<nsIThread> mThread; +}; + +void +ServiceWorkerRegistrar::ScheduleSaveData() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mShuttingDown); + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target, "Must have stream transport service"); + + RefPtr<Runnable> runnable = + new ServiceWorkerRegistrarSaveDataRunnable(); + nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + ++mRunnableCounter; +} + +void +ServiceWorkerRegistrar::ShutdownCompleted() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MOZ_ASSERT(mShutdownCompleteFlag && !*mShutdownCompleteFlag); + *mShutdownCompleteFlag = true; +} + +void +ServiceWorkerRegistrar::SaveData() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsresult rv = WriteData(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to write data for the ServiceWorker Registations."); + DeleteData(); + } +} + +void +ServiceWorkerRegistrar::DataSaved() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(mRunnableCounter); + + --mRunnableCounter; + MaybeScheduleShutdownCompleted(); +} + +void +ServiceWorkerRegistrar::MaybeScheduleShutdownCompleted() +{ + AssertIsOnBackgroundThread(); + + if (mRunnableCounter || !mShuttingDown) { + return; + } + + RefPtr<Runnable> runnable = + NewRunnableMethod(this, &ServiceWorkerRegistrar::ShutdownCompleted); + nsresult rv = NS_DispatchToMainThread(runnable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } +} + +bool +ServiceWorkerRegistrar::IsSupportedVersion(const nsACString& aVersion) const +{ + uint32_t numVersions = ArrayLength(gSupportedRegistrarVersions); + for (uint32_t i = 0; i < numVersions; i++) { + if (aVersion.EqualsASCII(gSupportedRegistrarVersions[i])) { + return true; + } + } + return false; +} + +nsresult +ServiceWorkerRegistrar::WriteData() +{ + // We cannot assert about the correct thread because normally this method + // runs on a IO thread, but in gTests we call it from the main-thread. + + nsCOMPtr<nsIFile> file; + + { + MonitorAutoLock lock(mMonitor); + + if (!mProfileDir) { + return NS_ERROR_FAILURE; + } + + nsresult rv = mProfileDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsresult rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // We need a lock to take a snapshot of the data. + nsTArray<ServiceWorkerRegistrationData> data; + { + MonitorAutoLock lock(mMonitor); + data = mData; + } + + nsCOMPtr<nsIOutputStream> stream; + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString buffer; + buffer.AppendLiteral(SERVICEWORKERREGISTRAR_VERSION); + buffer.Append('\n'); + + uint32_t count; + rv = stream->Write(buffer.Data(), buffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (count != buffer.Length()) { + return NS_ERROR_UNEXPECTED; + } + + for (uint32_t i = 0, len = data.Length(); i < len; ++i) { + const mozilla::ipc::PrincipalInfo& info = data[i].principal(); + + MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo); + + const mozilla::ipc::ContentPrincipalInfo& cInfo = + info.get_ContentPrincipalInfo(); + + nsAutoCString suffix; + cInfo.attrs().CreateSuffix(suffix); + + buffer.Truncate(); + buffer.Append(suffix.get()); + buffer.Append('\n'); + + buffer.Append(data[i].scope()); + buffer.Append('\n'); + + buffer.Append(data[i].currentWorkerURL()); + buffer.Append('\n'); + + buffer.Append(NS_ConvertUTF16toUTF8(data[i].cacheName())); + buffer.Append('\n'); + + buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR); + buffer.Append('\n'); + + rv = stream->Write(buffer.Data(), buffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (count != buffer.Length()) { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream); + MOZ_ASSERT(safeStream); + + rv = safeStream->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void +ServiceWorkerRegistrar::ProfileStarted() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mMonitor); + MOZ_DIAGNOSTIC_ASSERT(!mProfileDir); + + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mProfileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target, "Must have stream transport service"); + + nsCOMPtr<nsIRunnable> runnable = + NewRunnableMethod(this, &ServiceWorkerRegistrar::LoadData); + rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch the LoadDataRunnable."); + } +} + +void +ServiceWorkerRegistrar::ProfileStopped() +{ + MOZ_ASSERT(NS_IsMainThread()); + + MonitorAutoLock lock(mMonitor); + + if (!mProfileDir) { + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(mProfileDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + } + + // We must set the pointer before potentially entering the fast-path shutdown + // below. + bool completed = false; + mShutdownCompleteFlag = &completed; + + PBackgroundChild* child = BackgroundChild::GetForCurrentThread(); + if (!child) { + // Mutations to the ServiceWorkerRegistrar happen on the PBackground thread, + // issued by the ServiceWorkerManagerService, so the appropriate place to + // trigger shutdown is on that thread. + // + // However, it's quite possible that the PBackground thread was not brought + // into existence for xpcshell tests. We don't cause it to be created + // ourselves for any reason, for example. + // + // In this scenario, we know that: + // - We will receive exactly one call to ourself from BlockShutdown() and + // BlockShutdown() will be called (at most) once. + // - The only way our Shutdown() method gets called is via + // BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar() being + // invoked, which only happens if we get to that send below here that we + // can't get to. + // - All Shutdown() does is set mShuttingDown=true (essential for + // invariants) and invoke MaybeScheduleShutdownCompleted(). + // - Since there is no PBackground thread, mRunnableCounter must be 0 + // because only ScheduleSaveData() increments it and it only runs on the + // background thread, so it cannot have run. And so we would expect + // MaybeScheduleShutdownCompleted() to schedule an invocation of + // ShutdownCompleted on the main thread. + // + // So it's appropriate for us to set mShuttingDown=true (as Shutdown would + // do) and directly invoke ShutdownCompleted() (as Shutdown would indirectly + // do via MaybeScheduleShutdownCompleted). + mShuttingDown = true; + ShutdownCompleted(); + return; + } + + child->SendShutdownServiceWorkerRegistrar(); + + nsCOMPtr<nsIThread> thread(do_GetCurrentThread()); + while (true) { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread)); + if (completed) { + break; + } + } +} + +void +ServiceWorkerRegistrar::Shutdown() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mShuttingDown); + + mShuttingDown = true; + MaybeScheduleShutdownCompleted(); +} + +NS_IMETHODIMP +ServiceWorkerRegistrar::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!strcmp(aTopic, "profile-after-change")) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + observerService->RemoveObserver(this, "profile-after-change"); + + // The profile is fully loaded, now we can proceed with the loading of data + // from disk. + ProfileStarted(); + + return NS_OK; + } + + if (!strcmp(aTopic, "profile-before-change")) { + // Hygiene; gServiceWorkerRegistrar should still be keeping a reference + // alive well past this phase of shutdown, but it's bad form to drop your + // last potentially owning reference and then make a call that requires you + // to still be alive, especially when you spin a nested event loop. + RefPtr<ServiceWorkerRegistrar> kungFuDeathGrip(this); + + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + observerService->RemoveObserver(this, "profile-before-change"); + + // Shutting down, let's sync the data. + ProfileStopped(); + + return NS_OK; + } + + MOZ_ASSERT(false, "ServiceWorkerRegistrar got unexpected topic!"); + return NS_ERROR_UNEXPECTED; +} + +} // namespace dom +} // namespace mozilla |