author | Ben Kelly <ben@wanderview.com> |
Tue, 05 Dec 2017 20:45:22 -0500 | |
changeset 395174 | 699fd0f2b04666c7b043f0350cf56f0bc07189ac |
parent 395173 | 202ff4e53892c94131974daf3b9637fac064e558 |
child 395175 | 09942818c67041082051e9bd25aee3f7899546ba |
push id | 98024 |
push user | bkelly@mozilla.com |
push date | Wed, 06 Dec 2017 01:45:29 +0000 |
treeherder | mozilla-inbound@7750a46652fa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | baku, jld |
bugs | 1423412 |
milestone | 59.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -3445,16 +3445,42 @@ nsDocShell::MaybeCreateInitialClientSour if (NS_WARN_IF(!mInitialClientSource)) { return; } // Mark the initial client as execution ready, but owned by the docshell. // If the client is actually used this will cause ClientSource to force // the creation of the initial about:blank by calling nsDocShell::GetDocument(). mInitialClientSource->DocShellExecutionReady(this); + + // Next, check to see if the parent is controlled. + nsCOMPtr<nsIDocShell> parent = GetParentDocshell(); + nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; + nsPIDOMWindowInner* parentInner = + parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; + if (!parentInner) { + return; + } + + Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController()); + if (controller.isNothing()) { + 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()); + + // Also mark the ClientSource as controlled directly in case script + // immediately accesses navigator.serviceWorker.controller. + mInitialClientSource->SetController(controller.ref()); } Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const { if (mInitialClientSource) { Maybe<ClientInfo> result; result.emplace(mInitialClientSource->Info());
--- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -1785,26 +1785,44 @@ nsGlobalWindowInner::EnsureClientSource( } } // 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. + // 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 + // principal. We do this in docshell, but as mentioned we aren't + // smart enough to handle all cases yet. For example, a + // window.open() with new URL should inherit the controller from + // the opener, but we probably don't handle that yet. if (!mClientSource) { mClientSource = ClientManager::CreateSource(ClientType::Window, EventTargetFor(TaskCategory::Other), mDoc->NodePrincipal()); if (NS_WARN_IF(!mClientSource)) { return NS_ERROR_FAILURE; } newClientSource = true; } + // 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) { + const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController(); + if (controller.isSome()) { + mClientSource->SetController(controller.ref()); + } + } + // Its possible that we got a client just after being frozen in // the bfcache. In that case freeze the client immediately. if (newClientSource && IsFrozen()) { mClientSource->Freeze(); } return NS_OK; } @@ -2277,16 +2295,22 @@ nsPIDOMWindowInner::SyncStateFromParentW } Maybe<ClientInfo> nsPIDOMWindowInner::GetClientInfo() const { return Move(nsGlobalWindowInner::Cast(this)->GetClientInfo()); } +Maybe<ServiceWorkerDescriptor> +nsPIDOMWindowInner::GetController() const +{ + return Move(nsGlobalWindowInner::Cast(this)->GetController()); +} + void nsGlobalWindowInner::UpdateTopInnerWindow() { if (IsTopInnerWindow() || !mTopInnerWindow) { return; } mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets); @@ -6113,16 +6137,27 @@ nsGlobalWindowInner::GetClientInfo() con MOZ_ASSERT(NS_IsMainThread()); Maybe<ClientInfo> clientInfo; if (mClientSource) { clientInfo.emplace(mClientSource->Info()); } return Move(clientInfo); } +Maybe<ServiceWorkerDescriptor> +nsGlobalWindowInner::GetController() const +{ + MOZ_ASSERT(NS_IsMainThread()); + Maybe<ServiceWorkerDescriptor> controller; + if (mClientSource) { + controller = mClientSource->GetController(); + } + return Move(controller); +} + nsresult nsGlobalWindowInner::FireDelayedDOMEvents() { if (mApplicationCache) { static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents(); } // Fires an offline status event if the offline status has changed
--- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -344,16 +344,17 @@ public: void Resume(); virtual bool IsSuspended() const override; void Freeze(); void Thaw(); virtual bool IsFrozen() const override; void SyncStateFromParentWindow(); mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const; + mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const; virtual nsresult FireDelayedDOMEvents() override; virtual nsresult SetNewDocument(nsIDocument *aDocument, nsISupports *aState, bool aForceReuseInnerWindow) override; virtual void SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
--- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -46,16 +46,17 @@ namespace mozilla { class ThrottledEventQueue; namespace dom { class AudioContext; class ClientInfo; class DocGroup; class TabGroup; class Element; class Performance; +class ServiceWorkerDescriptor; class ServiceWorkerRegistration; class Timeout; class TimeoutManager; class CustomElementRegistry; enum class CallerType : uint32_t; } // namespace dom } // namespace mozilla @@ -321,16 +322,17 @@ public: // Increase/Decrease the number of open WebSockets. void UpdateWebSocketCount(int32_t aDelta); // Return true if there are any open WebSockets that could block // timeout-throttling. bool HasOpenWebSockets() const; mozilla::Maybe<mozilla::dom::ClientInfo> GetClientInfo() const; + mozilla::Maybe<mozilla::dom::ServiceWorkerDescriptor> GetController() const; mozilla::dom::TabGroup* TabGroup(); virtual nsPIDOMWindowOuter* GetPrivateRoot() = 0; virtual mozilla::dom::CustomElementRegistry* CustomElements() = 0; // XXX: This is called on inner windows
--- a/dom/clients/manager/ClientManager.h +++ b/dom/clients/manager/ClientManager.h @@ -32,16 +32,17 @@ class WorkerPrivate; // The ClientManager provides a per-thread singleton interface workering // with the client subsystem. It allows globals to create ClientSource // objects. It allows other parts of the system to attach to this globals // by creating ClientHandle objects. The ClientManager also provides // methods for querying the list of clients active in the system. class ClientManager final : public ClientThing<ClientManagerChild> { friend class ClientManagerChild; + friend class ClientSource; ClientManager(); ~ClientManager(); // Utility method to trigger a shutdown of the ClientManager. This // is called in various error conditions or when the last reference // is dropped. void
--- a/dom/clients/manager/ClientSource.cpp +++ b/dom/clients/manager/ClientSource.cpp @@ -253,16 +253,27 @@ ClientSource::Thaw() const ClientInfo& ClientSource::Info() const { return mClientInfo; } void +ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate) +{ + NS_ASSERT_OWNINGTHREAD(ClientSource); + MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate); + MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate()); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_DIAGNOSTIC_ASSERT(GetActor()); + GetActor()->SendWorkerSyncPing(); +} + +void ClientSource::SetController(const ServiceWorkerDescriptor& aServiceWorker) { NS_ASSERT_OWNINGTHREAD(ClientSource); if (mController.isSome() && mController.ref() == aServiceWorker) { return; }
--- a/dom/clients/manager/ClientSource.h +++ b/dom/clients/manager/ClientSource.h @@ -92,16 +92,23 @@ public: Freeze(); void Thaw(); const ClientInfo& Info() const; + // Trigger a synchronous IPC ping to the parent process to confirm that + // the ClientSource actor has been created. This should only be used + // by the WorkerPrivate startup code to deal with a ClientHandle::Control() + // call racing on the main thread. Do not call this in other circumstances! + void + WorkerSyncPing(mozilla::dom::workers::WorkerPrivate* aWorkerPrivate); + // Synchronously mark the ClientSource as controlled by the given service // worker. This can happen as a result of a remote operation or directly // by local code. For example, if a client's initial network load is // intercepted by a controlling service worker then this should be called // immediately. // // Note, there is no way to clear the controlling service worker because // the specification does not allow that operation.
--- a/dom/clients/manager/ClientSourceParent.cpp +++ b/dom/clients/manager/ClientSourceParent.cpp @@ -15,16 +15,17 @@ #include "mozilla/dom/PClientManagerParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/SystemGroup.h" #include "mozilla/Unused.h" namespace mozilla { namespace dom { +using mozilla::ipc::AssertIsOnBackgroundThread; using mozilla::ipc::BackgroundParent; using mozilla::ipc::IPCResult; using mozilla::ipc::PrincipalInfo; namespace { // It would be nice to use a lambda instead of this class, but we cannot // move capture in lambdas yet and ContentParent cannot be AddRef'd off @@ -77,16 +78,25 @@ ClientSourceParent::KillInvalidChild() // typically mean someone sent us bogus data over the IPC link. We can't // trust that process any more. We have to do this on the main thread, so // there is a small window of time before we kill the process. This is why // we start the actor destruction immediately above. nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(Move(process)); MOZ_ALWAYS_SUCCEEDS(SystemGroup::Dispatch(TaskCategory::Other, r.forget())); } +mozilla::ipc::IPCResult +ClientSourceParent::RecvWorkerSyncPing() +{ + AssertIsOnBackgroundThread(); + // Do nothing here. This is purely a sync message allowing the child to + // confirm that the actor has been created on the parent process. + return IPC_OK(); +} + IPCResult ClientSourceParent::RecvTeardown() { Unused << Send__delete__(this); return IPC_OK(); } IPCResult
--- a/dom/clients/manager/ClientSourceParent.h +++ b/dom/clients/manager/ClientSourceParent.h @@ -26,16 +26,19 @@ class ClientSourceParent final : public bool mExecutionReady; bool mFrozen; void KillInvalidChild(); // PClientSourceParent mozilla::ipc::IPCResult + RecvWorkerSyncPing() override; + + mozilla::ipc::IPCResult RecvTeardown() override; mozilla::ipc::IPCResult RecvExecutionReady(const ClientSourceExecutionReadyArgs& aArgs) override; mozilla::ipc::IPCResult RecvFreeze() override;
--- a/dom/clients/manager/PClientSource.ipdl +++ b/dom/clients/manager/PClientSource.ipdl @@ -15,16 +15,17 @@ namespace dom { sync protocol PClientSource { manager PClientManager; manages PClientSourceOp; parent: + sync WorkerSyncPing(); async Teardown(); async ExecutionReady(ClientSourceExecutionReadyArgs aArgs); async Freeze(); async Thaw(); child: async PClientSourceOp(ClientOpConstructorArgs aArgs);
--- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -565,16 +565,17 @@ class ScriptLoaderRunnable final : publi friend class CachePromiseHandler; friend class CacheScriptLoader; friend class LoaderListener; WorkerPrivate* mWorkerPrivate; nsCOMPtr<nsIEventTarget> mSyncLoopTarget; nsTArray<ScriptLoadInfo> mLoadInfos; RefPtr<CacheCreator> mCacheCreator; + Maybe<ServiceWorkerDescriptor> mController; bool mIsMainScript; WorkerScriptType mWorkerScriptType; bool mCanceled; bool mCanceledMainThread; ErrorResult& mRv; public: NS_DECL_THREADSAFE_ISUPPORTS @@ -1211,16 +1212,20 @@ private: mWorkerPrivate->SetReferrerPolicyFromHeaderValue(tRPHeaderCValue); WorkerPrivate* parent = mWorkerPrivate->GetParent(); if (parent) { // XHR Params Allowed mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed()); } + + if (chanLoadInfo) { + mController = chanLoadInfo->GetController(); + } } return NS_OK; } void DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString, uint32_t aStringLen, @@ -1977,18 +1982,21 @@ ScriptExecutorRunnable::WorkerRun(JSCont // Top level scripts only! if (mIsWorkerScript) { aWorkerPrivate->MaybeDispatchLoadFailedRunnable(); } return true; } // If this is a top level script that succeeded, then mark the - // Client execution ready. + // Client execution ready and possible controlled by a service worker. if (mIsWorkerScript) { + if (mScriptLoader.mController.isSome()) { + aWorkerPrivate->Control(mScriptLoader.mController.ref()); + } aWorkerPrivate->ExecutionReady(); } NS_ConvertUTF16toUTF8 filename(loadInfo.mURL); JS::CompileOptions options(aCx); options.setFileAndLine(filename.get(), 1) .setNoScriptRval(true);
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -32,16 +32,18 @@ #include "mozilla/BasePrincipal.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/ErrorNames.h" #include "mozilla/LoadContext.h" #include "mozilla/SystemGroup.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/ClientHandle.h" +#include "mozilla/dom/ClientManager.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ErrorEvent.h" #include "mozilla/dom/Headers.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/NotificationEvent.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Request.h" @@ -2319,35 +2321,57 @@ ServiceWorkerManager::MaybeCheckNavigati // algorithm. RefPtr<ServiceWorkerRegistrationInfo> registration; mControlledDocuments.Get(aDoc, getter_AddRefs(registration)); if (registration) { registration->MaybeScheduleUpdate(); } } -void +RefPtr<GenericPromise> ServiceWorkerManager::StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration, nsIDocument* aDoc, const nsAString& aDocumentId) { 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); if (!aDocumentId.IsEmpty()) { aDoc->SetId(aDocumentId); } + + // 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(); + nsPIDOMWindowInner* innerWindow = aDoc->GetInnerWindow(); + if (activeWorker && innerWindow) { + Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo(); + if (clientInfo.isSome()) { + RefPtr<ClientHandle> clientHandle = + ClientManager::CreateHandle(clientInfo.ref(), + SystemGroup::EventTargetFor(TaskCategory::Other)); + if (clientHandle) { + ref = Move(clientHandle->Control(activeWorker->Descriptor())); + } + } + } + Telemetry::Accumulate(Telemetry::SERVICE_WORKER_CONTROLLED_DOCUMENTS, 1); + return Move(ref); } void ServiceWorkerManager::StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration) { aRegistration->StopControllingADocument(); if (aRegistration->IsControllingDocuments() || !aRegistration->IsIdle()) { return; @@ -2648,16 +2672,58 @@ ServiceWorkerManager::DispatchFetchEvent // before we get to this point. Therefore we must handle a nullptr // active worker here. serviceWorker = registration->GetActive(); if (!serviceWorker) { aRv.Throw(NS_ERROR_FAILURE); return; } + // If there is a reserved client it should be marked as controlled before + // the FetchEvent is dispatched. + nsCOMPtr<nsILoadInfo> loadInfo = internalChannel->GetLoadInfo(); + if (loadInfo) { + Maybe<ClientInfo> clientInfo = loadInfo->GetReservedClientInfo(); + + // Also override the initial about:blank controller since the real + // network load may be intercepted by a different service worker. If + // the intial about:blank has a controller here its simply been + // inherited from its parent. + if (clientInfo.isNothing()) { + clientInfo = loadInfo->GetInitialClientInfo(); + + // TODO: We need to handle the case where the initial about:blank is + // controlled, but the final document load is not. Right now + // the spec does not really say what to do. There currently + // is no way for the controller to be cleared from a client in + // the spec or our implementation. We may want to force a + // new inner window to be created instead of reusing the + // initial about:blank global. See bug 1419620 and the spec + // issue here: https://github.com/w3c/ServiceWorker/issues/1232 + } + + if (clientInfo.isSome()) { + // 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)); + if (clientHandle) { + clientHandle->Control(serviceWorker->Descriptor()); + } + } + + // But we also note the reserved state on the LoadInfo. This allows the + // ClientSource to be updated immediately after the nsIChannel starts. + // This is necessary to have the correct controller in place for immediate + // follow-on requests. + loadInfo->SetController(serviceWorker->Descriptor()); + } + AddNavigationInterception(serviceWorker->Scope(), aChannel); } if (NS_WARN_IF(aRv.Failed())) { return; } MOZ_DIAGNOSTIC_ASSERT(serviceWorker);
--- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -10,16 +10,17 @@ #include "nsIServiceWorkerManager.h" #include "nsCOMPtr.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "mozilla/ConsoleReportCollector.h" #include "mozilla/LinkedList.h" +#include "mozilla/MozPromise.h" #include "mozilla/Preferences.h" #include "mozilla/TypedEnumBits.h" #include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorkerCommon.h" #include "mozilla/dom/ServiceWorkerRegistrar.h" @@ -385,17 +386,17 @@ private: WhichServiceWorker aWhichOne); void InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration, WhichServiceWorker aWhichOnes); void NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration); - void + RefPtr<GenericPromise> StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration, nsIDocument* aDoc, const nsAString& aDocumentId); void StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration); already_AddRefed<ServiceWorkerRegistrationInfo>
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -5328,28 +5328,61 @@ WorkerPrivate::EnsureClientSource() if (!mClientSource) { return false; } if (mFrozen) { mClientSource->Freeze(); } + // Shortly after the client is reserved we will try loading the main script + // for the worker. This may get intercepted by the ServiceWorkerManager + // which will then try to create a ClientHandle. Its actually possible for + // the main thread to create this ClientHandle before our IPC message creating + // the ClientSource completes. To avoid this race we synchronously ping our + // parent Client actor here. This ensure the worker ClientSource is created + // in the parent before the main thread might try reaching it with a + // ClientHandle. + // + // An alternative solution would have been to handle the out-of-order operations + // on the parent side. We could have created a small window where we allow + // ClientHandle objects to exist without a ClientSource. We would then time + // out these handles if they stayed orphaned for too long. This approach would + // be much more complex, but also avoid this extra bit of latency when starting + // workers. + // + // Note, we only have to do this for workers that can be controlled by a + // service worker. So avoid the sync overhead here if we are starting a + // service worker or a chrome worker. + if (Type() != WorkerTypeService && !IsChromeWorker()) { + mClientSource->WorkerSyncPing(this); + } + return true; } const ClientInfo& WorkerPrivate::GetClientInfo() const { AssertIsOnWorkerThread(); MOZ_DIAGNOSTIC_ASSERT(mClientSource); return mClientSource->Info(); } void +WorkerPrivate::Control(const ServiceWorkerDescriptor& aServiceWorker) +{ + AssertIsOnWorkerThread(); + MOZ_DIAGNOSTIC_ASSERT(mClientSource); + MOZ_DIAGNOSTIC_ASSERT(!IsChromeWorker()); + MOZ_DIAGNOSTIC_ASSERT(Type() != WorkerTypeService); + mClientSource->SetController(aServiceWorker); +} + +void WorkerPrivate::ExecutionReady() { AssertIsOnWorkerThread(); MOZ_DIAGNOSTIC_ASSERT(mClientSource); mClientSource->WorkerExecutionReady(this); } void
--- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1499,16 +1499,19 @@ public: bool EnsureClientSource(); const ClientInfo& GetClientInfo() const; void + Control(const ServiceWorkerDescriptor& aServiceWorker); + + void ExecutionReady(); private: WorkerPrivate(WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker, WorkerType aWorkerType, const nsAString& aWorkerName, const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo);
--- a/ipc/ipdl/sync-messages.ini +++ b/ipc/ipdl/sync-messages.ini @@ -1054,8 +1054,10 @@ description = [PHandlerService::ExistsForProtocol] description = bug 1382323 [PHandlerService::Exists] description = [PHandlerService::GetTypeFromExtension] description = [PLayerTransaction::ShutdownSync] description = bug 1363126 +[PClientSource::WorkerSyncPing] +description = Synchronous ping allowing worker thread to confirm actor is created. Necessary to avoid racing with ClientHandle actors on main thread.