Bug 1080109 - Clear ServiceWorkers when clearing history or forgetting about site. r=baku,ehsan

This commit is contained in:
Nikhil Marathe 2015-03-05 17:37:49 -08:00
parent c06477822f
commit a5f604fd62
15 changed files with 586 additions and 25 deletions

View File

@ -92,6 +92,8 @@ http://sub2.test1.example.com:80 privileged
http://sub2.test2.example.com:80 privileged
http://noxul.example.com:80 privileged,noxul
http://example.net:80 privileged
# Used to test that clearing Service Workers for domain example.com, does not clear prefixexample.com
http://prefixexample.com:80
https://example.com:443 privileged
https://test1.example.com:443 privileged

View File

@ -33,7 +33,7 @@ interface nsIServiceWorkerInfo : nsISupports
readonly attribute DOMString waitingCacheName;
};
[scriptable, builtinclass, uuid(ff13ee00-5485-4551-af6f-1ab6de843365)]
[scriptable, builtinclass, uuid(384c9aec-29e5-4bdb-abc2-fba10da83e17)]
interface nsIServiceWorkerManager : nsISupports
{
/**
@ -111,6 +111,22 @@ interface nsIServiceWorkerManager : nsISupports
*/
void softUpdate(in DOMString aScope);
/*
* Clears ServiceWorker registrations from memory and disk for the specified
* host.
* - All ServiceWorker instances change their state to redundant.
* - Existing ServiceWorker instances handling fetches will keep running.
* - All documents will immediately stop being controlled.
* - Unregister jobs will be queued for all registrations.
* This eventually results in the registration being deleted from disk too.
*/
void remove(in AUTF8String aHost);
/*
* Clear all registrations for all hosts. See remove().
*/
void removeAll();
// Testing
DOMString getScopeForUrl(in DOMString path);

View File

@ -1253,6 +1253,26 @@ ContentChild::RecvUpdateServiceWorkerRegistrations()
return true;
}
bool
ContentChild::RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain)
{
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
if (swm) {
swm->Remove(NS_ConvertUTF16toUTF8(aDomain));
}
return true;
}
bool
ContentChild::RecvRemoveServiceWorkerRegistrations()
{
nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
if (swm) {
swm->RemoveAll();
}
return true;
}
static CancelableTask* sFirstIdleTask;
static void FirstIdle(void)

View File

@ -302,6 +302,10 @@ public:
virtual bool RecvUpdateServiceWorkerRegistrations() override;
virtual bool RecvRemoveServiceWorkerRegistrationsForDomain(const nsString& aDomain) override;
virtual bool RecvRemoveServiceWorkerRegistrations() override;
virtual bool RecvNotifyVisited(const URIParams& aURI) override;
// auto remove when alertfinished is received.
nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);

View File

@ -505,6 +505,10 @@ child:
async UpdateServiceWorkerRegistrations();
async RemoveServiceWorkerRegistrationsForDomain(nsString aDomain);
async RemoveServiceWorkerRegistrations();
async DataStoreNotify(uint32_t aAppId, nsString aName,
nsString aManifestURL);

View File

