/* -*- 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/. */
static_assert(
nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow), "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error), "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual), "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
3 == ContiguousEnumSize<RequestRedirect>::value, "RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT == static_cast<uint32_t>(RequestCache::Default), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE == static_cast<uint32_t>(RequestCache::No_store), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD == static_cast<uint32_t>(RequestCache::Reload), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE == static_cast<uint32_t>(RequestCache::No_cache), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE == static_cast<uint32_t>(RequestCache::Force_cache), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED == static_cast<uint32_t>(RequestCache::Only_if_cached), "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(
6 == ContiguousEnumSize<RequestCache>::value, "RequestCache enumeration value should match Necko Cache mode value.");
static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::Imports) ==
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS, "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*" " should match ServiceWorkerUpdateViaCache enumeration.");
static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::All) ==
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL, "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*" " should match ServiceWorkerUpdateViaCache enumeration.");
static_assert(static_cast<uint16_t>(ServiceWorkerUpdateViaCache::None) ==
nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_NONE, "nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_*" " should match ServiceWorkerUpdateViaCache enumeration.");
// TODO: When bug 1426401 is implemented we will need to handle more // than just the active worker here.
RefPtr<ServiceWorkerInfo> active = aRegistration->GetActive();
MOZ_ASSERT(active); if (NS_WARN_IF(!active)) { return NS_ERROR_FAILURE;
}
struct ServiceWorkerManager::RegistrationDataPerPrincipal final { // Implements a container of keys for the "scope to registration map": // https://w3c.github.io/ServiceWorker/#dfn-scope-to-registration-map // // where each key is an absolute URL. // // The properties of this map that the spec uses are // 1) insertion, // 2) removal, // 3) iteration of scopes in FIFO order (excluding removed scopes), // 4) and finding, for a given path, the maximal length scope which is a // prefix of the path. // // Additionally, because this is a container of keys for a map, there // shouldn't be duplicate scopes. // // The current implementation uses a dynamic array as the underlying // container, which is not optimal for unbounded container sizes (all // supported operations are in linear time) but may be superior for small // container sizes. // // If this is proven to be too slow, the underlying storage should be replaced // with a linked list of scopes in combination with an ordered map that maps // scopes to linked list elements/iterators. This would reduce all of the // above operations besides iteration (necessarily linear) to logarithmic // time. class ScopeContainer final : private nsTArray<nsCString> { using Base = nsTArray<nsCString>;
public: using Base::Contains; using Base::IsEmpty; using Base::Length;
// No using-declaration to avoid importing the non-const overload.
decltype(auto) operator[](Base::index_type aIndex) const { return Base::operator[](aIndex);
}
if (NS_WARN_IF(parseResult.isErr())) { returnfalse;
}
auto clientPrincipal = parseResult.unwrap();
bool equals = false;
if (NS_WARN_IF(
NS_FAILED(scopePrincipal->Equals(clientPrincipal, &equals)))) { returnfalse;
}
return equals;
}
};
ScopeContainer mScopeContainer;
// Scope to registration. // The scope should be a fully qualified valid URL.
nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
// Maps scopes to job queues.
nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
// Map scopes to scheduled update timers.
nsInterfaceHashtable<nsCStringHashKey, nsITimer> mUpdateTimers;
// The number of times we have done a quota usage check for this origin for // mitigation purposes. See the docs on nsIServiceWorkerRegistrationInfo, // where this value is exposed.
int32_t mQuotaUsageCheckCount = 0;
};
ServiceWorkerManager::~ServiceWorkerManager() { // The map will assert if it is not empty when destroyed.
mRegistrationInfos.Clear();
// This can happen if the browser is started up in ProfileManager mode, in // which case XPCOM will startup and shutdown, but there won't be any // profile-* topic notifications. The shutdown blocker expects to be in a // NotAcceptingPromises state when it's destroyed, and this transition // normally happens in the "profile-change-teardown" notification callback // (which won't be called in ProfileManager mode). if (!mShuttingDown && mShutdownBlocker) {
mShutdownBlocker->StopAcceptingPromises();
}
}
void ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar) { // ServiceWorkers now only support parent intercept. In parent intercept // mode, only the parent process ServiceWorkerManager has any state or does // anything. // // It is our goal to completely eliminate support for content process // ServiceWorkerManager instances and make getting a SWM instance trigger a // fatal assertion. But until we've reached that point, we make // initialization a no-op so that content process ServiceWorkerManager // instances will simply have no state and no registrations. if (!XRE_IsParentProcess()) { return;
}
// mActor must be set before LoadRegistrations is called because it can purge // service workers if preferences are disabled.
nsTArray<ServiceWorkerRegistrationData> data;
aRegistrar->GetRegistrations(data);
LoadRegistrations(data);
mTelemetryLastChange = TimeStamp::Now();
mETPPermissionObserver = new ETPPermissionObserver();
}
void ServiceWorkerManager::RecordTelemetry(uint32_t aNumber, uint32_t aFetch) { // Submit N value pairs to Telemetry for the time we were at those values auto now = TimeStamp::Now(); // round down, with a minimum of 1 repeat. In theory this gives // inaccuracy if there are frequent changes, but that's uncommon.
uint32_t repeats = (uint32_t)((now - mTelemetryLastChange).ToMilliseconds()) /
mTelemetryPeriodMs;
mTelemetryLastChange = now; if (repeats == 0) {
repeats = 1;
}
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( "ServiceWorkerTelemetryRunnable", [aNumber, aFetch, repeats]() {
LOG(("ServiceWorkers running: %u samples of %u/%u", repeats, aNumber,
aFetch)); // Don't allocate infinitely huge arrays if someone visits a SW site // after a few months running. 1 month is about 500K repeats @ 5s // sampling
uint32_t num_repeats = std::min(repeats, 1000000U); // 4MB max
nsTArray<uint32_t> values;
uint32_t* array = values.AppendElements(num_repeats); for (uint32_t i = 0; i < num_repeats; i++) {
array[i] = aNumber;
}
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "All"_ns,
values);
for (uint32_t i = 0; i < num_repeats; i++) {
array[i] = aFetch;
}
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_RUNNING, "Fetch"_ns,
values);
});
NS_DispatchBackgroundTask(runnable.forget(), nsIEventTarget::DISPATCH_NORMAL);
}
// XXX We can't use a generic lambda (accepting auto&& entry) like elsewhere // with WithEntryHandle, since we get linker errors then using clang+lld. This // might be a toolchain issue? return mControlledClients.WithEntryHandle(
aClientInfo.Id(),
[&](decltype(mControlledClients)::EntryHandle&& entry)
-> RefPtr<GenericErrorResultPromise> { const RefPtr<ServiceWorkerManager> self = this;
const ServiceWorkerDescriptor& active =
aRegistrationInfo->GetActive()->Descriptor();
if (entry) { const RefPtr<ServiceWorkerRegistrationInfo> old =
std::move(entry.Data()->mRegistrationInfo);
if (old != aRegistrationInfo) {
StopControllingRegistration(old);
aRegistrationInfo->StartControllingClient();
}
// Always check to see if we failed to actually control the client. In // that case remove the client from our list of controlled clients. return promise->Then(
GetMainThreadSerialEventTarget(), __func__,
[](bool) { // do nothing on success return GenericErrorResultPromise::CreateAndResolve(true,
__func__);
},
[self, aClientInfo](const CopyableErrorResult& aRv) { // failed to control, forget about this client
self->StopControllingClient(aClientInfo); return GenericErrorResultPromise::CreateAndReject(aRv,
__func__);
});
}
// Always check to see if we failed to actually control the client. In // that case removed the client from our list of controlled clients. return promise->Then(
GetMainThreadSerialEventTarget(), __func__,
[](bool) { // do nothing on success return GenericErrorResultPromise::CreateAndResolve(true,
__func__);
},
[self, aClientInfo](const CopyableErrorResult& aRv) { // failed to control, forget about this client
self->StopControllingClient(aClientInfo); return GenericErrorResultPromise::CreateAndReject(aRv, __func__);
});
});
}
void ServiceWorkerManager::StopControllingClient( const ClientInfo& aClientInfo) { auto entry = mControlledClients.Lookup(aClientInfo.Id()); if (!entry) { return;
}
// This also submits final telemetry
ServiceWorkerPrivate::RunningShutdown();
}
class ServiceWorkerResolveWindowPromiseOnRegisterCallback final
: public ServiceWorkerJob::Callback { public:
NS_INLINE_DECL_REFCOUNTING(
ServiceWorkerResolveWindowPromiseOnRegisterCallback, override)
if (aScriptURL.IsEmpty()) {
outer->MaybeRejectWithAbortError("Missing script url");
outer.forget(aPromise); return NS_OK;
}
if (aScopeURL.IsEmpty()) {
outer->MaybeRejectWithAbortError("Missing scope url");
outer.forget(aPromise); return NS_OK;
}
// The ClientType isn't really used here, but ClientType::Window // is the least bad choice since this is happening on the main thread.
Maybe<ClientInfo> clientInfo =
dom::ClientManager::CreateInfo(ClientType::Window, aPrincipal);
if (!clientInfo.isSome()) {
outer->MaybeRejectWithUnknownError("Error creating clientInfo");
outer.forget(aPromise); return NS_OK;
}
auto scope = NS_ConvertUTF16toUTF8(aScopeURL); auto scriptURL = NS_ConvertUTF16toUTF8(aScriptURL);
nsCOMPtr<nsIURI> scopeURI;
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScopeURL); if (NS_FAILED(rv)) { // Odd, since it was serialiazed from an nsIURI.
CopyableErrorResult err;
err.ThrowInvalidStateError("Scope URL cannot be parsed"); return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
}
nsCOMPtr<nsIURI> scriptURI;
rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL); if (NS_FAILED(rv)) { // Odd, since it was serialiazed from an nsIURI.
CopyableErrorResult err;
err.ThrowInvalidStateError("Script URL cannot be parsed"); return ServiceWorkerRegistrationPromise::CreateAndReject(err, __func__);
}
/* * Implements the async aspects of the getRegistrations algorithm.
*/ class GetRegistrationsRunnable final : public Runnable { const ClientInfo mClientInfo;
RefPtr<ServiceWorkerRegistrationListPromise::Private> mPromise;
for (uint32_t i = 0; i < data->mScopeContainer.Length(); ++i) {
RefPtr<ServiceWorkerRegistrationInfo> info =
data->mInfos.GetWeak(data->mScopeContainer[i]);
// Unfortunately we don't seem to have an obvious window id here; in // particular ClientInfo does not have one, and neither do service worker // registrations, as far as I can tell.
rv = principal->CheckMayLoadWithReporting(
scopeURI, false/* allowIfInheritsPrincipal */,
0 /* innerWindowID */); if (NS_WARN_IF(NS_FAILED(rv))) { continue;
}
/* * Implements the async aspects of the getRegistration algorithm.
*/ class GetRegistrationRunnable final : public Runnable { const ClientInfo mClientInfo;
RefPtr<ServiceWorkerRegistrationPromise::Private> mPromise;
nsCString mURL;
auto principalOrErr = mClientInfo.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) {
mPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL); if (NS_WARN_IF(NS_FAILED(rv))) {
mPromise->Reject(rv, __func__); return NS_OK;
}
// Unfortunately we don't seem to have an obvious window id here; in // particular ClientInfo does not have one, and neither do service worker // registrations, as far as I can tell.
rv = principal->CheckMayLoadWithReporting(
uri, false/* allowIfInheritsPrincipal */, 0 /* innerWindowID */); if (NS_FAILED(rv)) {
mPromise->Reject(NS_ERROR_DOM_SECURITY_ERR, __func__); return NS_OK;
}
RefPtr<GetRegistrationRunnable> runnable = new GetRegistrationRunnable(aClientInfo, aURL);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(runnable));
return runnable->Promise();
}
NS_IMETHODIMP
ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes, const nsACString& aScope, const nsTArray<uint8_t>& aDataBytes,
uint8_t optional_argc) { if (optional_argc == 1) { // This does one copy here (while constructing the Maybe) and another when // we end up copying into the SendPushEventRunnable. We could fix that to // only do one copy by making things between here and there take // Maybe<nsTArray<uint8_t>>&&, but then we'd need to copy before we know // whether we really need to in PushMessageDispatcher::NotifyWorkers. Since // in practice this only affects JS callers that pass data, and we don't // have any right now, let's not worry about it. return SendPushEvent(aOriginAttributes, aScope, u""_ns,
Some(aDataBytes.Clone()));
}
MOZ_ASSERT(optional_argc == 0); return SendPushEvent(aOriginAttributes, aScope, u""_ns, Nothing());
}
// The registration handling a push notification must have an exact scope // match. This will try to find an exact match, unlike how fetch may find the // registration with the longest scope that's a prefix of the fetched URL.
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetRegistration(principal, aScope); if (NS_WARN_IF(!registration)) { return NS_ERROR_FAILURE;
}
RegistrationDataPerPrincipal* data; if (!mRegistrationInfos.Get(scopeKey, &data)) { return nullptr;
}
// If the ClientInfo has CSP data populated, we need to normalize that off, so // make a copy and only propagate the non-CSP fields.
ClientInfo normalized = ClientInfo(
aClientInfo.Id(), aClientInfo.AgentClusterId(), aClientInfo.Type(),
aClientInfo.PrincipalInfo(), aClientInfo.CreationTime(),
aClientInfo.URL(), aClientInfo.FrameType());
for (constauto& registration : data->mInfos.Values()) {
ServiceWorkerInfo* info = registration->GetByClientInfo(normalized); if (info) { return info;
}
}
return nullptr;
}
ServiceWorkerInfo* ServiceWorkerManager::GetServiceWorkerByDescriptor( const ServiceWorkerDescriptor& aServiceWorker) const { auto principalOrErr = aServiceWorker.GetPrincipal(); if (NS_WARN_IF(principalOrErr.isErr())) { return nullptr;
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
// This is not accessible by content, and callers should always ensure scope is // a correct URI, so this is wrapped in DEBUG #ifdef DEBUG
nsCOMPtr<nsIURI> scopeURI;
rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_SECURITY_ERR;
} #endif
RefPtr<ServiceWorkerRegistrationInfo> reg =
GetRegistration(aWorker->Principal(), aWorker->Scope()); if (!reg) { return;
}
// We only care about the active worker becoming idle because it means we // can now promote a waiting worker to active. if (reg->GetActive() != aWorker) { return;
}
// The active worker becoming idle is not a reason to extend a waiting SW's // lifetime (or spawn it) on its own because that potentially enables // unlimited lifetime extension. `TryToActivate` will handle upgrading the // lifetime extension to a full extension if the registration is controlling // pages (and skipWaiting was used). It's fine for the ServiceWorker to // receive its activation message prior to its next functional event.
reg->TryToActivateAsync(
ServiceWorkerLifetimeExtension(NoLifetimeExtension{}));
}
already_AddRefed<ServiceWorkerJobQueue>
ServiceWorkerManager::GetOrCreateJobQueue(const nsACString& aKey, const nsACString& aScope) {
MOZ_ASSERT(!aKey.IsEmpty());
ServiceWorkerManager::RegistrationDataPerPrincipal* data; // XXX we could use WithEntryHandle here to avoid a hashtable lookup, except // that leads to a false positive assertion, see bug 1370674 comment 7. if (!mRegistrationInfos.Get(aKey, &data)) {
data = mRegistrationInfos
.InsertOrUpdate(aKey, MakeUnique<RegistrationDataPerPrincipal>())
.get();
}
// XXX: Substitute this with an assertion. See comment in Init. if (XRE_IsParentProcess()) { // Don't (re-)create the ServiceWorkerManager if we are already shutting // down. if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { return nullptr;
} // Don't create the ServiceWorkerManager until the ServiceWorkerRegistrar // is initialized.
swr = ServiceWorkerRegistrar::Get(); if (!swr) { return nullptr;
}
}
ServiceWorkerManager::RegistrationDataPerPrincipal* data; if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) { return;
}
// Always report any uncaught exceptions or errors to the console of // each client.
ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
aColumnNumber, aFlags);
}
auto principalOrErr = PrincipalInfoToPrincipal(aRegistration.principal()); if (NS_WARN_IF(principalOrErr.isErr())) { return;
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
if (!StaticPrefs::dom_serviceWorkers_enabled()) { // If service workers are disabled, remove the registration from disk // instead of loading.
PurgeServiceWorker(aRegistration, principal); return;
}
// Purge extensions registrations if they are disabled by prefs. if (!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
nsCOMPtr<nsIURI> uri = principal->GetURI();
// We do check the URI scheme here because when this is going to run // the extension may not have been loaded yet and the WebExtensionPolicy // may not exist yet. if (uri->SchemeIs("moz-extension")) {
PurgeServiceWorker(aRegistration, principal); return;
}
}
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetRegistration(principal, aRegistration.scope()); if (!registration) {
registration =
CreateNewRegistration(aRegistration.scope(), principal, static_cast<ServiceWorkerUpdateViaCache>(
aRegistration.updateViaCache()),
aRegistration.navigationPreloadState());
} else { // If active worker script matches our expectations for a "current worker", // then we are done. Since scripts with the same URL might have different // contents such as updated scripts or scripts with different LoadFlags, we // use the CacheName to judge whether the two scripts are identical, where // the CacheName is an UUID generated when a new script is found. if (registration->GetActive() &&
registration->GetActive()->CacheName() == aRegistration.cacheName()) { // No needs for updates. return;
}
}
// Do not store a registration for addons that are not installed, not enabled // or installed temporarily. // // If the dom.serviceWorkers.testing.persistTemporaryInstalledAddons is set // to true, the registration for a temporary installed addon will still be // persisted (only meant to be used to make it easier to test some particular // scenario with a temporary installed addon which doesn't need to be signed // to be installed on release channel builds). if (aPrincipal->SchemeIs("moz-extension")) {
RefPtr<extensions::WebExtensionPolicy> addonPolicy =
BasePrincipal::Cast(aPrincipal)->AddonPolicy(); if (!addonPolicy || !addonPolicy->Active() ||
(addonPolicy->TemporarilyInstalled() &&
!StaticPrefs::
dom_serviceWorkers_testing_persistTemporarilyInstalledAddons())) { return;
}
}
RefPtr<ServiceWorkerRegistrationInfo> registration;
data->mInfos.Get(scope, getter_AddRefs(registration)); // ordered scopes and registrations better be in sync.
MOZ_ASSERT(registration);
if (scope) { // scope.isSome() will still truen true after this; we are just moving the // string inside the Maybe, so the Maybe will contain an empty string.
aMatch = std::move(*scope);
}
RegistrationDataPerPrincipal* data; if (!swm->mRegistrationInfos.Get(scopeKey, &data)) { return;
}
if (auto entry = data->mUpdateTimers.Lookup(aRegistration->Scope())) {
entry.Data()->Cancel();
entry.Remove();
}
// Verify there are no controlled clients for the purged registration. for (auto iter = swm->mControlledClients.Iter(); !iter.Done(); iter.Next()) { auto& reg = iter.UserData()->mRegistrationInfo; if (reg->Scope().Equals(aRegistration->Scope()) &&
reg->Principal()->Equals(aRegistration->Principal()) &&
reg->IsCorrupt()) {
iter.Remove();
}
}
void ServiceWorkerManager::MaybeRemoveRegistrationInfo( const nsACString& aScopeKey) { if (auto entry = mRegistrationInfos.Lookup(aScopeKey)) { if (entry.Data()->mScopeContainer.IsEmpty() &&
entry.Data()->mJobQueues.Count() == 0) {
entry.Remove();
// Need to reset the mQuotaUsageCheckCount, if // RegistrationDataPerPrincipal:: mScopeContainer is empty. This // RegistrationDataPerPrincipal might be reused, such that quota usage // mitigation can be triggered for the new added registration.
} elseif (entry.Data()->mScopeContainer.IsEmpty() &&
entry.Data()->mQuotaUsageCheckCount) {
entry.Data()->mQuotaUsageCheckCount = 0;
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.