gecko-dev/dom/workers/ServiceWorkerRegistrar.cpp
Bill McCloskey aefa2aa5da Bug 1365097 - Convert NS_GetCurrentThread uses in dom (except for dom/media) (r=smaug)
For the Quatum DOM project, it's better to work in terms of event targets than
threads. This patch converts DOM code to operate on event targets rather than
threads, when possible.

MozReview-Commit-ID: 5FgvpKadUA2
2017-06-12 20:20:08 -07:00

1040 lines
26 KiB
C++

/* -*- 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,
"6",
"5",
"4",
"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);
OriginAttributes 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 fetchFlag;
GET_LINE(fetchFlag);
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
return NS_ERROR_INVALID_ARG;
}
entry->currentWorkerHandlesFetch() =
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
nsAutoCString loadFlags;
GET_LINE(loadFlags);
entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
} else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString installedTimeStr;
GET_LINE(installedTimeStr);
int64_t installedTime = installedTimeStr.ToInteger64(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
entry->currentWorkerInstalledTime() = installedTime;
nsAutoCString activatedTimeStr;
GET_LINE(activatedTimeStr);
int64_t activatedTime = activatedTimeStr.ToInteger64(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
entry->currentWorkerActivatedTime() = activatedTime;
nsAutoCString lastUpdateTimeStr;
GET_LINE(lastUpdateTimeStr);
int64_t lastUpdateTime = lastUpdateTimeStr.ToInteger64(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
entry->lastUpdateTime() = lastUpdateTime;
} else if (version.EqualsLiteral("6")) {
nsAutoCString suffix;
GET_LINE(suffix);
OriginAttributes 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 fetchFlag;
GET_LINE(fetchFlag);
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
return NS_ERROR_INVALID_ARG;
}
entry->currentWorkerHandlesFetch() =
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
nsAutoCString loadFlags;
GET_LINE(loadFlags);
entry->loadFlags() = loadFlags.ToInteger(&rv, 16);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
} else if (entry->loadFlags() != nsIRequest::LOAD_NORMAL &&
entry->loadFlags() != nsIRequest::VALIDATE_ALWAYS) {
return NS_ERROR_INVALID_ARG;
}
entry->currentWorkerInstalledTime() = 0;
entry->currentWorkerActivatedTime() = 0;
entry->lastUpdateTime() = 0;
} else if (version.EqualsLiteral("5")) {
overwrite = true;
dedupe = true;
nsAutoCString suffix;
GET_LINE(suffix);
OriginAttributes 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 fetchFlag;
GET_LINE(fetchFlag);
if (!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
!fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
return NS_ERROR_INVALID_ARG;
}
entry->currentWorkerHandlesFetch() =
fetchFlag.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
entry->currentWorkerInstalledTime() = 0;
entry->currentWorkerActivatedTime() = 0;
entry->lastUpdateTime() = 0;
} else if (version.EqualsLiteral("4")) {
overwrite = true;
dedupe = true;
nsAutoCString suffix;
GET_LINE(suffix);
OriginAttributes 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());
// default handlesFetch flag to Enabled
entry->currentWorkerHandlesFetch() = true;
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
entry->currentWorkerInstalledTime() = 0;
entry->currentWorkerActivatedTime() = 0;
entry->lastUpdateTime() = 0;
} else if (version.EqualsLiteral("3")) {
overwrite = true;
dedupe = true;
nsAutoCString suffix;
GET_LINE(suffix);
OriginAttributes 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());
// default handlesFetch flag to Enabled
entry->currentWorkerHandlesFetch() = true;
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
entry->currentWorkerInstalledTime() = 0;
entry->currentWorkerActivatedTime() = 0;
entry->lastUpdateTime() = 0;
} else if (version.EqualsLiteral("2")) {
overwrite = true;
dedupe = true;
nsAutoCString suffix;
GET_LINE(suffix);
OriginAttributes 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());
// default handlesFetch flag to Enabled
entry->currentWorkerHandlesFetch() = true;
nsAutoCString cacheName;
GET_LINE(cacheName);
CopyUTF8toUTF16(cacheName, entry->cacheName());
// waitingCacheName is no more used in latest version.
GET_LINE(unused);
entry->loadFlags() = nsIRequest::VALIDATE_ALWAYS;
entry->currentWorkerInstalledTime() = 0;
entry->currentWorkerActivatedTime() = 0;
entry->lastUpdateTime() = 0;
} 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()
: mEventTarget(GetCurrentThreadEventTarget())
{
AssertIsOnBackgroundThread();
}
NS_IMETHOD
Run() override
{
RefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get();
MOZ_ASSERT(service);
service->SaveData();
RefPtr<Runnable> runnable =
NewRunnableMethod(service, &ServiceWorkerRegistrar::DataSaved);
nsresult rv = mEventTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
private:
nsCOMPtr<nsIEventTarget> mEventTarget;
};
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(data[i].currentWorkerHandlesFetch() ?
SERVICEWORKERREGISTRAR_TRUE : SERVICEWORKERREGISTRAR_FALSE);
buffer.Append('\n');
buffer.Append(NS_ConvertUTF16toUTF8(data[i].cacheName()));
buffer.Append('\n');
buffer.AppendInt(data[i].loadFlags(), 16);
buffer.Append('\n');
MOZ_DIAGNOSTIC_ASSERT(data[i].loadFlags() == nsIRequest::LOAD_NORMAL ||
data[i].loadFlags() == nsIRequest::VALIDATE_ALWAYS);
static_assert(nsIRequest::LOAD_NORMAL == 0,
"LOAD_NORMAL matches serialized value.");
static_assert(nsIRequest::VALIDATE_ALWAYS == (1 << 11),
"VALIDATE_ALWAYS matches serialized value");
buffer.AppendInt(data[i].currentWorkerInstalledTime());
buffer.Append('\n');
buffer.AppendInt(data[i].currentWorkerActivatedTime());
buffer.Append('\n');
buffer.AppendInt(data[i].lastUpdateTime());
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;
}
}
PBackgroundChild* child = BackgroundChild::GetForCurrentThread();
if (!child) {
return;
}
bool completed = false;
mShutdownCompleteFlag = &completed;
child->SendShutdownServiceWorkerRegistrar();
MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return completed; }));
}
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")) {
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