Bug 1618295 - Make imgRequestProxy implement and use PreloaderBase to support the preload as speculative load feature; r=tnikkel,smaug,mayhemer

Depends on D72083

Differential Revision: https://phabricator.services.mozilla.com/D69860
This commit is contained in:
Edgar Chen 2020-05-11 14:43:05 +00:00
parent ae65cbe96e
commit 3f250fba44
23 changed files with 352 additions and 78 deletions

View File

@ -11310,16 +11310,9 @@ already_AddRefed<nsIURI> Document::ResolvePreloadImage(
return uri.forget();
}
void Document::MaybePreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
enum ReferrerPolicy aReferrerPolicy,
bool aIsImgSet) {
// Early exit if the img is already present in the img-cache
// which indicates that the "real" load has already started and
// that we shouldn't preload it.
if (nsContentUtils::IsImageInCache(uri, this)) {
return;
}
void Document::PreLoadImage(nsIURI* aUri, const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
bool aLinkPreload) {
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL |
nsContentUtils::CORSModeToLoadImageFlags(
Element::StringToCORSMode(aCrossOriginAttr));
@ -11331,21 +11324,50 @@ void Document::MaybePreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
nsCOMPtr<nsIReferrerInfo> referrerInfo =
ReferrerInfo::CreateFromDocumentAndPolicyOverride(this, aReferrerPolicy);
// Image not in cache - trigger preload
RefPtr<imgRequestProxy> request;
nsresult rv = nsContentUtils::LoadImage(
uri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
nullptr, // no observer
loadFlags, NS_LITERAL_STRING("img"), getter_AddRefs(request), policyType);
aUri, static_cast<nsINode*>(this), this, NodePrincipal(), 0, referrerInfo,
nullptr /* no observer */, loadFlags,
aLinkPreload ? NS_LITERAL_STRING("link") : NS_LITERAL_STRING("img"),
getter_AddRefs(request), policyType, false /* urgent */, aLinkPreload);
// Pin image-reference to avoid evicting it from the img-cache before
// the "real" load occurs. Unpinned in DispatchContentLoadedEvents and
// unlink
if (NS_SUCCEEDED(rv)) {
mPreloadingImages.Put(uri, std::move(request));
if (!aLinkPreload && NS_SUCCEEDED(rv)) {
mPreloadingImages.Put(aUri, std::move(request));
}
}
void Document::MaybePreLoadImage(nsIURI* aUri,
const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy,
bool aIsImgSet, bool aLinkPreload) {
if (aLinkPreload) {
// Check if the image was already preloaded in this document to avoid
// duplicate preloading.
PreloadHashKey key = PreloadHashKey::CreateAsImage(
aUri, NodePrincipal(), dom::Element::StringToCORSMode(aCrossOriginAttr),
aReferrerPolicy);
if (!mPreloadService.PreloadExists(&key)) {
PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
aLinkPreload);
}
return;
}
// Early exit if the img is already present in the img-cache
// which indicates that the "real" load has already started and
// that we shouldn't preload it.
if (nsContentUtils::IsImageInCache(aUri, this)) {
return;
}
// Image not in cache - trigger preload
PreLoadImage(aUri, aCrossOriginAttr, aReferrerPolicy, aIsImgSet,
aLinkPreload);
}
void Document::MaybePreconnect(nsIURI* aOrigURI, mozilla::CORSMode aCORSMode) {
NS_MutateURI mutator(aOrigURI);
if (NS_FAILED(mutator.GetStatus())) {

View File

@ -2907,7 +2907,11 @@ class Document : public nsINode,
* when this image is for loading <picture> or <img srcset> images.
*/
void MaybePreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet);
ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
bool aLinkPreload);
void PreLoadImage(nsIURI* uri, const nsAString& aCrossOriginAttr,
ReferrerPolicyEnum aReferrerPolicy, bool aIsImgSet,
bool aLinkPreload);
/**
* Called by images to forget an image preload when they start doing

View File

@ -3341,7 +3341,7 @@ nsresult nsContentUtils::LoadImage(
nsIReferrerInfo* aReferrerInfo, imgINotificationObserver* aObserver,
int32_t aLoadFlags, const nsAString& initiatorType,
imgRequestProxy** aRequest, uint32_t aContentPolicyType,
bool aUseUrgentStartForChannel) {
bool aUseUrgentStartForChannel, bool aLinkPreload) {
MOZ_ASSERT(aURI, "Must have a URI");
MOZ_ASSERT(aContext, "Must have a context");
MOZ_ASSERT(aLoadingDocument, "Must have a document");
@ -3377,6 +3377,7 @@ nsresult nsContentUtils::LoadImage(
aContentPolicyType, /* content policy type */
initiatorType, /* the load initiator */
aUseUrgentStartForChannel, /* urgent-start flag */
aLinkPreload, /* <link preload> initiator */
aRequest);
}

View File

