/* -*- 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 "ServiceWorkerContainer.h" #include "nsContentUtils.h" #include "nsIDocument.h" #include "nsIServiceWorkerManager.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsPIDOMWindow.h" #include "mozilla/Services.h" #include "nsCycleCollectionParticipant.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/DOMPrefs.h" #include "mozilla/dom/Navigator.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerContainerBinding.h" #include "ServiceWorker.h" namespace mozilla { namespace dom { NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerContainer) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorkerContainer, DOMEventTargetHelper, mControllerWorker, mReadyPromise) /* static */ bool ServiceWorkerContainer::IsEnabled(JSContext* aCx, JSObject* aGlobal) { MOZ_ASSERT(NS_IsMainThread()); JS::Rooted global(aCx, aGlobal); nsCOMPtr window = Navigator::GetWindowFromGlobal(global); if (!window) { return false; } nsIDocument* doc = window->GetExtantDoc(); if (!doc || nsContentUtils::IsInPrivateBrowsing(doc)) { return false; } return DOMPrefs::ServiceWorkersEnabled(); } ServiceWorkerContainer::ServiceWorkerContainer(nsPIDOMWindowInner* aWindow) : DOMEventTargetHelper(aWindow) { } ServiceWorkerContainer::~ServiceWorkerContainer() { RemoveReadyPromise(); } void ServiceWorkerContainer::DisconnectFromOwner() { mControllerWorker = nullptr; RemoveReadyPromise(); DOMEventTargetHelper::DisconnectFromOwner(); } void ServiceWorkerContainer::ControllerChanged(ErrorResult& aRv) { mControllerWorker = nullptr; aRv = DispatchTrustedEvent(NS_LITERAL_STRING("controllerchange")); } void ServiceWorkerContainer::RemoveReadyPromise() { if (nsCOMPtr window = GetOwner()) { nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { // If the browser is shutting down, we don't need to remove the promise. return; } swm->RemoveReadyPromise(window); } } JSObject* ServiceWorkerContainer::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return ServiceWorkerContainerBinding::Wrap(aCx, this, aGivenProto); } static nsresult CheckForSlashEscapedCharsInPath(nsIURI* aURI) { MOZ_ASSERT(aURI); // A URL that can't be downcast to a standard URL is an invalid URL and should // be treated as such and fail with SecurityError. nsCOMPtr url(do_QueryInterface(aURI)); if (NS_WARN_IF(!url)) { return NS_ERROR_DOM_SECURITY_ERR; } nsAutoCString path; nsresult rv = url->GetFilePath(path); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } ToLowerCase(path); if (path.Find("%2f") != kNotFound || path.Find("%5c") != kNotFound) { return NS_ERROR_DOM_TYPE_ERR; } return NS_OK; } already_AddRefed ServiceWorkerContainer::Register(const nsAString& aScriptURL, const RegistrationOptions& aOptions, ErrorResult& aRv) { nsCOMPtr promise; nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr baseURI; nsCOMPtr window = GetOwner(); if (window) { baseURI = window->GetDocBaseURI(); } else { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } nsresult rv; nsCOMPtr scriptURI; rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.ThrowTypeError(aScriptURL); return nullptr; } aRv = CheckForSlashEscapedCharsInPath(scriptURI); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // In ServiceWorkerContainer.register() the scope argument is parsed against // different base URLs depending on whether it was passed or not. nsCOMPtr scopeURI; // Step 4. If none passed, parse against script's URL if (!aOptions.mScope.WasPassed()) { NS_NAMED_LITERAL_STRING(defaultScope, "./"); rv = NS_NewURI(getter_AddRefs(scopeURI), defaultScope, nullptr, scriptURI); if (NS_WARN_IF(NS_FAILED(rv))) { nsAutoCString spec; scriptURI->GetSpec(spec); NS_ConvertUTF8toUTF16 wSpec(spec); aRv.ThrowTypeError(defaultScope, wSpec); return nullptr; } } else { // Step 5. Parse against entry settings object's base URL. rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), nullptr, baseURI); if (NS_WARN_IF(NS_FAILED(rv))) { nsIURI* uri = baseURI ? baseURI : scriptURI; nsAutoCString spec; uri->GetSpec(spec); NS_ConvertUTF8toUTF16 wSpec(spec); aRv.ThrowTypeError(aOptions.mScope.Value(), wSpec); return nullptr; } aRv = CheckForSlashEscapedCharsInPath(scopeURI); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } // The spec says that the "client" passed to Register() must be the global // where the ServiceWorkerContainer was retrieved from. aRv = swm->Register(GetOwner(), scopeURI, scriptURI, static_cast(aOptions.mUpdateViaCache), getter_AddRefs(promise)); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RefPtr ret = static_cast(promise.get()); MOZ_ASSERT(ret); return ret.forget(); } already_AddRefed ServiceWorkerContainer::GetController() { if (!mControllerWorker) { // If we don't have a controller reference cached, then we need to // check if we should create one. We try to do this in a thread-agnostic // way here to help support workers in the future. There are still // some main thread calls for now, though. nsIGlobalObject* owner = GetOwnerGlobal(); NS_ENSURE_TRUE(owner, nullptr); Maybe controller(owner->GetController()); if (controller.isNothing()) { return nullptr; } RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { return nullptr; } // This is a main thread only call. We will need to replace it with // something for worker threads. RefPtr reg = swm->GetRegistration(controller.ref().PrincipalInfo(), controller.ref().Scope()); NS_ENSURE_TRUE(reg, nullptr); ServiceWorkerInfo* info = reg->GetActive(); NS_ENSURE_TRUE(info, nullptr); nsCOMPtr inner = do_QueryInterface(owner); NS_ENSURE_TRUE(inner, nullptr); // Right now we only know how to create ServiceWorker DOM objects on // the main thread with a window. In the future this should operate // on only nsIGlobalObject somehow. mControllerWorker = inner->GetOrCreateServiceWorker(info->Descriptor()); } RefPtr ref = mControllerWorker; return ref.forget(); } already_AddRefed ServiceWorkerContainer::GetRegistrations(ErrorResult& aRv) { nsresult rv; nsCOMPtr swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } nsCOMPtr promise; aRv = swm->GetRegistrations(GetOwner(), getter_AddRefs(promise)); if (aRv.Failed()) { return nullptr; } RefPtr ret = static_cast(promise.get()); MOZ_ASSERT(ret); return ret.forget(); } already_AddRefed ServiceWorkerContainer::GetRegistration(const nsAString& aDocumentURL, ErrorResult& aRv) { nsresult rv; nsCOMPtr swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } nsCOMPtr promise; aRv = swm->GetRegistration(GetOwner(), aDocumentURL, getter_AddRefs(promise)); if (aRv.Failed()) { return nullptr; } RefPtr ret = static_cast(promise.get()); MOZ_ASSERT(ret); return ret.forget(); } Promise* ServiceWorkerContainer::GetReady(ErrorResult& aRv) { if (mReadyPromise) { return mReadyPromise; } nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr promise; aRv = swm->GetReadyPromise(GetOwner(), getter_AddRefs(promise)); mReadyPromise = static_cast(promise.get()); return mReadyPromise; } // Testing only. void ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl, nsString& aScope, ErrorResult& aRv) { nsCOMPtr swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { aRv.Throw(NS_ERROR_FAILURE); return; } nsCOMPtr window = GetOwner(); if (NS_WARN_IF(!window)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } nsCOMPtr doc = window->GetExtantDoc(); if (NS_WARN_IF(!doc)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } aRv = swm->GetScopeForUrl(doc->NodePrincipal(), aUrl, aScope); } } // namespace dom } // namespace mozilla