Bug 1425975 P7 Use the mControlledClients list to drive controller start and stop logic. r=asuth

This commit is contained in:
Ben Kelly 2018-01-05 12:10:21 -05:00
parent aaf7a2cb50
commit c67746c9b2
4 changed files with 129 additions and 81 deletions

View File

@ -155,6 +155,7 @@
#include "mozIAsyncFavicons.h"
#endif
#include "nsINetworkPredictor.h"
#include "nsIServiceWorkerManager.h"
// Editor-related
#include "nsIEditingSession.h"
@ -3459,13 +3460,27 @@ nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
return;
}
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
if (!swm) {
return;
}
// If the parent is controlled then propagate that controller to the
// initial about:blank client as well. This will set the controller
// in the ClientManagerService in the parent.
RefPtr<ClientHandle> handle =
ClientManager::CreateHandle(mInitialClientSource->Info(),
parentInner->EventTargetFor(TaskCategory::Other));
handle->Control(controller.ref());
//
// Note: If the registration is missing from the SWM we avoid setting
// the controller on the client. We can do this synchronously
// for now since SWM is in the child process. In the future
// when SWM is in the parent process we will probably have to
// always set the initial client source and then somehow clear
// it if we find the registration is acutally gone. Its also
// possible this race only occurs in cases where the resulting
// window is no longer exposed. For example, in theory the SW
// should not go away if our parent window is controlled.
if (!swm->StartControlling(mInitialClientSource->Info(), controller.ref())) {
return;
}
// Also mark the ClientSource as controlled directly in case script
// immediately accesses navigator.serviceWorker.controller.

View File

