gecko-dev/dom/serviceworkers/ServiceWorkerScriptCache.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1504 строки
43 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 "js/Array.h" // JS::GetArrayLength
#include "js/PropertyAndElement.h" // JS_GetElement
#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/dom/ScriptLoader.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "nsICacheInfoChannel.h"
#include "nsIHttpChannel.h"
#include "nsIStreamLoader.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIUUIDGenerator.h"
#include "nsIXPConnect.h"
#include "nsIInputStreamPump.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentUtils.h"
#include "nsNetUtil.h"
#include "ServiceWorkerManager.h"
#include "nsStringStream.h"
using mozilla::dom::cache::Cache;
using mozilla::dom::cache::CacheStorage;
using mozilla::ipc::PrincipalInfo;
namespace mozilla::dom::serviceWorkerScriptCache {
namespace {
already_AddRefed<CacheStorage> CreateCacheStorage(JSContext* aCx,
nsIPrincipal* aPrincipal,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
nsIXPConnect* xpc = nsContentUtils::XPConnect();
MOZ_ASSERT(xpc, "This should never be null!");
JS::Rooted<JSObject*> sandbox(aCx);
aRv = xpc->CreateSandbox(aCx, aPrincipal, sandbox.address());
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// This is called when the JSContext is not in a realm, so CreateSandbox
// returned an unwrapped global.
MOZ_ASSERT(JS_IsGlobalObject(sandbox));
nsCOMPtr<nsIGlobalObject> sandboxGlobalObject = xpc::NativeGlobal(sandbox);
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 browsing.
//
// 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,
true /* force trusted origin */, aRv);
}
class CompareManager;
class CompareCache;
// This class downloads a URL from the network, compare the downloaded script
// with an existing cache if provided, and report to CompareManager via calling
// ComparisonFinished().
class CompareNetwork final : public nsIStreamLoaderObserver,
public nsIRequestObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
NS_DECL_NSIREQUESTOBSERVER
CompareNetwork(CompareManager* aManager,
ServiceWorkerRegistrationInfo* aRegistration,
bool aIsMainScript)
: mManager(aManager),
mRegistration(aRegistration),
mInternalHeaders(new InternalHeaders()),
mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
mState(WaitingForInitialization),
mNetworkResult(NS_OK),
mCacheResult(NS_OK),
mIsMainScript(aIsMainScript),
mIsFromCache(false) {
MOZ_ASSERT(aManager);
MOZ_ASSERT(NS_IsMainThread());
}
nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
Cache* const aCache);
void Abort();
void NetworkFinish(nsresult aRv);
void CacheFinish(nsresult aRv);
const nsString& URL() const {
MOZ_ASSERT(NS_IsMainThread());
return mURL;
}
const nsString& Buffer() const {
MOZ_ASSERT(NS_IsMainThread());
return mBuffer;
}
const ChannelInfo& GetChannelInfo() const { return mChannelInfo; }
already_AddRefed<InternalHeaders> GetInternalHeaders() const {
RefPtr<InternalHeaders> internalHeaders = mInternalHeaders;
return internalHeaders.forget();
}
UniquePtr<PrincipalInfo> TakePrincipalInfo() {
return std::move(mPrincipalInfo);
}
bool Succeeded() const { return NS_SUCCEEDED(mNetworkResult); }
const nsTArray<nsCString>& URLList() const { return mURLList; }
private:
~CompareNetwork() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mCC);
}
void Finish();
nsresult SetPrincipalInfo(nsIChannel* aChannel);
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<CompareManager> mManager;
RefPtr<CompareCache> mCC;
RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
nsCOMPtr<nsIChannel> mChannel;
nsString mBuffer;
nsString mURL;
ChannelInfo mChannelInfo;
RefPtr<InternalHeaders> mInternalHeaders;
UniquePtr<PrincipalInfo> mPrincipalInfo;
nsTArray<nsCString> mURLList;
nsCString mMaxScope;
nsLoadFlags mLoadFlags;
enum {
WaitingForInitialization,
WaitingForBothFinished,
WaitingForNetworkFinished,
WaitingForCacheFinished,
Finished
} mState;
nsresult mNetworkResult;
nsresult mCacheResult;
const bool mIsMainScript;
bool mIsFromCache;
};
NS_IMPL_ISUPPORTS(CompareNetwork, nsIStreamLoaderObserver, nsIRequestObserver)
// This class gets a cached Response from the CacheStorage and then it calls
// CacheFinish() in the CompareNetwork.
class CompareCache final : public PromiseNativeHandler,
public nsIStreamLoaderObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSISTREAMLOADEROBSERVER
explicit CompareCache(CompareNetwork* aCN)
: mCN(aCN), mState(WaitingForInitialization), mInCache(false) {
MOZ_ASSERT(aCN);
MOZ_ASSERT(NS_IsMainThread());
}
nsresult Initialize(Cache* const aCache, const nsAString& aURL);
void Finish(nsresult aStatus, bool aInCache);
void Abort();
virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;
virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;
const nsString& Buffer() const {
MOZ_ASSERT(NS_IsMainThread());
return mBuffer;
}
bool InCache() { return mInCache; }
private:
~CompareCache() { MOZ_ASSERT(NS_IsMainThread()); }
void ManageValueResult(JSContext* aCx, JS::Handle<JS::Value> aValue);
RefPtr<CompareNetwork> mCN;
nsCOMPtr<nsIInputStreamPump> mPump;
nsString mURL;
nsString mBuffer;
enum {
WaitingForInitialization,
WaitingForScript,
Finished,
} mState;
bool mInCache;
};
NS_IMPL_ISUPPORTS(CompareCache, nsIStreamLoaderObserver)
class CompareManager final : public PromiseNativeHandler {
public:
NS_DECL_ISUPPORTS
explicit CompareManager(ServiceWorkerRegistrationInfo* aRegistration,
CompareCallback* aCallback)
: mRegistration(aRegistration),
mCallback(aCallback),
mLoadFlags(nsIChannel::LOAD_BYPASS_SERVICE_WORKER),
mState(WaitingForInitialization),
mPendingCount(0),
mOnFailure(OnFailure::DoNothing),
mAreScriptsEqual(true) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRegistration);
}
nsresult Initialize(nsIPrincipal* aPrincipal, const nsAString& aURL,
const nsAString& aCacheName);
void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) override;
CacheStorage* CacheStorage_() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCacheStorage);
return mCacheStorage;
}
void ComparisonFinished(nsresult aStatus, bool aIsMainScript, bool aIsEqual,
const nsACString& aMaxScope, nsLoadFlags aLoadFlags) {
MOZ_ASSERT(NS_IsMainThread());
if (mState == Finished) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForScriptOrComparisonResult);
if (NS_WARN_IF(NS_FAILED(aStatus))) {
Fail(aStatus);
return;
}
mAreScriptsEqual = mAreScriptsEqual && aIsEqual;
if (aIsMainScript) {
mMaxScope = aMaxScope;
mLoadFlags = aLoadFlags;
}
// Check whether all CompareNetworks finished their jobs.
MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
if (--mPendingCount) {
return;
}
if (mAreScriptsEqual) {
MOZ_ASSERT(mCallback);
mCallback->ComparisonResult(aStatus, true /* aSameScripts */, mOnFailure,
u""_ns, mMaxScope, mLoadFlags);
Cleanup();
return;
}
// Write to Cache so ScriptLoader reads succeed.
WriteNetworkBufferToNewCache();
}
private:
~CompareManager() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCNList.Length() == 0);
}
void Fail(nsresult aStatus);
void Cleanup();
nsresult FetchScript(const nsAString& aURL, bool aIsMainScript,
Cache* const aCache = nullptr) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization ||
mState == WaitingForScriptOrComparisonResult);
RefPtr<CompareNetwork> cn =
new CompareNetwork(this, mRegistration, aIsMainScript);
mCNList.AppendElement(cn);
mPendingCount += 1;
nsresult rv = cn->Initialize(mPrincipal, aURL, aCache);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void ManageOldCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingOpen);
// RAII Cleanup when fails.
nsresult rv = NS_ERROR_FAILURE;
auto guard = MakeScopeExit([&] { Fail(rv); });
if (NS_WARN_IF(!aValue.isObject())) {
return;
}
MOZ_ASSERT(!mOldCache);
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj) ||
NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Cache, obj, mOldCache)))) {
return;
}
Optional<RequestOrUSVString> request;
CacheQueryOptions options;
ErrorResult error;
RefPtr<Promise> promise = mOldCache->Keys(aCx, request, options, error);
if (NS_WARN_IF(error.Failed())) {
// No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!error.IsJSException());
rv = error.StealNSResult();
return;
}
mState = WaitingForExistingKeys;
promise->AppendNativeHandler(this);
guard.release();
}
void ManageOldKeys(JSContext* aCx, JS::Handle<JS::Value> aValue) {
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForExistingKeys);
// RAII Cleanup when fails.
nsresult rv = NS_ERROR_FAILURE;
auto guard = MakeScopeExit([&] { Fail(rv); });
if (NS_WARN_IF(!aValue.isObject())) {
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
return;
}
uint32_t len = 0;
if (!JS::GetArrayLength(aCx, obj, &len)) {
return;
}
// Fetch and compare the source scripts.
MOZ_ASSERT(mPendingCount == 0);
mState = WaitingForScriptOrComparisonResult;
bool hasMainScript = false;
AutoTArray<nsString, 8> urlList;
// Extract the list of URLs in the old cache.
for (uint32_t i = 0; i < len; ++i) {
JS::Rooted<JS::Value> val(aCx);
if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &val)) ||
NS_WARN_IF(!val.isObject())) {
return;
}
Request* request;
JS::Rooted<JSObject*> requestObj(aCx, &val.toObject());
if (NS_WARN_IF(NS_FAILED(UNWRAP_OBJECT(Request, &requestObj, request)))) {
return;
};
nsString url;
request->GetUrl(url);
if (!hasMainScript && url == mURL) {
hasMainScript = true;
}
urlList.AppendElement(url);
}
// If the main script is missing, then something has gone wrong. We
// will try to continue with the update process to trigger a new
// installation. If that fails, however, then uninstall the registration
// because it is broken in a way that cannot be fixed.
if (!hasMainScript) {
mOnFailure = OnFailure::Uninstall;
}
// Always make sure to fetch the main script. If the old cache has
// no entries or the main script entry is missing, then the loop below
// may not trigger it. This should not really happen, but we handle it
// gracefully if it does occur. Its possible the bad cache state is due
// to a crash or shutdown during an update, etc.
rv = FetchScript(mURL, true /* aIsMainScript */, mOldCache);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
for (const auto& url : urlList) {
// We explicitly start the fetch for the main script above.
if (mURL == url) {
continue;
}
rv = FetchScript(url, false /* aIsMainScript */, mOldCache);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
guard.release();
}
void ManageNewCache(JSContext* aCx, JS::Handle<JS::Value> aValue) {
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
// RAII Cleanup when fails.
nsresult rv = NS_ERROR_FAILURE;
auto guard = MakeScopeExit([&] { Fail(rv); });
if (NS_WARN_IF(!aValue.isObject())) {
return;
}
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
return;
}
Cache* cache = nullptr;
rv = UNWRAP_OBJECT(Cache, &obj, cache);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
// Just to be safe.
RefPtr<Cache> kungfuDeathGrip = cache;
MOZ_ASSERT(mPendingCount == 0);
for (uint32_t i = 0; i < mCNList.Length(); ++i) {
// We bail out immediately when something goes wrong.
rv = WriteToCache(aCx, cache, mCNList[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
mState = WaitingForPut;
guard.release();
}
void WriteNetworkBufferToNewCache() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCNList.Length() != 0);
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;
}
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<Promise> cacheOpenPromise =
mCacheStorage->Open(mNewCacheName, result);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
}
mState = WaitingForOpen;
cacheOpenPromise->AppendNativeHandler(this);
}
nsresult WriteToCache(JSContext* aCx, Cache* aCache, CompareNetwork* aCN) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCache);
MOZ_ASSERT(aCN);
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForOpen);
// We don't have to save any information from a failed CompareNetwork.
if (!aCN->Succeeded()) {
return NS_OK;
}
nsCOMPtr<nsIInputStream> body;
nsresult rv = NS_NewCStringInputStream(
getter_AddRefs(body), NS_ConvertUTF16toUTF8(aCN->Buffer()));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
SafeRefPtr<InternalResponse> ir =
MakeSafeRefPtr<InternalResponse>(200, "OK"_ns);
ir->SetBody(body, aCN->Buffer().Length());
ir->SetURLList(aCN->URLList());
ir->InitChannelInfo(aCN->GetChannelInfo());
UniquePtr<PrincipalInfo> principalInfo = aCN->TakePrincipalInfo();
if (principalInfo) {
ir->SetPrincipalInfo(std::move(principalInfo));
}
RefPtr<InternalHeaders> internalHeaders = aCN->GetInternalHeaders();
ir->Headers()->Fill(*(internalHeaders.get()), IgnoreErrors());
RefPtr<Response> response =
new Response(aCache->GetGlobalObject(), std::move(ir), nullptr);
RequestOrUSVString request;
request.SetAsUSVString().ShareOrDependUpon(aCN->URL());
// 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.
ErrorResult result;
RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
result.WouldReportJSException();
if (NS_WARN_IF(result.Failed())) {
// No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!result.IsJSException());
MOZ_ASSERT(!result.IsErrorWithMessage());
return result.StealNSResult();
}
mPendingCount += 1;
cachePromise->AppendNativeHandler(this);
return NS_OK;
}
RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 08:24:48 +03:00
RefPtr<CompareCallback> mCallback;
RefPtr<CacheStorage> mCacheStorage;
nsTArray<RefPtr<CompareNetwork>> mCNList;
nsString mURL;
RefPtr<nsIPrincipal> mPrincipal;
// Used for the old cache where saves the old source scripts.
RefPtr<Cache> mOldCache;
// Only used if the network script has changed and needs to be cached.
nsString mNewCacheName;
nsCString mMaxScope;
nsLoadFlags mLoadFlags;
enum {
WaitingForInitialization,
WaitingForExistingOpen,
WaitingForExistingKeys,
WaitingForScriptOrComparisonResult,
WaitingForOpen,
WaitingForPut,
Finished
} mState;
uint32_t mPendingCount;
OnFailure mOnFailure;
bool mAreScriptsEqual;
};
NS_IMPL_ISUPPORTS0(CompareManager)
nsresult CompareNetwork::Initialize(nsIPrincipal* aPrincipal,
const nsAString& aURL,
Cache* const aCache) {
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mURL = aURL;
mURLList.AppendElement(NS_ConvertUTF16toUTF8(mURL));
nsCOMPtr<nsILoadGroup> loadGroup;
rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), aPrincipal);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Update LoadFlags for propagating to ServiceWorkerInfo.
mLoadFlags = nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
ServiceWorkerUpdateViaCache uvc = mRegistration->GetUpdateViaCache();
if (uvc == ServiceWorkerUpdateViaCache::None ||
(uvc == ServiceWorkerUpdateViaCache::Imports && mIsMainScript)) {
mLoadFlags |= nsIRequest::VALIDATE_ALWAYS;
}
if (mRegistration->IsLastUpdateCheckTimeOverOneDay()) {
mLoadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
}
// Different settings are needed for fetching imported scripts, since they
// might be cross-origin scripts.
uint32_t secFlags =
mIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
nsContentPolicyType contentPolicyType =
mIsMainScript ? nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
: nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS;
// Create a new cookieJarSettings.
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
mozilla::net::CookieJarSettings::Create(aPrincipal);
// Populate the partitionKey by using the given prinicpal. The ServiceWorkers
// are using the foreign partitioned principal, so we can get the partitionKey
// from it and the partitionKey will only exist if it's in the third-party
// context. In first-party context, we can still use the uri to set the
// partitionKey.
if (!aPrincipal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetPartitionKey(aPrincipal->OriginAttributesRef().mPartitionKey);
} else {
net::CookieJarSettings::Cast(cookieJarSettings)->SetPartitionKey(uri);
}
// 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, secFlags,
contentPolicyType, cookieJarSettings,
nullptr /* aPerformanceStorage */, loadGroup,
nullptr /* aCallbacks */, mLoadFlags);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
if (httpChannel) {
// Spec says no redirects allowed for top-level SW scripts.
if (mIsMainScript) {
rv = httpChannel->SetRedirectionLimit(0);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
rv = httpChannel->SetRequestHeader("Service-Worker"_ns, "script"_ns,
/* merge */ false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this, this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = mChannel->AsyncOpen(loader);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// If we do have an existing cache to compare with.
if (aCache) {
mCC = new CompareCache(this);
rv = mCC->Initialize(aCache, aURL);
if (NS_WARN_IF(NS_FAILED(rv))) {
Abort();
return rv;
}
mState = WaitingForBothFinished;
return NS_OK;
}
mState = WaitingForNetworkFinished;
return NS_OK;
}
void CompareNetwork::Finish() {
if (mState == Finished) {
return;
}
bool same = true;
nsresult rv = NS_OK;
// mNetworkResult is prior to mCacheResult, since it's needed for reporting
// various errors to web content.
if (NS_FAILED(mNetworkResult)) {
// An imported script could become offline, since it might no longer be
// needed by the new importing script. In that case, the importing script
// must be different, and thus, it's okay to report same script found here.
rv = mIsMainScript ? mNetworkResult : NS_OK;
same = true;
} else if (mCC && NS_FAILED(mCacheResult)) {
rv = mCacheResult;
} else { // Both passed.
same = mCC && mCC->InCache() && mCC->Buffer().Equals(mBuffer);
}
mManager->ComparisonFinished(rv, mIsMainScript, same, mMaxScope, mLoadFlags);
// We have done with the CompareCache.
mCC = nullptr;
}
void CompareNetwork::NetworkFinish(nsresult aRv) {
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
mState == WaitingForNetworkFinished);
mNetworkResult = aRv;
if (mState == WaitingForBothFinished) {
mState = WaitingForCacheFinished;
return;
}
if (mState == WaitingForNetworkFinished) {
Finish();
return;
}
}
void CompareNetwork::CacheFinish(nsresult aRv) {
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForBothFinished ||
mState == WaitingForCacheFinished);
mCacheResult = aRv;
if (mState == WaitingForBothFinished) {
mState = WaitingForNetworkFinished;
return;
}
if (mState == WaitingForCacheFinished) {
Finish();
return;
}
}
void CompareNetwork::Abort() {
MOZ_ASSERT(NS_IsMainThread());
if (mState != Finished) {
mState = Finished;
MOZ_ASSERT(mChannel);
mChannel->Cancel(NS_BINDING_ABORTED);
mChannel = nullptr;
if (mCC) {
mCC->Abort();
mCC = nullptr;
}
}
}
NS_IMETHODIMP
CompareNetwork::OnStartRequest(nsIRequest* aRequest) {
MOZ_ASSERT(NS_IsMainThread());
if (mState == Finished) {
return NS_OK;
}
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
MOZ_ASSERT_IF(mIsMainScript, channel == mChannel);
mChannel = channel;
MOZ_ASSERT(!mChannelInfo.IsInitialized());
mChannelInfo.InitFromChannel(mChannel);
nsresult rv = SetPrincipalInfo(mChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mInternalHeaders->FillResponseHeaders(mChannel);
nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
if (cacheChannel) {
cacheChannel->IsFromCache(&mIsFromCache);
}
return NS_OK;
}
nsresult CompareNetwork::SetPrincipalInfo(nsIChannel* aChannel) {
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (!ssm) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> channelPrincipal;
nsresult rv = ssm->GetChannelResultPrincipal(
aChannel, getter_AddRefs(channelPrincipal));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
UniquePtr<PrincipalInfo> principalInfo = MakeUnique<PrincipalInfo>();
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mPrincipalInfo = std::move(principalInfo);
return NS_OK;
}
NS_IMETHODIMP
CompareNetwork::OnStopRequest(nsIRequest* aRequest, 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) {
MOZ_ASSERT(NS_IsMainThread());
if (mState == Finished) {
return NS_OK;
}
nsresult rv = NS_ERROR_FAILURE;
auto guard = MakeScopeExit([&] { NetworkFinish(rv); });
if (NS_WARN_IF(NS_FAILED(aStatus))) {
rv = (aStatus == NS_ERROR_REDIRECT_LOOP) ? NS_ERROR_DOM_SECURITY_ERR
: aStatus;
return NS_OK;
}
nsCOMPtr<nsIRequest> request;
rv = aLoader->GetRequest(getter_AddRefs(request));
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_OK;
}
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
MOZ_ASSERT(channel, "How come we don't have any channel?");
nsCOMPtr<nsIURI> uri;
channel->GetOriginalURI(getter_AddRefs(uri));
bool isExtension = uri->SchemeIs("moz-extension");
if (isExtension &&
!StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup()) {
// Return earlier with error is the worker script is a moz-extension url
// but the feature isn't enabled by prefs.
return NS_ERROR_FAILURE;
}
if (isExtension) {
// NOTE: trying to register any moz-extension use that doesn't ends
// with .js/.jsm/.mjs seems to be already completing with an error
// in aStatus and they never reach this point.
// TODO: look into avoid duplicated parts that could be shared with the HTTP
// channel scenario.
nsCOMPtr<nsIURI> channelURL;
rv = channel->GetURI(getter_AddRefs(channelURL));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString channelURLSpec;
MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
// Append the final URL (which for an extension worker script is going to
// be a file or jar url).
MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
if (channelURLSpec != mURLList[0]) {
mURLList.AppendElement(channelURLSpec);
}
char16_t* buffer = nullptr;
size_t len = 0;
rv = ScriptLoader::ConvertToUTF16(channel, aString, aLen, u"UTF-8"_ns,
nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mBuffer.Adopt(buffer, len);
rv = NS_OK;
return NS_OK;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
// Main scripts cannot be redirected successfully, however extensions
// may successfuly redirect imported scripts to a moz-extension url
// (if listed in the web_accessible_resources manifest property).
//
// When the service worker is initially registered the imported scripts
// will be loaded from the child process (see dom/workers/ScriptLoader.cpp)
// and in that case this method will only be called for the main script.
//
// When a registered worker is loaded again (e.g. when the webpage calls
// the ServiceWorkerRegistration's update method):
//
// - both the main and imported scripts are loaded by the
// CompareManager::FetchScript
// - the update requests for the imported scripts will also be calling this
// method and we should expect scripts redirected to an extension script
// to have a null httpChannel.
//
// The request that triggers this method is:
//
// - the one that is coming from the network (which may be intercepted by
// WebRequest listeners in extensions and redirected to a web_accessible
// moz-extension url)
// - it will then be compared with a previous response that we may have
// in the cache
//
// When the next service worker update occurs, if the request (for an imported
// script) is not redirected by an extension the cache entry is invalidated
// and a network request is triggered for the import.
if (!httpChannel) {
// Redirecting a service worker main script should fail before reaching this
// method.
// If a main script is somehow redirected, the diagnostic assert will crash
// in non-release builds. Release builds will return an explicit error.
MOZ_DIAGNOSTIC_ASSERT(!mIsMainScript,
"Unexpected ServiceWorker main script redirected");
if (mIsMainScript) {
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIPrincipal> channelPrincipal;
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
if (!ssm) {
return NS_ERROR_UNEXPECTED;
}
nsresult rv = ssm->GetChannelResultPrincipal(
channel, getter_AddRefs(channelPrincipal));
// An extension did redirect a non-MainScript request to a moz-extension url
// (in that case the originalURL is the resolved jar URI and so we have to
// look to the channel principal instead).
if (channelPrincipal->SchemeIs("moz-extension")) {
char16_t* buffer = nullptr;
size_t len = 0;
rv = ScriptLoader::ConvertToUTF16(channel, aString, aLen, u"UTF-8"_ns,
nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mBuffer.Adopt(buffer, len);
return NS_OK;
}
// Make non-release and debug builds to crash if this happens and fail
// explicitly on release builds.
MOZ_DIAGNOSTIC_ASSERT(false,
"ServiceWorker imported script redirected to an url "
"with an unexpected scheme");
return NS_ERROR_UNEXPECTED;
}
bool requestSucceeded;
rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_OK;
}
if (NS_WARN_IF(!requestSucceeded)) {
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-05 23:47:10 +03:00
// Get the stringified numeric status code, not statusText which could be
// something misleading like OK for a 404.
uint32_t status = 0;
Unused << httpChannel->GetResponseStatus(
&status); // don't care if this fails, use 0.
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-05 23:47:10 +03:00
nsAutoString statusAsText;
statusAsText.AppendInt(status);
ServiceWorkerManager::LocalizeAndReportToAllClients(
mRegistration->Scope(), "ServiceWorkerRegisterNetworkError",
nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
statusAsText, mURL});
rv = NS_ERROR_FAILURE;
return NS_OK;
}
// 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("Service-Worker-Allowed"_ns,
mMaxScope);
// [9.2 Update]4.13, If response's cache state is not "local",
// set registration's last update check time to the current time
if (!mIsFromCache) {
mRegistration->RefreshLastUpdateCheckTime();
}
nsAutoCString mimeType;
rv = httpChannel->GetContentType(mimeType);
if (NS_WARN_IF(NS_FAILED(rv))) {
// We should only end up here if !mResponseHead in the channel. If headers
// were received but no content type was specified, we'll be given
// UNKNOWN_CONTENT_TYPE "application/x-unknown-content-type" and so fall
// into the next case with its better error message.
rv = NS_ERROR_DOM_SECURITY_ERR;
return rv;
}
if (mimeType.IsEmpty() ||
!nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
ServiceWorkerManager::LocalizeAndReportToAllClients(
mRegistration->Scope(), "ServiceWorkerRegisterMimeTypeError2",
nsTArray<nsString>{NS_ConvertUTF8toUTF16(mRegistration->Scope()),
NS_ConvertUTF8toUTF16(mimeType), mURL});
rv = NS_ERROR_DOM_SECURITY_ERR;
return rv;
}
nsCOMPtr<nsIURI> channelURL;
rv = httpChannel->GetURI(getter_AddRefs(channelURL));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString channelURLSpec;
MOZ_ALWAYS_SUCCEEDS(channelURL->GetSpec(channelURLSpec));
// Append the final URL if its different from the original
// request URL. This lets us note that a redirect occurred
// even though we don't track every redirect URL here.
MOZ_DIAGNOSTIC_ASSERT(!mURLList.IsEmpty());
if (channelURLSpec != mURLList[0]) {
mURLList.AppendElement(channelURLSpec);
}
char16_t* buffer = nullptr;
size_t len = 0;
rv = ScriptLoader::ConvertToUTF16(httpChannel, aString, aLen, u"UTF-8"_ns,
nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mBuffer.Adopt(buffer, len);
rv = NS_OK;
return NS_OK;
}
nsresult CompareCache::Initialize(Cache* const aCache, const nsAString& aURL) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCache);
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
// This JSContext will not end up executing JS code because here there are
// no ReadableStreams involved.
AutoJSAPI jsapi;
jsapi.Init();
RequestOrUSVString request;
request.SetAsUSVString().ShareOrDependUpon(aURL);
ErrorResult error;
CacheQueryOptions params;
RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
if (NS_WARN_IF(error.Failed())) {
// No exception here because there are no ReadableStreams involved here.
MOZ_ASSERT(!error.IsJSException());
mState = Finished;
return error.StealNSResult();
}
// Retrieve the script from aCache.
mState = WaitingForScript;
promise->AppendNativeHandler(this);
return NS_OK;
}
void CompareCache::Finish(nsresult aStatus, bool aInCache) {
if (mState != Finished) {
mState = Finished;
mInCache = aInCache;
mCN->CacheFinish(aStatus);
}
}
void CompareCache::Abort() {
MOZ_ASSERT(NS_IsMainThread());
if (mState != Finished) {
mState = Finished;
if (mPump) {
mPump->Cancel(NS_BINDING_ABORTED);
mPump = nullptr;
}
}
}
NS_IMETHODIMP
CompareCache::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
nsresult aStatus, uint32_t aLen,
const uint8_t* aString) {
MOZ_ASSERT(NS_IsMainThread());
if (mState == Finished) {
return aStatus;
}
if (NS_WARN_IF(NS_FAILED(aStatus))) {
Finish(aStatus, false);
return aStatus;
}
char16_t* buffer = nullptr;
size_t len = 0;
nsresult rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aLen,
u"UTF-8"_ns, nullptr, buffer, len);
if (NS_WARN_IF(NS_FAILED(rv))) {
Finish(rv, false);
return rv;
}
mBuffer.Adopt(buffer, len);
Finish(NS_OK, true);
return NS_OK;
}
void CompareCache::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
switch (mState) {
case Finished:
return;
case WaitingForScript:
ManageValueResult(aCx, aValue);
return;
default:
MOZ_CRASH("Unacceptable state.");
}
}
void CompareCache::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
if (mState != Finished) {
Finish(NS_ERROR_FAILURE, false);
return;
}
}
void CompareCache::ManageValueResult(JSContext* aCx,
JS::Handle<JS::Value> aValue) {
MOZ_ASSERT(NS_IsMainThread());
// The cache returns undefined if the object is not stored.
if (aValue.isUndefined()) {
Finish(NS_OK, false);
return;
}
MOZ_ASSERT(aValue.isObject());
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
if (NS_WARN_IF(!obj)) {
Finish(NS_ERROR_FAILURE, false);
return;
}
Response* response = nullptr;
nsresult rv = UNWRAP_OBJECT(Response, &obj, response);
if (NS_WARN_IF(NS_FAILED(rv))) {
Finish(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.forget(),
0, /* default segsize */
0, /* default segcount */
false, /* default closeWhenDone */
GetMainThreadSerialEventTarget());
if (NS_WARN_IF(NS_FAILED(rv))) {
Finish(rv, false);
return;
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
if (NS_WARN_IF(NS_FAILED(rv))) {
Finish(rv, false);
return;
}
rv = mPump->AsyncRead(loader);
if (NS_WARN_IF(NS_FAILED(rv))) {
mPump = nullptr;
Finish(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;
Finish(rv, false);
return;
}
}
}
nsresult CompareManager::Initialize(nsIPrincipal* aPrincipal,
const nsAString& aURL,
const nsAString& aCacheName) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(mPendingCount == 0);
MOZ_DIAGNOSTIC_ASSERT(mState == WaitingForInitialization);
// RAII Cleanup when fails.
auto guard = MakeScopeExit([&] { Cleanup(); });
mURL = aURL;
mPrincipal = aPrincipal;
// 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;
mCacheStorage = CreateCacheStorage(jsapi.cx(), aPrincipal, result);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
return result.StealNSResult();
}
// If there is no existing cache, proceed to fetch the script directly.
if (aCacheName.IsEmpty()) {
mState = WaitingForScriptOrComparisonResult;
nsresult rv = FetchScript(aURL, true /* aIsMainScript */);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
guard.release();
return NS_OK;
}
// Open the cache saving the old source scripts.
RefPtr<Promise> promise = mCacheStorage->Open(aCacheName, result);
if (NS_WARN_IF(result.Failed())) {
MOZ_ASSERT(!result.IsErrorWithMessage());
return result.StealNSResult();
}
mState = WaitingForExistingOpen;
promise->AppendNativeHandler(this);
guard.release();
return NS_OK;
}
// This class manages 4 promises if needed:
// 1. Retrieve the Cache object by a given CacheName of OldCache.
// 2. Retrieve the URLs saved in OldCache.
// 3. Retrieve the Cache object of the NewCache for the newly created SW.
// 4. Put the value in the cache.
// For this reason we have mState to know what callback we are handling.
void CompareManager::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mCallback);
switch (mState) {
case Finished:
return;
case WaitingForExistingOpen:
ManageOldCache(aCx, aValue);
return;
case WaitingForExistingKeys:
ManageOldKeys(aCx, aValue);
return;
case WaitingForOpen:
ManageNewCache(aCx, aValue);
return;
case WaitingForPut:
MOZ_DIAGNOSTIC_ASSERT(mPendingCount > 0);
if (--mPendingCount == 0) {
mCallback->ComparisonResult(NS_OK, false /* aIsEqual */, mOnFailure,
mNewCacheName, mMaxScope, mLoadFlags);
Cleanup();
}
return;
default:
MOZ_DIAGNOSTIC_ASSERT(false);
}
}
void CompareManager::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
MOZ_ASSERT(NS_IsMainThread());
switch (mState) {
case Finished:
return;
case WaitingForExistingOpen:
NS_WARNING("Could not open the existing cache.");
break;
case WaitingForExistingKeys:
NS_WARNING("Could not get the existing URLs.");
break;
case WaitingForOpen:
NS_WARNING("Could not open cache.");
break;
case WaitingForPut:
NS_WARNING("Could not write to cache.");
break;
default:
MOZ_DIAGNOSTIC_ASSERT(false);
}
Fail(NS_ERROR_FAILURE);
}
void CompareManager::Fail(nsresult aStatus) {
MOZ_ASSERT(NS_IsMainThread());
mCallback->ComparisonResult(aStatus, false /* aIsEqual */, mOnFailure, u""_ns,
""_ns, mLoadFlags);
Cleanup();
}
void CompareManager::Cleanup() {
MOZ_ASSERT(NS_IsMainThread());
if (mState != Finished) {
mState = Finished;
MOZ_ASSERT(mCallback);
mCallback = nullptr;
// Abort and release CompareNetworks.
for (uint32_t i = 0; i < mCNList.Length(); ++i) {
mCNList[i]->Abort();
}
mCNList.Clear();
}
}
} // namespace
nsresult PurgeCache(nsIPrincipal* aPrincipal, const nsAString& aCacheName) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aPrincipal);
if (aCacheName.IsEmpty()) {
return NS_OK;
}
AutoJSAPI jsapi;
jsapi.Init();
ErrorResult rv;
RefPtr<CacheStorage> cacheStorage =
CreateCacheStorage(jsapi.cx(), aPrincipal, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
// We use the ServiceWorker scope as key for the cacheStorage.
RefPtr<Promise> promise = cacheStorage->Delete(aCacheName, rv);
if (NS_WARN_IF(rv.Failed())) {
return rv.StealNSResult();
}
// Set [[PromiseIsHandled]] to ensure that if this promise gets rejected,
// we don't end up reporting a rejected promise to the console.
MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
// 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);
// NSID_LENGTH counts the null terminator.
aName.AssignASCII(chars, NSID_LENGTH - 1);
return NS_OK;
}
nsresult Compare(ServiceWorkerRegistrationInfo* aRegistration,
nsIPrincipal* aPrincipal, const nsAString& aCacheName,
const nsAString& aURL, CompareCallback* aCallback) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRegistration);
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(!aURL.IsEmpty());
MOZ_ASSERT(aCallback);
RefPtr<CompareManager> cm = new CompareManager(aRegistration, aCallback);
nsresult rv = cm->Initialize(aPrincipal, aURL, aCacheName);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
} // namespace mozilla::dom::serviceWorkerScriptCache