/* 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/. */
// // To enable logging (see mozilla/Logging.h for full details): // // set MOZ_LOG=EarlyHint:5 // set MOZ_LOG_FILE=earlyhint.log // // this enables LogLevel::Debug level information and places all output in // the file earlyhint.log // static mozilla::LazyLogModule gEarlyHintLog("EarlyHint");
namespace { // This id uniquely identifies each early hint preloader in the // EarlyHintRegistrar. Must only be accessed from main thread. static uint64_t gEarlyHintPreloaderId{0};
} // namespace
if (!StaticPrefs::network_early_hints_enabled()) { return;
}
if (destination == ASDestination::DESTINATION_INVALID && !aIsModulepreload) { // return early when it's definitly not an asset type we preload // would be caught later as well, e.g. when creating the PreloadHashKey return;
}
if (destination == ASDestination::DESTINATION_FONT &&
!gfxPlatform::GetPlatform()->DownloadableFontsEnabled()) { return;
}
nsCOMPtr<nsIURI> uri;
NS_ENSURE_SUCCESS_VOID(
NS_NewURI(getter_AddRefs(uri), aLinkHeader.mHref, nullptr, aBaseURI)); // The link relation may apply to a different resource, specified // in the anchor parameter. For the link relations supported so far, // we simply abort if the link applies to a resource different to the // one we've loaded if (!nsContentUtils::LinkContextIsURI(aLinkHeader.mAnchor, uri)) { return;
}
// only preload secure context urls if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri)) { return;
}
// The early hint may have two referrer policies, one from the response header // and one from the link element. // // For example, in this server response: // HTTP/1.1 103 Early Hints // Referrer-Policy : origin // Link: </style.css>; rel=preload; as=style referrerpolicy=no-referrer // // The link header referrer policy, if present, will take precedence over // the response referrer policy
dom::ReferrerPolicy finalReferrerPolicy = responseReferrerPolicy; if (linkReferrerPolicy != dom::ReferrerPolicy::_empty) {
finalReferrerPolicy = linkReferrerPolicy;
}
nsCOMPtr<nsIReferrerInfo> referrerInfo = new dom::ReferrerInfo(aBaseURI, finalReferrerPolicy);
RefPtr<EarlyHintPreloader> earlyHintPreloader = new EarlyHintPreloader();
// Security flags for modulepreload's request mode are computed here directly // until full support for worker destinations can be added. // // Implements "To fetch a single module script," // Step 9. If destination is "worker", "sharedworker", or "serviceworker", // and the top-level module fetch flag is set, then set request's // mode to "same-origin".
nsSecurityFlags securityFlags =
aIsModulepreload
? ((aLinkHeader.mAs.LowerCaseEqualsASCII("worker") ||
aLinkHeader.mAs.LowerCaseEqualsASCII("sharedworker") ||
aLinkHeader.mAs.LowerCaseEqualsASCII("serviceworker"))
? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
: nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT) |
(corsMode == CORS_USE_CREDENTIALS
? nsILoadInfo::SEC_COOKIES_INCLUDE
: nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) |
nsILoadInfo::SEC_ALLOW_CHROME
: EarlyHintPreloader::ComputeSecurityFlags(corsMode, destination);
// Verify that the resource should be loaded. // This isn't the ideal way to test the resource against the CSP. // The problem comes from the fact that at the stage of Early Hint // processing we have not yet created a document where we would normally store // the CSP.
// First we will create a load info, // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
aPrincipal, // loading principal
aPrincipal, // triggering principal
nullptr /* aLoadingContext node */,
nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, contentPolicyType);
if (aCSPHeader.Length() != 0) { // If the CSP header is present then create a new CSP and apply the header // directives to it
nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();
nsresult rv = csp->SetRequestContextWithPrincipal(
aPrincipal, aBaseURI, ""_ns, 0 /* aInnerWindowId */);
NS_ENSURE_SUCCESS_VOID(rv);
rv = CSP_AppendCSPFromHeader(csp, NS_ConvertUTF8toUTF16(aCSPHeader), false/* report only */);
NS_ENSURE_SUCCESS_VOID(rv);
// We create a temporary ClientInfo. This is required on the loadInfo as // that is how the CSP is queried. More specificially, as a hack to be able // to call NS_CheckContentLoadPolicy on nsILoadInfo which exclusively // accesses the CSP from the ClientInfo, we create a synthetic ClientInfo to // hold the CSP we are creating. This is not a safe thing to do in any other // circumstance because ClientInfos are always describing a ClientSource // that corresponds to a global or potential global, so creating an info // without a source is unsound. For the purposes of doing things before a // global exists, fetch has the concept of a // https://fetch.spec.whatwg.org/#concept-request-reserved-client and // nsILoadInfo explicity has methods around GiveReservedClientSource which // are primarily used by ClientChannelHelper. If you are trying to do real // CSP stuff and the ClientInfo is not there yet, please enhance the logic // around ClientChannelHelper.
// Our newly-created CSP is set on the ClientInfo via the indirect route of // first serializing to CSPInfo
ipc::CSPInfo cspInfo;
rv = CSPToCSPInfo(csp, &cspInfo);
NS_ENSURE_SUCCESS_VOID(rv);
clientInfo.SetCspInfo(cspInfo);
// This ClientInfo is then set on the new loadInfo. // It can now be used to test the resource against the policy
secCheckLoadInfo->SetClientInfo(clientInfo);
}
mParentListener = new ParentChannelListener(this, nullptr);
PriorizeAsPreload();
if (nsCOMPtr<nsIRaceCacheWithNetwork> rcwn = do_QueryInterface(httpChannel)) { // Since this is an early hint, we should consult the cache first.
rcwn->SetAllowRacing(false);
}
// Setting the BrowsingContextID here to let Early Hint requests show up in // devtools. Normally that would automatically happen if we would pass the // nsILoadGroup in ns_NewChannel above, but the nsILoadGroup is inaccessible // here in the ParentProcess. The nsILoadGroup only exists in ContentProcess // as part of the document and nsDocShell. It is also not yet determined which // ContentProcess this load belongs to.
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); static_cast<LoadInfo*>(loadInfo.get())
->UpdateBrowsingContextID(aBrowsingContextID);
// Set minimum delay of 1ms to always start the timer after the function call // completed.
nsresult rv = NS_NewTimerWithCallback(
getter_AddRefs(mTimer), this,
std::max(StaticPrefs::network_early_hints_parent_connect_timeout(),
(uint32_t)1),
nsITimer::TYPE_ONE_SHOT); if (NS_FAILED(rv)) {
MOZ_ASSERT(!mTimer);
CancelChannel(NS_ERROR_ABORT, "new-timer-failed"_ns, /* aDeleteEntry */ false); returnfalse;
}
// Create an entry in the redirect channel registrar
RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
registrar->RegisterEarlyHint(mConnectArgs.earlyHintPreloaderId(), this);
if (mTimer) {
mTimer->Cancel();
mTimer = nullptr;
} if (aDeleteEntry) {
RefPtr<EarlyHintRegistrar> registrar = EarlyHintRegistrar::GetOrCreate();
registrar->DeleteEntry(mCpId, mConnectArgs.earlyHintPreloaderId());
} // clear redirect channel in case this channel is cleared between the call of // EarlyHintPreloader::AsyncOnChannelRedirect and // EarlyHintPreloader::OnRedirectResult
mRedirectChannel = nullptr; if (mChannel) { if (mSuspended) {
mChannel->Resume();
}
mChannel->CancelWithReason(aStatus, aReason); // Clearing mChannel is safe, because this EarlyHintPreloader is not in the // EarlyHintRegistrar after this function call and we won't call // SetHttpChannelFromEarlyHintPreloader nor OnStartRequest on mParent.
mChannel = nullptr;
SetState(ePreloaderCancelled);
} return NS_OK;
}
// If we failed to suspend the channel, then we might have received // some messages while the redirected was being handled. // Manually send them on now. if (!mIsFinished) { // This is safe to do, because OnStartRequest/OnStopRequest/OnDataAvailable // are all called on the main thread. They can't be called until we worked // through all functions in the streamListnerFunctions array.
mParentListener->SetListenerAfterRedirect(mParent);
}
nsTArray<StreamListenerFunction> streamListenerFunctions =
std::move(mStreamListenerFunctions);
// We don't expect to get new stream listener functions added // via re-entrancy. If this ever happens, we should understand // exactly why before allowing it.
NS_ASSERTION(mStreamListenerFunctions.IsEmpty(), "Should not have added new stream listener function!");
nsresult status = NS_OK;
Unused << aRequest->GetStatus(&status);
if (mParent) {
SetParentChannel();
mParent->OnStartRequest(aRequest);
InvokeStreamListenerFunctions();
} else { // Don't suspend the chanel when the channel got cancelled with // CancelChannel, because then OnStopRequest wouldn't get called and we // wouldn't clean up the channel. if (NS_SUCCEEDED(status)) {
mChannel->Suspend();
mSuspended = true;
}
mStreamListenerFunctions.AppendElement(
AsVariant(OnStartRequestParams{aRequest}));
}
// return error after adding the OnStartRequest forward. The OnStartRequest // failure has to be forwarded to listener, because they called AsyncOpen on // this channel return status;
}
// If we're not a multi-part channel, then we're finished and we don't // expect any further events. If we are, then this might be called again, // so wait for OnAfterLastPart instead.
nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); if (!multiPartChannel) {
mIsFinished = true;
}
// Implementation copied from DocumentLoadListener::OnDataAvailable // https://searchfox.org/mozilla-central/rev/380fc5571b039fd453b45bbb64ed13146fe9b066/netwerk/ipc/DocumentLoadListener.cpp#2530-2549
NS_IMETHODIMP
EarlyHintPreloader::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) {
AssertIsOnMainThread();
LOG(("EarlyHintPreloader::OnDataAvailable [this=%p]\n", this)); // This isn't supposed to happen, since we suspended the channel, but // sometimes Suspend just doesn't work. This can happen when we're routing // through nsUnknownDecoder to sniff the content type, and it doesn't handle // being suspended. Let's just store the data and manually forward it to our // redirected channel when it's ready.
nsCString data;
nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
NS_ENSURE_SUCCESS(rv, rv);
rv = aNewChannel->GetURI(getter_AddRefs(newURI)); if (NS_FAILED(rv)) {
callback->OnRedirectVerifyCallback(rv); return NS_OK;
}
// HTTP request headers are not automatically forwarded to the new channel.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel);
NS_ENSURE_STATE(httpChannel);
NS_IMETHODIMP
EarlyHintPreloader::Notify(nsITimer* timer) { // Death grip, because we will most likely remove the last reference when // deleting us from the EarlyHintRegistrar
RefPtr<EarlyHintPreloader> deathGrip(this);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.