Merge mozilla-central to autoland. r=merge a=merge on a CLOSED TREE

This commit is contained in:
Cosmin Sabou 2017-12-23 11:48:25 +02:00
Родитель 9abacb8834 e6fd22c374
Коммит 60c29d9778
22 изменённых файлов: 425 добавлений и 162 удалений

Просмотреть файл

@ -155,6 +155,7 @@
#include "mozIAsyncFavicons.h"
#endif
#include "nsINetworkPredictor.h"
#include "nsIServiceWorkerManager.h"
// Editor-related
#include "nsIEditingSession.h"
@ -3417,7 +3418,7 @@ nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
// Don't pre-allocate the client when we are sandboxed. The inherited
// principal does not take sandboxing into account.
// TODO: Refactor sandboxing principal code out so we can use it here.
if (!aPrincipal && (mSandboxFlags & SANDBOXED_ORIGIN)) {
if (!aPrincipal && mSandboxFlags) {
return;
}
@ -3459,18 +3460,35 @@ nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal)
return;
}
// We're done if there is no parent controller. Also, don't inherit
// the controller if we're sandboxed. This matches our behavior in
// ShouldPrepareForIntercept(),
Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
if (controller.isNothing()) {
if (controller.isNothing() || mSandboxFlags) {
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.

Просмотреть файл

@ -5599,7 +5599,10 @@ nsDocument::DispatchContentLoadedEvents()
using mozilla::dom::workers::ServiceWorkerManager;
RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (swm) {
swm->MaybeCheckNavigationUpdate(this);
Maybe<ClientInfo> clientInfo = GetClientInfo();
if (clientInfo.isSome()) {
swm->MaybeCheckNavigationUpdate(clientInfo.ref());
}
}
}

Просмотреть файл

@ -1786,11 +1786,34 @@ nsGlobalWindowInner::EnsureClientSource()
}
}
// Verify the final ClientSource principal matches the final document
// principal. The ClientChannelHelper handles things like network
// redirects, but there are other ways the document principal can change.
// For example, if something sets the nsIChannel.owner property, then
// the final channel principal can be anything. Unfortunately there is
// no good way to detect this until after the channel completes loading.
//
// For now we handle this just by reseting the ClientSource. This will
// result in a new ClientSource with the correct principal being created.
// To APIs like ServiceWorker and Clients API it will look like there was
// an initial content page created that was then immediately replaced.
// This is pretty close to what we are actually doing.
if (mClientSource) {
nsCOMPtr<nsIPrincipal> clientPrincipal(mClientSource->Info().GetPrincipal());
if (!clientPrincipal || !clientPrincipal->Equals(mDoc->NodePrincipal())) {
mClientSource.reset();
}
}
// If we don't have a reserved client or an initial client, then create
// one now. This can happen in certain cases where we avoid preallocating
// the client in the docshell. This mainly occurs in situations where
// the principal is not clearly inherited from the parent; e.g. sandboxed
// iframes, window.open(), etc.
//
// We also do this late ClientSource creation if the final document ended
// up with a different principal.
//
// TODO: We may not be marking initial about:blank documents created
// this way as controlled by a service worker properly. The
// controller should be coming from the same place as the inheritted
@ -1804,13 +1827,23 @@ nsGlobalWindowInner::EnsureClientSource()
mDoc->NodePrincipal());
MOZ_DIAGNOSTIC_ASSERT(mClientSource);
newClientSource = true;
// Note, we don't apply the loadinfo controller below if we create
// the ClientSource here.
}
// The load may have started controlling the Client as well. If
// so, mark it as controlled immediately here. The actor may
// or may not have been notified by the parent side about being
// controlled yet.
if (loadInfo) {
//
// Note: We should be careful not to control a client that was created late.
// These clients were not seen by the ServiceWorkerManager when it
// marked the LoadInfo controlled and it won't know about them. Its
// also possible we are creating the client late due to the final
// principal changing and these clients should definitely not be
// controlled by a service worker with a different principal.
else if (loadInfo) {
const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
if (controller.isSome()) {
mClientSource->SetController(controller.ref());

Просмотреть файл

@ -65,6 +65,16 @@ ClientHandle::StartOp(const ClientOpConstructorArgs& aArgs)
return ref.forget();
}
void
ClientHandle::OnShutdownThing()
{
NS_ASSERT_OWNINGTHREAD(ClientHandle);
if (!mDetachPromise) {
return;
}
mDetachPromise->Resolve(true, __func__);
}
ClientHandle::ClientHandle(ClientManager* aManager,
nsISerialEventTarget* aSerialEventTarget,
const ClientInfo& aClientInfo)
@ -182,5 +192,21 @@ ClientHandle::PostMessage(StructuredCloneData& aData,
return ref.forget();
}
RefPtr<GenericPromise>
ClientHandle::OnDetach()
{
NS_ASSERT_OWNINGTHREAD(ClientSource);
if (!mDetachPromise) {
mDetachPromise = new GenericPromise::Private(__func__);
if (IsShutdown()) {
mDetachPromise->Resolve(true, __func__);
}
}
RefPtr<GenericPromise> ref(mDetachPromise);
return Move(ref);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -42,6 +42,7 @@ class ClientHandle final : public ClientThing<ClientHandleChild>
RefPtr<ClientManager> mManager;
nsCOMPtr<nsISerialEventTarget> mSerialEventTarget;
RefPtr<GenericPromise::Private> mDetachPromise;
ClientInfo mClientInfo;
~ClientHandle();
@ -52,6 +53,10 @@ class ClientHandle final : public ClientThing<ClientHandleChild>
already_AddRefed<ClientOpPromise>
StartOp(const ClientOpConstructorArgs& aArgs);
// ClientThing interface
void
OnShutdownThing() override;
// Private methods called by ClientHandleChild
void
ExecutionReady(const ClientInfo& aClientInfo);
@ -90,6 +95,17 @@ public:
PostMessage(ipc::StructuredCloneData& aData,
const ServiceWorkerDescriptor& aSource);
// Return a Promise that resolves when the ClientHandle object is detached
// from its remote actors. This will happen if the ClientSource is destroyed
// and triggers the cleanup of the handle actors. It will also naturally
// happen when the ClientHandle is de-referenced and tears down its own
// actors.
//
// Note: This method can only be called on the ClientHandle owning thread,
// but the MozPromise lets you Then() to another thread.
RefPtr<GenericPromise>
OnDetach();
NS_INLINE_DECL_REFCOUNTING(ClientHandle);
};

Просмотреть файл

@ -7,11 +7,13 @@
#include "ClientInfo.h"
#include "mozilla/dom/ClientIPCTypes.h"
#include "mozilla/ipc/BackgroundUtils.h"
namespace mozilla {
namespace dom {
using mozilla::ipc::PrincipalInfo;
using mozilla::ipc::PrincipalInfoToPrincipal;
ClientInfo::ClientInfo(const nsID& aId,
ClientType aType,
@ -138,5 +140,13 @@ ClientInfo::IsPrivateBrowsing() const
}
}
nsCOMPtr<nsIPrincipal>
ClientInfo::GetPrincipal() const
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIPrincipal> ref = PrincipalInfoToPrincipal(PrincipalInfo());
return Move(ref);
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -95,6 +95,11 @@ public:
// Determine if the client is in private browsing mode.
bool
IsPrivateBrowsing() const;
// Get a main-thread nsIPrincipal for the client. This may return nullptr
// if the PrincipalInfo() fails to deserialize for some reason.
nsCOMPtr<nsIPrincipal>
GetPrincipal() const;
};
} // namespace dom

Просмотреть файл

@ -206,6 +206,14 @@ ClientSource::WorkerExecutionReady(WorkerPrivate* aWorkerPrivate)
return;
}
// A client without access to storage should never be controlled by
// a service worker. Check this here in case we were controlled before
// execution ready. We can't reliably determine what our storage policy
// is before execution ready, unfortunately.
if (mController.isSome()) {
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate->IsStorageAllowed());
}
// Its safe to store the WorkerPrivate* here because the ClientSource
// is explicitly destroyed by WorkerPrivate before exiting its run loop.
MOZ_DIAGNOSTIC_ASSERT(mOwner.is<Nothing>());
@ -235,6 +243,15 @@ ClientSource::WindowExecutionReady(nsPIDOMWindowInner* aInnerWindow)
return NS_ERROR_UNEXPECTED;
}
// A client without access to storage should never be controlled by
// a service worker. Check this here in case we were controlled before
// execution ready. We can't reliably determine what our storage policy
// is before execution ready, unfortunately.
if (mController.isSome()) {
MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(aInnerWindow) ==
nsContentUtils::StorageAccess::eAllow);
}
// Don't use nsAutoCString here since IPC requires a full nsCString anyway.
nsCString spec;
@ -290,6 +307,10 @@ ClientSource::DocShellExecutionReady(nsIDocShell* aDocShell)
return NS_ERROR_UNEXPECTED;
}
// Note: We don't assert storage access for a controlled client. If
// the about:blank actually gets used then WindowExecutionReady() will
// get called which asserts storage access.
// TODO: dedupe this with WindowExecutionReady
FrameType frameType = FrameType::Top_level;
if (!outer->IsTopLevelWindow()) {
@ -360,6 +381,16 @@ ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker)
// this invariant.
MOZ_DIAGNOSTIC_ASSERT(!mClientInfo.IsPrivateBrowsing());
// A client without access to storage should never be controlled a
// a service worker. If we are already execution ready with a real
// window or worker, then verify assert the storage policy is correct.
if (GetInnerWindow()) {
MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
nsContentUtils::StorageAccess::eAllow);
} else if (GetWorkerPrivate()) {
MOZ_DIAGNOSTIC_ASSERT(GetWorkerPrivate()->IsStorageAllowed());
}
if (mController.isSome() && mController.ref() == aServiceWorker) {
return;
}

