forked from mirrors/gecko-dev
This patch will use the width/height attributes from <source> to override width/height/aspect-ratio CSS property values of <img> elements. So basically, we need to introduce an extra nsMappedAttribtue member in HTMLSourceElement (and it only stores width and height attributes). And then we use it as an extra declarations (which are generated by Gecko_GetExtraContentStyleDeclarations()) so we can override the declarations created from presentation attributes of <img>. Besides, we need to make sure <img> elements get restyled in the following cases: 1. width/height attributes is changed in <source> elements 2. <source> is inserted as a <picture>'s child 3. <source> is removed from the child list of <picture> 4. <img> is inserted as a <picture>'s child 5. <img> is removed from the child list of <picture> We make the responsive source synchronously get updated in the previous patch, so now we can just restyle the image when updating its responsive source. Note: We fix the reflection of percentages for width/height attributes in the next patch. Differential Revision: https://phabricator.services.mozilla.com/D152586
235 lines
8.2 KiB
C++
235 lines
8.2 KiB
C++
/* -*- 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<mozilla::dom::NodeInfo>&& 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 =
|
|
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);
|
|
}
|
|
|
|
nsresult 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 <picture> with a valid <img>, 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 <source> and notify them of the change
|
|
nsCOMPtr<nsIContent> 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<nsIURI> 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<nsIContent> 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<JSObject*> 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<nsMappedAttributes> 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<nsMappedAttributes> 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
|