Bug 984050 - Persist ServiceWorkerRegistrations. r=bent, r=nsm

This commit is contained in:
Andrea Marchesini 2015-02-11 06:53:00 -05:00
parent 9f8d9fe372
commit 3557feb052
22 changed files with 1623 additions and 31 deletions

View File

@ -6,6 +6,7 @@
#include "domstubs.idl"
interface nsIDocument;
interface nsIPrincipal;
interface nsIURI;
[builtinclass, uuid(d4367ffe-e435-4195-95f8-0a51b1bbfdfb)]
@ -17,7 +18,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports
[noscript] void UnregisterFailed();
};
[builtinclass, uuid(430f02bf-63d0-44ab-9a59-7bd49c608949)]
[builtinclass, uuid(861b55e9-d6ac-47cf-a528-8590e9b44de6)]
interface nsIServiceWorkerManager : nsISupports
{
/**
@ -34,7 +35,8 @@ interface nsIServiceWorkerManager : nsISupports
* Unregister an existing ServiceWorker registration for `aScope`.
* It keeps aCallback alive until the operation is concluded.
*/
void unregister(in nsIServiceWorkerUnregisterCallback aCallback,
void unregister(in nsIPrincipal aPrincipal,
in nsIServiceWorkerUnregisterCallback aCallback,
in DOMString aScope);
// Returns a Promise

View File

@ -19,6 +19,7 @@ include DOMTypes;
include JavaScriptTypes;
include URIParams;
include PContentPermission;
include ServiceWorkerRegistrarTypes;
using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
@ -65,6 +66,11 @@ struct NativeKeyBinding
CommandInt[] richTextCommands;
};
struct BrowserConfiguration
{
ServiceWorkerRegistrationData[] serviceWorkerRegistrations;
};
union MaybeNativeKeyBinding
{
NativeKeyBinding;
@ -471,7 +477,7 @@ child:
nullable PRenderFrame renderFrame,
bool parentIsActive);
LoadURL(nsCString uri);
LoadURL(nsCString uri, BrowserConfiguration config);
CacheFileDescriptor(nsString path, FileDescriptor fd);

View File

@ -15,6 +15,7 @@
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/IntentionalCrash.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
#include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
#include "mozilla/plugins/PluginWidgetChild.h"
#include "mozilla/ipc/DocumentRendererChild.h"
@ -99,6 +100,7 @@
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using namespace mozilla::dom::workers;
using namespace mozilla::ipc;
using namespace mozilla::layers;
using namespace mozilla::layout;
@ -1566,7 +1568,7 @@ TabChild::ProvideWindowCommon(nsIDOMWindow* aOpener,
}
if (!urlToLoad.IsEmpty()) {
newChild->RecvLoadURL(urlToLoad);
newChild->RecvLoadURL(urlToLoad, BrowserConfiguration());
}
nsCOMPtr<nsIDOMWindow> win = do_GetInterface(newChild->WebNavigation());
@ -1717,11 +1719,12 @@ TabChild::IsRootContentDocument()
}
bool
TabChild::RecvLoadURL(const nsCString& uri)
TabChild::RecvLoadURL(const nsCString& aURI,
const BrowserConfiguration& aConfiguration)
{
SetProcessNameToAppName();
nsresult rv = WebNavigation()->LoadURI(NS_ConvertUTF8toUTF16(uri).get(),
nsresult rv = WebNavigation()->LoadURI(NS_ConvertUTF8toUTF16(aURI).get(),
nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_OWNER,
nullptr, nullptr, nullptr);
@ -1730,9 +1733,13 @@ TabChild::RecvLoadURL(const nsCString& uri)
}
#ifdef MOZ_CRASHREPORTER
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("URL"), uri);
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("URL"), aURI);
#endif
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm);
swm->LoadRegistrations(aConfiguration.serviceWorkerRegistrations());
return true;
}

View File

@ -312,7 +312,8 @@ public:
const ViewID& aViewId,
const bool& aIsRoot,
const ZoomConstraints& aConstraints) MOZ_OVERRIDE;
virtual bool RecvLoadURL(const nsCString& uri) MOZ_OVERRIDE;
virtual bool RecvLoadURL(const nsCString& aURI,
const BrowserConfiguration& aConfiguration) MOZ_OVERRIDE;
virtual bool RecvCacheFileDescriptor(const nsString& aPath,
const FileDescriptor& aFileDescriptor)
MOZ_OVERRIDE;

View File

@ -13,6 +13,7 @@
#include "mozilla/BrowserElementParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/plugins/PluginWidgetParent.h"
#include "mozilla/EventStateManager.h"
@ -45,6 +46,7 @@
#include "nsIDOMWindowUtils.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadInfo.h"
#include "nsPrincipal.h"
#include "nsIPromptFactory.h"
#include "nsIURI.h"
#include "nsIWebBrowserChrome.h"
@ -693,6 +695,19 @@ TabParent::SendLoadRemoteScript(const nsString& aURL,
return PBrowserParent::SendLoadRemoteScript(aURL, aRunInGlobalScope);
}
bool
TabParent::InitBrowserConfiguration(nsIURI* aURI,
BrowserConfiguration& aConfiguration)
{
// Get the list of ServiceWorkerRegistation for this origin.
nsRefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get();
MOZ_ASSERT(swr);
swr->GetRegistrations(aConfiguration.serviceWorkerRegistrations());
return true;
}
void
TabParent::LoadURL(nsIURI* aURI)
{
@ -727,7 +742,13 @@ TabParent::LoadURL(nsIURI* aURI)
}
mSendOfflineStatus = false;
unused << SendLoadURL(spec);
// This object contains the configuration for this new app.
BrowserConfiguration configuration;
if (NS_WARN_IF(!InitBrowserConfiguration(aURI, configuration))) {
return;
}
unused << SendLoadURL(spec, configuration);
// If this app is a packaged app then we can speed startup by sending over
// the file descriptor for the "application.zip" file that it will

View File

@ -403,6 +403,9 @@ protected:
bool SendCompositionChangeEvent(mozilla::WidgetCompositionEvent& event);
bool InitBrowserConfiguration(nsIURI* aURI,
BrowserConfiguration& aConfiguration);
// IME
static TabParent *mIMETabParent;
nsString mIMECacheText;

View File

@ -112,6 +112,7 @@ include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/caps',
'/chrome',
'/docshell/base',
'/dom/base',

View File

