Files
palemoon27/dom/workers/ServiceWorkerScriptCache.cpp
T
roytam1 938961e76b import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1182147: Ensure ImageHost does not attempt to call DrawQuad with an effect unsupported by the compositor. r=nical (5624e9134f)
- Bug 1151749 - "Win-Only: Disabled Hardware Acceleration + CSS 3D transform on canvas -> black artefacts on hover". r=nical (54c49071b9)
- Bug 1184224 - Remove gfxPrefs.h include from gfxPlatformGtk.h. r=karlt (eb907daa58)
- Bug 1183820 - Expose if cairo uses XRender to GetAzureBackendInfo. r=mattwoodrow (10044ecfb0)
- Bug 1128934 - Call XFlush after compositing to prevent jank. r=karlt (f63b9dc1b5)
- Bug 1151663 - New mochitest for this bug. r=kats (9344d22ed6)
- Bug 1176402 - Have synthesizeNativeWheel() and synthesizeNativeMouseMove() accept coordinates relative to the element's bounding rect. r=kats (cbdffe3869)
- Bug 1177018 - Work around the iframe in which mochitests are run not being scrollable. r=kats (d24291080c)
- Bug 1177018 - Disable smooth scrolling in the APZ layerization test. r=kats (e1af545da1)
- Bug 1181678 - Improve the check for skipping test_layerization if APZ is disabled. r=botond (91e5ead610)
- Bug 1177018 - Enable chaos mode for the APZ layerization test. r=kats (f07fdeb936)
- Bug 1177018 - Only enable APZ test logging for tests that actually use it. r=kats (9f24ea01c4)
- Bug 1196294 - Remove the mCause from TimelineMarkers, r=tromey (5abd1c057d)
- Bug 1195232 - Stop using TracingMetadata from GeckoProfiler.h, r=tromey (e736038873)
- Bug 1200118 - Create a barebones TimelineMarker abstract class, r=tromey (9930c83c2f)
- Bug 1200252 - Add marker for when the IPC request for a composite is sent to the compositor thread, r=jsantell,matt (becc11f58b)
- align some minor thing to gecko (5bb64e322a)
- Bug 1204581 - Add a deprecation warning for the usage of AppCache when service worker fetch interception is enabled; r=mcmanus,baku (69949c8d96)
- Bug 1210941 P2 Use LOAD_BYPASS_SERVICE_WORKER in HttpBaseChannel instead of mForceNoIntercept. r=jduell (fdb7afc0be)
- Bug 1173171 - Disable external jar: via preference. r=jduell (704cfb0b9c)
- Bug 1210941 P3 Make jar channels use LOAD_BYPASS_SERVICE_WORKER internally. r=jduell (d871dc4837)
- Bug 1210941 P4 Use LOAD_BYPASS_SERVICE_WORKER instead of ForceNoIntercept in nsDocShell. r=ehsan (ea5ed76717)
- Bug 1210941 P5 Use LOAD_BYPASS_SERVICE_WORKER in nsObjectLoadingContent instead of ForceNoIntercept(). r=ehsan (9e821adfaa)
- Bug 1210941 P6 Use LOAD_BYPASS_SERVICE_WORKER instead of ForceNoIntercept in FetchDriver. r=ehsan (02685be882)
- Bug 1210941 P7 Use LOAD_BYPASS_SERVICE_WORKER in ServiceWorkerScriptCache. r=ehsan (45fa163dd2)
- Bug 1182112 - XML documents should not be intercepted by ServiceWorkers. r=ehsan (a9dfeffcb3)
- Bug 1210941 P8 Use LOAD_BYPASS_SERVICE_WORKER in xslt txURIUtils. r=ehsan (d7686d572b)
- Bug 1210941 P9 Use LOAD_BYPASS_SERVICE_WORKER in nsCORSListenerProxy. r=ehsan (653f50de45)
- Bug 1205302 - Disallow intercepting OCSP requests; r=jdm (b4650e78d9)
- Bug 1210941 P10 Use LOAD_BYPASS_SERVICE_WORKER in nsNSSCallbacks. r=ehsan (b4b8b3f503)
- Bug 1210941 P11 Use LOAD_BYPASS_SERVICE_WORKER in worker ScriptLoader. r=ehsan (ac07404832)
- Bug 1210941 P12 Remove http channel's ForceNoIntercept. r=jduell IGNORE IDL (a7592a83c4)
- Bug 1210941 P13 Remove ForceNoIntercept from jar channel. r=jduell (617544fcee)
- Bug 1169613 - Use content type of synthesized response for JAR channel requests if available. Part 1: Set Content-Type of synthesized reponses for JAR channel requests. r=jdm (e32061a26a)
- Bug 1169613 - Use content type of synthesized response for JAR channel requests if available. Part 2: Set Content-Type for JAR Channel requests on Fetch API responses. r=nsm (da43e29583)
- Bug 1207556 - Part 1: Stop reusing the loadinfo in StartCORSPreflight; r=sicking (4724bfa8cd)
- Bug 1207556 - Part 2: Fix the beacon CORS preflight tests; r=sicking (c61a699f9f)
- Bug 1111834 - CORS preflight of navigator.sendBeacon() should not follow 30x redirect - tests. r=sicking (2871ad22e7)
- fix build because of previous backports (e3096e6f9a)
- Bug 1161677 - Expose dev mode state read-only through the navigator.hasFeature() api r=ehsan (013399847b)
- Bug 1203680 P8 nsCORSListenerProxy::OnStartRequest must always call outer OnStartRequest. r=ehsan (9a67709770)
- spacing (d4511b37c4)
- missing bit of 920017 and some telemetry (2bf267ce72)
2022-09-26 16:21:15 +08:00

