HTMLImageElement.cpp (40776B)
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/. */ 6 7 #include "mozilla/dom/HTMLImageElement.h" 8 9 #include "imgLoader.h" 10 #include "mozilla/FocusModel.h" 11 #include "mozilla/MouseEvents.h" 12 #include "mozilla/PresShell.h" 13 #include "mozilla/StaticPrefs_image.h" 14 #include "mozilla/dom/BindContext.h" 15 #include "mozilla/dom/BindingUtils.h" 16 #include "mozilla/dom/DOMIntersectionObserver.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/HTMLFormElement.h" 19 #include "mozilla/dom/HTMLImageElementBinding.h" 20 #include "mozilla/dom/NameSpaceConstants.h" 21 #include "mozilla/dom/UnbindContext.h" 22 #include "mozilla/dom/UserActivation.h" 23 #include "nsAttrValueOrString.h" 24 #include "nsContainerFrame.h" 25 #include "nsContentUtils.h" 26 #include "nsFocusManager.h" 27 #include "nsGenericHTMLElement.h" 28 #include "nsGkAtoms.h" 29 #include "nsIMutationObserver.h" 30 #include "nsImageFrame.h" 31 #include "nsNodeInfoManager.h" 32 #include "nsPresContext.h" 33 #include "nsSize.h" 34 35 // Responsive images! 36 #include "imgINotificationObserver.h" 37 #include "imgRequestProxy.h" 38 #include "mozilla/EventDispatcher.h" 39 #include "mozilla/MappedDeclarationsBuilder.h" 40 #include "mozilla/Maybe.h" 41 #include "mozilla/RestyleManager.h" 42 #include "mozilla/dom/HTMLSourceElement.h" 43 #include "mozilla/dom/ResponsiveImageSelector.h" 44 #include "nsLayoutUtils.h" 45 46 using namespace mozilla::net; 47 using mozilla::Maybe; 48 49 NS_IMPL_NS_NEW_HTML_ELEMENT(Image) 50 51 #ifdef DEBUG 52 // Is aSubject a previous sibling of aNode. 53 static bool IsPreviousSibling(const nsINode* aSubject, const nsINode* aNode) { 54 if (aSubject == aNode) { 55 return false; 56 } 57 58 nsINode* parent = aSubject->GetParentNode(); 59 if (parent && parent == aNode->GetParentNode()) { 60 const Maybe<uint32_t> indexOfSubject = parent->ComputeIndexOf(aSubject); 61 const Maybe<uint32_t> indexOfNode = parent->ComputeIndexOf(aNode); 62 if (MOZ_LIKELY(indexOfSubject.isSome() && indexOfNode.isSome())) { 63 return *indexOfSubject < *indexOfNode; 64 } 65 // XXX Keep the odd traditional behavior for now. 66 return indexOfSubject.isNothing() && indexOfNode.isSome(); 67 } 68 69 return false; 70 } 71 #endif 72 73 namespace mozilla::dom { 74 75 HTMLImageElement::HTMLImageElement( 76 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 77 : nsGenericHTMLElement(std::move(aNodeInfo)) { 78 // We start out broken 79 AddStatesSilently(ElementState::BROKEN); 80 } 81 82 HTMLImageElement::~HTMLImageElement() { 83 nsImageLoadingContent::Destroy(); 84 if (mInDocResponsiveContent) { 85 OwnerDoc()->RemoveResponsiveContent(this); 86 mInDocResponsiveContent = false; 87 } 88 } 89 90 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLImageElement, nsGenericHTMLElement, 91 mResponsiveSelector) 92 93 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLImageElement, 94 nsGenericHTMLElement, 95 nsIImageLoadingContent, 96 imgINotificationObserver) 97 98 NS_IMPL_ELEMENT_CLONE(HTMLImageElement) 99 100 bool HTMLImageElement::IsInteractiveHTMLContent() const { 101 return HasAttr(nsGkAtoms::usemap) || 102 nsGenericHTMLElement::IsInteractiveHTMLContent(); 103 } 104 105 void HTMLImageElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) { 106 nsImageLoadingContent::AsyncEventRunning(aEvent); 107 } 108 109 void HTMLImageElement::GetCurrentSrc(nsAString& aValue) { 110 nsCOMPtr<nsIURI> currentURI; 111 GetCurrentURI(getter_AddRefs(currentURI)); 112 if (currentURI) { 113 nsAutoCString spec; 114 currentURI->GetSpec(spec); 115 CopyUTF8toUTF16(spec, aValue); 116 } else { 117 SetDOMStringToNull(aValue); 118 } 119 } 120 121 bool HTMLImageElement::Draggable() const { 122 // images may be dragged unless the draggable attribute is false 123 return !AttrValueIs(kNameSpaceID_None, nsGkAtoms::draggable, 124 nsGkAtoms::_false, eIgnoreCase); 125 } 126 127 bool HTMLImageElement::Complete() { 128 // It is still not clear what value should img.complete return in various 129 // cases, see https://github.com/whatwg/html/issues/4884 130 if (!HasAttr(nsGkAtoms::srcset) && !HasNonEmptyAttr(nsGkAtoms::src)) { 131 return true; 132 } 133 134 if (!mCurrentRequest || mPendingRequest || mPendingImageLoadTask) { 135 return false; 136 } 137 138 uint32_t status; 139 mCurrentRequest->GetImageStatus(&status); 140 return (status & 141 (imgIRequest::STATUS_LOAD_COMPLETE | imgIRequest::STATUS_ERROR)) != 0; 142 } 143 144 CSSIntPoint HTMLImageElement::GetXY() { 145 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); 146 if (!frame) { 147 return CSSIntPoint(0, 0); 148 } 149 return CSSIntPoint::FromAppUnitsRounded( 150 frame->GetOffsetTo(frame->PresShell()->GetRootFrame())); 151 } 152 153 int32_t HTMLImageElement::X() { return GetXY().x; } 154 155 int32_t HTMLImageElement::Y() { return GetXY().y; } 156 157 void HTMLImageElement::GetDecoding(nsAString& aValue) { 158 GetEnumAttr(nsGkAtoms::decoding, kDecodingTableDefault->tag, aValue); 159 } 160 161 already_AddRefed<Promise> HTMLImageElement::Decode(ErrorResult& aRv) { 162 return nsImageLoadingContent::QueueDecodeAsync(aRv); 163 } 164 165 bool HTMLImageElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 166 const nsAString& aValue, 167 nsIPrincipal* aMaybeScriptedPrincipal, 168 nsAttrValue& aResult) { 169 if (aNamespaceID == kNameSpaceID_None) { 170 if (aAttribute == nsGkAtoms::align) { 171 return ParseAlignValue(aValue, aResult); 172 } 173 if (aAttribute == nsGkAtoms::crossorigin) { 174 ParseCORSValue(aValue, aResult); 175 return true; 176 } 177 if (aAttribute == nsGkAtoms::decoding) { 178 return aResult.ParseEnumValue(aValue, kDecodingTable, 179 /* aCaseSensitive = */ false, 180 kDecodingTableDefault); 181 } 182 if (aAttribute == nsGkAtoms::loading) { 183 return ParseLoadingAttribute(aValue, aResult); 184 } 185 if (aAttribute == nsGkAtoms::fetchpriority) { 186 ParseFetchPriority(aValue, aResult); 187 return true; 188 } 189 if (ParseImageAttribute(aAttribute, aValue, aResult)) { 190 return true; 191 } 192 } 193 194 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 195 aMaybeScriptedPrincipal, aResult); 196 } 197 198 void HTMLImageElement::MapAttributesIntoRule( 199 MappedDeclarationsBuilder& aBuilder) { 200 MapImageAlignAttributeInto(aBuilder); 201 MapImageBorderAttributeInto(aBuilder); 202 MapImageMarginAttributeInto(aBuilder); 203 MapImageSizeAttributesInto(aBuilder, MapAspectRatio::Yes); 204 MapCommonAttributesInto(aBuilder); 205 } 206 207 nsChangeHint HTMLImageElement::GetAttributeChangeHint( 208 const nsAtom* aAttribute, AttrModType aModType) const { 209 nsChangeHint retval = 210 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType); 211 if (aAttribute == nsGkAtoms::usemap || aAttribute == nsGkAtoms::ismap) { 212 retval |= nsChangeHint_ReconstructFrame; 213 } else if (aAttribute == nsGkAtoms::alt) { 214 if (IsAdditionOrRemoval(aModType)) { 215 retval |= nsChangeHint_ReconstructFrame; 216 } 217 } 218 return retval; 219 } 220 221 NS_IMETHODIMP_(bool) 222 HTMLImageElement::IsAttributeMapped(const nsAtom* aAttribute) const { 223 static const MappedAttributeEntry* const map[] = { 224 sCommonAttributeMap, sImageMarginSizeAttributeMap, 225 sImageBorderAttributeMap, sImageAlignAttributeMap}; 226 227 return FindAttributeDependence(aAttribute, map); 228 } 229 230 nsMapRuleToAttributesFunc HTMLImageElement::GetAttributeMappingFunction() 231 const { 232 return &MapAttributesIntoRule; 233 } 234 235 void HTMLImageElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 236 const nsAttrValue* aValue, bool aNotify) { 237 if (aNameSpaceID == kNameSpaceID_None && mForm && 238 (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) { 239 // remove the image from the hashtable as needed 240 if (const auto* old = GetParsedAttr(aName); old && !old->IsEmptyString()) { 241 mForm->RemoveImageElementFromTable( 242 this, nsDependentAtomString(old->GetAtomValue())); 243 } 244 } 245 246 return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, 247 aNotify); 248 } 249 250 void HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 251 const nsAttrValue* aValue, 252 const nsAttrValue* aOldValue, 253 nsIPrincipal* aMaybeScriptedPrincipal, 254 bool aNotify) { 255 if (aNameSpaceID != kNameSpaceID_None) { 256 return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, 257 aOldValue, 258 aMaybeScriptedPrincipal, aNotify); 259 } 260 261 nsAttrValueOrString attrVal(aValue); 262 if (aName == nsGkAtoms::src) { 263 mSrcURI = nullptr; 264 if (aValue && !aValue->IsEmptyString()) { 265 StringToURI(attrVal.String(), OwnerDoc(), getter_AddRefs(mSrcURI)); 266 } 267 } 268 269 if (aValue) { 270 AfterMaybeChangeAttr(aNameSpaceID, aName, attrVal, aOldValue, 271 aMaybeScriptedPrincipal, aNotify); 272 } 273 274 if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) && aValue && 275 !aValue->IsEmptyString()) { 276 // add the image to the hashtable as needed 277 MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom, 278 "Expected atom value for name/id"); 279 mForm->AddImageElementToTable( 280 this, nsDependentAtomString(aValue->GetAtomValue())); 281 } 282 283 bool forceReload = false; 284 if (aName == nsGkAtoms::loading) { 285 if (aValue && Loading(aValue->GetEnumValue()) == Loading::Lazy) { 286 SetLazyLoading(); 287 } else if (aOldValue && 288 Loading(aOldValue->GetEnumValue()) == Loading::Lazy) { 289 StopLazyLoading(StartLoad(aNotify)); 290 } 291 } else if (aName == nsGkAtoms::src && !aValue) { 292 // AfterMaybeChangeAttr handles setting src since it needs to catch 293 // img.src = img.src, so we only need to handle the unset case 294 // NOTE: regular src value changes are handled in AfterMaybeChangeAttr, so 295 // this only needs to handle unsetting the src attribute. 296 // Mark channel as urgent-start before load image if the image load is 297 // initiated by a user interaction. 298 if (mResponsiveSelector && mResponsiveSelector->Content() == this) { 299 mResponsiveSelector->SetDefaultSource(VoidString()); 300 } 301 forceReload = true; 302 } else if (aName == nsGkAtoms::srcset) { 303 // Mark channel as urgent-start before load image if the image load is 304 // initaiated by a user interaction. 305 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); 306 307 mSrcsetTriggeringPrincipal = aMaybeScriptedPrincipal; 308 309 if (aValue) { 310 if (!mInDocResponsiveContent) { 311 OwnerDoc()->AddResponsiveContent(this); 312 mInDocResponsiveContent = true; 313 } 314 } else if (mInDocResponsiveContent && !IsInPicture()) { 315 OwnerDoc()->RemoveResponsiveContent(this); 316 mInDocResponsiveContent = false; 317 } 318 319 PictureSourceSrcsetChanged(this, attrVal.String(), aNotify); 320 } else if (aName == nsGkAtoms::sizes) { 321 // Mark channel as urgent-start before load image if the image load is 322 // initiated by a user interaction. 323 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); 324 325 PictureSourceSizesChanged(this, attrVal.String(), aNotify); 326 } else if (aName == nsGkAtoms::decoding) { 327 // Request sync or async image decoding. 328 SetSyncDecodingHint( 329 aValue && static_cast<ImageDecodingType>(aValue->GetEnumValue()) == 330 ImageDecodingType::Sync); 331 } else if (aName == nsGkAtoms::referrerpolicy) { 332 ReferrerPolicy referrerPolicy = GetReferrerPolicyAsEnum(); 333 forceReload = referrerPolicy != ReferrerPolicy::_empty && 334 referrerPolicy != ReferrerPolicyFromAttr(aOldValue); 335 } else if (aName == nsGkAtoms::crossorigin) { 336 forceReload = GetCORSMode() != AttrValueToCORSMode(aOldValue); 337 } 338 339 // NOTE(emilio): When not notifying, we come from the parser or some other 340 // internal caller, in which cases we can skip the load since we are about to 341 // get bound to a tree. 342 if (forceReload) { 343 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); 344 UpdateSourceSyncAndQueueImageTask(true, aNotify); 345 } 346 347 return nsGenericHTMLElement::AfterSetAttr( 348 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); 349 } 350 351 void HTMLImageElement::OnAttrSetButNotChanged(int32_t aNamespaceID, 352 nsAtom* aName, 353 const nsAttrValueOrString& aValue, 354 bool aNotify) { 355 AfterMaybeChangeAttr(aNamespaceID, aName, aValue, nullptr, nullptr, aNotify); 356 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName, 357 aValue, aNotify); 358 } 359 360 void HTMLImageElement::AfterMaybeChangeAttr( 361 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue, 362 const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, 363 bool aNotify) { 364 if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::src) { 365 return; 366 } 367 368 // We need to force our image to reload. This must be done here, not in 369 // AfterSetAttr or BeforeSetAttr, because we want to do it even if the attr is 370 // being set to its existing value, which is normally optimized away as a 371 // no-op. 372 // 373 // If we are in responsive mode, we drop the forced reload behavior, but still 374 // trigger a image load task for img.src = img.src per spec. 375 // 376 // Both cases handle unsetting src in AfterSetAttr 377 mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal( 378 this, aValue.String(), aMaybeScriptedPrincipal); 379 380 if (mResponsiveSelector && mResponsiveSelector->Content() == this) { 381 mResponsiveSelector->SetDefaultSource(mSrcURI, mSrcTriggeringPrincipal); 382 } 383 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); 384 UpdateSourceSyncAndQueueImageTask(true, aNotify); 385 } 386 387 void HTMLImageElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 388 // We handle image element with attribute ismap in its corresponding frame 389 // element. Set mMultipleActionsPrevented here to prevent the click event 390 // trigger the behaviors in Element::PostHandleEventForLinks 391 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent(); 392 if (mouseEvent && mouseEvent->IsLeftClickEvent() && IsMap()) { 393 mouseEvent->mFlags.mMultipleActionsPrevented = true; 394 } 395 nsGenericHTMLElement::GetEventTargetParent(aVisitor); 396 } 397 398 nsINode* HTMLImageElement::GetScopeChainParent() const { 399 if (mForm) { 400 return mForm; 401 } 402 return nsGenericHTMLElement::GetScopeChainParent(); 403 } 404 405 bool HTMLImageElement::IsHTMLFocusable(IsFocusableFlags aFlags, 406 bool* aIsFocusable, int32_t* aTabIndex) { 407 int32_t tabIndex = TabIndex(); 408 409 if (IsInComposedDoc() && FindImageMap()) { 410 // Use tab index on individual map areas. 411 *aTabIndex = FocusModel::IsTabFocusable(TabFocusableType::Links) ? 0 : -1; 412 // Image map is not focusable itself, but flag as tabbable 413 // so that image map areas get walked into. 414 *aIsFocusable = false; 415 return false; 416 } 417 418 // Can be in tab order if tabindex >=0 and form controls are tabbable. 419 *aTabIndex = FocusModel::IsTabFocusable(TabFocusableType::FormElements) 420 ? tabIndex 421 : -1; 422 *aIsFocusable = IsFormControlDefaultFocusable(aFlags) && 423 (tabIndex >= 0 || GetTabIndexAttrValue().isSome()); 424 425 return false; 426 } 427 428 nsresult HTMLImageElement::BindToTree(BindContext& aContext, nsINode& aParent) { 429 MOZ_TRY(nsGenericHTMLElement::BindToTree(aContext, aParent)); 430 431 nsImageLoadingContent::BindToTree(aContext, aParent); 432 433 UpdateFormOwner(); 434 435 // Mark channel as urgent-start before load image if the image load is 436 // initiated by a user interaction. 437 if (IsInPicture()) { 438 if (!mInDocResponsiveContent) { 439 OwnerDoc()->AddResponsiveContent(this); 440 mInDocResponsiveContent = true; 441 } 442 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput(); 443 UpdateSourceSyncAndQueueImageTask(false, /* aNotify = */ false); 444 } 445 return NS_OK; 446 } 447 448 void HTMLImageElement::UnbindFromTree(UnbindContext& aContext) { 449 if (mForm) { 450 if (aContext.IsUnbindRoot(this) || !FindAncestorForm(mForm)) { 451 ClearForm(true); 452 } else { 453 UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 454 } 455 } 456 // Our in-pictureness can only change if we're the unbind root. 457 const bool wasInPicture = IsInPicture(); 458 459 nsImageLoadingContent::UnbindFromTree(); 460 nsGenericHTMLElement::UnbindFromTree(aContext); 461 462 if (wasInPicture != IsInPicture()) { 463 MOZ_ASSERT(wasInPicture); 464 MOZ_ASSERT(aContext.IsUnbindRoot(this)); 465 MOZ_ASSERT(mInDocResponsiveContent); 466 if (!HasAttr(nsGkAtoms::srcset)) { 467 OwnerDoc()->RemoveResponsiveContent(this); 468 mInDocResponsiveContent = false; 469 } 470 UpdateSourceSyncAndQueueImageTask(false, /* aNotify = */ false); 471 } 472 } 473 474 void HTMLImageElement::UpdateFormOwner() { 475 if (!mForm) { 476 mForm = FindAncestorForm(); 477 } 478 479 if (mForm && !HasFlag(ADDED_TO_FORM)) { 480 // Now we need to add ourselves to the form 481 nsAutoString nameVal, idVal; 482 GetAttr(nsGkAtoms::name, nameVal); 483 GetAttr(nsGkAtoms::id, idVal); 484 485 SetFlags(ADDED_TO_FORM); 486 487 mForm->AddImageElement(this); 488 489 if (!nameVal.IsEmpty()) { 490 mForm->AddImageElementToTable(this, nameVal); 491 } 492 493 if (!idVal.IsEmpty()) { 494 mForm->AddImageElementToTable(this, idVal); 495 } 496 } 497 } 498 499 void HTMLImageElement::NodeInfoChanged(Document* aOldDoc) { 500 nsGenericHTMLElement::NodeInfoChanged(aOldDoc); 501 502 if (mInDocResponsiveContent) { 503 aOldDoc->RemoveResponsiveContent(this); 504 OwnerDoc()->AddResponsiveContent(this); 505 } 506 507 // Reparse the URI if needed. Note that we can't check whether we already have 508 // a parsed URI, because it might be null even if we have a valid src 509 // attribute, if we tried to parse with a different base. 510 mSrcURI = nullptr; 511 nsAutoString src; 512 if (GetAttr(nsGkAtoms::src, src) && !src.IsEmpty()) { 513 StringToURI(src, OwnerDoc(), getter_AddRefs(mSrcURI)); 514 } 515 516 if (mLazyLoading) { 517 aOldDoc->GetLazyLoadObserver()->Unobserve(*this); 518 mLazyLoading = false; 519 SetLazyLoading(); 520 } 521 522 // Run selection algorithm synchronously and reload when an img element's 523 // adopting steps are run, in order to react to changes in the environment, 524 // per spec, 525 // https://html.spec.whatwg.org/#reacting-to-dom-mutations, and 526 // https://html.spec.whatwg.org/#reacting-to-environment-changes. 527 UpdateSourceSyncAndQueueImageTask(true, /* aNotify = */ false); 528 } 529 530 // static 531 already_AddRefed<HTMLImageElement> HTMLImageElement::Image( 532 const GlobalObject& aGlobal, const Optional<uint32_t>& aWidth, 533 const Optional<uint32_t>& aHeight, ErrorResult& aError) { 534 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports()); 535 Document* doc; 536 if (!win || !(doc = win->GetExtantDoc())) { 537 aError.Throw(NS_ERROR_FAILURE); 538 return nullptr; 539 } 540 541 RefPtr<mozilla::dom::NodeInfo> nodeInfo = doc->NodeInfoManager()->GetNodeInfo( 542 nsGkAtoms::img, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE); 543 544 auto* nim = nodeInfo->NodeInfoManager(); 545 RefPtr<HTMLImageElement> img = new (nim) HTMLImageElement(nodeInfo.forget()); 546 547 if (aWidth.WasPassed()) { 548 img->SetWidth(aWidth.Value(), aError); 549 if (aError.Failed()) { 550 return nullptr; 551 } 552 553 if (aHeight.WasPassed()) { 554 img->SetHeight(aHeight.Value(), aError); 555 if (aError.Failed()) { 556 return nullptr; 557 } 558 } 559 } 560 561 return img.forget(); 562 } 563 564 uint32_t HTMLImageElement::Height() { return GetWidthHeightForImage().height; } 565 566 uint32_t HTMLImageElement::Width() { return GetWidthHeightForImage().width; } 567 568 nsresult HTMLImageElement::CopyInnerTo(HTMLImageElement* aDest) { 569 MOZ_TRY(nsGenericHTMLElement::CopyInnerTo(aDest)); 570 571 // In SetAttr (called from nsGenericHTMLElement::CopyInnerTo), aDest skipped 572 // doing the image load because we passed in false for aNotify. But we 573 // really do want it to do the load, so set it up to happen once the cloning 574 // reaches a stable state. 575 aDest->UpdateSourceSyncAndQueueImageTask(false, /* aNotify = */ false); 576 return NS_OK; 577 } 578 579 CORSMode HTMLImageElement::GetCORSMode() { 580 return AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin)); 581 } 582 583 JSObject* HTMLImageElement::WrapNode(JSContext* aCx, 584 JS::Handle<JSObject*> aGivenProto) { 585 return HTMLImageElement_Binding::Wrap(aCx, this, aGivenProto); 586 } 587 588 #ifdef DEBUG 589 HTMLFormElement* HTMLImageElement::GetForm() const { return mForm; } 590 #endif 591 592 void HTMLImageElement::SetForm(HTMLFormElement* aForm) { 593 MOZ_ASSERT(aForm, "Don't pass null here"); 594 NS_ASSERTION(!mForm, 595 "We don't support switching from one non-null form to another."); 596 597 mForm = aForm; 598 } 599 600 void HTMLImageElement::ClearForm(bool aRemoveFromForm) { 601 NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM), 602 "Form control should have had flag set correctly"); 603 604 if (!mForm) { 605 return; 606 } 607 608 if (aRemoveFromForm) { 609 nsAutoString nameVal, idVal; 610 GetAttr(nsGkAtoms::name, nameVal); 611 GetAttr(nsGkAtoms::id, idVal); 612 613 mForm->RemoveImageElement(this); 614 615 if (!nameVal.IsEmpty()) { 616 mForm->RemoveImageElementFromTable(this, nameVal); 617 } 618 619 if (!idVal.IsEmpty()) { 620 mForm->RemoveImageElementFromTable(this, idVal); 621 } 622 } 623 624 UnsetFlags(ADDED_TO_FORM); 625 mForm = nullptr; 626 } 627 628 // Roughly corresponds to https://html.spec.whatwg.org/#update-the-image-data 629 void HTMLImageElement::UpdateSourceSyncAndQueueImageTask( 630 bool aAlwaysLoad, bool aNotify, const HTMLSourceElement* aSkippedSource) { 631 // Per spec, when updating the image data or reacting to environment 632 // changes, we always run the full selection (including selecting the source 633 // element and the best fit image from srcset) even if it doesn't directly 634 // affect the source selection. 635 // 636 // However, in the spec of updating the image data, the selection of image 637 // source URL is in the asynchronous part (i.e. in a microtask), and so this 638 // doesn't guarantee that the image style is correct after we flush the style 639 // synchronously. So here we update the responsive source synchronously always 640 // to make sure the image source is always up-to-date after each DOM mutation. 641 // Spec issue: https://github.com/whatwg/html/issues/8207. 642 UpdateResponsiveSource(aSkippedSource); 643 644 nsImageLoadingContent::QueueImageTask(mSrcURI, mSrcTriggeringPrincipal, 645 HaveSrcsetOrInPicture(), aAlwaysLoad, 646 aNotify); 647 } 648 649 bool HTMLImageElement::HaveSrcsetOrInPicture() const { 650 return HasAttr(nsGkAtoms::srcset) || IsInPicture(); 651 } 652 653 bool HTMLImageElement::SelectedSourceMatchesLast(nsIURI* aSelectedSource) { 654 // If there was no selected source previously, we don't want to short-circuit 655 // the load. Similarly for if there is no newly selected source. 656 if (!mLastSelectedSource || !aSelectedSource) { 657 return false; 658 } 659 bool equal = false; 660 return NS_SUCCEEDED(mLastSelectedSource->Equals(aSelectedSource, &equal)) && 661 equal; 662 } 663 664 void HTMLImageElement::LoadSelectedImage(bool aAlwaysLoad, 665 bool aStopLazyLoading) { 666 // In responsive mode, we have to make sure we ran the full selection 667 // algorithm before loading the selected image. 668 // Use this assertion to catch any cases we missed. 669 MOZ_ASSERT(!UpdateResponsiveSource(), 670 "The image source should be the same because we update the " 671 "responsive source synchronously"); 672 673 if (aStopLazyLoading) { 674 StopLazyLoading(StartLoad::No); 675 } 676 677 // The density is default to 1.0 for the src attribute case. 678 double currentDensity = mResponsiveSelector 679 ? mResponsiveSelector->GetSelectedImageDensity() 680 : 1.0; 681 682 nsCOMPtr<nsIURI> selectedSource; 683 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 684 ImageLoadType type = eImageLoadType_Normal; 685 bool hasSrc = false; 686 if (mResponsiveSelector) { 687 selectedSource = mResponsiveSelector->GetSelectedImageURL(); 688 triggeringPrincipal = 689 mResponsiveSelector->GetSelectedImageTriggeringPrincipal(); 690 type = eImageLoadType_Imageset; 691 } else if (mSrcURI || HasAttr(nsGkAtoms::src)) { 692 hasSrc = true; 693 if (mSrcURI) { 694 selectedSource = mSrcURI; 695 if (HaveSrcsetOrInPicture()) { 696 // If we have a srcset attribute or are in a <picture> element, we 697 // always use the Imageset load type, even if we parsed no valid 698 // responsive sources from either, per spec. 699 type = eImageLoadType_Imageset; 700 } 701 triggeringPrincipal = mSrcTriggeringPrincipal; 702 } 703 } 704 705 if (!aAlwaysLoad && SelectedSourceMatchesLast(selectedSource)) { 706 // Update state when only density may have changed (i.e., the source to load 707 // hasn't changed, and we don't do any request at all). We need (apart from 708 // updating our internal state) to tell the image frame because its 709 // intrinsic size may have changed. 710 // 711 // In the case we actually trigger a new load, that load will trigger a call 712 // to nsImageFrame::NotifyNewCurrentRequest, which takes care of that for 713 // us. 714 SetDensity(currentDensity); 715 // If we're (re-)loading a broken image, we might have just become broken 716 // again. 717 UpdateImageState(true); 718 return; 719 } 720 721 if (mLazyLoading) { 722 return; 723 } 724 725 nsresult rv = NS_ERROR_FAILURE; 726 727 const bool kNotify = true; 728 // src triggers an error event on invalid URI, unlike other loads. 729 if (selectedSource || hasSrc) { 730 // We can pass true for aForce because we already do a manual check for 731 // SelectedSourceMatchesLast. 732 rv = LoadImage(selectedSource, /* aForce = */ true, kNotify, type, 733 triggeringPrincipal); 734 } 735 736 mLastSelectedSource = selectedSource; 737 mCurrentDensity = currentDensity; 738 739 if (NS_FAILED(rv)) { 740 CancelImageRequests(kNotify); 741 } 742 } 743 744 void HTMLImageElement::PictureSourceSrcsetChanged(nsIContent* aSourceNode, 745 const nsAString& aNewValue, 746 bool aNotify) { 747 MOZ_ASSERT(aSourceNode == this || IsPreviousSibling(aSourceNode, this), 748 "Should not be getting notifications for non-previous-siblings"); 749 750 nsIContent* currentSrc = 751 mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; 752 753 if (aSourceNode == currentSrc) { 754 // We're currently using this node as our responsive selector 755 // source. 756 nsCOMPtr<nsIPrincipal> principal; 757 if (aSourceNode == this) { 758 principal = mSrcsetTriggeringPrincipal; 759 } else if (auto* source = HTMLSourceElement::FromNode(aSourceNode)) { 760 principal = source->GetSrcsetTriggeringPrincipal(); 761 } 762 mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue, principal); 763 } 764 765 // This always triggers the image update steps per the spec, even if we are 766 // not using this source. 767 UpdateSourceSyncAndQueueImageTask(true, aNotify); 768 } 769 770 void HTMLImageElement::PictureSourceSizesChanged(nsIContent* aSourceNode, 771 const nsAString& aNewValue, 772 bool aNotify) { 773 MOZ_ASSERT(aSourceNode == this || IsPreviousSibling(aSourceNode, this), 774 "Should not be getting notifications for non-previous-siblings"); 775 776 nsIContent* currentSrc = 777 mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; 778 779 if (aSourceNode == currentSrc) { 780 // We're currently using this node as our responsive selector 781 // source. 782 mResponsiveSelector->SetSizesFromDescriptor(aNewValue); 783 } 784 785 // This always triggers the image update steps per the spec, even if 786 // we are not using this source. 787 UpdateSourceSyncAndQueueImageTask(true, aNotify); 788 } 789 790 void HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent* aSourceNode, 791 bool aNotify) { 792 MOZ_ASSERT(IsPreviousSibling(aSourceNode, this), 793 "Should not be getting notifications for non-previous-siblings"); 794 795 // This always triggers the image update steps per the spec, even if 796 // we are not switching to/from this source 797 UpdateSourceSyncAndQueueImageTask(true, aNotify); 798 } 799 800 void HTMLImageElement::PictureSourceDimensionChanged( 801 HTMLSourceElement* aSourceNode, bool aNotify) { 802 MOZ_ASSERT(IsPreviousSibling(aSourceNode, this), 803 "Should not be getting notifications for non-previous-siblings"); 804 805 // "width" and "height" affect the dimension of images, but they don't have 806 // impact on the selection of <source> elements. In other words, 807 // UpdateResponsiveSource doesn't change the source, so all we need to do is 808 // just request restyle. 809 if (mResponsiveSelector && mResponsiveSelector->Content() == aSourceNode) { 810 InvalidateAttributeMapping(); 811 } 812 } 813 814 void HTMLImageElement::PictureSourceAdded(bool aNotify, 815 HTMLSourceElement* aSourceNode) { 816 MOZ_ASSERT(!aSourceNode || IsPreviousSibling(aSourceNode, this), 817 "Should not be getting notifications for non-previous-siblings"); 818 819 UpdateSourceSyncAndQueueImageTask(true, aNotify); 820 } 821 822 void HTMLImageElement::PictureSourceRemoved(bool aNotify, 823 HTMLSourceElement* aSourceNode) { 824 MOZ_ASSERT(!aSourceNode || IsPreviousSibling(aSourceNode, this), 825 "Should not be getting notifications for non-previous-siblings"); 826 UpdateSourceSyncAndQueueImageTask(true, aNotify, aSourceNode); 827 } 828 829 bool HTMLImageElement::UpdateResponsiveSource( 830 const HTMLSourceElement* aSkippedSource) { 831 bool hadSelector = !!mResponsiveSelector; 832 833 nsIContent* currentSource = 834 mResponsiveSelector ? mResponsiveSelector->Content() : nullptr; 835 836 // Walk source nodes previous to ourselves if IsInPicture(). 837 nsINode* candidateSource = 838 IsInPicture() ? GetParentElement()->GetFirstChild() : this; 839 840 // Initialize this as nullptr so we don't have to nullify it when runing out 841 // of siblings without finding ourself, e.g. XBL magic. 842 RefPtr<ResponsiveImageSelector> newResponsiveSelector = nullptr; 843 844 for (; candidateSource; candidateSource = candidateSource->GetNextSibling()) { 845 if (aSkippedSource == candidateSource) { 846 continue; 847 } 848 849 if (candidateSource == currentSource) { 850 // found no better source before current, re-run selection on 851 // that and keep it if it's still usable. 852 bool changed = mResponsiveSelector->SelectImage(true); 853 if (mResponsiveSelector->NumCandidates()) { 854 bool isUsableCandidate = true; 855 856 // an otherwise-usable source element may still have a media query that 857 // may not match any more. 858 if (candidateSource->IsHTMLElement(nsGkAtoms::source) && 859 !SourceElementMatches(candidateSource->AsElement())) { 860 isUsableCandidate = false; 861 } 862 863 if (isUsableCandidate) { 864 // We are still using the current source, but the selected image may 865 // be changed, so always set the density from the selected image. 866 SetDensity(mResponsiveSelector->GetSelectedImageDensity()); 867 return changed; 868 } 869 } 870 871 // no longer valid 872 newResponsiveSelector = nullptr; 873 if (candidateSource == this) { 874 // No further possibilities 875 break; 876 } 877 } else if (candidateSource == this) { 878 // We are the last possible source 879 newResponsiveSelector = 880 TryCreateResponsiveSelector(candidateSource->AsElement()); 881 break; 882 } else if (auto* source = HTMLSourceElement::FromNode(candidateSource)) { 883 if (RefPtr<ResponsiveImageSelector> selector = 884 TryCreateResponsiveSelector(source)) { 885 newResponsiveSelector = selector.forget(); 886 // This led to a valid source, stop 887 break; 888 } 889 } 890 } 891 892 // If we reach this point, either: 893 // - there was no selector originally, and there is not one now 894 // - there was no selector originally, and there is one now 895 // - there was a selector, and there is a different one now 896 // - there was a selector, and there is not one now 897 SetResponsiveSelector(std::move(newResponsiveSelector)); 898 return hadSelector || mResponsiveSelector; 899 } 900 901 /*static */ 902 bool HTMLImageElement::SupportedPictureSourceType(const nsAString& aType) { 903 nsAutoString type; 904 nsAutoString params; 905 906 nsContentUtils::SplitMimeType(aType, type, params); 907 if (type.IsEmpty()) { 908 return true; 909 } 910 911 return imgLoader::SupportImageWithMimeType( 912 NS_ConvertUTF16toUTF8(type), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS); 913 } 914 915 bool HTMLImageElement::SourceElementMatches(Element* aSourceElement) { 916 MOZ_ASSERT(aSourceElement->IsHTMLElement(nsGkAtoms::source)); 917 918 MOZ_ASSERT(IsInPicture()); 919 MOZ_ASSERT(IsPreviousSibling(aSourceElement, this)); 920 921 // Check media and type 922 auto* src = static_cast<HTMLSourceElement*>(aSourceElement); 923 if (!src->MatchesCurrentMedia()) { 924 return false; 925 } 926 927 nsAutoString type; 928 return !src->GetAttr(nsGkAtoms::type, type) || 929 SupportedPictureSourceType(type); 930 } 931 932 already_AddRefed<ResponsiveImageSelector> 933 HTMLImageElement::TryCreateResponsiveSelector(Element* aSourceElement) { 934 nsCOMPtr<nsIPrincipal> principal; 935 936 // Skip if this is not a <source> with matching media query 937 bool isSourceTag = aSourceElement->IsHTMLElement(nsGkAtoms::source); 938 if (isSourceTag) { 939 if (!SourceElementMatches(aSourceElement)) { 940 return nullptr; 941 } 942 auto* source = HTMLSourceElement::FromNode(aSourceElement); 943 principal = source->GetSrcsetTriggeringPrincipal(); 944 } else if (aSourceElement->IsHTMLElement(nsGkAtoms::img)) { 945 // Otherwise this is the <img> tag itself 946 MOZ_ASSERT(aSourceElement == this); 947 principal = mSrcsetTriggeringPrincipal; 948 } 949 950 // Skip if has no srcset or an empty srcset 951 nsString srcset; 952 if (!aSourceElement->GetAttr(nsGkAtoms::srcset, srcset)) { 953 return nullptr; 954 } 955 956 if (srcset.IsEmpty()) { 957 return nullptr; 958 } 959 960 // Try to parse 961 RefPtr<ResponsiveImageSelector> sel = 962 new ResponsiveImageSelector(aSourceElement); 963 if (!sel->SetCandidatesFromSourceSet(srcset, principal)) { 964 // No possible candidates, don't need to bother parsing sizes 965 return nullptr; 966 } 967 968 nsAutoString sizes; 969 aSourceElement->GetAttr(nsGkAtoms::sizes, sizes); 970 sel->SetSizesFromDescriptor(sizes); 971 972 // If this is the <img> tag, also pull in src as the default source 973 if (!isSourceTag) { 974 MOZ_ASSERT(aSourceElement == this); 975 if (mSrcURI) { 976 sel->SetDefaultSource(mSrcURI, mSrcTriggeringPrincipal); 977 } 978 } 979 980 return sel.forget(); 981 } 982 983 /* static */ 984 bool HTMLImageElement::SelectSourceForTagWithAttrs( 985 Document* aDocument, bool aIsSourceTag, const nsAString& aSrcAttr, 986 const nsAString& aSrcsetAttr, const nsAString& aSizesAttr, 987 const nsAString& aTypeAttr, const nsAString& aMediaAttr, 988 nsAString& aResult) { 989 MOZ_ASSERT(aIsSourceTag || (aTypeAttr.IsEmpty() && aMediaAttr.IsEmpty()), 990 "Passing type or media attrs makes no sense without aIsSourceTag"); 991 MOZ_ASSERT(!aIsSourceTag || aSrcAttr.IsEmpty(), 992 "Passing aSrcAttr makes no sense with aIsSourceTag set"); 993 994 if (aSrcsetAttr.IsEmpty()) { 995 if (!aIsSourceTag) { 996 // For an <img> with no srcset, we would always select the src attr. 997 aResult.Assign(aSrcAttr); 998 return true; 999 } 1000 // Otherwise, a <source> without srcset is never selected 1001 return false; 1002 } 1003 1004 // Would not consider source tags with unsupported media or type 1005 if (aIsSourceTag && 1006 ((!aMediaAttr.IsVoid() && !HTMLSourceElement::WouldMatchMediaForDocument( 1007 aMediaAttr, aDocument)) || 1008 (!aTypeAttr.IsVoid() && !SupportedPictureSourceType(aTypeAttr)))) { 1009 return false; 1010 } 1011 1012 // Using srcset or picture <source>, build a responsive selector for this 1013 // tag. 1014 RefPtr<ResponsiveImageSelector> sel = new ResponsiveImageSelector(aDocument); 1015 1016 sel->SetCandidatesFromSourceSet(aSrcsetAttr); 1017 if (!aSizesAttr.IsEmpty()) { 1018 sel->SetSizesFromDescriptor(aSizesAttr); 1019 } 1020 if (!aIsSourceTag) { 1021 sel->SetDefaultSource(aSrcAttr); 1022 } 1023 1024 if (sel->GetSelectedImageURLSpec(aResult)) { 1025 return true; 1026 } 1027 1028 if (!aIsSourceTag) { 1029 // <img> tag with no match would definitively load nothing. 1030 aResult.Truncate(); 1031 return true; 1032 } 1033 1034 // <source> tags with no match would leave source yet-undetermined. 1035 return false; 1036 } 1037 1038 void HTMLImageElement::DestroyContent() { 1039 // Clear the load task to avoid running LoadSelectedImage() after getting 1040 // destroyed. 1041 ClearImageLoadTask(); 1042 1043 mResponsiveSelector = nullptr; 1044 1045 nsImageLoadingContent::Destroy(); 1046 nsGenericHTMLElement::DestroyContent(); 1047 } 1048 1049 void HTMLImageElement::MediaFeatureValuesChanged() { 1050 UpdateSourceSyncAndQueueImageTask(false, /* aNotify = */ true); 1051 } 1052 1053 void HTMLImageElement::SetLazyLoading() { 1054 if (mLazyLoading) { 1055 return; 1056 } 1057 1058 // If scripting is disabled don't do lazy load. 1059 // https://whatpr.org/html/3752/images.html#updating-the-image-data 1060 // 1061 // Same for printing. 1062 Document* doc = OwnerDoc(); 1063 if (!doc->IsScriptEnabled() || doc->IsStaticDocument()) { 1064 return; 1065 } 1066 1067 doc->EnsureLazyLoadObserver().Observe(*this); 1068 mLazyLoading = true; 1069 UpdateImageState(true); 1070 } 1071 1072 void HTMLImageElement::StopLazyLoading(StartLoad aStartLoad) { 1073 if (!mLazyLoading) { 1074 return; 1075 } 1076 mLazyLoading = false; 1077 Document* doc = OwnerDoc(); 1078 if (auto* obs = doc->GetLazyLoadObserver()) { 1079 obs->Unobserve(*this); 1080 } 1081 1082 if (aStartLoad == StartLoad::Yes) { 1083 UpdateSourceSyncAndQueueImageTask(true, /* aNotify = */ true); 1084 } 1085 } 1086 1087 const StyleLockedDeclarationBlock* 1088 HTMLImageElement::GetMappedAttributesFromSource() const { 1089 if (!IsInPicture() || !mResponsiveSelector) { 1090 return nullptr; 1091 } 1092 1093 const auto* source = 1094 HTMLSourceElement::FromNodeOrNull(mResponsiveSelector->Content()); 1095 if (!source) { 1096 return nullptr; 1097 } 1098 1099 MOZ_ASSERT(IsPreviousSibling(source, this), 1100 "Incorrect or out-of-date source"); 1101 return source->GetAttributesMappedForImage(); 1102 } 1103 1104 void HTMLImageElement::InvalidateAttributeMapping() { 1105 if (!IsInPicture()) { 1106 return; 1107 } 1108 1109 nsPresContext* presContext = nsContentUtils::GetContextForContent(this); 1110 if (!presContext) { 1111 return; 1112 } 1113 1114 // Note: Unfortunately, we have to use RESTYLE_SELF, instead of using 1115 // RESTYLE_STYLE_ATTRIBUTE or other ways, to avoid re-selector-match because 1116 // we are using Gecko_GetExtraContentStyleDeclarations() to retrieve the 1117 // extra declaration block from |this|'s width and height attributes, and 1118 // other restyle hints seems not enough. 1119 // FIXME: We may refine this together with the restyle for presentation 1120 // attributes in RestyleManger::AttributeChagned() 1121 presContext->RestyleManager()->PostRestyleEvent( 1122 this, RestyleHint::RESTYLE_SELF, nsChangeHint(0)); 1123 } 1124 1125 void HTMLImageElement::SetResponsiveSelector( 1126 RefPtr<ResponsiveImageSelector>&& aSource) { 1127 if (mResponsiveSelector == aSource) { 1128 return; 1129 } 1130 1131 mResponsiveSelector = std::move(aSource); 1132 1133 // Invalidate the style if needed. 1134 InvalidateAttributeMapping(); 1135 1136 // Update density. 1137 SetDensity(mResponsiveSelector 1138 ? mResponsiveSelector->GetSelectedImageDensity() 1139 : 1.0); 1140 } 1141 1142 void HTMLImageElement::SetDensity(double aDensity) { 1143 if (mCurrentDensity == aDensity) { 1144 return; 1145 } 1146 1147 mCurrentDensity = aDensity; 1148 1149 // Invalidate the reflow. 1150 if (nsImageFrame* f = do_QueryFrame(GetPrimaryFrame())) { 1151 f->ResponsiveContentDensityChanged(); 1152 } 1153 } 1154 1155 FetchPriority HTMLImageElement::GetFetchPriorityForImage() const { 1156 return Element::GetFetchPriority(); 1157 } 1158 1159 void HTMLImageElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes, 1160 size_t* aNodeSize) const { 1161 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize); 1162 1163 // It is okay to include the size of mSrcURI here even though it might have 1164 // strong references from elsewhere because the URI was created for this 1165 // object, in nsImageLoadingContent::StringToURI(). Only objects that created 1166 // their own URI will call nsIURI::SizeOfIncludingThis(). 1167 if (mSrcURI) { 1168 *aNodeSize += mSrcURI->SizeOfIncludingThis(aSizes.mState.mMallocSizeOf); 1169 } 1170 } 1171 1172 } // namespace mozilla::dom