mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-29 21:25:35 +00:00
4083fafafc
CLOSED TREE Backed out changeset bbc14d107fca (bug 1290116) Backed out changeset a81b6ce5c25f (bug 1290116) Backed out changeset e0b9c25b6125 (bug 1290116) Backed out changeset 8e9a9ba04ce5 (bug 1290116) Backed out changeset 2d5ad5e9ed20 (bug 1290116) Backed out changeset eb7da0dbfc72 (bug 1290116) Backed out changeset 6a4e8c92faae (bug 1290116) Backed out changeset 0342d98afa10 (bug 1293690) Backed out changeset d84b0768f0ce (bug 1293690) Backed out changeset f9f62ade9508 (bug 1293690)
2260 lines
67 KiB
C++
2260 lines
67 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 "ScriptLoader.h"
|
|
|
|
#include "nsIChannel.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsIContentSecurityPolicy.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIHttpChannelInternal.h"
|
|
#include "nsIInputStreamPump.h"
|
|
#include "nsIIOService.h"
|
|
#include "nsIProtocolHandler.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsIStreamLoader.h"
|
|
#include "nsIStreamListenerTee.h"
|
|
#include "nsIThreadRetargetableRequest.h"
|
|
#include "nsIURI.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "nsError.h"
|
|
#include "nsContentPolicyUtils.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDocShellCID.h"
|
|
#include "nsISupportsPrimitives.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsIPipe.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsScriptLoader.h"
|
|
#include "nsString.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsTArray.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXPCOM.h"
|
|
#include "xpcpublic.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/LoadContext.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/dom/CacheBinding.h"
|
|
#include "mozilla/dom/cache/CacheTypes.h"
|
|
#include "mozilla/dom/cache/Cache.h"
|
|
#include "mozilla/dom/cache/CacheStorage.h"
|
|
#include "mozilla/dom/ChannelInfo.h"
|
|
#include "mozilla/dom/Exceptions.h"
|
|
#include "mozilla/dom/InternalResponse.h"
|
|
#include "mozilla/dom/nsCSPService.h"
|
|
#include "mozilla/dom/nsCSPUtils.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/PromiseNativeHandler.h"
|
|
#include "mozilla/dom/Response.h"
|
|
#include "mozilla/dom/ScriptSettings.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "Principal.h"
|
|
#include "WorkerHolder.h"
|
|
#include "WorkerPrivate.h"
|
|
#include "WorkerRunnable.h"
|
|
#include "WorkerScope.h"
|
|
|
|
#define MAX_CONCURRENT_SCRIPTS 1000
|
|
|
|
USING_WORKERS_NAMESPACE
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using mozilla::dom::cache::Cache;
|
|
using mozilla::dom::cache::CacheStorage;
|
|
using mozilla::ipc::PrincipalInfo;
|
|
|
|
namespace {
|
|
|
|
nsIURI*
|
|
GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate);
|
|
nsIURI* baseURI;
|
|
WorkerPrivate* parentWorker = aWorkerPrivate->GetParent();
|
|
if (aIsMainScript) {
|
|
if (parentWorker) {
|
|
baseURI = parentWorker->GetBaseURI();
|
|
NS_ASSERTION(baseURI, "Should have been set already!");
|
|
}
|
|
else {
|
|
// May be null.
|
|
baseURI = aWorkerPrivate->GetBaseURI();
|
|
}
|
|
}
|
|
else {
|
|
baseURI = aWorkerPrivate->GetBaseURI();
|
|
NS_ASSERTION(baseURI, "Should have been set already!");
|
|
}
|
|
|
|
return baseURI;
|
|
}
|
|
|
|
nsresult
|
|
ChannelFromScriptURL(nsIPrincipal* principal,
|
|
nsIURI* baseURI,
|
|
nsIDocument* parentDoc,
|
|
nsILoadGroup* loadGroup,
|
|
nsIIOService* ios,
|
|
nsIScriptSecurityManager* secMan,
|
|
const nsAString& aScriptURL,
|
|
bool aIsMainScript,
|
|
WorkerScriptType aWorkerScriptType,
|
|
nsContentPolicyType aContentPolicyType,
|
|
nsLoadFlags aLoadFlags,
|
|
bool aDefaultURIEncoding,
|
|
nsIChannel** aChannel)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIURI> uri;
|
|
|
|
if (aDefaultURIEncoding) {
|
|
rv = NS_NewURI(getter_AddRefs(uri), aScriptURL, nullptr, baseURI);
|
|
} else {
|
|
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
|
|
aScriptURL, parentDoc,
|
|
baseURI);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
}
|
|
|
|
// If we have the document, use it. Unfortunately, for dedicated workers
|
|
// 'parentDoc' ends up being the parent document, which is not the document
|
|
// that we want to use. So make sure to avoid using 'parentDoc' in that
|
|
// situation.
|
|
if (parentDoc && parentDoc->NodePrincipal() != principal) {
|
|
parentDoc = nullptr;
|
|
}
|
|
|
|
aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
|
|
uint32_t secFlags = aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
|
|
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
|
|
|
|
if (aWorkerScriptType == DebuggerScript) {
|
|
// A DebuggerScript needs to be a local resource like chrome: or resource:
|
|
bool isUIResource = false;
|
|
rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
|
|
&isUIResource);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!isUIResource) {
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
}
|
|
|
|
secFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
|
|
}
|
|
|
|
// Note: this is for backwards compatibility and goes against spec.
|
|
// We should find a better solution.
|
|
bool isData = false;
|
|
if (aIsMainScript && NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) {
|
|
secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
// If we have the document, use it. Unfortunately, for dedicated workers
|
|
// 'parentDoc' ends up being the parent document, which is not the document
|
|
// that we want to use. So make sure to avoid using 'parentDoc' in that
|
|
// situation.
|
|
if (parentDoc && parentDoc->NodePrincipal() == principal) {
|
|
rv = NS_NewChannel(getter_AddRefs(channel),
|
|
uri,
|
|
parentDoc,
|
|
secFlags,
|
|
aContentPolicyType,
|
|
loadGroup,
|
|
nullptr, // aCallbacks
|
|
aLoadFlags,
|
|
ios);
|
|
} else {
|
|
// We must have a loadGroup with a load context for the principal to
|
|
// traverse the channel correctly.
|
|
MOZ_ASSERT(loadGroup);
|
|
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
|
|
|
|
rv = NS_NewChannel(getter_AddRefs(channel),
|
|
uri,
|
|
principal,
|
|
secFlags,
|
|
aContentPolicyType,
|
|
loadGroup,
|
|
nullptr, // aCallbacks
|
|
aLoadFlags,
|
|
ios);
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel)) {
|
|
rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc,
|
|
httpChannel, mozilla::net::RP_Default);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
channel.forget(aChannel);
|
|
return rv;
|
|
}
|
|
|
|
struct ScriptLoadInfo
|
|
{
|
|
ScriptLoadInfo()
|
|
: mScriptTextBuf(nullptr)
|
|
, mScriptTextLength(0)
|
|
, mLoadResult(NS_ERROR_NOT_INITIALIZED)
|
|
, mLoadingFinished(false)
|
|
, mExecutionScheduled(false)
|
|
, mExecutionResult(false)
|
|
, mCacheStatus(Uncached)
|
|
{ }
|
|
|
|
~ScriptLoadInfo()
|
|
{
|
|
if (mScriptTextBuf) {
|
|
js_free(mScriptTextBuf);
|
|
}
|
|
}
|
|
|
|
bool
|
|
ReadyToExecute()
|
|
{
|
|
return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled;
|
|
}
|
|
|
|
nsString mURL;
|
|
|
|
// This full URL string is populated only if this object is used in a
|
|
// ServiceWorker.
|
|
nsString mFullURL;
|
|
|
|
// This promise is set only when the script is for a ServiceWorker but
|
|
// it's not in the cache yet. The promise is resolved when the full body is
|
|
// stored into the cache. mCachePromise will be set to nullptr after
|
|
// resolution.
|
|
RefPtr<Promise> mCachePromise;
|
|
|
|
// The reader stream the cache entry should be filled from, for those cases
|
|
// when we're going to have an mCachePromise.
|
|
nsCOMPtr<nsIInputStream> mCacheReadStream;
|
|
|
|
nsCOMPtr<nsIChannel> mChannel;
|
|
char16_t* mScriptTextBuf;
|
|
size_t mScriptTextLength;
|
|
|
|
nsresult mLoadResult;
|
|
bool mLoadingFinished;
|
|
bool mExecutionScheduled;
|
|
bool mExecutionResult;
|
|
|
|
enum CacheStatus {
|
|
// By default a normal script is just loaded from the network. But for
|
|
// ServiceWorkers, we have to check if the cache contains the script and
|
|
// load it from the cache.
|
|
Uncached,
|
|
|
|
WritingToCache,
|
|
|
|
ReadingFromCache,
|
|
|
|
// This script has been loaded from the ServiceWorker cache.
|
|
Cached,
|
|
|
|
// This script must be stored in the ServiceWorker cache.
|
|
ToBeCached,
|
|
|
|
// Something went wrong or the worker went away.
|
|
Cancel
|
|
};
|
|
|
|
CacheStatus mCacheStatus;
|
|
|
|
Maybe<bool> mMutedErrorFlag;
|
|
|
|
bool Finished() const
|
|
{
|
|
return mLoadingFinished && !mCachePromise && !mChannel;
|
|
}
|
|
};
|
|
|
|
class ScriptLoaderRunnable;
|
|
|
|
class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable
|
|
{
|
|
ScriptLoaderRunnable& mScriptLoader;
|
|
bool mIsWorkerScript;
|
|
uint32_t mFirstIndex;
|
|
uint32_t mLastIndex;
|
|
|
|
public:
|
|
ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
|
|
nsIEventTarget* aSyncLoopTarget,
|
|
bool aIsWorkerScript,
|
|
uint32_t aFirstIndex,
|
|
uint32_t aLastIndex);
|
|
|
|
private:
|
|
~ScriptExecutorRunnable()
|
|
{ }
|
|
|
|
virtual bool
|
|
IsDebuggerRunnable() const override;
|
|
|
|
virtual bool
|
|
PreRun(WorkerPrivate* aWorkerPrivate) override;
|
|
|
|
virtual bool
|
|
WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;
|
|
|
|
virtual void
|
|
PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
|
|
override;
|
|
|
|
nsresult
|
|
Cancel() override;
|
|
|
|
void
|
|
ShutdownScriptLoader(JSContext* aCx,
|
|
WorkerPrivate* aWorkerPrivate,
|
|
bool aResult,
|
|
bool aMutedError);
|
|
|
|
void LogExceptionToConsole(JSContext* aCx,
|
|
WorkerPrivate* WorkerPrivate);
|
|
};
|
|
|
|
class CacheScriptLoader;
|
|
|
|
class CacheCreator final : public PromiseNativeHandler
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
explicit CacheCreator(WorkerPrivate* aWorkerPrivate)
|
|
: mCacheName(aWorkerPrivate->ServiceWorkerCacheName())
|
|
, mPrivateBrowsing(aWorkerPrivate->IsInPrivateBrowsing())
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
|
|
MOZ_ASSERT(aWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript());
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
void
|
|
AddLoader(CacheScriptLoader* aLoader)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(!mCacheStorage);
|
|
mLoaders.AppendElement(aLoader);
|
|
}
|
|
|
|
virtual void
|
|
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
virtual void
|
|
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
// Try to load from cache with aPrincipal used for cache access.
|
|
nsresult
|
|
Load(nsIPrincipal* aPrincipal);
|
|
|
|
Cache*
|
|
Cache_() const
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mCache);
|
|
return mCache;
|
|
}
|
|
|
|
nsIGlobalObject*
|
|
Global() const
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mSandboxGlobalObject);
|
|
return mSandboxGlobalObject;
|
|
}
|
|
|
|
void
|
|
DeleteCache();
|
|
|
|
private:
|
|
~CacheCreator()
|
|
{
|
|
}
|
|
|
|
nsresult
|
|
CreateCacheStorage(nsIPrincipal* aPrincipal);
|
|
|
|
void
|
|
FailLoaders(nsresult aRv);
|
|
|
|
RefPtr<Cache> mCache;
|
|
RefPtr<CacheStorage> mCacheStorage;
|
|
nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
|
|
nsTArray<RefPtr<CacheScriptLoader>> mLoaders;
|
|
|
|
nsString mCacheName;
|
|
bool mPrivateBrowsing;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS0(CacheCreator)
|
|
|
|
class CacheScriptLoader final : public PromiseNativeHandler
|
|
, public nsIStreamLoaderObserver
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSISTREAMLOADEROBSERVER
|
|
|
|
CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo,
|
|
uint32_t aIndex, bool aIsWorkerScript,
|
|
ScriptLoaderRunnable* aRunnable)
|
|
: mLoadInfo(aLoadInfo)
|
|
, mIndex(aIndex)
|
|
, mRunnable(aRunnable)
|
|
, mIsWorkerScript(aIsWorkerScript)
|
|
, mFailed(false)
|
|
{
|
|
MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
|
|
mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate);
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
void
|
|
Fail(nsresult aRv);
|
|
|
|
void
|
|
Load(Cache* aCache);
|
|
|
|
virtual void
|
|
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
virtual void
|
|
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
private:
|
|
~CacheScriptLoader()
|
|
{
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
ScriptLoadInfo& mLoadInfo;
|
|
uint32_t mIndex;
|
|
RefPtr<ScriptLoaderRunnable> mRunnable;
|
|
bool mIsWorkerScript;
|
|
bool mFailed;
|
|
nsCOMPtr<nsIInputStreamPump> mPump;
|
|
nsCOMPtr<nsIURI> mBaseURI;
|
|
mozilla::dom::ChannelInfo mChannelInfo;
|
|
UniquePtr<PrincipalInfo> mPrincipalInfo;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver)
|
|
|
|
class CachePromiseHandler final : public PromiseNativeHandler
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
CachePromiseHandler(ScriptLoaderRunnable* aRunnable,
|
|
ScriptLoadInfo& aLoadInfo,
|
|
uint32_t aIndex)
|
|
: mRunnable(aRunnable)
|
|
, mLoadInfo(aLoadInfo)
|
|
, mIndex(aIndex)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mRunnable);
|
|
}
|
|
|
|
virtual void
|
|
ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
virtual void
|
|
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
|
|
|
|
private:
|
|
~CachePromiseHandler()
|
|
{
|
|
AssertIsOnMainThread();
|
|
}
|
|
|
|
RefPtr<ScriptLoaderRunnable> mRunnable;
|
|
ScriptLoadInfo& mLoadInfo;
|
|
uint32_t mIndex;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS0(CachePromiseHandler)
|
|
|
|
class LoaderListener final : public nsIStreamLoaderObserver
|
|
, public nsIRequestObserver
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
LoaderListener(ScriptLoaderRunnable* aRunnable, uint32_t aIndex)
|
|
: mRunnable(aRunnable)
|
|
, mIndex(aIndex)
|
|
{
|
|
MOZ_ASSERT(mRunnable);
|
|
}
|
|
|
|
NS_IMETHOD
|
|
OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
|
|
nsresult aStatus, uint32_t aStringLen,
|
|
const uint8_t* aString) override;
|
|
|
|
NS_IMETHOD
|
|
OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override;
|
|
|
|
NS_IMETHOD
|
|
OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
|
|
nsresult aStatusCode) override
|
|
{
|
|
// Nothing to do here!
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
~LoaderListener() {}
|
|
|
|
RefPtr<ScriptLoaderRunnable> mRunnable;
|
|
uint32_t mIndex;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver)
|
|
|
|
class ScriptLoaderHolder;
|
|
|
|
class ScriptLoaderRunnable final : public nsIRunnable
|
|
{
|
|
friend class ScriptExecutorRunnable;
|
|
friend class ScriptLoaderHolder;
|
|
friend class CachePromiseHandler;
|
|
friend class CacheScriptLoader;
|
|
friend class LoaderListener;
|
|
|
|
WorkerPrivate* mWorkerPrivate;
|
|
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
|
|
nsTArray<ScriptLoadInfo> mLoadInfos;
|
|
RefPtr<CacheCreator> mCacheCreator;
|
|
bool mIsMainScript;
|
|
WorkerScriptType mWorkerScriptType;
|
|
bool mCanceled;
|
|
bool mCanceledMainThread;
|
|
ErrorResult& mRv;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
|
|
nsIEventTarget* aSyncLoopTarget,
|
|
nsTArray<ScriptLoadInfo>& aLoadInfos,
|
|
bool aIsMainScript,
|
|
WorkerScriptType aWorkerScriptType,
|
|
ErrorResult& aRv)
|
|
: mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget),
|
|
mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType),
|
|
mCanceled(false), mCanceledMainThread(false), mRv(aRv)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
MOZ_ASSERT(aSyncLoopTarget);
|
|
MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1);
|
|
|
|
mLoadInfos.SwapElements(aLoadInfos);
|
|
}
|
|
|
|
private:
|
|
~ScriptLoaderRunnable()
|
|
{ }
|
|
|
|
NS_IMETHOD
|
|
Run() override
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
nsresult rv = RunInternal();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
CancelMainThread(rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
LoadingFinished(uint32_t aIndex, nsresult aRv)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
|
|
|
|
loadInfo.mLoadResult = aRv;
|
|
|
|
MOZ_ASSERT(!loadInfo.mLoadingFinished);
|
|
loadInfo.mLoadingFinished = true;
|
|
|
|
MaybeExecuteFinishedScripts(aIndex);
|
|
}
|
|
|
|
void
|
|
MaybeExecuteFinishedScripts(uint32_t aIndex)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
|
|
|
|
// We execute the last step if we don't have a pending operation with the
|
|
// cache and the loading is completed.
|
|
if (loadInfo.Finished()) {
|
|
ExecuteFinishedScripts();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
OnStreamComplete(nsIStreamLoader* aLoader, uint32_t aIndex,
|
|
nsresult aStatus, uint32_t aStringLen,
|
|
const uint8_t* aString)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
|
|
nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen,
|
|
aString, mLoadInfos[aIndex]);
|
|
LoadingFinished(aIndex, rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OnStartRequest(nsIRequest* aRequest, uint32_t aIndex)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
|
|
// If one load info cancels or hits an error, it can race with the start
|
|
// callback coming from another load info.
|
|
if (mCanceledMainThread || !mCacheCreator) {
|
|
aRequest->Cancel(NS_ERROR_FAILURE);
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
|
|
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
|
MOZ_ASSERT(channel == loadInfo.mChannel);
|
|
|
|
// We synthesize the result code, but its never exposed to content.
|
|
RefPtr<mozilla::dom::InternalResponse> ir =
|
|
new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK"));
|
|
ir->SetBody(loadInfo.mCacheReadStream, InternalResponse::UNKNOWN_BODY_SIZE);
|
|
// Drop our reference to the stream now that we've passed it along, so it
|
|
// doesn't hang around once the cache is done with it and keep data alive.
|
|
loadInfo.mCacheReadStream = nullptr;
|
|
|
|
// Set the channel info of the channel on the response so that it's
|
|
// saved in the cache.
|
|
ir->InitChannelInfo(channel);
|
|
|
|
// Save the principal of the channel since its URI encodes the script URI
|
|
// rather than the ServiceWorkerRegistrationInfo URI.
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(ssm, "Should never be null!");
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
channel->Cancel(rv);
|
|
return rv;
|
|
}
|
|
|
|
UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
|
|
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
channel->Cancel(rv);
|
|
return rv;
|
|
}
|
|
|
|
ir->SetPrincipalInfo(Move(principalInfo));
|
|
|
|
RefPtr<mozilla::dom::Response> response =
|
|
new mozilla::dom::Response(mCacheCreator->Global(), ir);
|
|
|
|
mozilla::dom::RequestOrUSVString request;
|
|
|
|
MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty());
|
|
request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
|
|
loadInfo.mFullURL.Length());
|
|
|
|
ErrorResult error;
|
|
RefPtr<Promise> cachePromise =
|
|
mCacheCreator->Cache_()->Put(request, *response, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
nsresult rv = error.StealNSResult();
|
|
channel->Cancel(rv);
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<CachePromiseHandler> promiseHandler =
|
|
new CachePromiseHandler(this, loadInfo, aIndex);
|
|
cachePromise->AppendNativeHandler(promiseHandler);
|
|
|
|
loadInfo.mCachePromise.swap(cachePromise);
|
|
loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
Notify(Status aStatus)
|
|
{
|
|
mWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
if (aStatus >= Terminating && !mCanceled) {
|
|
mCanceled = true;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
NS_DispatchToMainThread(NewRunnableMethod(this,
|
|
&ScriptLoaderRunnable::CancelMainThreadWithBindingAborted)));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
IsMainWorkerScript() const
|
|
{
|
|
return mIsMainScript && mWorkerScriptType == WorkerScript;
|
|
}
|
|
|
|
void
|
|
CancelMainThreadWithBindingAborted()
|
|
{
|
|
CancelMainThread(NS_BINDING_ABORTED);
|
|
}
|
|
|
|
void
|
|
CancelMainThread(nsresult aCancelResult)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (mCanceledMainThread) {
|
|
return;
|
|
}
|
|
|
|
mCanceledMainThread = true;
|
|
|
|
if (mCacheCreator) {
|
|
MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
|
|
DeleteCache();
|
|
}
|
|
|
|
// Cancel all the channels that were already opened.
|
|
for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[index];
|
|
|
|
// If promise or channel is non-null, their failures will lead to
|
|
// LoadingFinished being called.
|
|
bool callLoadingFinished = true;
|
|
|
|
if (loadInfo.mCachePromise) {
|
|
MOZ_ASSERT(mWorkerPrivate->IsServiceWorker());
|
|
loadInfo.mCachePromise->MaybeReject(aCancelResult);
|
|
loadInfo.mCachePromise = nullptr;
|
|
callLoadingFinished = false;
|
|
}
|
|
|
|
if (loadInfo.mChannel) {
|
|
if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(aCancelResult))) {
|
|
callLoadingFinished = false;
|
|
} else {
|
|
NS_WARNING("Failed to cancel channel!");
|
|
}
|
|
}
|
|
|
|
if (callLoadingFinished && !loadInfo.Finished()) {
|
|
LoadingFinished(index, aCancelResult);
|
|
}
|
|
}
|
|
|
|
ExecuteFinishedScripts();
|
|
}
|
|
|
|
void
|
|
DeleteCache()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!mCacheCreator) {
|
|
return;
|
|
}
|
|
|
|
mCacheCreator->DeleteCache();
|
|
mCacheCreator = nullptr;
|
|
}
|
|
|
|
nsresult
|
|
RunInternal()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (IsMainWorkerScript() && mWorkerPrivate->IsServiceWorker()) {
|
|
mWorkerPrivate->SetLoadingWorkerScript(true);
|
|
}
|
|
|
|
if (!mWorkerPrivate->IsServiceWorker() ||
|
|
!mWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()) {
|
|
for (uint32_t index = 0, len = mLoadInfos.Length(); index < len;
|
|
++index) {
|
|
nsresult rv = LoadScript(index);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LoadingFinished(index, rv);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(!mCacheCreator);
|
|
mCacheCreator = new CacheCreator(mWorkerPrivate);
|
|
|
|
for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) {
|
|
RefPtr<CacheScriptLoader> loader =
|
|
new CacheScriptLoader(mWorkerPrivate, mLoadInfos[index], index,
|
|
IsMainWorkerScript(), this);
|
|
mCacheCreator->AddLoader(loader);
|
|
}
|
|
|
|
// The worker may have a null principal on first load, but in that case its
|
|
// parent definitely will have one.
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
if (!principal) {
|
|
WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
|
|
MOZ_ASSERT(parentWorker, "Must have a parent!");
|
|
principal = parentWorker->GetPrincipal();
|
|
}
|
|
|
|
nsresult rv = mCacheCreator->Load(principal);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
LoadScript(uint32_t aIndex)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
|
|
WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
|
|
|
|
// Figure out which principal to use.
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
|
|
if (!principal) {
|
|
NS_ASSERTION(parentWorker, "Must have a principal!");
|
|
NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!");
|
|
|
|
principal = parentWorker->GetPrincipal();
|
|
loadGroup = parentWorker->GetLoadGroup();
|
|
}
|
|
NS_ASSERTION(principal, "This should never be null here!");
|
|
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal));
|
|
|
|
// Figure out our base URI.
|
|
nsCOMPtr<nsIURI> baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate);
|
|
|
|
// May be null.
|
|
nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
if (IsMainWorkerScript()) {
|
|
// May be null.
|
|
channel = mWorkerPrivate->ForgetWorkerChannel();
|
|
}
|
|
|
|
nsCOMPtr<nsIIOService> ios(do_GetIOService());
|
|
|
|
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(secMan, "This should never be null!");
|
|
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
|
|
nsresult& rv = loadInfo.mLoadResult;
|
|
|
|
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
|
|
|
|
// Get the top-level worker.
|
|
WorkerPrivate* topWorkerPrivate = mWorkerPrivate;
|
|
WorkerPrivate* parent = topWorkerPrivate->GetParent();
|
|
while (parent) {
|
|
topWorkerPrivate = parent;
|
|
parent = topWorkerPrivate->GetParent();
|
|
}
|
|
|
|
// If the top-level worker is a dedicated worker and has a window, and the
|
|
// window has a docshell, the caching behavior of this worker should match
|
|
// that of that docshell.
|
|
if (topWorkerPrivate->IsDedicatedWorker()) {
|
|
nsCOMPtr<nsPIDOMWindowInner> window = topWorkerPrivate->GetWindow();
|
|
if (window) {
|
|
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
|
|
if (docShell) {
|
|
nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are loading a script for a ServiceWorker then we must not
|
|
// try to intercept it. If the interception matches the current
|
|
// ServiceWorker's scope then we could deadlock the load.
|
|
if (mWorkerPrivate->IsServiceWorker()) {
|
|
loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
|
|
}
|
|
|
|
if (!channel) {
|
|
// Only top level workers' main script use the document charset for the
|
|
// script uri encoding. Otherwise, default encoding (UTF-8) is applied.
|
|
bool useDefaultEncoding = !(!parentWorker && IsMainWorkerScript());
|
|
rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
|
|
secMan, loadInfo.mURL, IsMainWorkerScript(),
|
|
mWorkerScriptType,
|
|
mWorkerPrivate->ContentPolicyType(), loadFlags,
|
|
useDefaultEncoding,
|
|
getter_AddRefs(channel));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// We need to know which index we're on in OnStreamComplete so we know
|
|
// where to put the result.
|
|
RefPtr<LoaderListener> listener = new LoaderListener(this, aIndex);
|
|
|
|
// We don't care about progress so just use the simple stream loader for
|
|
// OnStreamComplete notification only.
|
|
nsCOMPtr<nsIStreamLoader> loader;
|
|
rv = NS_NewStreamLoader(getter_AddRefs(loader), listener);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
|
|
rv = channel->AsyncOpen2(loader);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIOutputStream> writer;
|
|
|
|
// In case we return early.
|
|
loadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
|
|
|
|
rv = NS_NewPipe(getter_AddRefs(loadInfo.mCacheReadStream),
|
|
getter_AddRefs(writer), 0,
|
|
UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case
|
|
true, false); // non-blocking reader, blocking writer
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListenerTee> tee =
|
|
do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
|
|
rv = tee->Init(loader, writer, listener);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsresult rv = channel->AsyncOpen2(tee);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
loadInfo.mChannel.swap(channel);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus,
|
|
uint32_t aStringLen, const uint8_t* aString,
|
|
ScriptLoadInfo& aLoadInfo)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (!aLoadInfo.mChannel) {
|
|
return NS_BINDING_ABORTED;
|
|
}
|
|
|
|
aLoadInfo.mChannel = nullptr;
|
|
|
|
if (NS_FAILED(aStatus)) {
|
|
return aStatus;
|
|
}
|
|
|
|
NS_ASSERTION(aString, "This should never be null!");
|
|
|
|
nsCOMPtr<nsIRequest> request;
|
|
nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
|
|
MOZ_ASSERT(channel);
|
|
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(ssm, "Should never be null!");
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
if (!principal) {
|
|
WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
|
|
MOZ_ASSERT(parentWorker, "Must have a parent!");
|
|
principal = parentWorker->GetPrincipal();
|
|
}
|
|
|
|
// We don't mute the main worker script becase we've already done
|
|
// same-origin checks on them so we should be able to see their errors.
|
|
// Note that for data: url, where we allow it through the same-origin check
|
|
// but then give it a different origin.
|
|
aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript()
|
|
? false
|
|
: !principal->Subsumes(channelPrincipal));
|
|
|
|
// Make sure we're not seeing the result of a 404 or something by checking
|
|
// the 'requestSucceeded' attribute on the http channel.
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
|
|
nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue;
|
|
|
|
if (httpChannel) {
|
|
bool requestSucceeded;
|
|
rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!requestSucceeded) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
httpChannel->GetResponseHeader(
|
|
NS_LITERAL_CSTRING("content-security-policy"),
|
|
tCspHeaderValue);
|
|
|
|
httpChannel->GetResponseHeader(
|
|
NS_LITERAL_CSTRING("content-security-policy-report-only"),
|
|
tCspROHeaderValue);
|
|
|
|
httpChannel->GetResponseHeader(
|
|
NS_LITERAL_CSTRING("referrer-policy"),
|
|
tRPHeaderCValue);
|
|
}
|
|
|
|
// May be null.
|
|
nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
|
|
|
|
// Use the regular nsScriptLoader for this grunt work! Should be just fine
|
|
// because we're running on the main thread.
|
|
// Unlike <script> tags, Worker scripts are always decoded as UTF-8,
|
|
// per spec. So we explicitly pass in the charset hint.
|
|
rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
|
|
NS_LITERAL_STRING("UTF-8"), parentDoc,
|
|
aLoadInfo.mScriptTextBuf,
|
|
aLoadInfo.mScriptTextLength);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (!aLoadInfo.mScriptTextLength && !aLoadInfo.mScriptTextBuf) {
|
|
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
|
NS_LITERAL_CSTRING("DOM"), parentDoc,
|
|
nsContentUtils::eDOM_PROPERTIES,
|
|
"EmptyWorkerSourceWarning");
|
|
} else if (!aLoadInfo.mScriptTextBuf) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// Figure out what we actually loaded.
|
|
nsCOMPtr<nsIURI> finalURI;
|
|
rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCString filename;
|
|
rv = finalURI->GetSpec(filename);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!filename.IsEmpty()) {
|
|
// This will help callers figure out what their script url resolved to in
|
|
// case of errors.
|
|
aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
|
|
}
|
|
|
|
// Update the principal of the worker and its base URI if we just loaded the
|
|
// worker's primary script.
|
|
if (IsMainWorkerScript()) {
|
|
// Take care of the base URI first.
|
|
mWorkerPrivate->SetBaseURI(finalURI);
|
|
|
|
// Store the channel info if needed.
|
|
mWorkerPrivate->InitChannelInfo(channel);
|
|
|
|
// Now to figure out which principal to give this worker.
|
|
WorkerPrivate* parent = mWorkerPrivate->GetParent();
|
|
|
|
NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
|
|
"Must have one of these!");
|
|
|
|
nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ?
|
|
mWorkerPrivate->GetPrincipal() :
|
|
parent->GetPrincipal();
|
|
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(ssm, "Should never be null!");
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsILoadGroup> channelLoadGroup;
|
|
rv = channel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
MOZ_ASSERT(channelLoadGroup);
|
|
|
|
// If the load principal is the system principal then the channel
|
|
// principal must also be the system principal (we do not allow chrome
|
|
// code to create workers with non-chrome scripts, and if we ever decide
|
|
// to change this we need to make sure we don't always set
|
|
// mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise
|
|
// this channel principal must be same origin with the load principal (we
|
|
// check again here in case redirects changed the location of the script).
|
|
if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) {
|
|
if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
|
|
// See if this is a resource URI. Since JSMs usually come from
|
|
// resource:// URIs we're currently considering all URIs with the
|
|
// URI_IS_UI_RESOURCE flag as valid for creating privileged workers.
|
|
bool isResource;
|
|
rv = NS_URIChainHasFlags(finalURI,
|
|
nsIProtocolHandler::URI_IS_UI_RESOURCE,
|
|
&isResource);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (isResource) {
|
|
// Assign the system principal to the resource:// worker only if it
|
|
// was loaded from code using the system principal.
|
|
channelPrincipal = loadPrincipal;
|
|
} else {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The principal can change, but it should still match the original
|
|
// load group's appId and browser element flag.
|
|
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
|
|
|
|
mWorkerPrivate->SetPrincipal(channelPrincipal, channelLoadGroup);
|
|
|
|
// We did inherit CSP in bug 1223647. If we do not already have a CSP, we
|
|
// should get it from the HTTP headers on the worker script.
|
|
if (!mWorkerPrivate->GetCSP() && CSPService::sCSPEnabled) {
|
|
NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
|
|
NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
|
|
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
MOZ_ASSERT(principal, "Should not be null");
|
|
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
|
|
|
|
if (csp) {
|
|
// If there's a CSP header, apply it.
|
|
if (!cspHeaderValue.IsEmpty()) {
|
|
rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
// If there's a report-only CSP header, apply it.
|
|
if (!cspROHeaderValue.IsEmpty()) {
|
|
rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// Set evalAllowed, default value is set in GetAllowsEval
|
|
bool evalAllowed = false;
|
|
bool reportEvalViolations = false;
|
|
rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mWorkerPrivate->SetCSP(csp);
|
|
mWorkerPrivate->SetEvalAllowed(evalAllowed);
|
|
mWorkerPrivate->SetReportCSPViolations(reportEvalViolations);
|
|
|
|
// Set ReferrerPolicy, default value is set in GetReferrerPolicy
|
|
bool hasReferrerPolicy = false;
|
|
uint32_t rp = mozilla::net::RP_Default;
|
|
rv = csp->GetReferrerPolicy(&rp, &hasReferrerPolicy);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (hasReferrerPolicy) {
|
|
mWorkerPrivate->SetReferrerPolicy(static_cast<net::ReferrerPolicy>(rp));
|
|
}
|
|
}
|
|
}
|
|
if (parent) {
|
|
// XHR Params Allowed
|
|
mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
|
|
}
|
|
}
|
|
|
|
NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
|
|
// If there's a Referrer-Policy header, apply it.
|
|
if (!tRPHeaderValue.IsEmpty()) {
|
|
net::ReferrerPolicy policy =
|
|
nsContentUtils::GetReferrerPolicyFromHeader(tRPHeaderValue);
|
|
if (policy != net::RP_Unset) {
|
|
mWorkerPrivate->SetReferrerPolicy(policy);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
|
|
uint32_t aStringLen,
|
|
const mozilla::dom::ChannelInfo& aChannelInfo,
|
|
UniquePtr<PrincipalInfo> aPrincipalInfo)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aIndex < mLoadInfos.Length());
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[aIndex];
|
|
MOZ_ASSERT(loadInfo.mCacheStatus == ScriptLoadInfo::Cached);
|
|
|
|
nsCOMPtr<nsIPrincipal> responsePrincipal =
|
|
PrincipalInfoToPrincipal(*aPrincipalInfo);
|
|
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
if (!principal) {
|
|
WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
|
|
MOZ_ASSERT(parentWorker, "Must have a parent!");
|
|
principal = parentWorker->GetPrincipal();
|
|
}
|
|
|
|
loadInfo.mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal));
|
|
|
|
// May be null.
|
|
nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
|
|
|
|
MOZ_ASSERT(!loadInfo.mScriptTextBuf);
|
|
|
|
nsresult rv =
|
|
nsScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen,
|
|
NS_LITERAL_STRING("UTF-8"), parentDoc,
|
|
loadInfo.mScriptTextBuf,
|
|
loadInfo.mScriptTextLength);
|
|
if (NS_SUCCEEDED(rv) && IsMainWorkerScript()) {
|
|
nsCOMPtr<nsIURI> finalURI;
|
|
rv = NS_NewURI(getter_AddRefs(finalURI), loadInfo.mFullURL, nullptr, nullptr);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mWorkerPrivate->SetBaseURI(finalURI);
|
|
}
|
|
|
|
mozilla::DebugOnly<nsIPrincipal*> principal = mWorkerPrivate->GetPrincipal();
|
|
MOZ_ASSERT(principal);
|
|
nsILoadGroup* loadGroup = mWorkerPrivate->GetLoadGroup();
|
|
MOZ_ASSERT(loadGroup);
|
|
|
|
mozilla::DebugOnly<bool> equal = false;
|
|
MOZ_ASSERT(responsePrincipal && NS_SUCCEEDED(responsePrincipal->Equals(principal, &equal)));
|
|
MOZ_ASSERT(equal);
|
|
|
|
mWorkerPrivate->InitChannelInfo(aChannelInfo);
|
|
mWorkerPrivate->SetPrincipal(responsePrincipal, loadGroup);
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
DataReceived();
|
|
}
|
|
|
|
LoadingFinished(aIndex, rv);
|
|
}
|
|
|
|
void
|
|
DataReceived()
|
|
{
|
|
if (IsMainWorkerScript()) {
|
|
WorkerPrivate* parent = mWorkerPrivate->GetParent();
|
|
|
|
if (parent) {
|
|
// XHR Params Allowed
|
|
mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
|
|
|
|
// Set Eval and ContentSecurityPolicy
|
|
mWorkerPrivate->SetCSP(parent->GetCSP());
|
|
mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed());
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ExecuteFinishedScripts()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
if (IsMainWorkerScript()) {
|
|
mWorkerPrivate->WorkerScriptLoaded();
|
|
}
|
|
|
|
uint32_t firstIndex = UINT32_MAX;
|
|
uint32_t lastIndex = UINT32_MAX;
|
|
|
|
// Find firstIndex based on whether mExecutionScheduled is unset.
|
|
for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
|
|
if (!mLoadInfos[index].mExecutionScheduled) {
|
|
firstIndex = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Find lastIndex based on whether mChannel is set, and update
|
|
// mExecutionScheduled on the ones we're about to schedule.
|
|
if (firstIndex != UINT32_MAX) {
|
|
for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) {
|
|
ScriptLoadInfo& loadInfo = mLoadInfos[index];
|
|
|
|
if (!loadInfo.Finished()) {
|
|
break;
|
|
}
|
|
|
|
// We can execute this one.
|
|
loadInfo.mExecutionScheduled = true;
|
|
|
|
lastIndex = index;
|
|
}
|
|
}
|
|
|
|
// This is the last index, we can unused things before the exection of the
|
|
// script and the stopping of the sync loop.
|
|
if (lastIndex == mLoadInfos.Length() - 1) {
|
|
mCacheCreator = nullptr;
|
|
}
|
|
|
|
if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) {
|
|
RefPtr<ScriptExecutorRunnable> runnable =
|
|
new ScriptExecutorRunnable(*this, mSyncLoopTarget, IsMainWorkerScript(),
|
|
firstIndex, lastIndex);
|
|
if (!runnable->Dispatch()) {
|
|
MOZ_ASSERT(false, "This should never fail!");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable)
|
|
|
|
class MOZ_STACK_CLASS ScriptLoaderHolder final : public WorkerHolder
|
|
{
|
|
// Raw pointer because this holder object follows the mRunnable life-time.
|
|
ScriptLoaderRunnable* mRunnable;
|
|
|
|
public:
|
|
explicit ScriptLoaderHolder(ScriptLoaderRunnable* aRunnable)
|
|
: mRunnable(aRunnable)
|
|
{
|
|
MOZ_ASSERT(aRunnable);
|
|
}
|
|
|
|
virtual bool
|
|
Notify(Status aStatus) override
|
|
{
|
|
mRunnable->Notify(aStatus);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
LoaderListener::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
|
|
nsresult aStatus, uint32_t aStringLen,
|
|
const uint8_t* aString)
|
|
{
|
|
return mRunnable->OnStreamComplete(aLoader, mIndex, aStatus, aStringLen, aString);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
LoaderListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
|
|
{
|
|
return mRunnable->OnStartRequest(aRequest, mIndex);
|
|
}
|
|
|
|
void
|
|
CachePromiseHandler::ResolvedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
// May already have been canceled by CacheScriptLoader::Fail from
|
|
// CancelMainThread.
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
|
|
mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
|
|
MOZ_ASSERT_IF(mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel, !mLoadInfo.mCachePromise);
|
|
|
|
if (mLoadInfo.mCachePromise) {
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
|
|
mLoadInfo.mCachePromise = nullptr;
|
|
mRunnable->MaybeExecuteFinishedScripts(mIndex);
|
|
}
|
|
}
|
|
|
|
void
|
|
CachePromiseHandler::RejectedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
// May already have been canceled by CacheScriptLoader::Fail from
|
|
// CancelMainThread.
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
|
|
mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
|
|
|
|
mLoadInfo.mCachePromise = nullptr;
|
|
|
|
// This will delete the cache object and will call LoadingFinished() with an
|
|
// error for each ongoing operation.
|
|
mRunnable->DeleteCache();
|
|
}
|
|
|
|
nsresult
|
|
CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(!mCacheStorage);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
nsIXPConnect* xpc = nsContentUtils::XPConnect();
|
|
MOZ_ASSERT(xpc, "This should never be null!");
|
|
|
|
mozilla::AutoSafeJSContext cx;
|
|
JS::Rooted<JSObject*> sandbox(cx);
|
|
nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mSandboxGlobalObject = xpc::NativeGlobal(sandbox);
|
|
if (NS_WARN_IF(!mSandboxGlobalObject)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// If we're in private browsing mode, don't even try to create the
|
|
// CacheStorage. Instead, just fail immediately to terminate the
|
|
// ServiceWorker load.
|
|
if (NS_WARN_IF(mPrivateBrowsing)) {
|
|
return NS_ERROR_DOM_SECURITY_ERR;
|
|
}
|
|
|
|
// Create a CacheStorage bypassing its trusted origin checks. The
|
|
// ServiceWorker has already performed its own checks before getting
|
|
// to this point.
|
|
ErrorResult error;
|
|
mCacheStorage =
|
|
CacheStorage::CreateOnMainThread(mozilla::dom::cache::CHROME_ONLY_NAMESPACE,
|
|
mSandboxGlobalObject,
|
|
aPrincipal, mPrivateBrowsing,
|
|
true /* force trusted origin */,
|
|
error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CacheCreator::Load(nsIPrincipal* aPrincipal)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(!mLoaders.IsEmpty());
|
|
|
|
nsresult rv = CreateCacheStorage(aPrincipal);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
ErrorResult error;
|
|
MOZ_ASSERT(!mCacheName.IsEmpty());
|
|
RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
return error.StealNSResult();
|
|
}
|
|
|
|
promise->AppendNativeHandler(this);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CacheCreator::FailLoaders(nsresult aRv)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
// Fail() can call LoadingFinished() which may call ExecuteFinishedScripts()
|
|
// which sets mCacheCreator to null, so hold a ref.
|
|
RefPtr<CacheCreator> kungfuDeathGrip = this;
|
|
|
|
for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
|
|
mLoaders[i]->Fail(aRv);
|
|
}
|
|
|
|
mLoaders.Clear();
|
|
}
|
|
|
|
void
|
|
CacheCreator::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
FailLoaders(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
void
|
|
CacheCreator::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aValue.isObject());
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
|
|
Cache* cache = nullptr;
|
|
nsresult rv = UNWRAP_OBJECT(Cache, obj, cache);
|
|
MOZ_ALWAYS_SUCCEEDS(rv);
|
|
|
|
mCache = cache;
|
|
MOZ_ASSERT(mCache);
|
|
|
|
// If the worker is canceled, CancelMainThread() will have cleared the
|
|
// loaders.
|
|
for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
|
|
mLoaders[i]->Load(cache);
|
|
}
|
|
}
|
|
|
|
void
|
|
CacheCreator::DeleteCache()
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
ErrorResult rv;
|
|
|
|
// It's safe to do this while Cache::Match() and Cache::Put() calls are
|
|
// running.
|
|
RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
rv.SuppressException();
|
|
return;
|
|
}
|
|
|
|
// We don't care to know the result of the promise object.
|
|
FailLoaders(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
void
|
|
CacheScriptLoader::Fail(nsresult aRv)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(NS_FAILED(aRv));
|
|
|
|
if (mFailed) {
|
|
return;
|
|
}
|
|
|
|
mFailed = true;
|
|
|
|
if (mPump) {
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
|
|
mPump->Cancel(aRv);
|
|
mPump = nullptr;
|
|
}
|
|
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cancel;
|
|
|
|
// Stop if the load was aborted on the main thread.
|
|
// Can't use Finished() because mCachePromise may still be true.
|
|
if (mLoadInfo.mLoadingFinished) {
|
|
MOZ_ASSERT(!mLoadInfo.mChannel);
|
|
MOZ_ASSERT_IF(mLoadInfo.mCachePromise,
|
|
mLoadInfo.mCacheStatus == ScriptLoadInfo::WritingToCache ||
|
|
mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
|
|
return;
|
|
}
|
|
|
|
mRunnable->LoadingFinished(mIndex, aRv);
|
|
}
|
|
|
|
void
|
|
CacheScriptLoader::Load(Cache* aCache)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(aCache);
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(uri), mLoadInfo.mURL, nullptr,
|
|
mBaseURI);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
nsAutoCString spec;
|
|
rv = uri->GetSpec(spec);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mLoadInfo.mFullURL.IsEmpty());
|
|
CopyUTF8toUTF16(spec, mLoadInfo.mFullURL);
|
|
|
|
mozilla::dom::RequestOrUSVString request;
|
|
request.SetAsUSVString().Rebind(mLoadInfo.mFullURL.Data(),
|
|
mLoadInfo.mFullURL.Length());
|
|
|
|
mozilla::dom::CacheQueryOptions params;
|
|
|
|
ErrorResult error;
|
|
RefPtr<Promise> promise = aCache->Match(request, params, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
Fail(error.StealNSResult());
|
|
return;
|
|
}
|
|
|
|
promise->AppendNativeHandler(this);
|
|
}
|
|
|
|
void
|
|
CacheScriptLoader::RejectedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
|
|
Fail(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
void
|
|
CacheScriptLoader::ResolvedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue)
|
|
{
|
|
AssertIsOnMainThread();
|
|
// If we have already called 'Fail', we should not proceed.
|
|
if (mFailed) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::Uncached);
|
|
|
|
nsresult rv;
|
|
|
|
if (aValue.isUndefined()) {
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::ToBeCached;
|
|
rv = mRunnable->LoadScript(mIndex);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
}
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(aValue.isObject());
|
|
|
|
JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
|
|
mozilla::dom::Response* response = nullptr;
|
|
rv = UNWRAP_OBJECT(Response, obj, response);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
response->GetBody(getter_AddRefs(inputStream));
|
|
mChannelInfo = response->GetChannelInfo();
|
|
const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
|
|
if (pInfo) {
|
|
mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
|
|
}
|
|
|
|
if (!inputStream) {
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
|
|
mRunnable->DataReceivedFromCache(mIndex, (uint8_t*)"", 0, mChannelInfo,
|
|
Move(mPrincipalInfo));
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!mPump);
|
|
rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamLoader> loader;
|
|
rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
rv = mPump->AsyncRead(loader, nullptr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mPump = nullptr;
|
|
Fail(rv);
|
|
return;
|
|
}
|
|
|
|
|
|
nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
|
|
if (rr) {
|
|
nsCOMPtr<nsIEventTarget> sts =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
rv = rr->RetargetDeliveryTo(sts);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
|
|
}
|
|
}
|
|
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::ReadingFromCache;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CacheScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
|
|
nsresult aStatus, uint32_t aStringLen,
|
|
const uint8_t* aString)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
mPump = nullptr;
|
|
|
|
if (NS_FAILED(aStatus)) {
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache ||
|
|
mLoadInfo.mCacheStatus == ScriptLoadInfo::Cancel);
|
|
Fail(aStatus);
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mLoadInfo.mCacheStatus == ScriptLoadInfo::ReadingFromCache);
|
|
mLoadInfo.mCacheStatus = ScriptLoadInfo::Cached;
|
|
|
|
MOZ_ASSERT(mPrincipalInfo);
|
|
mRunnable->DataReceivedFromCache(mIndex, aString, aStringLen, mChannelInfo,
|
|
Move(mPrincipalInfo));
|
|
return NS_OK;
|
|
}
|
|
|
|
class ChannelGetterRunnable final : public WorkerMainThreadRunnable
|
|
{
|
|
const nsAString& mScriptURL;
|
|
nsIChannel** mChannel;
|
|
nsresult mResult;
|
|
|
|
public:
|
|
ChannelGetterRunnable(WorkerPrivate* aParentWorker,
|
|
const nsAString& aScriptURL,
|
|
nsIChannel** aChannel)
|
|
: WorkerMainThreadRunnable(aParentWorker,
|
|
NS_LITERAL_CSTRING("ScriptLoader :: ChannelGetter"))
|
|
, mScriptURL(aScriptURL)
|
|
, mChannel(aChannel)
|
|
, mResult(NS_ERROR_FAILURE)
|
|
{
|
|
MOZ_ASSERT(aParentWorker);
|
|
aParentWorker->AssertIsOnWorkerThread();
|
|
}
|
|
|
|
virtual bool
|
|
MainThreadRun() override
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
|
|
MOZ_ASSERT(principal);
|
|
|
|
// Figure out our base URI.
|
|
nsCOMPtr<nsIURI> baseURI = mWorkerPrivate->GetBaseURI();
|
|
MOZ_ASSERT(baseURI);
|
|
|
|
// May be null.
|
|
nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
|
|
|
|
nsCOMPtr<nsILoadGroup> loadGroup = mWorkerPrivate->GetLoadGroup();
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
mResult =
|
|
scriptloader::ChannelFromScriptURLMainThread(principal, baseURI,
|
|
parentDoc, loadGroup,
|
|
mScriptURL,
|
|
// Nested workers are always dedicated.
|
|
nsIContentPolicy::TYPE_INTERNAL_WORKER,
|
|
// Nested workers use default uri encoding.
|
|
true,
|
|
getter_AddRefs(channel));
|
|
if (NS_SUCCEEDED(mResult)) {
|
|
channel.forget(mChannel);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
GetResult() const
|
|
{
|
|
return mResult;
|
|
}
|
|
|
|
private:
|
|
virtual ~ChannelGetterRunnable()
|
|
{ }
|
|
};
|
|
|
|
ScriptExecutorRunnable::ScriptExecutorRunnable(
|
|
ScriptLoaderRunnable& aScriptLoader,
|
|
nsIEventTarget* aSyncLoopTarget,
|
|
bool aIsWorkerScript,
|
|
uint32_t aFirstIndex,
|
|
uint32_t aLastIndex)
|
|
: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
|
|
mScriptLoader(aScriptLoader), mIsWorkerScript(aIsWorkerScript),
|
|
mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
|
|
{
|
|
MOZ_ASSERT(aFirstIndex <= aLastIndex);
|
|
MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
|
|
}
|
|
|
|
bool
|
|
ScriptExecutorRunnable::IsDebuggerRunnable() const
|
|
{
|
|
// ScriptExecutorRunnable is used to execute both worker and debugger scripts.
|
|
// In the latter case, the runnable needs to be dispatched to the debugger
|
|
// queue.
|
|
return mScriptLoader.mWorkerScriptType == DebuggerScript;
|
|
}
|
|
|
|
bool
|
|
ScriptExecutorRunnable::PreRun(WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
if (!mIsWorkerScript) {
|
|
return true;
|
|
}
|
|
|
|
if (!aWorkerPrivate->GetJSContext()) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(mFirstIndex == 0);
|
|
MOZ_ASSERT(!mScriptLoader.mRv.Failed());
|
|
|
|
AutoJSAPI jsapi;
|
|
jsapi.Init();
|
|
|
|
WorkerGlobalScope* globalScope =
|
|
aWorkerPrivate->GetOrCreateGlobalScope(jsapi.cx());
|
|
if (NS_WARN_IF(!globalScope)) {
|
|
NS_WARNING("Failed to make global!");
|
|
// There's no way to report the exception on jsapi right now, because there
|
|
// is no way to even enter a compartment on this thread anymore. Just clear
|
|
// the exception. We'll report some sort of error to our caller in
|
|
// ShutdownScriptLoader, but it will get squelched for the same reason we're
|
|
// squelching here: all the error reporting machinery relies on being able
|
|
// to enter a compartment to report the error.
|
|
jsapi.ClearException();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
|
|
|
|
// Don't run if something else has already failed.
|
|
for (uint32_t index = 0; index < mFirstIndex; index++) {
|
|
ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
|
|
|
|
NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
|
|
NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
|
|
|
|
if (!loadInfo.mExecutionResult) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If nothing else has failed, our ErrorResult better not be a failure either.
|
|
MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
|
|
|
|
// Slightly icky action at a distance, but there's no better place to stash
|
|
// this value, really.
|
|
JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
|
|
MOZ_ASSERT(global);
|
|
|
|
for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) {
|
|
ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
|
|
|
|
NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
|
|
NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
|
|
NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!");
|
|
|
|
MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
|
|
mScriptLoader.mRv.MightThrowJSException();
|
|
if (NS_FAILED(loadInfo.mLoadResult)) {
|
|
scriptloader::ReportLoadError(mScriptLoader.mRv,
|
|
loadInfo.mLoadResult, loadInfo.mURL);
|
|
// Top level scripts only!
|
|
if (mIsWorkerScript) {
|
|
aWorkerPrivate->MaybeDispatchLoadFailedRunnable();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
|
|
|
|
JS::CompileOptions options(aCx);
|
|
options.setFileAndLine(filename.get(), 1)
|
|
.setNoScriptRval(true);
|
|
|
|
if (mScriptLoader.mWorkerScriptType == DebuggerScript) {
|
|
options.setVersion(JSVERSION_LATEST);
|
|
}
|
|
|
|
MOZ_ASSERT(loadInfo.mMutedErrorFlag.isSome());
|
|
options.setMutedErrors(loadInfo.mMutedErrorFlag.valueOr(true));
|
|
|
|
JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf,
|
|
loadInfo.mScriptTextLength,
|
|
JS::SourceBufferHolder::GiveOwnership);
|
|
loadInfo.mScriptTextBuf = nullptr;
|
|
loadInfo.mScriptTextLength = 0;
|
|
|
|
// Our ErrorResult still shouldn't be a failure.
|
|
MOZ_ASSERT(!mScriptLoader.mRv.Failed(), "Who failed it and why?");
|
|
JS::Rooted<JS::Value> unused(aCx);
|
|
if (!JS::Evaluate(aCx, options, srcBuf, &unused)) {
|
|
mScriptLoader.mRv.StealExceptionFromJSContext(aCx);
|
|
return true;
|
|
}
|
|
|
|
loadInfo.mExecutionResult = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
|
|
bool aRunResult)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
MOZ_ASSERT(!JS_IsExceptionPending(aCx), "Who left an exception on there?");
|
|
|
|
nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
|
|
|
|
if (mLastIndex == loadInfos.Length() - 1) {
|
|
// All done. If anything failed then return false.
|
|
bool result = true;
|
|
bool mutedError = false;
|
|
for (uint32_t index = 0; index < loadInfos.Length(); index++) {
|
|
if (!loadInfos[index].mExecutionResult) {
|
|
mutedError = loadInfos[index].mMutedErrorFlag.valueOr(true);
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The only way we can get here with "result" false but without
|
|
// mScriptLoader.mRv being a failure is if we're loading the main worker
|
|
// script and GetOrCreateGlobalScope() fails. In that case we would have
|
|
// returned false from WorkerRun, so assert that.
|
|
MOZ_ASSERT_IF(!result && !mScriptLoader.mRv.Failed(),
|
|
!aRunResult);
|
|
ShutdownScriptLoader(aCx, aWorkerPrivate, result, mutedError);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
ScriptExecutorRunnable::Cancel()
|
|
{
|
|
if (mLastIndex == mScriptLoader.mLoadInfos.Length() - 1) {
|
|
ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate,
|
|
false, false);
|
|
}
|
|
return MainThreadWorkerSyncRunnable::Cancel();
|
|
}
|
|
|
|
void
|
|
ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx,
|
|
WorkerPrivate* aWorkerPrivate,
|
|
bool aResult,
|
|
bool aMutedError)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
MOZ_ASSERT(mLastIndex == mScriptLoader.mLoadInfos.Length() - 1);
|
|
|
|
if (mIsWorkerScript && aWorkerPrivate->IsServiceWorker()) {
|
|
aWorkerPrivate->SetLoadingWorkerScript(false);
|
|
}
|
|
|
|
if (!aResult) {
|
|
// At this point there are two possibilities:
|
|
//
|
|
// 1) mScriptLoader.mRv.Failed(). In that case we just want to leave it
|
|
// as-is, except if it has a JS exception and we need to mute JS
|
|
// exceptions. In that case, we log the exception without firing any
|
|
// events and then replace it on the ErrorResult with a NetworkError,
|
|
// per spec.
|
|
//
|
|
// 2) mScriptLoader.mRv succeeded. As far as I can tell, this can only
|
|
// happen when loading the main worker script and
|
|
// GetOrCreateGlobalScope() fails or if ScriptExecutorRunnable::Cancel
|
|
// got called. Does it matter what we throw in this case? I'm not
|
|
// sure...
|
|
if (mScriptLoader.mRv.Failed()) {
|
|
if (aMutedError && mScriptLoader.mRv.IsJSException()) {
|
|
LogExceptionToConsole(aCx, aWorkerPrivate);
|
|
mScriptLoader.mRv.Throw(NS_ERROR_DOM_NETWORK_ERR);
|
|
}
|
|
} else {
|
|
mScriptLoader.mRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
}
|
|
}
|
|
|
|
aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
|
|
}
|
|
|
|
void
|
|
ScriptExecutorRunnable::LogExceptionToConsole(JSContext* aCx,
|
|
WorkerPrivate* aWorkerPrivate)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
|
|
MOZ_ASSERT(mScriptLoader.mRv.IsJSException());
|
|
|
|
JS::Rooted<JS::Value> exn(aCx);
|
|
if (!ToJSValue(aCx, mScriptLoader.mRv, &exn)) {
|
|
return;
|
|
}
|
|
|
|
// Now the exception state should all be in exn.
|
|
MOZ_ASSERT(!JS_IsExceptionPending(aCx));
|
|
MOZ_ASSERT(!mScriptLoader.mRv.Failed());
|
|
|
|
js::ErrorReport report(aCx);
|
|
if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
|
|
JS_ClearPendingException(aCx);
|
|
return;
|
|
}
|
|
|
|
RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
|
|
xpcReport->Init(report.report(), report.message(),
|
|
aWorkerPrivate->IsChromeWorker(), aWorkerPrivate->WindowID());
|
|
|
|
RefPtr<AsyncErrorReporter> r = new AsyncErrorReporter(xpcReport);
|
|
NS_DispatchToMainThread(r);
|
|
}
|
|
|
|
void
|
|
LoadAllScripts(WorkerPrivate* aWorkerPrivate,
|
|
nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsMainScript,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv)
|
|
{
|
|
aWorkerPrivate->AssertIsOnWorkerThread();
|
|
NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
|
|
|
|
AutoSyncLoopHolder syncLoop(aWorkerPrivate);
|
|
|
|
RefPtr<ScriptLoaderRunnable> loader =
|
|
new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(),
|
|
aLoadInfos, aIsMainScript, aWorkerScriptType,
|
|
aRv);
|
|
|
|
NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
|
|
|
|
ScriptLoaderHolder workerHolder(loader);
|
|
|
|
if (NS_WARN_IF(!workerHolder.HoldWorker(aWorkerPrivate))) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
if (NS_FAILED(NS_DispatchToMainThread(loader))) {
|
|
NS_ERROR("Failed to dispatch!");
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
syncLoop.Run();
|
|
}
|
|
|
|
} /* anonymous namespace */
|
|
|
|
BEGIN_WORKERS_NAMESPACE
|
|
|
|
namespace scriptloader {
|
|
|
|
nsresult
|
|
ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal,
|
|
nsIURI* aBaseURI,
|
|
nsIDocument* aParentDoc,
|
|
nsILoadGroup* aLoadGroup,
|
|
const nsAString& aScriptURL,
|
|
nsContentPolicyType aContentPolicyType,
|
|
bool aDefaultURIEncoding,
|
|
nsIChannel** aChannel)
|
|
{
|
|
AssertIsOnMainThread();
|
|
|
|
nsCOMPtr<nsIIOService> ios(do_GetIOService());
|
|
|
|
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
|
|
NS_ASSERTION(secMan, "This should never be null!");
|
|
|
|
return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, aLoadGroup,
|
|
ios, secMan, aScriptURL, true, WorkerScript,
|
|
aContentPolicyType, nsIRequest::LOAD_NORMAL,
|
|
aDefaultURIEncoding, aChannel);
|
|
}
|
|
|
|
nsresult
|
|
ChannelFromScriptURLWorkerThread(JSContext* aCx,
|
|
WorkerPrivate* aParent,
|
|
const nsAString& aScriptURL,
|
|
nsIChannel** aChannel)
|
|
{
|
|
aParent->AssertIsOnWorkerThread();
|
|
|
|
RefPtr<ChannelGetterRunnable> getter =
|
|
new ChannelGetterRunnable(aParent, aScriptURL, aChannel);
|
|
|
|
ErrorResult rv;
|
|
getter->Dispatch(rv);
|
|
if (rv.Failed()) {
|
|
NS_ERROR("Failed to dispatch!");
|
|
return rv.StealNSResult();
|
|
}
|
|
|
|
return getter->GetResult();
|
|
}
|
|
|
|
void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
|
|
const nsAString& aScriptURL)
|
|
{
|
|
MOZ_ASSERT(!aRv.Failed());
|
|
|
|
switch (aLoadResult) {
|
|
case NS_ERROR_FILE_NOT_FOUND:
|
|
case NS_ERROR_NOT_AVAILABLE:
|
|
aLoadResult = NS_ERROR_DOM_NETWORK_ERR;
|
|
break;
|
|
|
|
case NS_ERROR_MALFORMED_URI:
|
|
aLoadResult = NS_ERROR_DOM_SYNTAX_ERR;
|
|
break;
|
|
|
|
case NS_BINDING_ABORTED:
|
|
// Note: we used to pretend like we didn't set an exception for
|
|
// NS_BINDING_ABORTED, but then ShutdownScriptLoader did it anyway. The
|
|
// other callsite, in WorkerPrivate::Constructor, never passed in
|
|
// NS_BINDING_ABORTED. So just throw it directly here. Consumers will
|
|
// deal as needed. But note that we do NOT want to ThrowDOMException()
|
|
// for this case, because that will make it impossible for consumers to
|
|
// realize that our error was NS_BINDING_ABORTED.
|
|
aRv.Throw(aLoadResult);
|
|
return;
|
|
|
|
case NS_ERROR_DOM_SECURITY_ERR:
|
|
case NS_ERROR_DOM_SYNTAX_ERR:
|
|
break;
|
|
|
|
case NS_ERROR_DOM_BAD_URI:
|
|
// This is actually a security error.
|
|
aLoadResult = NS_ERROR_DOM_SECURITY_ERR;
|
|
break;
|
|
|
|
default:
|
|
// For lack of anything better, go ahead and throw a NetworkError here.
|
|
// We don't want to throw a JS exception, because for toplevel script
|
|
// loads that would get squelched.
|
|
aRv.ThrowDOMException(NS_ERROR_DOM_NETWORK_ERR,
|
|
nsPrintfCString("Failed to load worker script at %s (nsresult = 0x%x)",
|
|
NS_ConvertUTF16toUTF8(aScriptURL).get(),
|
|
aLoadResult));
|
|
return;
|
|
}
|
|
|
|
aRv.ThrowDOMException(aLoadResult,
|
|
NS_LITERAL_CSTRING("Failed to load worker script at \"") +
|
|
NS_ConvertUTF16toUTF8(aScriptURL) +
|
|
NS_LITERAL_CSTRING("\""));
|
|
}
|
|
|
|
void
|
|
LoadMainScript(WorkerPrivate* aWorkerPrivate,
|
|
const nsAString& aScriptURL,
|
|
WorkerScriptType aWorkerScriptType,
|
|
ErrorResult& aRv)
|
|
{
|
|
nsTArray<ScriptLoadInfo> loadInfos;
|
|
|
|
ScriptLoadInfo* info = loadInfos.AppendElement();
|
|
info->mURL = aScriptURL;
|
|
|
|
LoadAllScripts(aWorkerPrivate, loadInfos, true, aWorkerScriptType, aRv);
|
|
}
|
|
|
|
void
|
|
Load(WorkerPrivate* aWorkerPrivate,
|
|
const nsTArray<nsString>& aScriptURLs, WorkerScriptType aWorkerScriptType,
|
|
ErrorResult& aRv)
|
|
{
|
|
const uint32_t urlCount = aScriptURLs.Length();
|
|
|
|
if (!urlCount) {
|
|
return;
|
|
}
|
|
|
|
if (urlCount > MAX_CONCURRENT_SCRIPTS) {
|
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
|
return;
|
|
}
|
|
|
|
nsTArray<ScriptLoadInfo> loadInfos;
|
|
loadInfos.SetLength(urlCount);
|
|
|
|
for (uint32_t index = 0; index < urlCount; index++) {
|
|
loadInfos[index].mURL = aScriptURLs[index];
|
|
}
|
|
|
|
LoadAllScripts(aWorkerPrivate, loadInfos, false, aWorkerScriptType, aRv);
|
|
}
|
|
|
|
} // namespace scriptloader
|
|
|
|
END_WORKERS_NAMESPACE
|