3838#include " imgINotificationObserver.h"
3939#include " imgRequestProxy.h"
4040
41+ #include " mozilla/CycleCollectedJSContext.h"
42+
4143#include " mozilla/EventDispatcher.h"
4244#include " mozilla/MappedDeclarationsBuilder.h"
4345#include " mozilla/Maybe.h"
@@ -74,6 +76,48 @@ static bool IsPreviousSibling(const nsINode* aSubject, const nsINode* aNode) {
7476
7577namespace mozilla ::dom {
7678
79+ // Calls LoadSelectedImage on host element unless it has been superseded or
80+ // canceled -- this is the synchronous section of "update the image data".
81+ // https://html.spec.whatwg.org/#update-the-image-data
82+ class ImageLoadTask final : public MicroTaskRunnable {
83+ public:
84+ ImageLoadTask (HTMLImageElement* aElement, bool aAlwaysLoad,
85+ bool aUseUrgentStartForChannel)
86+ : mElement (aElement),
87+ mDocument (aElement->OwnerDoc ()),
88+ mAlwaysLoad(aAlwaysLoad),
89+ mUseUrgentStartForChannel(aUseUrgentStartForChannel) {
90+ mDocument ->BlockOnload ();
91+ }
92+
93+ void Run (AutoSlowOperation& aAso) override {
94+ if (mElement ->mPendingImageLoadTask == this ) {
95+ JSCallingLocation::AutoFallback fallback (&mCallingLocation );
96+ mElement ->ClearImageLoadTask ();
97+ mElement ->mUseUrgentStartForChannel = mUseUrgentStartForChannel ;
98+ mElement ->LoadSelectedImage (mAlwaysLoad );
99+ }
100+ mDocument ->UnblockOnload (false );
101+ }
102+
103+ bool Suppressed () override {
104+ nsIGlobalObject* global = mElement ->GetOwnerGlobal ();
105+ return global && global->IsInSyncOperation ();
106+ }
107+
108+ bool AlwaysLoad () const { return mAlwaysLoad ; }
109+
110+ private:
111+ ~ImageLoadTask () = default ;
112+ const RefPtr<HTMLImageElement> mElement ;
113+ const RefPtr<Document> mDocument ;
114+ const JSCallingLocation mCallingLocation {JSCallingLocation::Get ()};
115+ const bool mAlwaysLoad ;
116+ // True if we want to set nsIClassOfService::UrgentStart to the channel to get
117+ // the response ASAP for better user responsiveness.
118+ const bool mUseUrgentStartForChannel ;
119+ };
120+
77121HTMLImageElement::HTMLImageElement (
78122 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
79123 : nsGenericHTMLElement(std::move(aNodeInfo)) {
@@ -686,6 +730,11 @@ void HTMLImageElement::ClearForm(bool aRemoveFromForm) {
686730 mForm = nullptr ;
687731}
688732
733+ void HTMLImageElement::ClearImageLoadTask () {
734+ mPendingImageLoadTask = nullptr ;
735+ mHasPendingLoadTask = false ;
736+ }
737+
689738// Roughly corresponds to https://html.spec.whatwg.org/#update-the-image-data
690739void HTMLImageElement::UpdateSourceSyncAndQueueImageTask (
691740 bool aAlwaysLoad, bool aNotify, const HTMLSourceElement* aSkippedSource) {
@@ -702,9 +751,72 @@ void HTMLImageElement::UpdateSourceSyncAndQueueImageTask(
702751 // Spec issue: https://github.com/whatwg/html/issues/8207.
703752 UpdateResponsiveSource (aSkippedSource);
704753
705- nsImageLoadingContent::QueueImageTask (mSrcURI , mSrcTriggeringPrincipal ,
706- HaveSrcsetOrInPicture (), aAlwaysLoad,
707- aNotify);
754+ // If loading is temporarily disabled, we don't want to queue tasks that may
755+ // then run when loading is re-enabled.
756+ // Roughly step 1 and 2.
757+ // FIXME(emilio): Would be great to do this more per-spec. We don't cancel
758+ // existing loads etc.
759+ if (!LoadingEnabled () || !ShouldLoadImage ()) {
760+ return ;
761+ }
762+
763+ // Ensure that we don't overwrite a previous load request that requires
764+ // a complete load to occur.
765+ const bool alwaysLoad = aAlwaysLoad || (mPendingImageLoadTask &&
766+ mPendingImageLoadTask ->AlwaysLoad ());
767+
768+ // Steps 5 and 7 (sync cache check for src).
769+ const bool shouldLoadSync = [&] {
770+ if (HaveSrcsetOrInPicture ()) {
771+ return false ;
772+ }
773+ if (!mSrcURI ) {
774+ // NOTE(emilio): we need to also do a sync check for empty / invalid src,
775+ // see https://github.com/whatwg/html/issues/2429
776+ // But do it sync only when there's a current request.
777+ return !!mCurrentRequest ;
778+ }
779+ return nsContentUtils::IsImageAvailable (
780+ this , mSrcURI , mSrcTriggeringPrincipal , GetCORSMode ());
781+ }();
782+
783+ if (shouldLoadSync) {
784+ if (!nsContentUtils::IsSafeToRunScript ()) {
785+ // If not safe to run script, we should do the sync load task as soon as
786+ // possible instead. This prevents unsound state changes from frame
787+ // construction and such.
788+ nsContentUtils::AddScriptRunner (
789+ NewRunnableMethod<bool , bool , HTMLSourceElement*>(
790+ " HTMLImageElement::UpdateSourceSyncAndQueueImageTask" , this ,
791+ &HTMLImageElement::UpdateSourceSyncAndQueueImageTask, aAlwaysLoad,
792+ /* aNotify = */ true , nullptr ));
793+ return ;
794+ }
795+
796+ if (mLazyLoading && mSrcURI ) {
797+ StopLazyLoading (StartLoad::No);
798+ }
799+ ClearImageLoadTask ();
800+ LoadSelectedImage (alwaysLoad);
801+ return ;
802+ }
803+
804+ if (mLazyLoading ) {
805+ // This check is not in the spec, but it is just a performance optimization.
806+ // The reasoning for why it is sound is that we early-return from the image
807+ // task when lazy loading, and that StopLazyLoading makes us queue a new
808+ // task (which will implicitly cancel all the pre-existing tasks).
809+ return ;
810+ }
811+
812+ RefPtr task = new ImageLoadTask (this , alwaysLoad, mUseUrgentStartForChannel );
813+ mPendingImageLoadTask = task;
814+ mHasPendingLoadTask = true ;
815+ // We might have just become non-broken.
816+ UpdateImageState (aNotify);
817+ // The task checks this to determine if it was the last queued event, and so
818+ // earlier tasks are implicitly canceled.
819+ CycleCollectedJSContext::Get ()->DispatchToMicroTask (task.forget ());
708820}
709821
710822bool HTMLImageElement::HaveSrcsetOrInPicture () const {
@@ -722,19 +834,14 @@ bool HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource) {
722834 equal;
723835}
724836
725- void HTMLImageElement::LoadSelectedImage (bool aAlwaysLoad,
726- bool aStopLazyLoading) {
837+ void HTMLImageElement::LoadSelectedImage (bool aAlwaysLoad) {
727838 // In responsive mode, we have to make sure we ran the full selection
728839 // algorithm before loading the selected image.
729840 // Use this assertion to catch any cases we missed.
730841 MOZ_ASSERT (!UpdateResponsiveSource (),
731842 " The image source should be the same because we update the "
732843 " responsive source synchronously" );
733844
734- if (aStopLazyLoading) {
735- StopLazyLoading (StartLoad::No);
736- }
737-
738845 // The density is default to 1.0 for the src attribute case.
739846 double currentDensity = mResponsiveSelector
740847 ? mResponsiveSelector ->GetSelectedImageDensity ()
@@ -1111,6 +1218,10 @@ void HTMLImageElement::MediaFeatureValuesChanged() {
11111218 UpdateSourceSyncAndQueueImageTask (false , /* aNotify = */ true );
11121219}
11131220
1221+ bool HTMLImageElement::ShouldLoadImage () const {
1222+ return OwnerDoc ()->ShouldLoadImages ();
1223+ }
1224+
11141225void HTMLImageElement::SetLazyLoading () {
11151226 if (mLazyLoading ) {
11161227 return ;
0 commit comments