1039 lines
25 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/. */
#include "ServiceWorkerScriptCache.h"
#include "mozilla/unused.h"
#include "mozilla/dom/CacheBinding.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "nsIHttpChannelInternal.h"
#include "nsIStreamLoader.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIPrincipal.h"
#include "nsNetUtil.h"
#include "nsScriptLoader.h"
#include "Workers.h"
#include "nsStringStream.h"
using mozilla::dom::cache::Cache;
using mozilla::dom::cache::CacheStorage;
BEGIN_WORKERS_NAMESPACE
namespace serviceWorkerScriptCache {
namespace {
// XXX A sandbox nsIGlobalObject does not preserve its reflector, so |aSandbox|
// must be kept alive as long as the CacheStorage if you want to ensure that
// the CacheStorage will continue to work. Failures will manifest as errors
// like "JavaScript error: , line 0: TypeError: The expression cannot be
// converted to return the specified type."
already_AddRefed<CacheStorage>
CreateCacheStorage(JSContext* aCx, nsIPrincipal* aPrincipal, ErrorResult& aRv,
JS::MutableHandle<JSObject*> aSandbox)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
aRv = xpc->CreateSandbox(aCx, aPrincipal, aSandbox.address());
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(aSandbox);
if (!sandboxGlobalObject) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// We assume private browsing is not enabled here. The ScriptLoader
// explicitly fails for private browsing so there should never be
// a service worker running in private browsing mode. Therefore if
// we are purging scripts or running a comparison algorithm we cannot
// be in private browing.
//
// Also, bypass the CacheStorage trusted origin checks. The ServiceWorker
// has validated the origin prior to this point. All the information
// to revalidate is not available now.
return CacheStorage::CreateOnMainThread(cache::CHROME_ONLY_NAMESPACE,
sandboxGlobalObject, aPrincipal,
false /* private browsing */,
true /* force trusted origin */,
aRv);
}
class CompareManager;
// This class downloads a URL from the network and then it calls
// NetworkFinished() in the CompareManager.
class CompareNetwork final : public nsIStreamLoaderObserver,
public nsIRequestObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
NS_DECL_NSIREQUESTOBSERVER
explicit CompareNetwork(CompareManager* aManager)
: mManager(aManager)
{
MOZ_ASSERT(aManager);
AssertIsOnMainThread();
}
nsresult
Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL, nsILoadGroup* aLoadGroup)
{
MOZ_ASSERT(aPrincipal);
AssertIsOnMainThread();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsILoadGroup> loadGroup;
rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Note that because there is no "serviceworker" RequestContext type, we can
// use the TYPE_INTERNAL_SCRIPT content policy types when loading a service
// worker.
rv = NS_NewChannel(getter_AddRefs(mChannel),
uri, aPrincipal,
nsILoadInfo::SEC_NORMAL,
nsIContentPolicy::TYPE_INTERNAL_SCRIPT,
loadGroup,
nullptr, // aCallbacks
nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsLoadFlags flags;
rv = mChannel->GetLoadFlags(&flags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
flags |= nsIRequest::LOAD_BYPASS_CACHE;
rv = mChannel->SetLoadFlags(flags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
// Spec says no redirects allowed for SW scripts.
httpChannel->SetRedirectionLimit(0);
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mChannel->AsyncOpen(loader, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void
Abort()
{
AssertIsOnMainThread();
MOZ_ASSERT(mChannel);
mChannel->Cancel(NS_BINDING_ABORTED);
mChannel = nullptr;
}
const nsString& Buffer() const
{
AssertIsOnMainThread();
return mBuffer;
}
private:
~CompareNetwork()
{
AssertIsOnMainThread();
}
nsRefPtr<CompareManager> mManager;
nsCOMPtr<nsIChannel> mChannel;
nsString mBuffer;
};
NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver,
nsIRequestObserver)
// This class gets a cached Response from the CacheStorage and then it calls
// CacheFinished() in the CompareManager.
class CompareCache final : public PromiseNativeHandler
, public nsIStreamLoaderObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
explicit CompareCache(CompareManager* aManager)
: mManager(aManager)
, mState(WaitingForCache)
, mAborted(false)
{
MOZ_ASSERT(aManager);
AssertIsOnMainThread();
}
nsresult
Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
const nsAString& aCacheName);
void
Abort()
{
AssertIsOnMainThread();
MOZ_ASSERT(!mAborted);
mAborted = true;
if (mPump) {
mPump->Cancel(NS_BINDING_ABORTED);
mPump = nullptr;
}
}
// This class manages 2 promises: 1 is to retrieve cache object, and 2 is for
// the value from the cache. For this reason we have mState to know what
// reject/resolve callback we are handling.
virtual void
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
AssertIsOnMainThread();
if (mAborted) {
return;
}
if (mState == WaitingForCache) {
ManageCacheResult(aCx, aValue);
return;
}
MOZ_ASSERT(mState == WaitingForValue);
ManageValueResult(aCx, aValue);
}
virtual void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
const nsString& Buffer() const
{
AssertIsOnMainThread();
return mBuffer;
}
const nsString& URL() const
{
AssertIsOnMainThread();
return mURL;
}
private:
~CompareCache()
{
AssertIsOnMainThread();
}
void
ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
void
ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
nsRefPtr<CompareManager> mManager;
nsCOMPtr<nsIInputStreamPump> mPump;
nsString mURL;
nsString mBuffer;
enum {
WaitingForCache,
WaitingForValue
} mState;
bool mAborted;
};
NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
class CompareManager final : public PromiseNativeHandler
{
public:
NS_DECL_ISUPPORTS
explicit CompareManager(CompareCallback* aCallback)
: mCallback(aCallback)
, mState(WaitingForOpen)
, mNetworkFinished(false)
, mCacheFinished(false)
, mInCache(false)
{
AssertIsOnMainThread();
}
nsresult
Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
const nsAString& aCacheName, nsILoadGroup* aLoadGroup)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
mURL = aURL;
// Always create a CacheStorage since we want to write the network entry to
// the cache even if there isn't an existing one.
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult result;
mSandbox.init(jsapi.cx());
mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result, &mSandbox);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
return result.StealNSResult();
}
mCN = new CompareNetwork(this);
nsresult rv = mCN->Initialize(aPrincipal, aURL, aLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!aCacheName.IsEmpty()) {
mCC = new CompareCache(this);
rv = mCC->Initialize(aPrincipal, aURL, aCacheName);
if (NS_WARN_IF(NS_FAILED(rv))) {
mCN->Abort();
return rv;
}
}
return NS_OK;
}
const nsAString&
URL() const
{
AssertIsOnMainThread();
return mURL;
}
void
SetMaxScope(const nsACString& aMaxScope)
{
MOZ_ASSERT(!mNetworkFinished);
mMaxScope = aMaxScope;
}
void
NetworkFinished(nsresult aStatus)
{
AssertIsOnMainThread();
mNetworkFinished = true;
if (NS_WARN_IF(NS_FAILED(aStatus))) {
if (mCC) {
mCC->Abort();
}
ComparisonFinished(aStatus, false);
return;
}
MaybeCompare();
}
void
CacheFinished(nsresult aStatus, bool aInCache)
{
AssertIsOnMainThread();
mCacheFinished = true;
mInCache = aInCache;
if (NS_WARN_IF(NS_FAILED(aStatus))) {
if (mCN) {
mCN->Abort();
}
ComparisonFinished(aStatus, false);
return;
}
MaybeCompare();
}
void
MaybeCompare()
{
AssertIsOnMainThread();
if (!mNetworkFinished || (mCC && !mCacheFinished)) {
return;
}
if (NS_WARN_IF(!mCC || !mInCache)) {
ComparisonFinished(NS_OK, false);
return;
}
ComparisonFinished(NS_OK, mCC->Buffer().Equals(mCN->Buffer()));
}
// This class manages 2 promises: 1 is to retrieve Cache object, and 2 is to
// Put the value in the cache. For this reason we have mState to know what
// callback we are handling.
void
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
AssertIsOnMainThread();
MOZ_ASSERT(mCallback);
if (mState == WaitingForOpen) {
if (NS_WARN_IF(!aValue.isObject())) {
Fail(NS_ERROR_FAILURE);
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
Fail(NS_ERROR_FAILURE);
return;
}
Cache* cache = nullptr;
nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
if (NS_WARN_IF(NS_FAILED(rv))) {
Fail(rv);
return;
}
// Just to be safe.
nsRefPtr<Cache> kungfuDeathGrip = cache;
WriteToCache(cache);
return;
}
MOZ_ASSERT(mState == WaitingForPut);
mCallback->ComparisonResult(NS_OK, false /* aIsEqual */,
mNewCacheName, mMaxScope);
Cleanup();
}
void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
{
AssertIsOnMainThread();
if (mState == WaitingForOpen) {
NS_WARNING("Could not open cache.");
} else {
NS_WARNING("Could not write to cache.");
}
Fail(NS_ERROR_FAILURE);
}
CacheStorage*
CacheStorage_()
{
AssertIsOnMainThread();
MOZ_ASSERT(mCacheStorage);
return mCacheStorage;
}
void
InitChannelInfo(nsIChannel* aChannel)
{
mChannelInfo.InitFromChannel(aChannel);
}
nsresult
SetPrincipalInfo(nsIChannel* aChannel)
{
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
NS_ASSERTION(ssm, "Should never be null!");
nsCOMPtr<nsIPrincipal> channelPrincipal;
nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(new mozilla::ipc::PrincipalInfo());
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mPrincipalInfo = Move(principalInfo);
return NS_OK;
}
private:
~CompareManager()
{
AssertIsOnMainThread();
MOZ_ASSERT(!mCC);
MOZ_ASSERT(!mCN);
}
void
Fail(nsresult aStatus)
{
AssertIsOnMainThread();
mCallback->ComparisonResult(aStatus, false /* aIsEqual */,
EmptyString(), EmptyCString());
Cleanup();
}
void
Cleanup()
{
AssertIsOnMainThread();
MOZ_ASSERT(mCallback);
mCallback = nullptr;
mCN = nullptr;
mCC = nullptr;
}
void
ComparisonFinished(nsresult aStatus, bool aIsEqual)
{
AssertIsOnMainThread();
MOZ_ASSERT(mCallback);
if (NS_WARN_IF(NS_FAILED(aStatus))) {
Fail(aStatus);
return;
}
if (aIsEqual) {
mCallback->ComparisonResult(aStatus, aIsEqual, EmptyString(), mMaxScope);
Cleanup();
return;
}
// Write to Cache so ScriptLoader reads succeed.
WriteNetworkBufferToNewCache();
}
void
WriteNetworkBufferToNewCache()
{
AssertIsOnMainThread();
MOZ_ASSERT(mCN);
MOZ_ASSERT(mCacheStorage);
MOZ_ASSERT(mNewCacheName.IsEmpty());
ErrorResult result;
result = serviceWorkerScriptCache::GenerateCacheName(mNewCacheName);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
}
nsRefPtr<Promise> cacheOpenPromise = mCacheStorage->Open(mNewCacheName, result);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
}
cacheOpenPromise->AppendNativeHandler(this);
}
void
WriteToCache(Cache* aCache)
{
AssertIsOnMainThread();
MOZ_ASSERT(aCache);
MOZ_ASSERT(mState == WaitingForOpen);
ErrorResult result;
nsCOMPtr<nsIInputStream> body;
result = NS_NewStringInputStream(getter_AddRefs(body), mCN->Buffer());
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
}
nsRefPtr<InternalResponse> ir =
new InternalResponse(200, NS_LITERAL_CSTRING("OK"));
ir->SetBody(body);
ir->InitChannelInfo(mChannelInfo);
if (mPrincipalInfo) {
ir->SetPrincipalInfo(Move(mPrincipalInfo));
}
nsRefPtr<Response> response = new Response(aCache->GetGlobalObject(), ir);
RequestOrUSVString request;
request.SetAsUSVString().Rebind(URL().Data(), URL().Length());
// For now we have to wait until the Put Promise is fulfilled before we can
// continue since Cache does not yet support starting a read that is being
// written to.
nsRefPtr<Promise> cachePromise = aCache->Put(request, *response, result);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
}
mState = WaitingForPut;
cachePromise->AppendNativeHandler(this);
}
nsRefPtr<CompareCallback> mCallback;
JS::PersistentRooted<JSObject*> mSandbox;
nsRefPtr<CacheStorage> mCacheStorage;
nsRefPtr<CompareNetwork> mCN;
nsRefPtr<CompareCache> mCC;
nsString mURL;
// Only used if the network script has changed and needs to be cached.
nsString mNewCacheName;
ChannelInfo mChannelInfo;
UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
nsCString mMaxScope;
enum {
WaitingForOpen,
WaitingForPut
} mState;
bool mNetworkFinished;
bool mCacheFinished;
bool mInCache;
};
NS_IMPL_ISUPPORTS0(CompareManager)
NS_IMETHODIMP
CompareNetwork::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
AssertIsOnMainThread();
// If no channel, Abort() has been called.
if (!mChannel) {
return NS_OK;
}
#ifdef DEBUG
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
MOZ_ASSERT(channel == mChannel);
#endif
mManager->InitChannelInfo(mChannel);
nsresult rv = mManager->SetPrincipalInfo(mChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
CompareNetwork::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
nsresult aStatusCode)
{
// Nothing to do here!
return NS_OK;
}
NS_IMETHODIMP
CompareNetwork::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
nsresult aStatus, uint32_t aLen,
const uint8_t* aString)
{
AssertIsOnMainThread();
// If no channel, Abort() has been called.
if (!mChannel) {
return NS_OK;
}
if (NS_WARN_IF(NS_FAILED(aStatus))) {
if (aStatus == NS_ERROR_REDIRECT_LOOP) {
mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
} else {
mManager->NetworkFinished(aStatus);
}
return NS_OK;
}
nsCOMPtr<nsIRequest> request;
nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return NS_OK;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
if (httpChannel) {
bool requestSucceeded;
rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return NS_OK;
}
if (NS_WARN_IF(!requestSucceeded)) {
mManager->NetworkFinished(NS_ERROR_FAILURE);
return NS_OK;
}
nsAutoCString maxScope;
// Note: we explicitly don't check for the return value here, because the
// absence of the header is not an error condition.
unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Service-Worker-Allowed"),
maxScope);
mManager->SetMaxScope(maxScope);
nsAutoCString mimeType;
rv = httpChannel->GetContentType(mimeType);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
return rv;
}
if (!mimeType.LowerCaseEqualsLiteral("text/javascript") &&
!mimeType.LowerCaseEqualsLiteral("application/x-javascript") &&
!mimeType.LowerCaseEqualsLiteral("application/javascript")) {
mManager->NetworkFinished(NS_ERROR_DOM_SECURITY_ERR);
return rv;
}
}
else {
// The only supported request schemes are http, https, and app.
// Above, we check to ensure that the request is http or https
// based on the channel qi. Here we test the scheme to ensure
// that it is app. Otherwise, bail.
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (NS_WARN_IF(!channel)) {
mManager->NetworkFinished(NS_ERROR_FAILURE);
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
rv = channel->GetURI(getter_AddRefs(uri));
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return NS_OK;
}
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return NS_OK;
}
if (NS_WARN_IF(!scheme.LowerCaseEqualsLiteral("app"))) {
mManager->NetworkFinished(NS_ERROR_FAILURE);
return NS_OK;
}
}
char16_t* buffer = nullptr;
size_t len = 0;
rv = nsScriptLoader::ConvertToUTF16(httpChannel, aString, aLen,
NS_LITERAL_STRING("UTF-8"), nullptr,
buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->NetworkFinished(rv);
return rv;
}
mBuffer.Adopt(buffer, len);
mManager->NetworkFinished(NS_OK);
return NS_OK;
}
nsresult
CompareCache::Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
const nsAString& aCacheName)
{
MOZ_ASSERT(aPrincipal);
AssertIsOnMainThread();
mURL = aURL;
ErrorResult rv;
nsRefPtr<Promise> promise = mManager->CacheStorage_()->Open(aCacheName, rv);
if (NS_WARN_IF(rv.Failed())) {
MOZ_ASSERT(!rv.IsErrorWithMessage());
return rv.StealNSResult();
}
promise->AppendNativeHandler(this);
return NS_OK;
}
NS_IMETHODIMP
CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
nsresult aStatus, uint32_t aLen,
const uint8_t* aString)
{
AssertIsOnMainThread();
if (mAborted) {
return aStatus;
}
if (NS_WARN_IF(NS_FAILED(aStatus))) {
mManager->CacheFinished(aStatus, false);
return aStatus;
}
char16_t* buffer = nullptr;
size_t len = 0;
nsresult rv = nsScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
NS_LITERAL_STRING("UTF-8"),
nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return rv;
}
mBuffer.Adopt(buffer, len);
mManager->CacheFinished(NS_OK, true);
return NS_OK;
}
void
CompareCache::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
{
AssertIsOnMainThread();
if (mAborted) {
return;
}
mManager->CacheFinished(NS_ERROR_FAILURE, false);
}
void
CompareCache::ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
{
AssertIsOnMainThread();
if (NS_WARN_IF(!aValue.isObject())) {
mManager->CacheFinished(NS_ERROR_FAILURE, false);
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
mManager->CacheFinished(NS_ERROR_FAILURE, false);
return;
}
Cache* cache = nullptr;
nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return;
}
RequestOrUSVString request;
request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
ErrorResult error;
CacheQueryOptions params;
nsRefPtr<Promise> promise = cache->Match(request, params, error);
if (NS_WARN_IF(error.Failed())) {
mManager->CacheFinished(error.StealNSResult(), false);
return;
}
promise->AppendNativeHandler(this);
mState = WaitingForValue;
}
void
CompareCache::ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
{
AssertIsOnMainThread();
// The cache returns undefined if the object is not stored.
if (aValue.isUndefined()) {
mManager->CacheFinished(NS_OK, false);
return;
}
MOZ_ASSERT(aValue.isObject());
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
mManager->CacheFinished(NS_ERROR_FAILURE, false);
return;
}
Response* response = nullptr;
nsresult rv = UNWRAP_OBJECT(Response, obj, response);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return;
}
MOZ_ASSERT(response->Ok());
nsCOMPtr<nsIInputStream> inputStream;
response->GetBody(getter_AddRefs(inputStream));
MOZ_ASSERT(inputStream);
MOZ_ASSERT(!mPump);
rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return;
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
if (NS_WARN_IF(NS_FAILED(rv))) {
mManager->CacheFinished(rv, false);
return;
}
rv = mPump->AsyncRead(loader, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
mPump = nullptr;
mManager->CacheFinished(rv, false);
return;
}
nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
if (rr) {
nsCOMPtr<nsIEventTarget> sts =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
rv = rr->RetargetDeliveryTo(sts);
if (NS_WARN_IF(NS_FAILED(rv))) {
mPump = nullptr;
mManager->CacheFinished(rv, false);
return;
}
}
}
} // namespace
nsresult
PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
if (aCacheName.IsEmpty()) {
return NS_OK;
}
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult rv;
JS::Rooted<JSObject*> sandboxObject(jsapi.cx());
nsRefPtr<CacheStorage> cacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, rv, &sandboxObject);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
// We use the ServiceWorker scope as key for the cacheStorage.
nsRefPtr<Promise> promise =
cacheStorage->Delete(aCacheName, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
// We don't actually care about the result of the delete operation.
return NS_OK;
}
nsresult
GenerateCacheName(nsAString& aName)
{
nsresult rv;
nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
do_GetService("@mozilla.org/uuid-generator;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsID id;
rv = uuidGenerator->GenerateUUIDInPlace(&id);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
char chars[NSID_LENGTH];
id.ToProvidedString(chars);
aName.AssignASCII(chars, NSID_LENGTH);
return NS_OK;
}
nsresult
Compare(nsIPrincipal* aPrincipal, const nsAString& aCacheName,
const nsAString& aURL, CompareCallback* aCallback,
nsILoadGroup* aLoadGroup)
{
AssertIsOnMainThread();
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(!aURL.IsEmpty());
MOZ_ASSERT(aCallback);
nsRefPtr<CompareManager> cm = new CompareManager(aCallback);
nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName, aLoadGroup);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
} // namespace serviceWorkerScriptCache
END_WORKERS_NAMESPACE