Loading dom/base/nsDocument.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,8 @@ #include "mozilla/dom/FeaturePolicy.h" #include "mozilla/dom/FramingChecker.h" #include "mozilla/dom/HTMLSharedElement.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/SVGUseElement.h" #include "nsGenericHTMLElement.h" #include "mozilla/dom/CDATASection.h" Loading Loading @@ -5195,6 +5197,14 @@ nsIDocument::DispatchContentLoadedEvents() NS_LITERAL_STRING("DOMContentLoaded"), CanBubble::eYes, Cancelable::eNo); if (auto* const window = GetInnerWindow()) { const RefPtr<ServiceWorkerContainer> serviceWorker = window->Navigator()->ServiceWorker(); // This could cause queued messages from a service worker to get // dispatched on serviceWorker. serviceWorker->StartMessages(); } if (MayStartLayout()) { MaybeResolveReadyForIdle(); } Loading dom/clients/manager/ClientSource.cpp +8 −116 Original line number Diff line number Diff line Loading @@ -605,125 +605,17 @@ RefPtr<ClientOpPromise> ClientSource::PostMessage(const ClientPostMessageArgs& aArgs) { NS_ASSERT_OWNINGTHREAD(ClientSource); RefPtr<ClientOpPromise> ref; ServiceWorkerDescriptor source(aArgs.serviceWorker()); const PrincipalInfo& principalInfo = source.PrincipalInfo(); StructuredCloneData clonedData; clonedData.BorrowFromClonedMessageDataForBackgroundChild(aArgs.clonedData()); // Currently we only support firing these messages on window Clients. // Once we expose ServiceWorkerContainer and the ServiceWorker on Worker // threads then this will need to change. See bug 1113522. if (mClientInfo.Type() != ClientType::Window) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__); return ref.forget(); } MOZ_ASSERT(NS_IsMainThread()); RefPtr<ServiceWorkerContainer> target; nsCOMPtr<nsIGlobalObject> globalObject; // We don't need to force the creation of the about:blank document // here because there is no postMessage listener. If a listener // was registered then the document will already be created. nsPIDOMWindowInner* window = GetInnerWindow(); if (window) { globalObject = do_QueryInterface(window); target = window->Navigator()->ServiceWorker(); // TODO: Currently this function only supports clients whose global // object is a Window; it should also support those whose global // object is a WorkerGlobalScope. if (nsPIDOMWindowInner* const window = GetInnerWindow()) { const RefPtr<ServiceWorkerContainer> container = window->Navigator()->ServiceWorker(); container->ReceiveMessage(aArgs); return ClientOpPromise::CreateAndResolve(NS_OK, __func__).forget(); } if (NS_WARN_IF(!target)) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return ref.forget(); } // If AutoJSAPI::Init() fails then either global is nullptr or not // in a usable state. AutoJSAPI jsapi; if (!jsapi.Init(globalObject)) { ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } JSContext* cx = jsapi.cx(); ErrorResult result; JS::Rooted<JS::Value> messageData(cx); clonedData.Read(cx, &messageData, result); if (result.MaybeSetPendingException(cx)) { // We reported the error in the current window context. Resolve // promise instead of rejecting. ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } RootedDictionary<MessageEventInit> init(cx); init.mData = messageData; if (!clonedData.TakeTransferredPortsAsSequence(init.mPorts)) { // Report the error in the current window context and resolve the // promise instead of rejecting. xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY); ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } nsresult rv = NS_OK; nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_FAILED(rv) || !principal) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } nsAutoCString origin; rv = principal->GetOriginNoSuffix(origin); if (NS_SUCCEEDED(rv)) { CopyUTF8toUTF16(origin, init.mOrigin); } RefPtr<ServiceWorker> instance; if (ServiceWorkerParentInterceptEnabled()) { instance = globalObject->GetOrCreateServiceWorker(source); } else { // If we are in legacy child-side intercept mode then we need to verify // this registration exists in the current process. RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (!swm) { // Shutting down. Just don't deliver this message. ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } RefPtr<ServiceWorkerRegistrationInfo> reg = swm->GetRegistration(principal, source.Scope()); if (reg) { instance = globalObject->GetOrCreateServiceWorker(source); } } if (instance) { init.mSource.SetValue().SetAsServiceWorker() = instance; } RefPtr<MessageEvent> event = MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init); event->SetTrusted(true); target->DispatchEvent(*event, result); if (result.Failed()) { result.SuppressException(); ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); return ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__).forget(); } RefPtr<ClientOpPromise> Loading dom/serviceworkers/ServiceWorkerContainer.cpp +202 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ #include "nsIDocument.h" #include "nsIServiceWorkerManager.h" #include "nsIScriptError.h" #include "nsThreadUtils.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" Loading @@ -22,11 +23,16 @@ #include "nsServiceManagerUtils.h" #include "mozilla/LoadInfo.h" #include "mozilla/dom/ClientIPCTypes.h" #include "mozilla/dom/DOMMozPromiseRequestHolder.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerContainerBinding.h" #include "mozilla/dom/ServiceWorkerManager.h" #include "mozilla/dom/ipc/StructuredCloneData.h" #include "RemoteServiceWorkerContainerImpl.h" #include "ServiceWorker.h" Loading @@ -34,6 +40,11 @@ #include "ServiceWorkerRegistration.h" #include "ServiceWorkerUtils.h" // This is defined to something else on Windows #ifdef DispatchMessage #undef DispatchMessage #endif namespace mozilla { namespace dom { Loading Loading @@ -152,6 +163,39 @@ ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) aRv = DispatchTrustedEvent(NS_LITERAL_STRING("controllerchange")); } using mozilla::dom::ipc::StructuredCloneData; // A ReceivedMessage represents a message sent via // Client.postMessage(). It is used as used both for queuing of // incoming messages and as an interface to DispatchMessage(). struct MOZ_HEAP_CLASS ServiceWorkerContainer::ReceivedMessage { explicit ReceivedMessage(const ClientPostMessageArgs& aArgs) : mServiceWorker(aArgs.serviceWorker()) { mClonedData.CopyFromClonedMessageDataForBackgroundChild(aArgs.clonedData()); } ServiceWorkerDescriptor mServiceWorker; StructuredCloneData mClonedData; NS_INLINE_DECL_REFCOUNTING(ReceivedMessage) private: ~ReceivedMessage() = default; }; void ServiceWorkerContainer::ReceiveMessage(const ClientPostMessageArgs& aArgs) { RefPtr<ReceivedMessage> message = new ReceivedMessage(aArgs); if (mMessagesStarted) { EnqueueReceivedMessageDispatch(message.forget()); } else { mPendingMessages.AppendElement(message.forget()); } } JSObject* ServiceWorkerContainer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { Loading Loading @@ -426,6 +470,16 @@ ServiceWorkerContainer::GetRegistrations(ErrorResult& aRv) return outer.forget(); } void ServiceWorkerContainer::StartMessages() { while (!mPendingMessages.IsEmpty()) { EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0)); mPendingMessages.RemoveElementAt(0); } mMessagesStarted = true; } already_AddRefed<Promise> ServiceWorkerContainer::GetRegistration(const nsAString& aURL, ErrorResult& aRv) Loading Loading @@ -618,5 +672,153 @@ ServiceWorkerContainer::GetGlobalIfValid(ErrorResult& aRv, return window->AsGlobal(); } void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(RefPtr<ReceivedMessage> aMessage) { if (nsPIDOMWindowInner* const window = GetOwner()) { if (auto* const target = window->EventTargetFor(TaskCategory::Other)) { target->Dispatch( NewRunnableMethod<RefPtr<ReceivedMessage>>( "ServiceWorkerContainer::DispatchMessage", this, &ServiceWorkerContainer::DispatchMessage, std::move(aMessage) ) ); } } } template <typename F> void ServiceWorkerContainer::RunWithJSContext(F&& aCallable) { nsCOMPtr<nsIGlobalObject> globalObject; if (nsPIDOMWindowInner* const window = GetOwner()) { globalObject = do_QueryInterface(window); } // If AutoJSAPI::Init() fails then either global is nullptr or not // in a usable state. AutoJSAPI jsapi; if (!jsapi.Init(globalObject)) { return; } aCallable(jsapi.cx(), globalObject); } void ServiceWorkerContainer::DispatchMessage(RefPtr<ReceivedMessage> aMessage) { MOZ_ASSERT(NS_IsMainThread()); // When dispatching a message, either DOMContentLoaded has already // been fired, or someone called startMessages() or set onmessage. // Either way, a global object is supposed to be present. If it's // not, we'd fail to initialize the JS API and exit. RunWithJSContext([this, message = std::move(aMessage)](JSContext* const aCx, nsIGlobalObject* const aGlobal) { RootedDictionary<MessageEventInit> init(aCx); if (!FillInMessageEventInit(aCx, aGlobal, *message, init)) { // TODO: The spec requires us to fire a messageerror event here. return; } RefPtr<MessageEvent> event = MessageEvent::Constructor(this, NS_LITERAL_STRING("message"), init); event->SetTrusted(true); ErrorResult result; DispatchEvent(*event, result); if (result.Failed()) { result.SuppressException(); } }); } namespace { nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker, nsString& aOrigin) { using mozilla::ipc::PrincipalInfoToPrincipal; nsresult rv; nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo(), &rv); if (NS_FAILED(rv) || !principal) { return rv; } nsAutoCString originUTF8; rv = principal->GetOriginNoSuffix(originUTF8); if (NS_FAILED(rv)) { return rv; } CopyUTF8toUTF16(originUTF8, aOrigin); return NS_OK; } already_AddRefed<ServiceWorker> GetOrCreateServiceWorkerWithoutWarnings(nsIGlobalObject* const aGlobal, const ServiceWorkerDescriptor& aDescriptor) { // In child-intercept mode we have to verify that the registration // exists in the current process. This exact check is also performed // (indirectly) in nsIGlobalObject::GetOrCreateServiceWorker, but it // also emits a warning when the registration is not present. To // to avoid having too many warnings, we do a precheck here. if (!ServiceWorkerParentInterceptEnabled()) { const RefPtr<ServiceWorkerManager> serviceWorkerManager = ServiceWorkerManager::GetInstance(); if (!serviceWorkerManager) { return nullptr; } const RefPtr<ServiceWorkerRegistrationInfo> registration = serviceWorkerManager->GetRegistration(aDescriptor.PrincipalInfo(), aDescriptor.Scope()); if (!registration) { return nullptr; } } return aGlobal->GetOrCreateServiceWorker(aDescriptor).forget(); } } bool ServiceWorkerContainer::FillInMessageEventInit(JSContext* const aCx, nsIGlobalObject* const aGlobal, ReceivedMessage& aMessage, MessageEventInit& aInit) { ErrorResult result; JS::Rooted<JS::Value> messageData(aCx); aMessage.mClonedData.Read(aCx, &messageData, result); if (result.Failed()) { return false; } aInit.mData = messageData; if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) { return false; } const nsresult rv = FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin); if (NS_FAILED(rv)) { return false; } const RefPtr<ServiceWorker> serviceWorkerInstance = GetOrCreateServiceWorkerWithoutWarnings(aGlobal, aMessage.mServiceWorker); if (serviceWorkerInstance) { aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance; } return true; } } // namespace dom } // namespace mozilla dom/serviceworkers/ServiceWorkerContainer.h +49 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ class nsIGlobalWindow; namespace mozilla { namespace dom { class ClientPostMessageArgs; struct MessageEventInit; class Promise; struct RegistrationOptions; class ServiceWorker; Loading Loading @@ -64,7 +66,19 @@ public: IMPL_EVENT_HANDLER(controllerchange) IMPL_EVENT_HANDLER(error) IMPL_EVENT_HANDLER(message) // Almost a manual expansion of IMPL_EVENT_HANDLER(message), but // with the additional StartMessages() when setting the handler, as // required by the spec. inline mozilla::dom::EventHandlerNonNull* GetOnmessage() { return GetEventHandler(nsGkAtoms::onmessage); } inline void SetOnmessage(mozilla::dom::EventHandlerNonNull* aCallback) { SetEventHandler(nsGkAtoms::onmessage, aCallback); StartMessages(); } static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); Loading @@ -89,6 +103,9 @@ public: already_AddRefed<Promise> GetRegistrations(ErrorResult& aRv); void StartMessages(); Promise* GetReady(ErrorResult& aRv); Loading @@ -104,6 +121,9 @@ public: void ControllerChanged(ErrorResult& aRv); void ReceiveMessage(const ClientPostMessageArgs& aArgs); private: ServiceWorkerContainer(nsIGlobalObject* aGlobal, already_AddRefed<ServiceWorkerContainer::Inner> aInner); Loading @@ -121,6 +141,27 @@ private: GetGlobalIfValid(ErrorResult& aRv, const std::function<void(nsIDocument*)>&& aStorageFailureCB = nullptr) const; struct ReceivedMessage; // Dispatch a Runnable that dispatches the given message on this // object. When the owner of this object is a Window, the Runnable // is dispatched on the corresponding TabGroup. void EnqueueReceivedMessageDispatch(RefPtr<ReceivedMessage> aMessage); template <typename F> void RunWithJSContext(F&& aCallable); void DispatchMessage(RefPtr<ReceivedMessage> aMessage); static bool FillInMessageEventInit(JSContext* aCx, nsIGlobalObject* aGlobal, ReceivedMessage& aMessage, MessageEventInit& aInit); RefPtr<Inner> mInner; // This only changes when a worker hijacks everything in its scope by calling Loading @@ -129,6 +170,13 @@ private: RefPtr<Promise> mReadyPromise; MozPromiseRequestHolder<ServiceWorkerRegistrationPromise> mReadyPromiseHolder; // Set after StartMessages() has been called. bool mMessagesStarted = false; // Queue holding messages posted from service worker as long as // StartMessages() hasn't been called. nsTArray<RefPtr<ReceivedMessage>> mPendingMessages; }; } // namespace dom Loading dom/webidl/ServiceWorkerContainer.webidl +3 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html * https://w3c.github.io/ServiceWorker/#serviceworkercontainer * */ Loading @@ -29,6 +29,8 @@ interface ServiceWorkerContainer : EventTarget { [NewObject] Promise<sequence<ServiceWorkerRegistration>> getRegistrations(); void startMessages(); attribute EventHandler oncontrollerchange; attribute EventHandler onerror; attribute EventHandler onmessage; Loading Loading
dom/base/nsDocument.cpp +10 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,8 @@ #include "mozilla/dom/FeaturePolicy.h" #include "mozilla/dom/FramingChecker.h" #include "mozilla/dom/HTMLSharedElement.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/SVGUseElement.h" #include "nsGenericHTMLElement.h" #include "mozilla/dom/CDATASection.h" Loading Loading @@ -5195,6 +5197,14 @@ nsIDocument::DispatchContentLoadedEvents() NS_LITERAL_STRING("DOMContentLoaded"), CanBubble::eYes, Cancelable::eNo); if (auto* const window = GetInnerWindow()) { const RefPtr<ServiceWorkerContainer> serviceWorker = window->Navigator()->ServiceWorker(); // This could cause queued messages from a service worker to get // dispatched on serviceWorker. serviceWorker->StartMessages(); } if (MayStartLayout()) { MaybeResolveReadyForIdle(); } Loading
dom/clients/manager/ClientSource.cpp +8 −116 Original line number Diff line number Diff line Loading @@ -605,125 +605,17 @@ RefPtr<ClientOpPromise> ClientSource::PostMessage(const ClientPostMessageArgs& aArgs) { NS_ASSERT_OWNINGTHREAD(ClientSource); RefPtr<ClientOpPromise> ref; ServiceWorkerDescriptor source(aArgs.serviceWorker()); const PrincipalInfo& principalInfo = source.PrincipalInfo(); StructuredCloneData clonedData; clonedData.BorrowFromClonedMessageDataForBackgroundChild(aArgs.clonedData()); // Currently we only support firing these messages on window Clients. // Once we expose ServiceWorkerContainer and the ServiceWorker on Worker // threads then this will need to change. See bug 1113522. if (mClientInfo.Type() != ClientType::Window) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__); return ref.forget(); } MOZ_ASSERT(NS_IsMainThread()); RefPtr<ServiceWorkerContainer> target; nsCOMPtr<nsIGlobalObject> globalObject; // We don't need to force the creation of the about:blank document // here because there is no postMessage listener. If a listener // was registered then the document will already be created. nsPIDOMWindowInner* window = GetInnerWindow(); if (window) { globalObject = do_QueryInterface(window); target = window->Navigator()->ServiceWorker(); // TODO: Currently this function only supports clients whose global // object is a Window; it should also support those whose global // object is a WorkerGlobalScope. if (nsPIDOMWindowInner* const window = GetInnerWindow()) { const RefPtr<ServiceWorkerContainer> container = window->Navigator()->ServiceWorker(); container->ReceiveMessage(aArgs); return ClientOpPromise::CreateAndResolve(NS_OK, __func__).forget(); } if (NS_WARN_IF(!target)) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_DOM_INVALID_STATE_ERR, __func__); return ref.forget(); } // If AutoJSAPI::Init() fails then either global is nullptr or not // in a usable state. AutoJSAPI jsapi; if (!jsapi.Init(globalObject)) { ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } JSContext* cx = jsapi.cx(); ErrorResult result; JS::Rooted<JS::Value> messageData(cx); clonedData.Read(cx, &messageData, result); if (result.MaybeSetPendingException(cx)) { // We reported the error in the current window context. Resolve // promise instead of rejecting. ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } RootedDictionary<MessageEventInit> init(cx); init.mData = messageData; if (!clonedData.TakeTransferredPortsAsSequence(init.mPorts)) { // Report the error in the current window context and resolve the // promise instead of rejecting. xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY); ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); } nsresult rv = NS_OK; nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(principalInfo, &rv); if (NS_FAILED(rv) || !principal) { ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } nsAutoCString origin; rv = principal->GetOriginNoSuffix(origin); if (NS_SUCCEEDED(rv)) { CopyUTF8toUTF16(origin, init.mOrigin); } RefPtr<ServiceWorker> instance; if (ServiceWorkerParentInterceptEnabled()) { instance = globalObject->GetOrCreateServiceWorker(source); } else { // If we are in legacy child-side intercept mode then we need to verify // this registration exists in the current process. RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (!swm) { // Shutting down. Just don't deliver this message. ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } RefPtr<ServiceWorkerRegistrationInfo> reg = swm->GetRegistration(principal, source.Scope()); if (reg) { instance = globalObject->GetOrCreateServiceWorker(source); } } if (instance) { init.mSource.SetValue().SetAsServiceWorker() = instance; } RefPtr<MessageEvent> event = MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init); event->SetTrusted(true); target->DispatchEvent(*event, result); if (result.Failed()) { result.SuppressException(); ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); return ref.forget(); } ref = ClientOpPromise::CreateAndResolve(NS_OK, __func__); return ref.forget(); return ClientOpPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, __func__).forget(); } RefPtr<ClientOpPromise> Loading
dom/serviceworkers/ServiceWorkerContainer.cpp +202 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ #include "nsIDocument.h" #include "nsIServiceWorkerManager.h" #include "nsIScriptError.h" #include "nsThreadUtils.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" Loading @@ -22,11 +23,16 @@ #include "nsServiceManagerUtils.h" #include "mozilla/LoadInfo.h" #include "mozilla/dom/ClientIPCTypes.h" #include "mozilla/dom/DOMMozPromiseRequestHolder.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerContainerBinding.h" #include "mozilla/dom/ServiceWorkerManager.h" #include "mozilla/dom/ipc/StructuredCloneData.h" #include "RemoteServiceWorkerContainerImpl.h" #include "ServiceWorker.h" Loading @@ -34,6 +40,11 @@ #include "ServiceWorkerRegistration.h" #include "ServiceWorkerUtils.h" // This is defined to something else on Windows #ifdef DispatchMessage #undef DispatchMessage #endif namespace mozilla { namespace dom { Loading Loading @@ -152,6 +163,39 @@ ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) aRv = DispatchTrustedEvent(NS_LITERAL_STRING("controllerchange")); } using mozilla::dom::ipc::StructuredCloneData; // A ReceivedMessage represents a message sent via // Client.postMessage(). It is used as used both for queuing of // incoming messages and as an interface to DispatchMessage(). struct MOZ_HEAP_CLASS ServiceWorkerContainer::ReceivedMessage { explicit ReceivedMessage(const ClientPostMessageArgs& aArgs) : mServiceWorker(aArgs.serviceWorker()) { mClonedData.CopyFromClonedMessageDataForBackgroundChild(aArgs.clonedData()); } ServiceWorkerDescriptor mServiceWorker; StructuredCloneData mClonedData; NS_INLINE_DECL_REFCOUNTING(ReceivedMessage) private: ~ReceivedMessage() = default; }; void ServiceWorkerContainer::ReceiveMessage(const ClientPostMessageArgs& aArgs) { RefPtr<ReceivedMessage> message = new ReceivedMessage(aArgs); if (mMessagesStarted) { EnqueueReceivedMessageDispatch(message.forget()); } else { mPendingMessages.AppendElement(message.forget()); } } JSObject* ServiceWorkerContainer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { Loading Loading @@ -426,6 +470,16 @@ ServiceWorkerContainer::GetRegistrations(ErrorResult& aRv) return outer.forget(); } void ServiceWorkerContainer::StartMessages() { while (!mPendingMessages.IsEmpty()) { EnqueueReceivedMessageDispatch(mPendingMessages.ElementAt(0)); mPendingMessages.RemoveElementAt(0); } mMessagesStarted = true; } already_AddRefed<Promise> ServiceWorkerContainer::GetRegistration(const nsAString& aURL, ErrorResult& aRv) Loading Loading @@ -618,5 +672,153 @@ ServiceWorkerContainer::GetGlobalIfValid(ErrorResult& aRv, return window->AsGlobal(); } void ServiceWorkerContainer::EnqueueReceivedMessageDispatch(RefPtr<ReceivedMessage> aMessage) { if (nsPIDOMWindowInner* const window = GetOwner()) { if (auto* const target = window->EventTargetFor(TaskCategory::Other)) { target->Dispatch( NewRunnableMethod<RefPtr<ReceivedMessage>>( "ServiceWorkerContainer::DispatchMessage", this, &ServiceWorkerContainer::DispatchMessage, std::move(aMessage) ) ); } } } template <typename F> void ServiceWorkerContainer::RunWithJSContext(F&& aCallable) { nsCOMPtr<nsIGlobalObject> globalObject; if (nsPIDOMWindowInner* const window = GetOwner()) { globalObject = do_QueryInterface(window); } // If AutoJSAPI::Init() fails then either global is nullptr or not // in a usable state. AutoJSAPI jsapi; if (!jsapi.Init(globalObject)) { return; } aCallable(jsapi.cx(), globalObject); } void ServiceWorkerContainer::DispatchMessage(RefPtr<ReceivedMessage> aMessage) { MOZ_ASSERT(NS_IsMainThread()); // When dispatching a message, either DOMContentLoaded has already // been fired, or someone called startMessages() or set onmessage. // Either way, a global object is supposed to be present. If it's // not, we'd fail to initialize the JS API and exit. RunWithJSContext([this, message = std::move(aMessage)](JSContext* const aCx, nsIGlobalObject* const aGlobal) { RootedDictionary<MessageEventInit> init(aCx); if (!FillInMessageEventInit(aCx, aGlobal, *message, init)) { // TODO: The spec requires us to fire a messageerror event here. return; } RefPtr<MessageEvent> event = MessageEvent::Constructor(this, NS_LITERAL_STRING("message"), init); event->SetTrusted(true); ErrorResult result; DispatchEvent(*event, result); if (result.Failed()) { result.SuppressException(); } }); } namespace { nsresult FillInOriginNoSuffix(const ServiceWorkerDescriptor& aServiceWorker, nsString& aOrigin) { using mozilla::ipc::PrincipalInfoToPrincipal; nsresult rv; nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(aServiceWorker.PrincipalInfo(), &rv); if (NS_FAILED(rv) || !principal) { return rv; } nsAutoCString originUTF8; rv = principal->GetOriginNoSuffix(originUTF8); if (NS_FAILED(rv)) { return rv; } CopyUTF8toUTF16(originUTF8, aOrigin); return NS_OK; } already_AddRefed<ServiceWorker> GetOrCreateServiceWorkerWithoutWarnings(nsIGlobalObject* const aGlobal, const ServiceWorkerDescriptor& aDescriptor) { // In child-intercept mode we have to verify that the registration // exists in the current process. This exact check is also performed // (indirectly) in nsIGlobalObject::GetOrCreateServiceWorker, but it // also emits a warning when the registration is not present. To // to avoid having too many warnings, we do a precheck here. if (!ServiceWorkerParentInterceptEnabled()) { const RefPtr<ServiceWorkerManager> serviceWorkerManager = ServiceWorkerManager::GetInstance(); if (!serviceWorkerManager) { return nullptr; } const RefPtr<ServiceWorkerRegistrationInfo> registration = serviceWorkerManager->GetRegistration(aDescriptor.PrincipalInfo(), aDescriptor.Scope()); if (!registration) { return nullptr; } } return aGlobal->GetOrCreateServiceWorker(aDescriptor).forget(); } } bool ServiceWorkerContainer::FillInMessageEventInit(JSContext* const aCx, nsIGlobalObject* const aGlobal, ReceivedMessage& aMessage, MessageEventInit& aInit) { ErrorResult result; JS::Rooted<JS::Value> messageData(aCx); aMessage.mClonedData.Read(aCx, &messageData, result); if (result.Failed()) { return false; } aInit.mData = messageData; if (!aMessage.mClonedData.TakeTransferredPortsAsSequence(aInit.mPorts)) { return false; } const nsresult rv = FillInOriginNoSuffix(aMessage.mServiceWorker, aInit.mOrigin); if (NS_FAILED(rv)) { return false; } const RefPtr<ServiceWorker> serviceWorkerInstance = GetOrCreateServiceWorkerWithoutWarnings(aGlobal, aMessage.mServiceWorker); if (serviceWorkerInstance) { aInit.mSource.SetValue().SetAsServiceWorker() = serviceWorkerInstance; } return true; } } // namespace dom } // namespace mozilla
dom/serviceworkers/ServiceWorkerContainer.h +49 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ class nsIGlobalWindow; namespace mozilla { namespace dom { class ClientPostMessageArgs; struct MessageEventInit; class Promise; struct RegistrationOptions; class ServiceWorker; Loading Loading @@ -64,7 +66,19 @@ public: IMPL_EVENT_HANDLER(controllerchange) IMPL_EVENT_HANDLER(error) IMPL_EVENT_HANDLER(message) // Almost a manual expansion of IMPL_EVENT_HANDLER(message), but // with the additional StartMessages() when setting the handler, as // required by the spec. inline mozilla::dom::EventHandlerNonNull* GetOnmessage() { return GetEventHandler(nsGkAtoms::onmessage); } inline void SetOnmessage(mozilla::dom::EventHandlerNonNull* aCallback) { SetEventHandler(nsGkAtoms::onmessage, aCallback); StartMessages(); } static bool IsEnabled(JSContext* aCx, JSObject* aGlobal); Loading @@ -89,6 +103,9 @@ public: already_AddRefed<Promise> GetRegistrations(ErrorResult& aRv); void StartMessages(); Promise* GetReady(ErrorResult& aRv); Loading @@ -104,6 +121,9 @@ public: void ControllerChanged(ErrorResult& aRv); void ReceiveMessage(const ClientPostMessageArgs& aArgs); private: ServiceWorkerContainer(nsIGlobalObject* aGlobal, already_AddRefed<ServiceWorkerContainer::Inner> aInner); Loading @@ -121,6 +141,27 @@ private: GetGlobalIfValid(ErrorResult& aRv, const std::function<void(nsIDocument*)>&& aStorageFailureCB = nullptr) const; struct ReceivedMessage; // Dispatch a Runnable that dispatches the given message on this // object. When the owner of this object is a Window, the Runnable // is dispatched on the corresponding TabGroup. void EnqueueReceivedMessageDispatch(RefPtr<ReceivedMessage> aMessage); template <typename F> void RunWithJSContext(F&& aCallable); void DispatchMessage(RefPtr<ReceivedMessage> aMessage); static bool FillInMessageEventInit(JSContext* aCx, nsIGlobalObject* aGlobal, ReceivedMessage& aMessage, MessageEventInit& aInit); RefPtr<Inner> mInner; // This only changes when a worker hijacks everything in its scope by calling Loading @@ -129,6 +170,13 @@ private: RefPtr<Promise> mReadyPromise; MozPromiseRequestHolder<ServiceWorkerRegistrationPromise> mReadyPromiseHolder; // Set after StartMessages() has been called. bool mMessagesStarted = false; // Queue holding messages posted from service worker as long as // StartMessages() hasn't been called. nsTArray<RefPtr<ReceivedMessage>> mPendingMessages; }; } // namespace dom Loading
dom/webidl/ServiceWorkerContainer.webidl +3 −1 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. * * The origin of this IDL file is * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html * https://w3c.github.io/ServiceWorker/#serviceworkercontainer * */ Loading @@ -29,6 +29,8 @@ interface ServiceWorkerContainer : EventTarget { [NewObject] Promise<sequence<ServiceWorkerRegistration>> getRegistrations(); void startMessages(); attribute EventHandler oncontrollerchange; attribute EventHandler onerror; attribute EventHandler onmessage; Loading