gecko/dom/workers/ServiceWorkerManager.cpp

307 lines
9.4 KiB
C++

/* 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<nsIServiceWorkerManager*>(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<nsPIDOMWindow> mWindow;
const nsCString mScope;
nsCOMPtr<nsIURI> mScriptURI;
nsRefPtr<Promise> 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<ServiceWorkerManager> 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<ServiceWorkerRegistration> 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> 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<nsPIDOMWindow> window = do_QueryInterface(aWindow);
if (!window) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
nsRefPtr<Promise> promise = new Promise(sgo);
nsCOMPtr<nsIURI> 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<nsIPrincipal> documentPrincipal;
if (window->GetExtantDoc()) {
documentPrincipal = window->GetExtantDoc()->NodePrincipal();
} else {
documentPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
}
nsCOMPtr<nsIURI> 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<nsIURI> 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<nsIRunnable> 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>
ServiceWorkerManager::GetInstance()
{
nsCOMPtr<nsIServiceWorkerManager> swm = do_GetService(SERVICEWORKERMANAGER_CONTRACTID);
nsRefPtr<ServiceWorkerManager> 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> serviceWorker;
nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(aWindow);
AutoSafeJSContext cx;
JS::Rooted<JSObject*> 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