Bug 1469873 Make ClientSource::Control() fail if storage access is not available and make claim() respect the result. r=mrbkap

This commit is contained in:
Ben Kelly 2018-06-28 12:58:23 -07:00
Родитель 1d536a58df
Коммит 78b42d6228
8 изменённых файлов: 181 добавлений и 27 удалений

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

@ -475,6 +475,8 @@ ClaimOnMainThread(const ClientInfo& aClientInfo,
}, [promise] (nsresult aRv) { }, [promise] (nsresult aRv) {
promise->Reject(aRv, __func__); promise->Reject(aRv, __func__);
}); });
scopeExit.release();
}); });
MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));

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

@ -124,6 +124,28 @@ ClientSource::GetDocShell() const
return mOwner.as<nsCOMPtr<nsIDocShell>>(); return mOwner.as<nsCOMPtr<nsIDocShell>>();
} }
nsIGlobalObject*
ClientSource::GetGlobal() const
{
NS_ASSERT_OWNINGTHREAD(ClientSource);
nsPIDOMWindowInner* win = GetInnerWindow();
if (win) {
return win->AsGlobal();
}
WorkerPrivate* wp = GetWorkerPrivate();
if (wp) {
return wp->GlobalScope();
}
// Note, ClientSource objects attached to docshell for conceptual
// initial about:blank will get nullptr here. The caller should
// use MaybeCreateIntitialDocument() to create the window before
// GetGlobal() if it wants this before.
return nullptr;
}
void void
ClientSource::MaybeCreateInitialDocument() ClientSource::MaybeCreateInitialDocument()
{ {
@ -431,10 +453,47 @@ ClientSource::Control(const ClientControlledArgs& aArgs)
{ {
NS_ASSERT_OWNINGTHREAD(ClientSource); NS_ASSERT_OWNINGTHREAD(ClientSource);
// Determine if the client is allowed to be controlled. Currently we
// prevent service workers from controlling clients that cannot access
// storage. We exempt this restriction for local URL clients, like about:blank
// and blob:, since access to service workers is dictated by their parent.
//
// Note, we default to allowing the client to be controlled in the case
// where we are not execution ready yet. This can only happen if the
// the non-subresource load is intercepted by a service worker. Since
// ServiceWorkerInterceptController() uses StorageAllowedForChannel()
// it should be fine to accept these control messages.
//
// Its also fine to default to allowing ClientSource attached to a docshell
// to be controlled. These clients represent inital about:blank windows
// that do not have an inner window created yet. We explicitly allow initial
// about:blank.
bool controlAllowed = true;
if (GetInnerWindow()) {
// Local URL windows and windows with access to storage can be controlled.
controlAllowed = Info().URL().LowerCaseEqualsLiteral("about:blank") ||
StringBeginsWith(Info().URL(), NS_LITERAL_CSTRING("blob:")) ||
nsContentUtils::StorageAllowedForWindow(GetInnerWindow()) ==
nsContentUtils::StorageAccess::eAllow;
} else if (GetWorkerPrivate()) {
// Local URL workers and workers with access to storage cna be controlled.
controlAllowed = GetWorkerPrivate()->IsStorageAllowed() ||
StringBeginsWith(GetWorkerPrivate()->ScriptURL(),
NS_LITERAL_STRING("blob:"));
}
RefPtr<ClientOpPromise> ref;
if (NS_WARN_IF(!controlAllowed)) {
ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
__func__);
return ref.forget();
}
SetController(ServiceWorkerDescriptor(aArgs.serviceWorker())); SetController(ServiceWorkerDescriptor(aArgs.serviceWorker()));
RefPtr<ClientOpPromise> ref = ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__);
ClientOpPromise::CreateAndResolve(NS_OK, __func__);
return ref.forget(); return ref.forget();
} }
@ -670,35 +729,55 @@ ClientSource::PostMessage(const ClientPostMessageArgs& aArgs)
RefPtr<ClientOpPromise> RefPtr<ClientOpPromise>
ClientSource::Claim(const ClientClaimArgs& aArgs) ClientSource::Claim(const ClientClaimArgs& aArgs)
{ {
// The ClientSource::Claim method is only needed in the legacy
// mode where the ServiceWorkerManager is run in each child-process.
// In parent-process mode this method should not be called.
MOZ_DIAGNOSTIC_ASSERT(!ServiceWorkerParentInterceptEnabled());
RefPtr<ClientOpPromise> ref; RefPtr<ClientOpPromise> ref;
nsIGlobalObject* global = GetGlobal();
if (NS_WARN_IF(!global)) {
ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR,
__func__);
return ref.forget();
}
// Note, we cannot just mark the ClientSource controlled. We must go through
// the SWM so that it can keep track of which clients are controlled by each
// registration. We must tell the child-process SWM in legacy child-process
// mode. In parent-process service worker mode the SWM is notified in the
// parent-process in ClientManagerService::Claim().
RefPtr<GenericPromise::Private> innerPromise =
new GenericPromise::Private(__func__);
ServiceWorkerDescriptor swd(aArgs.serviceWorker()); ServiceWorkerDescriptor swd(aArgs.serviceWorker());
// Today the ServiceWorkerManager maintains its own list of nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
// nsIDocument objects controlled by each service worker. We "ClientSource::Claim",
// need to try to update that data structure for now. If we [innerPromise, clientInfo = mClientInfo, swd] () mutable {
// can't, however, then simply mark the Client as controlled. RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
// In the future this will be enough for the SWM as well since if (NS_WARN_IF(!swm)) {
// it will eventually hold ClientHandle objects instead of innerPromise->Reject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__);
// nsIDocuments. return;
nsPIDOMWindowInner* innerWindow = GetInnerWindow(); }
nsIDocument* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
RefPtr<ServiceWorkerManager> swm = doc ? ServiceWorkerManager::GetInstance() RefPtr<GenericPromise> p = swm->MaybeClaimClient(clientInfo, swd);
: nullptr; p->ChainTo(innerPromise.forget(), __func__);
if (!swm || !doc) { });
SetController(swd);
ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); if (NS_IsMainThread()) {
return ref.forget(); r->Run();
} else {
MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget()));
} }
RefPtr<ClientOpPromise::Private> outerPromise = RefPtr<ClientOpPromise::Private> outerPromise =
new ClientOpPromise::Private(__func__); new ClientOpPromise::Private(__func__);
auto holder = auto holder = MakeRefPtr<DOMMozPromiseRequestHolder<GenericPromise>>(global);
MakeRefPtr<DOMMozPromiseRequestHolder<GenericPromise>>(innerWindow->AsGlobal());
RefPtr<GenericPromise> p = swm->MaybeClaimClient(mClientInfo, swd); innerPromise->Then(mEventTarget, __func__,
p->Then(mEventTarget, __func__,
[outerPromise, holder] (bool aResult) { [outerPromise, holder] (bool aResult) {
holder->Complete(); holder->Complete();
outerPromise->Resolve(NS_OK, __func__); outerPromise->Resolve(NS_OK, __func__);

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

@ -17,6 +17,7 @@
#endif #endif
class nsIDocShell; class nsIDocShell;
class nsIGlobalObject;
class nsISerialEventTarget; class nsISerialEventTarget;
class nsPIDOMWindowInner; class nsPIDOMWindowInner;
@ -78,6 +79,9 @@ class ClientSource final : public ClientThing<ClientSourceChild>
nsIDocShell* nsIDocShell*
GetDocShell() const; GetDocShell() const;
nsIGlobalObject*
GetGlobal() const;
void void
MaybeCreateInitialDocument(); MaybeCreateInitialDocument();

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

@ -6,6 +6,8 @@
#include "ClientSourceOpParent.h" #include "ClientSourceOpParent.h"
#include "ClientSourceParent.h"
namespace mozilla { namespace mozilla {
namespace dom { namespace dom {
@ -25,10 +27,22 @@ ClientSourceOpParent::Recv__delete__(const ClientOpResult& aResult)
{ {
if (aResult.type() == ClientOpResult::Tnsresult && if (aResult.type() == ClientOpResult::Tnsresult &&
NS_FAILED(aResult.get_nsresult())) { NS_FAILED(aResult.get_nsresult())) {
// If a control message fails then clear the controller from
// the ClientSourceParent. We eagerly marked it controlled at
// the start of the operation.
if (mArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) {
auto source = static_cast<ClientSourceParent*>(Manager());
if (source) {
source->ClearController();
}
}
mPromise->Reject(aResult.get_nsresult(), __func__); mPromise->Reject(aResult.get_nsresult(), __func__);
mPromise = nullptr; mPromise = nullptr;
return IPC_OK(); return IPC_OK();
} }
mPromise->Resolve(aResult, __func__); mPromise->Resolve(aResult, __func__);
mPromise = nullptr; mPromise = nullptr;
return IPC_OK(); return IPC_OK();
@ -36,7 +50,8 @@ ClientSourceOpParent::Recv__delete__(const ClientOpResult& aResult)
ClientSourceOpParent::ClientSourceOpParent(const ClientOpConstructorArgs& aArgs, ClientSourceOpParent::ClientSourceOpParent(const ClientOpConstructorArgs& aArgs,
ClientOpPromise::Private* aPromise) ClientOpPromise::Private* aPromise)
: mPromise(aPromise) : mArgs(aArgs)
, mPromise(aPromise)
{ {
MOZ_DIAGNOSTIC_ASSERT(mPromise); MOZ_DIAGNOSTIC_ASSERT(mPromise);
} }

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

@ -14,6 +14,7 @@ namespace dom {
class ClientSourceOpParent final : public PClientSourceOpParent class ClientSourceOpParent final : public PClientSourceOpParent
{ {
const ClientOpConstructorArgs mArgs;
RefPtr<ClientOpPromise::Private> mPromise; RefPtr<ClientOpPromise::Private> mPromise;
// PClientSourceOpParent interface // PClientSourceOpParent interface

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

@ -274,6 +274,12 @@ ClientSourceParent::GetController() const
return mController; return mController;
} }
void
ClientSourceParent::ClearController()
{
mController.reset();
}
void void
ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle) ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle)
{ {
@ -298,7 +304,11 @@ ClientSourceParent::StartOp(const ClientOpConstructorArgs& aArgs)
new ClientOpPromise::Private(__func__); new ClientOpPromise::Private(__func__);
// If we are being controlled, remember that data before propagating // If we are being controlled, remember that data before propagating
// on to the ClientSource. // on to the ClientSource. This must be set prior to triggering
// the controllerchange event from the ClientSource since some tests
// expect matchAll() to find the controlled client immediately after.
// If the control operation fails, then we reset the controller value
// to reflect the final state.
if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) { if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) {
mController.reset(); mController.reset();
mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker()); mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker());

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

@ -79,6 +79,9 @@ public:
const Maybe<ServiceWorkerDescriptor>& const Maybe<ServiceWorkerDescriptor>&
GetController() const; GetController() const;
void
ClearController();
void void
AttachHandle(ClientHandleParent* aClientSource); AttachHandle(ClientHandleParent* aClientSource);

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

@ -317,6 +317,7 @@ ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive()); MOZ_DIAGNOSTIC_ASSERT(aRegistrationInfo->GetActive());
RefPtr<GenericPromise> ref; RefPtr<GenericPromise> ref;
RefPtr<ServiceWorkerManager> self(this);
const ServiceWorkerDescriptor& active = const ServiceWorkerDescriptor& active =
aRegistrationInfo->GetActive()->Descriptor(); aRegistrationInfo->GetActive()->Descriptor();
@ -326,7 +327,12 @@ ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
RefPtr<ServiceWorkerRegistrationInfo> old = RefPtr<ServiceWorkerRegistrationInfo> old =
entry.Data()->mRegistrationInfo.forget(); entry.Data()->mRegistrationInfo.forget();
ref = entry.Data()->mClientHandle->Control(active); if (aControlClientHandle) {
ref = entry.Data()->mClientHandle->Control(active);
} else {
ref = GenericPromise::CreateAndResolve(false, __func__);
}
entry.Data()->mRegistrationInfo = aRegistrationInfo; entry.Data()->mRegistrationInfo = aRegistrationInfo;
if (old != aRegistrationInfo) { if (old != aRegistrationInfo) {
@ -336,6 +342,17 @@ ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1); Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
// Always check to see if we failed to actually control the client. In
// that case removed the client from our list of controlled clients.
ref->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[] (bool) {
// do nothing on success
}, [self, aClientInfo] (nsresult aRv) {
// failed to control, forget about this client
self->StopControllingClient(aClientInfo);
});
return ref; return ref;
} }
@ -355,15 +372,25 @@ ServiceWorkerManager::StartControllingClient(const ClientInfo& aClientInfo,
return new ControlledClientData(clientHandle, aRegistrationInfo); return new ControlledClientData(clientHandle, aRegistrationInfo);
}); });
RefPtr<ServiceWorkerManager> self(this);
clientHandle->OnDetach()->Then( clientHandle->OnDetach()->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__, SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[self = std::move(self), aClientInfo] { [self, aClientInfo] {
self->StopControllingClient(aClientInfo); self->StopControllingClient(aClientInfo);
}); });
Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1); Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1);
// Always check to see if we failed to actually control the client. In
// that case removed the client from our list of controlled clients.
ref->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[] (bool) {
// do nothing on success
}, [self, aClientInfo] (nsresult aRv) {
// failed to control, forget about this client
self->StopControllingClient(aClientInfo);
});
return ref; return ref;
} }
@ -2695,7 +2722,20 @@ ServiceWorkerManager::UpdateClientControllers(ServiceWorkerRegistrationInfo* aRe
// Fire event after iterating mControlledClients is done to prevent // Fire event after iterating mControlledClients is done to prevent
// modification by reentering from the event handlers during iteration. // modification by reentering from the event handlers during iteration.
for (auto& handle : handleList) { for (auto& handle : handleList) {
handle->Control(activeWorker->Descriptor()); RefPtr<GenericPromise> p = handle->Control(activeWorker->Descriptor());
RefPtr<ServiceWorkerManager> self = this;
// If we fail to control the client, then automatically remove it
// from our list of controlled clients.
p->Then(
SystemGroup::EventTargetFor(TaskCategory::Other), __func__,
[] (bool) {
// do nothing on success
}, [self, clientInfo = handle->Info()] (nsresult aRv) {
// failed to control, forget about this client
self->StopControllingClient(clientInfo);
});
} }
} }