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 "mozilla/dom/UnbindContext.h"
14 #include "nsGenericHTMLElement.h"
15 #include "nsGkAtoms.h"
16 #include "nsPresContext.h"
18 #include "mozilla/dom/Document.h"
19 #include "nsImageFrame.h"
20 #include "nsContentUtils.h"
21 #include "nsContainerFrame.h"
22 #include "nsNodeInfoManager.h"
23 #include "mozilla/MouseEvents.h"
24 #include "nsFocusManager.h"
25 #include "mozilla/dom/DOMIntersectionObserver.h"
26 #include "mozilla/dom/HTMLFormElement.h"
27 #include "mozilla/dom/MutationEventBinding.h"
28 #include "mozilla/dom/UserActivation.h"
29 #include "nsAttrValueOrString.h"
30 #include "imgLoader.h"
33 #include "mozilla/dom/HTMLSourceElement.h"
34 #include "mozilla/dom/ResponsiveImageSelector.h"
36 #include "imgINotificationObserver.h"
37 #include "imgRequestProxy.h"
39 #include "mozilla/CycleCollectedJSContext.h"
41 #include "mozilla/EventDispatcher.h"
42 #include "mozilla/MappedDeclarationsBuilder.h"
43 #include "mozilla/Maybe.h"
44 #include "mozilla/RestyleManager.h"
46 #include "nsLayoutUtils.h"
48 using namespace mozilla::net
;
51 NS_IMPL_NS_NEW_HTML_ELEMENT(Image
)
54 // Is aSubject a previous sibling of aNode.
55 static bool IsPreviousSibling(const nsINode
* aSubject
, const nsINode
* aNode
) {
56 if (aSubject
== aNode
) {
60 nsINode
* parent
= aSubject
->GetParentNode();
61 if (parent
&& parent
== aNode
->GetParentNode()) {
62 const Maybe
<uint32_t> indexOfSubject
= parent
->ComputeIndexOf(aSubject
);
63 const Maybe
<uint32_t> indexOfNode
= parent
->ComputeIndexOf(aNode
);
64 if (MOZ_LIKELY(indexOfSubject
.isSome() && indexOfNode
.isSome())) {
65 return *indexOfSubject
< *indexOfNode
;
67 // XXX Keep the odd traditional behavior for now.
68 return indexOfSubject
.isNothing() && indexOfNode
.isSome();
75 namespace mozilla::dom
{
77 // Calls LoadSelectedImage on host element unless it has been superseded or
78 // canceled -- this is the synchronous section of "update the image data".
79 // https://html.spec.whatwg.org/multipage/embedded-content.html#update-the-image-data
80 class ImageLoadTask final
: public MicroTaskRunnable
{
82 ImageLoadTask(HTMLImageElement
* aElement
, bool aAlwaysLoad
,
83 bool aUseUrgentStartForChannel
)
85 mAlwaysLoad(aAlwaysLoad
),
86 mUseUrgentStartForChannel(aUseUrgentStartForChannel
) {
87 mDocument
= aElement
->OwnerDoc();
88 mDocument
->BlockOnload();
91 void Run(AutoSlowOperation
& aAso
) override
{
92 if (mElement
->mPendingImageLoadTask
== this) {
93 mElement
->mPendingImageLoadTask
= nullptr;
94 mElement
->mUseUrgentStartForChannel
= mUseUrgentStartForChannel
;
95 mElement
->LoadSelectedImage(true, true, mAlwaysLoad
);
97 mDocument
->UnblockOnload(false);
100 bool Suppressed() override
{
101 nsIGlobalObject
* global
= mElement
->GetOwnerGlobal();
102 return global
&& global
->IsInSyncOperation();
105 bool AlwaysLoad() const { return mAlwaysLoad
; }
108 ~ImageLoadTask() = default;
109 RefPtr
<HTMLImageElement
> mElement
;
110 nsCOMPtr
<Document
> mDocument
;
113 // True if we want to set nsIClassOfService::UrgentStart to the channel to
114 // get the response ASAP for better user responsiveness.
115 bool mUseUrgentStartForChannel
;
118 HTMLImageElement::HTMLImageElement(
119 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
120 : nsGenericHTMLElement(std::move(aNodeInfo
)) {
121 // We start out broken
122 AddStatesSilently(ElementState::BROKEN
);
125 HTMLImageElement::~HTMLImageElement() { nsImageLoadingContent::Destroy(); }
127 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement
, nsGenericHTMLElement
,
130 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLImageElement
,
131 nsGenericHTMLElement
,
132 nsIImageLoadingContent
,
133 imgINotificationObserver
)
135 NS_IMPL_ELEMENT_CLONE(HTMLImageElement
)
137 bool HTMLImageElement::IsInteractiveHTMLContent() const {
138 return HasAttr(nsGkAtoms::usemap
) ||
139 nsGenericHTMLElement::IsInteractiveHTMLContent();
142 void HTMLImageElement::AsyncEventRunning(AsyncEventDispatcher
* aEvent
) {
143 nsImageLoadingContent::AsyncEventRunning(aEvent
);
146 void HTMLImageElement::GetCurrentSrc(nsAString
& aValue
) {
147 nsCOMPtr
<nsIURI
> currentURI
;
148 GetCurrentURI(getter_AddRefs(currentURI
));
151 currentURI
->GetSpec(spec
);
152 CopyUTF8toUTF16(spec
, aValue
);
154 SetDOMStringToNull(aValue
);
158 bool HTMLImageElement::Draggable() const {
159 // images may be dragged unless the draggable attribute is false
160 return !AttrValueIs(kNameSpaceID_None
, nsGkAtoms::draggable
,
161 nsGkAtoms::_false
, eIgnoreCase
);
164 bool HTMLImageElement::Complete() {
165 // It is still not clear what value should img.complete return in various
166 // cases, see https://github.com/whatwg/html/issues/4884
168 if (!HasAttr(nsGkAtoms::srcset
) && !HasNonEmptyAttr(nsGkAtoms::src
)) {
172 if (!mCurrentRequest
|| mPendingRequest
) {
177 mCurrentRequest
->GetImageStatus(&status
);
179 (imgIRequest::STATUS_LOAD_COMPLETE
| imgIRequest::STATUS_ERROR
)) != 0;
182 CSSIntPoint
HTMLImageElement::GetXY() {
183 nsIFrame
* frame
= GetPrimaryFrame(FlushType::Layout
);
185 return CSSIntPoint(0, 0);
187 return CSSIntPoint::FromAppUnitsRounded(
188 frame
->GetOffsetTo(frame
->PresShell()->GetRootFrame()));
191 int32_t HTMLImageElement::X() { return GetXY().x
; }
193 int32_t HTMLImageElement::Y() { return GetXY().y
; }
195 void HTMLImageElement::GetDecoding(nsAString
& aValue
) {
196 GetEnumAttr(nsGkAtoms::decoding
, kDecodingTableDefault
->tag
, aValue
);
199 already_AddRefed
<Promise
> HTMLImageElement::Decode(ErrorResult
& aRv
) {
200 return nsImageLoadingContent::QueueDecodeAsync(aRv
);
203 bool HTMLImageElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
204 const nsAString
& aValue
,
205 nsIPrincipal
* aMaybeScriptedPrincipal
,
206 nsAttrValue
& aResult
) {
207 if (aNamespaceID
== kNameSpaceID_None
) {
208 if (aAttribute
== nsGkAtoms::align
) {
209 return ParseAlignValue(aValue
, aResult
);
211 if (aAttribute
== nsGkAtoms::crossorigin
) {
212 ParseCORSValue(aValue
, aResult
);
215 if (aAttribute
== nsGkAtoms::decoding
) {
216 return aResult
.ParseEnumValue(aValue
, kDecodingTable
,
217 /* aCaseSensitive = */ false,
218 kDecodingTableDefault
);
220 if (aAttribute
== nsGkAtoms::loading
) {
221 return ParseLoadingAttribute(aValue
, aResult
);
223 if (aAttribute
== nsGkAtoms::fetchpriority
) {
224 ParseFetchPriority(aValue
, aResult
);
227 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
232 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
233 aMaybeScriptedPrincipal
, aResult
);
236 void HTMLImageElement::MapAttributesIntoRule(
237 MappedDeclarationsBuilder
& aBuilder
) {
238 MapImageAlignAttributeInto(aBuilder
);
239 MapImageBorderAttributeInto(aBuilder
);
240 MapImageMarginAttributeInto(aBuilder
);
241 MapImageSizeAttributesInto(aBuilder
, MapAspectRatio::Yes
);
242 MapCommonAttributesInto(aBuilder
);
245 nsChangeHint
HTMLImageElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
246 int32_t aModType
) const {
247 nsChangeHint retval
=
248 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute
, aModType
);
249 if (aAttribute
== nsGkAtoms::usemap
|| aAttribute
== nsGkAtoms::ismap
) {
250 retval
|= nsChangeHint_ReconstructFrame
;
251 } else if (aAttribute
== nsGkAtoms::alt
) {
252 if (aModType
== MutationEvent_Binding::ADDITION
||
253 aModType
== MutationEvent_Binding::REMOVAL
) {
254 retval
|= nsChangeHint_ReconstructFrame
;
261 HTMLImageElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
262 static const MappedAttributeEntry
* const map
[] = {
263 sCommonAttributeMap
, sImageMarginSizeAttributeMap
,
264 sImageBorderAttributeMap
, sImageAlignAttributeMap
};
266 return FindAttributeDependence(aAttribute
, map
);
269 nsMapRuleToAttributesFunc
HTMLImageElement::GetAttributeMappingFunction()
271 return &MapAttributesIntoRule
;
274 void HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
275 const nsAttrValue
* aValue
, bool aNotify
) {
276 if (aNameSpaceID
== kNameSpaceID_None
&& mForm
&&
277 (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
)) {
278 // remove the image from the hashtable as needed
279 if (const auto* old
= GetParsedAttr(aName
); old
&& !old
->IsEmptyString()) {
280 mForm
->RemoveImageElementFromTable(
281 this, nsDependentAtomString(old
->GetAtomValue()));
285 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID
, aName
, aValue
,
289 void HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
290 const nsAttrValue
* aValue
,
291 const nsAttrValue
* aOldValue
,
292 nsIPrincipal
* aMaybeScriptedPrincipal
,
294 if (aNameSpaceID
!= kNameSpaceID_None
) {
295 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID
, aName
, aValue
,
297 aMaybeScriptedPrincipal
, aNotify
);
300 nsAttrValueOrString
attrVal(aValue
);
301 if (aName
== nsGkAtoms::src
) {
303 if (aValue
&& !aValue
->IsEmptyString()) {
304 StringToURI(attrVal
.String(), OwnerDoc(), getter_AddRefs(mSrcURI
));
309 AfterMaybeChangeAttr(aNameSpaceID
, aName
, attrVal
, aOldValue
,
310 aMaybeScriptedPrincipal
, aNotify
);
313 if (mForm
&& (aName
== nsGkAtoms::name
|| aName
== nsGkAtoms::id
) && aValue
&&
314 !aValue
->IsEmptyString()) {
315 // add the image to the hashtable as needed
316 MOZ_ASSERT(aValue
->Type() == nsAttrValue::eAtom
,
317 "Expected atom value for name/id");
318 mForm
->AddImageElementToTable(
319 this, nsDependentAtomString(aValue
->GetAtomValue()));
322 bool forceReload
= false;
324 if (aName
== nsGkAtoms::loading
&& !mLoading
) {
325 if (aValue
&& Loading(aValue
->GetEnumValue()) == Loading::Lazy
) {
327 } else if (aOldValue
&&
328 Loading(aOldValue
->GetEnumValue()) == Loading::Lazy
) {
329 StopLazyLoading(StartLoading::Yes
);
331 } else if (aName
== nsGkAtoms::src
&& !aValue
) {
332 // NOTE: regular src value changes are handled in AfterMaybeChangeAttr, so
333 // this only needs to handle unsetting the src attribute.
334 // Mark channel as urgent-start before load image if the image load is
335 // initaiated by a user interaction.
336 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
338 // AfterMaybeChangeAttr handles setting src since it needs to catch
339 // img.src = img.src, so we only need to handle the unset case
340 if (InResponsiveMode()) {
341 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == this) {
342 mResponsiveSelector
->SetDefaultSource(VoidString());
344 UpdateSourceSyncAndQueueImageTask(true);
346 // Bug 1076583 - We still behave synchronously in the non-responsive case
347 CancelImageRequests(aNotify
);
349 } else if (aName
== nsGkAtoms::srcset
) {
350 // Mark channel as urgent-start before load image if the image load is
351 // initaiated by a user interaction.
352 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
354 mSrcsetTriggeringPrincipal
= aMaybeScriptedPrincipal
;
356 PictureSourceSrcsetChanged(this, attrVal
.String(), aNotify
);
357 } else if (aName
== nsGkAtoms::sizes
) {
358 // Mark channel as urgent-start before load image if the image load is
359 // initiated by a user interaction.
360 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
362 PictureSourceSizesChanged(this, attrVal
.String(), aNotify
);
363 } else if (aName
== nsGkAtoms::decoding
) {
364 // Request sync or async image decoding.
366 aValue
&& static_cast<ImageDecodingType
>(aValue
->GetEnumValue()) ==
367 ImageDecodingType::Sync
);
368 } else if (aName
== nsGkAtoms::referrerpolicy
) {
369 ReferrerPolicy referrerPolicy
= GetReferrerPolicyAsEnum();
370 // FIXME(emilio): Why only when not in responsive mode? Also see below for
372 forceReload
= aNotify
&& !InResponsiveMode() &&
373 referrerPolicy
!= ReferrerPolicy::_empty
&&
374 referrerPolicy
!= ReferrerPolicyFromAttr(aOldValue
);
375 } else if (aName
== nsGkAtoms::crossorigin
) {
376 // FIXME(emilio): The aNotify bit seems a bit suspicious, but it is useful
377 // to avoid extra sync loads, specially in non-responsive mode. Ideally we
378 // can unify the responsive and non-responsive code paths (bug 1076583), and
379 // simplify this a bit.
380 forceReload
= aNotify
&& GetCORSMode() != AttrValueToCORSMode(aOldValue
);
384 // Because we load image synchronously in non-responsive-mode, we need to do
385 // reload after the attribute has been set if the reload is triggered by
386 // cross origin / referrer policy changing.
388 // Mark channel as urgent-start before load image if the image load is
389 // initiated by a user interaction.
390 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
391 if (InResponsiveMode()) {
392 // Per spec, full selection runs when this changes, even though
393 // it doesn't directly affect the source selection
394 UpdateSourceSyncAndQueueImageTask(true);
395 } else if (ShouldLoadImage()) {
396 // Bug 1076583 - We still use the older synchronous algorithm in
397 // non-responsive mode. Force a new load of the image with the
398 // new cross origin policy
399 ForceReload(aNotify
, IgnoreErrors());
403 return nsGenericHTMLElement::AfterSetAttr(
404 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
407 void HTMLImageElement::OnAttrSetButNotChanged(int32_t aNamespaceID
,
409 const nsAttrValueOrString
& aValue
,
411 AfterMaybeChangeAttr(aNamespaceID
, aName
, aValue
, nullptr, nullptr, aNotify
);
412 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID
, aName
,
416 void HTMLImageElement::AfterMaybeChangeAttr(
417 int32_t aNamespaceID
, nsAtom
* aName
, const nsAttrValueOrString
& aValue
,
418 const nsAttrValue
* aOldValue
, nsIPrincipal
* aMaybeScriptedPrincipal
,
420 if (aNamespaceID
!= kNameSpaceID_None
|| aName
!= nsGkAtoms::src
) {
424 // We need to force our image to reload. This must be done here, not in
425 // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is
426 // being set to its existing value, which is normally optimized away as a
429 // If we are in responsive mode, we drop the forced reload behavior,
430 // but still trigger a image load task for img.src = img.src per
433 // Both cases handle unsetting src in AfterSetAttr
434 // Mark channel as urgent-start before load image if the image load is
435 // initaiated by a user interaction.
436 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
438 mSrcTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
439 this, aValue
.String(), aMaybeScriptedPrincipal
);
441 if (InResponsiveMode()) {
442 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == this) {
443 mResponsiveSelector
->SetDefaultSource(mSrcURI
, mSrcTriggeringPrincipal
);
445 UpdateSourceSyncAndQueueImageTask(true);
446 } else if (aNotify
&& ShouldLoadImage()) {
447 // If aNotify is false, we are coming from the parser or some such place;
448 // we'll get bound after all the attributes have been set, so we'll do the
449 // sync image load from BindToTree. Skip the LoadImage call in that case.
451 // Note that this sync behavior is partially removed from the spec, bug
454 // A hack to get animations to reset. See bug 594771.
455 mNewRequestsWillNeedAnimationReset
= true;
457 // Force image loading here, so that we'll try to load the image from
458 // network if it's set to be not cacheable.
459 // Potentially, false could be passed here rather than aNotify since
460 // UpdateState will be called by SetAttrAndNotify, but there are two
461 // obstacles to this: 1) LoadImage will end up calling
462 // UpdateState(aNotify), and we do not want it to call UpdateState(false)
463 // when aNotify is true, and 2) When this function is called by
464 // OnAttrSetButNotChanged, SetAttrAndNotify will not subsequently call
466 LoadSelectedImage(/* aForce = */ true, aNotify
,
467 /* aAlwaysLoad = */ true);
469 mNewRequestsWillNeedAnimationReset
= false;
473 void HTMLImageElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
474 // We handle image element with attribute ismap in its corresponding frame
475 // element. Set mMultipleActionsPrevented here to prevent the click event
476 // trigger the behaviors in Element::PostHandleEventForLinks
477 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
478 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent() && IsMap()) {
479 mouseEvent
->mFlags
.mMultipleActionsPrevented
= true;
481 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
484 nsINode
* HTMLImageElement::GetScopeChainParent() const {
488 return nsGenericHTMLElement::GetScopeChainParent();
491 bool HTMLImageElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
492 int32_t* aTabIndex
) {
493 int32_t tabIndex
= TabIndex();
495 if (IsInComposedDoc() && FindImageMap()) {
496 // Use tab index on individual map areas.
497 *aTabIndex
= (sTabFocusModel
& eTabFocus_linksMask
) ? 0 : -1;
498 // Image map is not focusable itself, but flag as tabbable
499 // so that image map areas get walked into.
500 *aIsFocusable
= false;
504 // Can be in tab order if tabindex >=0 and form controls are tabbable.
505 *aTabIndex
= (sTabFocusModel
& eTabFocus_formElementsMask
) ? tabIndex
: -1;
506 *aIsFocusable
= IsFormControlDefaultFocusable(aWithMouse
) &&
507 (tabIndex
>= 0 || GetTabIndexAttrValue().isSome());
512 nsresult
HTMLImageElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
513 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
514 NS_ENSURE_SUCCESS(rv
, rv
);
516 nsImageLoadingContent::BindToTree(aContext
, aParent
);
520 if (HaveSrcsetOrInPicture()) {
521 if (IsInComposedDoc() && !mInDocResponsiveContent
) {
522 aContext
.OwnerDoc().AddResponsiveContent(this);
523 mInDocResponsiveContent
= true;
526 // Mark channel as urgent-start before load image if the image load is
527 // initaiated by a user interaction.
528 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
530 // Run selection algorithm when an img element is inserted into a document
531 // in order to react to changes in the environment. See note of
532 // https://html.spec.whatwg.org/multipage/embedded-content.html#img-environment-changes
534 // We also do this in PictureSourceAdded() if it is in <picture>, so here
535 // we only need to do if its parent is not <picture>, even if there is no
537 if (!IsInPicture()) {
538 UpdateSourceSyncAndQueueImageTask(false);
540 } else if (!InResponsiveMode() && HasAttr(nsGkAtoms::src
)) {
541 // We skip loading when our attributes were set from parser land,
542 // so trigger a aForce=false load now to check if things changed.
543 // This isn't necessary for responsive mode, since creating the
544 // image load task is asynchronous we don't need to take special
545 // care to avoid doing so when being filled by the parser.
547 // Mark channel as urgent-start before load image if the image load is
548 // initaiated by a user interaction.
549 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
551 // We still act synchronously for the non-responsive case (Bug
552 // 1076583), but still need to delay if it is unsafe to run
555 // If loading is temporarily disabled, don't even launch MaybeLoadImage.
556 // Otherwise MaybeLoadImage may run later when someone has reenabled
558 if (LoadingEnabled() && ShouldLoadImage()) {
559 nsContentUtils::AddScriptRunner(
560 NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage", this,
561 &HTMLImageElement::MaybeLoadImage
, false));
568 void HTMLImageElement::UnbindFromTree(UnbindContext
& aContext
) {
570 if (aContext
.IsUnbindRoot(this) || !FindAncestorForm(mForm
)) {
573 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT
);
577 if (mInDocResponsiveContent
) {
578 OwnerDoc()->RemoveResponsiveContent(this);
579 mInDocResponsiveContent
= false;
582 nsImageLoadingContent::UnbindFromTree();
583 nsGenericHTMLElement::UnbindFromTree(aContext
);
586 void HTMLImageElement::UpdateFormOwner() {
588 mForm
= FindAncestorForm();
591 if (mForm
&& !HasFlag(ADDED_TO_FORM
)) {
592 // Now we need to add ourselves to the form
593 nsAutoString nameVal
, idVal
;
594 GetAttr(nsGkAtoms::name
, nameVal
);
595 GetAttr(nsGkAtoms::id
, idVal
);
597 SetFlags(ADDED_TO_FORM
);
599 mForm
->AddImageElement(this);
601 if (!nameVal
.IsEmpty()) {
602 mForm
->AddImageElementToTable(this, nameVal
);
605 if (!idVal
.IsEmpty()) {
606 mForm
->AddImageElementToTable(this, idVal
);
611 void HTMLImageElement::MaybeLoadImage(bool aAlwaysForceLoad
) {
612 // Our base URI may have changed, or we may have had responsive parameters
613 // change while not bound to the tree. However, at this moment, we should have
614 // updated the responsive source in other places, so we don't have to re-parse
615 // src/srcset here. Just need to LoadImage.
617 // Note, check LoadingEnabled() after LoadImage call.
619 LoadSelectedImage(aAlwaysForceLoad
, /* aNotify */ true, aAlwaysForceLoad
);
621 if (!LoadingEnabled()) {
622 CancelImageRequests(true);
626 void HTMLImageElement::NodeInfoChanged(Document
* aOldDoc
) {
627 nsGenericHTMLElement::NodeInfoChanged(aOldDoc
);
629 // Reparse the URI if needed. Note that we can't check whether we already have
630 // a parsed URI, because it might be null even if we have a valid src
631 // attribute, if we tried to parse with a different base.
634 if (GetAttr(nsGkAtoms::src
, src
) && !src
.IsEmpty()) {
635 StringToURI(src
, OwnerDoc(), getter_AddRefs(mSrcURI
));
639 aOldDoc
->GetLazyLoadObserver()->Unobserve(*this);
640 mLazyLoading
= false;
644 // Run selection algorithm synchronously when an img element's adopting steps
645 // are run, in order to react to changes in the environment, per spec,
646 // https://html.spec.whatwg.org/multipage/images.html#reacting-to-dom-mutations,
648 // https://html.spec.whatwg.org/multipage/images.html#reacting-to-environment-changes.
649 if (InResponsiveMode()) {
650 UpdateResponsiveSource();
653 // Force reload image if adoption steps are run.
654 // If loading is temporarily disabled, don't even launch script runner.
655 // Otherwise script runner may run later when someone has reenabled loading.
656 StartLoadingIfNeeded();
660 already_AddRefed
<HTMLImageElement
> HTMLImageElement::Image(
661 const GlobalObject
& aGlobal
, const Optional
<uint32_t>& aWidth
,
662 const Optional
<uint32_t>& aHeight
, ErrorResult
& aError
) {
663 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
.GetAsSupports());
665 if (!win
|| !(doc
= win
->GetExtantDoc())) {
666 aError
.Throw(NS_ERROR_FAILURE
);
670 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(
671 nsGkAtoms::img
, nullptr, kNameSpaceID_XHTML
, ELEMENT_NODE
);
673 auto* nim
= nodeInfo
->NodeInfoManager();
674 RefPtr
<HTMLImageElement
> img
= new (nim
) HTMLImageElement(nodeInfo
.forget());
676 if (aWidth
.WasPassed()) {
677 img
->SetWidth(aWidth
.Value(), aError
);
678 if (aError
.Failed()) {
682 if (aHeight
.WasPassed()) {
683 img
->SetHeight(aHeight
.Value(), aError
);
684 if (aError
.Failed()) {
693 uint32_t HTMLImageElement::Height() { return GetWidthHeightForImage().height
; }
695 uint32_t HTMLImageElement::Width() { return GetWidthHeightForImage().width
; }
697 nsIntSize
HTMLImageElement::NaturalSize() {
698 if (!mCurrentRequest
) {
702 nsCOMPtr
<imgIContainer
> image
;
703 mCurrentRequest
->GetImage(getter_AddRefs(image
));
709 Unused
<< image
->GetHeight(&size
.height
);
710 Unused
<< image
->GetWidth(&size
.width
);
712 ImageResolution resolution
= image
->GetResolution();
713 // NOTE(emilio): What we implement here matches the image-set() spec, but it's
714 // unclear whether this is the right thing to do, see
715 // https://github.com/whatwg/html/pull/5574#issuecomment-826335244.
716 if (mResponsiveSelector
) {
717 float density
= mResponsiveSelector
->GetSelectedImageDensity();
718 MOZ_ASSERT(density
>= 0.0);
719 resolution
.ScaleBy(density
);
722 resolution
.ApplyTo(size
.width
, size
.height
);
726 nsresult
HTMLImageElement::CopyInnerTo(HTMLImageElement
* aDest
) {
727 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
732 // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), aDest skipped
733 // doing the image load because we passed in false for aNotify. But we
734 // really do want it to do the load, so set it up to happen once the cloning
735 // reaches a stable state.
736 if (!aDest
->InResponsiveMode() && aDest
->HasAttr(nsGkAtoms::src
) &&
737 aDest
->ShouldLoadImage()) {
738 // Mark channel as urgent-start before load image if the image load is
739 // initaiated by a user interaction.
740 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
742 nsContentUtils::AddScriptRunner(
743 NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage", aDest
,
744 &HTMLImageElement::MaybeLoadImage
, false));
750 CORSMode
HTMLImageElement::GetCORSMode() {
751 return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin
));
754 JSObject
* HTMLImageElement::WrapNode(JSContext
* aCx
,
755 JS::Handle
<JSObject
*> aGivenProto
) {
756 return HTMLImageElement_Binding::Wrap(aCx
, this, aGivenProto
);
760 HTMLFormElement
* HTMLImageElement::GetForm() const { return mForm
; }
763 void HTMLImageElement::SetForm(HTMLFormElement
* aForm
) {
764 MOZ_ASSERT(aForm
, "Don't pass null here");
766 "We don't support switching from one non-null form to another.");
771 void HTMLImageElement::ClearForm(bool aRemoveFromForm
) {
772 NS_ASSERTION((mForm
!= nullptr) == HasFlag(ADDED_TO_FORM
),
773 "Form control should have had flag set correctly");
779 if (aRemoveFromForm
) {
780 nsAutoString nameVal
, idVal
;
781 GetAttr(nsGkAtoms::name
, nameVal
);
782 GetAttr(nsGkAtoms::id
, idVal
);
784 mForm
->RemoveImageElement(this);
786 if (!nameVal
.IsEmpty()) {
787 mForm
->RemoveImageElementFromTable(this, nameVal
);
790 if (!idVal
.IsEmpty()) {
791 mForm
->RemoveImageElementFromTable(this, idVal
);
795 UnsetFlags(ADDED_TO_FORM
);
799 void HTMLImageElement::UpdateSourceSyncAndQueueImageTask(
800 bool aAlwaysLoad
, const HTMLSourceElement
* aSkippedSource
) {
801 // Per spec, when updating the image data or reacting to environment
802 // changes, we always run the full selection (including selecting the source
803 // element and the best fit image from srcset) even if it doesn't directly
804 // affect the source selection.
806 // However, in the spec of updating the image data, the selection of image
807 // source URL is in the asynchronous part (i.e. in a microtask), and so this
808 // doesn't guarantee that the image style is correct after we flush the style
809 // synchornously. So here we update the responsive source synchronously always
810 // to make sure the image source is always up-to-date after each DOM mutation.
811 // Spec issue: https://github.com/whatwg/html/issues/8207.
812 const bool changed
= UpdateResponsiveSource(aSkippedSource
);
814 // If loading is temporarily disabled, we don't want to queue tasks
815 // that may then run when loading is re-enabled.
816 if (!LoadingEnabled() || !ShouldLoadImage()) {
820 // Ensure that we don't overwrite a previous load request that requires
821 // a complete load to occur.
822 bool alwaysLoad
= aAlwaysLoad
;
823 if (mPendingImageLoadTask
) {
824 alwaysLoad
= alwaysLoad
|| mPendingImageLoadTask
->AlwaysLoad();
827 if (!changed
&& !alwaysLoad
) {
831 QueueImageLoadTask(alwaysLoad
);
834 bool HTMLImageElement::HaveSrcsetOrInPicture() {
835 if (HasAttr(nsGkAtoms::srcset
)) {
839 return IsInPicture();
842 bool HTMLImageElement::InResponsiveMode() {
843 // When we lose srcset or leave a <picture> element, the fallback to img.src
844 // will happen from the microtask, and we should behave responsively in the
846 return mResponsiveSelector
|| mPendingImageLoadTask
||
847 HaveSrcsetOrInPicture();
850 bool HTMLImageElement::SelectedSourceMatchesLast(nsIURI
* aSelectedSource
) {
851 // If there was no selected source previously, we don't want to short-circuit
852 // the load. Similarly for if there is no newly selected source.
853 if (!mLastSelectedSource
|| !aSelectedSource
) {
857 return NS_SUCCEEDED(mLastSelectedSource
->Equals(aSelectedSource
, &equal
)) &&
861 nsresult
HTMLImageElement::LoadSelectedImage(bool aForce
, bool aNotify
,
863 // In responsive mode, we have to make sure we ran the full selection algrithm
864 // before loading the selected image.
865 // Use this assertion to catch any cases we missed.
866 MOZ_ASSERT(!UpdateResponsiveSource(),
867 "The image source should be the same because we update the "
868 "responsive source synchronously");
870 // The density is default to 1.0 for the src attribute case.
871 double currentDensity
= mResponsiveSelector
872 ? mResponsiveSelector
->GetSelectedImageDensity()
875 nsCOMPtr
<nsIURI
> selectedSource
;
876 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
877 ImageLoadType type
= eImageLoadType_Normal
;
879 if (mResponsiveSelector
) {
880 selectedSource
= mResponsiveSelector
->GetSelectedImageURL();
881 triggeringPrincipal
=
882 mResponsiveSelector
->GetSelectedImageTriggeringPrincipal();
883 type
= eImageLoadType_Imageset
;
884 } else if (mSrcURI
|| HasAttr(nsGkAtoms::src
)) {
887 selectedSource
= mSrcURI
;
888 if (HaveSrcsetOrInPicture()) {
889 // If we have a srcset attribute or are in a <picture> element, we
890 // always use the Imageset load type, even if we parsed no valid
891 // responsive sources from either, per spec.
892 type
= eImageLoadType_Imageset
;
894 triggeringPrincipal
= mSrcTriggeringPrincipal
;
898 if (!aAlwaysLoad
&& SelectedSourceMatchesLast(selectedSource
)) {
899 // Update state when only density may have changed (i.e., the source to load
900 // hasn't changed, and we don't do any request at all). We need (apart from
901 // updating our internal state) to tell the image frame because its
902 // intrinsic size may have changed.
904 // In the case we actually trigger a new load, that load will trigger a call
905 // to nsImageFrame::NotifyNewCurrentRequest, which takes care of that for
907 SetDensity(currentDensity
);
911 // Before we actually defer the lazy-loading
913 if (!selectedSource
||
914 !nsContentUtils::IsImageAvailable(this, selectedSource
,
915 triggeringPrincipal
, GetCORSMode())) {
918 StopLazyLoading(StartLoading::No
);
921 nsresult rv
= NS_ERROR_FAILURE
;
923 // src triggers an error event on invalid URI, unlike other loads.
924 if (selectedSource
|| hasSrc
) {
925 rv
= LoadImage(selectedSource
, aForce
, aNotify
, type
, triggeringPrincipal
);
928 mLastSelectedSource
= selectedSource
;
929 mCurrentDensity
= currentDensity
;
932 CancelImageRequests(aNotify
);
937 void HTMLImageElement::PictureSourceSrcsetChanged(nsIContent
* aSourceNode
,
938 const nsAString
& aNewValue
,
940 MOZ_ASSERT(aSourceNode
== this || IsPreviousSibling(aSourceNode
, this),
941 "Should not be getting notifications for non-previous-siblings");
943 nsIContent
* currentSrc
=
944 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
946 if (aSourceNode
== currentSrc
) {
947 // We're currently using this node as our responsive selector
949 nsCOMPtr
<nsIPrincipal
> principal
;
950 if (aSourceNode
== this) {
951 principal
= mSrcsetTriggeringPrincipal
;
952 } else if (auto* source
= HTMLSourceElement::FromNode(aSourceNode
)) {
953 principal
= source
->GetSrcsetTriggeringPrincipal();
955 mResponsiveSelector
->SetCandidatesFromSourceSet(aNewValue
, principal
);
958 if (!mInDocResponsiveContent
&& IsInComposedDoc()) {
959 OwnerDoc()->AddResponsiveContent(this);
960 mInDocResponsiveContent
= true;
963 // This always triggers the image update steps per the spec, even if
964 // we are not using this source.
965 UpdateSourceSyncAndQueueImageTask(true);
968 void HTMLImageElement::PictureSourceSizesChanged(nsIContent
* aSourceNode
,
969 const nsAString
& aNewValue
,
971 MOZ_ASSERT(aSourceNode
== this || IsPreviousSibling(aSourceNode
, this),
972 "Should not be getting notifications for non-previous-siblings");
974 nsIContent
* currentSrc
=
975 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
977 if (aSourceNode
== currentSrc
) {
978 // We're currently using this node as our responsive selector
980 mResponsiveSelector
->SetSizesFromDescriptor(aNewValue
);
983 // This always triggers the image update steps per the spec, even if
984 // we are not using this source.
985 UpdateSourceSyncAndQueueImageTask(true);
988 void HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent
* aSourceNode
,
990 MOZ_ASSERT(IsPreviousSibling(aSourceNode
, this),
991 "Should not be getting notifications for non-previous-siblings");
993 // This always triggers the image update steps per the spec, even if
994 // we are not switching to/from this source
995 UpdateSourceSyncAndQueueImageTask(true);
998 void HTMLImageElement::PictureSourceDimensionChanged(
999 HTMLSourceElement
* aSourceNode
, bool aNotify
) {
1000 MOZ_ASSERT(IsPreviousSibling(aSourceNode
, this),
1001 "Should not be getting notifications for non-previous-siblings");
1003 // "width" and "height" affect the dimension of images, but they don't have
1004 // impact on the selection of <source> elements. In other words,
1005 // UpdateResponsiveSource doesn't change the source, so all we need to do is
1006 // just request restyle.
1007 if (mResponsiveSelector
&& mResponsiveSelector
->Content() == aSourceNode
) {
1008 InvalidateAttributeMapping();
1012 void HTMLImageElement::PictureSourceAdded(HTMLSourceElement
* aSourceNode
) {
1013 MOZ_ASSERT(!aSourceNode
|| IsPreviousSibling(aSourceNode
, this),
1014 "Should not be getting notifications for non-previous-siblings");
1016 UpdateSourceSyncAndQueueImageTask(true);
1019 void HTMLImageElement::PictureSourceRemoved(HTMLSourceElement
* aSourceNode
) {
1020 MOZ_ASSERT(!aSourceNode
|| IsPreviousSibling(aSourceNode
, this),
1021 "Should not be getting notifications for non-previous-siblings");
1023 UpdateSourceSyncAndQueueImageTask(true, aSourceNode
);
1026 bool HTMLImageElement::UpdateResponsiveSource(
1027 const HTMLSourceElement
* aSkippedSource
) {
1028 bool hadSelector
= !!mResponsiveSelector
;
1030 nsIContent
* currentSource
=
1031 mResponsiveSelector
? mResponsiveSelector
->Content() : nullptr;
1033 // Walk source nodes previous to ourselves if IsInPicture().
1034 nsINode
* candidateSource
=
1035 IsInPicture() ? GetParentElement()->GetFirstChild() : this;
1037 // Initialize this as nullptr so we don't have to nullify it when runing out
1038 // of siblings without finding ourself, e.g. XBL magic.
1039 RefPtr
<ResponsiveImageSelector
> newResponsiveSelector
= nullptr;
1041 for (; candidateSource
; candidateSource
= candidateSource
->GetNextSibling()) {
1042 if (aSkippedSource
== candidateSource
) {
1046 if (candidateSource
== currentSource
) {
1047 // found no better source before current, re-run selection on
1048 // that and keep it if it's still usable.
1049 bool changed
= mResponsiveSelector
->SelectImage(true);
1050 if (mResponsiveSelector
->NumCandidates()) {
1051 bool isUsableCandidate
= true;
1053 // an otherwise-usable source element may still have a media query that
1054 // may not match any more.
1055 if (candidateSource
->IsHTMLElement(nsGkAtoms::source
) &&
1056 !SourceElementMatches(candidateSource
->AsElement())) {
1057 isUsableCandidate
= false;
1060 if (isUsableCandidate
) {
1061 // We are still using the current source, but the selected image may
1062 // be changed, so always set the density from the selected image.
1063 SetDensity(mResponsiveSelector
->GetSelectedImageDensity());
1069 newResponsiveSelector
= nullptr;
1070 if (candidateSource
== this) {
1071 // No further possibilities
1074 } else if (candidateSource
== this) {
1075 // We are the last possible source
1076 newResponsiveSelector
=
1077 TryCreateResponsiveSelector(candidateSource
->AsElement());
1079 } else if (auto* source
= HTMLSourceElement::FromNode(candidateSource
)) {
1080 if (RefPtr
<ResponsiveImageSelector
> selector
=
1081 TryCreateResponsiveSelector(source
)) {
1082 newResponsiveSelector
= selector
.forget();
1083 // This led to a valid source, stop
1089 // If we reach this point, either:
1090 // - there was no selector originally, and there is not one now
1091 // - there was no selector originally, and there is one now
1092 // - there was a selector, and there is a different one now
1093 // - there was a selector, and there is not one now
1094 SetResponsiveSelector(std::move(newResponsiveSelector
));
1095 return hadSelector
|| mResponsiveSelector
;
1099 bool HTMLImageElement::SupportedPictureSourceType(const nsAString
& aType
) {
1101 nsAutoString params
;
1103 nsContentUtils::SplitMimeType(aType
, type
, params
);
1104 if (type
.IsEmpty()) {
1108 return imgLoader::SupportImageWithMimeType(
1109 NS_ConvertUTF16toUTF8(type
), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS
);
1112 bool HTMLImageElement::SourceElementMatches(Element
* aSourceElement
) {
1113 MOZ_ASSERT(aSourceElement
->IsHTMLElement(nsGkAtoms::source
));
1115 MOZ_ASSERT(IsInPicture());
1116 MOZ_ASSERT(IsPreviousSibling(aSourceElement
, this));
1118 // Check media and type
1119 auto* src
= static_cast<HTMLSourceElement
*>(aSourceElement
);
1120 if (!src
->MatchesCurrentMedia()) {
1125 return !src
->GetAttr(nsGkAtoms::type
, type
) ||
1126 SupportedPictureSourceType(type
);
1129 already_AddRefed
<ResponsiveImageSelector
>
1130 HTMLImageElement::TryCreateResponsiveSelector(Element
* aSourceElement
) {
1131 nsCOMPtr
<nsIPrincipal
> principal
;
1133 // Skip if this is not a <source> with matching media query
1134 bool isSourceTag
= aSourceElement
->IsHTMLElement(nsGkAtoms::source
);
1136 if (!SourceElementMatches(aSourceElement
)) {
1139 auto* source
= HTMLSourceElement::FromNode(aSourceElement
);
1140 principal
= source
->GetSrcsetTriggeringPrincipal();
1141 } else if (aSourceElement
->IsHTMLElement(nsGkAtoms::img
)) {
1142 // Otherwise this is the <img> tag itself
1143 MOZ_ASSERT(aSourceElement
== this);
1144 principal
= mSrcsetTriggeringPrincipal
;
1147 // Skip if has no srcset or an empty srcset
1149 if (!aSourceElement
->GetAttr(nsGkAtoms::srcset
, srcset
)) {
1153 if (srcset
.IsEmpty()) {
1158 RefPtr
<ResponsiveImageSelector
> sel
=
1159 new ResponsiveImageSelector(aSourceElement
);
1160 if (!sel
->SetCandidatesFromSourceSet(srcset
, principal
)) {
1161 // No possible candidates, don't need to bother parsing sizes
1166 aSourceElement
->GetAttr(nsGkAtoms::sizes
, sizes
);
1167 sel
->SetSizesFromDescriptor(sizes
);
1169 // If this is the <img> tag, also pull in src as the default source
1171 MOZ_ASSERT(aSourceElement
== this);
1173 sel
->SetDefaultSource(mSrcURI
, mSrcTriggeringPrincipal
);
1177 return sel
.forget();
1181 bool HTMLImageElement::SelectSourceForTagWithAttrs(
1182 Document
* aDocument
, bool aIsSourceTag
, const nsAString
& aSrcAttr
,
1183 const nsAString
& aSrcsetAttr
, const nsAString
& aSizesAttr
,
1184 const nsAString
& aTypeAttr
, const nsAString
& aMediaAttr
,
1185 nsAString
& aResult
) {
1186 MOZ_ASSERT(aIsSourceTag
|| (aTypeAttr
.IsEmpty() && aMediaAttr
.IsEmpty()),
1187 "Passing type or media attrs makes no sense without aIsSourceTag");
1188 MOZ_ASSERT(!aIsSourceTag
|| aSrcAttr
.IsEmpty(),
1189 "Passing aSrcAttr makes no sense with aIsSourceTag set");
1191 if (aSrcsetAttr
.IsEmpty()) {
1192 if (!aIsSourceTag
) {
1193 // For an <img> with no srcset, we would always select the src attr.
1194 aResult
.Assign(aSrcAttr
);
1197 // Otherwise, a <source> without srcset is never selected
1201 // Would not consider source tags with unsupported media or type
1203 ((!aMediaAttr
.IsVoid() && !HTMLSourceElement::WouldMatchMediaForDocument(
1204 aMediaAttr
, aDocument
)) ||
1205 (!aTypeAttr
.IsVoid() && !SupportedPictureSourceType(aTypeAttr
)))) {
1209 // Using srcset or picture <source>, build a responsive selector for this tag.
1210 RefPtr
<ResponsiveImageSelector
> sel
= new ResponsiveImageSelector(aDocument
);
1212 sel
->SetCandidatesFromSourceSet(aSrcsetAttr
);
1213 if (!aSizesAttr
.IsEmpty()) {
1214 sel
->SetSizesFromDescriptor(aSizesAttr
);
1216 if (!aIsSourceTag
) {
1217 sel
->SetDefaultSource(aSrcAttr
);
1220 if (sel
->GetSelectedImageURLSpec(aResult
)) {
1224 if (!aIsSourceTag
) {
1225 // <img> tag with no match would definitively load nothing.
1230 // <source> tags with no match would leave source yet-undetermined.
1234 void HTMLImageElement::DestroyContent() {
1235 // Clear mPendingImageLoadTask to avoid running LoadSelectedImage() after
1236 // getting destroyed.
1237 mPendingImageLoadTask
= nullptr;
1239 mResponsiveSelector
= nullptr;
1241 nsImageLoadingContent::Destroy();
1242 nsGenericHTMLElement::DestroyContent();
1245 void HTMLImageElement::MediaFeatureValuesChanged() {
1246 UpdateSourceSyncAndQueueImageTask(false);
1249 bool HTMLImageElement::ShouldLoadImage() const {
1250 return OwnerDoc()->ShouldLoadImages();
1253 void HTMLImageElement::SetLazyLoading() {
1258 // If scripting is disabled don't do lazy load.
1259 // https://whatpr.org/html/3752/images.html#updating-the-image-data
1261 // Same for printing.
1262 Document
* doc
= OwnerDoc();
1263 if (!doc
->IsScriptEnabled() || doc
->IsStaticDocument()) {
1267 doc
->EnsureLazyLoadObserver().Observe(*this);
1268 mLazyLoading
= true;
1269 UpdateImageState(true);
1272 void HTMLImageElement::StartLoadingIfNeeded() {
1273 if (!LoadingEnabled() || !ShouldLoadImage()) {
1277 // Use script runner for the case the adopt is from appendChild.
1278 // Bug 1076583 - We still behave synchronously in the non-responsive case
1279 nsContentUtils::AddScriptRunner(
1281 ? NewRunnableMethod
<bool>("dom::HTMLImageElement::QueueImageLoadTask",
1282 this, &HTMLImageElement::QueueImageLoadTask
,
1284 : NewRunnableMethod
<bool>("dom::HTMLImageElement::MaybeLoadImage",
1285 this, &HTMLImageElement::MaybeLoadImage
,
1289 void HTMLImageElement::StopLazyLoading(StartLoading aStartLoading
) {
1290 if (!mLazyLoading
) {
1293 mLazyLoading
= false;
1294 Document
* doc
= OwnerDoc();
1295 if (auto* obs
= doc
->GetLazyLoadObserver()) {
1296 obs
->Unobserve(*this);
1299 if (aStartLoading
== StartLoading::Yes
) {
1300 StartLoadingIfNeeded();
1304 const StyleLockedDeclarationBlock
*
1305 HTMLImageElement::GetMappedAttributesFromSource() const {
1306 if (!IsInPicture() || !mResponsiveSelector
) {
1310 const auto* source
=
1311 HTMLSourceElement::FromNodeOrNull(mResponsiveSelector
->Content());
1316 MOZ_ASSERT(IsPreviousSibling(source
, this),
1317 "Incorrect or out-of-date source");
1318 return source
->GetAttributesMappedForImage();
1321 void HTMLImageElement::InvalidateAttributeMapping() {
1322 if (!IsInPicture()) {
1326 nsPresContext
* presContext
= nsContentUtils::GetContextForContent(this);
1331 // Note: Unfortunately, we have to use RESTYLE_SELF, instead of using
1332 // RESTYLE_STYLE_ATTRIBUTE or other ways, to avoid re-selector-match because
1333 // we are using Gecko_GetExtraContentStyleDeclarations() to retrieve the
1334 // extra declaration block from |this|'s width and height attributes, and
1335 // other restyle hints seems not enough.
1336 // FIXME: We may refine this together with the restyle for presentation
1337 // attributes in RestyleManger::AttributeChagned()
1338 presContext
->RestyleManager()->PostRestyleEvent(
1339 this, RestyleHint::RESTYLE_SELF
, nsChangeHint(0));
1342 void HTMLImageElement::SetResponsiveSelector(
1343 RefPtr
<ResponsiveImageSelector
>&& aSource
) {
1344 if (mResponsiveSelector
== aSource
) {
1348 mResponsiveSelector
= std::move(aSource
);
1350 // Invalidate the style if needed.
1351 InvalidateAttributeMapping();
1354 SetDensity(mResponsiveSelector
1355 ? mResponsiveSelector
->GetSelectedImageDensity()
1359 void HTMLImageElement::SetDensity(double aDensity
) {
1360 if (mCurrentDensity
== aDensity
) {
1364 mCurrentDensity
= aDensity
;
1366 // Invalidate the reflow.
1367 if (nsImageFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
1368 f
->ResponsiveContentDensityChanged();
1372 void HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad
) {
1373 RefPtr
<ImageLoadTask
> task
=
1374 new ImageLoadTask(this, aAlwaysLoad
, mUseUrgentStartForChannel
);
1375 // The task checks this to determine if it was the last
1376 // queued event, and so earlier tasks are implicitly canceled.
1377 mPendingImageLoadTask
= task
;
1378 CycleCollectedJSContext::Get()->DispatchToMicroTask(task
.forget());
1381 FetchPriority
HTMLImageElement::GetFetchPriorityForImage() const {
1382 return nsGenericHTMLElement::GetFetchPriority();
1385 } // namespace mozilla::dom