@ -4,6 +4,7 @@
#include "ServiceWorkerManager.h"
#include "nsIAppsService.h"
#include "nsIDOMEventTarget.h"
#include "nsIDocument.h"
#include "nsIScriptSecurityManager.h"
@ -21,6 +22,9 @@
#include "mozilla/dom/InstallEventBinding.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
@ -45,9 +49,58 @@
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::ipc;
BEGIN_WORKERS_NAMESPACE
struct ServiceWorkerManager::PendingOperation
{
nsCOMPtr<nsIRunnable> mRunnable;
ServiceWorkerJobQueue* mQueue;
nsRefPtr<ServiceWorkerJob> mJob;
ServiceWorkerRegistrationData mRegistration;
};
namespace {
nsresult
PopulateRegistrationData(nsIPrincipal* aPrincipal,
const ServiceWorkerRegistrationInfo* aRegistration,
ServiceWorkerRegistrationData& aData)
{
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aRegistration);
bool isNullPrincipal = true;
nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// No null principals.
if (NS_WARN_IF(isNullPrincipal)) {
return NS_ERROR_FAILURE;
}
rv = PrincipalToPrincipalInfo(aPrincipal, &aData.principal());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
aData.scope() = aRegistration->mScope;
aData.scriptSpec() = aRegistration->mScriptSpec;
if (aRegistration->mActiveWorker) {
aData.currentWorkerURL() = aRegistration->mActiveWorker->ScriptSpec();
}
return NS_OK;
}
} // Anonymous namespace
NS_IMPL_ISUPPORTS0(ServiceWorkerJob)
NS_IMPL_ISUPPORTS0(ServiceWorkerRegistrationInfo)
@ -93,10 +146,12 @@ ServiceWorkerRegistrationInfo::Clear()
WhichServiceWorker::ACTIVE_WORKER);
}
ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope)
: mControlledDocumentsCounter(0),
mScope(aScope),
mPendingUninstall(false)
ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
nsIPrincipal* aPrincipal)
: mControlledDocumentsCounter(0)
, mScope(aScope)
, mPrincipal(aPrincipal)
, mPendingUninstall(false)
{ }
ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
@ -115,6 +170,7 @@ NS_IMPL_RELEASE(ServiceWorkerManager)
NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
if (aIID.Equals(NS_GET_IID(ServiceWorkerManager)))
foundInterface = static_cast<nsIServiceWorkerManager*>(this);
else
@ -122,7 +178,19 @@ NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
NS_INTERFACE_MAP_END
ServiceWorkerManager::ServiceWorkerManager()
: mActor(nullptr)
{
// Register this component to PBackground.
MOZ_ALWAYS_TRUE(BackgroundChild::GetOrCreateForCurrentThread(this));
if (XRE_GetProcessType() == GeckoProcessType_Default) {
nsRefPtr<ServiceWorkerRegistrar> swr = ServiceWorkerRegistrar::Get();
MOZ_ASSERT(swr);
nsTArray<ServiceWorkerRegistrationData> data;
swr->GetRegistrations(data);
LoadRegistrations(data);
}
}
ServiceWorkerManager::~ServiceWorkerManager()
@ -380,6 +448,7 @@ class ServiceWorkerRegisterJob MOZ_FINAL : public ServiceWorkerJob,
nsCString mScriptSpec;
nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration;
nsRefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
nsCOMPtr<nsIPrincipal> mPrincipal;
~ServiceWorkerRegisterJob()
{ }
@ -397,11 +466,13 @@ public:
ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue,
const nsCString& aScope,
const nsCString& aScriptSpec,
ServiceWorkerUpdateFinishCallback* aCallback)
ServiceWorkerUpdateFinishCallback* aCallback,
nsIPrincipal* aPrincipal)
: ServiceWorkerJob(aQueue)
, mScope(aScope)
, mScriptSpec(aScriptSpec)
, mCallback(aCallback)
, mPrincipal(aPrincipal)
, mJobType(REGISTER_JOB)
{ }
@ -418,8 +489,17 @@ public:
void
Start() MOZ_OVERRIDE
{
if (mJobType == REGISTER_JOB) {
MOZ_ASSERT(NS_IsMainThread());
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm->HasBackgroundActor()) {
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::Start);
swm->AppendPendingOperation(runnable);
return;
}
if (mJobType == REGISTER_JOB) {
mRegistration = swm->GetRegistration(mScope);
if (mRegistration) {
@ -432,10 +512,11 @@ public:
return;
}
} else {
mRegistration = swm->CreateNewRegistration(mScope);
mRegistration = swm->CreateNewRegistration(mScope, mPrincipal);
}
mRegistration->mScriptSpec = mScriptSpec;
swm->StoreRegistration(mPrincipal, mRegistration);
} else {
MOZ_ASSERT(mJobType == UPDATE_JOB);
}
@ -866,13 +947,36 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
new ServiceWorkerResolveWindowPromiseOnUpdateCallback(window, promise);
nsRefPtr<ServiceWorkerRegisterJob> job =
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb);
new ServiceWorkerRegisterJob(queue, cleanedScope, spec, cb, documentPrincipal);
queue->Append(job);
promise.forget(aPromise);
return NS_OK;
}
void
ServiceWorkerManager::AppendPendingOperation(ServiceWorkerJobQueue* aQueue,
ServiceWorkerJob* aJob)
{
MOZ_ASSERT(!mActor);
MOZ_ASSERT(aQueue);
MOZ_ASSERT(aJob);
PendingOperation* opt = mPendingOperations.AppendElement();
opt->mQueue = aQueue;
opt->mJob = aJob;
}
void
ServiceWorkerManager::AppendPendingOperation(nsIRunnable* aRunnable)
{
MOZ_ASSERT(!mActor);
MOZ_ASSERT(aRunnable);
PendingOperation* opt = mPendingOperations.AppendElement();
opt->mRunnable = aRunnable;
}
/*
* Used to handle ExtendableEvent::waitUntil() and proceed with
* installation/activation.
@ -1027,6 +1131,7 @@ ServiceWorkerRegistrationInfo::Activate()
mActiveWorker->UpdateState(ServiceWorkerState::Activating);
swm->CheckPendingReadyPromises();
swm->StoreRegistration(mPrincipal, this);
// "Queue a task to fire a simple event named controllerchange..."
nsCOMPtr<nsIRunnable> controllerChangeRunnable =
@ -1382,6 +1487,7 @@ class ServiceWorkerUnregisterJob MOZ_FINAL : public ServiceWorkerJob
nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration;
const nsCString mScope;
nsCOMPtr<nsIServiceWorkerUnregisterCallback> mCallback;
PrincipalInfo mPrincipalInfo;
~ServiceWorkerUnregisterJob()
{ }
@ -1389,10 +1495,12 @@ class ServiceWorkerUnregisterJob MOZ_FINAL : public ServiceWorkerJob
public:
ServiceWorkerUnregisterJob(ServiceWorkerJobQueue* aQueue,
const nsACString& aScope,
nsIServiceWorkerUnregisterCallback* aCallback)
nsIServiceWorkerUnregisterCallback* aCallback,
PrincipalInfo& aPrincipalInfo)
: ServiceWorkerJob(aQueue)
, mScope(aScope)
, mCallback(aCallback)
, mPrincipalInfo(aPrincipalInfo)
{
AssertIsOnMainThread();
}
@ -1445,6 +1553,9 @@ private:
swm->RemoveRegistration(registration);
}
MOZ_ASSERT(swm->mActor);
swm->mActor->SendUnregisterServiceWorker(mPrincipalInfo,
NS_ConvertUTF8toUTF16(mScope));
return NS_OK;
}
@ -1457,10 +1568,12 @@ private:
};
NS_IMETHODIMP
ServiceWorkerManager::Unregister(nsIServiceWorkerUnregisterCallback* aCallback,
ServiceWorkerManager::Unregister(nsIPrincipal* aPrincipal,
nsIServiceWorkerUnregisterCallback* aCallback,
const nsAString& aScope)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aCallback);
// This is not accessible by content, and callers should always ensure scope is
@ -1477,10 +1590,22 @@ ServiceWorkerManager::Unregister(nsIServiceWorkerUnregisterCallback* aCallback,
ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scope);
MOZ_ASSERT(queue);
PrincipalInfo principalInfo;
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
&principalInfo)))) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsRefPtr<ServiceWorkerUnregisterJob> job =
new ServiceWorkerUnregisterJob(queue, scope, aCallback);
new ServiceWorkerUnregisterJob(queue, scope, aCallback, principalInfo);
if (mActor) {
queue->Append(job);
return NS_OK;
}
AppendPendingOperation(queue, job);
return NS_OK;
}
/* static */
@ -1613,6 +1738,85 @@ ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
return rv;
}
void
ServiceWorkerManager::LoadRegistrations(
const nsTArray<ServiceWorkerRegistrationData>& aRegistrations)
{
AssertIsOnMainThread();
for (uint32_t i = 0, len = aRegistrations.Length(); i < len; ++i) {
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(aRegistrations[i].principal());
if (!principal) {
continue;
}
ServiceWorkerRegistrationInfo* registration =
CreateNewRegistration(aRegistrations[i].scope(), principal);
registration->mScriptSpec = aRegistrations[i].scriptSpec();
registration->mActiveWorker =
new ServiceWorkerInfo(registration, aRegistrations[i].currentWorkerURL());
}
}
void
ServiceWorkerManager::ActorFailed()
{
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
}
void
ServiceWorkerManager::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
{
MOZ_ASSERT(aActor);
MOZ_ASSERT(!mActor);
mActor = aActor;
// Flush the pending requests.
for (uint32_t i = 0, len = mPendingOperations.Length(); i < len; ++i) {
MOZ_ASSERT(mPendingOperations[i].mRunnable ||
(mPendingOperations[i].mJob && mPendingOperations[i].mQueue));
if (mPendingOperations[i].mRunnable) {
nsresult rv = NS_DispatchToCurrentThread(mPendingOperations[i].mRunnable);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch a runnable.");
return;
}
} else {
mPendingOperations[i].mQueue->Append(mPendingOperations[i].mJob);
}
}
mPendingOperations.Clear();
}
void
ServiceWorkerManager::StoreRegistration(
nsIPrincipal* aPrincipal,
ServiceWorkerRegistrationInfo* aRegistration)
{
MOZ_ASSERT(mActor);
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aRegistration);
ServiceWorkerRegistrationData data;
nsresult rv = PopulateRegistrationData(aPrincipal, aRegistration, data);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
PrincipalInfo principalInfo;
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aPrincipal,
&principalInfo)))) {
return;
}
mActor->SendRegisterServiceWorker(data);
}
already_AddRefed<ServiceWorkerRegistrationInfo>
ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsPIDOMWindow* aWindow)
{
@ -2166,7 +2370,8 @@ ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegis
}
ServiceWorkerRegistrationInfo*
ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope)
ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope,
nsIPrincipal* aPrincipal)
{
#ifdef DEBUG
AssertIsOnMainThread();
@ -2174,7 +2379,7 @@ ServiceWorkerManager::CreateNewRegistration(const nsCString& aScope)
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
MOZ_ASSERT(NS_SUCCEEDED(rv));
#endif
ServiceWorkerRegistrationInfo* registration = new ServiceWorkerRegistrationInfo(aScope);
ServiceWorkerRegistrationInfo* registration = new ServiceWorkerRegistrationInfo(aScope, aPrincipal);
// From now on ownership of registration is with
// mServiceWorkerRegistrationInfos.
mServiceWorkerRegistrationInfos.Put(aScope, registration);

View File

@ -8,6 +8,7 @@
#include "nsIServiceWorkerManager.h"
#include "nsCOMPtr.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Preferences.h"
@ -17,6 +18,10 @@
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState
#include "mozilla/dom/ServiceWorkerCommon.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsRefPtrHashtable.h"
@ -26,6 +31,11 @@
class nsIScriptError;
namespace mozilla {
namespace ipc {
class BackgroundChild;
}
namespace dom {
class ServiceWorkerRegistration;
@ -131,6 +141,8 @@ public:
// the URLs of the following three workers.
nsCString mScriptSpec;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsRefPtr<ServiceWorkerInfo> mActiveWorker;
nsRefPtr<ServiceWorkerInfo> mWaitingWorker;
nsRefPtr<ServiceWorkerInfo> mInstallingWorker;
@ -141,7 +153,8 @@ public:
bool mPendingUninstall;
bool mWaitingToActivate;
explicit ServiceWorkerRegistrationInfo(const nsACString& aScope);
explicit ServiceWorkerRegistrationInfo(const nsACString& aScope,
nsIPrincipal* aPrincipal);
already_AddRefed<ServiceWorkerInfo>
Newest()
@ -218,6 +231,12 @@ public:
return mScriptSpec;
}
void SetScriptSpec(const nsCString& aSpec)
{
MOZ_ASSERT(!aSpec.IsEmpty());
mScriptSpec = aSpec;
}
explicit ServiceWorkerInfo(ServiceWorkerRegistrationInfo* aReg,
const nsACString& aScriptSpec)
: mRegistration(aReg)
@ -266,7 +285,9 @@ public:
* installation, querying and event dispatch of ServiceWorkers for all the
* origins in the process.
*/
class ServiceWorkerManager MOZ_FINAL : public nsIServiceWorkerManager
class ServiceWorkerManager MOZ_FINAL
: public nsIServiceWorkerManager
, public nsIIPCBackgroundChildCreateCallback
{
friend class ActivationRunnable;
friend class ServiceWorkerRegistrationInfo;
@ -280,14 +301,12 @@ class ServiceWorkerManager MOZ_FINAL : public nsIServiceWorkerManager
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERMANAGER
NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
static ServiceWorkerManager* FactoryCreate()
{
AssertIsOnMainThread();
if (!Preferences::GetBool("dom.serviceWorkers.enabled")) {
return nullptr;
}
ServiceWorkerManager* res = new ServiceWorkerManager;
NS_ADDREF(res);
@ -325,7 +344,7 @@ public:
}
ServiceWorkerRegistrationInfo*
CreateNewRegistration(const nsCString& aScope);
CreateNewRegistration(const nsCString& aScope, nsIPrincipal* aPrincipal);
void
RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
@ -341,6 +360,9 @@ public:
return mJobQueues.LookupOrAdd(aScope);
}
void StoreRegistration(nsIPrincipal* aPrincipal,
ServiceWorkerRegistrationInfo* aRegistration);
void
FinishFetch(ServiceWorkerRegistrationInfo* aRegistration);
@ -364,6 +386,9 @@ public:
static already_AddRefed<ServiceWorkerManager>
GetInstance();
void LoadRegistrations(
const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
private:
ServiceWorkerManager();
~ServiceWorkerManager();
@ -450,12 +475,26 @@ private:
nsRefPtr<Promise> mPromise;
};
void AppendPendingOperation(nsIRunnable* aRunnable);
void AppendPendingOperation(ServiceWorkerJobQueue* aQueue,
ServiceWorkerJob* aJob);
bool HasBackgroundActor() const
{
return !!mActor;
}
static PLDHashOperator
CheckPendingReadyPromisesEnumerator(nsISupports* aSupports,
nsAutoPtr<PendingReadyPromise>& aData,
void* aUnused);
nsClassHashtable<nsISupportsHashKey, PendingReadyPromise> mPendingReadyPromises;
mozilla::ipc::PBackgroundChild* mActor;
struct PendingOperation;
nsTArray<PendingOperation> mPendingOperations;
};
NS_DEFINE_STATIC_IID_ACCESSOR(ServiceWorkerManager,

View File

@ -0,0 +1,671 @@
/* 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 "ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
#include "nsIEventTarget.h"
#include "nsIInputStream.h"
#include "nsILineInputStream.h"
#include "nsIObserverService.h"
#include "MainThreadUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ModuleUtils.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsAutoPtr.h"
#include "nsDirectoryServiceUtils.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace dom {
namespace {
StaticRefPtr<ServiceWorkerRegistrar> gServiceWorkerRegistrar;
} // anonymous namespace
NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar,
nsIObserver)
void
ServiceWorkerRegistrar::Initialize()
{
MOZ_ASSERT(!gServiceWorkerRegistrar);
if (XRE_GetProcessType() != GeckoProcessType_Default) {
return;
}
gServiceWorkerRegistrar = new ServiceWorkerRegistrar();
ClearOnShutdown(&gServiceWorkerRegistrar);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
DebugOnly<nsresult> rv = obs->AddObserver(gServiceWorkerRegistrar,
"profile-after-change", false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = obs->AddObserver(gServiceWorkerRegistrar, "profile-before-change",
false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
/* static */ already_AddRefed<ServiceWorkerRegistrar>
ServiceWorkerRegistrar::Get()
{
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
MOZ_ASSERT(gServiceWorkerRegistrar);
nsRefPtr<ServiceWorkerRegistrar> service = gServiceWorkerRegistrar.get();
return service.forget();
}
ServiceWorkerRegistrar::ServiceWorkerRegistrar()
: mMonitor("ServiceWorkerRegistrar.mMonitor")
, mDataLoaded(false)
, mShuttingDown(false)
, mShutdownCompleteFlag(nullptr)
, mRunnableCounter(0)
{
MOZ_ASSERT(NS_IsMainThread());
}
ServiceWorkerRegistrar::~ServiceWorkerRegistrar()
{
MOZ_ASSERT(!mRunnableCounter);
}
void
ServiceWorkerRegistrar::GetRegistrations(
nsTArray<ServiceWorkerRegistrationData>& aValues)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aValues.IsEmpty());
// If we don't have the profile directory, profile is not started yet (and
// probably we are in a utest).
if (!mProfileDir) {
return;
}
// We care just about the first execution because this can be blocked by
// loading data from disk.
static bool firstTime = true;
TimeStamp startTime;
if (firstTime) {
startTime = TimeStamp::NowLoRes();
}
{
MonitorAutoLock lock(mMonitor);
// Waiting for data loaded.
mMonitor.AssertCurrentThreadOwns();
while (!mDataLoaded) {
mMonitor.Wait();
}
aValues.AppendElements(mData);
}
if (firstTime) {
firstTime = false;
Telemetry::AccumulateTimeDelta(
Telemetry::SERVICE_WORKER_REGISTRATION_LOADING,
startTime);
}
}
void
ServiceWorkerRegistrar::RegisterServiceWorker(
const ServiceWorkerRegistrationData& aData)
{
AssertIsOnBackgroundThread();
if (mShuttingDown) {
NS_WARNING("Failed to register a serviceWorker during shutting down.");
return;
}
{
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mDataLoaded);
bool found = false;
for (uint32_t i = 0, len = mData.Length(); i < len; ++i) {
if (mData[i].scope() == aData.scope()) {
mData[i] = aData;
found = true;
break;
}
}
if (!found) {
mData.AppendElement(aData);
}
}
ScheduleSaveData();
}
void
ServiceWorkerRegistrar::UnregisterServiceWorker(const nsACString& aScope)
{
AssertIsOnBackgroundThread();
if (mShuttingDown) {
NS_WARNING("Failed to unregister a serviceWorker during shutting down.");
return;
}
bool deleted = false;
{
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(mDataLoaded);
for (uint32_t i = 0; i < mData.Length(); ++i) {
if (mData[i].scope() == aScope) {
mData.RemoveElementAt(i);
deleted = true;
break;
}
}
}
if (deleted) {
ScheduleSaveData();
}
}
void
ServiceWorkerRegistrar::LoadData()
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mDataLoaded);
nsresult rv = ReadData();
if (NS_WARN_IF(NS_FAILED(rv))) {
DeleteData();
// Also if the reading failed we have to notify what is waiting for data.
}
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(!mDataLoaded);
mDataLoaded = true;
mMonitor.Notify();
}
nsresult
ServiceWorkerRegistrar::ReadData()
{
// We cannot assert about the correct thread because normally this method
// runs on a IO thread, but in gTests we call it from the main-thread.
MOZ_ASSERT(mProfileDir);
nsCOMPtr<nsIFile> file;
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool exists;
rv = file->Exists(&exists);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!exists) {
return NS_OK;
}
nsCOMPtr<nsIInputStream> stream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(stream);
MOZ_ASSERT(lineInputStream);
nsAutoCString line;
bool hasMoreLines;
rv = lineInputStream->ReadLine(line, &hasMoreLines);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// The file is corrupted ?
// FIXME: in case we implement a version 2, we should inform the user using
// the console about this issue.
if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_VERSION)) {
return NS_ERROR_FAILURE;
}
while (hasMoreLines) {
ServiceWorkerRegistrationData* entry = mData.AppendElement();
#define GET_LINE(x) \
rv = lineInputStream->ReadLine(x, &hasMoreLines); \
if (NS_WARN_IF(NS_FAILED(rv))) { \
return rv; \
} \
if (NS_WARN_IF(!hasMoreLines)) { \
return NS_ERROR_FAILURE; \
}
GET_LINE(line);
if (line.EqualsLiteral(SERVICEWORKERREGISTRAR_SYSTEM_PRINCIPAL)) {
entry->principal() = mozilla::ipc::SystemPrincipalInfo();
} else {
if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL)) {
return NS_ERROR_FAILURE;
}
GET_LINE(line);
uint32_t appId = line.ToInteger(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
GET_LINE(line);
if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE) &&
!line.EqualsLiteral(SERVICEWORKERREGISTRAR_FALSE)) {
return NS_ERROR_FAILURE;
}
bool isInBrowserElement = line.EqualsLiteral(SERVICEWORKERREGISTRAR_TRUE);
GET_LINE(line);
entry->principal() =
mozilla::ipc::ContentPrincipalInfo(appId, isInBrowserElement,
line);
}
GET_LINE(entry->scope());
GET_LINE(entry->scriptSpec());
GET_LINE(entry->currentWorkerURL());
#undef GET_LINE
rv = lineInputStream->ReadLine(line, &hasMoreLines);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!line.EqualsLiteral(SERVICEWORKERREGISTRAR_TERMINATOR)) {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
void
ServiceWorkerRegistrar::DeleteData()
{
// We cannot assert about the correct thread because normally this method
// runs on a IO thread, but in gTests we call it from the main-thread.
MOZ_ASSERT(mProfileDir);
{
MonitorAutoLock lock(mMonitor);
mData.Clear();
}
nsCOMPtr<nsIFile> file;
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
rv = file->Remove(false);
if (rv == NS_ERROR_FILE_NOT_FOUND) {
return;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
class ServiceWorkerRegistrarSaveDataRunnable MOZ_FINAL : public nsRunnable
{
public:
ServiceWorkerRegistrarSaveDataRunnable()
: mThread(do_GetCurrentThread())
{
AssertIsOnBackgroundThread();
}
NS_IMETHODIMP
Run()
{
nsRefPtr<ServiceWorkerRegistrar> service = ServiceWorkerRegistrar::Get();
MOZ_ASSERT(service);
service->SaveData();
nsRefPtr<nsRunnable> runnable =
NS_NewRunnableMethod(service, &ServiceWorkerRegistrar::DataSaved);
nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
private:
nsCOMPtr<nsIThread> mThread;
};
void
ServiceWorkerRegistrar::ScheduleSaveData()
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mShuttingDown);
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
MOZ_ASSERT(target, "Must have stream transport service");
nsRefPtr<nsRunnable> runnable =
new ServiceWorkerRegistrarSaveDataRunnable();
nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
++mRunnableCounter;
}
void
ServiceWorkerRegistrar::ShutdownCompleted()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mShutdownCompleteFlag && !*mShutdownCompleteFlag);
*mShutdownCompleteFlag = true;
}
void
ServiceWorkerRegistrar::SaveData()
{
MOZ_ASSERT(!NS_IsMainThread());
nsresult rv = WriteData();
if (NS_FAILED(rv)) {
NS_WARNING("Failed to write data for the ServiceWorker Registations.");
DeleteData();
}
}
void
ServiceWorkerRegistrar::DataSaved()
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(mRunnableCounter);
--mRunnableCounter;
MaybeScheduleShutdownCompleted();
}
void
ServiceWorkerRegistrar::MaybeScheduleShutdownCompleted()
{
AssertIsOnBackgroundThread();
if (mRunnableCounter || !mShuttingDown) {
return;
}
nsRefPtr<nsRunnable> runnable =
NS_NewRunnableMethod(this, &ServiceWorkerRegistrar::ShutdownCompleted);
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
nsresult
ServiceWorkerRegistrar::WriteData()
{
// We cannot assert about the correct thread because normally this method
// runs on a IO thread, but in gTests we call it from the main-thread.
MOZ_ASSERT(mProfileDir);
nsCOMPtr<nsIFile> file;
nsresult rv = mProfileDir->Clone(getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// We need a lock to take a snapshot of the data.
nsTArray<ServiceWorkerRegistrationData> data;
{
MonitorAutoLock lock(mMonitor);
data = mData;
}
nsCOMPtr<nsIOutputStream> stream;
rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoCString buffer;
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_VERSION);
buffer.Append('\n');
uint32_t count;
rv = stream->Write(buffer.Data(), buffer.Length(), &count);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (count != buffer.Length()) {
return NS_ERROR_UNEXPECTED;
}
for (uint32_t i = 0, len = data.Length(); i < len; ++i) {
const mozilla::ipc::PrincipalInfo& info = data[i].principal();
if (info.type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
buffer.AssignLiteral(SERVICEWORKERREGISTRAR_SYSTEM_PRINCIPAL);
} else {
MOZ_ASSERT(info.type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
const mozilla::ipc::ContentPrincipalInfo& cInfo =
info.get_ContentPrincipalInfo();
buffer.AssignLiteral(SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL);
buffer.Append('\n');
buffer.AppendInt(cInfo.appId());
buffer.Append('\n');
if (cInfo.isInBrowserElement()) {
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TRUE);
} else {
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_FALSE);
}
buffer.Append('\n');
buffer.Append(cInfo.spec());
}
buffer.Append('\n');
buffer.Append(data[i].scope());
buffer.Append('\n');
buffer.Append(data[i].scriptSpec());
buffer.Append('\n');
buffer.Append(data[i].currentWorkerURL());
buffer.Append('\n');
buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR);
buffer.Append('\n');
rv = stream->Write(buffer.Data(), buffer.Length(), &count);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (count != buffer.Length()) {
return NS_ERROR_UNEXPECTED;
}
}
nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream);
MOZ_ASSERT(safeStream);
rv = safeStream->Finish();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void
ServiceWorkerRegistrar::ProfileStarted()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mProfileDir);
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mProfileDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
MOZ_ASSERT(target, "Must have stream transport service");
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &ServiceWorkerRegistrar::LoadData);
rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the LoadDataRunnable.");
}
}
void
ServiceWorkerRegistrar::ProfileStopped()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mProfileDir) {
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mProfileDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
PBackgroundChild* child = BackgroundChild::GetForCurrentThread();
if (!child) {
return;
}
bool completed = false;
mShutdownCompleteFlag = &completed;
child->SendShutdownServiceWorkerRegistrar();
nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
while (true) {
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread));
if (completed) {
break;
}
}
}
void
ServiceWorkerRegistrar::Shutdown()
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mShuttingDown);
mShuttingDown = true;
MaybeScheduleShutdownCompleted();
}
NS_IMETHODIMP
ServiceWorkerRegistrar::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
if (!strcmp(aTopic, "profile-after-change")) {
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
observerService->RemoveObserver(this, "profile-after-change");
// The profile is fully loaded, now we can proceed with the loading of data
// from disk.
ProfileStarted();
return NS_OK;
}
if (!strcmp(aTopic, "profile-before-change")) {
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
observerService->RemoveObserver(this, "profile-before-change");
// Shutting down, let's sync the data.
ProfileStopped();
return NS_OK;
}
MOZ_ASSERT(false, "ServiceWorkerRegistrar got unexpected topic!");
return NS_ERROR_UNEXPECTED;
}
} // dom namespace
} // mozilla namespace

