Bug 1542807 part 1 - Create generated content and use normal box construction for list-style-type/list-style-image ::markers. r=emilio

The change from 0x25FE to 0x25AA for list-style-type:square
was approved here:
https://github.com/w3c/csswg-drafts/issues/6200#issuecomment-828616747

Differential Revision: https://phabricator.services.mozilla.com/D111691
This commit is contained in:
Mats Palmgren 2021-06-14 01:22:04 +00:00
parent f62193272a
commit a901b05850
21 changed files with 555 additions and 122 deletions

View File

@ -1129,11 +1129,10 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
} }
} else if (content->IsGeneratedContentContainerForMarker()) { } else if (content->IsGeneratedContentContainerForMarker()) {
if (aContext->IsHTMLListItem()) { if (aContext->IsHTMLListItem()) {
const nsStyleList* styleList = frame->StyleList(); newAcc = new HTMLListBulletAccessible(content, document);
if (!styleList->mListStyleImage.IsNone() || }
!styleList->mCounterStyle.IsNone()) { if (aIsSubtreeHidden) {
newAcc = new HTMLListBulletAccessible(content, document); *aIsSubtreeHidden = true;
}
} }
} }
} }

View File

@ -422,8 +422,8 @@ uint32_t HyperTextAccessible::TransformOffset(LocalAccessible* aDescendant,
// We manually set the offset so the error doesn't propagate up. // We manually set the offset so the error doesn't propagate up.
if (offset == 0 && parent && parent->IsHTMLListItem() && if (offset == 0 && parent && parent->IsHTMLListItem() &&
descendant->LocalPrevSibling() && descendant->LocalPrevSibling() &&
descendant->LocalPrevSibling()->GetFrame() && descendant->LocalPrevSibling() ==
descendant->LocalPrevSibling()->GetFrame()->IsBulletFrame()) { parent->AsHTMLListItem()->Bullet()) {
offset = 0; offset = 0;
} else { } else {
offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;

View File

@ -10,11 +10,10 @@
#include "DocAccessible.h" #include "DocAccessible.h"
#include "EventTree.h" #include "EventTree.h"
#include "nsAccUtils.h" #include "nsAccUtils.h"
#include "nsTextEquivUtils.h" #include "nsPersistentProperties.h"
#include "Role.h" #include "Role.h"
#include "States.h" #include "States.h"
#include "nsBulletFrame.h"
#include "nsLayoutUtils.h" #include "nsLayoutUtils.h"
using namespace mozilla; using namespace mozilla;
@ -90,24 +89,7 @@ HTMLListBulletAccessible::HTMLListBulletAccessible(nsIContent* aContent,
// HTMLListBulletAccessible: LocalAccessible // HTMLListBulletAccessible: LocalAccessible
ENameValueFlag HTMLListBulletAccessible::Name(nsString& aName) const { ENameValueFlag HTMLListBulletAccessible::Name(nsString& aName) const {
aName.Truncate(); nsLayoutUtils::GetMarkerSpokenText(mContent, aName);
// Native anonymous content, ARIA can't be used. Get list bullet text.
if (nsBulletFrame* frame = do_QueryFrame(GetFrame())) {
if (!frame->StyleList()->mListStyleImage.IsNone()) {
// Bullet is an image, so use default bullet character.
const char16_t kDiscCharacter = 0x2022;
aName.Assign(kDiscCharacter);
aName.Append(' ');
return eNameOK;
}
frame->GetSpokenText(aName);
} else {
// If marker is not a bullet frame but instead has content
nsTextEquivUtils::AppendFromDOMChildren(mContent, &aName);
aName.CompressWhitespace();
}
return eNameOK; return eNameOK;
} }

View File

@ -30,6 +30,11 @@ already_AddRefed<GeneratedImageContent> GeneratedImageContent::Create(
return image.forget(); return image.forget();
} }
already_AddRefed<GeneratedImageContent>
GeneratedImageContent::CreateForListStyleImage(Document& aDocument) {
return Create(aDocument, uint32_t(-1));
}
JSObject* GeneratedImageContent::WrapNode(JSContext* aCx, JSObject* GeneratedImageContent::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) { JS::Handle<JSObject*> aGivenProto) {
return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto); return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto);

View File

@ -23,6 +23,9 @@ class GeneratedImageContent final : public nsGenericHTMLElement {
public: public:
static already_AddRefed<GeneratedImageContent> Create(Document&, static already_AddRefed<GeneratedImageContent> Create(Document&,
uint32_t aContentIndex); uint32_t aContentIndex);
// An image created from 'list-style-image' for a ::marker pseudo.
static already_AddRefed<GeneratedImageContent> CreateForListStyleImage(
Document&);
explicit GeneratedImageContent(already_AddRefed<dom::NodeInfo>&& aNodeInfo) explicit GeneratedImageContent(already_AddRefed<dom::NodeInfo>&& aNodeInfo)
: nsGenericHTMLElement(std::move(aNodeInfo)) { : nsGenericHTMLElement(std::move(aNodeInfo)) {
@ -30,6 +33,13 @@ class GeneratedImageContent final : public nsGenericHTMLElement {
"Someone messed up our nodeinfo"); "Someone messed up our nodeinfo");
} }
EventStates IntrinsicState() const override {
EventStates state = nsGenericHTMLElement::IntrinsicState();
if (mBroken) {
state |= NS_EVENT_STATE_BROKEN;
}
return state;
}
nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const final; nsresult Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const final;
nsresult CopyInnerTo(GeneratedImageContent* aDest) { nsresult CopyInnerTo(GeneratedImageContent* aDest) {
@ -39,14 +49,25 @@ class GeneratedImageContent final : public nsGenericHTMLElement {
return NS_OK; return NS_OK;
} }
// Is this an image created from 'list-style-image'?
bool IsForListStyleImageMarker() const { return Index() == uint32_t(-1); }
// @note we use -1 for images created from 'list-style-image'
uint32_t Index() const { return mIndex; } uint32_t Index() const { return mIndex; }
// Notify this image failed to load.
void NotifyLoadFailed() {
mBroken = true;
UpdateState(true);
}
protected: protected:
JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final; JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
private: private:
virtual ~GeneratedImageContent() = default; virtual ~GeneratedImageContent() = default;
uint32_t mIndex = 0; uint32_t mIndex = 0;
bool mBroken = false;
}; };
} // namespace dom } // namespace dom

View File