@ -26,6 +26,7 @@
#include "mozilla/ErrorNames.h"
#include "mozilla/LoadContext.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/ErrorEvent.h"
#include "mozilla/dom/Headers.h"
@ -68,6 +69,9 @@ using namespace mozilla::ipc;
BEGIN_WORKERS_NAMESPACE
#define PURGE_DOMAIN_DATA "browser:purge-domain-data"
#define PURGE_SESSION_HISTORY "browser:purge-session-history"
static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin),
"RequestMode enumeration value should match Necko CORS mode value.");
static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
@ -217,6 +221,7 @@ NS_IMPL_RELEASE(ServiceWorkerManager)
NS_INTERFACE_MAP_BEGIN(ServiceWorkerManager)
NS_INTERFACE_MAP_ENTRY(nsIServiceWorkerManager)
NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
if (aIID.Equals(NS_GET_IID(ServiceWorkerManager)))
foundInterface = static_cast<nsIServiceWorkerManager*>(this);
else
@ -236,6 +241,17 @@ ServiceWorkerManager::ServiceWorkerManager()
nsTArray<ServiceWorkerRegistrationData> data;
swr->GetRegistrations(data);
LoadRegistrations(data);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
DebugOnly<nsresult> rv;
rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false /* ownsWeak */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = obs->AddObserver(this, PURGE_SESSION_HISTORY, false /* ownsWeak */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = obs->AddObserver(this, PURGE_DOMAIN_DATA, false /* ownsWeak */);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
}
@ -536,6 +552,8 @@ class ServiceWorkerRegisterJob final : public ServiceWorkerJob,
UPDATE_JOB = 1,
} mJobType;
bool mCanceled;
public:
NS_DECL_ISUPPORTS_INHERITED
@ -551,6 +569,7 @@ public:
, mCallback(aCallback)
, mPrincipal(aPrincipal)
, mJobType(REGISTER_JOB)
, mCanceled(false)
{ }
// [[Update]]
@ -561,12 +580,27 @@ public:
, mRegistration(aRegistration)
, mCallback(aCallback)
, mJobType(UPDATE_JOB)
, mCanceled(false)
{ }
bool
IsRegisterJob() const override
{
return true;
}
void
Cancel()
{
mQueue = nullptr;
mCanceled = true;
}
void
Start() override
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mCanceled);
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
if (!swm->HasBackgroundActor()) {
@ -605,6 +639,12 @@ public:
void
ComparisonResult(nsresult aStatus, bool aInCacheAndEqual, const nsAString& aNewCacheName) override
{
nsRefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
if (mCanceled) {
Fail(NS_ERROR_DOM_TYPE_ERR);
return;
}
if (NS_WARN_IF(NS_FAILED(aStatus))) {
Fail(NS_ERROR_DOM_TYPE_ERR);
return;
@ -672,11 +712,19 @@ public:
}
// Public so our error handling code can use it.
// Callers MUST hold a strong ref before calling this!
void
Fail(const ErrorEventInit& aError)
{
MOZ_ASSERT(mCallback);
mCallback->UpdateFailed(aError);
nsRefPtr<ServiceWorkerUpdateFinishCallback> callback = mCallback.forget();
// With cancellation support, we may only be running with one reference
// from another object like a stream loader or something.
// UpdateFailed may do something with that, so hold a ref to ourself since
// FailCommon relies on it.
// FailCommon does check for cancellation, but let's be safe here.
nsRefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
callback->UpdateFailed(aError);
FailCommon(NS_ERROR_DOM_JS_EXCEPTION);
}
@ -684,12 +732,19 @@ public:
void
ContinueInstall()
{
// Even if we are canceled, ensure integrity of mSetOfScopesBeingUpdated
// first.
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
MOZ_ASSERT(swm->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
swm->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
// This is effectively the end of Step 4.3 of the [[Update]] algorithm.
// The invocation of [[Install]] is not part of the atomic block.
nsRefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
if (mCanceled) {
return Fail(NS_ERROR_DOM_ABORT_ERR);
}
// Begin [[Install]] atomic step 4.
if (mRegistration->mInstallingWorker) {
// FIXME(nsm): Terminate and stuff
@ -703,6 +758,7 @@ public:
mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
Succeed();
// The job should NOT call fail from this point on.
// Step 4.6 "Queue a task..." for updatefound.
nsCOMPtr<nsIRunnable> upr =
@ -743,6 +799,8 @@ private:
void
Update()
{
// Since Update() is called synchronously from Start(), we can assert this.
MOZ_ASSERT(!mCanceled);
MOZ_ASSERT(mRegistration);
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::ContinueUpdate);
@ -755,6 +813,11 @@ private:
ContinueUpdate()
{
AssertIsOnMainThread();
nsRefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
if (mCanceled) {
return Fail(NS_ERROR_DOM_ABORT_ERR);
}
if (mRegistration->mInstallingWorker) {
// FIXME(nsm): "Terminate installing worker".
mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
@ -800,7 +863,6 @@ private:
}
}
mCallback = nullptr;
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->MaybeRemoveRegistration(mRegistration);
// Ensures that the job can't do anything useful from this point on.
@ -812,18 +874,31 @@ private:
// This MUST only be called when the job is still performing actions related
// to registration or update. After the spec resolves the update promise, use
// Done() with the failure code instead.
// Callers MUST hold a strong ref before calling this!
void
Fail(nsresult aRv)
{
MOZ_ASSERT(mCallback);
mCallback->UpdateFailed(aRv);
nsRefPtr<ServiceWorkerUpdateFinishCallback> callback = mCallback.forget();
// With cancellation support, we may only be running with one reference
// from another object like a stream loader or something.
// UpdateFailed may do something with that, so hold a ref to ourself since
// FailCommon relies on it.
// FailCommon does check for cancellation, but let's be safe here.
nsRefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
callback->UpdateFailed(aRv);
FailCommon(aRv);
}
void
ContinueAfterInstallEvent(bool aInstallEventSuccess, bool aActivateImmediately)
{
if (NS_WARN_IF(!mRegistration->mInstallingWorker)) {
if (mCanceled) {
return Done(NS_ERROR_DOM_ABORT_ERR);
}
if (!mRegistration->mInstallingWorker) {
NS_WARNING("mInstallingWorker was null.");
return Done(NS_ERROR_DOM_ABORT_ERR);
}
@ -866,6 +941,28 @@ private:
NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerRegisterJob, ServiceWorkerJob);
void
ServiceWorkerJobQueue::CancelJobs()
{
if (mJobs.IsEmpty()) {
return;
}
// We have to treat the first job specially. It is the running job and needs
// to be notified correctly.
nsRefPtr<ServiceWorkerJob> runningJob = mJobs[0];
// We can just let an Unregister job run to completion.
if (runningJob->IsRegisterJob()) {
ServiceWorkerRegisterJob* job = static_cast<ServiceWorkerRegisterJob*>(runningJob.get());
job->Cancel();
}
// Get rid of everything. Non-main thread objects may still be holding a ref
// to the running register job. Since we called Cancel() on it, the job's
// main thread functions will just exit.
mJobs.Clear();
}
NS_IMETHODIMP
ContinueUpdateRunnable::Run()
{
@ -1907,20 +2004,23 @@ ServiceWorkerManager::HandleError(JSContext* aCx,
ServiceWorkerJobQueue* queue = mJobQueues.Get(aScope);
MOZ_ASSERT(queue);
ServiceWorkerJob* job = queue->Peek();
ServiceWorkerRegisterJob* regJob = static_cast<ServiceWorkerRegisterJob*>(job);
MOZ_ASSERT(regJob);
if (job) {
MOZ_ASSERT(job->IsRegisterJob());
nsRefPtr<ServiceWorkerRegisterJob> regJob = static_cast<ServiceWorkerRegisterJob*>(job);
RootedDictionary<ErrorEventInit> init(aCx);
init.mMessage = aMessage;
init.mFilename = aFilename;
init.mLineno = aLineNumber;
init.mColno = aColumnNumber;
RootedDictionary<ErrorEventInit> init(aCx);
init.mMessage = aMessage;
init.mFilename = aFilename;
init.mLineno = aLineNumber;
init.mColno = aColumnNumber;
NS_WARNING(nsPrintfCString(
"Script error caused ServiceWorker registration to fail: %s:%u '%s'",
NS_ConvertUTF16toUTF8(aFilename).get(), aLineNumber,
NS_ConvertUTF16toUTF8(aMessage).get()).get());
regJob->Fail(init);
regJob->Fail(init);
}
return true;
}
@ -2987,19 +3087,13 @@ ServiceWorkerManager::MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRe
}
void
ServiceWorkerManager::RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
ServiceWorkerManager::RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration)
{
MOZ_ASSERT(aRegistration);
MOZ_ASSERT(!aRegistration->IsControllingDocuments());
MOZ_ASSERT(mServiceWorkerRegistrationInfos.Contains(aRegistration->mScope));
ServiceWorkerManager::RemoveScope(mOrderedScopes, aRegistration->mScope);
// Hold a ref since the hashtable may be the last ref.
nsRefPtr<ServiceWorkerRegistrationInfo> reg;
mServiceWorkerRegistrationInfos.Remove(aRegistration->mScope,
getter_AddRefs(reg));
MOZ_ASSERT(reg);
// All callers should be either from a job in which case the actor is
// available, or from MaybeStopControlling(), in which case, this will only be
// called if a valid registration is found. If a valid registration exists,
@ -3009,14 +3103,14 @@ ServiceWorkerManager::RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistr
MOZ_ASSERT(mActor);
PrincipalInfo principalInfo;
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(reg->mPrincipal,
if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(aRegistration->mPrincipal,
&principalInfo)))) {
//XXXnsm I can't think of any other reason a stored principal would fail to
//convert.
NS_WARNING("Unable to unregister serviceworker due to possible OOM");
return;
}
mActor->SendUnregisterServiceWorker(principalInfo, NS_ConvertUTF8toUTF16(reg->mScope));
mActor->SendUnregisterServiceWorker(principalInfo, NS_ConvertUTF8toUTF16(aRegistration->mScope));
}
class ServiceWorkerDataInfo final : public nsIServiceWorkerInfo
@ -3042,7 +3136,86 @@ private:
nsString mActiveCacheName;
nsString mWaitingCacheName;
};
void
ServiceWorkerManager::RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration)
{
RemoveRegistrationInternal(aRegistration);
MOZ_ASSERT(mServiceWorkerRegistrationInfos.Contains(aRegistration->mScope));
mServiceWorkerRegistrationInfos.Remove(aRegistration->mScope);
}
namespace {
/**
* See browser/components/sessionstore/Utils.jsm function hasRootDomain().
*
* Returns true if the |url| passed in is part of the given root |domain|.
* For example, if |url| is "www.mozilla.org", and we pass in |domain| as
* "mozilla.org", this will return true. It would return false the other way
* around.
*/
bool
HasRootDomain(nsIURI* aURI, const nsACString& aDomain)
{
AssertIsOnMainThread();
MOZ_ASSERT(aURI);
nsAutoCString host;
nsresult rv = aURI->GetHost(host);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
nsACString::const_iterator start, end;
host.BeginReading(start);
host.EndReading(end);
if (!FindInReadable(aDomain, start, end)) {
return false;
}
if (host.Equals(aDomain)) {
return true;
}
// Beginning of the string matches, can't look at the previous char.
if (start.get() == host.BeginReading()) {
// Equals failed so this is fine.
return false;
}
char prevChar = *(--start);
return prevChar == '.';
}
// If host/aData is null, unconditionally unregisters.
PLDHashOperator
UnregisterIfMatchesHost(const nsACString& aScope,
ServiceWorkerRegistrationInfo* aReg,
void* aData)
{
// We avoid setting toRemove = aReg by default since there is a possibility
// of failure when aData is passed, in which case we don't want to remove the
// registration.
ServiceWorkerRegistrationInfo* toRemove = nullptr;
if (aData) {
const nsACString& domain = *static_cast<nsACString*>(aData);
nsCOMPtr<nsIURI> scopeURI;
nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, nullptr);
// This way subdomains are also cleared.
if (NS_SUCCEEDED(rv) && HasRootDomain(scopeURI, domain)) {
toRemove = aReg;
}
} else {
toRemove = aReg;
}
if (toRemove) {
nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
swm->ForceUnregister(toRemove);
}
return PL_DHASH_NEXT;
}
} // anonymous namespace
NS_IMPL_ISUPPORTS(ServiceWorkerDataInfo, nsIServiceWorkerInfo)
/* static */ already_AddRefed<ServiceWorkerDataInfo>
@ -3143,6 +3316,38 @@ ServiceWorkerManager::GetAllRegistrations(nsIArray** aResult)
return NS_OK;
}
// MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!
void
ServiceWorkerManager::ForceUnregister(ServiceWorkerRegistrationInfo* aRegistration)
{
MOZ_ASSERT(aRegistration);
ServiceWorkerJobQueue* mQueue;
mJobQueues.Get(aRegistration->mScope, &mQueue);
if (mQueue) {
mQueue->CancelJobs();
}
// Since Unregister is async, it is ok to call it in an enumeration.
Unregister(aRegistration->mPrincipal, nullptr, NS_ConvertUTF8toUTF16(aRegistration->mScope));
}
NS_IMETHODIMP
ServiceWorkerManager::Remove(const nsACString& aHost)
{
AssertIsOnMainThread();
mServiceWorkerRegistrationInfos.EnumerateRead(UnregisterIfMatchesHost, &const_cast<nsACString&>(aHost));
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerManager::RemoveAll()
{
AssertIsOnMainThread();
mServiceWorkerRegistrationInfos.EnumerateRead(UnregisterIfMatchesHost, nullptr);
return NS_OK;
}
static PLDHashOperator
UpdateEachRegistration(const nsACString& aKey,
ServiceWorkerRegistrationInfo* aInfo,
@ -3165,6 +3370,43 @@ ServiceWorkerManager::UpdateAllRegistrations()
return NS_OK;
}
NS_IMETHODIMP
ServiceWorkerManager::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
nsAutoTArray<ContentParent*,1> children;
ContentParent::GetAll(children);
if (strcmp(aTopic, PURGE_SESSION_HISTORY) == 0) {
for (uint32_t i = 0; i < children.Length(); i++) {
unused << children[i]->SendRemoveServiceWorkerRegistrations();
}
RemoveAll();
} else if (strcmp(aTopic, PURGE_DOMAIN_DATA) == 0) {
nsAutoString domain(aData);
for (uint32_t i = 0; i < children.Length(); i++) {
unused << children[i]->SendRemoveServiceWorkerRegistrationsForDomain(domain);
}
Remove(NS_ConvertUTF16toUTF8(domain));
} else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
obs->RemoveObserver(this, PURGE_SESSION_HISTORY);
obs->RemoveObserver(this, PURGE_DOMAIN_DATA);
}
} else {
MOZ_CRASH("Received message we aren't supposed to be registered for!");
}
return NS_OK;
}
void
ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker)
{

View File

@ -12,6 +12,7 @@
#include "ipc/IPCMessageUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Preferences.h"
#include "mozilla/TypedEnumBits.h"
@ -60,6 +61,9 @@ public:
virtual void Start() = 0;
virtual bool
IsRegisterJob() const { return false; }
protected:
explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue)
: mQueue(aQueue)
@ -78,8 +82,13 @@ class ServiceWorkerJobQueue final
friend class ServiceWorkerJob;
nsTArray<nsRefPtr<ServiceWorkerJob>> mJobs;
bool mPopping;
public:
explicit ServiceWorkerJobQueue()
: mPopping(false)
{}
~ServiceWorkerJobQueue()
{
if (!mJobs.IsEmpty()) {
@ -99,11 +108,16 @@ public:
}
}
void
CancelJobs();
// Only used by HandleError, keep it that way!
ServiceWorkerJob*
Peek()
{
MOZ_ASSERT(!mJobs.IsEmpty());
if (mJobs.IsEmpty()) {
return nullptr;
}
return mJobs[0];
}
@ -111,6 +125,10 @@ private:
void
Pop()
{
MOZ_ASSERT(!mPopping,
"Pop() called recursively, did you write a job which calls Done() synchronously from Start()?");
AutoRestore<bool> savePopping(mPopping);
mPopping = true;
MOZ_ASSERT(!mJobs.IsEmpty());
mJobs.RemoveElementAt(0);
if (!mJobs.IsEmpty()) {
@ -304,6 +322,7 @@ public:
class ServiceWorkerManager final
: public nsIServiceWorkerManager
, public nsIIPCBackgroundChildCreateCallback
, public nsIObserver
{
friend class GetReadyPromiseRunnable;
friend class GetRegistrationsRunnable;
@ -316,6 +335,7 @@ public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERMANAGER
NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
NS_DECL_NSIOBSERVER
NS_DECLARE_STATIC_IID_ACCESSOR(NS_SERVICEWORKERMANAGER_IMPL_IID)
static ServiceWorkerManager* FactoryCreate()
@ -336,7 +356,7 @@ public:
// memmoves associated with inserting stuff in the middle of the array.
nsTArray<nsCString> mOrderedScopes;
// Scope to registration.
// Scope to registration.
// The scope should be a fully qualified valid URL.
nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mServiceWorkerRegistrationInfos;
@ -398,6 +418,11 @@ public:
void LoadRegistrations(
const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
// Used by remove() and removeAll() when clearing history.
// MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!
void
ForceUnregister(ServiceWorkerRegistrationInfo* aRegistration);
NS_IMETHOD
AddRegistrationEventListener(const nsAString& aScope,
ServiceWorkerRegistrationListener* aListener);
@ -503,10 +528,18 @@ private:
void* aUnused);
nsClassHashtable<nsISupportsHashKey, PendingReadyPromise> mPendingReadyPromises;
void
MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
// Does all cleanup except removing the registration from
// mServiceWorkerRegistrationInfos. This is useful when we clear
// registrations via remove()/removeAll() since we are iterating over the
// hashtable and can cleanly remove within the hashtable enumeration
// function.
void
RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration);
mozilla::ipc::PBackgroundChild* mActor;
struct PendingOperation;

View File

@ -78,6 +78,10 @@ support-files =
periodic/register.html
periodic/wait_for_update.html
periodic/unregister.html
sanitize/frame.html
sanitize/register.html
sanitize/example_check_and_unregister.html
sanitize_worker.js
[test_unregister.html]
[test_installation_simple.html]
@ -110,3 +114,5 @@ support-files =
[test_empty_serviceworker.html]
[test_periodic_update.html]
[test_periodic_https_update.html]
[test_sanitize.html]
[test_sanitize_domain.html]

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<script>
function done(exists) {
parent.postMessage(exists, '*');
}
function fail() {
parent.postMessage("FAIL", '*');
}
navigator.serviceWorker.getRegistration(".").then(function(reg) {
if (reg) {
reg.unregister().then(done.bind(undefined, true), fail);
} else {
dump("getRegistration() returned undefined registration\n");
done(false);
}
}, function(e) {
dump("getRegistration() failed\n");
fail();
});
</script>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<script>
fetch("intercept-this").then(function(r) {
if (!r.ok) {
return "FAIL";
}
return r.text();
}).then(function(body) {
parent.postMessage(body, '*');
});
</script>

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<script>
function done() {
parent.postMessage('', '*');
}
navigator.serviceWorker.ready.then(done);
navigator.serviceWorker.register("../sanitize_worker.js", {scope: "."});
</script>

View File

@ -0,0 +1,5 @@
onfetch = function(e) {
if (e.request.url.indexOf("intercept-this") != -1) {
e.respondWith(new Response("intercepted"));
}
}

View File

@ -0,0 +1,87 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1080109 - Clear ServiceWorker registrations for all domains</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
function start() {
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
function testNotIntercepted() {
testFrame("sanitize/frame.html").then(function(body) {
is(body, "FAIL", "Expected frame to not be controlled");
// No need to unregister since that already happened.
navigator.serviceWorker.getRegistration("sanitize/foo").then(function(reg) {
ok(reg === undefined, "There should no longer be a valid registration");
}, function(e) {
ok(false, "getRegistration() should not error");
}).then(function(e) {
SimpleTest.finish();
});
});
}
registerSW().then(function() {
return testFrame("sanitize/frame.html").then(function(body) {
is(body, "intercepted", "Expected serviceworker to intercept request");
});
}).then(function() {
return navigator.serviceWorker.getRegistration("sanitize/foo");
}).then(function(reg) {
reg.active.onstatechange = function(e) {
e.target.onstatechange = null;
ok(e.target.state, "redundant", "On clearing data, serviceworker should become redundant");
testNotIntercepted();
};
}).then(function() {
SpecialPowers.removeAllServiceWorkerData();
});
}
function testFrame(src) {
return new Promise(function(resolve, reject) {
var iframe = document.createElement("iframe");
iframe.src = src;
window.onmessage = function(message) {
window.onmessage = null;
iframe.src = "about:blank";
document.body.removeChild(iframe);
iframe = null;
SpecialPowers.exactGC(window, function() {
resolve(message.data);
});
};
document.body.appendChild(iframe);
});
}
function registerSW() {
return testFrame("sanitize/register.html");
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
]}, function() {
start();
});
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 1080109 - Clear ServiceWorker registrations for specific domains</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
function start() {
const Cc = SpecialPowers.Cc;
const Ci = SpecialPowers.Ci;
function checkDomainRegistration(domain, exists) {
return testFrame("http://" + domain + "/tests/dom/workers/test/serviceworkers/sanitize/example_check_and_unregister.html").then(function(body) {
if (body === "FAIL") {
ok(false, "Error acquiring registration or unregistering for " + domain);
} else {
if (exists) {
ok(body === true, "Expected " + domain + " to still have a registration.");
} else {
ok(body === false, "Expected " + domain + " to have no registration.");
}
}
});
}
registerSW().then(function() {
return testFrame("http://example.com/tests/dom/workers/test/serviceworkers/sanitize/frame.html").then(function(body) {
is(body, "intercepted", "Expected serviceworker to intercept request");
});
}).then(function() {
SpecialPowers.removeServiceWorkerDataForExampleDomain();
}).then(function() {
return checkDomainRegistration("prefixexample.com", true /* exists */)
.then(function(e) {
return checkDomainRegistration("example.com", false /* exists */);
}).then(function(e) {
SimpleTest.finish();
});
})
}
function testFrame(src) {
return new Promise(function(resolve, reject) {
var iframe = document.createElement("iframe");
iframe.src = src;
window.onmessage = function(message) {
window.onmessage = null;
iframe.src = "about:blank";
document.body.removeChild(iframe);
iframe = null;
SpecialPowers.exactGC(window, function() {
resolve(message.data);
});
};
document.body.appendChild(iframe);
});
}
function registerSW() {
return testFrame("http://example.com/tests/dom/workers/test/serviceworkers/sanitize/register.html")
.then(function(e) {
// Register for prefixexample.com and then ensure it does not get unregistered.
return testFrame("http://prefixexample.com/tests/dom/workers/test/serviceworkers/sanitize/register.html");
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
]}, function() {
start();
});
</script>
</pre>
</body>
</html>

View File

@ -1926,6 +1926,14 @@ SpecialPowersAPI.prototype = {
startPeriodicServiceWorkerUpdates: function() {
return this._sendSyncMessage('SPPeriodicServiceWorkerUpdates', {});
},
removeAllServiceWorkerData: function() {
this.notifyObserversInParentProcess(null, "browser:purge-session-history", "");
},
removeServiceWorkerDataForExampleDomain: function() {
this.notifyObserversInParentProcess(null, "browser:purge-domain-data", "example.com");
},
};
this.SpecialPowersAPI = SpecialPowersAPI;