@ -15,6 +15,17 @@ interface nsIInterceptedChannel;
interface nsIPrincipal;
interface nsIRunnable;
interface nsIURI;
%{C++
namespace mozilla {
namespace dom {
class ClientInfo;
class ServiceWorkerDescriptor;
} // namespace dom
} // namespace mozilla
%}
[ref] native const_ClientInfoRef(const mozilla::dom::ClientInfo);
[ref] native const_ServiceWorkerDescriptorRef(const mozilla::dom::ServiceWorkerDescriptor);
[scriptable, uuid(52ee2c9d-ee87-4caf-9588-23ae77ff8798)]
interface nsIServiceWorkerUnregisterCallback : nsISupports
@ -150,6 +161,9 @@ interface nsIServiceWorkerManager : nsISupports
*/
[notxpcom,nostdcall] void MaybeStartControlling(in nsIDocument aDoc);
[notxpcom, nostdcall] bool StartControlling(in const_ClientInfoRef aClientInfo,
in const_ServiceWorkerDescriptorRef aServiceWorker);
/**
* Documents that have called MaybeStartControlling() should call this when
* they are destroyed. This function may be called multiple times, and is

View File

@ -313,29 +313,57 @@ ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar)
mActor = static_cast<ServiceWorkerManagerChild*>(actor);
}
void
RefPtr<GenericPromise>
ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
ServiceWorkerRegistrationInfo* aRegistrationInfo)
{
MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());
RefPtr<GenericPromise> ref;
const ServiceWorkerDescriptor& active =
aRegistrationInfo->GetActive()->Descriptor();
auto entry = mControlledClients.LookupForAdd(aClientInfo.Id());
if (entry) {
RefPtr<ServiceWorkerRegistrationInfo> old =
entry.Data()->mRegistrationInfo.forget();
ref = Move(entry.Data()->mClientHandle->Control(active));
entry.Data()->mRegistrationInfo = aRegistrationInfo;
if (old != aRegistrationInfo) {
StopControllingRegistration(old);
aRegistrationInfo->StartControllingClient();
}
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
return Move(ref);
}
RefPtr<ClientHandle> clientHandle =
ClientManager::CreateHandle(aClientInfo,
SystemGroup::EventTargetFor(TaskCategory::Other));
auto entry = mControlledClients.LookupForAdd(aClientInfo.Id());
if (entry) {
entry.Data()->mRegistrationInfo = aRegistrationInfo;
} else {
entry.OrInsert([&] {
return new ControlledClientData(clientHandle, aRegistrationInfo);
ref = Move(clientHandle->Control(active));
aRegistrationInfo->StartControllingClient();
entry.OrInsert([&] {
return new ControlledClientData(clientHandle, aRegistrationInfo);
});
RefPtr<ServiceWorkerManager> self(this);
clientHandle->OnDetach()->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[self = Move(self), aClientInfo] {
self->StopControllingClient(aClientInfo);
});
RefPtr<ServiceWorkerManager> self(this);
clientHandle->OnDetach()->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[self = Move(self), aClientInfo] {
self->StopControllingClient(aClientInfo);
});
}
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
return Move(ref);
}
void
@ -345,7 +373,13 @@ ServiceWorkerManager::StopControllingClient(const ClientInfo& aClientInfo)
if (!entry) {
return;
}
RefPtr<ServiceWorkerRegistrationInfo> reg =
entry.Data()->mRegistrationInfo.forget();
entry.Remove();
StopControllingRegistration(reg);
}
void
@ -2309,18 +2343,27 @@ ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo*
entry.Remove();
}
// Verify there are no controlled documents for the purged registration.
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
ServiceWorkerRegistrationInfo* reg = iter.UserData();
// 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->mScope.Equals(aRegistration->mScope)) {
MOZ_DIAGNOSTIC_ASSERT(false,
"controlled document when removing registration");
"controlled client when removing registration");
iter.Remove();
break;
}
}
// Registration lifecycle is managed via mControlledClients now. Do not
// assert on on mControlledDocuments as races may cause this to still be
// set when the registration is destroyed.
for (auto iter = swm->mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
ServiceWorkerRegistrationInfo* reg = iter.UserData();
if (reg->mScope.Equals(aRegistration->mScope)) {
iter.Remove();
break;
}
}
#endif
RefPtr<ServiceWorkerRegistrationInfo> info;
data->mInfos.Remove(aRegistration->mScope, getter_AddRefs(info));
@ -2349,25 +2392,43 @@ ServiceWorkerManager::MaybeStartControlling(nsIDocument* aDoc)
MOZ_ASSERT(aDoc);
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetServiceWorkerRegistrationInfo(aDoc);
if (registration) {
if (registration && registration->GetActive() &&
aDoc->GetSandboxFlags() == 0) {
MOZ_ASSERT(!mControlledDocuments.Contains(aDoc));
StartControllingADocument(registration, aDoc);
}
}
bool
ServiceWorkerManager::StartControlling(const ClientInfo& aClientInfo,
const ServiceWorkerDescriptor& aServiceWorker)
{
AssertIsOnMainThread();
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo());
NS_ENSURE_TRUE(principal, false);
nsCOMPtr<nsIURI> scope;
nsresult rv =
NS_NewURI(getter_AddRefs(scope), aServiceWorker.Scope(), nullptr, nullptr);
NS_ENSURE_SUCCESS(rv, false);
RefPtr<ServiceWorkerRegistrationInfo> registration =
GetServiceWorkerRegistrationInfo(principal, scope);
NS_ENSURE_TRUE(registration, false);
StartControllingClient(aClientInfo, registration);
return true;
}
void
ServiceWorkerManager::MaybeStopControlling(nsIDocument* aDoc)
{
AssertIsOnMainThread();
MOZ_ASSERT(aDoc);
RefPtr<ServiceWorkerRegistrationInfo> registration;
mControlledDocuments.Remove(aDoc, getter_AddRefs(registration));
// A document which was uncontrolled does not maintain that state itself, so
// it will always call MaybeStopControlling() even if there isn't an
// associated registration. So this check is required.
if (registration) {
StopControllingRegistration(registration);
}
mControlledDocuments.Remove(aDoc);
}
void
@ -2391,50 +2452,14 @@ ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
}
}
RefPtr<GenericPromise>
void
ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
nsIDocument* aDoc)
{
MOZ_ASSERT(aRegistration);
MOZ_ASSERT(aDoc);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
auto storageAllowed = nsContentUtils::StorageAllowedForDocument(aDoc);
MOZ_DIAGNOSTIC_ASSERT(storageAllowed == nsContentUtils::StorageAccess::eAllow);
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
RefPtr<GenericPromise> ref;
ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
if (NS_WARN_IF(!activeWorker)) {
ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
__func__);
return ref.forget();
}
Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
if (NS_WARN_IF(clientInfo.isNothing())) {
ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
__func__);
return ref.forget();
}
aRegistration->StartControllingClient();
mControlledDocuments.Put(aDoc, aRegistration);
StartControllingClient(clientInfo.ref(), aRegistration);
// Mark the document's ClientSource as controlled using the ClientHandle
// interface. While we could get at the ClientSource directly from the
// document here, our goal is to move ServiceWorkerManager to a separate
// process. Using the ClientHandle supports this remote operation.
RefPtr<ClientHandle> clientHandle =
ClientManager::CreateHandle(clientInfo.ref(),
SystemGroup::EventTargetFor(TaskCategory::Other));
ref = Move(clientHandle->Control(activeWorker->Descriptor()));
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
return Move(ref);
}
void
@ -2771,10 +2796,7 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut
// First, attempt to mark the reserved client controlled directly. This
// will update the controlled status in the ClientManagerService in the
// parent. It will also eventually propagate back to the ClientSource.
RefPtr<ClientHandle> clientHandle =
ClientManager::CreateHandle(clientInfo.ref(),
SystemGroup::EventTargetFor(TaskCategory::Other));
clientHandle->Control(serviceWorker->Descriptor());
StartControllingClient(clientInfo.ref(), registration);
}
// But we also note the reserved state on the LoadInfo. This allows the
@ -3245,11 +3267,8 @@ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
return ref.forget();
}
if (controllingRegistration) {
StopControllingRegistration(controllingRegistration);
}
ref = StartControllingADocument(aWorkerRegistration, aDocument);
StartControllingADocument(aWorkerRegistration, aDocument);
ref = StartControllingClient(clientInfo.ref(), aWorkerRegistration);
return ref.forget();
}

View File

@ -344,7 +344,7 @@ private:
void
Init(ServiceWorkerRegistrar* aRegistrar);
void
RefPtr<GenericPromise>
StartControllingClient(const ClientInfo& aClientInfo,
ServiceWorkerRegistrationInfo* aRegistrationInfo);
@ -398,7 +398,7 @@ private:
void
NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
RefPtr<GenericPromise>
void
StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
nsIDocument* aDoc);