From a901b05850dbc492d86d052ef28b55eb8029789a Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Mon, 14 Jun 2021 01:22:04 +0000 Subject: [PATCH] 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 --- accessible/base/nsAccessibilityService.cpp | 9 +- accessible/generic/HyperTextAccessible.cpp | 4 +- accessible/html/HTMLListAccessible.cpp | 22 +-- dom/base/GeneratedImageContent.cpp | 5 + dom/base/GeneratedImageContent.h | 21 +++ layout/base/RestyleManager.cpp | 9 +- layout/base/nsCSSFrameConstructor.cpp | 171 ++++++++++++++---- layout/base/nsCSSFrameConstructor.h | 20 ++- layout/base/nsCounterManager.cpp | 100 ++++++++--- layout/base/nsCounterManager.h | 27 ++- layout/base/nsGenConList.h | 13 +- layout/base/nsLayoutUtils.cpp | 34 ++++ layout/base/nsLayoutUtils.h | 8 + layout/generic/nsBlockFrame.cpp | 5 + layout/generic/nsImageFrame.cpp | 191 +++++++++++++++++++-- layout/generic/nsImageFrame.h | 12 +- layout/generic/nsTextFrame.cpp | 9 + layout/painting/nsDisplayItemTypesList.h | 1 + layout/style/CounterStyleManager.cpp | 2 +- layout/style/ServoStyleSet.cpp | 7 + layout/style/nsStyleStruct.cpp | 7 +- 21 files changed, 555 insertions(+), 122 deletions(-) diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 788cde555524..19ef4eeb3f81 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -1129,11 +1129,10 @@ LocalAccessible* nsAccessibilityService::CreateAccessible( } } else if (content->IsGeneratedContentContainerForMarker()) { if (aContext->IsHTMLListItem()) { - const nsStyleList* styleList = frame->StyleList(); - if (!styleList->mListStyleImage.IsNone() || - !styleList->mCounterStyle.IsNone()) { - newAcc = new HTMLListBulletAccessible(content, document); - } + newAcc = new HTMLListBulletAccessible(content, document); + } + if (aIsSubtreeHidden) { + *aIsSubtreeHidden = true; } } } diff --git a/accessible/generic/HyperTextAccessible.cpp b/accessible/generic/HyperTextAccessible.cpp index 5a83b0b8e678..0d3065967d67 100644 --- a/accessible/generic/HyperTextAccessible.cpp +++ b/accessible/generic/HyperTextAccessible.cpp @@ -422,8 +422,8 @@ uint32_t HyperTextAccessible::TransformOffset(LocalAccessible* aDescendant, // We manually set the offset so the error doesn't propagate up. if (offset == 0 && parent && parent->IsHTMLListItem() && descendant->LocalPrevSibling() && - descendant->LocalPrevSibling()->GetFrame() && - descendant->LocalPrevSibling()->GetFrame()->IsBulletFrame()) { + descendant->LocalPrevSibling() == + parent->AsHTMLListItem()->Bullet()) { offset = 0; } else { offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0; diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp index f65f1d3421b7..a3eb5847f3a5 100644 --- a/accessible/html/HTMLListAccessible.cpp +++ b/accessible/html/HTMLListAccessible.cpp @@ -10,11 +10,10 @@ #include "DocAccessible.h" #include "EventTree.h" #include "nsAccUtils.h" -#include "nsTextEquivUtils.h" +#include "nsPersistentProperties.h" #include "Role.h" #include "States.h" -#include "nsBulletFrame.h" #include "nsLayoutUtils.h" using namespace mozilla; @@ -90,24 +89,7 @@ HTMLListBulletAccessible::HTMLListBulletAccessible(nsIContent* aContent, // HTMLListBulletAccessible: LocalAccessible ENameValueFlag HTMLListBulletAccessible::Name(nsString& aName) const { - aName.Truncate(); - - // 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(); - } - + nsLayoutUtils::GetMarkerSpokenText(mContent, aName); return eNameOK; } diff --git a/dom/base/GeneratedImageContent.cpp b/dom/base/GeneratedImageContent.cpp index dabd3ac7cc9f..78e6673220ec 100644 --- a/dom/base/GeneratedImageContent.cpp +++ b/dom/base/GeneratedImageContent.cpp @@ -30,6 +30,11 @@ already_AddRefed GeneratedImageContent::Create( return image.forget(); } +already_AddRefed +GeneratedImageContent::CreateForListStyleImage(Document& aDocument) { + return Create(aDocument, uint32_t(-1)); +} + JSObject* GeneratedImageContent::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto); diff --git a/dom/base/GeneratedImageContent.h b/dom/base/GeneratedImageContent.h index a72694d73a6d..9b592f79a229 100644 --- a/dom/base/GeneratedImageContent.h +++ b/dom/base/GeneratedImageContent.h @@ -23,6 +23,9 @@ class GeneratedImageContent final : public nsGenericHTMLElement { public: static already_AddRefed Create(Document&, uint32_t aContentIndex); + // An image created from 'list-style-image' for a ::marker pseudo. + static already_AddRefed CreateForListStyleImage( + Document&); explicit GeneratedImageContent(already_AddRefed&& aNodeInfo) : nsGenericHTMLElement(std::move(aNodeInfo)) { @@ -30,6 +33,13 @@ class GeneratedImageContent final : public nsGenericHTMLElement { "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 CopyInnerTo(GeneratedImageContent* aDest) { @@ -39,14 +49,25 @@ class GeneratedImageContent final : public nsGenericHTMLElement { 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; } + // Notify this image failed to load. + void NotifyLoadFailed() { + mBroken = true; + UpdateState(true); + } + protected: JSObject* WrapNode(JSContext* aCx, JS::Handle aGivenProto) final; private: virtual ~GeneratedImageContent() = default; uint32_t mIndex = 0; + bool mBroken = false; }; } // namespace dom diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index fd037cd613a5..8135e9934fde 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -37,7 +37,6 @@ #include "Layers.h" #include "nsAnimationManager.h" #include "nsBlockFrame.h" -#include "nsBulletFrame.h" #include "nsContentUtils.h" #include "nsCSSFrameConstructor.h" #include "nsCSSRendering.h" @@ -465,7 +464,11 @@ static bool StateChangeMayAffectFrame(const Element& aElement, const nsIFrame& aFrame, EventStates aStates) { 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; } @@ -1961,7 +1964,7 @@ static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) { 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 // generated by our DOM parent, and go find the owner frame for it. while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) { diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 3b29a9ae7487..96ba6730480f 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -268,8 +268,8 @@ nsIFrame* NS_NewScrollbarButtonFrame(PresShell* aPresShell, ComputedStyle* aStyle); nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*); - nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*); +nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*); // Returns true if aFrame is an anonymous flex/grid item. static inline bool IsAnonymousFlexOrGridItem(const nsIFrame* aFrame) { @@ -1600,6 +1600,66 @@ already_AddRefed nsCSSFrameConstructor::CreateGeneratedContent( return nullptr; } +void nsCSSFrameConstructor::CreateGeneratedContentFromListStyle( + nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle, + const FunctionRef aAddChild) { + const nsStyleList* styleList = aPseudoStyle.StyleList(); + if (!styleList->mListStyleImage.IsNone()) { + RefPtr 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 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::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 child = CreateGenConTextNode(aState, text, nullptr); + aAddChild(child); + return; + } + + nsCounterList* counterList = + mCounterManager.CounterListFor(nsGkAtoms::list_item); + auto initializer = MakeUnique( + std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty); + RefPtr child = + CreateGenConTextNode(aState, EmptyString(), std::move(initializer)); + aAddChild(child); +} + /* * aParentFrame - the frame that should be the parent of the generated * content. This is the frame for the corresponding content node, @@ -1660,8 +1720,10 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem( MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement"); } - // |ProbePseudoStyleFor| checked the 'display' property and the - // |ContentCount()| of the 'content' property for us. + // |ProbePseudoElementStyle| checked the 'display' property and the + // |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 = mDocument->NodeInfoManager()->GetNodeInfo( elemName, nullptr, kNameSpaceID_None, nsINode::ELEMENT_NODE); RefPtr container; @@ -1704,26 +1766,31 @@ void nsCSSFrameConstructor::CreateGeneratedContentItem( pseudoStyle = ServoStyleSet::ResolveServoStyle(*container); } - uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount(); - for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) { - nsCOMPtr content = CreateGeneratedContent( - aState, aOriginatingElement, *pseudoStyle, contentIndex); - if (!content) { - continue; - } + auto AppendChild = [&container, this](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. - content->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE); - container->AppendChildTo(content, false, IgnoreErrors()); - if (auto* element = Element::FromNode(content)) { + aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE); + container->AppendChildTo(aChild, 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(element); + mPresShell->StyleSet()->StyleNewSubtree(childElement); + } + }; + const uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount(); + for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) { + if (RefPtr 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, pseudoStyle, {ItemFlag::IsGeneratedContent}, aItems); @@ -3354,6 +3421,13 @@ nsCSSFrameConstructor::FindGeneratedImageData(const Element& aElement, return nullptr; } + auto& generatedContent = static_cast(aElement); + if (generatedContent.IsForListStyleImageMarker()) { + static const FrameConstructionData sImgData = + SIMPLE_FCDATA(NS_NewImageFrameForListStyleImage); + return &sImgData; + } + static const FrameConstructionData sImgData = SIMPLE_FCDATA(NS_NewImageFrameForGeneratedContentIndex); 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) == ((bits & FCDATA_IS_LINE_PARTICIPANT) != 0), "Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits"); @@ -5231,16 +5295,6 @@ nsCSSFrameConstructor::FindElementTagData(const Element& aElement, ComputedStyle& aStyle, nsIFrame* aParentFrame, 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()) { case kNameSpaceID_XHTML: 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&& 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); ParentType parentType = GetParentType(frameType); FlattenedChildIterator iter(insertion.mContainer); @@ -9575,6 +9671,13 @@ void nsCSSFrameConstructor::ProcessChildren( listItem->SetMarkerFrameForListItem(childFrame); MOZ_ASSERT(listItem->HasAnyStateBits( 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; } } diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 529ebbc00814..352110493bf7 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -14,6 +14,7 @@ #include "mozilla/ArenaAllocator.h" #include "mozilla/Attributes.h" +#include "mozilla/FunctionRef.h" #include "mozilla/LinkedList.h" #include "mozilla/Maybe.h" #include "mozilla/RestyleManager.h" @@ -333,8 +334,10 @@ class nsCSSFrameConstructor final : public nsFrameManager { void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const; - // temporary - please don't add external uses outside of nsBulletFrame - nsCounterManager* CounterManager() { return &mCounterManager; } +#ifdef ACCESSIBILITY + // Exposed only for nsLayoutUtils::GetMarkerSpokenText to use. + const nsCounterManager* CounterManager() const { return &mCounterManager; } +#endif private: struct FrameConstructionItem; @@ -460,6 +463,19 @@ class nsCSSFrameConstructor final : public nsFrameManager { nsFrameConstructorState& aState, const Element& aOriginatingElement, 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 aAddChild); + /** + * Create child content nodes for a ::marker from its 'list-style-type'. + */ + void CreateGeneratedContentFromListStyleType( + nsFrameConstructorState& aState, const ComputedStyle& aPseudoStyle, + const mozilla::FunctionRef aAddChild); + // aParentFrame may be null; this method doesn't use it directly in any case. void CreateGeneratedContentItem(nsFrameConstructorState& aState, nsContainerFrame* aParentFrame, diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp index 0d442474514b..dbe973481ac1 100644 --- a/layout/base/nsCounterManager.cpp +++ b/layout/base/nsCounterManager.cpp @@ -13,7 +13,6 @@ #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/WritingModes.h" -#include "nsBulletFrame.h" // legacy location for list style type to text code #include "nsContentUtils.h" #include "nsIContent.h" #include "nsTArray.h" @@ -43,13 +42,6 @@ bool nsCounterUseNode::InitTextFrame(nsGenConList* aList, 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 // Should be called immediately after calling |Insert|. void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) { @@ -59,11 +51,6 @@ void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) { nsAutoString contentString; GetText(contentString); 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) { - 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 stack; stack.AppendElement(static_cast(this)); @@ -95,21 +107,26 @@ void nsCounterUseNode::GetText(nsString& aResult) { } } - WritingMode wm = mPseudoFrame->GetWritingMode(); - CounterStyle* style = - mPseudoFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle( - mCounterStyle); - for (uint32_t i = stack.Length() - 1;; --i) { - nsCounterNode* n = stack[i]; + for (nsCounterNode* n : Reversed(stack)) { nsAutoString text; bool isTextRTL; - style->GetCounterText(n->mValueAfter, wm, text, isTextRTL); - aResult.Append(text); - if (i == 0) { + aStyle->GetCounterText(n->mValueAfter, aWM, text, isTextRTL); + if (!mForLegacyBullet || aStyle->IsBullet()) { + aResult.Append(text); + } else { + AppendCounterText(text, isTextRTL); + } + if (n == this) { break; } aResult.Append(mSeparator); } + + if (mForLegacyBullet) { + nsAutoString suffix; + aStyle->GetSuffix(suffix); + aResult.Append(suffix); + } } void nsCounterList::SetScope(nsCounterNode* aNode) { @@ -340,6 +357,41 @@ bool nsCounterManager::DestroyNodesFor(nsIFrame* aFrame) { 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 void nsCounterManager::Dump() { printf("\n\nCounter Manager Lists:\n"); diff --git a/layout/base/nsCounterManager.h b/layout/base/nsCounterManager.h index e6ca7df45b5d..68e4f2025a40 100644 --- a/layout/base/nsCounterManager.h +++ b/layout/base/nsCounterManager.h @@ -62,6 +62,7 @@ struct nsCounterNode : public nsGenConNode { // 'counter-reset', 'counter-increment' or 'counter-set' property // instead of within the 'content' property but offset to ensure // 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'.) nsCounterNode(int32_t aContentIndex, Type aType) : nsGenConNode(aContentIndex), mType(aType) {} @@ -83,10 +84,10 @@ struct nsCounterUseNode : public nsCounterNode { bool mForLegacyBullet = false; enum ForLegacyBullet { ForLegacyBullet }; - explicit nsCounterUseNode(enum ForLegacyBullet) - : nsCounterNode(0, USE), mForLegacyBullet(true) { - mCounterStyle = nsGkAtoms::list_item; - } + nsCounterUseNode(enum ForLegacyBullet, mozilla::CounterStylePtr aCounterStyle) + : nsCounterNode(0, USE), + mCounterStyle(std::move(aCounterStyle)), + mForLegacyBullet(true) {} // args go directly to member variables here and of nsGenConNode nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle, nsString aSeparator, @@ -98,10 +99,8 @@ struct nsCounterUseNode : public nsCounterNode { NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); } - virtual bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame, - nsIFrame* aTextFrame) override; - - bool InitBullet(nsGenConList* aList, nsIFrame* aBulletFrame); + bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame, + nsIFrame* aTextFrame) override; // 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. @@ -110,6 +109,8 @@ struct nsCounterUseNode : public nsCounterNode { // The text that should be displayed for this counter. void GetText(nsString& aResult); + void GetText(mozilla::WritingMode aWM, mozilla::CounterStyle* aStyle, + nsString& aResult); }; struct nsCounterChangeNode : public nsCounterNode { @@ -171,6 +172,11 @@ class nsCounterList : public nsGenConList { public: nsCounterList() : nsGenConList(), mDirty(false) {} + // Return the first node for aFrame on this list, or nullptr. + nsCounterNode* GetFirstNodeFor(nsIFrame* aFrame) const { + return static_cast(nsGenConList::GetFirstNodeFor(aFrame)); + } + void Insert(nsCounterNode* aNode) { nsGenConList::Insert(aNode); // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, @@ -235,6 +241,11 @@ class nsCounterManager { // Clear all data. 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 void Dump(); #endif diff --git a/layout/base/nsGenConList.h b/layout/base/nsGenConList.h index 96986496ad79..fa2e9eb3d26b 100644 --- a/layout/base/nsGenConList.h +++ b/layout/base/nsGenConList.h @@ -34,7 +34,6 @@ struct nsGenConNode : public mozilla::LinkedListElement { // null for: // * content: no-open-quote / content: no-close-quote // * counter nodes for increments and resets - // * counter nodes for bullets (mPseudoFrame->IsBulletFrame()). RefPtr mText; explicit nsGenConNode(int32_t aContentIndex) @@ -65,12 +64,9 @@ struct nsGenConNode : public mozilla::LinkedListElement { void CheckFrameAssertions() { NS_ASSERTION( 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. - (mPseudoFrame->IsBulletFrame() && mContentIndex == 0 && - mPseudoFrame->Style()->GetPseudoType() == - mozilla::PseudoStyleType::marker && - !mPseudoFrame->StyleContent()->ContentCount()), + mContentIndex == 0, "index out of range"); // We allow negative values of mContentIndex for 'counter-reset' and // 'counter-increment'. @@ -112,6 +108,11 @@ class nsGenConList { // have been destroyed; otherwise false. 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|. static bool NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 21f20b4dd490..b5e9b6341bd0 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -893,6 +893,40 @@ nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) { 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 nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame, LayoutFrameType aFrameType, diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 493153a49b91..7e8c4dbb73d5 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -273,6 +273,14 @@ class nsLayoutUtils { */ 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 * ancestor that (or the frame itself) is of type aFrameType, if any. diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 37b14626b9cf..b50300c2d1c7 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -7395,6 +7395,11 @@ void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) { SetProperty(InsideMarkerProperty(), aMarkerFrame); AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_MARKER); } 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(), new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame)); AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER); diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp index 46e55bbc05f7..0472ceefa5e3 100644 --- a/layout/generic/nsImageFrame.cpp +++ b/layout/generic/nsImageFrame.cpp @@ -39,6 +39,7 @@ #include "nsFontMetrics.h" #include "nsIImageLoadingContent.h" #include "nsImageLoadingContent.h" +#include "nsImageRenderer.h" #include "nsString.h" #include "nsPrintfCString.h" #include "nsPresContext.h" @@ -98,6 +99,89 @@ using namespace mozilla::layers; 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(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(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(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 #define ICON_SIZE (16) #define ICON_PADDING (3) @@ -178,6 +262,12 @@ nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell* aPresShell, nsImageFrame::Kind::ContentPropertyAtIndex); } +nsIFrame* NS_NewImageFrameForListStyleImage(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) nsImageFrame(aStyle, aPresShell->GetPresContext(), + nsImageFrame::Kind::ListStyleImage); +} + bool nsImageFrame::ShouldShowBrokenImageIcon() const { // NOTE(emilio, https://github.com/w3c/csswg-drafts/issues/2832): WebKit and // Blink behave differently here for content: url(..), for now adapt to @@ -241,6 +331,11 @@ NS_QUERYFRAME_TAIL_INHERITING(nsAtomicContainerFrame) #ifdef ACCESSIBILITY a11y::AccType nsImageFrame::AccessibleType() { + if (mKind == Kind::ListStyleImage) { + // This is an HTMLListBulletAccessible. + return a11y::eNoType; + } + // Don't use GetImageMap() to avoid reentrancy into accessibility. if (HasImageMap()) { return a11y::eHTMLImageMapType; @@ -327,6 +422,12 @@ void nsImageFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) { 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; // 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 { if (mKind == Kind::ImageElement) { + MOZ_ASSERT_UNREACHABLE("Don't call me"); return nullptr; } + if (mKind == Kind::ListStyleImage) { + MOZ_ASSERT( + GetParent()->GetContent()->IsGeneratedContentContainerForMarker()); + MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)); + return &StyleList()->mListStyleImage; + } uint32_t contentIndex = 0; const nsStyleContent* styleContent = StyleContent(); if (mKind == Kind::ContentPropertyAtIndex) { @@ -414,12 +522,14 @@ void nsImageFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, imageLoader->FrameCreated(this); } else { const StyleImage* image = GetImageFromStyle(); - MOZ_ASSERT(image->IsImageRequestType(), + MOZ_ASSERT(mKind == Kind::ListStyleImage || image->IsImageRequestType(), "Content image should only parse url() type"); - Document* doc = PresContext()->Document(); - if (imgRequestProxy* proxy = image->GetImageRequest()) { - proxy->Clone(mListener, doc, getter_AddRefs(mContentURLRequest)); - SetupForContentURLRequest(); + if (image->IsImageRequestType()) { + if (imgRequestProxy* proxy = image->GetImageRequest()) { + proxy->Clone(mListener, PresContext()->Document(), + getter_AddRefs(mContentURLRequest)); + SetupForContentURLRequest(); + } } } @@ -491,6 +601,22 @@ static void ScaleIntrinsicSizeForDensity(imgIContainer* aImage, 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 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, bool aUseMappedRatio, nsImageFrame::Kind aKind, @@ -505,6 +631,17 @@ static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage, IntrinsicSize intrinsicSize; intrinsicSize.width = size.width == -1 ? Nothing() : Some(size.width); 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) { ScaleIntrinsicSizeForDensity(aImage, *aFrame.GetContent(), intrinsicSize); } else { @@ -514,6 +651,12 @@ static IntrinsicSize ComputeIntrinsicSize(imgIContainer* aImage, 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()) { nscoord edgeLengthToUse = nsPresContext::CSSPixelsToAppUnits( ICON_SIZE + (2 * (ICON_PADDING + ALT_BORDER_WIDTH))); @@ -780,6 +923,13 @@ void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { } else { // We no longer have a valid image, so release our stored image container. mImage = mPrevImage = nullptr; + if (mKind == Kind::ListStyleImage) { + auto* genContent = static_cast(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 // short-circuiting. @@ -796,8 +946,10 @@ void nsImageFrame::UpdateImage(imgIRequest* aRequest, imgIContainer* aImage) { // already gotten the initial reflow. if (!(mState & IMAGE_SIZECONSTRAINED)) { #ifdef ACCESSIBILITY - if (nsAccessibilityService* accService = GetAccService()) { - accService->NotifyOfImageSizeAvailable(PresShell(), mContent); + if (mKind != Kind::ListStyleImage) { + if (nsAccessibilityService* accService = GetAccService()) { + accService->NotifyOfImageSizeAvailable(PresShell(), mContent); + } } #endif PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange, @@ -955,6 +1107,14 @@ nsRect nsImageFrame::PredictedDestRect(const nsRect& aFrameContentBox) { mIntrinsicRatio, StylePosition()); } +bool nsImageFrame::IsForMarkerPseudo() const { + if (mKind == Kind::ImageElement) { + return false; + } + auto* subtreeRoot = GetContent()->GetClosestNativeAnonymousSubtreeRoot(); + return subtreeRoot && subtreeRoot->IsGeneratedContentContainerForMarker(); +} + void nsImageFrame::EnsureIntrinsicSizeAndRatio() { if (StyleDisplay()->IsContainSize()) { // 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 - // image container. - if (mIntrinsicSize != IntrinsicSize(0, 0)) { + // image container. Note that we handle ::marker intrinsic size/ratio in + // DidSetComputedStyle. + if (mIntrinsicSize != IntrinsicSize(0, 0) && !IsForMarkerPseudo()) { return; } @@ -2188,7 +2349,9 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, // 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 // 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 // indicating the status aLists.Content()->AppendNewToTop(aBuilder, this); @@ -2208,8 +2371,12 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, } } } else { - aLists.Content()->AppendNewToTop(aBuilder, this, mImage, - mPrevImage); + if (mImage) { + aLists.Content()->AppendNewToTop(aBuilder, this, mImage, + mPrevImage); + } else if (mKind != Kind::ImageElement) { + aLists.Content()->AppendNewToTop(aBuilder, this); + } // If we were previously displaying an icon, we're not anymore if (mDisplayingIcon) { diff --git a/layout/generic/nsImageFrame.h b/layout/generic/nsImageFrame.h index 8b7908ae867f..67d8633b935a 100644 --- a/layout/generic/nsImageFrame.h +++ b/layout/generic/nsImageFrame.h @@ -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 the content property. ContentPropertyAtIndex, + // For a list-style-image ::marker. + ListStyleImage, }; // Creates a suitable continuing frame for this frame. @@ -199,6 +201,8 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { ComputedStyle*); friend nsIFrame* NS_NewImageFrameForGeneratedContentIndex(mozilla::PresShell*, ComputedStyle*); + friend nsIFrame* NS_NewImageFrameForListStyleImage(mozilla::PresShell*, + ComputedStyle*); nsImageFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, Kind aKind) : nsImageFrame(aStyle, aPresContext, kClassID, aKind) {} @@ -260,6 +264,11 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { */ void MaybeDecodeForPredictedSize(); + /** + * Is this frame part of a ::marker pseudo? + */ + bool IsForMarkerPseudo() const; + protected: friend class nsImageListener; friend class nsImageLoadingContent; @@ -353,7 +362,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { RefPtr mListener; - // An image request created for content: url(..). + // An image request created for content: url(..) or list-style-image. RefPtr mContentURLRequest; nsCOMPtr mImage; @@ -426,6 +435,7 @@ class nsImageFrame : public nsAtomicContainerFrame, public nsIReflowCallback { static mozilla::StaticRefPtr gIconLoad; friend class nsDisplayImage; + friend class nsDisplayGradient; }; /** diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index ce7a1d47ab31..accd8f90145b 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -5027,6 +5027,15 @@ void nsTextFrame::GetTextDecorations( 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 StyleTextDecorationLine textDecorations = styleTextReset->mTextDecorationLine; diff --git a/layout/painting/nsDisplayItemTypesList.h b/layout/painting/nsDisplayItemTypesList.h index 99e92a95cd17..ea34d9f6949d 100644 --- a/layout/painting/nsDisplayItemTypesList.h +++ b/layout/painting/nsDisplayItemTypesList.h @@ -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_BORDER, 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(IMAGE, TYPE_IS_CONTENTFUL) DECLARE_DISPLAY_ITEM_TYPE(LINK, TYPE_RENDERS_NO_IMAGES) diff --git a/layout/style/CounterStyleManager.cpp b/layout/style/CounterStyleManager.cpp index fcf3829b484b..1436adf1dc2e 100644 --- a/layout/style/CounterStyleManager.cpp +++ b/layout/style/CounterStyleManager.cpp @@ -585,7 +585,7 @@ void BuiltinCounterStyle::GetSuffix(nsAString& aResult) { static const char16_t kDiscCharacter = 0x2022; 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 kLeftPointingCharacter = 0x25c2; static const char16_t kDownPointingCharacter = 0x25be; diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp index 1dbe456d76d0..5d8f5cc872f2 100644 --- a/layout/style/ServoStyleSet.cpp +++ b/layout/style/ServoStyleSet.cpp @@ -677,6 +677,13 @@ bool ServoStyleSet::GeneratedContentPseudoExists( if (!aParentStyle.StyleDisplay()->IsListItem()) { 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. if (aPseudoStyle.StyleDisplay()->mDisplay == StyleDisplay::None) { return false; diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp index d85d10d5a790..4595e4f8578e 100644 --- a/layout/style/nsStyleStruct.cpp +++ b/layout/style/nsStyleStruct.cpp @@ -631,12 +631,11 @@ nsChangeHint nsStyleList::CalcDifference( // relies on that when the display value changes from something else // to list-item, that change itself would cause ReconstructFrame. if (aOldDisplay.IsListItem()) { - if (mListStylePosition != aNewData.mListStylePosition) { + if (mListStylePosition != aNewData.mListStylePosition || + mCounterStyle != aNewData.mCounterStyle || + mListStyleImage != aNewData.mListStyleImage) { return nsChangeHint_ReconstructFrame; } - if (mCounterStyle != aNewData.mCounterStyle) { - return NS_STYLE_HINT_REFLOW; - } } else if (mListStylePosition != aNewData.mListStylePosition || mCounterStyle != aNewData.mCounterStyle) { hint = nsChangeHint_NeutralChange;