/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/HTMLSourceElement.h" #include "mozilla/dom/HTMLSourceElementBinding.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/ResponsiveImageSelector.h" #include "mozilla/dom/MediaList.h" #include "mozilla/dom/MediaSource.h" #include "mozilla/dom/BlobURLProtocolHandler.h" #include "mozilla/Preferences.h" #include "nsGkAtoms.h" #include "nsHTMLStyleSheet.h" #include "nsMappedAttributes.h" NS_IMPL_NS_NEW_HTML_ELEMENT(Source) namespace mozilla::dom { HTMLSourceElement::HTMLSourceElement( already_AddRefed&& aNodeInfo) : nsGenericHTMLElement(std::move(aNodeInfo)) {} HTMLSourceElement::~HTMLSourceElement() = default; NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLSourceElement, nsGenericHTMLElement, mSrcMediaSource) NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLSourceElement, nsGenericHTMLElement) NS_IMPL_ELEMENT_CLONE(HTMLSourceElement) bool HTMLSourceElement::MatchesCurrentMedia() { if (mMediaList) { return mMediaList->Matches(*OwnerDoc()); } // No media specified return true; } /* static */ bool HTMLSourceElement::WouldMatchMediaForDocument(const nsAString& aMedia, const Document* aDocument) { if (aMedia.IsEmpty()) { return true; } RefPtr mediaList = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia)); return mediaList->Matches(*aDocument); } void HTMLSourceElement::UpdateMediaList(const nsAttrValue* aValue) { mMediaList = nullptr; if (!aValue) { return; } NS_ConvertUTF16toUTF8 mediaStr(aValue->GetStringValue()); mMediaList = MediaList::Create(mediaStr); } bool HTMLSourceElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) { if (StaticPrefs::dom_picture_source_dimension_attributes_enabled() && aNamespaceID == kNameSpaceID_None && (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) { return aResult.ParseHTMLDimension(aValue); } return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); } void HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify) { if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::srcset) { mSrcsetTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal( this, aValue ? aValue->GetStringValue() : EmptyString(), aMaybeScriptedPrincipal); } // If we are associated with a with a valid , notify it of // responsive parameter changes if (aNameSpaceID == kNameSpaceID_None && (aName == nsGkAtoms::srcset || aName == nsGkAtoms::sizes || aName == nsGkAtoms::media || aName == nsGkAtoms::type) && IsInPicture()) { if (aName == nsGkAtoms::media) { UpdateMediaList(aValue); } nsString strVal = aValue ? aValue->GetStringValue() : EmptyString(); // Find all img siblings after this and notify them of the change nsCOMPtr sibling = AsContent(); while ((sibling = sibling->GetNextSibling())) { if (auto* img = HTMLImageElement::FromNode(sibling)) { if (aName == nsGkAtoms::srcset) { img->PictureSourceSrcsetChanged(this, strVal, aNotify); } else if (aName == nsGkAtoms::sizes) { img->PictureSourceSizesChanged(this, strVal, aNotify); } else if (aName == nsGkAtoms::media || aName == nsGkAtoms::type) { img->PictureSourceMediaOrTypeChanged(this, aNotify); } } } } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::media) { UpdateMediaList(aValue); } else if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) { mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal( this, aValue ? aValue->GetStringValue() : EmptyString(), aMaybeScriptedPrincipal); mSrcMediaSource = nullptr; if (aValue) { nsString srcStr = aValue->GetStringValue(); nsCOMPtr uri; NewURIFromString(srcStr, getter_AddRefs(uri)); if (uri && IsMediaSourceURI(uri)) { NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource)); } } } else if (StaticPrefs::dom_picture_source_dimension_attributes_enabled() && aNameSpaceID == kNameSpaceID_None && IsAttributeMappedToImages(aName) && IsInPicture()) { BuildMappedAttributesForImage(); nsCOMPtr sibling = AsContent(); while ((sibling = sibling->GetNextSibling())) { if (auto* img = HTMLImageElement::FromNode(sibling)) { img->PictureSourceDimensionChanged(this, aNotify); } } } return nsGenericHTMLElement::AfterSetAttr( aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); } nsresult HTMLSourceElement::BindToTree(BindContext& aContext, nsINode& aParent) { nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); NS_ENSURE_SUCCESS(rv, rv); if (auto* media = HTMLMediaElement::FromNode(aParent)) { media->NotifyAddedSource(); } if (aParent.IsHTMLElement(nsGkAtoms::picture)) { BuildMappedAttributesForImage(); } else { mMappedAttributesForImage = nullptr; } return NS_OK; } void HTMLSourceElement::UnbindFromTree(bool aNullParent) { mMappedAttributesForImage = nullptr; nsGenericHTMLElement::UnbindFromTree(aNullParent); } JSObject* HTMLSourceElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return HTMLSourceElement_Binding::Wrap(aCx, this, aGivenProto); } void HTMLSourceElement::BuildMappedAttributesForImage() { MOZ_ASSERT(NS_IsMainThread()); if (!StaticPrefs::dom_picture_source_dimension_attributes_enabled()) { return; } mMappedAttributesForImage = nullptr; Document* document = GetComposedDoc(); nsHTMLStyleSheet* sheet = document ? document->GetAttributeStyleSheet() : nullptr; if (!sheet) { return; } const nsAttrValue* width = mAttrs.GetAttr(nsGkAtoms::width); const nsAttrValue* height = mAttrs.GetAttr(nsGkAtoms::height); if (!width && !height) { return; } const size_t count = (width ? 1 : 0) + (height ? 1 : 0); RefPtr modifiableMapped(new (count) nsMappedAttributes( sheet, nsGenericHTMLElement::MapPictureSourceSizeAttributesInto)); MOZ_ASSERT(modifiableMapped); auto maybeSetAttr = [&](nsAtom* aName, const nsAttrValue* aValue) { if (!aValue) { return; } nsAttrValue val(*aValue); bool oldValueSet = false; modifiableMapped->SetAndSwapAttr(aName, val, &oldValueSet); }; maybeSetAttr(nsGkAtoms::width, width); maybeSetAttr(nsGkAtoms::height, height); RefPtr newAttrs = sheet->UniqueMappedAttributes(modifiableMapped); NS_ENSURE_TRUE_VOID(newAttrs); if (newAttrs != modifiableMapped) { // Reset the stylesheet of modifiableMapped so that it doesn't // spend time trying to remove itself from the hash. There is no // risk that modifiableMapped is in the hash since we created // it ourselves and it didn't come from the stylesheet (in which // case it would not have been modifiable). modifiableMapped->DropStyleSheetReference(); } mMappedAttributesForImage = std::move(newAttrs); } } // namespace mozilla::dom