tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a5efa5c5014ee194c2b79095f216e223a54e9aef
parent 57d3ff4e0713684663d8002b8b902a8aba4f74a0
Author: Edgar Chen <echen@mozilla.com>
Date:   Fri, 19 Dec 2025 09:50:02 +0000

Bug 2003896 - Do not speculatively load preload image if the type is not supported; r=hsivonen

Differential Revision: https://phabricator.services.mozilla.com/D277117

Diffstat:
Mparser/html/nsHtml5SpeculativeLoad.cpp | 9+++++----
Mparser/html/nsHtml5SpeculativeLoad.h | 17++++++++++-------
Mparser/html/nsHtml5TreeBuilderCppSupplement.h | 12++++++++----
Mparser/html/nsHtml5TreeOpExecutor.cpp | 14++++++++++++--
Mparser/html/nsHtml5TreeOpExecutor.h | 7++++++-
Atesting/web-platform/tests/preload/preload-with-unsupported-type.html | 39+++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/preload/resources/preload-count.py | 17+++++++++++++++++
7 files changed, 97 insertions(+), 18 deletions(-)

diff --git a/parser/html/nsHtml5SpeculativeLoad.cpp b/parser/html/nsHtml5SpeculativeLoad.cpp @@ -45,7 +45,8 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) { aExecutor->PreloadImage( mUrlOrSizes, mCrossOrigin, mMedia, mCharsetOrSrcset, mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity, - mReferrerPolicyOrIntegrity, mIsLinkPreload, mFetchPriority); + mReferrerPolicyOrIntegrity, mIsLinkPreload, mFetchPriority, + mNonceOrType); break; case eSpeculativeLoadOpenPicture: aExecutor->PreloadOpenPicture(); @@ -63,7 +64,7 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) { aExecutor->PreloadScript( mUrlOrSizes, mCharsetOrSrcset, mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity, - mCrossOrigin, mMedia, mNonce, mFetchPriority, + mCrossOrigin, mMedia, mNonceOrType, mFetchPriority, mReferrerPolicyOrIntegrity, mScriptReferrerPolicy, false, mIsAsync, mIsDefer, mIsLinkPreload); break; @@ -71,14 +72,14 @@ void nsHtml5SpeculativeLoad::Perform(nsHtml5TreeOpExecutor* aExecutor) { aExecutor->PreloadScript( mUrlOrSizes, mCharsetOrSrcset, mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity, - mCrossOrigin, mMedia, mNonce, mFetchPriority, + mCrossOrigin, mMedia, mNonceOrType, mFetchPriority, mReferrerPolicyOrIntegrity, mScriptReferrerPolicy, true, mIsAsync, mIsDefer, mIsLinkPreload); break; case eSpeculativeLoadStyle: aExecutor->PreloadStyle( mUrlOrSizes, mCharsetOrSrcset, mCrossOrigin, mMedia, - mReferrerPolicyOrIntegrity, mNonce, + mReferrerPolicyOrIntegrity, mNonceOrType, mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity, mIsLinkPreload, mFetchPriority); break; diff --git a/parser/html/nsHtml5SpeculativeLoad.h b/parser/html/nsHtml5SpeculativeLoad.h @@ -75,7 +75,8 @@ class nsHtml5SpeculativeLoad { inline void InitImage(nsHtml5String aUrl, nsHtml5String aCrossOrigin, nsHtml5String aMedia, nsHtml5String aReferrerPolicy, nsHtml5String aSrcset, nsHtml5String aSizes, - bool aLinkPreload, nsHtml5String aFetchPriority) { + bool aLinkPreload, nsHtml5String aFetchPriority, + nsHtml5String aType) { MOZ_ASSERT(mOpCode == eSpeculativeLoadUninitialized, "Trying to reinitialize a speculative load!"); mOpCode = eSpeculativeLoadImage; @@ -93,6 +94,7 @@ class nsHtml5SpeculativeLoad { mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity); mIsLinkPreload = aLinkPreload; aFetchPriority.ToString(mFetchPriority); + aType.ToString(mNonceOrType); } inline void InitFont(nsHtml5String aUrl, nsHtml5String aCrossOrigin, @@ -184,7 +186,7 @@ class nsHtml5SpeculativeLoad { mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity); aCrossOrigin.ToString(mCrossOrigin); aMedia.ToString(mMedia); - aNonce.ToString(mNonce); + aNonce.ToString(mNonceOrType); aFetchPriority.ToString(mFetchPriority); aIntegrity.ToString(mReferrerPolicyOrIntegrity); nsAutoString referrerPolicy; @@ -210,7 +212,7 @@ class nsHtml5SpeculativeLoad { mCrossOrigin.SetIsVoid(true); mMedia.SetIsVoid(true); mReferrerPolicyOrIntegrity.SetIsVoid(true); - mNonce.SetIsVoid(true); + mNonceOrType.SetIsVoid(true); mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity.SetIsVoid( true); } @@ -233,7 +235,7 @@ class nsHtml5SpeculativeLoad { mReferrerPolicyOrIntegrity.Assign( nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>( referrerPolicy)); - aNonce.ToString(mNonce); + aNonce.ToString(mNonceOrType); aIntegrity.ToString( mTypeOrCharsetSourceOrDocumentModeOrMetaCSPOrSizesOrIntegrity); mIsLinkPreload = aLinkPreload; @@ -410,10 +412,11 @@ class nsHtml5SpeculativeLoad { */ nsString mMedia; /** - * If mOpCode is eSpeculativeLoadScript[FromHead] this represents the value - * of the "nonce" attribute. + * If mOpCode is eSpeculativeLoadImage this represents the value of the "type" + * attribute. If the attribute is not set, this will be a void string. + * Otherwise, it is empty or the value of the "nonce" attribute. */ - nsString mNonce; + nsString mNonceOrType; /** * If mOpCode is eSpeculativeLoadNoModuleScript[FromHead] or * eSpeculativeLoadScript[FromHead] this represents the value of the diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -220,9 +220,11 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement( aAttributes->getValue(nsHtml5AttributeName::ATTR_SIZES); nsHtml5String fetchPriority = aAttributes->getValue(nsHtml5AttributeName::ATTR_FETCHPRIORITY); + nsHtml5String type = + aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE); mSpeculativeLoadQueue.AppendElement()->InitImage( url, crossOrigin, /* aMedia = */ nullptr, referrerPolicy, - srcset, sizes, false, fetchPriority); + srcset, sizes, false, fetchPriority, type); } } else if (nsGkAtoms::source == aName) { nsHtml5String srcset = @@ -439,9 +441,11 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement( nsHtml5AttributeName::ATTR_IMAGESRCSET); nsHtml5String sizes = aAttributes->getValue( nsHtml5AttributeName::ATTR_IMAGESIZES); + nsHtml5String type = + aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE); mSpeculativeLoadQueue.AppendElement()->InitImage( url, crossOrigin, media, referrerPolicy, srcset, sizes, - true, fetchPriority); + true, fetchPriority, type); } else if (as.LowerCaseEqualsASCII("font")) { mSpeculativeLoadQueue.AppendElement()->InitFont( url, crossOrigin, media, referrerPolicy, fetchPriority); @@ -500,7 +504,7 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement( mSpeculativeLoadQueue.AppendElement()->InitImage( url, nullptr, nullptr, nullptr, nullptr, nullptr, false, - fetchPriority); + fetchPriority, nullptr); } } else if (nsGkAtoms::style == aName) { mImportScanner.Start(); @@ -562,7 +566,7 @@ nsIContentHandle* nsHtml5TreeBuilder::createElement( mSpeculativeLoadQueue.AppendElement()->InitImage( url, crossOrigin, /* aMedia = */ nullptr, nullptr, nullptr, - nullptr, false, fetchPriority); + nullptr, false, fetchPriority, nullptr); } } else if (nsGkAtoms::script == aName) { nsHtml5TreeOperation* treeOp = diff --git a/parser/html/nsHtml5TreeOpExecutor.cpp b/parser/html/nsHtml5TreeOpExecutor.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/nsCSPService.h" #include "mozilla/dom/PolicyContainer.h" +#include "imgLoader.h" #include "mozAutoDocUpdate.h" #include "mozilla/IdleTaskRunner.h" #include "mozilla/Preferences.h" @@ -1167,6 +1168,14 @@ bool nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI* aURI) { return mPreloadedURLs.EnsureInserted(spec); } +bool nsHtml5TreeOpExecutor::ImageTypeSupports(const nsAString& aType) { + if (aType.IsEmpty()) { + return true; + } + return imgLoader::SupportImageWithMimeType( + NS_ConvertUTF16toUTF8(aType), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS); +} + dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy( const nsAString& aReferrerPolicy) { dom::ReferrerPolicy referrerPolicy = @@ -1239,12 +1248,13 @@ void nsHtml5TreeOpExecutor::PreloadImage( const nsAString& aURL, const nsAString& aCrossOrigin, const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aImageReferrerPolicy, bool aLinkPreload, - const nsAString& aFetchPriority) { + const nsAString& aFetchPriority, const nsAString& aType) { nsCOMPtr<nsIURI> baseURI = BaseURIForPreload(); bool isImgSet = false; nsCOMPtr<nsIURI> uri = mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset, aSizes, &isImgSet); - if (uri && ShouldPreloadURI(uri) && MediaApplies(aMedia)) { + if (uri && ShouldPreloadURI(uri) && MediaApplies(aMedia) && + ImageTypeSupports(aType)) { // use document wide referrer policy mDocument->MaybePreLoadImage(uri, aCrossOrigin, GetPreloadReferrerPolicy(aImageReferrerPolicy), diff --git a/parser/html/nsHtml5TreeOpExecutor.h b/parser/html/nsHtml5TreeOpExecutor.h @@ -262,7 +262,7 @@ class nsHtml5TreeOpExecutor final const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes, const nsAString& aImageReferrerPolicy, bool aLinkPreload, - const nsAString& aFetchPriority); + const nsAString& aFetchPriority, const nsAString& aType); void PreloadOpenPicture(); @@ -317,6 +317,11 @@ class nsHtml5TreeOpExecutor final */ bool ShouldPreloadURI(nsIURI* aURI); + /** + * Returns true if the image type is supported. + */ + bool ImageTypeSupports(const nsAString& aType); + ReferrerPolicy GetPreloadReferrerPolicy(const nsAString& aReferrerPolicy); ReferrerPolicy GetPreloadReferrerPolicy(ReferrerPolicy aReferrerPolicy); diff --git a/testing/web-platform/tests/preload/preload-with-unsupported-type.html b/testing/web-platform/tests/preload/preload-with-unsupported-type.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>Makes sure the resources with an unsupported type attribute should not be loaded</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + var imageLoaded = false; + var imageError = false; + var preloadCount = undefined; +</script> +<link rel="preload" href="resources/preload-count.py?action=image" as="image" type="not-a-mime" onerror="imageError=true" onload="imageLoaded=true"> +<body> +<script> + setup({single_test: true}); + + function getPreloadCount() { + return new Promise(resolve => { + var script = document.createElement("script"); + script.src = "resources/preload-count.py?action=result"; + script.onload = resolve; + document.body.appendChild(script); + }); + } + + function check_result() { + assert_false(imageLoaded, "image should not trigger load event"); + assert_false(imageLoaded, "image should not trigger error event"); + getPreloadCount().then(() => { + assert_equals(preloadCount, 0, "should not try to load resource"); + done(); + }); + } + + window.addEventListener("load", function() { + step_timeout(check_result, 3000); + }); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/preload/resources/preload-count.py b/testing/web-platform/tests/preload/resources/preload-count.py @@ -0,0 +1,17 @@ +import sys + +def main(request, response): + key = b"a8697ae7-c8cb-4dbd-a8ef-27111dc7042f" + count = request.server.stash.take(key) + if count is None: + count = 0 + + action = request.GET.first(b"action") + if action == b"result": + response.headers.append(b"Content-Type", b"text/javascript") + return "preloadCount = {0};".format(count) + else: + count += 1 + request.server.stash.put(key, count) + response.status = 404 + return 'No entry is found'