@ -921,7 +921,7 @@ class nsContentUtils {
int32_t aLoadFlags, const nsAString& initiatorType,
imgRequestProxy** aRequest,
uint32_t aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE,
bool aUseUrgentStartForChannel = false);
bool aUseUrgentStartForChannel = false, bool aLinkPreload = false);
/**
* Obtain an image loader that respects the given document/channel's privacy

View File

@ -53,6 +53,7 @@
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsQueryObject.h"
#include "nsReadableUtils.h"
#include "nsStreamUtils.h"
#include "prtime.h"
@ -1197,6 +1198,21 @@ NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
static imgLoader* gNormalLoader = nullptr;
static imgLoader* gPrivateBrowsingLoader = nullptr;
/* static */
mozilla::CORSMode imgLoader::ConvertToCORSMode(uint32_t aImgCORS) {
switch (aImgCORS) {
case imgIRequest::CORS_NONE:
return CORSMode::CORS_NONE;
case imgIRequest::CORS_ANONYMOUS:
return CORSMode::CORS_ANONYMOUS;
case imgIRequest::CORS_USE_CREDENTIALS:
return CORSMode::CORS_USE_CREDENTIALS;
}
MOZ_ASSERT(false, "Unexpected imgIRequest CORS value");
return CORSMode::CORS_NONE;
}
/* static */
already_AddRefed<imgLoader> imgLoader::CreateImageLoader() {
// In some cases, such as xpctests, XPCOM modules are not automatically
@ -1665,7 +1681,7 @@ bool imgLoader::ValidateRequestWithNewChannel(
imgINotificationObserver* aObserver, Document* aLoadingDocument,
uint64_t aInnerWindowId, nsLoadFlags aLoadFlags,
nsContentPolicyType aLoadPolicyType, imgRequestProxy** aProxyRequest,
nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode,
nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode, bool aLinkPreload,
bool* aNewChannelCreated) {
// now we need to insert a new channel request object in between the real
// request and the proxy that basically delays loading the image until it
@ -1675,7 +1691,7 @@ bool imgLoader::ValidateRequestWithNewChannel(
// If we're currently in the middle of validating this request, just hand
// back a proxy to it; the required work will be done for us.
if (request->GetValidator()) {
if (imgCacheValidator* validator = request->GetValidator()) {
rv = CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
aObserver, aLoadFlags, aProxyRequest);
if (NS_FAILED(rv)) {
@ -1691,8 +1707,18 @@ bool imgLoader::ValidateRequestWithNewChannel(
// resulting from methods such as StartDecoding(). See bug 579122.
proxy->MarkValidating();
if (aLinkPreload) {
MOZ_ASSERT(aLoadingDocument);
MOZ_ASSERT(aReferrerInfo);
proxy->PrioritizeAsPreload();
auto preloadKey = PreloadHashKey::CreateAsImage(
aURI, aTriggeringPrincipal, ConvertToCORSMode(aCORSMode),
aReferrerInfo->ReferrerPolicy());
proxy->NotifyOpen(&preloadKey, aLoadingDocument, true);
}
// Attach the proxy without notifying
request->GetValidator()->AddProxy(proxy);
validator->AddProxy(proxy);
}
return NS_SUCCEEDED(rv);
@ -1750,6 +1776,16 @@ bool imgLoader::ValidateRequestWithNewChannel(
// resulting from methods such as StartDecoding(). See bug 579122.
req->MarkValidating();
if (aLinkPreload) {
MOZ_ASSERT(aLoadingDocument);
MOZ_ASSERT(aReferrerInfo);
req->PrioritizeAsPreload();
auto preloadKey = PreloadHashKey::CreateAsImage(
aURI, aTriggeringPrincipal, ConvertToCORSMode(aCORSMode),
aReferrerInfo->ReferrerPolicy());
req->NotifyOpen(&preloadKey, aLoadingDocument, true);
}
// Add the proxy without notifying
hvc->AddProxy(req);
@ -1773,7 +1809,7 @@ bool imgLoader::ValidateEntry(
nsLoadFlags aLoadFlags, nsContentPolicyType aLoadPolicyType,
bool aCanMakeNewChannel, bool* aNewChannelCreated,
imgRequestProxy** aProxyRequest, nsIPrincipal* aTriggeringPrincipal,
int32_t aCORSMode) {
int32_t aCORSMode, bool aLinkPreload) {
LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
// If the expiration time is zero, then the request has not gotten far enough
@ -1892,7 +1928,8 @@ bool imgLoader::ValidateEntry(
return ValidateRequestWithNewChannel(
request, aURI, aInitialDocumentURI, aReferrerInfo, aLoadGroup,
aObserver, aLoadingDocument, innerWindowID, aLoadFlags, aLoadPolicyType,
aProxyRequest, aTriggeringPrincipal, aCORSMode, aNewChannelCreated);
aProxyRequest, aTriggeringPrincipal, aCORSMode, aLinkPreload,
aNewChannelCreated);
}
return !validateRequest;
@ -2051,11 +2088,11 @@ imgLoader::LoadImageXPCOM(
aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
}
imgRequestProxy* proxy;
nsresult rv =
LoadImage(aURI, aInitialDocumentURI, aReferrerInfo, aTriggeringPrincipal,
0, aLoadGroup, aObserver, aLoadingDocument, aLoadingDocument,
aLoadFlags, aCacheKey, aContentPolicyType, EmptyString(),
/* aUseUrgentStartForChannel */ false, &proxy);
nsresult rv = LoadImage(
aURI, aInitialDocumentURI, aReferrerInfo, aTriggeringPrincipal, 0,
aLoadGroup, aObserver, aLoadingDocument, aLoadingDocument, aLoadFlags,
aCacheKey, aContentPolicyType, EmptyString(),
/* aUseUrgentStartForChannel */ false, /* aListPreload */ false, &proxy);
*_retval = proxy;
return rv;
}
@ -2067,7 +2104,7 @@ nsresult imgLoader::LoadImage(
nsINode* aContext, Document* aLoadingDocument, nsLoadFlags aLoadFlags,
nsISupports* aCacheKey, nsContentPolicyType aContentPolicyType,
const nsAString& initiatorType, bool aUseUrgentStartForChannel,
imgRequestProxy** _retval) {
bool aLinkPreload, imgRequestProxy** _retval) {
VerifyCacheSizes();
NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
@ -2137,6 +2174,9 @@ nsresult imgLoader::LoadImage(
if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
// Propagate background loading...
requestFlags |= nsIRequest::LOAD_BACKGROUND;
} else if (aLinkPreload) {
// Set background loading if it is <link rel=preload>
requestFlags |= nsIRequest::LOAD_BACKGROUND;
}
int32_t corsmode = imgIRequest::CORS_NONE;
@ -2146,6 +2186,54 @@ nsresult imgLoader::LoadImage(
corsmode = imgIRequest::CORS_USE_CREDENTIALS;
}
// Look in the preloaded images of loading document first.
if (StaticPrefs::network_preload_experimental() && !aLinkPreload &&
aLoadingDocument) {
auto key = PreloadHashKey::CreateAsImage(
aURI, aTriggeringPrincipal, ConvertToCORSMode(corsmode),
aReferrerInfo ? aReferrerInfo->ReferrerPolicy()
: ReferrerPolicy::_empty);
if (RefPtr<PreloaderBase> preload =
aLoadingDocument->Preloads().LookupPreload(&key)) {
RefPtr<imgRequestProxy> proxy = do_QueryObject(preload);
MOZ_ASSERT(proxy);
MOZ_LOG(gImgLog, LogLevel::Debug,
("[this=%p] imgLoader::LoadImage -- preloaded [proxy=%p]"
" [document=%p]\n",
this, proxy.get(), aLoadingDocument));
proxy->NotifyUsage();
// XXXedgar: we link the preloaded image to all subsequent "real" load,
// where Blink seems only link the preloaded image to the next "real"
// load.
// There is a spec discussion for "preload cache", see
// https://github.com/w3c/preload/issues/97. And it is also not clear how
// preload image interacts with list of available images, see
// https://github.com/whatwg/html/issues/4474.
imgRequest* request = proxy->GetOwner();
nsresult rv =
CreateNewProxyForRequest(request, aURI, aLoadGroup, aLoadingDocument,
aObserver, requestFlags, _retval);
NS_ENSURE_SUCCESS(rv, rv);
imgRequestProxy* newProxy = *_retval;
if (imgCacheValidator* validator = request->GetValidator()) {
newProxy->MarkValidating();
// Attach the proxy without notifying and this will add us to the load
// group.
validator->AddProxy(newProxy);
} else {
// It's OK to add here even if the request is done. If it is, it'll send
// a OnStopRequest()and the proxy will be removed from the loadgroup in
// imgRequestProxy::OnLoadComplete.
newProxy->AddToLoadGroup();
newProxy->NotifyListener();
}
return NS_OK;
}
}
RefPtr<imgCacheEntry> entry;
// Look in the cache for our URI, and then validate it.
@ -2164,7 +2252,7 @@ nsresult imgLoader::LoadImage(
if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerInfo,
aLoadGroup, aObserver, aLoadingDocument, requestFlags,
aContentPolicyType, true, &newChannelCreated, _retval,
aTriggeringPrincipal, corsmode)) {
aTriggeringPrincipal, corsmode, aLinkPreload)) {
request = entry->GetRequest();
// If this entry has no proxies, its request has no reference to the
@ -2324,6 +2412,16 @@ nsresult imgLoader::LoadImage(
newChannel->SetNotificationCallbacks(requestor);
}
if (aLinkPreload) {
MOZ_ASSERT(aLoadingDocument);
MOZ_ASSERT(aReferrerInfo);
proxy->PrioritizeAsPreload();
auto preloadKey = PreloadHashKey::CreateAsImage(
aURI, aTriggeringPrincipal, ConvertToCORSMode(corsmode),
aReferrerInfo->ReferrerPolicy());
proxy->NotifyOpen(&preloadKey, aLoadingDocument, true);
}
// Note that it's OK to add here even if the request is done. If it is,
// it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
// the proxy will be removed from the loadgroup.
@ -2419,7 +2517,8 @@ nsresult imgLoader::LoadImageWithChannel(nsIChannel* channel,
if (ValidateEntry(entry, uri, nullptr, nullptr, nullptr, aObserver,
aLoadingDocument, requestFlags, policyType, false,
nullptr, nullptr, nullptr, imgIRequest::CORS_NONE)) {
nullptr, nullptr, nullptr, imgIRequest::CORS_NONE,
false)) {
request = entry->GetRequest();
} else {
nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel));
@ -2788,6 +2887,11 @@ void imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy) {
mProxies.RemoveElement(aProxy);
}
void imgCacheValidator::PrioritizeAsPreload() {
MOZ_ASSERT(mNewRequest);
mNewRequest->PrioritizeAsPreload();
}
void imgCacheValidator::UpdateProxies(bool aCancelRequest, bool aSyncNotify) {
MOZ_ASSERT(mRequest);

View File

@ -8,6 +8,8 @@
#define mozilla_image_imgLoader_h
#include "mozilla/Attributes.h"
#include "mozilla/CORSMode.h"
#include "mozilla/dom/Document.h"
#include "mozilla/Mutex.h"
#include "mozilla/UniquePtr.h"
@ -246,7 +248,8 @@ class imgLoader final : public imgILoader,
nsINode* aContext, mozilla::dom::Document* aLoadingDocument,
nsLoadFlags aLoadFlags, nsISupports* aCacheKey,
nsContentPolicyType aContentPolicyType, const nsAString& initiatorType,
bool aUseUrgentStartForChannel, imgRequestProxy** _retval);
bool aUseUrgentStartForChannel, bool aLinkPreload,
imgRequestProxy** _retval);
[[nodiscard]] nsresult LoadImageWithChannel(
nsIChannel* channel, imgINotificationObserver* aObserver,
@ -338,21 +341,23 @@ class imgLoader final : public imgILoader,
bool SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry);
bool SetHasProxies(imgRequest* aRequest);
// This method converts imgIRequest::CORS_* values to mozilla::CORSMode
// values.
static mozilla::CORSMode ConvertToCORSMode(uint32_t aImgCORS);
private: // methods
static already_AddRefed<imgLoader> CreateImageLoader();
bool PreferLoadFromCache(nsIURI* aURI) const;
bool ValidateEntry(imgCacheEntry* aEntry, nsIURI* aKey,
nsIURI* aInitialDocumentURI,
nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
imgINotificationObserver* aObserver,
mozilla::dom::Document* aLoadingDocument,
nsLoadFlags aLoadFlags,
nsContentPolicyType aContentPolicyType,
bool aCanMakeNewChannel, bool* aNewChannelCreated,
imgRequestProxy** aProxyRequest,
nsIPrincipal* aLoadingPrincipal, int32_t aCORSMode);
bool ValidateEntry(
imgCacheEntry* aEntry, nsIURI* aURI, nsIURI* aInitialDocumentURI,
nsIReferrerInfo* aReferrerInfo, nsILoadGroup* aLoadGroup,
imgINotificationObserver* aObserver,
mozilla::dom::Document* aLoadingDocument, nsLoadFlags aLoadFlags,
nsContentPolicyType aLoadPolicyType, bool aCanMakeNewChannel,
bool* aNewChannelCreated, imgRequestProxy** aProxyRequest,
nsIPrincipal* aTriggeringPrincipal, int32_t aCORSMode, bool aLinkPreload);
bool ValidateRequestWithNewChannel(
imgRequest* request, nsIURI* aURI, nsIURI* aInitialDocumentURI,
@ -361,7 +366,7 @@ class imgLoader final : public imgILoader,
mozilla::dom::Document* aLoadingDocument, uint64_t aInnerWindowId,
nsLoadFlags aLoadFlags, nsContentPolicyType aContentPolicyType,
imgRequestProxy** aProxyRequest, nsIPrincipal* aLoadingPrincipal,
int32_t aCORSMode, bool* aNewChannelCreated);
int32_t aCORSMode, bool aLinkPreload, bool* aNewChannelCreated);
// aURI may be different from imgRequest's URI in the case of blob URIs, as we
// can share requests with different URIs.
@ -481,6 +486,8 @@ class imgCacheValidator : public nsIStreamListener,
void AddProxy(imgRequestProxy* aProxy);
void RemoveProxy(imgRequestProxy* aProxy);
void PrioritizeAsPreload();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
NS_DECL_NSISTREAMLISTENER

View File

@ -18,6 +18,7 @@
#include "nsIChannel.h"
#include "nsICacheInfoChannel.h"
#include "nsIClassOfService.h"
#include "mozilla/dom/Document.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIInputStream.h"
@ -67,7 +68,9 @@ imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
mIsInCache(false),
mDecodeRequested(false),
mNewPartPending(false),
mHadInsecureRedirect(false) {
mHadInsecureRedirect(false),
mIsDeniedCrossSiteCORSRequest(false),
mIsCrossSiteNoCORSRequest(false) {
LOG_FUNC(gImgLog, "imgRequest::imgRequest()");
}
@ -642,6 +645,15 @@ imgRequest::OnStartRequest(nsIRequest* aRequest) {
RefPtr<Image> image;
if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) {
nsresult rv;
nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo();
mIsDeniedCrossSiteCORSRequest =
loadInfo->GetTainting() == LoadTainting::CORS &&
(NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv));
mIsCrossSiteNoCORSRequest = loadInfo->GetTainting() == LoadTainting::Opaque;
}
// Figure out if we're multipart.
nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel,
@ -983,6 +995,13 @@ void imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) {
bool imgRequest::ImageAvailable() const { return mImageAvailable; }
void imgRequest::PrioritizeAsPreload() {
if (nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(mChannel)) {
cos->AddClassFlags(nsIClassOfService::Unblocked);
}
AdjustPriorityInternal(nsISupportsPriority::PRIORITY_HIGHEST);
}
NS_IMETHODIMP
imgRequest::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr,
uint64_t aOffset, uint32_t aCount) {

View File

@ -201,6 +201,14 @@ class imgRequest final : public nsIStreamListener,
bool ImageAvailable() const;
void PrioritizeAsPreload();
bool IsDeniedCrossSiteCORSRequest() const {
return mIsDeniedCrossSiteCORSRequest;
}
bool IsCrossSiteNoCORSRequest() const { return mIsCrossSiteNoCORSRequest; }
private:
friend class FinishPreparingForNewPartRunnable;
@ -289,6 +297,8 @@ class imgRequest final : public nsIStreamListener,
bool mDecodeRequested : 1;
bool mNewPartPending : 1;
bool mHadInsecureRedirect : 1;
bool mIsDeniedCrossSiteCORSRequest : 1;
bool mIsCrossSiteNoCORSRequest : 1;
};
#endif // mozilla_image_imgRequest_h

