gecko-dev/dom/workers/ServiceWorkerManager.h
Andrew Sutherland d1659931b7 Bug 1267473 - Report to console if service worker script 404s. r=bkelly
Add an error message of the following form for when a register/update job
fails for network reasons:

Failed to register/update a ServiceWorker for scope
‘http://mochi.test:8888/tests/dom/workers/test/serviceworkers/network_error/’:
Load failed with status 404 for script
‘http://mochi.test:8888/tests/dom/workers/test/serviceworkers/404.js’.

A mochitest is added that verifies this.

To simplify the process of logging error messages, ServiceWorkerManager gains
a new LocalizeAndReportToAllClients method that always provides the SW scope as
the first argument to the localized string since all good error messages should
include it.

Its argument list takes an nsTArray<nsString> in order to reduce the potential
for use-after-free scenarios from the char16_t** signature that unfortunately
has rippled outwards from the nsIStringBundle interface.  This potentially
results in more memory allocation and byte shuffling than is strictly
necessary, but we're also talking about rare error logging where it's
better to optimize for easily adding the messages without needing to get hung
up on the life-cycle of temporaries.

nsTArray gained a std::initializer_list in bug 1228641.  It is explicit, so
inline argument usages may take a form along the lines of:
`nsTArray<nsString> { string1, string2, ... }`

This change did necessitate a change to nsContentUtils to add an nsTArray
variant of FormatLocalizedString since the existing public function was
slightly too clever.  It used a template function to statically acquire the
number of arguments at compile time, which is not compatible with the dynamic
nsTArray usage. Since nsTArray may be useful to other consumers as well, I
placed the conversion logic in nsContentUtils.
2016-07-10 22:22:27 -04:00

