/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ServiceWorkerClient.h" #include "ServiceWorkerContainer.h" #include "mozilla/dom/MessageEvent.h" #include "nsGlobalWindow.h" #include "nsIDocument.h" #include "WorkerPrivate.h" #include "WorkerStructuredClone.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::workers; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner) NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient) NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc) : mWindowId(0) { MOZ_ASSERT(aDoc); nsresult rv = aDoc->GetId(mClientId); if (NS_FAILED(rv)) { NS_WARNING("Failed to get the UUID of the document."); } nsRefPtr innerWindow = static_cast(aDoc->GetInnerWindow()); if (innerWindow) { // XXXcatalinb: The inner window can be null if the document is navigating // and was detached. mWindowId = innerWindow->WindowID(); } aDoc->GetURL(mUrl); mVisibilityState = aDoc->VisibilityState(); ErrorResult result; mFocused = aDoc->HasFocus(result); if (result.Failed()) { NS_WARNING("Failed to get focus information."); } nsRefPtr outerWindow = static_cast(aDoc->GetWindow()); MOZ_ASSERT(outerWindow); if (!outerWindow->IsTopLevelWindow()) { mFrameType = FrameType::Nested; } else if (outerWindow->HadOriginalOpener()) { mFrameType = FrameType::Auxiliary; } else { mFrameType = FrameType::Top_level; } } JSObject* ServiceWorkerClient::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return ClientBinding::Wrap(aCx, this, aGivenProto); } namespace { class ServiceWorkerClientPostMessageRunnable final : public nsRunnable { uint64_t mWindowId; JSAutoStructuredCloneBuffer mBuffer; WorkerStructuredCloneClosure mClosure; public: ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId, JSAutoStructuredCloneBuffer&& aData, WorkerStructuredCloneClosure& aClosure) : mWindowId(aWindowId), mBuffer(Move(aData)) { mClosure.mClonedObjects.SwapElements(aClosure.mClonedObjects); MOZ_ASSERT(aClosure.mMessagePorts.IsEmpty()); mClosure.mMessagePortIdentifiers.SwapElements(aClosure.mMessagePortIdentifiers); } NS_IMETHOD Run() { AssertIsOnMainThread(); nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId); if (!window) { return NS_ERROR_FAILURE; } ErrorResult result; dom::Navigator* navigator = window->GetNavigator(result); if (NS_WARN_IF(result.Failed())) { return result.StealNSResult(); } nsRefPtr container = navigator->ServiceWorker(); AutoJSAPI jsapi; jsapi.Init(window); JSContext* cx = jsapi.cx(); return DispatchDOMEvent(cx, container); } private: NS_IMETHOD DispatchDOMEvent(JSContext* aCx, ServiceWorkerContainer* aTargetContainer) { AssertIsOnMainThread(); // Release reference to objects that were AddRef'd for // cloning into worker when array goes out of scope. WorkerStructuredCloneClosure closure; closure.mClonedObjects.SwapElements(mClosure.mClonedObjects); MOZ_ASSERT(mClosure.mMessagePorts.IsEmpty()); closure.mMessagePortIdentifiers.SwapElements(mClosure.mMessagePortIdentifiers); JS::Rooted messageData(aCx); if (!mBuffer.read(aCx, &messageData, WorkerStructuredCloneCallbacks(true))) { xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); return NS_ERROR_FAILURE; } nsCOMPtr event = new MessageEvent(aTargetContainer, nullptr, nullptr); nsresult rv = event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, false /* not cancelable */, messageData, EmptyString(), EmptyString(), nullptr); if (NS_FAILED(rv)) { xpc::Throw(aCx, rv); return NS_ERROR_FAILURE; } event->SetTrusted(true); bool status = false; aTargetContainer->DispatchEvent(event, &status); if (!status) { return NS_ERROR_FAILURE; } return NS_OK; } }; } // anonymous namespace void ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle aMessage, const Optional>& aTransferable, ErrorResult& aRv) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); workerPrivate->AssertIsOnWorkerThread(); JS::Rooted transferable(aCx, JS::UndefinedValue()); if (aTransferable.WasPassed()) { const Sequence& realTransferable = aTransferable.Value(); JS::HandleValueArray elements = JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(), realTransferable.Elements()); JSObject* array = JS_NewArrayObject(aCx, elements); if (!array) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } transferable.setObject(*array); } const JSStructuredCloneCallbacks* callbacks = WorkerStructuredCloneCallbacks(false); WorkerStructuredCloneClosure closure; JSAutoStructuredCloneBuffer buffer; if (!buffer.write(aCx, aMessage, transferable, callbacks, &closure)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } nsRefPtr runnable = new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer), closure); nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { aRv.Throw(NS_ERROR_FAILURE); } }