View File

@ -93,10 +93,11 @@ NS_IMPL_ADDREF(imgRequestProxy)
NS_IMPL_RELEASE(imgRequestProxy)
NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase)
NS_INTERFACE_MAP_ENTRY(imgIRequest)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
NS_INTERFACE_MAP_END
@ -1035,6 +1036,22 @@ void imgRequestProxy::OnLoadComplete(bool aLastPart) {
if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
if (aLastPart) {
RemoveFromLoadGroup();
nsresult errorCode = NS_OK;
// if the load is cross origin without CORS, or the CORS access is
// rejected, always fire load event to avoid leaking site information for
// <link rel=preload>.
// XXXedgar, currently we don't do the same thing for <img>.
imgRequest* request = GetOwner();
if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
request->IsCrossSiteNoCORSRequest())) {
uint32_t status = imgIRequest::STATUS_NONE;
GetImageStatus(&status);
if (status & imgIRequest::STATUS_ERROR) {
errorCode = NS_ERROR_FAILURE;
}
}
NotifyStop(errorCode);
} else {
// More data is coming, so change the request to be a background request
// and put it back in the loadgroup.
@ -1190,6 +1207,15 @@ imgCacheValidator* imgRequestProxy::GetValidator() const {
return owner->GetValidator();
}
void imgRequestProxy::PrioritizeAsPreload() {
if (imgRequest* owner = GetOwner()) {
owner->PrioritizeAsPreload();
}
if (imgCacheValidator* validator = GetValidator()) {
validator->PrioritizeAsPreload();
}
}
////////////////// imgRequestProxyStatic methods
class StaticBehaviour : public ProxyBehaviour {

View File

@ -14,6 +14,7 @@
#include "nsITimedChannel.h"
#include "nsCOMPtr.h"
#include "nsThreadUtils.h"
#include "mozilla/PreloaderBase.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/Rect.h"
@ -40,7 +41,8 @@ class ProgressTracker;
} // namespace image
} // namespace mozilla
class imgRequestProxy : public imgIRequest,
class imgRequestProxy : public mozilla::PreloaderBase,
public imgIRequest,
public mozilla::image::IProgressObserver,
public nsISupportsPriority,
public nsITimedChannel {
@ -52,6 +54,7 @@ class imgRequestProxy : public imgIRequest,
typedef mozilla::image::Image Image;
typedef mozilla::image::ProgressTracker ProgressTracker;
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMGREQUESTPROXY_CID)
MOZ_DECLARE_REFCOUNTED_TYPENAME(imgRequestProxy)
NS_DECL_ISUPPORTS
NS_DECL_IMGIREQUEST
@ -124,6 +127,11 @@ class imgRequestProxy : public imgIRequest,
nsresult GetStaticRequest(Document* aLoadingDocument,
imgRequestProxy** aReturn);
imgRequest* GetOwner() const;
// PreloaderBase
virtual void PrioritizeAsPreload() override;
protected:
friend class mozilla::image::ProgressTracker;
friend class imgStatusNotifyRunnable;
@ -173,7 +181,6 @@ class imgRequestProxy : public imgIRequest,
already_AddRefed<Image> GetImage() const;
bool HasImage() const;
imgRequest* GetOwner() const;
imgCacheValidator* GetValidator() const;
nsresult PerformClone(imgINotificationObserver* aObserver,
@ -228,6 +235,8 @@ class imgRequestProxy : public imgIRequest,
bool mHadDispatch : 1;
};
NS_DEFINE_STATIC_IID_ACCESSOR(imgRequestProxy, NS_IMGREQUESTPROXY_CID)
// Used for static image proxies for which no requests are available, so
// certain behaviours must be overridden to compensate.
class imgRequestProxyStatic : public imgRequestProxy {

View File

@ -2473,6 +2473,7 @@ nsresult nsImageFrame::LoadIcon(const nsAString& aSpec,
nullptr, /* Not associated with any particular document */
loadFlags, nullptr, contentPolicyType, EmptyString(),
false, /* aUseUrgentStartForChannel */
false, /* aLinkPreload */
aRequest);
}

