mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-06 00:55:37 +00:00
307 lines
9.4 KiB
C++
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->GetSpecIgnoringRef(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
|