Bug 1181887 Fall back to network if ServiceWorker script fails to load. r=ehsan r=khuey

This commit is contained in:
Ben Kelly 2015-07-15 12:21:40 -07:00
parent 422a1bbf1d
commit 55e3066722
8 changed files with 115 additions and 20 deletions

View File

@ -9,6 +9,7 @@ interface nsIArray;
interface nsIDocument;
interface nsIInterceptedChannel;
interface nsIPrincipal;
interface nsIRunnable;
interface nsIURI;
[scriptable, uuid(52ee2c9d-ee87-4caf-9588-23ae77ff8798)]
@ -33,7 +34,7 @@ interface nsIServiceWorkerInfo : nsISupports
readonly attribute DOMString waitingCacheName;
};
[scriptable, builtinclass, uuid(ed1cbbf2-0400-4caa-8eb2-b09d21a94e20)]
[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)]
interface nsIServiceWorkerManager : nsISupports
{
/**
@ -93,8 +94,11 @@ interface nsIServiceWorkerManager : nsISupports
/*
* Returns a ServiceWorker.
* - aLoadFailedRunnable is an optional callback that will fire on main thread if
* a ServiceWorker object is returned, but later fails to load for some reason.
*/
[noscript] nsISupports GetDocumentController(in nsIDOMWindow aWindow);
[noscript] nsISupports GetDocumentController(in nsIDOMWindow aWindow,
in nsIRunnable aLoadFailedRunnable);
/*
* Clears ServiceWorker registrations from memory and disk for the specified

View File

@ -1729,6 +1729,7 @@ ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
if (NS_FAILED(loadInfo.mLoadResult)) {
scriptloader::ReportLoadError(aCx, loadInfo.mURL, loadInfo.mLoadResult,
false);
aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
return true;
}

View File

@ -164,8 +164,13 @@ ServiceWorkerContainer::GetController()
return nullptr;
}
// TODO: What should we do here if the ServiceWorker script fails to load?
// In theory the DOM ServiceWorker object can exist without the worker
// thread running, but it seems our design does not expect that.
nsCOMPtr<nsISupports> serviceWorker;
rv = swm->GetDocumentController(GetOwner(), getter_AddRefs(serviceWorker));
rv = swm->GetDocumentController(GetOwner(),
nullptr, // aLoadFailedRunnable
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}

View File

@ -1062,6 +1062,11 @@ public:
MOZ_ASSERT(!data->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
data->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true);
// Call FailScopeUpdate on main thread if the SW script load fails below.
nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs
<StorensRefPtrPassByPtr<ServiceWorkerManager>, nsCString>
(this, &ServiceWorkerRegisterJob::FailScopeUpdate, swm, scopeKey);
MOZ_ASSERT(!mUpdateAndInstallInfo);
mUpdateAndInstallInfo =
new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec,
@ -1069,11 +1074,11 @@ public:
nsRefPtr<ServiceWorker> serviceWorker;
rv = swm->CreateServiceWorker(mRegistration->mPrincipal,
mUpdateAndInstallInfo,
failRunnable,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
data->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
return Fail(NS_ERROR_DOM_ABORT_ERR);
return FailScopeUpdate(swm, scopeKey);
}
nsRefPtr<ServiceWorkerJob> upcasted = this;
@ -1088,11 +1093,22 @@ public:
jsapi.Init();
bool ok = r->Dispatch(jsapi.cx());
if (NS_WARN_IF(!ok)) {
data->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
return Fail(NS_ERROR_DOM_ABORT_ERR);
return FailScopeUpdate(swm, scopeKey);
}
}
void
FailScopeUpdate(ServiceWorkerManager* aSwm, const nsACString& aScopeKey)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aSwm);
ServiceWorkerManager::RegistrationDataPerPrincipal* data;
if (aSwm->mRegistrationInfos.Get(aScopeKey, &data)) {
data->mSetOfScopesBeingUpdated.Remove(aScopeKey);
}
Fail(NS_ERROR_DOM_ABORT_ERR);
}
// Public so our error handling code can use it.
// Callers MUST hold a strong ref before calling this!
void
@ -1168,9 +1184,15 @@ public:
NS_DispatchToMainThread(upr);
// Call ContinueAfterInstallEvent(false, false) on main thread if the SW
// script fails to load.
nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs<bool, bool>
(this, &ServiceWorkerRegisterJob::ContinueAfterInstallEvent, false, false);
nsRefPtr<ServiceWorker> serviceWorker;
rv = swm->CreateServiceWorker(mRegistration->mPrincipal,
mRegistration->mInstallingWorker,
failRunnable,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -1834,18 +1856,20 @@ ServiceWorkerRegistrationInfo::Activate()
this);
NS_DispatchToMainThread(controllerChangeRunnable);
nsCOMPtr<nsIRunnable> failRunnable =
NS_NewRunnableMethodWithArg<bool>(this,
&ServiceWorkerRegistrationInfo::FinishActivate,
false /* success */);
MOZ_ASSERT(mActiveWorker);
nsRefPtr<ServiceWorker> serviceWorker;
nsresult rv =
swm->CreateServiceWorker(mPrincipal,
mActiveWorker,
failRunnable,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableMethodWithArg<bool>(this,
&ServiceWorkerRegistrationInfo::FinishActivate,
false /* success */);
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(failRunnable)));
return;
}
@ -2227,7 +2251,7 @@ ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
}
nsRefPtr<ServiceWorker> serviceWorker =
CreateServiceWorkerForScope(attrs, aScope);
CreateServiceWorkerForScope(attrs, aScope, nullptr /* failure runnable */);
if (!serviceWorker) {
return NS_ERROR_FAILURE;
}
@ -2262,7 +2286,7 @@ ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginA
}
nsRefPtr<ServiceWorker> serviceWorker =
CreateServiceWorkerForScope(attrs, aScope);
CreateServiceWorkerForScope(attrs, aScope, nullptr /* fail runnable */);
if (!serviceWorker) {
return NS_ERROR_FAILURE;
}
@ -2387,7 +2411,8 @@ ServiceWorkerManager::SendNotificationClickEvent(const nsACString& aOriginSuffix
return NS_ERROR_INVALID_ARG;
}
nsRefPtr<ServiceWorker> serviceWorker = CreateServiceWorkerForScope(attrs, aScope);
nsRefPtr<ServiceWorker> serviceWorker =
CreateServiceWorkerForScope(attrs, aScope, nullptr);
if (!serviceWorker) {
return NS_ERROR_FAILURE;
}
@ -2525,7 +2550,8 @@ ServiceWorkerManager::CheckReadyPromise(nsPIDOMWindow* aWindow,
already_AddRefed<ServiceWorker>
ServiceWorkerManager::CreateServiceWorkerForScope(const OriginAttributes& aOriginAttributes,
const nsACString& aScope)
const nsACString& aScope,
nsIRunnable* aLoadFailedRunnable)
{
AssertIsOnMainThread();
@ -2547,6 +2573,7 @@ ServiceWorkerManager::CreateServiceWorkerForScope(const OriginAttributes& aOrigi
nsRefPtr<ServiceWorker> sw;
rv = CreateServiceWorker(registration->mPrincipal,
registration->mActiveWorker,
aLoadFailedRunnable,
getter_AddRefs(sw));
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -2827,6 +2854,7 @@ ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess)
NS_IMETHODIMP
ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
ServiceWorkerInfo* aInfo,
nsIRunnable* aLoadFailedRunnable,
ServiceWorker** aServiceWorker)
{
AssertIsOnMainThread();
@ -2851,6 +2879,7 @@ ServiceWorkerManager::CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
MOZ_ASSERT(!aInfo->CacheName().IsEmpty());
loadInfo.mServiceWorkerCacheName = aInfo->CacheName();
loadInfo.mServiceWorkerID = aInfo->ID();
loadInfo.mLoadFailedAsyncRunnable = aLoadFailedRunnable;
RuntimeService* rs = RuntimeService::GetOrCreateService();
if (!rs) {
@ -3418,9 +3447,13 @@ ServiceWorkerManager::GetServiceWorkerForScope(nsIDOMWindow* aWindow,
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
// TODO: How should we handle async failure of SW load for getters? In
// theory getting a handle to the DOM ServiceWorker object should not
// require the worker thread to actually be running.
nsRefPtr<ServiceWorker> serviceWorker;
rv = CreateServiceWorkerForWindow(window,
info,
nullptr, // load failed runnable
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@ -3690,11 +3723,17 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut
return;
}
// if the ServiceWorker script fails to load for some reason, just resume
// the original channel.
nsCOMPtr<nsIRunnable> failRunnable =
NS_NewRunnableMethod(aChannel, &nsIInterceptedChannel::ResetInterception);
nsAutoPtr<ServiceWorkerClientInfo> clientInfo;
if (!isNavigation) {
MOZ_ASSERT(aDoc);
aRv = GetDocumentController(aDoc->GetInnerWindow(), getter_AddRefs(serviceWorker));
aRv = GetDocumentController(aDoc->GetInnerWindow(), failRunnable,
getter_AddRefs(serviceWorker));
clientInfo = new ServiceWorkerClientInfo(aDoc, aDoc->GetWindow());
} else {
nsCOMPtr<nsIChannel> internalChannel;
@ -3723,6 +3762,7 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut
nsRefPtr<ServiceWorker> sw;
aRv = CreateServiceWorker(registration->mPrincipal,
registration->mActiveWorker,
failRunnable,
getter_AddRefs(sw));
serviceWorker = sw.forget();
}
@ -3806,7 +3846,9 @@ ServiceWorkerManager::GetDocumentRegistration(nsIDocument* aDoc,
* the document was loaded.
*/
NS_IMETHODIMP
ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow, nsISupports** aServiceWorker)
ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow,
nsIRunnable* aLoadFailedRunnable,
nsISupports** aServiceWorker)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aWindow);
MOZ_ASSERT(window);
@ -3825,6 +3867,7 @@ ServiceWorkerManager::GetDocumentController(nsIDOMWindow* aWindow, nsISupports**
nsRefPtr<ServiceWorker> serviceWorker;
rv = CreateServiceWorkerForWindow(window,
registration->mActiveWorker,
aLoadFailedRunnable,
getter_AddRefs(serviceWorker));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
@ -3867,6 +3910,7 @@ ServiceWorkerManager::GetActive(nsIDOMWindow* aWindow,
NS_IMETHODIMP
ServiceWorkerManager::CreateServiceWorker(nsIPrincipal* aPrincipal,
ServiceWorkerInfo* aInfo,
nsIRunnable* aLoadFailedRunnable,
ServiceWorker** aServiceWorker)
{
AssertIsOnMainThread();
@ -3921,6 +3965,8 @@ ServiceWorkerManager::CreateServiceWorker(nsIPrincipal* aPrincipal,
// them here.
WorkerPrivate::OverrideLoadInfoLoadGroup(info);
info.mLoadFailedAsyncRunnable = aLoadFailedRunnable;
RuntimeService* rs = RuntimeService::GetOrCreateService();
if (!rs) {
return NS_ERROR_FAILURE;

View File

@ -420,11 +420,13 @@ private:
NS_IMETHOD
CreateServiceWorkerForWindow(nsPIDOMWindow* aWindow,
ServiceWorkerInfo* aInfo,
nsIRunnable* aLoadFailedRunnable,
ServiceWorker** aServiceWorker);
NS_IMETHOD
CreateServiceWorker(nsIPrincipal* aPrincipal,
ServiceWorkerInfo* aInfo,
nsIRunnable* aLoadFailedRunnable,
ServiceWorker** aServiceWorker);
NS_IMETHODIMP
@ -435,7 +437,8 @@ private:
already_AddRefed<ServiceWorker>
CreateServiceWorkerForScope(const OriginAttributes& aOriginAttributes,
const nsACString& aScope);
const nsACString& aScope,
nsIRunnable* aLoadFailedRunnable);
void
InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,

View File

@ -2384,6 +2384,9 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo& aOther)
MOZ_ASSERT(!mLoadGroup);
aOther.mLoadGroup.swap(mLoadGroup);
MOZ_ASSERT(!mLoadFailedAsyncRunnable);
aOther.mLoadFailedAsyncRunnable.swap(mLoadFailedAsyncRunnable);
MOZ_ASSERT(!mInterfaceRequestor);
aOther.mInterfaceRequestor.swap(mInterfaceRequestor);
@ -3418,7 +3421,7 @@ WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
AssertIsOnParentThread();
MOZ_ASSERT(!mMainThreadObjectsForgotten);
static const uint32_t kDoomedCount = 9;
static const uint32_t kDoomedCount = 10;
aDoomed.SetCapacity(kDoomedCount);
@ -3430,6 +3433,7 @@ WorkerPrivateParent<Derived>::ForgetMainThreadObjects(
SwapToISupportsArray(mLoadInfo.mChannel, aDoomed);
SwapToISupportsArray(mLoadInfo.mCSP, aDoomed);
SwapToISupportsArray(mLoadInfo.mLoadGroup, aDoomed);
SwapToISupportsArray(mLoadInfo.mLoadFailedAsyncRunnable, aDoomed);
SwapToISupportsArray(mLoadInfo.mInterfaceRequestor, aDoomed);
// Before adding anything here update kDoomedCount above!
@ -5466,6 +5470,19 @@ WorkerPrivate::RunBeforeNextEvent(nsIRunnable* aRunnable)
return true;
}
void
WorkerPrivate::MaybeDispatchLoadFailedRunnable()
{
AssertIsOnWorkerThread();
nsCOMPtr<nsIRunnable> runnable = StealLoadFailedAsyncRunnable();
if (!runnable) {
return;
}
MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget())));
}
void
WorkerPrivate::InitializeGCTimers()
{

View File

@ -798,6 +798,12 @@ public:
void
UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup);
already_AddRefed<nsIRunnable>
StealLoadFailedAsyncRunnable()
{
return mLoadInfo.mLoadFailedAsyncRunnable.forget();
}
IMPL_EVENT_HANDLER(message)
IMPL_EVENT_HANDLER(error)
@ -956,6 +962,9 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
nsRefPtrHashtable<nsUint64HashKey, MessagePort> mWorkerPorts;
// fired on the main thread if the worker script fails to load
nsCOMPtr<nsIRunnable> mLoadFailedRunnable;
TimeStamp mKillTime;
uint32_t mErrorHandlerRecursionCount;
uint32_t mNextTimeoutId;
@ -1367,6 +1376,9 @@ public:
bool
RunBeforeNextEvent(nsIRunnable* aRunnable);
void
MaybeDispatchLoadFailedRunnable();
private:
WorkerPrivate(JSContext* aCx, WorkerPrivate* aParent,
const nsAString& aScriptURL, bool aIsChromeWorker,

View File

@ -39,6 +39,7 @@ class nsIPrincipal;
class nsILoadGroup;
class nsITabChild;
class nsIChannel;
class nsIRunnable;
class nsIURI;
namespace mozilla {
@ -221,6 +222,12 @@ struct WorkerLoadInfo
nsCOMPtr<nsIChannel> mChannel;
nsCOMPtr<nsILoadGroup> mLoadGroup;
// mLoadFailedAsyncRunnable will execute on main thread if script loading
// fails during script loading. If script loading is never started due to
// a synchronous error, then the runnable is never executed. The runnable
// is guaranteed to be released on the main thread.
nsCOMPtr<nsIRunnable> mLoadFailedAsyncRunnable;
class InterfaceRequestor final : public nsIInterfaceRequestor
{
NS_DECL_ISUPPORTS