View File

@ -0,0 +1,92 @@
/* 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/. */
#ifndef mozilla_dom_workers_ServiceWorkerRegistrar_h
#define mozilla_dom_workers_ServiceWorkerRegistrar_h
#include "mozilla/Monitor.h"
#include "mozilla/Telemetry.h"
#include "nsClassHashtable.h"
#include "nsIObserver.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsTArray.h"
#define SERVICEWORKERREGISTRAR_FILE "serviceworker.txt"
#define SERVICEWORKERREGISTRAR_VERSION "1"
#define SERVICEWORKERREGISTRAR_TERMINATOR "#"
#define SERVICEWORKERREGISTRAR_TRUE "true"
#define SERVICEWORKERREGISTRAR_FALSE "false"
#define SERVICEWORKERREGISTRAR_SYSTEM_PRINCIPAL "system"
#define SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL "content"
class nsIFile;
class nsRunnable;
namespace mozilla {
namespace dom {
class ServiceWorkerRegistrationData;
class ServiceWorkerRegistrar : public nsIObserver
{
friend class ServiceWorkerRegistrarSaveDataRunnable;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIOBSERVER
static void Initialize();
void Shutdown();
void DataSaved();
static already_AddRefed<ServiceWorkerRegistrar> Get();
void GetRegistrations(nsTArray<ServiceWorkerRegistrationData>& aValues);
void RegisterServiceWorker(const ServiceWorkerRegistrationData& aData);
void UnregisterServiceWorker(const nsACString& aScope);
protected:
// These methods are protected because we test this class using gTest
// subclassing it.
void LoadData();
void SaveData();
nsresult ReadData();
nsresult WriteData();
void DeleteData();
ServiceWorkerRegistrar();
virtual ~ServiceWorkerRegistrar();
private:
void ProfileStarted();
void ProfileStopped();
void ScheduleSaveData();
void ShutdownCompleted();
void MaybeScheduleShutdownCompleted();
mozilla::Monitor mMonitor;
protected:
// mData and mDataLoaded are protected by mMonitor.
nsTArray<ServiceWorkerRegistrationData> mData;
bool mDataLoaded;
bool mShuttingDown;
bool* mShutdownCompleteFlag;
uint32_t mRunnableCounter;
nsCOMPtr<nsIFile> mProfileDir;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_workers_ServiceWorkerRegistrar_h

View File

@ -0,0 +1,21 @@
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
/* 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 PBackgroundSharedTypes;
namespace mozilla {
namespace dom {
struct ServiceWorkerRegistrationData
{
nsCString scope;
nsCString scriptSpec;
nsCString currentWorkerURL;
PrincipalInfo principal;
};
} // namespace dom
} // namespace mozilla

View File

@ -200,7 +200,7 @@ ServiceWorkerRegistration::Unregister(ErrorResult& aRv)
nsRefPtr<UnregisterCallback> cb = new UnregisterCallback(promise);
NS_ConvertUTF8toUTF16 scope(uriSpec);
aRv = swm->Unregister(cb, scope);
aRv = swm->Unregister(documentPrincipal, cb, scope);
if (aRv.Failed()) {
return nullptr;
}

View File

@ -590,7 +590,7 @@ public:
// We don't need to check if the principal can load this mScope because a
// ServiceWorkerGlobalScope can always unregister itself.
rv = swm->Unregister(this, mScope);
rv = swm->Unregister(mWorkerPrivate->GetPrincipal(), this, mScope);
if (NS_WARN_IF(NS_FAILED(rv))) {
UnregisterFailed();
return NS_OK;

View File

@ -9,6 +9,7 @@ EXPORTS.mozilla.dom += [
'ServiceWorkerCommon.h',
'ServiceWorkerContainer.h',
'ServiceWorkerEvents.h',
'ServiceWorkerRegistrar.h',
'ServiceWorkerRegistration.h',
'WorkerPrivate.h',
'WorkerRunnable.h',
@ -66,6 +67,7 @@ UNIFIED_SOURCES += [
'ServiceWorkerContainer.cpp',
'ServiceWorkerEvents.cpp',
'ServiceWorkerManager.cpp',
'ServiceWorkerRegistrar.cpp',
'ServiceWorkerRegistration.cpp',
'SharedWorker.cpp',
'URL.cpp',
@ -78,6 +80,10 @@ UNIFIED_SOURCES += [
'XMLHttpRequestUpload.cpp',
]
IPDL_SOURCES += [
'ServiceWorkerRegistrarTypes.ipdlh',
]
FAIL_ON_WARNINGS = True
MSVC_ENABLE_PGO = True
@ -108,3 +114,5 @@ MOCHITEST_MANIFESTS += [
MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
TEST_DIRS += ['test/gtest']

View File

@ -0,0 +1,282 @@
/* 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 "gtest/gtest.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIFile.h"
#include "nsIOutputStream.h"
#include "nsNetUtil.h"
using namespace mozilla::dom;
using namespace mozilla::ipc;
class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar
{
public:
ServiceWorkerRegistrarTest()
{
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mProfileDir));
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
nsresult TestReadData() { return ReadData(); }
nsresult TestWriteData() { return WriteData(); }
void TestDeleteData() { DeleteData(); }
nsTArray<ServiceWorkerRegistrationData>& TestGetData() { return mData; }
};
already_AddRefed<nsIFile>
GetFile()
{
nsCOMPtr<nsIFile> file;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
file->Append(NS_LITERAL_STRING(SERVICEWORKERREGISTRAR_FILE));
return file.forget();
}
bool
CreateFile(const nsACString& aData)
{
nsCOMPtr<nsIFile> file = GetFile();
nsCOMPtr<nsIOutputStream> stream;
nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
uint32_t count;
rv = stream->Write(aData.Data(), aData.Length(), &count);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
if (count != aData.Length()) {
return false;
}
return true;
}
TEST(ServiceWorkerRegistrar, TestNoFile)
{
nsCOMPtr<nsIFile> file = GetFile();
ASSERT_TRUE(file) << "GetFile must return a nsIFIle";
bool exists;
nsresult rv = file->Exists(&exists);
ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail";
if (exists) {
rv = file->Remove(false);
ASSERT_EQ(NS_OK, rv) << "nsIFile::Remove cannot fail";
}
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
rv = swr->TestReadData();
ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
}
TEST(ServiceWorkerRegistrar, TestEmptyFile)
{
ASSERT_TRUE(CreateFile(EmptyCString())) << "CreateFile should not fail";
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsresult rv = swr->TestReadData();
ASSERT_NE(NS_OK, rv) << "ReadData() should fail if the file is empty";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
}
TEST(ServiceWorkerRegistrar, TestRightVersionFile)
{
ASSERT_TRUE(CreateFile(nsAutoCString(SERVICEWORKERREGISTRAR_VERSION "\n"))) << "CreateFile should not fail";
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsresult rv = swr->TestReadData();
ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail when the version is correct";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
}
TEST(ServiceWorkerRegistrar, TestWrongVersionFile)
{
ASSERT_TRUE(CreateFile(nsAutoCString(SERVICEWORKERREGISTRAR_VERSION "bla\n"))) << "CreateFile should not fail";
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsresult rv = swr->TestReadData();
ASSERT_NE(NS_OK, rv) << "ReadData() should fail when the version is not correct";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)0, data.Length()) << "No data should be found in an empty file";
}
TEST(ServiceWorkerRegistrar, TestReadData)
{
nsAutoCString buffer(SERVICEWORKERREGISTRAR_VERSION "\n");
buffer.Append(SERVICEWORKERREGISTRAR_SYSTEM_PRINCIPAL "\n");
buffer.Append("scope 0\nscriptSpec 0\ncurrentWorkerURL 0\n");
buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
buffer.Append(SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL "\n");
buffer.Append("123\n" SERVICEWORKERREGISTRAR_TRUE "\n");
buffer.Append("spec 1\nscope 1\nscriptSpec 1\ncurrentWorkerURL 1\n");
buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
buffer.Append(SERVICEWORKERREGISTRAR_CONTENT_PRINCIPAL "\n");
buffer.Append("0\n" SERVICEWORKERREGISTRAR_FALSE "\n");
buffer.Append("spec 2\nscope 2\nscriptSpec 2\ncurrentWorkerURL 2\n");
buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
ASSERT_TRUE(CreateFile(buffer)) << "CreateFile should not fail";
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsresult rv = swr->TestReadData();
ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)3, data.Length()) << "4 entries should be found";
const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) << "First principal must be system";
ASSERT_STREQ("scope 0", data[0].scope().get());
ASSERT_STREQ("scriptSpec 0", data[0].scriptSpec().get());
ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
const mozilla::ipc::PrincipalInfo& info1 = data[1].principal();
ASSERT_EQ(info1.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
const mozilla::ipc::ContentPrincipalInfo& cInfo1 = data[1].principal();
ASSERT_EQ((uint32_t)123, cInfo1.appId());
ASSERT_EQ((uint32_t)true, cInfo1.isInBrowserElement());
ASSERT_STREQ("spec 1", cInfo1.spec().get());
ASSERT_STREQ("scope 1", data[1].scope().get());
ASSERT_STREQ("scriptSpec 1", data[1].scriptSpec().get());
ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
const mozilla::ipc::PrincipalInfo& info2 = data[2].principal();
ASSERT_EQ(info2.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) << "First principal must be content";
const mozilla::ipc::ContentPrincipalInfo& cInfo2 = data[2].principal();
ASSERT_EQ((uint32_t)0, cInfo2.appId());
ASSERT_EQ((uint32_t)false, cInfo2.isInBrowserElement());
ASSERT_STREQ("spec 2", cInfo2.spec().get());
ASSERT_STREQ("scope 2", data[2].scope().get());
ASSERT_STREQ("scriptSpec 2", data[2].scriptSpec().get());
ASSERT_STREQ("currentWorkerURL 2", data[2].currentWorkerURL().get());
}
TEST(ServiceWorkerRegistrar, TestDeleteData)
{
ASSERT_TRUE(CreateFile(nsAutoCString("Foobar"))) << "CreateFile should not fail";
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
swr->TestDeleteData();
nsCOMPtr<nsIFile> file = GetFile();
bool exists;
nsresult rv = file->Exists(&exists);
ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail";
ASSERT_FALSE(exists) << "The file should not exist after a DeleteData().";
}
TEST(ServiceWorkerRegistrar, TestWriteData)
{
{
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
for (int i = 0; i < 10; ++i) {
ServiceWorkerRegistrationData* d = data.AppendElement();
if ((i % 2) == 0) {
d->principal() = mozilla::ipc::SystemPrincipalInfo();
} else if ((i % 2) == 1) {
nsAutoCString spec;
spec.AppendPrintf("spec write %d", i);
d->principal() = mozilla::ipc::ContentPrincipalInfo(i, i % 2, spec);
}
d->scope().AppendPrintf("scope write %d", i);
d->scriptSpec().AppendPrintf("scriptSpec write %d", i);
d->currentWorkerURL().AppendPrintf("currentWorkerURL write %d", i);
}
nsresult rv = swr->TestWriteData();
ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
}
nsRefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
nsresult rv = swr->TestReadData();
ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
ASSERT_EQ((uint32_t)10, data.Length()) << "10 entries should be found";
for (int i = 0; i < 10; ++i) {
nsAutoCString test;
if ((i % 2) == 0) {
ASSERT_EQ(data[i].principal().type(), mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo);
} else if ((i % 2) == 1) {
ASSERT_EQ(data[i].principal().type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();
ASSERT_EQ((uint32_t)i, cInfo.appId());
ASSERT_EQ((uint32_t)(i %2), cInfo.isInBrowserElement());
test.AppendPrintf("spec write %d", i);
ASSERT_STREQ(test.get(), cInfo.spec().get());
}
test.Truncate();
test.AppendPrintf("scope write %d", i);
ASSERT_STREQ(test.get(), data[i].scope().get());
test.Truncate();
test.AppendPrintf("scriptSpec write %d", i);
ASSERT_STREQ(test.get(), data[i].scriptSpec().get());
test.Truncate();
test.AppendPrintf("currentWorkerURL write %d", i);
ASSERT_STREQ(test.get(), data[i].currentWorkerURL().get());
}
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
int rv = RUN_ALL_TESTS();
return rv;
}

View File

@ -0,0 +1,13 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES = [
'TestReadWrite.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul-gtest'

View File

@ -10,10 +10,12 @@
#include "mozilla/Assertions.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/PBlobParent.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/indexedDB/ActorsParent.h"
#include "mozilla/dom/ipc/BlobParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/ipc/PBackgroundTestParent.h"
#include "mozilla/layout/VsyncParent.h"
#include "nsNetUtil.h"
@ -71,6 +73,7 @@ namespace ipc {
using mozilla::dom::ContentParent;
using mozilla::dom::BroadcastChannelParent;
using mozilla::dom::ServiceWorkerRegistrationData;
BackgroundParentImpl::BackgroundParentImpl()
{
@ -353,6 +356,197 @@ BackgroundParentImpl::DeallocPBroadcastChannelParent(
return true;
}
namespace {
class RegisterServiceWorkerCallback MOZ_FINAL : public nsRunnable
{
public:
explicit RegisterServiceWorkerCallback(
const ServiceWorkerRegistrationData& aData)
: mData(aData)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
}
NS_IMETHODIMP
Run()
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
nsRefPtr<dom::ServiceWorkerRegistrar> service =
dom::ServiceWorkerRegistrar::Get();
MOZ_ASSERT(service);
service->RegisterServiceWorker(mData);
return NS_OK;
}
private:
ServiceWorkerRegistrationData mData;
};
class UnregisterServiceWorkerCallback MOZ_FINAL : public nsRunnable
{
public:
explicit UnregisterServiceWorkerCallback(const nsString& aScope)
: mScope(aScope)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
}
NS_IMETHODIMP
Run()
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
nsRefPtr<dom::ServiceWorkerRegistrar> service =
dom::ServiceWorkerRegistrar::Get();
MOZ_ASSERT(service);
service->UnregisterServiceWorker(NS_ConvertUTF16toUTF8(mScope));
return NS_OK;
}
private:
nsString mScope;
};
class CheckPrincipalWithCallbackRunnable MOZ_FINAL : public nsRunnable
{
public:
CheckPrincipalWithCallbackRunnable(already_AddRefed<ContentParent> aParent,
const PrincipalInfo& aPrincipalInfo,
nsRunnable* aCallback)
: mContentParent(aParent)
, mPrincipalInfo(aPrincipalInfo)
, mCallback(aCallback)
, mBackgroundThread(NS_GetCurrentThread())
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(mContentParent);
MOZ_ASSERT(mCallback);
MOZ_ASSERT(mBackgroundThread);
}
NS_IMETHODIMP Run()
{
if (NS_IsMainThread()) {
nsCOMPtr<nsIPrincipal> principal = PrincipalInfoToPrincipal(mPrincipalInfo);
AssertAppPrincipal(mContentParent, principal);
mContentParent = nullptr;
mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL);
return NS_OK;
}
AssertIsOnBackgroundThread();
mCallback->Run();
mCallback = nullptr;
return NS_OK;
}
private:
nsRefPtr<ContentParent> mContentParent;
PrincipalInfo mPrincipalInfo;
nsRefPtr<nsRunnable> mCallback;
nsCOMPtr<nsIThread> mBackgroundThread;
};
} // anonymous namespace
bool
BackgroundParentImpl::RecvRegisterServiceWorker(
const ServiceWorkerRegistrationData& aData)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
// Basic validation.
if (aData.scope().IsEmpty() ||
aData.scriptSpec().IsEmpty() ||
aData.principal().type() == PrincipalInfo::TNullPrincipalInfo) {
return false;
}
nsRefPtr<RegisterServiceWorkerCallback> callback =
new RegisterServiceWorkerCallback(aData);
nsRefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this);
// If the ContentParent is null we are dealing with a same-process actor.
if (!parent) {
callback->Run();
return true;
}
nsRefPtr<CheckPrincipalWithCallbackRunnable> runnable =
new CheckPrincipalWithCallbackRunnable(parent.forget(), aData.principal(),
callback);
nsresult rv = NS_DispatchToMainThread(runnable);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
return true;
}
bool
BackgroundParentImpl::RecvUnregisterServiceWorker(
const PrincipalInfo& aPrincipalInfo,
const nsString& aScope)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
// Basic validation.
if (aScope.IsEmpty() ||
aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
return false;
}
nsRefPtr<UnregisterServiceWorkerCallback> callback =
new UnregisterServiceWorkerCallback(aScope);
nsRefPtr<ContentParent> parent = BackgroundParent::GetContentParent(this);
// If the ContentParent is null we are dealing with a same-process actor.
if (!parent) {
callback->Run();
return true;
}
nsRefPtr<CheckPrincipalWithCallbackRunnable> runnable =
new CheckPrincipalWithCallbackRunnable(parent.forget(), aPrincipalInfo,
callback);
nsresult rv = NS_DispatchToMainThread(runnable);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
return true;
}
bool
BackgroundParentImpl::RecvShutdownServiceWorkerRegistrar()
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
if (BackgroundParent::IsOtherProcessActor(this)) {
return false;
}
nsRefPtr<dom::ServiceWorkerRegistrar> service =
dom::ServiceWorkerRegistrar::Get();
MOZ_ASSERT(service);
service->Shutdown();
return true;
}
} // namespace ipc
} // namespace mozilla

View File

@ -83,6 +83,17 @@ protected:
virtual bool
DeallocPBroadcastChannelParent(PBroadcastChannelParent* aActor) MOZ_OVERRIDE;
virtual bool
RecvRegisterServiceWorker(const ServiceWorkerRegistrationData& aData)
MOZ_OVERRIDE;
virtual bool
RecvUnregisterServiceWorker(const PrincipalInfo& aPrincipalInfo,
const nsString& aScope) MOZ_OVERRIDE;
virtual bool
RecvShutdownServiceWorkerRegistrar() MOZ_OVERRIDE;
};
} // namespace ipc

View File

@ -12,6 +12,7 @@ include protocol PVsync;
include DOMTypes;
include PBackgroundSharedTypes;
include PBackgroundIDBSharedTypes;
include ServiceWorkerRegistrarTypes;
namespace mozilla {
namespace ipc {
@ -35,6 +36,11 @@ parent:
PBroadcastChannel(PrincipalInfo pInfo, nsString origin, nsString channel);
RegisterServiceWorker(ServiceWorkerRegistrationData data);
UnregisterServiceWorker(PrincipalInfo principalInfo,
nsString scope);
ShutdownServiceWorkerRegistrar();
both:
PBlob(BlobConstructorParams params);

View File

@ -301,6 +301,8 @@ nsLayoutStatics::Initialize()
IMEStateManager::Init();
ServiceWorkerRegistrar::Initialize();
#ifdef MOZ_B2G
RequestSyncWifiService::Init();
#endif

View File

@ -7039,6 +7039,13 @@
"n_values": 3,
"description": "Doorhanger shown = 0, Disable = 1, Enable = 2"
},
"SERVICE_WORKER_REGISTRATION_LOADING": {
"expires_in_version": "never",
"kind": "exponential",
"high": "5000",
"n_buckets": 20,
"description": "Tracking how ServiceWorkerRegistrar loads data before the first content is shown"
},
"LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS": {
"expires_in_version": "never",
"kind": "boolean",