Просмотреть файл

@ -90,6 +90,15 @@ protected:
mActor->MaybeStartTeardown();
mActor = nullptr;
}
OnShutdownThing();
}
// Allow extending classes to take action when shutdown.
virtual void
OnShutdownThing()
{
// by default do nothing
}
public:
@ -106,6 +115,8 @@ public:
// instead of calling ShutdownThing() to avoid calling MaybeStartTeardown()
// on the destroyed actor.
mShutdown = true;
OnShutdownThing();
}
};

Просмотреть файл

@ -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

Просмотреть файл

@ -313,6 +313,75 @@ ServiceWorkerManager::Init(ServiceWorkerRegistrar* aRegistrar)
mActor = static_cast<ServiceWorkerManagerChild*>(actor);
}
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));
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);
});
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
return Move(ref);
}
void
ServiceWorkerManager::StopControllingClient(const ClientInfo& aClientInfo)
{
auto entry = mControlledClients.Lookup(aClientInfo.Id());
if (!entry) {
return;
}
RefPtr<ServiceWorkerRegistrationInfo> reg =
entry.Data()->mRegistrationInfo.forget();
entry.Remove();
StopControllingRegistration(reg);
}
void
ServiceWorkerManager::MaybeStartShutdown()
{
@ -1432,8 +1501,13 @@ ServiceWorkerManager::GetActiveWorkerInfoForDocument(nsIDocument* aDocument)
{
AssertIsOnMainThread();
Maybe<ClientInfo> clientInfo(aDocument->GetClientInfo());
if (clientInfo.isNothing()) {
return nullptr;
}
RefPtr<ServiceWorkerRegistrationInfo> registration;
GetDocumentRegistration(aDocument, getter_AddRefs(registration));
GetClientRegistration(clientInfo.ref(), getter_AddRefs(registration));
if (!registration) {
return nullptr;
@ -1578,7 +1652,7 @@ ServiceWorkerManager::WorkerIsIdle(ServiceWorkerInfo* aWorker)
return;
}
if (!reg->IsControllingDocuments() && reg->mPendingUninstall) {
if (!reg->IsControllingClients() && reg->mPendingUninstall) {
RemoveRegistration(reg);
return;
}
@ -2269,15 +2343,25 @@ ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo*
entry.Remove();
}
// The registration should generally only be removed if there are no controlled
// documents, but mControlledDocuments can contain references to potentially
// controlled docs. This happens when the service worker is not active yet.
// We must purge these references since we are evicting the registration.
// 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 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();
MOZ_ASSERT(reg);
if (reg->mScope.Equals(aRegistration->mScope)) {
iter.Remove();
break;
}
}
@ -2308,32 +2392,49 @@ 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) {
StopControllingADocument(registration);
}
mControlledDocuments.Remove(aDoc);
}
void
ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
ServiceWorkerManager::MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo)
{
AssertIsOnMainThread();
MOZ_ASSERT(aDoc);
// We perform these success path navigation update steps when the
// document tells us its more or less done loading. This avoids
// slowing down page load and also lets pages consistently get
@ -2343,52 +2444,27 @@ ServiceWorkerManager::MaybeCheckNavigationUpdate(nsIDocument* aDoc)
// 9.8.22 Else: (respondWith was entered and succeeded)
// If request is a non-subresource request, then: Invoke Soft Update
// algorithm.
RefPtr<ServiceWorkerRegistrationInfo> registration;
mControlledDocuments.Get(aDoc, getter_AddRefs(registration));
if (registration) {
registration->MaybeScheduleUpdate();
ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
if (data && data->mRegistrationInfo) {
data->mRegistrationInfo->MaybeScheduleUpdate();
}
}
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 = GenericPromise::CreateAndResolve(true, __func__);
aRegistration->StartControllingADocument();
mControlledDocuments.Put(aDoc, 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.
ServiceWorkerInfo* activeWorker = aRegistration->GetActive();
Maybe<ClientInfo> clientInfo = aDoc->GetClientInfo();
if (activeWorker && clientInfo.isSome()) {
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
ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration)
ServiceWorkerManager::StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration)
{
aRegistration->StopControllingADocument();
if (aRegistration->IsControllingDocuments() || !aRegistration->IsIdle()) {
aRegistration->StopControllingClient();
if (aRegistration->IsControllingClients() || !aRegistration->IsIdle()) {
return;
}
@ -2718,10 +2794,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
@ -2785,20 +2858,21 @@ ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal,
}
nsresult
ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc,
ServiceWorkerRegistrationInfo** aRegistrationInfo)
ServiceWorkerManager::GetClientRegistration(const ClientInfo& aClientInfo,
ServiceWorkerRegistrationInfo** aRegistrationInfo)
{
RefPtr<ServiceWorkerRegistrationInfo> registration;
if (!mControlledDocuments.Get(aDoc, getter_AddRefs(registration))) {
ControlledClientData* data = mControlledClients.Get(aClientInfo.Id());
if (!data || !data->mRegistrationInfo) {
return NS_ERROR_NOT_AVAILABLE;
}
// If the document is controlled, the current worker MUST be non-null.
if (!registration->GetActive()) {
if (!data->mRegistrationInfo->GetActive()) {
return NS_ERROR_NOT_AVAILABLE;
}
registration.forget(aRegistrationInfo);
RefPtr<ServiceWorkerRegistrationInfo> ref = data->mRegistrationInfo;
ref.forget(aRegistrationInfo);
return NS_OK;
}
@ -3169,13 +3243,21 @@ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
return ref.forget();
}
Maybe<ClientInfo> clientInfo(aDocument->GetClientInfo());
if (NS_WARN_IF(clientInfo.isNothing())) {
ref = GenericPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
__func__);
return ref.forget();
}
// The registration that should be controlling the client
RefPtr<ServiceWorkerRegistrationInfo> matchingRegistration =
GetServiceWorkerRegistrationInfo(aDocument);
// The registration currently controlling the client
RefPtr<ServiceWorkerRegistrationInfo> controllingRegistration;
GetDocumentRegistration(aDocument, getter_AddRefs(controllingRegistration));
GetClientRegistration(clientInfo.ref(),
getter_AddRefs(controllingRegistration));
if (aWorkerRegistration != matchingRegistration ||
aWorkerRegistration == controllingRegistration) {
@ -3183,11 +3265,8 @@ ServiceWorkerManager::MaybeClaimClient(nsIDocument* aDocument,
return ref.forget();
}
if (controllingRegistration) {
StopControllingADocument(controllingRegistration);
}
ref = StartControllingADocument(aWorkerRegistration, aDocument);
StartControllingADocument(aWorkerRegistration, aDocument);
ref = StartControllingClient(clientInfo.ref(), aWorkerRegistration);
return ref.forget();
}
@ -3248,31 +3327,19 @@ ServiceWorkerManager::UpdateClientControllers(ServiceWorkerRegistrationInfo* aRe
RefPtr<ServiceWorkerInfo> activeWorker = aRegistration->GetActive();
MOZ_DIAGNOSTIC_ASSERT(activeWorker);
AutoTArray<nsCOMPtr<nsIDocument>, 16> docList;
for (auto iter = mControlledDocuments.Iter(); !iter.Done(); iter.Next()) {
if (iter.UserData() != aRegistration) {
AutoTArray<RefPtr<ClientHandle>, 16> handleList;
for (auto iter = mControlledClients.Iter(); !iter.Done(); iter.Next()) {
if (iter.UserData()->mRegistrationInfo != aRegistration) {
continue;
}
nsCOMPtr<nsIDocument> doc = do_QueryInterface(iter.Key());
if (NS_WARN_IF(!doc)) {
continue;
}
docList.AppendElement(doc.forget());
handleList.AppendElement(iter.UserData()->mClientHandle);
}
// Fire event after iterating mControlledDocuments is done to prevent
// Fire event after iterating mControlledClients is done to prevent
// modification by reentering from the event handlers during iteration.
for (auto& doc : docList) {
Maybe<ClientInfo> clientInfo = doc->GetClientInfo();
if (clientInfo.isNothing()) {
continue;
}
RefPtr<ClientHandle> clientHandle =
ClientManager::CreateHandle(clientInfo.ref(),
SystemGroup::EventTargetFor(TaskCategory::Other));
clientHandle->Control(activeWorker->Descriptor());
for (auto& handle : handleList) {
handle->Control(activeWorker->Descriptor());
}
}

Просмотреть файл

@ -21,6 +21,7 @@
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ClientHandle.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerCommon.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
@ -105,6 +106,21 @@ public:
nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
struct ControlledClientData
{
RefPtr<ClientHandle> mClientHandle;
RefPtr<ServiceWorkerRegistrationInfo> mRegistrationInfo;
ControlledClientData(ClientHandle* aClientHandle,
ServiceWorkerRegistrationInfo* aRegistrationInfo)
: mClientHandle(aClientHandle)
, mRegistrationInfo(aRegistrationInfo)
{
}
};
nsClassHashtable<nsIDHashKey, ControlledClientData> mControlledClients;
// Track all documents that have attempted to register a service worker for a
// given scope.
typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
@ -304,7 +320,7 @@ public:
ServiceWorkerRegistrationListener* aListener);
void
MaybeCheckNavigationUpdate(nsIDocument* aDoc);
MaybeCheckNavigationUpdate(const ClientInfo& aClientInfo);
nsresult
SendPushEvent(const nsACString& aOriginAttributes,
@ -328,6 +344,13 @@ private:
void
Init(ServiceWorkerRegistrar* aRegistrar);
RefPtr<GenericPromise>
StartControllingClient(const ClientInfo& aClientInfo,
ServiceWorkerRegistrationInfo* aRegistrationInfo);
void
StopControllingClient(const ClientInfo& aClientInfo);
void
MaybeStartShutdown();
@ -349,8 +372,8 @@ private:
Update(ServiceWorkerRegistrationInfo* aRegistration);
nsresult
GetDocumentRegistration(nsIDocument* aDoc,
ServiceWorkerRegistrationInfo** aRegistrationInfo);
GetClientRegistration(const ClientInfo& aClientInfo,
ServiceWorkerRegistrationInfo** aRegistrationInfo);
nsresult
GetServiceWorkerForScope(nsPIDOMWindowInner* aWindow,
@ -375,12 +398,12 @@ private:
void
NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
RefPtr<GenericPromise>
void
StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
nsIDocument* aDoc);
void
StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
StopControllingRegistration(ServiceWorkerRegistrationInfo* aRegistration);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);

Просмотреть файл

@ -81,7 +81,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
const nsACString& aScope,
nsIPrincipal* aPrincipal,
ServiceWorkerUpdateViaCache aUpdateViaCache)
: mControlledDocumentsCounter(0)
: mControlledClientsCounter(0)
, mUpdateState(NoUpdate)
, mCreationTime(PR_Now())
, mCreationTimeStamp(TimeStamp::Now())
@ -94,9 +94,7 @@ ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(
ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
{
if (IsControllingDocuments()) {
NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
}
MOZ_DIAGNOSTIC_ASSERT(!IsControllingClients());
}
NS_IMPL_ISUPPORTS(ServiceWorkerRegistrationInfo, nsIServiceWorkerRegistrationInfo)
@ -248,7 +246,7 @@ void
ServiceWorkerRegistrationInfo::TryToActivate()
{
AssertIsOnMainThread();
bool controlling = IsControllingDocuments();
bool controlling = IsControllingClients();
bool skipWaiting = mWaitingWorker && mWaitingWorker->SkipWaitingFlag();
bool idle = IsIdle();
if (idle && (!controlling || skipWaiting)) {

Просмотреть файл

@ -17,7 +17,7 @@ namespace workers {
class ServiceWorkerRegistrationInfo final
: public nsIServiceWorkerRegistrationInfo
{
uint32_t mControlledDocumentsCounter;
uint32_t mControlledClientsCounter;
enum
{
@ -79,22 +79,22 @@ public:
GetServiceWorkerInfoById(uint64_t aId);
void
StartControllingADocument()
StartControllingClient()
{
++mControlledDocumentsCounter;
++mControlledClientsCounter;
}
void
StopControllingADocument()
StopControllingClient()
{
MOZ_ASSERT(mControlledDocumentsCounter);
--mControlledDocumentsCounter;
MOZ_ASSERT(mControlledClientsCounter);
--mControlledClientsCounter;
}
bool
IsControllingDocuments() const
IsControllingClients() const
{
return mActiveWorker && mControlledDocumentsCounter;
return mActiveWorker && mControlledClientsCounter;
}
void

Просмотреть файл

@ -138,7 +138,7 @@ ServiceWorkerUnregisterJob::Unregister()
InvokeResultCallbacks(NS_OK);
// "If no service worker client is using registration..."
if (!registration->IsControllingDocuments() && registration->IsIdle()) {
if (!registration->IsControllingClients() && registration->IsIdle()) {
// "Invoke [[Clear Registration]]..."
swm->RemoveRegistration(registration);
}

Просмотреть файл

@ -19,10 +19,6 @@
info("skip_waiting_scope/index.html shouldn't be launched directly!");
}
navigator.serviceWorker.ready.then(function() {
parent.postMessage("READY", "*");
});
navigator.serviceWorker.oncontrollerchange = function() {
parent.postMessage({
event: "controllerchange",

Просмотреть файл

@ -13,6 +13,7 @@
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script src="utils.js"></script>
<script class="testbody" type="text/javascript">
var registration, iframe, content;
@ -21,19 +22,9 @@
{scope: "./skip_waiting_scope/"});
}
function waitForActivated(swr) {
async function waitForActivated(swr) {
registration = swr;
var promise = new Promise(function(resolve, reject) {
window.onmessage = function(e) {
if (e.data === "READY") {
ok(true, "Active worker is activated now");
resolve();
} else {
ok(false, "Wrong value. Somenting went wrong");
resolve();
}
}
});
await waitForState(registration.installing, "activated")
iframe = document.createElement("iframe");
iframe.setAttribute("src", "skip_waiting_scope/index.html");
@ -41,7 +32,7 @@
content = document.getElementById("content");
content.appendChild(iframe);
return promise;
await new Promise(resolve => iframe.onload = resolve);
}
function checkWhetherItSkippedWaiting() {

Просмотреть файл

@ -13,14 +13,15 @@
<p id="display"></p>
<div id="content"></div>
<pre id="test"></pre>
<script src="utils.js"></script>
<script class="testbody" type="text/javascript">
var registration;
var promise;
function start() {
return navigator.serviceWorker.register("worker_updatefoundevent.js",
{ scope: "./updatefoundevent.html" })
.then((swr) => registration = swr);
async function start() {
registration = await navigator.serviceWorker.register("worker_updatefoundevent.js",
{ scope: "./updatefoundevent.html" })
await waitForState(registration.installing, 'activated');
}
function startWaitForUpdateFound() {

Просмотреть файл

@ -3,21 +3,16 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
onactivate = function(e) {
e.waitUntil(new Promise(function(resolve, reject) {
registration.onupdatefound = function(e) {
clients.matchAll().then(function(clients) {
if (!clients.length) {
reject("No clients found");
}
if (registration.scope.match(/updatefoundevent\.html$/)) {
clients[0].postMessage("finish");
resolve();
} else {
dump("Scope did not match");
}
}, reject);
registration.onupdatefound = function(e) {
clients.matchAll().then(function(clients) {
if (!clients.length) {
reject("No clients found");
}
}));
if (registration.scope.match(/updatefoundevent\.html$/)) {
clients[0].postMessage("finish");
} else {
dump("Scope did not match");
}
});
}

Просмотреть файл

@ -534,7 +534,7 @@ def print_test_summary(num_tests, failures, complete, doing, options):
if options.show_failed:
print(' ' + subprocess.list2cmdline(res.cmd))
else:
print(' ' + ' '.join(res.test.jitflags + [res.test.path]))
print(' ' + ' '.join(res.test.jitflags + [res.test.relpath_tests]))
print('FAILURES:')
for res in failures:

Просмотреть файл

@ -4396,11 +4396,11 @@ MOZ_ARG_WITH_STRING(macbundlename-prefix,
MOZ_MACBUNDLE_NAME=$MOZ_APP_DISPLAYNAME
if test "$MOZ_MACBUNDLE_NAME_PREFIX"; then
MOZ_MACBUNDLE_NAME="${MOZ_MACBUNDLE_NAME_PREFIX} ${MOZ_MACBUNDLE_NAME}"
MOZ_MACBUNDLE_NAME="${MOZ_MACBUNDLE_NAME_PREFIX}${MOZ_MACBUNDLE_NAME}"
fi
if test "$MOZ_DEBUG"; then
MOZ_MACBUNDLE_NAME="${MOZ_MACBUNDLE_NAME} Debug.app"
MOZ_MACBUNDLE_NAME="${MOZ_MACBUNDLE_NAME}Debug.app"
else
MOZ_MACBUNDLE_NAME=${MOZ_MACBUNDLE_NAME}.app
fi

Просмотреть файл

@ -90,9 +90,12 @@ async_test(function(t) {
return registration.unregister();
})
.then(function() {
// Step 5.1 of Register clears the uninstall flag before fetching
// the script:
//
// https://w3c.github.io/ServiceWorker/#register-algorithm
var promise = navigator.serviceWorker.register('this-will-404',
{ scope: scope });
iframe.remove();
return promise;
})
.then(
@ -100,17 +103,28 @@ async_test(function(t) {
assert_unreached('register should reject the promise');
},
function() {
assert_equals(registration.installing, null,
'registration.installing');
assert_equals(registration.waiting, null,
'registration.waiting');
assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
'registration.active');
iframe.remove();
return with_iframe(scope);
})
.then(function(frame) {
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null,
'document should not load with a controller');
assert_equals(
frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
normalizeURL(worker_url),
'the original worker should control a new document');
frame.remove();
return registration.unregister();
})
.then(function() {
t.done();
})
.catch(unreached_rejection(t));
}, 'Registering a new script URL that 404s does not resurrect an ' +
}, 'Registering a new script URL that 404s does resurrect an ' +
'unregistered registration');
async_test(function(t) {
@ -131,9 +145,12 @@ async_test(function(t) {
return registration.unregister();
})
.then(function() {
// Step 5.1 of Register clears the uninstall flag before firing
// the install event:
//
// https://w3c.github.io/ServiceWorker/#register-algorithm
var promise = navigator.serviceWorker.register(
'resources/reject-install-worker.js', { scope: scope });
iframe.remove();
return promise;
})
.then(function(r) {
@ -141,12 +158,20 @@ async_test(function(t) {
return wait_for_state(t, r.installing, 'redundant');
})
.then(function() {
assert_equals(registration.installing, null,
'registration.installing');
assert_equals(registration.waiting, null,
'registration.waiting');
assert_equals(registration.active.scriptURL, normalizeURL(worker_url),
'registration.active');
iframe.remove();
return with_iframe(scope);
})
.then(function(frame) {
assert_equals(frame.contentWindow.navigator.serviceWorker.controller,
null,
'document should not load with a controller');
assert_equals(
frame.contentWindow.navigator.serviceWorker.controller.scriptURL,
normalizeURL(worker_url),
'the original worker should control a new document');
frame.remove();
return registration.unregister();
})
@ -154,6 +179,6 @@ async_test(function(t) {
t.done();
})
.catch(unreached_rejection(t));
}, 'Registering a new script URL that fails to install does not resurrect ' +
}, 'Registering a new script URL that fails to install does resurrect ' +
'an unregistered registration');
</script>