View File

@ -43,7 +43,7 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) {
aExecutor->PreloadImage(
mUrlOrSizes, mCrossOriginOrMedia, mCharsetOrSrcset,
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity,
mReferrerPolicyOrIntegrity);
mReferrerPolicyOrIntegrity, mIsLinkPreload);
break;
case eSpeculativeLoadOpenPicture:
aExecutor->PreloadOpenPicture();

View File

@ -73,7 +73,7 @@ class nsHtml5SpeculativeLoad {
inline void InitImage(nsHtml5String aUrl, nsHtml5String aCrossOrigin,
nsHtml5String aReferrerPolicy, nsHtml5String aSrcset,
nsHtml5String aSizes) {
nsHtml5String aSizes, bool aLinkPreload) {
MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized,
"Trying to reinitialize a speculative load!");
mOpCode = eSpeculativeLoadImage;
@ -88,6 +88,7 @@ class nsHtml5SpeculativeLoad {
aSrcset.ToString(mCharsetOrSrcset);
aSizes.ToString(
mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity);
mIsLinkPreload = aLinkPreload;
}
// <picture> elements have multiple <source> nodes followed by an <img>,

View File

@ -186,7 +186,7 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
nsHtml5String sizes =
aAttributes->getValue(nsHtml5AttributeName::ATTR_SIZES);
mSpeculativeLoadQueue.AppendElement()->InitImage(
url, crossOrigin, referrerPolicy, srcset, sizes);
url, crossOrigin, referrerPolicy, srcset, sizes, false);
}
} else if (nsGkAtoms::source == aName) {
nsHtml5String srcset =
@ -306,6 +306,13 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
mSpeculativeLoadQueue.AppendElement()->InitStyle(
url, charset, crossOrigin, referrerPolicy, integrity,
true);
} else if (as.LowerCaseEqualsASCII("image")) {
nsHtml5String srcset = aAttributes->getValue(
nsHtml5AttributeName::ATTR_IMAGESRCSET);
nsHtml5String sizes = aAttributes->getValue(
nsHtml5AttributeName::ATTR_IMAGESIZES);
mSpeculativeLoadQueue.AppendElement()->InitImage(
url, crossOrigin, referrerPolicy, srcset, sizes, true);
}
// Other "as" values will be supported later.
}
@ -316,7 +323,7 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
if (url) {
mSpeculativeLoadQueue.AppendElement()->InitImage(
url, nullptr, nullptr, nullptr, nullptr);
url, nullptr, nullptr, nullptr, nullptr, false);
}
} else if (nsGkAtoms::style == aName) {
mImportScanner.Start();
@ -369,7 +376,7 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement(
aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
if (url) {
mSpeculativeLoadQueue.AppendElement()->InitImage(
url, nullptr, nullptr, nullptr, nullptr);
url, nullptr, nullptr, nullptr, nullptr, false);
}
} else if (nsGkAtoms::script == aName) {
nsHtml5TreeOperation* treeOp =

View File

@ -1004,10 +1004,12 @@ void nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
aLinkPreload);
}
void nsHtml5TreeOpExecutor::PreloadImage(
const nsAString& aURL, const nsAString& aCrossOrigin,
const nsAString& aSrcset, const nsAString& aSizes,
const nsAString& aImageReferrerPolicy) {
void nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
const nsAString& aCrossOrigin,
const nsAString& aSrcset,
const nsAString& aSizes,
const nsAString& aImageReferrerPolicy,
bool aLinkPreload) {
nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
bool isImgSet = false;
nsCOMPtr<nsIURI> uri =
@ -1016,7 +1018,7 @@ void nsHtml5TreeOpExecutor::PreloadImage(
// use document wide referrer policy
mDocument->MaybePreLoadImage(uri, aCrossOrigin,
GetPreloadReferrerPolicy(aImageReferrerPolicy),
isImgSet);
isImgSet, aLinkPreload);
}
}