@ -37,7 +37,6 @@
#include "Layers.h" #include "Layers.h"
#include "nsAnimationManager.h" #include "nsAnimationManager.h"
#include "nsBlockFrame.h" #include "nsBlockFrame.h"
#include "nsBulletFrame.h"
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsCSSFrameConstructor.h" #include "nsCSSFrameConstructor.h"
#include "nsCSSRendering.h" #include "nsCSSRendering.h"
@ -465,7 +464,11 @@ static bool StateChangeMayAffectFrame(const Element& aElement,
const nsIFrame& aFrame, const nsIFrame& aFrame,
EventStates aStates) { EventStates aStates) {
if (aFrame.IsGeneratedContentFrame()) { if (aFrame.IsGeneratedContentFrame()) {
// If it's generated content, ignore LOADING/etc state changes on it. if (aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)) {
return aStates.HasState(NS_EVENT_STATE_BROKEN);
}
// If it's other generated content, ignore LOADING/etc state changes on it.
return false; return false;
} }
@ -1961,7 +1964,7 @@ static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
parent = FirstContinuationOrPartOfIBSplit(parent); parent = FirstContinuationOrPartOfIBSplit(parent);
// We've handled already anon boxes and bullet frames, so now we're looking at // We've handled already anon boxes, so now we're looking at
// a frame of a DOM element or pseudo. Hop through anon and line-boxes // a frame of a DOM element or pseudo. Hop through anon and line-boxes
// generated by our DOM parent, and go find the owner frame for it. // generated by our DOM parent, and go find the owner frame for it.
while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) { while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {

View File

@ -268,8 +268,8 @@ nsIFrame* NS_NewScrollbarButtonFrame(PresShell* aPresShell,
ComputedStyle* aStyle); ComputedStyle* aStyle);
nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*); nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*);
nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*);
// Returns true if aFrame is an anonymous flex/grid item. // Returns true if aFrame is an anonymous flex/grid item.
static inline bool IsAnonymousFlexOrGridItem(const nsIFrame* aFrame) { static inline bool IsAnonymousFlexOrGridItem(const nsIFrame* aFrame) {
@ -1600,6 +1600,66 @@ already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGeneratedContent(
return nullptr; return nullptr;
} }
void nsCSSFrameConstructor::CreateGeneratedContentFromListStyle(
nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle,
const FunctionRef<void(nsIContent*)> aAddChild) {
const nsStyleList* styleList = aPseudoStyle.StyleList();
if (!styleList->mListStyleImage.IsNone()) {
RefPtr<nsIContent> child =
GeneratedImageContent::CreateForListStyleImage(*mDocument);
aAddChild(child);
child = CreateGenConTextNode(aState, u" "_ns, nullptr);
aAddChild(child);
return;
}
CreateGeneratedContentFromListStyleType(aState, aPseudoStyle, aAddChild);
}
void nsCSSFrameConstructor::CreateGeneratedContentFromListStyleType(
nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle,
const FunctionRef<void(nsIContent*)> aAddChild) {
const nsStyleList* styleList = aPseudoStyle.StyleList();
CounterStyle* counterStyle =
mPresShell->GetPresContext()->CounterStyleManager()->ResolveCounterStyle(
styleList->mCounterStyle);
bool needUseNode = false;
switch (counterStyle->GetStyle()) {
case NS_STYLE_LIST_STYLE_NONE:
return;
case NS_STYLE_LIST_STYLE_DISC:
case NS_STYLE_LIST_STYLE_CIRCLE:
case NS_STYLE_LIST_STYLE_SQUARE:
case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
break;
default:
const auto* anonStyle = counterStyle->AsAnonymous();
if (!anonStyle || !anonStyle->IsSingleString()) {
needUseNode = true;
}
}
auto node = MakeUnique<nsCounterUseNode>(nsCounterUseNode::ForLegacyBullet,
styleList->mCounterStyle);
if (!needUseNode) {
nsAutoString text;
node->GetText(WritingMode(&aPseudoStyle), counterStyle, text);
// Note that we're done with 'node' in this case. It's not inserted into
// any list so it's deleted when we return.
RefPtr<nsIContent> child = CreateGenConTextNode(aState, text, nullptr);
aAddChild(child);
return;
}
nsCounterList* counterList =
mCounterManager.CounterListFor(nsGkAtoms::list_item);
auto initializer = MakeUnique<nsGenConInitializer>(
std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
RefPtr<nsIContent> child =
CreateGenConTextNode(aState, EmptyString(), std::move(initializer));
aAddChild(child);
}
/* /*
* aParentFrame - the frame that should be the parent of the generated * aParentFrame - the frame that should be the parent of the generated
* content. This is the frame for the corresponding content node, * content. This is the frame for the corresponding content node,
@ -1660,8 +1720,10 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem(
MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement"); MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
} }
// |ProbePseudoStyleFor| checked the 'display' property and the // |ProbePseudoElementStyle| checked the 'display' property and the
// |ContentCount()| of the 'content' property for us. // |ContentCount()| of the 'content' property for us, and for ::marker
// also that we have either a 'list-style-type' or 'list-style-image'
// non-initial value in case we have no 'content'.
RefPtr<NodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo( RefPtr<NodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
elemName, nullptr, kNameSpaceID_None, nsINode::ELEMENT_NODE); elemName, nullptr, kNameSpaceID_None, nsINode::ELEMENT_NODE);
RefPtr<Element> container; RefPtr<Element> container;
@ -1704,26 +1766,31 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem(
pseudoStyle = ServoStyleSet::ResolveServoStyle(*container); pseudoStyle = ServoStyleSet::ResolveServoStyle(*container);
} }
uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount(); auto AppendChild = [&container, this](nsIContent* aChild) {
for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
nsCOMPtr<nsIContent> content = CreateGeneratedContent(
aState, aOriginatingElement, *pseudoStyle, contentIndex);
if (!content) {
continue;
}
// We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE // We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
// here; it would get set under AppendChildTo. But AppendChildTo might // here; it would get set under AppendChildTo. But AppendChildTo might
// think that we're going from not being anonymous to being anonymous and // think that we're going from not being anonymous to being anonymous and
// do some extra work; setting the flag here avoids that. // do some extra work; setting the flag here avoids that.
content->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE); aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
container->AppendChildTo(content, false, IgnoreErrors()); container->AppendChildTo(aChild, false, IgnoreErrors());
if (auto* element = Element::FromNode(content)) { if (auto* childElement = Element::FromNode(aChild)) {
// If we created any children elements, Servo needs to traverse them, but // If we created any children elements, Servo needs to traverse them, but
// the root is already set up. // the root is already set up.
mPresShell->StyleSet()->StyleNewSubtree(element); mPresShell->StyleSet()->StyleNewSubtree(childElement);
}
};
const uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount();
for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
if (RefPtr<nsIContent> content = CreateGeneratedContent(
aState, aOriginatingElement, *pseudoStyle, contentIndex)) {
AppendChild(content);
} }
} }
// If a ::marker has no 'content' then generate it from its 'list-style-*'.
if (contentCount == 0 && aPseudoElement == PseudoStyleType::marker) {
CreateGeneratedContentFromListStyle(aState, *pseudoStyle, AppendChild);
}
AddFrameConstructionItemsInternal(aState, container, aParentFrame, true, AddFrameConstructionItemsInternal(aState, container, aParentFrame, true,
pseudoStyle, {ItemFlag::IsGeneratedContent}, pseudoStyle, {ItemFlag::IsGeneratedContent},
aItems); aItems);
@ -3354,6 +3421,13 @@ nsCSSFrameConstructor::FindGeneratedImageData(const Element& aElement,
return nullptr; return nullptr;
} }
auto& generatedContent = static_cast<const GeneratedImageContent&>(aElement);
if (generatedContent.IsForListStyleImageMarker()) {
static const FrameConstructionData sImgData =
SIMPLE_FCDATA(NS_NewImageFrameForListStyleImage);
return &sImgData;
}
static const FrameConstructionData sImgData = static const FrameConstructionData sImgData =
SIMPLE_FCDATA(NS_NewImageFrameForGeneratedContentIndex); SIMPLE_FCDATA(NS_NewImageFrameForGeneratedContentIndex);
return &sImgData; return &sImgData;
@ -3807,16 +3881,6 @@ void nsCSSFrameConstructor::ConstructFrameFromItemInternal(
} }
} }
if (computedStyle->GetPseudoType() == PseudoStyleType::marker &&
newFrame->IsBulletFrame()) {
MOZ_ASSERT(!computedStyle->StyleContent()->ContentCount());
auto* node = new nsCounterUseNode(nsCounterUseNode::ForLegacyBullet);
auto* list = mCounterManager.CounterListFor(nsGkAtoms::list_item);
if (node->InitBullet(list, newFrame)) {
CountersDirty();
}
}
NS_ASSERTION(newFrame->IsFrameOfType(nsIFrame::eLineParticipant) == NS_ASSERTION(newFrame->IsFrameOfType(nsIFrame::eLineParticipant) ==
((bits & FCDATA_IS_LINE_PARTICIPANT) != 0), ((bits & FCDATA_IS_LINE_PARTICIPANT) != 0),
"Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits"); "Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits");
@ -5231,16 +5295,6 @@ nsCSSFrameConstructor::FindElementTagData(const Element& aElement,
ComputedStyle& aStyle, ComputedStyle& aStyle,
nsIFrame* aParentFrame, nsIFrame* aParentFrame,
ItemFlags aFlags) { ItemFlags aFlags) {
// A ::marker pseudo creates a nsBulletFrame, unless 'content' was set.
if (aStyle.GetPseudoType() == PseudoStyleType::marker &&
aStyle.StyleContent()->ContentCount() == 0) {
static const FrameConstructionData data = FCDATA_DECL(
FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH |
FCDATA_DISALLOW_GENERATED_CONTENT | FCDATA_IS_LINE_PARTICIPANT |
FCDATA_IS_INLINE | FCDATA_USE_CHILD_ITEMS,
NS_NewBulletFrame);
return &data;
}
switch (aElement.GetNameSpaceID()) { switch (aElement.GetNameSpaceID()) {
case kNameSpaceID_XHTML: case kNameSpaceID_XHTML:
return FindHTMLData(aElement, aParentFrame, aStyle); return FindHTMLData(aElement, aParentFrame, aStyle);
@ -7063,6 +7117,48 @@ void nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aStartChild,
} }
} }
// This handles fallback to 'list-style-type' when a 'list-style-image' fails
// to load.
if (aStartChild->IsInNativeAnonymousSubtree() &&
aStartChild->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)) {
MOZ_ASSERT(isSingleInsert);
MOZ_ASSERT(insertion.mParentFrame->Style()->GetPseudoType() ==
PseudoStyleType::marker,
"we can only handle ::marker fallback for now");
nsIContent* const nextSibling = aStartChild->GetNextSibling();
MOZ_ASSERT(nextSibling && nextSibling->IsText(),
"expected a text node after the list-style-image image");
RemoveFrame(kPrincipalList, nextSibling->GetPrimaryFrame());
auto* const container = aStartChild->GetParent()->AsElement();
nsIContent* firstNewChild = nullptr;
auto InsertChild = [this, container, nextSibling,
&firstNewChild](RefPtr<nsIContent>&& aChild) {
// We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
// here; it would get set under AppendChildTo. But AppendChildTo might
// think that we're going from not being anonymous to being anonymous and
// do some extra work; setting the flag here avoids that.
aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
container->InsertChildBefore(aChild, nextSibling, false, IgnoreErrors());
if (auto* childElement = Element::FromNode(aChild)) {
// If we created any children elements, Servo needs to traverse them,
// but the root is already set up.
mPresShell->StyleSet()->StyleNewSubtree(childElement);
}
if (!firstNewChild) {
firstNewChild = aChild;
}
};
CreateGeneratedContentFromListStyleType(
state, *insertion.mParentFrame->Style(), InsertChild);
if (!firstNewChild) {
// No fallback content - we're done.
return;
}
aStartChild = firstNewChild;
MOZ_ASSERT(firstNewChild->GetNextSibling() == nextSibling,
"list-style-type should only create one child");
}
AutoFrameConstructionItemList items(this); AutoFrameConstructionItemList items(this);
ParentType parentType = GetParentType(frameType); ParentType parentType = GetParentType(frameType);
FlattenedChildIterator iter(insertion.mContainer); FlattenedChildIterator iter(insertion.mContainer);
@ -9575,6 +9671,13 @@ void nsCSSFrameConstructor::ProcessChildren(
listItem->SetMarkerFrameForListItem(childFrame); listItem->SetMarkerFrameForListItem(childFrame);
MOZ_ASSERT(listItem->HasAnyStateBits( MOZ_ASSERT(listItem->HasAnyStateBits(
NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER) == isOutsideMarker); NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER) == isOutsideMarker);
#ifdef ACCESSIBILITY
if (nsAccessibilityService* accService =
PresShell::GetAccessibilityService()) {
auto* marker = markerFrame->GetContent();
accService->ContentRangeInserted(mPresShell, marker, nullptr);
}
#endif
break; break;
} }
} }

View File

@ -14,6 +14,7 @@
#include "mozilla/ArenaAllocator.h" #include "mozilla/ArenaAllocator.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/FunctionRef.h"
#include "mozilla/LinkedList.h" #include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
#include "mozilla/RestyleManager.h" #include "mozilla/RestyleManager.h"
@ -333,8 +334,10 @@ class nsCSSFrameConstructor final : public nsFrameManager {
void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const; void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
// temporary - please don't add external uses outside of nsBulletFrame #ifdef ACCESSIBILITY
nsCounterManager* CounterManager() { return &mCounterManager; } // Exposed only for nsLayoutUtils::GetMarkerSpokenText to use.
const nsCounterManager* CounterManager() const { return &mCounterManager; }
#endif
private: private:
struct FrameConstructionItem; struct FrameConstructionItem;
@ -460,6 +463,19 @@ class nsCSSFrameConstructor final : public nsFrameManager {
nsFrameConstructorState& aState, const Element& aOriginatingElement, nsFrameConstructorState& aState, const Element& aOriginatingElement,
ComputedStyle& aComputedStyle, uint32_t aContentIndex); ComputedStyle& aComputedStyle, uint32_t aContentIndex);
/**
* Create child content nodes for a ::marker from its 'list-style-*' values.
*/
void CreateGeneratedContentFromListStyle(
nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle,
const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
/**
* Create child content nodes for a ::marker from its 'list-style-type'.
*/
void CreateGeneratedContentFromListStyleType(
nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle,
const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
// aParentFrame may be null; this method doesn't use it directly in any case. // aParentFrame may be null; this method doesn't use it directly in any case.
void CreateGeneratedContentItem(nsFrameConstructorState& aState, void CreateGeneratedContentItem(nsFrameConstructorState& aState,
nsContainerFrame* aParentFrame, nsContainerFrame* aParentFrame,

View File

@ -13,7 +13,6 @@
#include "mozilla/PresShell.h" #include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h" #include "mozilla/StaticPrefs_layout.h"
#include "mozilla/WritingModes.h" #include "mozilla/WritingModes.h"
#include "nsBulletFrame.h" // legacy location for list style type to text code
#include "nsContentUtils.h" #include "nsContentUtils.h"
#include "nsIContent.h" #include "nsIContent.h"
#include "nsTArray.h" #include "nsTArray.h"
@ -43,13 +42,6 @@ bool nsCounterUseNode::InitTextFrame(nsGenConList* aList,
return false; return false;
} }
bool nsCounterUseNode::InitBullet(nsGenConList* aList, nsIFrame* aBullet) {
MOZ_ASSERT(aBullet->IsBulletFrame());
MOZ_ASSERT(aBullet->Style()->GetPseudoType() == PseudoStyleType::marker);
MOZ_ASSERT(mForLegacyBullet);
return InitTextFrame(aList, aBullet, nullptr);
}
// assign the correct |mValueAfter| value to a node that has been inserted // assign the correct |mValueAfter| value to a node that has been inserted
// Should be called immediately after calling |Insert|. // Should be called immediately after calling |Insert|.
void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) { void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) {
@ -59,11 +51,6 @@ void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) {
nsAutoString contentString; nsAutoString contentString;
GetText(contentString); GetText(contentString);
mText->SetText(contentString, aNotify); mText->SetText(contentString, aNotify);
} else if (mForLegacyBullet) {
MOZ_ASSERT_IF(mPseudoFrame, mPseudoFrame->IsBulletFrame());
if (nsBulletFrame* f = do_QueryFrame(mPseudoFrame)) {
f->SetOrdinal(mValueAfter, aNotify);
}
} }
} }
@ -82,9 +69,34 @@ void nsCounterChangeNode::Calc(nsCounterList* aList) {
} }
} }
// The text that should be displayed for this counter.
void nsCounterUseNode::GetText(nsString& aResult) { void nsCounterUseNode::GetText(nsString& aResult) {
aResult.Truncate(); CounterStyle* style =
mPseudoFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle(
mCounterStyle);
GetText(mPseudoFrame->GetWritingMode(), style, aResult);
}
void nsCounterUseNode::GetText(WritingMode aWM, CounterStyle* aStyle,
nsString& aResult) {
const bool isBidiRTL = aWM.IsBidiRTL();
auto AppendCounterText = [&aResult, isBidiRTL](const nsAutoString& aText,
bool aIsRTL) {
if (MOZ_LIKELY(isBidiRTL == aIsRTL)) {
aResult.Append(aText);
} else {
// RLM = 0x200f, LRM = 0x200e
const char16_t mark = aIsRTL ? 0x200f : 0x200e;
aResult.Append(mark);
aResult.Append(aText);
aResult.Append(mark);
}
};
if (mForLegacyBullet) {
nsAutoString prefix;
aStyle->GetPrefix(prefix);
aResult.Assign(prefix);
}
AutoTArray<nsCounterNode*, 8> stack; AutoTArray<nsCounterNode*, 8> stack;
stack.AppendElement(static_cast<nsCounterNode*>(this)); stack.AppendElement(static_cast<nsCounterNode*>(this));
@ -95,21 +107,26 @@ void nsCounterUseNode::GetText(nsString& aResult) {
} }
} }
WritingMode wm = mPseudoFrame->GetWritingMode(); for (nsCounterNode* n : Reversed(stack)) {
CounterStyle* style =
mPseudoFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle(
mCounterStyle);
for (uint32_t i = stack.Length() - 1;; --i) {
nsCounterNode* n = stack[i];
nsAutoString text; nsAutoString text;
bool isTextRTL; bool isTextRTL;
style->GetCounterText(n->mValueAfter, wm, text, isTextRTL); aStyle->GetCounterText(n->mValueAfter, aWM, text, isTextRTL);
aResult.Append(text); if (!mForLegacyBullet || aStyle->IsBullet()) {
if (i == 0) { aResult.Append(text);
} else {
AppendCounterText(text, isTextRTL);
}
if (n == this) {
break; break;
} }
aResult.Append(mSeparator); aResult.Append(mSeparator);
} }
if (mForLegacyBullet) {
nsAutoString suffix;
aStyle->GetSuffix(suffix);
aResult.Append(suffix);
}
} }
void nsCounterList::SetScope(nsCounterNode* aNode) { void nsCounterList::SetScope(nsCounterNode* aNode) {
@ -340,6 +357,41 @@ bool nsCounterManager::DestroyNodesFor(nsIFrame* aFrame) {
return destroyedAny; return destroyedAny;
} }
#ifdef ACCESSIBILITY
void nsCounterManager::GetSpokenCounterText(nsIFrame* aFrame,
nsAString& aText) const {
CounterValue ordinal = 1;
if (const auto* list = mNames.Get(nsGkAtoms::list_item)) {
for (nsCounterNode* n = list->GetFirstNodeFor(aFrame);
n && n->mPseudoFrame == aFrame; n = list->Next(n)) {
if (n->mType == nsCounterNode::USE) {
ordinal = n->mValueAfter;
break;
}
}
}
CounterStyle* counterStyle =
aFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle(
aFrame->StyleList()->mCounterStyle);
nsAutoString text;
bool isBullet;
counterStyle->GetSpokenCounterText(ordinal, aFrame->GetWritingMode(), text,
isBullet);
if (isBullet) {
aText = text;
if (!counterStyle->IsNone()) {
aText.Append(' ');
}
} else {
counterStyle->GetPrefix(aText);
aText += text;
nsAutoString suffix;
counterStyle->GetSuffix(suffix);
aText += suffix;
}
}
#endif
#ifdef DEBUG #ifdef DEBUG
void nsCounterManager::Dump() { void nsCounterManager::Dump() {
printf("\n\nCounter Manager Lists:\n"); printf("\n\nCounter Manager Lists:\n");

View File

@ -62,6 +62,7 @@ struct nsCounterNode : public nsGenConNode {
// 'counter-reset', 'counter-increment' or 'counter-set' property // 'counter-reset', 'counter-increment' or 'counter-set' property
// instead of within the 'content' property but offset to ensure // instead of within the 'content' property but offset to ensure
// that (reset, increment, set, use) sort in that order. // that (reset, increment, set, use) sort in that order.
// It is zero for legacy bullet USE counter nodes.
// (This slight weirdness allows sharing a lot of code with 'quotes'.) // (This slight weirdness allows sharing a lot of code with 'quotes'.)
nsCounterNode(int32_t aContentIndex, Type aType) nsCounterNode(int32_t aContentIndex, Type aType)
: nsGenConNode(aContentIndex), mType(aType) {} : nsGenConNode(aContentIndex), mType(aType) {}
@ -83,10 +84,10 @@ struct nsCounterUseNode : public nsCounterNode {
bool mForLegacyBullet = false; bool mForLegacyBullet = false;
enum ForLegacyBullet { ForLegacyBullet }; enum ForLegacyBullet { ForLegacyBullet };
explicit nsCounterUseNode(enum ForLegacyBullet) nsCounterUseNode(enum ForLegacyBullet, mozilla::CounterStylePtr aCounterStyle)
: nsCounterNode(0, USE), mForLegacyBullet(true) { : nsCounterNode(0, USE),
mCounterStyle = nsGkAtoms::list_item; mCounterStyle(std::move(aCounterStyle)),
} mForLegacyBullet(true) {}
// args go directly to member variables here and of nsGenConNode // args go directly to member variables here and of nsGenConNode
nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle, nsString aSeparator, nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle, nsString aSeparator,
@ -98,10 +99,8 @@ struct nsCounterUseNode : public nsCounterNode {
NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
} }
virtual bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame, bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
nsIFrame* aTextFrame) override; nsIFrame* aTextFrame) override;
bool InitBullet(nsGenConList* aList, nsIFrame* aBulletFrame);
// assign the correct |mValueAfter| value to a node that has been inserted, // assign the correct |mValueAfter| value to a node that has been inserted,
// and update the value of the text node, notifying if `aNotify` is true. // and update the value of the text node, notifying if `aNotify` is true.
@ -110,6 +109,8 @@ struct nsCounterUseNode : public nsCounterNode {
// The text that should be displayed for this counter. // The text that should be displayed for this counter.
void GetText(nsString& aResult); void GetText(nsString& aResult);
void GetText(mozilla::WritingMode aWM, mozilla::CounterStyle* aStyle,
nsString& aResult);
}; };
struct nsCounterChangeNode : public nsCounterNode { struct nsCounterChangeNode : public nsCounterNode {
@ -171,6 +172,11 @@ class nsCounterList : public nsGenConList {
public: public:
nsCounterList() : nsGenConList(), mDirty(false) {} nsCounterList() : nsGenConList(), mDirty(false) {}
// Return the first node for aFrame on this list, or nullptr.
nsCounterNode* GetFirstNodeFor(nsIFrame* aFrame) const {
return static_cast<nsCounterNode*>(nsGenConList::GetFirstNodeFor(aFrame));
}
void Insert(nsCounterNode* aNode) { void Insert(nsCounterNode* aNode) {
nsGenConList::Insert(aNode); nsGenConList::Insert(aNode);
// Don't SetScope if we're dirty -- we'll reset all the scopes anyway, // Don't SetScope if we're dirty -- we'll reset all the scopes anyway,
@ -235,6 +241,11 @@ class nsCounterManager {
// Clear all data. // Clear all data.
void Clear() { mNames.Clear(); } void Clear() { mNames.Clear(); }
#ifdef ACCESSIBILITY
// Returns the spoken text for the 'list-item' counter for aFrame in aText.
void GetSpokenCounterText(nsIFrame* aFrame, nsAString& aText) const;
#endif
#ifdef DEBUG #ifdef DEBUG
void Dump(); void Dump();
#endif #endif

View File

@ -34,7 +34,6 @@ struct nsGenConNode : public mozilla::LinkedListElement<nsGenConNode> {
// null for: // null for:
// * content: no-open-quote / content: no-close-quote // * content: no-open-quote / content: no-close-quote
// * counter nodes for increments and resets // * counter nodes for increments and resets
// * counter nodes for bullets (mPseudoFrame->IsBulletFrame()).
RefPtr<nsTextNode> mText; RefPtr<nsTextNode> mText;
explicit nsGenConNode(int32_t aContentIndex) explicit nsGenConNode(int32_t aContentIndex)
@ -65,12 +64,9 @@ struct nsGenConNode : public mozilla::LinkedListElement<nsGenConNode> {
void CheckFrameAssertions() { void CheckFrameAssertions() {
NS_ASSERTION( NS_ASSERTION(
mContentIndex < int32_t(mPseudoFrame->StyleContent()->ContentCount()) || mContentIndex < int32_t(mPseudoFrame->StyleContent()->ContentCount()) ||
// Special-case for the use node created for the legacy markers, // Special-case for the USE node created for the legacy markers,
// which don't use the content property. // which don't use the content property.
(mPseudoFrame->IsBulletFrame() && mContentIndex == 0 && mContentIndex == 0,
mPseudoFrame->Style()->GetPseudoType() ==
mozilla::PseudoStyleType::marker &&
!mPseudoFrame->StyleContent()->ContentCount()),
"index out of range"); "index out of range");
// We allow negative values of mContentIndex for 'counter-reset' and // We allow negative values of mContentIndex for 'counter-reset' and
// 'counter-increment'. // 'counter-increment'.
@ -112,6 +108,11 @@ class nsGenConList {
// have been destroyed; otherwise false. // have been destroyed; otherwise false.
bool DestroyNodesFor(nsIFrame* aFrame); bool DestroyNodesFor(nsIFrame* aFrame);
// Return the first node for aFrame on this list, or nullptr.
nsGenConNode* GetFirstNodeFor(nsIFrame* aFrame) const {
return mNodes.Get(aFrame);
}
// Return true if |aNode1| is after |aNode2|. // Return true if |aNode1| is after |aNode2|.
static bool NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2); static bool NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2);

View File

@ -893,6 +893,40 @@ nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) {
return pseudo ? pseudo->GetPrimaryFrame() : nullptr; return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
} }
#ifdef ACCESSIBILITY
void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
nsAString& aText) {
MOZ_ASSERT(aContent && aContent->IsGeneratedContentContainerForMarker());
aText.Truncate();
nsIFrame* frame = aContent->GetPrimaryFrame();
if (!frame) {
return;
}
if (frame->StyleContent()->ContentCount() > 0) {
for (nsIFrame* child : frame->PrincipalChildList()) {
nsIFrame::RenderedText text = child->GetRenderedText();
aText += text.mString;
}
return;
}
if (!frame->StyleList()->mListStyleImage.IsNone()) {
// ::marker is an image, so use default bullet character.
static const char16_t kDiscMarkerString[] = {0x2022, ' ', 0};
aText.AssignLiteral(kDiscMarkerString);
return;
}
frame->PresContext()
->FrameConstructor()
->CounterManager()
->GetSpokenCounterText(frame, aText);
}
#endif
// static // static
nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
LayoutFrameType aFrameType, LayoutFrameType aFrameType,

View File

@ -273,6 +273,14 @@ class nsLayoutUtils {
*/ */
static nsIFrame* GetMarkerFrame(const nsIContent* aContent); static nsIFrame* GetMarkerFrame(const nsIContent* aContent);
#ifdef ACCESSIBILITY
/**
* Set aText to the spoken text for the given ::marker content (aContent)
* if it has a frame, or the empty string otherwise.
*/
static void GetMarkerSpokenText(const nsIContent* aContent, nsAString& aText);
#endif
/** /**
* Given a frame, search up the frame tree until we find an * Given a frame, search up the frame tree until we find an
* ancestor that (or the frame itself) is of type aFrameType, if any. * ancestor that (or the frame itself) is of type aFrameType, if any.

View File

@ -7395,6 +7395,11 @@ void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
SetProperty(InsideMarkerProperty(), aMarkerFrame); SetProperty(InsideMarkerProperty(), aMarkerFrame);
AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER); AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER);
} else { } else {
if (nsBlockFrame* marker = do_QueryFrame(aMarkerFrame)) {
// An outside ::marker needs to be an independent formatting context
// to avoid being influenced by the float manager etc.
marker->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
}
SetProperty(OutsideMarkerProperty(), SetProperty(OutsideMarkerProperty(),
new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame)); new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER); AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);

View File

@ -39,6 +39,7 @@
#include "nsFontMetrics.h" #include "nsFontMetrics.h"
#include "nsIImageLoadingContent.h" #include "nsIImageLoadingContent.h"
#include "nsImageLoadingContent.h" #include "nsImageLoadingContent.h"
#include "nsImageRenderer.h"
#include "nsString.h" #include "nsString.h"
#include "nsPrintfCString.h" #include "nsPrintfCString.h"
#include "nsPresContext.h" #include "nsPresContext.h"
@ -98,6 +99,89 @@ using namespace mozilla::layers;
using mozilla::layout::TextDrawTarget; using mozilla::layout::TextDrawTarget;
class nsDisplayGradient final : public nsPaintedDisplayItem {
public:
nsDisplayGradient(nsDisplayListBuilder* aBuilder, nsImageFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayGradient);
}
~nsDisplayGradient() final { MOZ_COUNT_DTOR(nsDisplayGradient); }
nsDisplayItemGeometry* AllocateGeometry(
nsDisplayListBuilder* aBuilder) final {
return new nsDisplayItemGenericImageGeometry(this, aBuilder);
}
nsRect GetBounds(bool* aSnap) const {
*aSnap = true;
auto* imageFrame = static_cast<nsImageFrame*>(mFrame);
return imageFrame->GetInnerArea() + ToReferenceFrame();
}
nsRect GetBounds(nsDisplayListBuilder*, bool* aSnap) const final {
return GetBounds(aSnap);
}
void Paint(nsDisplayListBuilder*, gfxContext* aCtx) final;
bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder&,
mozilla::wr::IpcResourceUpdateQueue&,
const StackingContextHelper&,
mozilla::layers::RenderRootStateManager*,
nsDisplayListBuilder*) final;
NS_DISPLAY_DECL_NAME("Gradient", TYPE_GRADIENT)
};
void nsDisplayGradient::Paint(nsDisplayListBuilder* aBuilder,
gfxContext* aCtx) {
auto* frame = static_cast<nsImageFrame*>(Frame());
nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
aBuilder->GetImageRendererFlags());
nsSize size = frame->GetSize();
imageRenderer.SetPreferredSize({}, size);
ImgDrawResult result;
if (!imageRenderer.PrepareImage()) {
result = imageRenderer.PrepareResult();
} else {
nsRect dest(ToReferenceFrame(), size);
result = imageRenderer.DrawLayer(frame->PresContext(), *aCtx, dest, dest,
dest.TopLeft(), GetPaintRect(),
dest.Size(), /* aOpacity = */ 1.0f);
}
nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
}
bool nsDisplayGradient::CreateWebRenderCommands(
wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
const StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
auto* frame = static_cast<nsImageFrame*>(Frame());
nsImageRenderer imageRenderer(frame, frame->GetImageFromStyle(),
aDisplayListBuilder->GetImageRendererFlags());
nsSize size = frame->GetSize();
imageRenderer.SetPreferredSize({}, size);
ImgDrawResult result;
if (!imageRenderer.PrepareImage()) {
result = imageRenderer.PrepareResult();
} else {
nsRect dest(ToReferenceFrame(), size);
result = imageRenderer.BuildWebRenderDisplayItemsForLayer(
frame->PresContext(), aBuilder, aResources, aSc, aManager, this, dest,
dest, dest.TopLeft(), dest, dest.Size(),
/* aOpacity = */ 1.0f);
if (result == ImgDrawResult::NOT_SUPPORTED) {
return false;
}
}
nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
return true;
}
// sizes (pixels) for image icon, padding and border frame // sizes (pixels) for image icon, padding and border frame
#define ICON_SIZE (16) #define ICON_SIZE (16)
#define ICON_PADDING (3) #define ICON_PADDING (3)
@ -178,6 +262,12 @@ nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell,
nsImageFrame::Kind::ContentPropertyAtIndex); nsImageFrame::Kind::ContentPropertyAtIndex);
} }
nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell,
ComputedStyle* aStyle) {
return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(),
nsImageFrame::Kind::ListStyleImage);
}
bool nsImageFrame::ShouldShowBrokenImageIcon() const { bool nsImageFrame::ShouldShowBrokenImageIcon() const {
// NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and
// Blink behave differently here for content: url(..), for now adapt to // Blink behave differently here for content: url(..), for now adapt to
@ -241,6 +331,11 @@ NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame)
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
a11y::AccType nsImageFrame::AccessibleType() { a11y::AccType nsImageFrame::AccessibleType() {
if (mKind == Kind::ListStyleImage) {
// This is an HTMLListBulletAccessible.
return a11y::eNoType;
}
// Don't use GetImageMap() to avoid reentrancy into accessibility. // Don't use GetImageMap() to avoid reentrancy into accessibility.
if (HasImageMap()) { if (HasImageMap()) {
return a11y::eHTMLImageMapType; return a11y::eHTMLImageMapType;
@ -327,6 +422,12 @@ void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
MaybeRecordContentUrlOnImageTelemetry(); MaybeRecordContentUrlOnImageTelemetry();
// A ::marker's default size is calculated from the font's em-size.
if (IsForMarkerPseudo()) {
mIntrinsicSize = IntrinsicSize(0, 0);
UpdateIntrinsicSize();
}
auto newOrientation = StyleVisibility()->mImageOrientation; auto newOrientation = StyleVisibility()->mImageOrientation;
// We need to update our orientation either if we had no ComputedStyle before // We need to update our orientation either if we had no ComputedStyle before
@ -361,8 +462,15 @@ static bool SizeIsAvailable(imgIRequest* aRequest) {
const StyleImage* nsImageFrame::GetImageFromStyle() const { const StyleImage* nsImageFrame::GetImageFromStyle() const {
if (mKind == Kind::ImageElement) { if (mKind == Kind::ImageElement) {
MOZ_ASSERT_UNREACHABLE("Don't call me");
return nullptr; return nullptr;
} }
if (mKind == Kind::ListStyleImage) {
MOZ_ASSERT(
GetParent()->GetContent()->IsGeneratedContentContainerForMarker());
MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage));
return &StyleList()->mListStyleImage;
}
uint32_t contentIndex = 0; uint32_t contentIndex = 0;
const nsStyleContent* styleContent = StyleContent(); const nsStyleContent* styleContent = StyleContent();
if (mKind == Kind::ContentPropertyAtIndex) { if (mKind == Kind::ContentPropertyAtIndex) {
@ -414,12 +522,14 @@ void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
imageLoader->FrameCreated(this); imageLoader->FrameCreated(this);
} else { } else {
const StyleImage* image = GetImageFromStyle(); const StyleImage* image = GetImageFromStyle();
MOZ_ASSERT(image->IsImageRequestType(), MOZ_ASSERT(mKind == Kind::ListStyleImage || image->IsImageRequestType(),
"Content image should only parse url() type"); "Content image should only parse url() type");
Document* doc = PresContext()->Document(); if (image->IsImageRequestType()) {
if (imgRequestProxy* proxy = image->GetImageRequest()) { if (imgRequestProxy* proxy = image->GetImageRequest()) {
proxy->Clone(mListener, doc, getter_AddRefs(mContentURLRequest)); proxy->Clone(mListener, PresContext()->Document(),
SetupForContentURLRequest(); getter_AddRefs(mContentURLRequest));
SetupForContentURLRequest();
}
} }
} }
@ -491,6 +601,22 @@ static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage,
ScaleIntrinsicSizeForDensity(aSize, resolution); ScaleIntrinsicSizeForDensity(aSize, resolution);
} }
static nscoord ListImageDefaultLength(const nsImageFrame& aFrame) {
// https://drafts.csswg.org/css-lists-3/#image-markers
// The spec says we should use 1em x 1em, but that seems too large.
// See disussion in https://github.com/w3c/csswg-drafts/issues/4207
auto* pc = aFrame.PresContext();
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForComputedStyle(aFrame.Style(), pc);
auto emAU = fm->GetThebesFontGroup()
->GetFirstValidFont()
->GetMetrics(fm->Orientation())
.emHeight *
pc->AppUnitsPerDevPixel();
return std::max(NSToCoordRound(0.4f * emAU),
nsPresContext::CSSPixelsToAppUnits(1));
}
static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage, static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
bool aUseMappedRatio, bool aUseMappedRatio,
nsImageFrame::Kind aKind, nsImageFrame::Kind aKind,
@ -505,6 +631,17 @@ static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
IntrinsicSize intrinsicSize; IntrinsicSize intrinsicSize;
intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width); intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width);
intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height); intrinsicSize.height = size.height == -1 ? Nothing() : Some(size.height);
if (aKind == nsImageFrame::Kind::ListStyleImage) {
if (intrinsicSize.width.isNothing() || intrinsicSize.height.isNothing()) {
nscoord defaultLength = ListImageDefaultLength(aFrame);
if (intrinsicSize.width.isNothing()) {
intrinsicSize.width = Some(defaultLength);
}
if (intrinsicSize.height.isNothing()) {
intrinsicSize.height = Some(defaultLength);
}
}
}
if (aKind == nsImageFrame::Kind::ImageElement) { if (aKind == nsImageFrame::Kind::ImageElement) {
ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize); ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize);
} else { } else {
@ -514,6 +651,12 @@ static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage,
return intrinsicSize; return intrinsicSize;
} }
if (aKind == nsImageFrame::Kind::ListStyleImage) {
// Note: images are handled above, this handles gradients etc.
nscoord defaultLength = ListImageDefaultLength(aFrame);
return IntrinsicSize(defaultLength, defaultLength);
}
if (aFrame.ShouldShowBrokenImageIcon()) { if (aFrame.ShouldShowBrokenImageIcon()) {
nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits(
ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH)));
@ -780,6 +923,13 @@ void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) {
} else { } else {
// We no longer have a valid image, so release our stored image container. // We no longer have a valid image, so release our stored image container.
mImage = mPrevImage = nullptr; mImage = mPrevImage = nullptr;
if (mKind == Kind::ListStyleImage) {
auto* genContent = static_cast<GeneratedImageContent*>(GetContent());
genContent->NotifyLoadFailed();
// No need to continue below since the above state change will destroy
// this frame.
return;
}
} }
// NOTE(emilio): Intentionally using `|` instead of `||` to avoid // NOTE(emilio): Intentionally using `|` instead of `||` to avoid
// short-circuiting. // short-circuiting.
@ -796,8 +946,10 @@ void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) {
// already gotten the initial reflow. // already gotten the initial reflow.
if (!(mState & IMAGE_SIZECONSTRAINED)) { if (!(mState & IMAGE_SIZECONSTRAINED)) {
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
if (nsAccessibilityService* accService = GetAccService()) { if (mKind != Kind::ListStyleImage) {
accService->NotifyOfImageSizeAvailable(PresShell(), mContent); if (nsAccessibilityService* accService = GetAccService()) {
accService->NotifyOfImageSizeAvailable(PresShell(), mContent);
}
} }
#endif #endif
PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange, PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
@ -955,6 +1107,14 @@ nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) {
mIntrinsicRatio, StylePosition()); mIntrinsicRatio, StylePosition());
} }
bool nsImageFrame::IsForMarkerPseudo() const {
if (mKind == Kind::ImageElement) {
return false;
}
auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot();
return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker();
}
void nsImageFrame::EnsureIntrinsicSizeAndRatio() { void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
if (StyleDisplay()->IsContainSize()) { if (StyleDisplay()->IsContainSize()) {
// If we have 'contain:size', then our intrinsic size and ratio are 0,0 // If we have 'contain:size', then our intrinsic size and ratio are 0,0
@ -965,8 +1125,9 @@ void nsImageFrame::EnsureIntrinsicSizeAndRatio() {
} }
// If mIntrinsicSize.width and height are 0, then we need to update from the // If mIntrinsicSize.width and height are 0, then we need to update from the
// image container. // image container. Note that we handle ::marker intrinsic size/ratio in
if (mIntrinsicSize != IntrinsicSize(0, 0)) { // DidSetComputedStyle.
if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) {
return; return;
} }
@ -2188,7 +2349,9 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
// XXX(seth): The SizeIsAvailable check here should not be necessary - the // XXX(seth): The SizeIsAvailable check here should not be necessary - the
// intention is that a non-null mImage means we have a size, but there is // intention is that a non-null mImage means we have a size, but there is
// currently some code that violates this invariant. // currently some code that violates this invariant.
if (!imageOK || !mImage || !SizeIsAvailable(currentRequest)) { if ((mKind == Kind::ImageElement ||
GetImageFromStyle()->IsImageRequestType()) &&
(!imageOK || !mImage || !SizeIsAvailable(currentRequest))) {
// No image yet, or image load failed. Draw the alt-text and an icon // No image yet, or image load failed. Draw the alt-text and an icon
// indicating the status // indicating the status
aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this); aLists.Content()->AppendNewToTop<nsDisplayAltFeedback>(aBuilder, this);
@ -2208,8 +2371,12 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
} }
} }
} else { } else {
aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage, if (mImage) {
mPrevImage); aLists.Content()->AppendNewToTop<nsDisplayImage>(aBuilder, this, mImage,
mPrevImage);
} else if (mKind != Kind::ImageElement) {
aLists.Content()->AppendNewToTop<nsDisplayGradient>(aBuilder, this);
}
// If we were previously displaying an icon, we're not anymore // If we were previously displaying an icon, we're not anymore
if (mDisplayingIcon) { if (mDisplayingIcon) {

View File

@ -187,6 +187,8 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
// For a child of a ::before / ::after pseudo-element that had an url() item // For a child of a ::before / ::after pseudo-element that had an url() item
// for the content property. // for the content property.
ContentPropertyAtIndex, ContentPropertyAtIndex,
// For a list-style-image ::marker.
ListStyleImage,
}; };
// Creates a suitable continuing frame for this frame. // Creates a suitable continuing frame for this frame.
@ -199,6 +201,8 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
ComputedStyle*); ComputedStyle*);
friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(mozilla::PresShell*, friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(mozilla::PresShell*,
ComputedStyle*); ComputedStyle*);
friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*,
ComputedStyle*);
nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind) nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind)
: nsImageFrame(aStyle, aPresContext, kClassID, aKind) {} : nsImageFrame(aStyle, aPresContext, kClassID, aKind) {}
@ -260,6 +264,11 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
*/ */
void MaybeDecodeForPredictedSize(); void MaybeDecodeForPredictedSize();
/**
* Is this frame part of a ::marker pseudo?
*/
bool IsForMarkerPseudo() const;
protected: protected:
friend class nsImageListener; friend class nsImageListener;
friend class nsImageLoadingContent; friend class nsImageLoadingContent;
@ -353,7 +362,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
RefPtr<nsImageListener> mListener; RefPtr<nsImageListener> mListener;
// An image request created for content: url(..). // An image request created for content: url(..) or list-style-image.
RefPtr<imgRequestProxy> mContentURLRequest; RefPtr<imgRequestProxy> mContentURLRequest;
nsCOMPtr<imgIContainer> mImage; nsCOMPtr<imgIContainer> mImage;
@ -426,6 +435,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback {
static mozilla::StaticRefPtr<IconLoad> gIconLoad; static mozilla::StaticRefPtr<IconLoad> gIconLoad;
friend class nsDisplayImage; friend class nsDisplayImage;
friend class nsDisplayGradient;
}; };
/** /**

View File

@ -5027,6 +5027,15 @@ void nsTextFrame::GetTextDecorations(
break; break;
} }
if (context->GetPseudoType() == PseudoStyleType::marker &&
(context->StyleList()->mListStylePosition ==
NS_STYLE_LIST_STYLE_POSITION_OUTSIDE ||
!context->StyleDisplay()->IsInlineOutsideStyle())) {
// Outside ::marker pseudos, and inside markers that aren't inlines, don't
// have text decorations.
break;
}
const nsStyleTextReset* const styleTextReset = context->StyleTextReset(); const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
const StyleTextDecorationLine textDecorations = const StyleTextDecorationLine textDecorations =
styleTextReset->mTextDecorationLine; styleTextReset->mTextDecorationLine;

View File

@ -48,6 +48,7 @@ DECLARE_DISPLAY_ITEM_TYPE(FOREIGN_OBJECT,
DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BLANK, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(FRAMESET_BORDER, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(GENERIC, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(GENERIC, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(GRADIENT, TYPE_IS_CONTENTFUL)
DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(HEADER_FOOTER, TYPE_RENDERS_NO_IMAGES)
DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL) DECLARE_DISPLAY_ITEM_TYPE(IMAGE, TYPE_IS_CONTENTFUL)
DECLARE_DISPLAY_ITEM_TYPE(LINK, TYPE_RENDERS_NO_IMAGES) DECLARE_DISPLAY_ITEM_TYPE(LINK, TYPE_RENDERS_NO_IMAGES)

View File

@ -585,7 +585,7 @@ void BuiltinCounterStyle::GetSuffix(nsAString& aResult) {
static const char16_t kDiscCharacter = 0x2022; static const char16_t kDiscCharacter = 0x2022;
static const char16_t kCircleCharacter = 0x25e6; static const char16_t kCircleCharacter = 0x25e6;
static const char16_t kSquareCharacter = 0x25fe; static const char16_t kSquareCharacter = 0x25aa;
static const char16_t kRightPointingCharacter = 0x25b8; static const char16_t kRightPointingCharacter = 0x25b8;
static const char16_t kLeftPointingCharacter = 0x25c2; static const char16_t kLeftPointingCharacter = 0x25c2;
static const char16_t kDownPointingCharacter = 0x25be; static const char16_t kDownPointingCharacter = 0x25be;

View File

@ -677,6 +677,13 @@ bool ServoStyleSet::GeneratedContentPseudoExists(
if (!aParentStyle.StyleDisplay()->IsListItem()) { if (!aParentStyle.StyleDisplay()->IsListItem()) {
return false; return false;
} }
// ::marker only exist if we have 'content' or at least one of
// 'list-style-type' or 'list-style-image'.
if (aPseudoStyle.StyleList()->mCounterStyle.IsNone() &&
aPseudoStyle.StyleList()->mListStyleImage.IsNone() &&
aPseudoStyle.StyleContent()->ContentCount() == 0) {
return false;
}
// display:none is equivalent to not having the pseudo-element at all. // display:none is equivalent to not having the pseudo-element at all.
if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) { if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) {
return false; return false;

View File

@ -631,12 +631,11 @@ nsChangeHint nsStyleList::CalcDifference(
// relies on that when the display value changes from something else // relies on that when the display value changes from something else
// to list-item, that change itself would cause ReconstructFrame. // to list-item, that change itself would cause ReconstructFrame.
if (aOldDisplay.IsListItem()) { if (aOldDisplay.IsListItem()) {
if (mListStylePosition != aNewData.mListStylePosition) { if (mListStylePosition != aNewData.mListStylePosition ||
mCounterStyle != aNewData.mCounterStyle ||
mListStyleImage != aNewData.mListStyleImage) {
return nsChangeHint_ReconstructFrame; return nsChangeHint_ReconstructFrame;
} }
if (mCounterStyle != aNewData.mCounterStyle) {
return NS_STYLE_HINT_REFLOW;
}
} else if (mListStylePosition != aNewData.mListStylePosition || } else if (mListStylePosition != aNewData.mListStylePosition ||
mCounterStyle != aNewData.mCounterStyle) { mCounterStyle != aNewData.mCounterStyle) {
hint = nsChangeHint_NeutralChange; hint = nsChangeHint_NeutralChange;