1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLImageElement.h"
8 #include "mozilla/PresShell.h"
9 #include "mozilla/dom/BindContext.h"
10 #include "mozilla/dom/BindingUtils.h"
11 #include "mozilla/dom/HTMLImageElementBinding.h"
12 #include "mozilla/dom/NameSpaceConstants.h"
13 #include "nsGenericHTMLElement.h"
14 #include "nsGkAtoms.h"
15 #include "nsStyleConsts.h"
16 #include "nsPresContext.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsImageFrame.h"
20 #include "nsIScriptContext.h"
21 #include "nsContentUtils.h"
22 #include "nsContainerFrame.h"
23 #include "nsNodeInfoManager.h"
24 #include "mozilla/MouseEvents.h"
25 #include "nsContentPolicyUtils.h"
26 #include "nsFocusManager.h"
27 #include "mozilla/dom/DOMIntersectionObserver.h"
28 #include "mozilla/dom/HTMLFormElement.h"
29 #include "mozilla/dom/MutationEventBinding.h"
30 #include "mozilla/dom/UserActivation.h"
31 #include "nsAttrValueOrString.h"
32 #include "imgLoader.h"
36 #include "mozilla/dom/HTMLSourceElement.h"
37 #include "mozilla/dom/ResponsiveImageSelector.h"
39 #include "imgINotificationObserver.h"
40 #include "imgRequestProxy.h"
42 #include "mozilla/CycleCollectedJSContext.h"
44 #include "mozilla/EventDispatcher.h"
45 #include "mozilla/MappedDeclarationsBuilder.h"
46 #include "mozilla/Maybe.h"
47 #include "mozilla/RestyleManager.h"
49 #include "nsLayoutUtils.h"
51 using namespace mozilla::net
;
54 NS_IMPL_NS_NEW_HTML_ELEMENT(Image
)
57 // Is aSubject a previous sibling of aNode.
58 static bool IsPreviousSibling(const nsINode
* aSubject
, const nsINode
* aNode
) {
59 if (aSubject
== aNode
) {
63 nsINode
* parent
= aSubject
->GetParentNode();
64 if (parent
&& parent
== aNode
->GetParentNode()) {
65 const Maybe
<uint32_t> indexOfSubject
= parent
->ComputeIndexOf(aSubject
);
66 const Maybe
<uint32_t> indexOfNode
= parent
->ComputeIndexOf(aNode
);
67 if (MOZ_LIKELY(indexOfSubject
.isSome() && indexOfNode
.isSome())) {
68 return *indexOfSubject
< *indexOfNode
;
70 // XXX Keep the odd traditional behavior for now.
71 return indexOfSubject
.isNothing() && indexOfNode
.isSome();
78 namespace mozilla::dom
{
80 // Calls LoadSelectedImage on host element unless it has been superseded or
81 // canceled -- this is the synchronous section of "update the image data".
82 // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data
83 class ImageLoadTask final
: public MicroTaskRunnable
{
85 ImageLoadTask(HTMLImageElement
* aElement
, bool aAlwaysLoad
,
86 bool aUseUrgentStartForChannel
)
87 : MicroTaskRunnable(),
89 mAlwaysLoad(aAlwaysLoad
),
90 mUseUrgentStartForChannel(aUseUrgentStartForChannel
) {
91 mDocument
= aElement
->OwnerDoc();
92 mDocument
->BlockOnload();
95 void Run(AutoSlowOperation
& aAso
) override
{
96 if (mElement
->mPendingImageLoadTask
== this) {
97 mElement
->mPendingImageLoadTask
= nullptr;
98 mElement
->mUseUrgentStartForChannel
= mUseUrgentStartForChannel
;
99 mElement
->LoadSelectedImage(true, true, mAlwaysLoad
);
101 mDocument
->UnblockOnload(false);
104 bool Suppressed() override
{
105 nsIGlobalObject
* global
= mElement
->GetOwnerGlobal();
106 return global
&& global
->IsInSyncOperation();
109 bool AlwaysLoad() const { return mAlwaysLoad
; }
112 ~ImageLoadTask() = default;
113 RefPtr
<HTMLImageElement
> mElement
;
114 nsCOMPtr
<Document
> mDocument
;
117 // True if we want to set nsIClassOfService::UrgentStart to the channel to
118 // get the response ASAP for better user responsiveness.
119 bool mUseUrgentStartForChannel
;
122 HTMLImageElement::HTMLImageElement(
123 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
124 : nsGenericHTMLElement(std::move(aNodeInfo
)) {
125 // We start out broken
126 AddStatesSilently(ElementState::BROKEN
);
129 HTMLImageElement::~HTMLImageElement() { nsImageLoadingContent::Destroy(); }
131 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement
, nsGenericHTMLElement
,
134 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLImageElement
,
135 nsGenericHTMLElement
,
136 nsIImageLoadingContent
,
137 imgINotificationObserver
)
139 NS_IMPL_ELEMENT_CLONE(HTMLImageElement
)
141 bool HTMLImageElement::IsInteractiveHTMLContent() const {
142 return HasAttr(nsGkAtoms::usemap
) ||
143 nsGenericHTMLElement::IsInteractiveHTMLContent();
146 void HTMLImageElement::AsyncEventRunning(AsyncEventDispatcher
* aEvent
) {
147 nsImageLoadingContent::AsyncEventRunning(aEvent
);
150 void HTMLImageElement::GetCurrentSrc(nsAString
& aValue
) {
151 nsCOMPtr
<nsIURI
> currentURI
;
152 GetCurrentURI(getter_AddRefs(currentURI
));
155 currentURI
->GetSpec(spec
);
156 CopyUTF8toUTF16(spec
, aValue
);
158 SetDOMStringToNull(aValue
);
162 bool HTMLImageElement::Draggable() const {
163 // images may be dragged unless the draggable attribute is false
164 return !AttrValueIs(kNameSpaceID_None
, nsGkAtoms::draggable
,
165 nsGkAtoms::_false
, eIgnoreCase
);
168 bool HTMLImageElement::Complete() {
169 // It is still not clear what value should img.complete return in various
170 // cases, see https://github.com/whatwg/html/issues/4884
172 if (!HasAttr(nsGkAtoms::srcset
) && !HasNonEmptyAttr(nsGkAtoms::src
)) {
176 if (!mCurrentRequest
|| mPendingRequest
) {
181 mCurrentRequest
->GetImageStatus(&status
);
183 (imgIRequest::STATUS_LOAD_COMPLETE
| imgIRequest::STATUS_ERROR
)) != 0;
186 CSSIntPoint
HTMLImageElement::GetXY() {
187 nsIFrame
* frame
= GetPrimaryFrame(FlushType::Layout
);
189 return CSSIntPoint(0, 0);
191 return CSSIntPoint::FromAppUnitsRounded(
192 frame
->GetOffsetTo(frame
->PresShell()->GetRootFrame()));
195 int32_t HTMLImageElement::X() { return GetXY().x
; }
197 int32_t HTMLImageElement::Y() { return GetXY().y
; }
199 void HTMLImageElement::GetDecoding(nsAString
& aValue
) {
200 GetEnumAttr(nsGkAtoms::decoding
, kDecodingTableDefault
->tag
, aValue
);
203 // https://whatpr.org/html/3752/urls-and-fetching.html#lazy-loading-attributes
204 static const nsAttrValue::EnumTable kLoadingTable
[] = {
205 {"eager", HTMLImageElement::Loading::Eager
},
206 {"lazy", HTMLImageElement::Loading::Lazy
},
209 void HTMLImageElement::GetLoading(nsAString
& aValue
) const {
210 GetEnumAttr(nsGkAtoms::loading
, kLoadingTable
[0].tag
, aValue
);
213 HTMLImageElement::Loading
HTMLImageElement::LoadingState() const {
214 const nsAttrValue
* val
= mAttrs
.GetAttr(nsGkAtoms::loading
);
216 return HTMLImageElement::Loading::Eager
;
218 return static_cast<HTMLImageElement::Loading
>(val
->GetEnumValue());
221 already_AddRefed
<Promise
> HTMLImageElement::Decode(ErrorResult
& aRv
) {
222 return nsImageLoadingContent::QueueDecodeAsync(aRv
);
225 bool HTMLImageElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
226 const nsAString
& aValue
,
227 nsIPrincipal
* aMaybeScriptedPrincipal
,
228 nsAttrValue
& aResult
) {
229 if (aNamespaceID
== kNameSpaceID_None
) {
230 if (aAttribute
== nsGkAtoms::align
) {
231 return ParseAlignValue(aValue
, aResult
);
233 if (aAttribute
== nsGkAtoms::crossorigin
) {
234 ParseCORSValue(aValue
, aResult
);
237 if (aAttribute
== nsGkAtoms::decoding
) {
238 return aResult
.ParseEnumValue(aValue
, kDecodingTable
,
239 /* aCaseSensitive = */ false,
240 kDecodingTableDefault
);
242 if (aAttribute
== nsGkAtoms::loading
) {
243 return aResult
.ParseEnumValue(aValue
, kLoadingTable
,
244 /* aCaseSensitive = */ false,
247 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
252 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
253 aMaybeScriptedPrincipal
, aResult
);
256 void HTMLImageElement::MapAttributesIntoRule(
257 MappedDeclarationsBuilder
& aBuilder
) {
258 MapImageAlignAttributeInto(aBuilder
);
259 MapImageBorderAttributeInto(aBuilder
);
260 MapImageMarginAttributeInto(aBuilder
);
261 MapImageSizeAttributesInto(aBuilder
, MapAspectRatio::Yes
);
262 MapCommonAttributesInto(aBuilder
);
265 nsChangeHint
HTMLImageElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
266 int32_t aModType
) const {
267 nsChangeHint retval
=
268 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute
, aModType
);
269 if (aAttribute
== nsGkAtoms::usemap
|| aAttribute
== nsGkAtoms::ismap
) {
270 retval
|= nsChangeHint_ReconstructFrame
;
271 } else if (aAttribute
== nsGkAtoms::alt
) {
272 if (aModType
== MutationEvent_Binding::ADDITION
||
273 aModType
== MutationEvent_Binding::REMOVAL
) {
274 retval
|= nsChangeHint_ReconstructFrame
;
281 HTMLImageElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
282 static const MappedAttributeEntry
* const map
[] = {
283 sCommonAttributeMap
, sImageMarginSizeAttributeMap
,
284 sImageBorderAttributeMap
, sImageAlignAttributeMap
};
286 return FindAttributeDependence(aAttribute
, map
);
289 nsMapRuleToAttributesFunc
HTMLImageElement::GetAttributeMappingFunction()
291 return &MapAttributesIntoRule
;
294 void HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
295 const nsAttrValue
* aValue
, bool aNotify
) {
296 if (aNameSpaceID
== kNameSpaceID_None
&& mForm
&&
297 (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
)) {
298 // remove the image from the hashtable as needed
299 if (const auto* old
= GetParsedAttr(aName
); old
&& !old
->IsEmptyString()) {
300 mForm
->RemoveImageElementFromTable(
301 this, nsDependentAtomString(old
->GetAtomValue()));
305 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID
, aName
, aValue
,
309 void HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
310 const nsAttrValue
* aValue
,
311 const nsAttrValue
* aOldValue
,
312 nsIPrincipal
* aMaybeScriptedPrincipal
,
314 if (aNameSpaceID
!= kNameSpaceID_None
) {
315 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID
, aName
, aValue
,
317 aMaybeScriptedPrincipal
, aNotify
);
320 nsAttrValueOrString
attrVal(aValue
);
321 if (aName
== nsGkAtoms::src
) {
323 if (aValue
&& !aValue
->IsEmptyString()) {
324 StringToURI(attrVal
.String(), OwnerDoc(), getter_AddRefs(mSrcURI
));
329 AfterMaybeChangeAttr(aNameSpaceID
, aName
, attrVal
, aOldValue
,
330 aMaybeScriptedPrincipal
, aNotify
);
333 if (mForm
&& (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
) && aValue
&&
334 !aValue
->IsEmptyString()) {
335 // add the image to the hashtable as needed
336 MOZ_ASSERT(aValue
->Type() == nsAttrValue::eAtom
,
337 "Expected atom value for name/id");
338 mForm
->AddImageElementToTable(
339 this, nsDependentAtomString(aValue
->GetAtomValue()));
342 bool forceReload
= false;
344 if (aName
== nsGkAtoms::loading
&& !mLoading
) {
345 if (aValue
&& Loading(aValue
->GetEnumValue()) == Loading::Lazy
) {
347 } else if (aOldValue
&&
348 Loading(aOldValue
->GetEnumValue()) == Loading::Lazy
) {
349 StopLazyLoading(StartLoading::Yes
);
351 } else if (aName
== nsGkAtoms::src
&& !aValue
) {
352 // NOTE: regular src value changes are handled in AfterMaybeChangeAttr, so
353 // this only needs to handle unsetting the src attribute.
354 // Mark channel as urgent-start before load image if the image load is
355 // initaiated by a user interaction.
356 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
358 // AfterMaybeChangeAttr handles setting src since it needs to catch
359 // img.src = img.src, so we only need to handle the unset case
360 if (InResponsiveMode()) {
361 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == this) {
362 mResponsiveSelector
->SetDefaultSource(VoidString());
364 UpdateSourceSyncAndQueueImageTask(true);
366 // Bug 1076583 - We still behave synchronously in the non-responsive case
367 CancelImageRequests(aNotify
);
369 } else if (aName
== nsGkAtoms::srcset
) {
370 // Mark channel as urgent-start before load image if the image load is
371 // initaiated by a user interaction.
372 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
374 mSrcsetTriggeringPrincipal
= aMaybeScriptedPrincipal
;
376 PictureSourceSrcsetChanged(this, attrVal
.String(), aNotify
);
377 } else if (aName
== nsGkAtoms::sizes
) {
378 // Mark channel as urgent-start before load image if the image load is
379 // initiated by a user interaction.
380 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
382 PictureSourceSizesChanged(this, attrVal
.String(), aNotify
);
383 } else if (aName
== nsGkAtoms::decoding
) {
384 // Request sync or async image decoding.
386 aValue
&& static_cast<ImageDecodingType
>(aValue
->GetEnumValue()) ==
387 ImageDecodingType::Sync
);
388 } else if (aName
== nsGkAtoms::referrerpolicy
) {
389 ReferrerPolicy referrerPolicy
= GetReferrerPolicyAsEnum();
390 // FIXME(emilio): Why only when not in responsive mode? Also see below for
392 forceReload
= aNotify
&& !InResponsiveMode() &&
393 referrerPolicy
!= ReferrerPolicy::_empty
&&
394 referrerPolicy
!= ReferrerPolicyFromAttr(aOldValue
);
395 } else if (aName
== nsGkAtoms::crossorigin
) {
396 // FIXME(emilio): The aNotify bit seems a bit suspicious, but it is useful
397 // to avoid extra sync loads, specially in non-responsive mode. Ideally we
398 // can unify the responsive and non-responsive code paths (bug 1076583), and
399 // simplify this a bit.
400 forceReload
= aNotify
&& GetCORSMode() != AttrValueToCORSMode(aOldValue
);
404 // Because we load image synchronously in non-responsive-mode, we need to do
405 // reload after the attribute has been set if the reload is triggered by
406 // cross origin / referrer policy changing.
408 // Mark channel as urgent-start before load image if the image load is
409 // initiated by a user interaction.
410 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
411 if (InResponsiveMode()) {
412 // Per spec, full selection runs when this changes, even though
413 // it doesn't directly affect the source selection
414 UpdateSourceSyncAndQueueImageTask(true);
415 } else if (ShouldLoadImage()) {
416 // Bug 1076583 - We still use the older synchronous algorithm in
417 // non-responsive mode. Force a new load of the image with the
418 // new cross origin policy
419 ForceReload(aNotify
, IgnoreErrors());
423 return nsGenericHTMLElement::AfterSetAttr(
424 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
427 void HTMLImageElement::OnAttrSetButNotChanged(int32_t aNamespaceID
,
429 const nsAttrValueOrString
& aValue
,
431 AfterMaybeChangeAttr(aNamespaceID
, aName
, aValue
, nullptr, nullptr, aNotify
);
432 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID
, aName
,
436 void HTMLImageElement::AfterMaybeChangeAttr(
437 int32_t aNamespaceID
, nsAtom
* aName
, const nsAttrValueOrString
& aValue
,
438 const nsAttrValue
* aOldValue
, nsIPrincipal
* aMaybeScriptedPrincipal
,
440 if (aNamespaceID
!= kNameSpaceID_None
|| aName
!= nsGkAtoms::src
) {
444 // We need to force our image to reload. This must be done here, not in
445 // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
446 // being set to its existing value, which is normally optimized away as a
449 // If we are in responsive mode, we drop the forced reload behavior,
450 // but still trigger a image load task for img.src = img.src per
453 // Both cases handle unsetting src in AfterSetAttr
454 // Mark channel as urgent-start before load image if the image load is
455 // initaiated by a user interaction.
456 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
458 mSrcTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
459 this, aValue
.String(), aMaybeScriptedPrincipal
);
461 if (InResponsiveMode()) {
462 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == this) {
463 mResponsiveSelector
->SetDefaultSource(mSrcURI
, mSrcTriggeringPrincipal
);
465 UpdateSourceSyncAndQueueImageTask(true);
466 } else if (aNotify
&& ShouldLoadImage()) {
467 // If aNotify is false, we are coming from the parser or some such place;
468 // we'll get bound after all the attributes have been set, so we'll do the
469 // sync image load from BindToTree. Skip the LoadImage call in that case.
471 // Note that this sync behavior is partially removed from the spec, bug
474 // A hack to get animations to reset. See bug 594771.
475 mNewRequestsWillNeedAnimationReset
= true;
477 // Force image loading here, so that we'll try to load the image from
478 // network if it's set to be not cacheable.
479 // Potentially, false could be passed here rather than aNotify since
480 // UpdateState will be called by SetAttrAndNotify, but there are two
481 // obstacles to this: 1) LoadImage will end up calling
482 // UpdateState(aNotify), and we do not want it to call UpdateState(false)
483 // when aNotify is true, and 2) When this function is called by
484 // OnAttrSetButNotChanged, SetAttrAndNotify will not subsequently call
486 LoadSelectedImage(/* aForce = */ true, aNotify
,
487 /* aAlwaysLoad = */ true);
489 mNewRequestsWillNeedAnimationReset
= false;
493 void HTMLImageElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
494 // We handle image element with attribute ismap in its corresponding frame
495 // element. Set mMultipleActionsPrevented here to prevent the click event
496 // trigger the behaviors in Element::PostHandleEventForLinks
497 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
498 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent() && IsMap()) {
499 mouseEvent
->mFlags
.mMultipleActionsPrevented
= true;
501 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
504 nsINode
* HTMLImageElement::GetScopeChainParent() const {
508 return nsGenericHTMLElement::GetScopeChainParent();
511 bool HTMLImageElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
512 int32_t* aTabIndex
) {
513 int32_t tabIndex
= TabIndex();
515 if (IsInComposedDoc() && FindImageMap()) {
517 // Use tab index on individual map areas
518 *aTabIndex
= (sTabFocusModel
& eTabFocus_linksMask
) ? 0 : -1;
520 // Image map is not focusable itself, but flag as tabbable
521 // so that image map areas get walked into.
522 *aIsFocusable
= false;
528 // Can be in tab order if tabindex >=0 and form controls are tabbable.
529 *aTabIndex
= (sTabFocusModel
& eTabFocus_formElementsMask
) ? tabIndex
: -1;
532 *aIsFocusable
= IsFormControlDefaultFocusable(aWithMouse
) &&
533 (tabIndex
>= 0 || GetTabIndexAttrValue().isSome());
538 nsresult
HTMLImageElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
539 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
540 NS_ENSURE_SUCCESS(rv
, rv
);
542 nsImageLoadingContent::BindToTree(aContext
, aParent
);
546 if (HaveSrcsetOrInPicture()) {
547 if (IsInComposedDoc() && !mInDocResponsiveContent
) {
548 aContext
.OwnerDoc().AddResponsiveContent(this);
549 mInDocResponsiveContent
= true;
552 // Mark channel as urgent-start before load image if the image load is
553 // initaiated by a user interaction.
554 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
556 // Run selection algorithm when an img element is inserted into a document
557 // in order to react to changes in the environment. See note of
558 // https://html.spec.whatwg.org/multipage/embedded-content.html#img-environment-changes
560 // We also do this in PictureSourceAdded() if it is in <picture>, so here
561 // we only need to do if its parent is not <picture>, even if there is no
563 if (!IsInPicture()) {
564 UpdateSourceSyncAndQueueImageTask(false);
566 } else if (!InResponsiveMode() && HasAttr(nsGkAtoms::src
)) {
567 // We skip loading when our attributes were set from parser land,
568 // so trigger a aForce=false load now to check if things changed.
569 // This isn't necessary for responsive mode, since creating the
570 // image load task is asynchronous we don't need to take special
571 // care to avoid doing so when being filled by the parser.
573 // Mark channel as urgent-start before load image if the image load is
574 // initaiated by a user interaction.
575 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
577 // We still act synchronously for the non-responsive case (Bug
578 // 1076583), but still need to delay if it is unsafe to run
581 // If loading is temporarily disabled, don't even launch MaybeLoadImage.
582 // Otherwise MaybeLoadImage may run later when someone has reenabled
584 if (LoadingEnabled() && ShouldLoadImage()) {
585 nsContentUtils::AddScriptRunner(
586 NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage", this,
587 &HTMLImageElement::MaybeLoadImage
, false));
594 void HTMLImageElement::UnbindFromTree(bool aNullParent
) {
596 if (aNullParent
|| !FindAncestorForm(mForm
)) {
599 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT
);
603 if (mInDocResponsiveContent
) {
604 OwnerDoc()->RemoveResponsiveContent(this);
605 mInDocResponsiveContent
= false;
608 nsImageLoadingContent::UnbindFromTree(aNullParent
);
609 nsGenericHTMLElement::UnbindFromTree(aNullParent
);
612 void HTMLImageElement::UpdateFormOwner() {
614 mForm
= FindAncestorForm();
617 if (mForm
&& !HasFlag(ADDED_TO_FORM
)) {
618 // Now we need to add ourselves to the form
619 nsAutoString nameVal
, idVal
;
620 GetAttr(nsGkAtoms::name
, nameVal
);
621 GetAttr(nsGkAtoms::id
, idVal
);
623 SetFlags(ADDED_TO_FORM
);
625 mForm
->AddImageElement(this);
627 if (!nameVal
.IsEmpty()) {
628 mForm
->AddImageElementToTable(this, nameVal
);
631 if (!idVal
.IsEmpty()) {
632 mForm
->AddImageElementToTable(this, idVal
);
637 void HTMLImageElement::MaybeLoadImage(bool aAlwaysForceLoad
) {
638 // Our base URI may have changed, or we may have had responsive parameters
639 // change while not bound to the tree. However, at this moment, we should have
640 // updated the responsive source in other places, so we don't have to re-parse
641 // src/srcset here. Just need to LoadImage.
643 // Note, check LoadingEnabled() after LoadImage call.
645 LoadSelectedImage(aAlwaysForceLoad
, /* aNotify */ true, aAlwaysForceLoad
);
647 if (!LoadingEnabled()) {
648 CancelImageRequests(true);
652 void HTMLImageElement::NodeInfoChanged(Document
* aOldDoc
) {
653 nsGenericHTMLElement::NodeInfoChanged(aOldDoc
);
655 // Reparse the URI if needed. Note that we can't check whether we already have
656 // a parsed URI, because it might be null even if we have a valid src
657 // attribute, if we tried to parse with a different base.
660 if (GetAttr(nsGkAtoms::src
, src
) && !src
.IsEmpty()) {
661 StringToURI(src
, OwnerDoc(), getter_AddRefs(mSrcURI
));
664 // Unlike the LazyLoadImageObserver, the intersection observer
665 // for the viewport could contain the element even if
666 // it's not lazy-loading. For instance, the element has
667 // started to load, but haven't reached to the viewport.
668 // So here we always try to unobserve it.
669 if (auto* observer
= aOldDoc
->GetLazyLoadImageObserverViewport()) {
670 observer
->Unobserve(*this);
674 aOldDoc
->GetLazyLoadImageObserver()->Unobserve(*this);
675 mLazyLoading
= false;
679 // Run selection algorithm synchronously when an img element's adopting steps
680 // are run, in order to react to changes in the environment, per spec,
681 // https://html.spec.whatwg.org/multipage/images.html#reacting-to-dom-mutations,
683 // https://html.spec.whatwg.org/multipage/images.html#reacting-to-environment-changes.
684 if (InResponsiveMode()) {
685 UpdateResponsiveSource();
688 // Force reload image if adoption steps are run.
689 // If loading is temporarily disabled, don't even launch script runner.
690 // Otherwise script runner may run later when someone has reenabled loading.
691 StartLoadingIfNeeded();
695 already_AddRefed
<HTMLImageElement
> HTMLImageElement::Image(
696 const GlobalObject
& aGlobal
, const Optional
<uint32_t>& aWidth
,
697 const Optional
<uint32_t>& aHeight
, ErrorResult
& aError
) {
698 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
.GetAsSupports());
700 if (!win
|| !(doc
= win
->GetExtantDoc())) {
701 aError
.Throw(NS_ERROR_FAILURE
);
705 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(
706 nsGkAtoms::img
, nullptr, kNameSpaceID_XHTML
, ELEMENT_NODE
);
708 auto* nim
= nodeInfo
->NodeInfoManager();
709 RefPtr
<HTMLImageElement
> img
= new (nim
) HTMLImageElement(nodeInfo
.forget());
711 if (aWidth
.WasPassed()) {
712 img
->SetWidth(aWidth
.Value(), aError
);
713 if (aError
.Failed()) {
717 if (aHeight
.WasPassed()) {
718 img
->SetHeight(aHeight
.Value(), aError
);
719 if (aError
.Failed()) {
728 uint32_t HTMLImageElement::Height() { return GetWidthHeightForImage().height
; }
730 uint32_t HTMLImageElement::Width() { return GetWidthHeightForImage().width
; }
732 nsIntSize
HTMLImageElement::NaturalSize() {
733 if (!mCurrentRequest
) {
737 nsCOMPtr
<imgIContainer
> image
;
738 mCurrentRequest
->GetImage(getter_AddRefs(image
));
744 Unused
<< image
->GetHeight(&size
.height
);
745 Unused
<< image
->GetWidth(&size
.width
);
747 ImageResolution resolution
= image
->GetResolution();
748 // NOTE(emilio): What we implement here matches the image-set() spec, but it's
749 // unclear whether this is the right thing to do, see
750 // https://github.com/whatwg/html/pull/5574#issuecomment-826335244.
751 if (mResponsiveSelector
) {
752 float density
= mResponsiveSelector
->GetSelectedImageDensity();
753 MOZ_ASSERT(density
>= 0.0);
754 resolution
.ScaleBy(density
);
757 resolution
.ApplyTo(size
.width
, size
.height
);
761 nsresult
HTMLImageElement::CopyInnerTo(HTMLImageElement
* aDest
) {
762 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
767 // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), aDest skipped
768 // doing the image load because we passed in false for aNotify. But we
769 // really do want it to do the load, so set it up to happen once the cloning
770 // reaches a stable state.
771 if (!aDest
->InResponsiveMode() && aDest
->HasAttr(nsGkAtoms::src
) &&
772 aDest
->ShouldLoadImage()) {
773 // Mark channel as urgent-start before load image if the image load is
774 // initaiated by a user interaction.
775 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
777 nsContentUtils::AddScriptRunner(
778 NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage", aDest
,
779 &HTMLImageElement::MaybeLoadImage
, false));
785 CORSMode
HTMLImageElement::GetCORSMode() {
786 return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin
));
789 JSObject
* HTMLImageElement::WrapNode(JSContext
* aCx
,
790 JS::Handle
<JSObject
*> aGivenProto
) {
791 return HTMLImageElement_Binding::Wrap(aCx
, this, aGivenProto
);
795 HTMLFormElement
* HTMLImageElement::GetForm() const { return mForm
; }
798 void HTMLImageElement::SetForm(HTMLFormElement
* aForm
) {
799 MOZ_ASSERT(aForm
, "Don't pass null here");
801 "We don't support switching from one non-null form to another.");
806 void HTMLImageElement::ClearForm(bool aRemoveFromForm
) {
807 NS_ASSERTION((mForm
!= nullptr) == HasFlag(ADDED_TO_FORM
),
808 "Form control should have had flag set correctly");
814 if (aRemoveFromForm
) {
815 nsAutoString nameVal
, idVal
;
816 GetAttr(nsGkAtoms::name
, nameVal
);
817 GetAttr(nsGkAtoms::id
, idVal
);
819 mForm
->RemoveImageElement(this);
821 if (!nameVal
.IsEmpty()) {
822 mForm
->RemoveImageElementFromTable(this, nameVal
);
825 if (!idVal
.IsEmpty()) {
826 mForm
->RemoveImageElementFromTable(this, idVal
);
830 UnsetFlags(ADDED_TO_FORM
);
834 void HTMLImageElement::UpdateSourceSyncAndQueueImageTask(
835 bool aAlwaysLoad
, const HTMLSourceElement
* aSkippedSource
) {
836 // Per spec, when updating the image data or reacting to environment
837 // changes, we always run the full selection (including selecting the source
838 // element and the best fit image from srcset) even if it doesn't directly
839 // affect the source selection.
841 // However, in the spec of updating the image data, the selection of image
842 // source URL is in the asynchronous part (i.e. in a microtask), and so this
843 // doesn't guarantee that the image style is correct after we flush the style
844 // synchornously. So here we update the responsive source synchronously always
845 // to make sure the image source is always up-to-date after each DOM mutation.
846 // Spec issue: https://github.com/whatwg/html/issues/8207.
847 const bool changed
= UpdateResponsiveSource(aSkippedSource
);
849 // If loading is temporarily disabled, we don't want to queue tasks
850 // that may then run when loading is re-enabled.
851 if (!LoadingEnabled() || !ShouldLoadImage()) {
855 // Ensure that we don't overwrite a previous load request that requires
856 // a complete load to occur.
857 bool alwaysLoad
= aAlwaysLoad
;
858 if (mPendingImageLoadTask
) {
859 alwaysLoad
= alwaysLoad
|| mPendingImageLoadTask
->AlwaysLoad();
862 if (!changed
&& !alwaysLoad
) {
866 QueueImageLoadTask(alwaysLoad
);
869 bool HTMLImageElement::HaveSrcsetOrInPicture() {
870 if (HasAttr(nsGkAtoms::srcset
)) {
874 return IsInPicture();
877 bool HTMLImageElement::InResponsiveMode() {
878 // When we lose srcset or leave a <picture> element, the fallback to img.src
879 // will happen from the microtask, and we should behave responsively in the
881 return mResponsiveSelector
|| mPendingImageLoadTask
||
882 HaveSrcsetOrInPicture();
885 bool HTMLImageElement::SelectedSourceMatchesLast(nsIURI
* aSelectedSource
) {
886 // If there was no selected source previously, we don't want to short-circuit
887 // the load. Similarly for if there is no newly selected source.
888 if (!mLastSelectedSource
|| !aSelectedSource
) {
892 return NS_SUCCEEDED(mLastSelectedSource
->Equals(aSelectedSource
, &equal
)) &&
896 nsresult
HTMLImageElement::LoadSelectedImage(bool aForce
, bool aNotify
,
898 // In responsive mode, we have to make sure we ran the full selection algrithm
899 // before loading the selected image.
900 // Use this assertion to catch any cases we missed.
901 MOZ_ASSERT(!UpdateResponsiveSource(),
902 "The image source should be the same because we update the "
903 "responsive source synchronously");
905 // The density is default to 1.0 for the src attribute case.
906 double currentDensity
= mResponsiveSelector
907 ? mResponsiveSelector
->GetSelectedImageDensity()
910 nsCOMPtr
<nsIURI
> selectedSource
;
911 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
912 ImageLoadType type
= eImageLoadType_Normal
;
914 if (mResponsiveSelector
) {
915 selectedSource
= mResponsiveSelector
->GetSelectedImageURL();
916 triggeringPrincipal
=
917 mResponsiveSelector
->GetSelectedImageTriggeringPrincipal();
918 type
= eImageLoadType_Imageset
;
919 } else if (mSrcURI
|| HasAttr(nsGkAtoms::src
)) {
922 selectedSource
= mSrcURI
;
923 if (HaveSrcsetOrInPicture()) {
924 // If we have a srcset attribute or are in a <picture> element, we
925 // always use the Imageset load type, even if we parsed no valid
926 // responsive sources from either, per spec.
927 type
= eImageLoadType_Imageset
;
929 triggeringPrincipal
= mSrcTriggeringPrincipal
;
933 if (!aAlwaysLoad
&& SelectedSourceMatchesLast(selectedSource
)) {
934 // Update state when only density may have changed (i.e., the source to load
935 // hasn't changed, and we don't do any request at all). We need (apart from
936 // updating our internal state) to tell the image frame because its
937 // intrinsic size may have changed.
939 // In the case we actually trigger a new load, that load will trigger a call
940 // to nsImageFrame::NotifyNewCurrentRequest, which takes care of that for
942 SetDensity(currentDensity
);
946 // Before we actually defer the lazy-loading
948 if (!selectedSource
||
949 !nsContentUtils::IsImageAvailable(this, selectedSource
,
950 triggeringPrincipal
, GetCORSMode())) {
953 StopLazyLoading(StartLoading::No
);
956 nsresult rv
= NS_ERROR_FAILURE
;
958 // src triggers an error event on invalid URI, unlike other loads.
959 if (selectedSource
|| hasSrc
) {
960 rv
= LoadImage(selectedSource
, aForce
, aNotify
, type
, triggeringPrincipal
);
963 mLastSelectedSource
= selectedSource
;
964 mCurrentDensity
= currentDensity
;
967 CancelImageRequests(aNotify
);
972 void HTMLImageElement::PictureSourceSrcsetChanged(nsIContent
* aSourceNode
,
973 const nsAString
& aNewValue
,
975 MOZ_ASSERT(aSourceNode
== this || IsPreviousSibling(aSourceNode
, this),
976 "Should not be getting notifications for non-previous-siblings");
978 nsIContent
* currentSrc
=
979 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
981 if (aSourceNode
== currentSrc
) {
982 // We're currently using this node as our responsive selector
984 nsCOMPtr
<nsIPrincipal
> principal
;
985 if (aSourceNode
== this) {
986 principal
= mSrcsetTriggeringPrincipal
;
987 } else if (auto* source
= HTMLSourceElement::FromNode(aSourceNode
)) {
988 principal
= source
->GetSrcsetTriggeringPrincipal();
990 mResponsiveSelector
->SetCandidatesFromSourceSet(aNewValue
, principal
);
993 if (!mInDocResponsiveContent
&& IsInComposedDoc()) {
994 OwnerDoc()->AddResponsiveContent(this);
995 mInDocResponsiveContent
= true;
998 // This always triggers the image update steps per the spec, even if
999 // we are not using this source.
1000 UpdateSourceSyncAndQueueImageTask(true);
1003 void HTMLImageElement::PictureSourceSizesChanged(nsIContent
* aSourceNode
,
1004 const nsAString
& aNewValue
,
1006 MOZ_ASSERT(aSourceNode
== this || IsPreviousSibling(aSourceNode
, this),
1007 "Should not be getting notifications for non-previous-siblings");
1009 nsIContent
* currentSrc
=
1010 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
1012 if (aSourceNode
== currentSrc
) {
1013 // We're currently using this node as our responsive selector
1015 mResponsiveSelector
->SetSizesFromDescriptor(aNewValue
);
1018 // This always triggers the image update steps per the spec, even if
1019 // we are not using this source.
1020 UpdateSourceSyncAndQueueImageTask(true);
1023 void HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent
* aSourceNode
,
1025 MOZ_ASSERT(IsPreviousSibling(aSourceNode
, this),
1026 "Should not be getting notifications for non-previous-siblings");
1028 // This always triggers the image update steps per the spec, even if
1029 // we are not switching to/from this source
1030 UpdateSourceSyncAndQueueImageTask(true);
1033 void HTMLImageElement::PictureSourceDimensionChanged(
1034 HTMLSourceElement
* aSourceNode
, bool aNotify
) {
1035 MOZ_ASSERT(IsPreviousSibling(aSourceNode
, this),
1036 "Should not be getting notifications for non-previous-siblings");
1038 // "width" and "height" affect the dimension of images, but they don't have
1039 // impact on the selection of <source> elements. In other words,
1040 // UpdateResponsiveSource doesn't change the source, so all we need to do is
1041 // just request restyle.
1042 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == aSourceNode
) {
1043 InvalidateAttributeMapping();
1047 void HTMLImageElement::PictureSourceAdded(HTMLSourceElement
* aSourceNode
) {
1048 MOZ_ASSERT(!aSourceNode
|| IsPreviousSibling(aSourceNode
, this),
1049 "Should not be getting notifications for non-previous-siblings");
1051 UpdateSourceSyncAndQueueImageTask(true);
1054 void HTMLImageElement::PictureSourceRemoved(HTMLSourceElement
* aSourceNode
) {
1055 MOZ_ASSERT(!aSourceNode
|| IsPreviousSibling(aSourceNode
, this),
1056 "Should not be getting notifications for non-previous-siblings");
1058 UpdateSourceSyncAndQueueImageTask(true, aSourceNode
);
1061 bool HTMLImageElement::UpdateResponsiveSource(
1062 const HTMLSourceElement
* aSkippedSource
) {
1063 bool hadSelector
= !!mResponsiveSelector
;
1065 nsIContent
* currentSource
=
1066 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
1068 // Walk source nodes previous to ourselves if IsInPicture().
1069 nsINode
* candidateSource
=
1070 IsInPicture() ? GetParentElement()->GetFirstChild() : this;
1072 // Initialize this as nullptr so we don't have to nullify it when runing out
1073 // of siblings without finding ourself, e.g. XBL magic.
1074 RefPtr
<ResponsiveImageSelector
> newResponsiveSelector
= nullptr;
1076 for (; candidateSource
; candidateSource
= candidateSource
->GetNextSibling()) {
1077 if (aSkippedSource
== candidateSource
) {
1081 if (candidateSource
== currentSource
) {
1082 // found no better source before current, re-run selection on
1083 // that and keep it if it's still usable.
1084 bool changed
= mResponsiveSelector
->SelectImage(true);
1085 if (mResponsiveSelector
->NumCandidates()) {
1086 bool isUsableCandidate
= true;
1088 // an otherwise-usable source element may still have a media query that
1089 // may not match any more.
1090 if (candidateSource
->IsHTMLElement(nsGkAtoms::source
) &&
1091 !SourceElementMatches(candidateSource
->AsElement())) {
1092 isUsableCandidate
= false;
1095 if (isUsableCandidate
) {
1096 // We are still using the current source, but the selected image may
1097 // be changed, so always set the density from the selected image.
1098 SetDensity(mResponsiveSelector
->GetSelectedImageDensity());
1104 newResponsiveSelector
= nullptr;
1105 if (candidateSource
== this) {
1106 // No further possibilities
1109 } else if (candidateSource
== this) {
1110 // We are the last possible source
1111 newResponsiveSelector
=
1112 TryCreateResponsiveSelector(candidateSource
->AsElement());
1114 } else if (auto* source
= HTMLSourceElement::FromNode(candidateSource
)) {
1115 if (RefPtr
<ResponsiveImageSelector
> selector
=
1116 TryCreateResponsiveSelector(source
)) {
1117 newResponsiveSelector
= selector
.forget();
1118 // This led to a valid source, stop
1124 // If we reach this point, either:
1125 // - there was no selector originally, and there is not one now
1126 // - there was no selector originally, and there is one now
1127 // - there was a selector, and there is a different one now
1128 // - there was a selector, and there is not one now
1129 SetResponsiveSelector(std::move(newResponsiveSelector
));
1130 return hadSelector
|| mResponsiveSelector
;
1134 bool HTMLImageElement::SupportedPictureSourceType(const nsAString
& aType
) {
1136 nsAutoString params
;
1138 nsContentUtils::SplitMimeType(aType
, type
, params
);
1139 if (type
.IsEmpty()) {
1143 return imgLoader::SupportImageWithMimeType(
1144 NS_ConvertUTF16toUTF8(type
), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS
);
1147 bool HTMLImageElement::SourceElementMatches(Element
* aSourceElement
) {
1148 MOZ_ASSERT(aSourceElement
->IsHTMLElement(nsGkAtoms::source
));
1150 MOZ_ASSERT(IsInPicture());
1151 MOZ_ASSERT(IsPreviousSibling(aSourceElement
, this));
1153 // Check media and type
1154 auto* src
= static_cast<HTMLSourceElement
*>(aSourceElement
);
1155 if (!src
->MatchesCurrentMedia()) {
1160 return !src
->GetAttr(nsGkAtoms::type
, type
) ||
1161 SupportedPictureSourceType(type
);
1164 already_AddRefed
<ResponsiveImageSelector
>
1165 HTMLImageElement::TryCreateResponsiveSelector(Element
* aSourceElement
) {
1166 nsCOMPtr
<nsIPrincipal
> principal
;
1168 // Skip if this is not a <source> with matching media query
1169 bool isSourceTag
= aSourceElement
->IsHTMLElement(nsGkAtoms::source
);
1171 if (!SourceElementMatches(aSourceElement
)) {
1174 auto* source
= HTMLSourceElement::FromNode(aSourceElement
);
1175 principal
= source
->GetSrcsetTriggeringPrincipal();
1176 } else if (aSourceElement
->IsHTMLElement(nsGkAtoms::img
)) {
1177 // Otherwise this is the <img> tag itself
1178 MOZ_ASSERT(aSourceElement
== this);
1179 principal
= mSrcsetTriggeringPrincipal
;
1182 // Skip if has no srcset or an empty srcset
1184 if (!aSourceElement
->GetAttr(nsGkAtoms::srcset
, srcset
)) {
1188 if (srcset
.IsEmpty()) {
1193 RefPtr
<ResponsiveImageSelector
> sel
=
1194 new ResponsiveImageSelector(aSourceElement
);
1195 if (!sel
->SetCandidatesFromSourceSet(srcset
, principal
)) {
1196 // No possible candidates, don't need to bother parsing sizes
1201 aSourceElement
->GetAttr(nsGkAtoms::sizes
, sizes
);
1202 sel
->SetSizesFromDescriptor(sizes
);
1204 // If this is the <img> tag, also pull in src as the default source
1206 MOZ_ASSERT(aSourceElement
== this);
1208 sel
->SetDefaultSource(mSrcURI
, mSrcTriggeringPrincipal
);
1212 return sel
.forget();
1216 bool HTMLImageElement::SelectSourceForTagWithAttrs(
1217 Document
* aDocument
, bool aIsSourceTag
, const nsAString
& aSrcAttr
,
1218 const nsAString
& aSrcsetAttr
, const nsAString
& aSizesAttr
,
1219 const nsAString
& aTypeAttr
, const nsAString
& aMediaAttr
,
1220 nsAString
& aResult
) {
1221 MOZ_ASSERT(aIsSourceTag
|| (aTypeAttr
.IsEmpty() && aMediaAttr
.IsEmpty()),
1222 "Passing type or media attrs makes no sense without aIsSourceTag");
1223 MOZ_ASSERT(!aIsSourceTag
|| aSrcAttr
.IsEmpty(),
1224 "Passing aSrcAttr makes no sense with aIsSourceTag set");
1226 if (aSrcsetAttr
.IsEmpty()) {
1227 if (!aIsSourceTag
) {
1228 // For an <img> with no srcset, we would always select the src attr.
1229 aResult
.Assign(aSrcAttr
);
1232 // Otherwise, a <source> without srcset is never selected
1236 // Would not consider source tags with unsupported media or type
1238 ((!aMediaAttr
.IsVoid() && !HTMLSourceElement::WouldMatchMediaForDocument(
1239 aMediaAttr
, aDocument
)) ||
1240 (!aTypeAttr
.IsVoid() && !SupportedPictureSourceType(aTypeAttr
)))) {
1244 // Using srcset or picture <source>, build a responsive selector for this tag.
1245 RefPtr
<ResponsiveImageSelector
> sel
= new ResponsiveImageSelector(aDocument
);
1247 sel
->SetCandidatesFromSourceSet(aSrcsetAttr
);
1248 if (!aSizesAttr
.IsEmpty()) {
1249 sel
->SetSizesFromDescriptor(aSizesAttr
);
1251 if (!aIsSourceTag
) {
1252 sel
->SetDefaultSource(aSrcAttr
);
1255 if (sel
->GetSelectedImageURLSpec(aResult
)) {
1259 if (!aIsSourceTag
) {
1260 // <img> tag with no match would definitively load nothing.
1265 // <source> tags with no match would leave source yet-undetermined.
1269 void HTMLImageElement::DestroyContent() {
1270 // Clear mPendingImageLoadTask to avoid running LoadSelectedImage() after
1271 // getting destroyed.
1272 mPendingImageLoadTask
= nullptr;
1274 mResponsiveSelector
= nullptr;
1276 nsImageLoadingContent::Destroy();
1277 nsGenericHTMLElement::DestroyContent();
1280 void HTMLImageElement::MediaFeatureValuesChanged() {
1281 UpdateSourceSyncAndQueueImageTask(false);
1284 bool HTMLImageElement::ShouldLoadImage() const {
1285 return OwnerDoc()->ShouldLoadImages();
1288 void HTMLImageElement::SetLazyLoading() {
1293 // If scripting is disabled don't do lazy load.
1294 // https://whatpr.org/html/3752/images.html#updating-the-image-data
1296 // Same for printing.
1297 Document
* doc
= OwnerDoc();
1298 if (!doc
->IsScriptEnabled() || doc
->IsStaticDocument()) {
1302 doc
->EnsureLazyLoadImageObserver().Observe(*this);
1303 mLazyLoading
= true;
1304 UpdateImageState(true);
1307 void HTMLImageElement::StartLoadingIfNeeded() {
1308 if (!LoadingEnabled() || !ShouldLoadImage()) {
1312 // Use script runner for the case the adopt is from appendChild.
1313 // Bug 1076583 - We still behave synchronously in the non-responsive case
1314 nsContentUtils::AddScriptRunner(
1316 ? NewRunnableMethod
<bool>("dom::HTMLImageElement::QueueImageLoadTask",
1317 this, &HTMLImageElement::QueueImageLoadTask
,
1319 : NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage",
1320 this, &HTMLImageElement::MaybeLoadImage
,
1324 void HTMLImageElement::StopLazyLoading(StartLoading aStartLoading
) {
1325 if (!mLazyLoading
) {
1328 mLazyLoading
= false;
1329 Document
* doc
= OwnerDoc();
1330 if (auto* obs
= doc
->GetLazyLoadImageObserver()) {
1331 obs
->Unobserve(*this);
1334 if (aStartLoading
== StartLoading::Yes
) {
1335 StartLoadingIfNeeded();
1339 const StyleLockedDeclarationBlock
*
1340 HTMLImageElement::GetMappedAttributesFromSource() const {
1341 if (!IsInPicture() || !mResponsiveSelector
) {
1345 const auto* source
=
1346 HTMLSourceElement::FromNodeOrNull(mResponsiveSelector
->Content());
1351 MOZ_ASSERT(IsPreviousSibling(source
, this),
1352 "Incorrect or out-of-date source");
1353 return source
->GetAttributesMappedForImage();
1356 void HTMLImageElement::InvalidateAttributeMapping() {
1357 if (!IsInPicture()) {
1361 nsPresContext
* presContext
= nsContentUtils::GetContextForContent(this);
1366 // Note: Unfortunately, we have to use RESTYLE_SELF, instead of using
1367 // RESTYLE_STYLE_ATTRIBUTE or other ways, to avoid re-selector-match because
1368 // we are using Gecko_GetExtraContentStyleDeclarations() to retrieve the
1369 // extra declaration block from |this|'s width and height attributes, and
1370 // other restyle hints seems not enough.
1371 // FIXME: We may refine this together with the restyle for presentation
1372 // attributes in RestyleManger::AttributeChagned()
1373 presContext
->RestyleManager()->PostRestyleEvent(
1374 this, RestyleHint::RESTYLE_SELF
, nsChangeHint(0));
1377 void HTMLImageElement::SetResponsiveSelector(
1378 RefPtr
<ResponsiveImageSelector
>&& aSource
) {
1379 if (mResponsiveSelector
== aSource
) {
1383 mResponsiveSelector
= std::move(aSource
);
1385 // Invalidate the style if needed.
1386 InvalidateAttributeMapping();
1389 SetDensity(mResponsiveSelector
1390 ? mResponsiveSelector
->GetSelectedImageDensity()
1394 void HTMLImageElement::SetDensity(double aDensity
) {
1395 if (mCurrentDensity
== aDensity
) {
1399 mCurrentDensity
= aDensity
;
1401 // Invalidate the reflow.
1402 if (nsImageFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
1403 f
->ResponsiveContentDensityChanged();
1407 void HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad
) {
1408 RefPtr
<ImageLoadTask
> task
=
1409 new ImageLoadTask(this, aAlwaysLoad
, mUseUrgentStartForChannel
);
1410 // The task checks this to determine if it was the last
1411 // queued event, and so earlier tasks are implicitly canceled.
1412 mPendingImageLoadTask
= task
;
1413 CycleCollectedJSContext::Get()->DispatchToMicroTask(task
.forget());
1416 } // namespace mozilla::dom