Bug 1880216 - Migrate Fenix docs into Sphinx. r=owlish,geckoview-reviewers,android...
[gecko.git] / dom / html / HTMLImageElement.cpp
blob691216d152740dbb4359bac774ccc440b711f2d9
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"
17 #include "nsSize.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"
32 // Responsive images!
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;
49 using mozilla::Maybe;
51 NS_IMPL_NS_NEW_HTML_ELEMENT(Image)
53 #ifdef DEBUG
54 // Is aSubject a previous sibling of aNode.
55 static bool IsPreviousSibling(const nsINode* aSubject, const nsINode* aNode) {
56 if (aSubject == aNode) {
57 return false;
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();
71 return false;
73 #endif
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 {
81 public:
82 ImageLoadTask(HTMLImageElement* aElement, bool aAlwaysLoad,
83 bool aUseUrgentStartForChannel)
84 : mElement(aElement),
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; }
107 private:
108 ~ImageLoadTask() = default;
109 RefPtr<HTMLImageElement> mElement;
110 nsCOMPtr<Document> mDocument;
111 bool mAlwaysLoad;
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,
128 mResponsiveSelector)
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));
149 if (currentURI) {
150 nsAutoCString spec;
151 currentURI->GetSpec(spec);
152 CopyUTF8toUTF16(spec, aValue);
153 } else {
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)) {
169 return true;
172 if (!mCurrentRequest || mPendingRequest) {
173 return false;
176 uint32_t status;
177 mCurrentRequest->GetImageStatus(&status);
178 return (status &
179 (imgIRequest::STATUS_LOAD_COMPLETE | imgIRequest::STATUS_ERROR)) != 0;
182 CSSIntPoint HTMLImageElement::GetXY() {
183 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
184 if (!frame) {
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);
213 return true;
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);
225 return true;
227 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
228 return true;
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;
257 return retval;
260 NS_IMETHODIMP_(bool)
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()
270 const {
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,
286 aNotify);
289 void HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
290 const nsAttrValue* aValue,
291 const nsAttrValue* aOldValue,
292 nsIPrincipal* aMaybeScriptedPrincipal,
293 bool aNotify) {
294 if (aNameSpaceID != kNameSpaceID_None) {
295 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
296 aOldValue,
297 aMaybeScriptedPrincipal, aNotify);
300 nsAttrValueOrString attrVal(aValue);
301 if (aName == nsGkAtoms::src) {
302 mSrcURI = nullptr;
303 if (aValue && !aValue->IsEmptyString()) {
304 StringToURI(attrVal.String(), OwnerDoc(), getter_AddRefs(mSrcURI));
308 if (aValue) {
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) {
326 SetLazyLoading();
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);
345 } else {
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.
365 SetSyncDecodingHint(
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
371 // aNotify.
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);
383 if (forceReload) {
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,
408 nsAtom* aName,
409 const nsAttrValueOrString& aValue,
410 bool aNotify) {
411 AfterMaybeChangeAttr(aNamespaceID, aName, aValue, nullptr, nullptr, aNotify);
412 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
413 aValue, aNotify);
416 void HTMLImageElement::AfterMaybeChangeAttr(
417 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue,
418 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal,
419 bool aNotify) {
420 if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::src) {
421 return;
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
427 // no-op.
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
431 // spec.
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
452 // 1076583
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
465 // UpdateState.
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 {
485 if (mForm) {
486 return mForm;
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;
501 return 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());
509 return false;
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);
518 UpdateFormOwner();
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
536 // <source>.
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
553 // script.
555 // If loading is temporarily disabled, don't even launch MaybeLoadImage.
556 // Otherwise MaybeLoadImage may run later when someone has reenabled
557 // loading.
558 if (LoadingEnabled() && ShouldLoadImage()) {
559 nsContentUtils::AddScriptRunner(
560 NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage", this,
561 &HTMLImageElement::MaybeLoadImage, false));
565 return rv;
568 void HTMLImageElement::UnbindFromTree(UnbindContext& aContext) {
569 if (mForm) {
570 if (aContext.IsUnbindRoot(this) || !FindAncestorForm(mForm)) {
571 ClearForm(true);
572 } else {
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() {
587 if (!mForm) {
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.
632 mSrcURI = nullptr;
633 nsAutoString src;
634 if (GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) {
635 StringToURI(src, OwnerDoc(), getter_AddRefs(mSrcURI));
638 if (mLazyLoading) {
639 aOldDoc->GetLazyLoadObserver()->Unobserve(*this);
640 mLazyLoading = false;
641 SetLazyLoading();
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,
647 // and
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();
659 // static
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());
664 Document* doc;
665 if (!win || !(doc = win->GetExtantDoc())) {
666 aError.Throw(NS_ERROR_FAILURE);
667 return nullptr;
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()) {
679 return nullptr;
682 if (aHeight.WasPassed()) {
683 img->SetHeight(aHeight.Value(), aError);
684 if (aError.Failed()) {
685 return nullptr;
690 return img.forget();
693 uint32_t HTMLImageElement::Height() { return GetWidthHeightForImage().height; }
695 uint32_t HTMLImageElement::Width() { return GetWidthHeightForImage().width; }
697 nsIntSize HTMLImageElement::NaturalSize() {
698 if (!mCurrentRequest) {
699 return {};
702 nsCOMPtr<imgIContainer> image;
703 mCurrentRequest->GetImage(getter_AddRefs(image));
704 if (!image) {
705 return {};
708 nsIntSize size;
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);
723 return size;
726 nsresult HTMLImageElement::CopyInnerTo(HTMLImageElement* aDest) {
727 nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
728 if (NS_FAILED(rv)) {
729 return rv;
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));
747 return NS_OK;
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);
759 #ifdef DEBUG
760 HTMLFormElement* HTMLImageElement::GetForm() const { return mForm; }
761 #endif
763 void HTMLImageElement::SetForm(HTMLFormElement* aForm) {
764 MOZ_ASSERT(aForm, "Don't pass null here");
765 NS_ASSERTION(!mForm,
766 "We don't support switching from one non-null form to another.");
768 mForm = aForm;
771 void HTMLImageElement::ClearForm(bool aRemoveFromForm) {
772 NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM),
773 "Form control should have had flag set correctly");
775 if (!mForm) {
776 return;
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);
796 mForm = nullptr;
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()) {
817 return;
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) {
828 return;
831 QueueImageLoadTask(alwaysLoad);
834 bool HTMLImageElement::HaveSrcsetOrInPicture() {
835 if (HasAttr(nsGkAtoms::srcset)) {
836 return true;
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
845 // interim
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) {
854 return false;
856 bool equal = false;
857 return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) &&
858 equal;
861 nsresult HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify,
862 bool aAlwaysLoad) {
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()
873 : 1.0;
875 nsCOMPtr<nsIURI> selectedSource;
876 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
877 ImageLoadType type = eImageLoadType_Normal;
878 bool hasSrc = false;
879 if (mResponsiveSelector) {
880 selectedSource = mResponsiveSelector->GetSelectedImageURL();
881 triggeringPrincipal =
882 mResponsiveSelector->GetSelectedImageTriggeringPrincipal();
883 type = eImageLoadType_Imageset;
884 } else if (mSrcURI || HasAttr(nsGkAtoms::src)) {
885 hasSrc = true;
886 if (mSrcURI) {
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
906 // us.
907 SetDensity(currentDensity);
908 return NS_OK;
911 // Before we actually defer the lazy-loading
912 if (mLazyLoading) {
913 if (!selectedSource ||
914 !nsContentUtils::IsImageAvailable(this, selectedSource,
915 triggeringPrincipal, GetCORSMode())) {
916 return NS_OK;
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;
931 if (NS_FAILED(rv)) {
932 CancelImageRequests(aNotify);
934 return rv;
937 void HTMLImageElement::PictureSourceSrcsetChanged(nsIContent* aSourceNode,
938 const nsAString& aNewValue,
939 bool aNotify) {
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
948 // source.
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,
970 bool aNotify) {
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
979 // source.
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,
989 bool aNotify) {
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) {
1043 continue;
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());
1064 return changed;
1068 // no longer valid
1069 newResponsiveSelector = nullptr;
1070 if (candidateSource == this) {
1071 // No further possibilities
1072 break;
1074 } else if (candidateSource == this) {
1075 // We are the last possible source
1076 newResponsiveSelector =
1077 TryCreateResponsiveSelector(candidateSource->AsElement());
1078 break;
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
1084 break;
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;
1098 /*static */
1099 bool HTMLImageElement::SupportedPictureSourceType(const nsAString& aType) {
1100 nsAutoString type;
1101 nsAutoString params;
1103 nsContentUtils::SplitMimeType(aType, type, params);
1104 if (type.IsEmpty()) {
1105 return true;
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()) {
1121 return false;
1124 nsAutoString type;
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);
1135 if (isSourceTag) {
1136 if (!SourceElementMatches(aSourceElement)) {
1137 return nullptr;
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
1148 nsString srcset;
1149 if (!aSourceElement->GetAttr(nsGkAtoms::srcset, srcset)) {
1150 return nullptr;
1153 if (srcset.IsEmpty()) {
1154 return nullptr;
1157 // Try to parse
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
1162 return nullptr;
1165 nsAutoString 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
1170 if (!isSourceTag) {
1171 MOZ_ASSERT(aSourceElement == this);
1172 if (mSrcURI) {
1173 sel->SetDefaultSource(mSrcURI, mSrcTriggeringPrincipal);
1177 return sel.forget();
1180 /* static */
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);
1195 return true;
1197 // Otherwise, a <source> without srcset is never selected
1198 return false;
1201 // Would not consider source tags with unsupported media or type
1202 if (aIsSourceTag &&
1203 ((!aMediaAttr.IsVoid() && !HTMLSourceElement::WouldMatchMediaForDocument(
1204 aMediaAttr, aDocument)) ||
1205 (!aTypeAttr.IsVoid() && !SupportedPictureSourceType(aTypeAttr)))) {
1206 return false;
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)) {
1221 return true;
1224 if (!aIsSourceTag) {
1225 // <img> tag with no match would definitively load nothing.
1226 aResult.Truncate();
1227 return true;
1230 // <source> tags with no match would leave source yet-undetermined.
1231 return false;
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() {
1254 if (mLazyLoading) {
1255 return;
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()) {
1264 return;
1267 doc->EnsureLazyLoadObserver().Observe(*this);
1268 mLazyLoading = true;
1269 UpdateImageState(true);
1272 void HTMLImageElement::StartLoadingIfNeeded() {
1273 if (!LoadingEnabled() || !ShouldLoadImage()) {
1274 return;
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(
1280 InResponsiveMode()
1281 ? NewRunnableMethod<bool>("dom::HTMLImageElement::QueueImageLoadTask",
1282 this, &HTMLImageElement::QueueImageLoadTask,
1283 true)
1284 : NewRunnableMethod<bool>("dom::HTMLImageElement::MaybeLoadImage",
1285 this, &HTMLImageElement::MaybeLoadImage,
1286 true));
1289 void HTMLImageElement::StopLazyLoading(StartLoading aStartLoading) {
1290 if (!mLazyLoading) {
1291 return;
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) {
1307 return nullptr;
1310 const auto* source =
1311 HTMLSourceElement::FromNodeOrNull(mResponsiveSelector->Content());
1312 if (!source) {
1313 return nullptr;
1316 MOZ_ASSERT(IsPreviousSibling(source, this),
1317 "Incorrect or out-of-date source");
1318 return source->GetAttributesMappedForImage();
1321 void HTMLImageElement::InvalidateAttributeMapping() {
1322 if (!IsInPicture()) {
1323 return;
1326 nsPresContext* presContext = nsContentUtils::GetContextForContent(this);
1327 if (!presContext) {
1328 return;
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) {
1345 return;
1348 mResponsiveSelector = std::move(aSource);
1350 // Invalidate the style if needed.
1351 InvalidateAttributeMapping();
1353 // Update density.
1354 SetDensity(mResponsiveSelector
1355 ? mResponsiveSelector->GetSelectedImageDensity()
1356 : 1.0);
1359 void HTMLImageElement::SetDensity(double aDensity) {
1360 if (mCurrentDensity == aDensity) {
1361 return;
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