/* 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 "ServiceWorkerManager.h" #include "nsIDocument.h" #include "nsPIDOMWindow.h" #include "jsapi.h" #include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "nsContentUtils.h" #include "nsCxPusher.h" #include "nsNetUtil.h" #include "nsTArray.h" #include "RuntimeService.h" #include "ServiceWorker.h" #include "WorkerInlines.h" using namespace mozilla; using namespace mozilla::dom; BEGIN_WORKERS_NAMESPACE ////////////////////////// // ServiceWorkerManager // ////////////////////////// NS_IMPL_ADDREF(ServiceWorkerManager) NS_IMPL_RELEASE(ServiceWorkerManager) NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager) NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager) if (aIID.Equals(NS_GET_IID(ServiceWorkerManager))) foundInterface = static_cast(this); else NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIServiceWorkerManager) NS_INTERFACE_MAP_END ServiceWorkerManager::ServiceWorkerManager() { } ServiceWorkerManager::~ServiceWorkerManager() { // The map will assert if it is not empty when destroyed. mDomainMap.EnumerateRead(CleanupServiceWorkerInformation, nullptr); mDomainMap.Clear(); } /* static */ PLDHashOperator ServiceWorkerManager::CleanupServiceWorkerInformation(const nsACString& aDomain, ServiceWorkerDomainInfo* aDomainInfo, void *aUnused) { aDomainInfo->mServiceWorkerRegistrations.Clear(); return PL_DHASH_NEXT; } /* * Implements the async aspects of the register algorithm. */ class RegisterRunnable : public nsRunnable { nsCOMPtr mWindow; const nsCString mScope; nsCOMPtr mScriptURI; nsRefPtr mPromise; public: RegisterRunnable(nsPIDOMWindow* aWindow, const nsCString aScope, nsIURI* aScriptURI, Promise* aPromise) : mWindow(aWindow), mScope(aScope), mScriptURI(aScriptURI), mPromise(aPromise) { } NS_IMETHODIMP Run() { nsCString domain; nsresult rv = mScriptURI->GetHost(domain); if (NS_WARN_IF(NS_FAILED(rv))) { mPromise->MaybeReject(rv); return NS_OK; } nsRefPtr swm = ServiceWorkerManager::GetInstance(); ServiceWorkerManager::ServiceWorkerDomainInfo* domainInfo = swm->mDomainMap.Get(domain); // FIXME(nsm): Refactor this pattern. if (!swm->mDomainMap.Get(domain, &domainInfo)) { domainInfo = new ServiceWorkerManager::ServiceWorkerDomainInfo; swm->mDomainMap.Put(domain, domainInfo); } nsRefPtr registration = domainInfo->GetRegistration(mScope); nsCString spec; rv = mScriptURI->GetSpec(spec); if (NS_WARN_IF(NS_FAILED(rv))) { mPromise->MaybeReject(rv); return NS_OK; } if (registration) { registration->mPendingUninstall = false; if (spec.Equals(registration->mScriptSpec)) { // FIXME(nsm): Force update on Shift+Reload. Algorithm not specified for // that yet. // There is an existing update in progress. Resolve with whatever it // results in. if (registration->HasUpdatePromise()) { registration->AddUpdatePromiseObserver(mPromise); return NS_OK; } // There is no update in progress and since SW updating is upto the UA, we // will not update right now. Simply resolve with whatever worker we // have. ServiceWorkerInfo info = registration->Newest(); if (info.IsValid()) { nsRefPtr serviceWorker; nsresult rv = swm->CreateServiceWorkerForWindow(mWindow, info.GetScriptSpec(), registration->mScope, getter_AddRefs(serviceWorker)); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_FAILURE; } mPromise->MaybeResolve(serviceWorker); return NS_OK; } } } else { registration = domainInfo->CreateNewRegistration(mScope); } registration->mScriptSpec = spec; // FIXME(nsm): Call Update. Same bug, different patch. // For now if the registration reaches this spot, the promise remains // unresolved. return NS_OK; } }; // If we return an error code here, the ServiceWorkerContainer will // automatically reject the Promise. NS_IMETHODIMP ServiceWorkerManager::Register(nsIDOMWindow* aWindow, const nsAString& aScope, const nsAString& aScriptURL, nsISupports** aPromise) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); // XXXnsm Don't allow chrome callers for now, we don't support chrome // ServiceWorkers. MOZ_ASSERT(!nsContentUtils::IsCallerChrome()); nsCOMPtr window = do_QueryInterface(aWindow); if (!window) { return NS_ERROR_FAILURE; } nsCOMPtr sgo = do_QueryInterface(window); nsRefPtr promise = new Promise(sgo); nsCOMPtr documentURI = window->GetDocumentURI(); if (!documentURI) { return NS_ERROR_FAILURE; } // Although the spec says that the same-origin checks should also be done // asynchronously, we do them in sync because the Promise created by the // WebIDL infrastructure due to a returned error will be resolved // asynchronously. We aren't making any internal state changes in these // checks, so ordering of multiple calls is not affected. nsresult rv; // FIXME(nsm): Bug 1003991. Disable check when devtools are open. if (!Preferences::GetBool("dom.serviceWorkers.testing.enabled")) { bool isHttps; rv = documentURI->SchemeIs("https", &isHttps); if (NS_FAILED(rv) || !isHttps) { NS_WARNING("ServiceWorker registration from insecure websites is not allowed."); return NS_ERROR_DOM_SECURITY_ERR; } } nsCOMPtr documentPrincipal; if (window->GetExtantDoc()) { documentPrincipal = window->GetExtantDoc()->NodePrincipal(); } else { documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1"); } nsCOMPtr scriptURI; rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, documentURI); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // https://github.com/slightlyoff/ServiceWorker/issues/262 // allowIfInheritsPrincipal: allow data: URLs for now. rv = documentPrincipal->CheckMayLoad(scriptURI, true /* report */, true /* allowIfInheritsPrincipal */); if (NS_FAILED(rv)) { return NS_ERROR_DOM_SECURITY_ERR; } nsCOMPtr scopeURI; rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, documentURI); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_SECURITY_ERR; } rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */, false /* allowIfInheritsPrinciple */); if (NS_FAILED(rv)) { return NS_ERROR_DOM_SECURITY_ERR; } nsCString cleanedScope; rv = scopeURI->GetSpec(cleanedScope); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_FAILURE; } nsRefPtr registerRunnable = new RegisterRunnable(window, cleanedScope, scriptURI, promise); promise.forget(aPromise); return NS_DispatchToCurrentThread(registerRunnable); } NS_IMETHODIMP ServiceWorkerManager::Update(ServiceWorkerRegistration* aRegistration, nsPIDOMWindow* aWindow) { // FIXME(nsm): Same bug, different patch. return NS_OK; } // If we return an error, ServiceWorkerContainer will reject the Promise. NS_IMETHODIMP ServiceWorkerManager::Unregister(nsIDOMWindow* aWindow, const nsAString& aScope, nsISupports** aPromise) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); // XXXnsm Don't allow chrome callers for now. MOZ_ASSERT(!nsContentUtils::IsCallerChrome()); // FIXME(nsm): Same bug, different patch. return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } /* static */ already_AddRefed ServiceWorkerManager::GetInstance() { nsCOMPtr swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID); nsRefPtr concrete = do_QueryObject(swm); return concrete.forget(); } NS_IMETHODIMP ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow, const nsACString& aScriptSpec, const nsACString& aScope, ServiceWorker** aServiceWorker) { AssertIsOnMainThread(); MOZ_ASSERT(aWindow); RuntimeService* rs = RuntimeService::GetOrCreateService(); nsRefPtr serviceWorker; nsCOMPtr sgo = do_QueryInterface(aWindow); AutoSafeJSContext cx; JS::Rooted jsGlobal(cx, sgo->GetGlobalJSObject()); JSAutoCompartment ac(cx, jsGlobal); GlobalObject global(cx, jsGlobal); nsresult rv = rs->CreateServiceWorker(global, NS_ConvertUTF8toUTF16(aScriptSpec), aScope, getter_AddRefs(serviceWorker)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } serviceWorker.forget(aServiceWorker); return rv; } END_WORKERS_NAMESPACE