Bug 1764598 - LoadHandler Part 1: Move onStreamComplete and onStartRequest methods from WorkerScriptLoader to LoaderListener; r=asuth
The LoaderListener is currently used to forward events to WorkerScriptLoader. It fills a similar role to the ScriptLoadHandler on the DOM both for OnStartRequest and OnStreamComplete. In the case of the DOM loader, the responsibility for performing certain channel related tasks is left to the ScriptLoadHandler, in particular the decoding of the string into UTF8 or UTF16. While the decoding is embedded in the onStreamCompleteInternal method, it is not a 1:1 mapping. Some of the responsibilities handled in the `OnStreamCompleteInternal` method are instead handled in `ScriptLoader::PrepareLoadedRequest`, such as populating the SourceMaps field, managing the channel, etc are normally handled within the ScriptLoader. However, in the case of the WorkerScriptLoader we have two paths to reach a completed load: * from the network * from the cache These have different requirements in terms of preparing the loaded request, for example the cache does not forward a SourceMap. In the interest of not complicating things too much, for now the method is moved wholesale into LoaderListener. In a separate PR I will handle renaming. The next step will be to do a similar move for the CacheScriptLoader. Differential Revision: https://phabricator.services.mozilla.com/D144836
This commit is contained in:
@@ -538,19 +538,22 @@ class LoaderListener final : public nsIStreamLoaderObserver,
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
LoaderListener(WorkerScriptLoader* aLoader, ScriptLoadInfo& aLoadInfo)
|
||||
: mLoader(aLoader), mLoadInfo(aLoadInfo) {
|
||||
MOZ_ASSERT(mLoader);
|
||||
}
|
||||
LoaderListener(WorkerScriptLoader* aLoader, ScriptLoadInfo& aLoadInfo);
|
||||
|
||||
NS_IMETHOD
|
||||
OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
|
||||
nsresult aStatus, uint32_t aStringLen,
|
||||
const uint8_t* aString) override;
|
||||
|
||||
nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus,
|
||||
uint32_t aStringLen,
|
||||
const uint8_t* aString);
|
||||
|
||||
NS_IMETHOD
|
||||
OnStartRequest(nsIRequest* aRequest) override;
|
||||
|
||||
nsresult OnStartRequestInternal(nsIRequest* aRequest);
|
||||
|
||||
NS_IMETHOD
|
||||
OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) override {
|
||||
// Nothing to do here!
|
||||
@@ -561,6 +564,7 @@ class LoaderListener final : public nsIStreamLoaderObserver,
|
||||
~LoaderListener() = default;
|
||||
|
||||
RefPtr<WorkerScriptLoader> mLoader;
|
||||
WorkerPrivate* const mWorkerPrivate;
|
||||
ScriptLoadInfo& mLoadInfo;
|
||||
};
|
||||
|
||||
@@ -802,128 +806,10 @@ class WorkerScriptLoader final : public nsINamed {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult OnStreamComplete(nsIStreamLoader* aLoader, ScriptLoadInfo& aLoadInfo,
|
||||
nsresult aStatus, uint32_t aStringLen,
|
||||
const uint8_t* aString) {
|
||||
nsresult OnStreamComplete(ScriptLoadInfo& aLoadInfo, nsresult aStatus) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen,
|
||||
aString, aLoadInfo);
|
||||
LoadingFinished(aLoadInfo, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult OnStartRequest(nsIRequest* aRequest, ScriptLoadInfo& aLoadInfo) {
|
||||
nsresult rv = OnStartRequestInternal(aRequest, aLoadInfo);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRequest->Cancel(rv);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult OnStartRequestInternal(nsIRequest* aRequest,
|
||||
ScriptLoadInfo& aLoadInfo) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
// If one load info cancels or hits an error, it can race with the start
|
||||
// callback coming from another load info.
|
||||
if (mCanceledMainThread || !mCacheCreator) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
||||
|
||||
// Checking the MIME type is only required for ServiceWorkers'
|
||||
// importScripts, per step 10 of
|
||||
// https://w3c.github.io/ServiceWorker/#importscripts
|
||||
//
|
||||
// "Extract a MIME type from the response’s header list. If this MIME type
|
||||
// (ignoring parameters) is not a JavaScript MIME type, return a network
|
||||
// error."
|
||||
if (mWorkerPrivate->IsServiceWorker()) {
|
||||
nsAutoCString mimeType;
|
||||
channel->GetContentType(mimeType);
|
||||
|
||||
if (!nsContentUtils::IsJavascriptMIMEType(
|
||||
NS_ConvertUTF8toUTF16(mimeType))) {
|
||||
const nsCString& scope =
|
||||
mWorkerPrivate->GetServiceWorkerRegistrationDescriptor().Scope();
|
||||
|
||||
ServiceWorkerManager::LocalizeAndReportToAllClients(
|
||||
scope, "ServiceWorkerRegisterMimeTypeError2",
|
||||
nsTArray<nsString>{NS_ConvertUTF8toUTF16(scope),
|
||||
NS_ConvertUTF8toUTF16(mimeType),
|
||||
aLoadInfo.mURL});
|
||||
|
||||
return NS_ERROR_DOM_NETWORK_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that importScripts() can redirect. In theory the main
|
||||
// script could also encounter an internal redirect, but currently
|
||||
// the assert does not allow that.
|
||||
MOZ_ASSERT_IF(mIsMainScript, channel == aLoadInfo.mChannel);
|
||||
aLoadInfo.mChannel = channel;
|
||||
|
||||
// We synthesize the result code, but its never exposed to content.
|
||||
SafeRefPtr<mozilla::dom::InternalResponse> ir =
|
||||
MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns);
|
||||
ir->SetBody(aLoadInfo.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.
|
||||
aLoadInfo.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;
|
||||
MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
|
||||
getter_AddRefs(channelPrincipal)));
|
||||
|
||||
UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
|
||||
MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
|
||||
|
||||
ir->SetPrincipalInfo(std::move(principalInfo));
|
||||
ir->Headers()->FillResponseHeaders(aLoadInfo.mChannel);
|
||||
|
||||
RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response(
|
||||
mCacheCreator->Global(), std::move(ir), nullptr);
|
||||
|
||||
mozilla::dom::RequestOrUSVString request;
|
||||
|
||||
MOZ_ASSERT(!aLoadInfo.mFullURL.IsEmpty());
|
||||
request.SetAsUSVString().ShareOrDependUpon(aLoadInfo.mFullURL);
|
||||
|
||||
// This JSContext will not end up executing JS code because here there are
|
||||
// no ReadableStreams involved.
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
|
||||
ErrorResult error;
|
||||
RefPtr<Promise> cachePromise =
|
||||
mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error);
|
||||
error.WouldReportJSException();
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
RefPtr<CachePromiseHandler> promiseHandler =
|
||||
new CachePromiseHandler(this, aLoadInfo);
|
||||
cachePromise->AppendNativeHandler(promiseHandler);
|
||||
|
||||
aLoadInfo.mCachePromise.swap(cachePromise);
|
||||
aLoadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
|
||||
|
||||
LoadingFinished(aLoadInfo, aStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -1224,209 +1110,6 @@ class WorkerScriptLoader final : public nsINamed {
|
||||
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();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (IsMainWorkerScript()) {
|
||||
nsCOMPtr<nsIPrincipal> loadingPrincipal =
|
||||
mWorkerPrivate->GetLoadingPrincipal();
|
||||
// if we are not in a ServiceWorker, and the principal is not null, then
|
||||
// the loading principal must subsume the worker principal if it is not a
|
||||
// nullPrincipal (sandbox).
|
||||
MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() ||
|
||||
principal->GetIsNullPrincipal() ||
|
||||
loadingPrincipal->Subsumes(principal));
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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() &&
|
||||
!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;
|
||||
}
|
||||
|
||||
Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
|
||||
tCspHeaderValue);
|
||||
|
||||
Unused << httpChannel->GetResponseHeader(
|
||||
"content-security-policy-report-only"_ns, tCspROHeaderValue);
|
||||
|
||||
Unused << httpChannel->GetResponseHeader("referrer-policy"_ns,
|
||||
tRPHeaderCValue);
|
||||
|
||||
nsAutoCString sourceMapURL;
|
||||
if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
|
||||
aLoadInfo.mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
|
||||
}
|
||||
}
|
||||
|
||||
// May be null.
|
||||
Document* parentDoc = mWorkerPrivate->GetDocument();
|
||||
|
||||
// Use the regular ScriptLoader for this grunt work! Should be just fine
|
||||
// because we're running on the main thread.
|
||||
// Worker scripts are always decoded as UTF-8 per spec. Passing null for a
|
||||
// channel and UTF-8 for the hint will always interpret |aString| as UTF-8.
|
||||
if (StaticPrefs::dom_worker_script_loader_utf8_parsing_enabled()) {
|
||||
aLoadInfo.InitUTF8Script();
|
||||
rv = ScriptLoader::ConvertToUTF8(
|
||||
nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
|
||||
aLoadInfo.mScript.mUTF8, aLoadInfo.mScriptLength);
|
||||
} else {
|
||||
aLoadInfo.InitUTF16Script();
|
||||
rv = ScriptLoader::ConvertToUTF16(
|
||||
nullptr, aString, aStringLen, u"UTF-8"_ns, parentDoc,
|
||||
aLoadInfo.mScript.mUTF16, aLoadInfo.mScriptLength);
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (aLoadInfo.ScriptTextIsNull()) {
|
||||
if (aLoadInfo.mScriptLength != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsContentUtils::ReportToConsole(
|
||||
nsIScriptError::warningFlag, "DOM"_ns, parentDoc,
|
||||
nsContentUtils::eDOM_PROPERTIES, "EmptyWorkerSourceWarning");
|
||||
}
|
||||
|
||||
// Figure out what we actually loaded.
|
||||
nsCOMPtr<nsIURI> finalURI;
|
||||
rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (principal->IsSameOrigin(finalURI)) {
|
||||
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);
|
||||
|
||||
// Our final channel principal should match the loading principal
|
||||
// in terms of the origin. This used to be an assert, but it seems
|
||||
// there are some rare cases where this check can fail in practice.
|
||||
// Perhaps some browser script setting nsIChannel.owner, etc.
|
||||
NS_ENSURE_TRUE(mWorkerPrivate->FinalChannelPrincipalIsValid(channel),
|
||||
NS_ERROR_FAILURE);
|
||||
|
||||
// However, we must still override the principal since the nsIPrincipal
|
||||
// URL may be different due to same-origin redirects. Unfortunately this
|
||||
// URL must exactly match the final worker script URL in order to
|
||||
// properly set the referrer header on fetch/xhr requests. If bug 1340694
|
||||
// is ever fixed this can be removed.
|
||||
rv = mWorkerPrivate->SetPrincipalsAndCSPFromChannel(channel);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerPrivate->GetCSP();
|
||||
// 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 (!csp) {
|
||||
rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
|
||||
tCspROHeaderValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
csp->EnsureEventTarget(mWorkerPrivate->MainThreadEventTarget());
|
||||
}
|
||||
|
||||
mWorkerPrivate->UpdateReferrerInfoFromHeader(tRPHeaderCValue);
|
||||
|
||||
WorkerPrivate* parent = mWorkerPrivate->GetParent();
|
||||
if (parent) {
|
||||
// XHR Params Allowed
|
||||
mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo();
|
||||
if (chanLoadInfo) {
|
||||
mController = chanLoadInfo->GetController();
|
||||
}
|
||||
|
||||
// If we are loading a blob URL we must inherit the controller
|
||||
// from the parent. This is a bit odd as the blob URL may have
|
||||
// been created in a different context with a different controller.
|
||||
// For now, though, this is what the spec says. See:
|
||||
//
|
||||
// https://github.com/w3c/ServiceWorker/issues/1261
|
||||
//
|
||||
if (IsBlobURI(mWorkerPrivate->GetBaseURI())) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mController.isNothing());
|
||||
mController = mWorkerPrivate->GetParentController();
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void DataReceivedFromCache(ScriptLoadInfo& aLoadInfo, const uint8_t* aString,
|
||||
uint32_t aStringLen,
|
||||
const mozilla::dom::ChannelInfo& aChannelInfo,
|
||||
@@ -1773,17 +1456,336 @@ bool WorkerScriptLoader::DispatchLoadScripts() {
|
||||
return true;
|
||||
}
|
||||
|
||||
LoaderListener::LoaderListener(WorkerScriptLoader* aLoader,
|
||||
ScriptLoadInfo& aLoadInfo)
|
||||
: mLoader(aLoader),
|
||||
mWorkerPrivate(aLoader->mWorkerPrivate),
|
||||
mLoadInfo(aLoadInfo) {
|
||||
MOZ_ASSERT(mLoader);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LoaderListener::OnStreamComplete(nsIStreamLoader* aLoader,
|
||||
nsISupports* aContext, nsresult aStatus,
|
||||
uint32_t aStringLen, const uint8_t* aString) {
|
||||
return mLoader->OnStreamComplete(aLoader, mLoadInfo, aStatus, aStringLen,
|
||||
aString);
|
||||
nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen, aString);
|
||||
return mLoader->OnStreamComplete(mLoadInfo, rv);
|
||||
}
|
||||
|
||||
nsresult LoaderListener::OnStreamCompleteInternal(nsIStreamLoader* aLoader,
|
||||
nsresult aStatus,
|
||||
uint32_t aStringLen,
|
||||
const uint8_t* aString) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
if (!mLoadInfo.mChannel) {
|
||||
return NS_BINDING_ABORTED;
|
||||
}
|
||||
|
||||
mLoadInfo.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();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if (mLoader->IsMainWorkerScript()) {
|
||||
nsCOMPtr<nsIPrincipal> loadingPrincipal =
|
||||
mWorkerPrivate->GetLoadingPrincipal();
|
||||
// if we are not in a ServiceWorker, and the principal is not null, then
|
||||
// the loading principal must subsume the worker principal if it is not a
|
||||
// nullPrincipal (sandbox).
|
||||
MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() ||
|
||||
principal->GetIsNullPrincipal() ||
|
||||
loadingPrincipal->Subsumes(principal));
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
mLoadInfo.mMutedErrorFlag.emplace(!mLoader->IsMainWorkerScript() &&
|
||||
!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;
|
||||
}
|
||||
|
||||
Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
|
||||
tCspHeaderValue);
|
||||
|
||||
Unused << httpChannel->GetResponseHeader(
|
||||
"content-security-policy-report-only"_ns, tCspROHeaderValue);
|
||||
|
||||
Unused << httpChannel->GetResponseHeader("referrer-policy"_ns,
|
||||
tRPHeaderCValue);
|
||||
|
||||
nsAutoCString sourceMapURL;
|
||||
if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
|
||||
mLoadInfo.mSourceMapURL = Some(NS_ConvertUTF8toUTF16(sourceMapURL));
|
||||
}
|
||||
}
|
||||
|
||||
// May be null.
|
||||
Document* parentDoc = mWorkerPrivate->GetDocument();
|
||||
|
||||
// Use the regular ScriptLoader for this grunt work! Should be just fine
|
||||
// because we're running on the main thread.
|
||||
// Worker scripts are always decoded as UTF-8 per spec. Passing null for a
|
||||
// channel and UTF-8 for the hint will always interpret |aString| as UTF-8.
|
||||
if (StaticPrefs::dom_worker_script_loader_utf8_parsing_enabled()) {
|
||||
mLoadInfo.InitUTF8Script();
|
||||
rv = ScriptLoader::ConvertToUTF8(nullptr, aString, aStringLen, u"UTF-8"_ns,
|
||||
parentDoc, mLoadInfo.mScript.mUTF8,
|
||||
mLoadInfo.mScriptLength);
|
||||
} else {
|
||||
mLoadInfo.InitUTF16Script();
|
||||
rv = ScriptLoader::ConvertToUTF16(nullptr, aString, aStringLen, u"UTF-8"_ns,
|
||||
parentDoc, mLoadInfo.mScript.mUTF16,
|
||||
mLoadInfo.mScriptLength);
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mLoadInfo.ScriptTextIsNull()) {
|
||||
if (mLoadInfo.mScriptLength != 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
|
||||
parentDoc, nsContentUtils::eDOM_PROPERTIES,
|
||||
"EmptyWorkerSourceWarning");
|
||||
}
|
||||
|
||||
// Figure out what we actually loaded.
|
||||
nsCOMPtr<nsIURI> finalURI;
|
||||
rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (principal->IsSameOrigin(finalURI)) {
|
||||
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.
|
||||
mLoadInfo.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 (mLoader->IsMainWorkerScript()) {
|
||||
// Take care of the base URI first.
|
||||
mWorkerPrivate->SetBaseURI(finalURI);
|
||||
|
||||
// Store the channel info if needed.
|
||||
mWorkerPrivate->InitChannelInfo(channel);
|
||||
|
||||
// Our final channel principal should match the loading principal
|
||||
// in terms of the origin. This used to be an assert, but it seems
|
||||
// there are some rare cases where this check can fail in practice.
|
||||
// Perhaps some browser script setting nsIChannel.owner, etc.
|
||||
NS_ENSURE_TRUE(mWorkerPrivate->FinalChannelPrincipalIsValid(channel),
|
||||
NS_ERROR_FAILURE);
|
||||
|
||||
// However, we must still override the principal since the nsIPrincipal
|
||||
// URL may be different due to same-origin redirects. Unfortunately this
|
||||
// URL must exactly match the final worker script URL in order to
|
||||
// properly set the referrer header on fetch/xhr requests. If bug 1340694
|
||||
// is ever fixed this can be removed.
|
||||
rv = mWorkerPrivate->SetPrincipalsAndCSPFromChannel(channel);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerPrivate->GetCSP();
|
||||
// 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 (!csp) {
|
||||
rv = mWorkerPrivate->SetCSPFromHeaderValues(tCspHeaderValue,
|
||||
tCspROHeaderValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
csp->EnsureEventTarget(mWorkerPrivate->MainThreadEventTarget());
|
||||
}
|
||||
|
||||
mWorkerPrivate->UpdateReferrerInfoFromHeader(tRPHeaderCValue);
|
||||
|
||||
WorkerPrivate* parent = mWorkerPrivate->GetParent();
|
||||
if (parent) {
|
||||
// XHR Params Allowed
|
||||
mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo();
|
||||
if (chanLoadInfo) {
|
||||
mLoader->mController = chanLoadInfo->GetController();
|
||||
}
|
||||
|
||||
// If we are loading a blob URL we must inherit the controller
|
||||
// from the parent. This is a bit odd as the blob URL may have
|
||||
// been created in a different context with a different controller.
|
||||
// For now, though, this is what the spec says. See:
|
||||
//
|
||||
// https://github.com/w3c/ServiceWorker/issues/1261
|
||||
//
|
||||
if (IsBlobURI(mWorkerPrivate->GetBaseURI())) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mLoader->mController.isNothing());
|
||||
mLoader->mController = mWorkerPrivate->GetParentController();
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
LoaderListener::OnStartRequest(nsIRequest* aRequest) {
|
||||
return mLoader->OnStartRequest(aRequest, mLoadInfo);
|
||||
nsresult rv = OnStartRequestInternal(aRequest);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRequest->Cancel(rv);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult LoaderListener::OnStartRequestInternal(nsIRequest* aRequest) {
|
||||
AssertIsOnMainThread();
|
||||
|
||||
// If one load info cancels or hits an error, it can race with the start
|
||||
// callback coming from another load info.
|
||||
if (mLoader->mCanceledMainThread || !mLoader->mCacheCreator) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
|
||||
|
||||
// Checking the MIME type is only required for ServiceWorkers'
|
||||
// importScripts, per step 10 of
|
||||
// https://w3c.github.io/ServiceWorker/#importscripts
|
||||
//
|
||||
// "Extract a MIME type from the response’s header list. If this MIME type
|
||||
// (ignoring parameters) is not a JavaScript MIME type, return a network
|
||||
// error."
|
||||
if (mWorkerPrivate->IsServiceWorker()) {
|
||||
nsAutoCString mimeType;
|
||||
channel->GetContentType(mimeType);
|
||||
|
||||
if (!nsContentUtils::IsJavascriptMIMEType(
|
||||
NS_ConvertUTF8toUTF16(mimeType))) {
|
||||
const nsCString& scope =
|
||||
mWorkerPrivate->GetServiceWorkerRegistrationDescriptor().Scope();
|
||||
|
||||
ServiceWorkerManager::LocalizeAndReportToAllClients(
|
||||
scope, "ServiceWorkerRegisterMimeTypeError2",
|
||||
nsTArray<nsString>{NS_ConvertUTF8toUTF16(scope),
|
||||
NS_ConvertUTF8toUTF16(mimeType), mLoadInfo.mURL});
|
||||
|
||||
return NS_ERROR_DOM_NETWORK_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
// Note that importScripts() can redirect. In theory the main
|
||||
// script could also encounter an internal redirect, but currently
|
||||
// the assert does not allow that.
|
||||
MOZ_ASSERT_IF(mLoader->mIsMainScript, channel == mLoadInfo.mChannel);
|
||||
mLoadInfo.mChannel = channel;
|
||||
|
||||
// We synthesize the result code, but its never exposed to content.
|
||||
SafeRefPtr<mozilla::dom::InternalResponse> ir =
|
||||
MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns);
|
||||
ir->SetBody(mLoadInfo.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.
|
||||
mLoadInfo.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;
|
||||
MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
|
||||
getter_AddRefs(channelPrincipal)));
|
||||
|
||||
UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
|
||||
MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
|
||||
|
||||
ir->SetPrincipalInfo(std::move(principalInfo));
|
||||
ir->Headers()->FillResponseHeaders(mLoadInfo.mChannel);
|
||||
|
||||
RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response(
|
||||
mLoader->mCacheCreator->Global(), std::move(ir), nullptr);
|
||||
|
||||
mozilla::dom::RequestOrUSVString request;
|
||||
|
||||
MOZ_ASSERT(!mLoadInfo.mFullURL.IsEmpty());
|
||||
request.SetAsUSVString().ShareOrDependUpon(mLoadInfo.mFullURL);
|
||||
|
||||
// This JSContext will not end up executing JS code because here there are
|
||||
// no ReadableStreams involved.
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
|
||||
ErrorResult error;
|
||||
RefPtr<Promise> cachePromise = mLoader->mCacheCreator->Cache_()->Put(
|
||||
jsapi.cx(), request, *response, error);
|
||||
error.WouldReportJSException();
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
||||
RefPtr<CachePromiseHandler> promiseHandler =
|
||||
new CachePromiseHandler(mLoader, mLoadInfo);
|
||||
cachePromise->AppendNativeHandler(promiseHandler);
|
||||
|
||||
mLoadInfo.mCachePromise.swap(cachePromise);
|
||||
mLoadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void CachePromiseHandler::ResolvedCallback(JSContext* aCx,
|
||||
|
||||
Reference in New Issue
Block a user