mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
a212459d62
This patch introduces an explicit concept of lifetimes with mechanisms in place so that actions taken by Clients (windows or non-ServiceWorker orkers) will extend the lifetime of a ServiceWorker, but a ServiceWorker cannot extend the life of another ServiceWorker. The areas of concern are: - ServiceWorker.postMessage: With ServiceWorkers exposed on workers and the ability to access other registrations via ServiceWorkerContainer being exposed, ServiceWorkers can message other ServiceWorkers. It's essential that they never be allowed to give a ServiceWorker a lifetime longer than their own. - ServiceWorkerRegistration.update(): Requesting an update of a registration should not allow any installed/updated ServiceWorker to have a lifetime longer than the ServiceWorker creating the request. - ServiceWorkerContainer.register(): Requesting the installation of a new ServiceWorker should likewise constrain the lifetime of the newly installed ServiceWorker. Note that in cases where we would potentially spawn a ServiceWorker, whether it be in response to postMessage or as part of an install or update, a key criteria is whether the lifetime extends far enough into the future for us to believe the ServiceWorker can accomplish anything. Currently we have a constant of 5 seconds against a normal full lifetime of 30 seconds (before 30 second grace period). So once a SW has < 5 seconds of lifetime left, it won't be able to spawn a SW. Note that in the case of install/update, we do not prevent the creation of the job at this time, instead the job will fail during the check script evaluation step as failure to spawn the ServiceWorker is equivalent to a script load failure. A somewhat ugly part of this implementation is that because Bug 1853706 is not yet implemented, our actors that are fundamentally associated with a global don't have an inherent understanding of their relationship to that global. So we approximate that by: - For postMessage, we always have a ServiceWorkerDescriptor if we are being messaged by a ServiceWorker, allowing us direct lookup. - ServiceWorkerRegistration.update(): In a previous patch in the stack we had ServiceWorkerRegistrationProxy latch the ClientInfo of its owning global when it was created. Note that in the case of a ServiceWorker's own registration, this will be created at startup before the worker hits the execution ready state. - Note that because we have at most one live ServiceWorkerRegistration per global at a time, and the registration is fundamentally associated with the ServiceWorkerGlobalScope, that registration and its proxy will remain alive for the duration of the global. - ServiceWorkerContainer.register(): We already were sending the client info along with the register call (as well as all other calls on the container). Looking up the ServiceWorker from its client is not something that was really intended. This is further complicated by ServiceWorkerManager being authoritative for ServiceWorkers on the parent process main thread whereas the ClientManagerService is authoritative on PBackground and actor-centric, making sketchy multi-threaded maps not really an option. Looking up the ServiceWorker from a ServiceWorkerDescriptor is intended, but the primary intent in those cases is so that the recipient of such a descriptor can easily create a ServiceWorker instance that is live-updating (by way of its owning ServiceWorkerRegistration; we don't have IPC actors directly for ServiceWorkers, just the registration). Adding the descriptor to clients until Bug 1853706 is implemented would be an exceedingly ugly workaround because it would greatly complicate the existing plumbing code, and a lot of the code is confusing enough as-is. This patch initially adopted an approach of encoding the scope of a ServiceWorker as its client URL, but it turns out web extension ServiceWorker support (reasonably) assumed the URL would be the script URL so the original behavior was restored and when performing our lookup we just check all registrations associated with the given origin. This is okay because register and update calls are inherently expensive, rare operations and the overhead of the additional checks is marginal. Additionally, we can remove this logic once Bug 1853706 is implemented. As part of that initial scope tunneling was that, as noted above, we do sample the ClientInfo for a ServiceWorker's own registration before the worker is execution-ready. And prior to this patch, we only would populate the URL during execution-ready because for most globals, we can't possibly know the URL when the ClientSource is created. However, for ServiceWorkers we can. Because we also want to know what the id of the ServiceWorker client would be, we also change the creation of the ServiceWorker ClientSource so that it uses a ClientInfo created by the authoritative ServiceWorkerPrivate in its Initialize method. A minor retained hack is that because the worker scriptloader propagates its CSP structure onto its ClientInfo (but not its ClientSource, which feels weird, but makes sense) and that does get sent via register(), we do also need to normalize the ClientInfo in the parent when we do equality checking to have it ignore the CSP. Differential Revision: https://phabricator.services.mozilla.com/D180915
943 lines
28 KiB
C++
943 lines
28 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 "ServiceWorkerRegistrationInfo.h"
|
|
|
|
#include "ServiceWorkerManager.h"
|
|
#include "ServiceWorkerPrivate.h"
|
|
#include "ServiceWorkerRegistrationListener.h"
|
|
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
|
|
namespace mozilla::dom {
|
|
|
|
namespace {
|
|
|
|
class ContinueActivateRunnable final : public LifeCycleEventCallback {
|
|
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> mRegistration;
|
|
bool mSuccess;
|
|
|
|
public:
|
|
explicit ContinueActivateRunnable(
|
|
const nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo>& aRegistration)
|
|
: mRegistration(aRegistration), mSuccess(false) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
}
|
|
|
|
void SetResult(bool aResult) override { mSuccess = aResult; }
|
|
|
|
NS_IMETHOD
|
|
Run() override {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mRegistration->FinishActivate(mSuccess);
|
|
mRegistration = nullptr;
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void ServiceWorkerRegistrationInfo::ShutdownWorkers() {
|
|
ForEachWorker([](RefPtr<ServiceWorkerInfo>& aWorker) {
|
|
aWorker->WorkerPrivate()->NoteDeadServiceWorkerInfo();
|
|
aWorker = nullptr;
|
|
});
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::Clear() {
|
|
ForEachWorker([](RefPtr<ServiceWorkerInfo>& aWorker) {
|
|
aWorker->UpdateState(ServiceWorkerState::Redundant);
|
|
aWorker->UpdateRedundantTime();
|
|
});
|
|
|
|
// FIXME: Abort any inflight requests from installing worker.
|
|
|
|
ShutdownWorkers();
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
NotifyCleared();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::ClearAsCorrupt() {
|
|
mCorrupt = true;
|
|
Clear();
|
|
}
|
|
|
|
bool ServiceWorkerRegistrationInfo::IsCorrupt() const { return mCorrupt; }
|
|
|
|
ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
|
|
const nsACString& aScope, nsIPrincipal* aPrincipal,
|
|
ServiceWorkerUpdateViaCache aUpdateViaCache,
|
|
IPCNavigationPreloadState&& aNavigationPreloadState)
|
|
: mPrincipal(aPrincipal),
|
|
mDescriptor(GetNextId(), GetNextVersion(), aPrincipal, aScope,
|
|
aUpdateViaCache),
|
|
mControlledClientsCounter(0),
|
|
mDelayMultiplier(0),
|
|
mUpdateState(NoUpdate),
|
|
mCreationTime(PR_Now()),
|
|
mCreationTimeStamp(TimeStamp::Now()),
|
|
mLastUpdateTime(0),
|
|
mUnregistered(false),
|
|
mCorrupt(false),
|
|
mNavigationPreloadState(std::move(aNavigationPreloadState)) {
|
|
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
|
|
}
|
|
|
|
ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo() {
|
|
MOZ_DIAGNOSTIC_ASSERT(!IsControllingClients());
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::AddInstance(
|
|
ServiceWorkerRegistrationListener* aInstance,
|
|
const ServiceWorkerRegistrationDescriptor& aDescriptor) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aInstance);
|
|
MOZ_ASSERT(!mInstanceList.Contains(aInstance));
|
|
MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Id() == mDescriptor.Id());
|
|
MOZ_DIAGNOSTIC_ASSERT(aDescriptor.PrincipalInfo() ==
|
|
mDescriptor.PrincipalInfo());
|
|
MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Scope() == mDescriptor.Scope());
|
|
MOZ_DIAGNOSTIC_ASSERT(aDescriptor.Version() <= mDescriptor.Version());
|
|
uint64_t lastVersion = aDescriptor.Version();
|
|
for (auto& entry : mVersionList) {
|
|
if (lastVersion > entry->mDescriptor.Version()) {
|
|
continue;
|
|
}
|
|
lastVersion = entry->mDescriptor.Version();
|
|
aInstance->UpdateState(entry->mDescriptor);
|
|
}
|
|
// Note, the mDescriptor may be contained in the version list. Since the
|
|
// version list is aged out, though, it may also not be in the version list.
|
|
// So always check for the mDescriptor update here.
|
|
if (lastVersion < mDescriptor.Version()) {
|
|
aInstance->UpdateState(mDescriptor);
|
|
}
|
|
mInstanceList.AppendElement(aInstance);
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::RemoveInstance(
|
|
ServiceWorkerRegistrationListener* aInstance) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aInstance);
|
|
DebugOnly<bool> removed = mInstanceList.RemoveElement(aInstance);
|
|
MOZ_ASSERT(removed);
|
|
}
|
|
|
|
const nsCString& ServiceWorkerRegistrationInfo::Scope() const {
|
|
return mDescriptor.Scope();
|
|
}
|
|
|
|
nsIPrincipal* ServiceWorkerRegistrationInfo::Principal() const {
|
|
return mPrincipal;
|
|
}
|
|
|
|
bool ServiceWorkerRegistrationInfo::IsUnregistered() const {
|
|
return mUnregistered;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetUnregistered() {
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(!mUnregistered);
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
RefPtr<ServiceWorkerRegistrationInfo> registration =
|
|
swm->GetRegistration(Principal(), Scope());
|
|
MOZ_ASSERT(registration != this);
|
|
#endif
|
|
|
|
mUnregistered = true;
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo,
|
|
nsIServiceWorkerRegistrationInfo)
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetPrincipal(nsIPrincipal** aPrincipal) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
NS_ADDREF(*aPrincipal = mPrincipal);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP ServiceWorkerRegistrationInfo::GetUnregistered(
|
|
bool* aUnregistered) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aUnregistered);
|
|
*aUnregistered = mUnregistered;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetScope(nsAString& aScope) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
CopyUTF8toUTF16(Scope(), aScope);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetScriptSpec(nsAString& aScriptSpec) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<ServiceWorkerInfo> newest = NewestIncludingEvaluating();
|
|
if (newest) {
|
|
CopyUTF8toUTF16(newest->ScriptSpec(), aScriptSpec);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetUpdateViaCache(uint16_t* aUpdateViaCache) {
|
|
*aUpdateViaCache = static_cast<uint16_t>(GetUpdateViaCache());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetLastUpdateTime(PRTime* _retval) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mLastUpdateTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetEvaluatingWorker(
|
|
nsIServiceWorkerInfo** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<ServiceWorkerInfo> info = mEvaluatingWorker;
|
|
info.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetInstallingWorker(
|
|
nsIServiceWorkerInfo** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<ServiceWorkerInfo> info = mInstallingWorker;
|
|
info.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetWaitingWorker(
|
|
nsIServiceWorkerInfo** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<ServiceWorkerInfo> info = mWaitingWorker;
|
|
info.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetActiveWorker(nsIServiceWorkerInfo** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
RefPtr<ServiceWorkerInfo> info = mActiveWorker;
|
|
info.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetQuotaUsageCheckCount(
|
|
int32_t* aQuotaUsageCheckCount) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aQuotaUsageCheckCount);
|
|
|
|
// This value is actually stored on SWM's internal-only
|
|
// RegistrationDataPerPrincipal structure, but we expose it here for
|
|
// simplicity for our consumers, so we have to ask SWM to look it up for us.
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
*aQuotaUsageCheckCount = swm->GetPrincipalQuotaUsageCheckCount(mPrincipal);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::GetWorkerByID(uint64_t aID,
|
|
nsIServiceWorkerInfo** aResult) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aResult);
|
|
|
|
RefPtr<ServiceWorkerInfo> info = GetServiceWorkerInfoById(aID);
|
|
// It is ok to return null for a missing service worker info.
|
|
info.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::AddListener(
|
|
nsIServiceWorkerRegistrationInfoListener* aListener) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aListener || mListeners.Contains(aListener)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mListeners.AppendElement(aListener);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::RemoveListener(
|
|
nsIServiceWorkerRegistrationInfoListener* aListener) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!aListener || !mListeners.Contains(aListener)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
mListeners.RemoveElement(aListener);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ServiceWorkerRegistrationInfo::ForceShutdown() {
|
|
ClearInstalling();
|
|
ShutdownWorkers();
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<ServiceWorkerInfo>
|
|
ServiceWorkerRegistrationInfo::GetServiceWorkerInfoById(uint64_t aId) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<ServiceWorkerInfo> serviceWorker;
|
|
if (mEvaluatingWorker && mEvaluatingWorker->ID() == aId) {
|
|
serviceWorker = mEvaluatingWorker;
|
|
} else if (mInstallingWorker && mInstallingWorker->ID() == aId) {
|
|
serviceWorker = mInstallingWorker;
|
|
} else if (mWaitingWorker && mWaitingWorker->ID() == aId) {
|
|
serviceWorker = mWaitingWorker;
|
|
} else if (mActiveWorker && mActiveWorker->ID() == aId) {
|
|
serviceWorker = mActiveWorker;
|
|
}
|
|
|
|
return serviceWorker.forget();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::TryToActivateAsync(
|
|
const ServiceWorkerLifetimeExtension& aLifetimeExtension,
|
|
TryToActivateCallback&& aCallback) {
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
|
|
NewRunnableMethod<StoreCopyPassByRRef<ServiceWorkerLifetimeExtension>,
|
|
StoreCopyPassByRRef<TryToActivateCallback>>(
|
|
"ServiceWorkerRegistrationInfo::TryToActivate", this,
|
|
&ServiceWorkerRegistrationInfo::TryToActivate, aLifetimeExtension,
|
|
std::move(aCallback))));
|
|
}
|
|
|
|
/*
|
|
* TryToActivate should not be called directly, use TryToActivateAsync instead.
|
|
*/
|
|
void ServiceWorkerRegistrationInfo::TryToActivate(
|
|
ServiceWorkerLifetimeExtension&& aLifetimeExtension,
|
|
TryToActivateCallback&& aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
bool controlling = IsControllingClients();
|
|
bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag();
|
|
bool idle = IsIdle();
|
|
if (idle && (!controlling || skipWaiting)) {
|
|
if (controlling) {
|
|
aLifetimeExtension.emplace<FullLifetimeExtension>();
|
|
}
|
|
Activate(aLifetimeExtension);
|
|
}
|
|
|
|
if (aCallback) {
|
|
aCallback();
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::Activate(
|
|
const ServiceWorkerLifetimeExtension& aLifetimeExtension) {
|
|
if (!mWaitingWorker) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
// browser shutdown began during async activation step
|
|
return;
|
|
}
|
|
|
|
TransitionWaitingToActive();
|
|
|
|
// FIXME(nsm): Unlink appcache if there is one.
|
|
|
|
// "Queue a task to fire a simple event named controllerchange..."
|
|
MOZ_DIAGNOSTIC_ASSERT(mActiveWorker);
|
|
swm->UpdateClientControllers(this);
|
|
|
|
nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> handle(
|
|
new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(
|
|
"ServiceWorkerRegistrationInfoProxy", this));
|
|
RefPtr<LifeCycleEventCallback> callback =
|
|
new ContinueActivateRunnable(handle);
|
|
|
|
ServiceWorkerPrivate* workerPrivate = mActiveWorker->WorkerPrivate();
|
|
MOZ_ASSERT(workerPrivate);
|
|
nsresult rv = workerPrivate->SendLifeCycleEvent(u"activate"_ns,
|
|
aLifetimeExtension, callback);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
nsCOMPtr<nsIRunnable> failRunnable = NewRunnableMethod<bool>(
|
|
"dom::ServiceWorkerRegistrationInfo::FinishActivate", this,
|
|
&ServiceWorkerRegistrationInfo::FinishActivate, false /* success */);
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(failRunnable.forget()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess) {
|
|
if (mUnregistered || !mActiveWorker ||
|
|
mActiveWorker->State() != ServiceWorkerState::Activating) {
|
|
return;
|
|
}
|
|
|
|
// Activation never fails, so aSuccess is ignored.
|
|
mActiveWorker->UpdateState(ServiceWorkerState::Activated);
|
|
mActiveWorker->UpdateActivatedTime();
|
|
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
// browser shutdown started during async activation completion step
|
|
return;
|
|
}
|
|
swm->StoreRegistration(mPrincipal, this);
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::RefreshLastUpdateCheckTime() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
mLastUpdateTime =
|
|
mCreationTime +
|
|
static_cast<PRTime>(
|
|
(TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds());
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
bool ServiceWorkerRegistrationInfo::IsLastUpdateCheckTimeOverOneDay() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
// For testing.
|
|
if (Preferences::GetBool("dom.serviceWorkers.testUpdateOverOneDay")) {
|
|
return true;
|
|
}
|
|
|
|
const int64_t kSecondsPerDay = 86400;
|
|
const int64_t nowMicros =
|
|
mCreationTime +
|
|
static_cast<PRTime>(
|
|
(TimeStamp::Now() - mCreationTimeStamp).ToMicroseconds());
|
|
|
|
// now < mLastUpdateTime if the system time is reset between storing
|
|
// and loading mLastUpdateTime from ServiceWorkerRegistrar.
|
|
if (nowMicros < mLastUpdateTime ||
|
|
(nowMicros - mLastUpdateTime) / PR_USEC_PER_SEC > kSecondsPerDay) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::UpdateRegistrationState() {
|
|
UpdateRegistrationState(mDescriptor.UpdateViaCache());
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::UpdateRegistrationState(
|
|
ServiceWorkerUpdateViaCache aUpdateViaCache) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
TimeStamp oldest = TimeStamp::Now() - TimeDuration::FromSeconds(30);
|
|
if (!mVersionList.IsEmpty() && mVersionList[0]->mTimeStamp < oldest) {
|
|
nsTArray<UniquePtr<VersionEntry>> list = std::move(mVersionList);
|
|
for (auto& entry : list) {
|
|
if (entry->mTimeStamp >= oldest) {
|
|
mVersionList.AppendElement(std::move(entry));
|
|
}
|
|
}
|
|
}
|
|
mVersionList.AppendElement(MakeUnique<VersionEntry>(mDescriptor));
|
|
|
|
// We are going to modify the descriptor, so increase its version number.
|
|
mDescriptor.SetVersion(GetNextVersion());
|
|
|
|
// Note, this also sets the new version number on the ServiceWorkerInfo
|
|
// objects before we copy over their updated descriptors.
|
|
mDescriptor.SetWorkers(mInstallingWorker, mWaitingWorker, mActiveWorker);
|
|
|
|
mDescriptor.SetUpdateViaCache(aUpdateViaCache);
|
|
|
|
for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget :
|
|
mInstanceList.ForwardRange()) {
|
|
pinnedTarget->UpdateState(mDescriptor);
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::NotifyChromeRegistrationListeners() {
|
|
nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners(
|
|
mListeners.Clone());
|
|
for (size_t index = 0; index < listeners.Length(); ++index) {
|
|
listeners[index]->OnChange();
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::MaybeScheduleTimeCheckAndUpdate() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
// shutting down, do nothing
|
|
return;
|
|
}
|
|
|
|
if (mUpdateState == NoUpdate) {
|
|
mUpdateState = NeedTimeCheckAndUpdate;
|
|
}
|
|
|
|
swm->ScheduleUpdateTimer(mPrincipal, Scope());
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::MaybeScheduleUpdate() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (!swm) {
|
|
// shutting down, do nothing
|
|
return;
|
|
}
|
|
|
|
// When reach the navigation fault threshold, calling unregister instead of
|
|
// scheduling update.
|
|
if (mActiveWorker && !mUnregistered) {
|
|
uint32_t navigationFaultCount;
|
|
mActiveWorker->GetNavigationFaultCount(&navigationFaultCount);
|
|
const auto navigationFaultThreshold = StaticPrefs::
|
|
dom_serviceWorkers_mitigations_navigation_fault_threshold();
|
|
// Disable unregister mitigation when navigation fault threshold is 0.
|
|
if (navigationFaultThreshold <= navigationFaultCount &&
|
|
navigationFaultThreshold != 0) {
|
|
CheckQuotaUsage();
|
|
swm->Unregister(mPrincipal, nullptr, NS_ConvertUTF8toUTF16(Scope()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
mUpdateState = NeedUpdate;
|
|
|
|
swm->ScheduleUpdateTimer(mPrincipal, Scope());
|
|
}
|
|
|
|
bool ServiceWorkerRegistrationInfo::CheckAndClearIfUpdateNeeded() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
bool result =
|
|
mUpdateState == NeedUpdate || (mUpdateState == NeedTimeCheckAndUpdate &&
|
|
IsLastUpdateCheckTimeOverOneDay());
|
|
|
|
mUpdateState = NoUpdate;
|
|
|
|
return result;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetEvaluating() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mEvaluatingWorker;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetInstalling() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mInstallingWorker;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetWaiting() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mWaitingWorker;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetActive() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mActiveWorker;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetByDescriptor(
|
|
const ServiceWorkerDescriptor& aDescriptor) const {
|
|
if (mActiveWorker && mActiveWorker->Descriptor().Matches(aDescriptor)) {
|
|
return mActiveWorker;
|
|
}
|
|
if (mWaitingWorker && mWaitingWorker->Descriptor().Matches(aDescriptor)) {
|
|
return mWaitingWorker;
|
|
}
|
|
if (mInstallingWorker &&
|
|
mInstallingWorker->Descriptor().Matches(aDescriptor)) {
|
|
return mInstallingWorker;
|
|
}
|
|
if (mEvaluatingWorker &&
|
|
mEvaluatingWorker->Descriptor().Matches(aDescriptor)) {
|
|
return mEvaluatingWorker;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ServiceWorkerInfo* ServiceWorkerRegistrationInfo::GetByClientInfo(
|
|
const ClientInfo& aClientInfo) const {
|
|
if (mActiveWorker) {
|
|
auto clientRef = mActiveWorker->GetClientInfo();
|
|
if (clientRef && *clientRef == aClientInfo) {
|
|
return mActiveWorker;
|
|
}
|
|
}
|
|
if (mWaitingWorker) {
|
|
auto clientRef = mWaitingWorker->GetClientInfo();
|
|
if (clientRef && *clientRef == aClientInfo) {
|
|
return mWaitingWorker;
|
|
}
|
|
}
|
|
if (mInstallingWorker) {
|
|
auto clientRef = mInstallingWorker->GetClientInfo();
|
|
if (clientRef && *clientRef == aClientInfo) {
|
|
return mInstallingWorker;
|
|
}
|
|
}
|
|
if (mEvaluatingWorker) {
|
|
auto clientRef = mEvaluatingWorker->GetClientInfo();
|
|
if (clientRef && *clientRef == aClientInfo) {
|
|
return mEvaluatingWorker;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetEvaluating(
|
|
ServiceWorkerInfo* aServiceWorker) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aServiceWorker);
|
|
MOZ_ASSERT(!mEvaluatingWorker);
|
|
MOZ_ASSERT(!mInstallingWorker);
|
|
MOZ_ASSERT(mWaitingWorker != aServiceWorker);
|
|
MOZ_ASSERT(mActiveWorker != aServiceWorker);
|
|
|
|
mEvaluatingWorker = aServiceWorker;
|
|
|
|
// We don't call UpdateRegistrationState() here because the evaluating worker
|
|
// is currently not exposed to content on the registration, so calling it here
|
|
// would produce redundant IPC traffic.
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::ClearEvaluating() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mEvaluatingWorker) {
|
|
return;
|
|
}
|
|
|
|
mEvaluatingWorker->UpdateState(ServiceWorkerState::Redundant);
|
|
// We don't update the redundant time for the sw here, since we've not expose
|
|
// evalutingWorker yet.
|
|
mEvaluatingWorker = nullptr;
|
|
|
|
// As for SetEvaluating, UpdateRegistrationState() does not need to be called.
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::ClearInstalling() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mInstallingWorker) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<ServiceWorkerInfo> installing = std::move(mInstallingWorker);
|
|
installing->UpdateState(ServiceWorkerState::Redundant);
|
|
installing->UpdateRedundantTime();
|
|
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::TransitionEvaluatingToInstalling() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mEvaluatingWorker);
|
|
MOZ_ASSERT(!mInstallingWorker);
|
|
|
|
mInstallingWorker = std::move(mEvaluatingWorker);
|
|
mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
|
|
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::TransitionInstallingToWaiting() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mInstallingWorker);
|
|
|
|
if (mWaitingWorker) {
|
|
MOZ_ASSERT(mInstallingWorker->CacheName() != mWaitingWorker->CacheName());
|
|
mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
|
|
mWaitingWorker->UpdateRedundantTime();
|
|
}
|
|
|
|
mWaitingWorker = std::move(mInstallingWorker);
|
|
mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
|
|
mWaitingWorker->UpdateInstalledTime();
|
|
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
|
|
// TODO: When bug 1426401 is implemented we will need to call
|
|
// StoreRegistration() here to persist the waiting worker.
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetActive(
|
|
ServiceWorkerInfo* aServiceWorker) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aServiceWorker);
|
|
|
|
// TODO: Assert installing, waiting, and active are nullptr once the SWM
|
|
// moves to the parent process. After that happens this code will
|
|
// only run for browser initialization and not for cross-process
|
|
// overrides.
|
|
MOZ_ASSERT(mInstallingWorker != aServiceWorker);
|
|
MOZ_ASSERT(mWaitingWorker != aServiceWorker);
|
|
MOZ_ASSERT(mActiveWorker != aServiceWorker);
|
|
|
|
if (mActiveWorker) {
|
|
MOZ_ASSERT(aServiceWorker->CacheName() != mActiveWorker->CacheName());
|
|
mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
|
|
mActiveWorker->UpdateRedundantTime();
|
|
}
|
|
|
|
// The active worker is being overriden due to initial load or
|
|
// another process activating a worker. Move straight to the
|
|
// Activated state.
|
|
mActiveWorker = aServiceWorker;
|
|
mActiveWorker->SetActivateStateUncheckedWithoutEvent(
|
|
ServiceWorkerState::Activated);
|
|
|
|
// We don't need to update activated time when we load registration from
|
|
// registrar.
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::TransitionWaitingToActive() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mWaitingWorker);
|
|
|
|
if (mActiveWorker) {
|
|
MOZ_ASSERT(mWaitingWorker->CacheName() != mActiveWorker->CacheName());
|
|
mActiveWorker->UpdateState(ServiceWorkerState::Redundant);
|
|
mActiveWorker->UpdateRedundantTime();
|
|
}
|
|
|
|
// We are transitioning from waiting to active normally, so go to
|
|
// the activating state.
|
|
mActiveWorker = std::move(mWaitingWorker);
|
|
mActiveWorker->UpdateState(ServiceWorkerState::Activating);
|
|
|
|
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
|
"ServiceWorkerRegistrationInfo::TransitionWaitingToActive", [] {
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (swm) {
|
|
swm->CheckPendingReadyPromises();
|
|
}
|
|
});
|
|
MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget()));
|
|
UpdateRegistrationState();
|
|
NotifyChromeRegistrationListeners();
|
|
}
|
|
|
|
bool ServiceWorkerRegistrationInfo::IsIdle() const {
|
|
return !mActiveWorker || mActiveWorker->WorkerPrivate()->IsIdle();
|
|
}
|
|
|
|
ServiceWorkerUpdateViaCache ServiceWorkerRegistrationInfo::GetUpdateViaCache()
|
|
const {
|
|
return mDescriptor.UpdateViaCache();
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetUpdateViaCache(
|
|
ServiceWorkerUpdateViaCache aUpdateViaCache) {
|
|
UpdateRegistrationState(aUpdateViaCache);
|
|
}
|
|
|
|
int64_t ServiceWorkerRegistrationInfo::GetLastUpdateTime() const {
|
|
return mLastUpdateTime;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetLastUpdateTime(const int64_t aTime) {
|
|
if (aTime == 0) {
|
|
return;
|
|
}
|
|
|
|
mLastUpdateTime = aTime;
|
|
}
|
|
|
|
const ServiceWorkerRegistrationDescriptor&
|
|
ServiceWorkerRegistrationInfo::Descriptor() const {
|
|
return mDescriptor;
|
|
}
|
|
|
|
uint64_t ServiceWorkerRegistrationInfo::Id() const { return mDescriptor.Id(); }
|
|
|
|
uint64_t ServiceWorkerRegistrationInfo::Version() const {
|
|
return mDescriptor.Version();
|
|
}
|
|
|
|
uint32_t ServiceWorkerRegistrationInfo::GetUpdateDelay(
|
|
const bool aWithMultiplier) {
|
|
uint32_t delay = Preferences::GetInt("dom.serviceWorkers.update_delay", 1000);
|
|
|
|
if (!aWithMultiplier) {
|
|
return delay;
|
|
}
|
|
|
|
// This can potentially happen if you spam registration->Update(). We don't
|
|
// want to wrap to a lower value.
|
|
if (mDelayMultiplier >= INT_MAX / (delay ? delay : 1)) {
|
|
return INT_MAX;
|
|
}
|
|
|
|
delay *= mDelayMultiplier;
|
|
|
|
if (!mControlledClientsCounter && mDelayMultiplier < (INT_MAX / 30)) {
|
|
mDelayMultiplier = (mDelayMultiplier ? mDelayMultiplier : 1) * 30;
|
|
}
|
|
|
|
return delay;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::FireUpdateFound() {
|
|
for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget :
|
|
mInstanceList.ForwardRange()) {
|
|
pinnedTarget->FireUpdateFound();
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::NotifyCleared() {
|
|
for (RefPtr<ServiceWorkerRegistrationListener> pinnedTarget :
|
|
mInstanceList.ForwardRange()) {
|
|
pinnedTarget->RegistrationCleared();
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::ClearWhenIdle() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(IsUnregistered());
|
|
MOZ_ASSERT(!IsControllingClients());
|
|
MOZ_ASSERT(!IsIdle(), "Already idle!");
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
swm->AddOrphanedRegistration(this);
|
|
|
|
/**
|
|
* Although a Service Worker will transition to idle many times during its
|
|
* lifetime, the promise is only resolved once `GetIdlePromise` has been
|
|
* called, populating the `MozPromiseHolder`. Additionally, this is the only
|
|
* time this method will be called for the given ServiceWorker. This means we
|
|
* will be notified to the transition we are interested in, and there are no
|
|
* other callers to get confused.
|
|
*
|
|
* Note that because we are using `MozPromise`, our callback will be invoked
|
|
* as a separate task, so there is a small potential for races in the event
|
|
* code if things are still holding onto the ServiceWorker binding and using
|
|
* `postMessage()` or other mechanisms to schedule new events on it, which
|
|
* would make it non-idle. However, this is a race inherent in the spec which
|
|
* does not deal with the reality of multiple threads in "Try Clear
|
|
* Registration".
|
|
*/
|
|
GetActive()->WorkerPrivate()->GetIdlePromise()->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self = RefPtr<ServiceWorkerRegistrationInfo>(this)](
|
|
const GenericPromise::ResolveOrRejectValue& aResult) {
|
|
MOZ_ASSERT(aResult.IsResolve());
|
|
// This registration was already unregistered and not controlling
|
|
// clients when `ClearWhenIdle` was called, so there should be no way
|
|
// that more clients were acquired.
|
|
MOZ_ASSERT(!self->IsControllingClients());
|
|
MOZ_ASSERT(self->IsIdle());
|
|
self->Clear();
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
if (swm) {
|
|
swm->RemoveOrphanedRegistration(self);
|
|
}
|
|
});
|
|
}
|
|
|
|
const nsID& ServiceWorkerRegistrationInfo::AgentClusterId() const {
|
|
return mAgentClusterId;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetNavigationPreloadEnabled(
|
|
const bool& aEnabled) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mNavigationPreloadState.enabled() = aEnabled;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::SetNavigationPreloadHeader(
|
|
const nsCString& aHeader) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mNavigationPreloadState.headerValue() = aHeader;
|
|
}
|
|
|
|
IPCNavigationPreloadState
|
|
ServiceWorkerRegistrationInfo::GetNavigationPreloadState() const {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
return mNavigationPreloadState;
|
|
}
|
|
|
|
// static
|
|
uint64_t ServiceWorkerRegistrationInfo::GetNextId() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
static uint64_t sNextId = 0;
|
|
return ++sNextId;
|
|
}
|
|
|
|
// static
|
|
uint64_t ServiceWorkerRegistrationInfo::GetNextVersion() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
static uint64_t sNextVersion = 0;
|
|
return ++sNextVersion;
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::ForEachWorker(
|
|
void (*aFunc)(RefPtr<ServiceWorkerInfo>&)) {
|
|
if (mEvaluatingWorker) {
|
|
aFunc(mEvaluatingWorker);
|
|
}
|
|
|
|
if (mInstallingWorker) {
|
|
aFunc(mInstallingWorker);
|
|
}
|
|
|
|
if (mWaitingWorker) {
|
|
aFunc(mWaitingWorker);
|
|
}
|
|
|
|
if (mActiveWorker) {
|
|
aFunc(mActiveWorker);
|
|
}
|
|
}
|
|
|
|
void ServiceWorkerRegistrationInfo::CheckQuotaUsage() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
|
|
MOZ_ASSERT(swm);
|
|
|
|
swm->CheckPrincipalQuotaUsage(mPrincipal, Scope());
|
|
}
|
|
|
|
} // namespace mozilla::dom
|