View File

@ -243,7 +243,7 @@ class nsHtml5TreeOpExecutor final
void PreloadImage(const nsAString& aURL, const nsAString& aCrossOrigin,
const nsAString& aSrcset, const nsAString& aSizes,
const nsAString& aImageReferrerPolicy);
const nsAString& aImageReferrerPolicy, bool aLinkPreload);
void PreloadOpenPicture();

View File

@ -23,6 +23,7 @@ PreloadHashKey::PreloadHashKey(PreloadHashKey&& aToMove)
mAs = std::move(aToMove.mAs);
mCORSMode = std::move(aToMove.mCORSMode);
mReferrerPolicy = std::move(aToMove.mReferrerPolicy);
mPrincipal = std::move(aToMove.mPrincipal);
switch (mAs) {
case ResourceType::SCRIPT:
@ -51,6 +52,7 @@ PreloadHashKey& PreloadHashKey::operator=(const PreloadHashKey& aOther) {
mAs = aOther.mAs;
mCORSMode = aOther.mCORSMode;
mReferrerPolicy = aOther.mReferrerPolicy;
mPrincipal = aOther.mPrincipal;
switch (mAs) {
case ResourceType::SCRIPT:
@ -104,8 +106,8 @@ PreloadHashKey PreloadHashKey::CreateAsStyle(
CORSMode aCORSMode, css::SheetParsingMode aParsingMode) {
PreloadHashKey key(aURI, ResourceType::STYLE);
key.mCORSMode = aCORSMode;
key.mPrincipal = aPrincipal;
key.mStyle.mPrincipal = aPrincipal;
key.mStyle.mParsingMode = aParsingMode;
key.mStyle.mReferrerInfo = aReferrerInfo;
@ -121,11 +123,31 @@ PreloadHashKey PreloadHashKey::CreateAsStyle(
aSheetLoadData.mSheet->ParsingMode());
}
// static
PreloadHashKey PreloadHashKey::CreateAsImage(
nsIURI* aURI, nsIPrincipal* aPrincipal, CORSMode aCORSMode,
dom::ReferrerPolicy const& aReferrerPolicy) {
PreloadHashKey key(aURI, ResourceType::IMAGE);
key.mReferrerPolicy = aReferrerPolicy;
key.mCORSMode = aCORSMode;
key.mPrincipal = aPrincipal;
return key;
}
bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
if (mAs != aOther->mAs || mCORSMode != aOther->mCORSMode ||
mReferrerPolicy != aOther->mReferrerPolicy) {
return false;
}
if (!mPrincipal != !aOther->mPrincipal) {
// One or the other has a principal, but not both... not equal
return false;
}
if (mPrincipal && !mPrincipal->Equals(aOther->mPrincipal)) {
return false;
}
if (!nsURIHashKey::KeyEquals(
static_cast<const nsURIHashKey*>(aOther)->GetKey())) {
@ -139,10 +161,6 @@ bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
}
break;
case ResourceType::STYLE: {
if (!mStyle.mPrincipal != !aOther->mStyle.mPrincipal) {
// One or the other has a principal, but not both... not equal
return false;
}
if (mStyle.mParsingMode != aOther->mStyle.mParsingMode) {
return false;
}
@ -152,14 +170,13 @@ bool PreloadHashKey::KeyEquals(KeyTypePointer aOther) const {
!eq) {
return false;
}
if (mStyle.mPrincipal && (NS_FAILED(mStyle.mPrincipal->Equals(
aOther->mStyle.mPrincipal, &eq)) ||
!eq)) {
return false;
}
break;
}
case ResourceType::IMAGE:
// No more checks needed. The image cache key consists of the document
// (which we are scoped into), origin attributes (which we compare as part
// of the principal check) and the URL. imgLoader::ValidateEntry compares
// CORS, referrer info and principal, which we do by default.
break;
case ResourceType::FONT:
break;

View File

@ -56,8 +56,12 @@ class PreloadHashKey : public nsURIHashKey {
css::SheetParsingMode aParsingMode);
static PreloadHashKey CreateAsStyle(css::SheetLoadData& aSheetLoadData);
// Construct key for "image"
static PreloadHashKey CreateAsImage(
nsIURI* aURI, nsIPrincipal* aPrincipal, CORSMode aCORSMode,
dom::ReferrerPolicy const& aReferrerPolicy);
// TODO
// static CreateAsImage(...);
// static CreateAsFont(...);
// static CreateAsFetch(...);
@ -83,13 +87,13 @@ class PreloadHashKey : public nsURIHashKey {
CORSMode mCORSMode = CORS_NONE;
enum dom::ReferrerPolicy mReferrerPolicy = dom::ReferrerPolicy::_empty;
nsCOMPtr<nsIPrincipal> mPrincipal;
struct {
dom::ScriptKind mScriptKind = dom::ScriptKind::eClassic;
} mScript;
struct {
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
css::SheetParsingMode mParsingMode = css::eAuthorSheetFeatures;
} mStyle;

View File

@ -80,6 +80,7 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
aLinkElement->GetReferrerPolicy(referrerPolicyAttr);
auto referrerPolicy = PreloadReferrerPolicy(referrerPolicyAttr);
bool isImgSet = false;
PreloadHashKey preloadKey;
if (as.LowerCaseEqualsASCII("script")) {
@ -96,6 +97,21 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
uri, mDocument->NodePrincipal(), referrerInfo,
dom::Element::StringToCORSMode(crossOrigin),
css::eAuthorSheetFeatures /* see Loader::LoadSheet */);
} else if (as.LowerCaseEqualsASCII("image")) {
aLinkElement->GetImageSrcset(srcset);
aLinkElement->GetImageSizes(sizes);
nsAutoString url;
aLinkElement->GetHref(url);
uri = mDocument->ResolvePreloadImage(BaseURIForPreload(), url, srcset,
sizes, &isImgSet);
if (!uri) {
NotifyNodeEvent(aLinkElement, false);
return nullptr;
}
preloadKey = PreloadHashKey::CreateAsImage(
uri, mDocument->NodePrincipal(),
dom::Element::StringToCORSMode(crossOrigin), referrerPolicy);
} else {
NotifyNodeEvent(aLinkElement, false);
return nullptr;
@ -108,6 +124,8 @@ already_AddRefed<PreloaderBase> PreloadService::PreloadLinkElement(
integrity, true /* isInHead - TODO */);
} else if (as.LowerCaseEqualsASCII("style")) {
PreloadStyle(uri, charset, crossOrigin, referrerPolicyAttr, integrity);
} else if (as.LowerCaseEqualsASCII("image")) {
PreloadImage(uri, crossOrigin, referrerPolicyAttr, isImgSet);
}
preload = LookupPreload(&preloadKey);
@ -142,6 +160,14 @@ void PreloadService::PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
true);
}
void PreloadService::PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aImageReferrerPolicy,
bool aIsImgSet) {
mDocument->PreLoadImage(aURI, aCrossOrigin,
PreloadReferrerPolicy(aImageReferrerPolicy),
aIsImgSet, true);
}
// static
void PreloadService::NotifyNodeEvent(nsINode* aNode, bool aSuccess) {
if (!aNode->IsInComposedDoc()) {

View File

@ -18,6 +18,7 @@ namespace dom {
class HTMLLinkElement;
class Document;
enum class ReferrerPolicy : uint8_t;
} // namespace dom
@ -71,6 +72,9 @@ class PreloadService {
const nsAString& aReferrerPolicy,
const nsAString& aIntegrity);
void PreloadImage(nsIURI* aURI, const nsAString& aCrossOrigin,
const nsAString& aImageReferrerPolicy, bool aIsImgSet);
static void NotifyNodeEvent(nsINode* aNode, bool aSuccess);
private:

View File

@ -81,8 +81,8 @@ void PreloaderBase::AddLoadBackgroundFlag(nsIChannel* aChannel) {
aChannel->SetLoadFlags(loadFlags | nsIRequest::LOAD_BACKGROUND);
}
void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel,
dom::Document* aDocument, bool aIsPreload) {
void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, dom::Document* aDocument,
bool aIsPreload) {
if (aDocument && !aDocument->Preloads().RegisterPreload(aKey, this)) {
// This means there is already a preload registered under this key in this
// document. We only allow replacement when this is a regular load.
@ -92,9 +92,14 @@ void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel,
aDocument->Preloads().RegisterPreload(aKey, this);
}
mChannel = aChannel;
mKey = *aKey;
mIsUsed = !aIsPreload;
}
void PreloaderBase::NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel,
dom::Document* aDocument, bool aIsPreload) {
NotifyOpen(aKey, aDocument, aIsPreload);
mChannel = aChannel;
// * Start the usage timer if aIsPreload.

View File

@ -47,6 +47,8 @@ class PreloaderBase : public SupportsWeakPtr<PreloaderBase>,
// Called by resource loaders to register this preload in the document's
// preload service to provide coalescing, and access to the preload when it
// should be used for an actual load.
void NotifyOpen(PreloadHashKey* aKey, dom::Document* aDocument,
bool aIsPreload);
void NotifyOpen(PreloadHashKey* aKey, nsIChannel* aChannel,
dom::Document* aDocument, bool aIsPreload);
@ -62,6 +64,9 @@ class PreloaderBase : public SupportsWeakPtr<PreloaderBase>,
// (OnStartRequest or equal) and when it finished (OnStopRequest or equal)
void NotifyStart(nsIRequest* aRequest);
void NotifyStop(nsIRequest* aRequest, nsresult aStatus);
// Use this variant only in complement to NotifyOpen without providing a
// channel.
void NotifyStop(nsresult aStatus);
// Called when this currently existing load has to be asynchronously
// revalidated before it can be used. This prevents link preload DOM nodes
@ -108,7 +113,6 @@ class PreloaderBase : public SupportsWeakPtr<PreloaderBase>,
virtual ~PreloaderBase();
private:
void NotifyStop(nsresult aStatus);
void NotifyNodeEvent(nsINode* aNode);
// A helper class that will update the PreloaderBase.mChannel member when a

View File

@ -101,12 +101,13 @@ nsresult nsIconLoaderService::LoadIcon(nsIURI* aIconURI, bool aIsInternalIcon =
if (aIsInternalIcon) {
rv = loader->LoadImage(aIconURI, nullptr, nullptr, nullptr, 0, loadGroup, this, nullptr,
nullptr, nsIRequest::LOAD_NORMAL, nullptr, mContentType, EmptyString(),
/* aUseUrgentStartForChannel */ false, getter_AddRefs(mIconRequest));
/* aUseUrgentStartForChannel */ false, /* aLinkPreload */ false,
getter_AddRefs(mIconRequest));
} else {
rv = loader->LoadImage(aIconURI, nullptr, nullptr, mContent->NodePrincipal(), 0, loadGroup,
this, mContent, document, nsIRequest::LOAD_NORMAL, nullptr, mContentType,
EmptyString(),
/* aUseUrgentStartForChannel */ false, getter_AddRefs(mIconRequest));
EmptyString(), /* aUseUrgentStartForChannel */ false,
/* aLinkPreload */ false, getter_AddRefs(mIconRequest));
}
if (NS_FAILED(rv)) {
return rv;