490 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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_serviceworkermanager_h
#define mozilla_dom_workers_serviceworkermanager_h
#include "nsIServiceWorkerManager.h"
#include "nsCOMPtr.h"
#include "ipc/IPCMessageUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Preferences.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerCommon.h"
#include "mozilla/dom/ServiceWorkerRegistrar.h"
#include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
#include "mozilla/dom/workers/ServiceWorkerRegistrationInfo.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "nsRefPtrHashtable.h"
#include "nsTArrayForwardDeclare.h"
#include "nsTObserverArray.h"
class mozIApplicationClearPrivateDataParams;
namespace mozilla {
class PrincipalOriginAttributes;
namespace dom {
class ServiceWorkerRegistrationListener;
namespace workers {
class ServiceWorkerClientInfo;
class ServiceWorkerInfo;
class ServiceWorkerJobQueue;
class ServiceWorkerManagerChild;
class ServiceWorkerPrivate;
class ServiceWorkerUpdateFinishCallback
{
protected:
virtual ~ServiceWorkerUpdateFinishCallback()
{}
public:
NS_INLINE_DECL_REFCOUNTING(ServiceWorkerUpdateFinishCallback)
virtual
void UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) = 0;
virtual
void UpdateFailed(ErrorResult& aStatus) = 0;
};
#define NS_SERVICEWORKERMANAGER_IMPL_IID \
{ /* f4f8755a-69ca-46e8-a65d-775745535990 */ \
0xf4f8755a, \
0x69ca, \
0x46e8, \
{ 0xa6, 0x5d, 0x77, 0x57, 0x45, 0x53, 0x59, 0x90 } \
}
/*
* The ServiceWorkerManager is a per-process global that deals with the
* installation, querying and event dispatch of ServiceWorkers for all the
* origins in the process.
*/
class ServiceWorkerManager final
: public nsIServiceWorkerManager
, public nsIIPCBackgroundChildCreateCallback
, public nsIObserver
{
friend class GetReadyPromiseRunnable;
friend class GetRegistrationsRunnable;
friend class GetRegistrationRunnable;
friend class ServiceWorkerJob;
friend class ServiceWorkerRegistrationInfo;
friend class ServiceWorkerUnregisterJob;
friend class ServiceWorkerUpdateJob;
friend class UpdateTimerCallback;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISERVICEWORKERMANAGER
NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
NS_DECL_NSIOBSERVER
struct RegistrationDataPerPrincipal;
nsClassHashtable<nsCStringHashKey, RegistrationDataPerPrincipal> mRegistrationInfos;
nsTObserverArray<ServiceWorkerRegistrationListener*> mServiceWorkerRegistrationListeners;
nsRefPtrHashtable<nsISupportsHashKey, ServiceWorkerRegistrationInfo> mControlledDocuments;
// Track all documents that have attempted to register a service worker for a
// given scope.
typedef nsTArray<nsCOMPtr<nsIWeakReference>> WeakDocumentList;
nsClassHashtable<nsCStringHashKey, WeakDocumentList> mRegisteringDocuments;
// Track all intercepted navigation channels for a given scope. Channels are
// placed in the appropriate list before dispatch the FetchEvent to the worker
// thread and removed once FetchEvent processing dispatches back to the main
// thread.
//
// Note: Its safe to use weak references here because a RAII-style callback
// is registered with the channel before its added to this list. We
// are guaranteed the callback will fire before and remove the ref
// from this list before the channel is destroyed.
typedef nsTArray<nsIInterceptedChannel*> InterceptionList;
nsClassHashtable<nsCStringHashKey, InterceptionList> mNavigationInterceptions;
bool
IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI);
bool
IsControlled(nsIDocument* aDocument, ErrorResult& aRv);
void
DispatchFetchEvent(const PrincipalOriginAttributes& aOriginAttributes,
nsIDocument* aDoc,
const nsAString& aDocumentIdForTopLevelNavigation,
nsIInterceptedChannel* aChannel,
bool aIsReload,
bool aIsSubresourceLoad,
ErrorResult& aRv);
void
Update(nsIPrincipal* aPrincipal,
const nsACString& aScope,
ServiceWorkerUpdateFinishCallback* aCallback);
void
SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
const nsACString& aScope);
void
PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
const nsAString& aScope);
void
PropagateRemove(const nsACString& aHost);
void
Remove(const nsACString& aHost);
void
PropagateRemoveAll();
void
RemoveAll();
already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(nsIPrincipal* aPrincipal, const nsACString& aScope) const;
ServiceWorkerRegistrationInfo*
CreateNewRegistration(const nsCString& aScope, nsIPrincipal* aPrincipal);
void
RemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
void StoreRegistration(nsIPrincipal* aPrincipal,
ServiceWorkerRegistrationInfo* aRegistration);
void
FinishFetch(ServiceWorkerRegistrationInfo* aRegistration);
/**
* Report an error for the given scope to any window we think might be
* interested, failing over to the Browser Console if we couldn't find any.
*
* Error messages should be localized, so you probably want to call
* LocalizeAndReportToAllClients instead, which in turn calls us after
* localizing the error.
*/
void
ReportToAllClients(const nsCString& aScope,
const nsString& aMessage,
const nsString& aFilename,
const nsString& aLine,
uint32_t aLineNumber,
uint32_t aColumnNumber,
uint32_t aFlags);
/**
* Report a localized error for the given scope to any window we think might
* be interested.
*
* Note that this method takes an nsTArray<nsString> for the parameters, not
* bare chart16_t*[]. You can use a std::initializer_list constructor inline
* so that argument might look like: nsTArray<nsString> { some_nsString,
* PromiseFlatString(some_nsSubString_aka_nsAString),
* NS_ConvertUTF8toUTF16(some_nsCString_or_nsCSubString),
* NS_LITERAL_STRING("some literal") }. If you have anything else, like a
* number, you can use an nsAutoString with AppendInt/friends.
*
* @param [aFlags]
* The nsIScriptError flag, one of errorFlag (0x0), warningFlag (0x1),
* infoFlag (0x8). We default to error if omitted because usually we're
* logging exceptional and/or obvious breakage.
*/
static void
LocalizeAndReportToAllClients(const nsCString& aScope,
const char* aStringKey,
const nsTArray<nsString>& aParamArray,
uint32_t aFlags = 0x0,
const nsString& aFilename = EmptyString(),
const nsString& aLine = EmptyString(),
uint32_t aLineNumber = 0,
uint32_t aColumnNumber = 0);
// Always consumes the error by reporting to consoles of all controlled
// documents.
void
HandleError(JSContext* aCx,
nsIPrincipal* aPrincipal,
const nsCString& aScope,
const nsString& aWorkerURL,
const nsString& aMessage,
const nsString& aFilename,
const nsString& aLine,
uint32_t aLineNumber,
uint32_t aColumnNumber,
uint32_t aFlags,
JSExnType aExnType);
UniquePtr<ServiceWorkerClientInfo>
GetClient(nsIPrincipal* aPrincipal,
const nsAString& aClientId,
ErrorResult& aRv);
void
GetAllClients(nsIPrincipal* aPrincipal,
const nsCString& aScope,
bool aIncludeUncontrolled,
nsTArray<ServiceWorkerClientInfo>& aDocuments);
void
MaybeClaimClient(nsIDocument* aDocument,
ServiceWorkerRegistrationInfo* aWorkerRegistration);
nsresult
ClaimClients(nsIPrincipal* aPrincipal, const nsCString& aScope, uint64_t aId);
nsresult
SetSkipWaitingFlag(nsIPrincipal* aPrincipal, const nsCString& aScope,
uint64_t aServiceWorkerID);
static already_AddRefed<ServiceWorkerManager>
GetInstance();
void
LoadRegistration(const ServiceWorkerRegistrationData& aRegistration);
void
LoadRegistrations(const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
// Used by remove() and removeAll() when clearing history.
// MUST ONLY BE CALLED FROM UnregisterIfMatchesHost!
void
ForceUnregister(RegistrationDataPerPrincipal* aRegistrationData,
ServiceWorkerRegistrationInfo* aRegistration);
NS_IMETHOD
AddRegistrationEventListener(const nsAString& aScope,
ServiceWorkerRegistrationListener* aListener);
NS_IMETHOD
RemoveRegistrationEventListener(const nsAString& aScope,
ServiceWorkerRegistrationListener* aListener);
void
MaybeCheckNavigationUpdate(nsIDocument* aDoc);
nsresult
SendPushEvent(const nsACString& aOriginAttributes,
const nsACString& aScope,
const nsAString& aMessageId,
const Maybe<nsTArray<uint8_t>>& aData);
nsresult
NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
private:
ServiceWorkerManager();
~ServiceWorkerManager();
void
Init();
already_AddRefed<ServiceWorkerJobQueue>
GetOrCreateJobQueue(const nsACString& aOriginSuffix,
const nsACString& aScope);
void
MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetRegistration(const nsACString& aScopeKey,
const nsACString& aScope) const;
void
AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration);
nsresult
Update(ServiceWorkerRegistrationInfo* aRegistration);
nsresult
GetDocumentRegistration(nsIDocument* aDoc,
ServiceWorkerRegistrationInfo** aRegistrationInfo);
nsresult
GetServiceWorkerForScope(nsPIDOMWindowInner* aWindow,
const nsAString& aScope,
WhichServiceWorker aWhichWorker,
nsISupports** aServiceWorker);
ServiceWorkerInfo*
GetActiveWorkerInfoForScope(const PrincipalOriginAttributes& aOriginAttributes,
const nsACString& aScope);
ServiceWorkerInfo*
GetActiveWorkerInfoForDocument(nsIDocument* aDocument);
void
InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
WhichServiceWorker aWhichOnes);
void
NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
void
StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
nsIDocument* aDoc,
const nsAString& aDocumentId);
void
StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(nsPIDOMWindowInner* aWindow);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(nsIDocument* aDoc);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal, nsIURI* aURI);
already_AddRefed<ServiceWorkerRegistrationInfo>
GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey,
nsIURI* aURI);
// This method generates a key using appId and isInElementBrowser from the
// principal. We don't use the origin because it can change during the
// loading.
static nsresult
PrincipalToScopeKey(nsIPrincipal* aPrincipal, nsACString& aKey);
static void
AddScopeAndRegistration(const nsACString& aScope,
ServiceWorkerRegistrationInfo* aRegistation);
static bool
FindScopeForPath(const nsACString& aScopeKey,
const nsACString& aPath,
RegistrationDataPerPrincipal** aData, nsACString& aMatch);
static bool
HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope);
static void
RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration);
void
QueueFireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration,
const nsAString& aName);
void
FireUpdateFoundOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration);
void
FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration);
void
StorePendingReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
Promise* aPromise);
void
CheckPendingReadyPromises();
bool
CheckReadyPromise(nsPIDOMWindowInner* aWindow, nsIURI* aURI,
Promise* aPromise);
struct PendingReadyPromise final
{
PendingReadyPromise(nsIURI* aURI, Promise* aPromise)
: mURI(aURI), mPromise(aPromise)
{}
nsCOMPtr<nsIURI> mURI;
RefPtr<Promise> mPromise;
};
void AppendPendingOperation(nsIRunnable* aRunnable);
bool HasBackgroundActor() const
{
return !!mActor;
}
nsClassHashtable<nsISupportsHashKey, PendingReadyPromise> mPendingReadyPromises;
void
MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration);
// Removes all service worker registrations that matches the given pattern.
void
RemoveAllRegistrations(OriginAttributesPattern* aPattern);
RefPtr<ServiceWorkerManagerChild> mActor;
nsTArray<nsCOMPtr<nsIRunnable>> mPendingOperations;
bool mShuttingDown;
nsTArray<nsCOMPtr<nsIServiceWorkerManagerListener>> mListeners;
void
NotifyListenersOnRegister(nsIServiceWorkerRegistrationInfo* aRegistration);
void
NotifyListenersOnUnregister(nsIServiceWorkerRegistrationInfo* aRegistration);
void
AddRegisteringDocument(const nsACString& aScope, nsIDocument* aDoc);
class InterceptionReleaseHandle;
void
AddNavigationInterception(const nsACString& aScope,
nsIInterceptedChannel* aChannel);
void
RemoveNavigationInterception(const nsACString& aScope,
nsIInterceptedChannel* aChannel);
void
ScheduleUpdateTimer(nsIPrincipal* aPrincipal, const nsACString& aScope);
void
UpdateTimerFired(nsIPrincipal* aPrincipal, const nsACString& aScope);
void
MaybeSendUnregister(nsIPrincipal* aPrincipal, const nsACString& aScope);
nsresult
SendNotificationEvent(const nsAString& aEventName,
const nsACString& aOriginSuffix,
const nsACString& aScope,
const nsAString& aID,
const nsAString& aTitle,
const nsAString& aDir,
const nsAString& aLang,
const nsAString& aBody,
const nsAString& aTag,
const nsAString& aIcon,
const nsAString& aData,
const nsAString& aBehavior);
};
} // namespace workers
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_workers_serviceworkermanager_h