Bug 288704 part 2 - [css-lists] Implement display:list-item counters using a built-in 'list-item' CSS counter. r=emilio
authorMats Palmgren <mats@mozilla.com>
Sun, 24 Mar 2019 23:13:52 +0100
changeset 465905 ae4e4daebdc4531be3f55fdced39155b97645cee
parent 465904 84c99621920420234be5ba591afe1ed32ccbee07
child 465906 6c2e7cfa54847968b0e98fc4e3719c2a633276ca
push id35754
push useraiakab@mozilla.com
push dateMon, 25 Mar 2019 15:53:40 +0000
treeherdermozilla-central@3d5cd10cb1b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs288704
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 288704 part 2 - [css-lists] Implement display:list-item counters using a built-in 'list-item' CSS counter. r=emilio
dom/html/HTMLLIElement.cpp
dom/html/HTMLSharedListElement.cpp
dom/html/HTMLSharedListElement.h
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCounterManager.cpp
layout/base/nsCounterManager.h
layout/reftests/bugs/reftest.list
layout/reftests/counters/counter-reset-integer-range-ref.html
layout/reftests/counters/counter-reset-integer-range.html
layout/reftests/list-item/reftest.list
layout/style/MappedDeclarations.h
layout/style/ServoBindings.h
layout/style/res/html.css
servo/components/style/style_adjuster.rs
servo/ports/geckolib/glue.rs
testing/web-platform/meta/css/CSS2/css1/c561-list-displ-000.xht.ini
testing/web-platform/meta/css/css-lists/inheritance.html.ini
testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-list-item-numbering.html.ini
testing/web-platform/meta/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html.ini
testing/web-platform/meta/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html.ini
testing/web-platform/tests/css/CSS2/lists/counter-reset-increment-002.xht
testing/web-platform/tests/css/css-lists/li-list-item-counter-ref.html
testing/web-platform/tests/css/css-lists/li-list-item-counter.html
testing/web-platform/tests/css/css-lists/li-value-counter-reset-001-ref.html
testing/web-platform/tests/css/css-lists/li-value-counter-reset-001.html
xpcom/ds/StaticAtoms.py
--- a/dom/html/HTMLLIElement.cpp
+++ b/dom/html/HTMLLIElement.cpp
@@ -62,23 +62,36 @@ void HTMLLIElement::MapAttributesIntoRul
   if (!aDecls.PropertyIsSet(eCSSProperty_list_style_type)) {
     // type: enum
     const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
     if (value && value->Type() == nsAttrValue::eEnum)
       aDecls.SetKeywordValue(eCSSProperty_list_style_type,
                              value->GetEnumValue());
   }
 
+  // Map <li value=INTEGER> to 'counter-set: list-item INTEGER;
+  // counter-increment: list-item 0;'.
+  const nsAttrValue* attrVal = aAttributes->GetAttr(nsGkAtoms::value);
+  if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
+    if (!aDecls.PropertyIsSet(eCSSProperty_counter_set)) {
+      aDecls.SetCounterSetListItem(attrVal->GetIntegerValue());
+    }
+    if (!aDecls.PropertyIsSet(eCSSProperty_counter_increment)) {
+      aDecls.SetCounterIncrementListItem(0);
+    }
+  }
+
   nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls);
 }
 
 NS_IMETHODIMP_(bool)
 HTMLLIElement::IsAttributeMapped(const nsAtom* aAttribute) const {
   static const MappedAttributeEntry attributes[] = {
       {nsGkAtoms::type},
+      {nsGkAtoms::value},
       {nullptr},
   };
 
   static const MappedAttributeEntry* const map[] = {
       attributes,
       sCommonAttributeMap,
   };
 
--- a/dom/html/HTMLSharedListElement.cpp
+++ b/dom/html/HTMLSharedListElement.cpp
@@ -54,62 +54,105 @@ bool HTMLSharedListElement::ParseAttribu
     int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue,
     nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) {
   if (aNamespaceID == kNameSpaceID_None) {
     if (mNodeInfo->Equals(nsGkAtoms::ol) || mNodeInfo->Equals(nsGkAtoms::ul)) {
       if (aAttribute == nsGkAtoms::type) {
         return aResult.ParseEnumValue(aValue, kListTypeTable, false) ||
                aResult.ParseEnumValue(aValue, kOldListTypeTable, true);
       }
+    }
+    if (mNodeInfo->Equals(nsGkAtoms::ol)) {
       if (aAttribute == nsGkAtoms::start) {
         return aResult.ParseIntValue(aValue);
       }
     }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aMaybeScriptedPrincipal, aResult);
 }
 
 void HTMLSharedListElement::MapAttributesIntoRule(
     const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
   if (!aDecls.PropertyIsSet(eCSSProperty_list_style_type)) {
-    // type: enum
     const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
     if (value && value->Type() == nsAttrValue::eEnum) {
       aDecls.SetKeywordValue(eCSSProperty_list_style_type,
                              value->GetEnumValue());
     }
   }
 
   nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aDecls);
 }
 
+void HTMLSharedListElement::MapOLAttributesIntoRule(
+    const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
+  if (!aDecls.PropertyIsSet(eCSSProperty_counter_reset)) {
+    const nsAttrValue* startAttr = aAttributes->GetAttr(nsGkAtoms::start);
+    bool haveStart = startAttr && startAttr->Type() == nsAttrValue::eInteger;
+    int32_t start = 0;
+    if (haveStart) {
+      start = startAttr->GetIntegerValue() - 1;
+    }
+    bool haveReversed = !!aAttributes->GetAttr(nsGkAtoms::reversed);
+    if (haveReversed) {
+      if (haveStart) {
+        start += 2; // i.e. the attr value + 1
+      } else {
+        start = std::numeric_limits<int32_t>::min();
+      }
+    }
+    if (haveStart || haveReversed) {
+      aDecls.SetCounterResetListItem(start);
+    }
+  }
+
+  HTMLSharedListElement::MapAttributesIntoRule(aAttributes, aDecls);
+}
+
 NS_IMETHODIMP_(bool)
 HTMLSharedListElement::IsAttributeMapped(const nsAtom* aAttribute) const {
-  if (mNodeInfo->Equals(nsGkAtoms::ol) || mNodeInfo->Equals(nsGkAtoms::ul)) {
+  if (mNodeInfo->Equals(nsGkAtoms::ul)) {
     static const MappedAttributeEntry attributes[] = {{nsGkAtoms::type},
                                                       {nullptr}};
 
     static const MappedAttributeEntry* const map[] = {
         attributes,
         sCommonAttributeMap,
     };
 
     return FindAttributeDependence(aAttribute, map);
   }
 
+  if (mNodeInfo->Equals(nsGkAtoms::ol)) {
+    static const MappedAttributeEntry attributes[] = {{nsGkAtoms::type},
+                                                      {nsGkAtoms::start},
+                                                      {nsGkAtoms::reversed},
+                                                      {nullptr}};
+
+    static const MappedAttributeEntry* const map[] = {
+        attributes,
+        sCommonAttributeMap,
+    };
+
+    return FindAttributeDependence(aAttribute, map);
+  }
+
   return nsGenericHTMLElement::IsAttributeMapped(aAttribute);
 }
 
 nsMapRuleToAttributesFunc HTMLSharedListElement::GetAttributeMappingFunction()
     const {
-  if (mNodeInfo->Equals(nsGkAtoms::ol) || mNodeInfo->Equals(nsGkAtoms::ul)) {
+  if (mNodeInfo->Equals(nsGkAtoms::ul)) {
     return &MapAttributesIntoRule;
   }
+  if (mNodeInfo->Equals(nsGkAtoms::ol)) {
+    return &MapOLAttributesIntoRule;
+  }
 
   return nsGenericHTMLElement::GetAttributeMappingFunction();
 }
 
 JSObject* HTMLSharedListElement::WrapNode(JSContext* aCx,
                                           JS::Handle<JSObject*> aGivenProto) {
   if (mNodeInfo->Equals(nsGkAtoms::ol)) {
     return HTMLOListElement_Binding::Wrap(aCx, this, aGivenProto);
--- a/dom/html/HTMLSharedListElement.h
+++ b/dom/html/HTMLSharedListElement.h
@@ -53,14 +53,16 @@ class HTMLSharedListElement final : publ
   virtual ~HTMLSharedListElement();
 
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aGivenProto) override;
 
  private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     MappedDeclarations&);
+  static void MapOLAttributesIntoRule(const nsMappedAttributes* aAttributes,
+                                      MappedDeclarations&);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_HTMLSharedListElement_h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -13,16 +13,17 @@
 
 #include "mozilla/AutoRestore.h"
 #include "mozilla/ComputedStyleInlines.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/GeneratedImageContent.h"
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLSharedListElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/Likely.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/ServoStyleSetInlines.h"
@@ -9584,19 +9585,31 @@ inline void nsCSSFrameConstructor::Const
   // that information offhand in many cases.
   MOZ_ASSERT(ParentIsWrapperAnonBox(aParentFrame) == aParentIsWrapperAnonBox);
 
   CreateNeededPseudoContainers(aState, aItems, aParentFrame);
   CreateNeededAnonFlexOrGridItems(aState, aItems, aParentFrame);
   CreateNeededPseudoInternalRubyBoxes(aState, aItems, aParentFrame);
   CreateNeededPseudoSiblings(aState, aItems, aParentFrame);
 
+  bool listItemListIsDirty = false;
   for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
     NS_ASSERTION(iter.item().DesiredParentType() == GetParentType(aParentFrame),
                  "Needed pseudos didn't get created; expect bad things");
+    // display:list-item boxes affects the start value of the "list-item" counter
+    // when an <ol reversed> element doesn't have an explicit start value.
+    if (!listItemListIsDirty &&
+        iter.item().mComputedStyle->StyleList()->mMozListReversed
+            == StyleMozListReversed::True &&
+        iter.item().mComputedStyle->StyleDisplay()->mDisplay == StyleDisplay::ListItem) {
+      auto* list = mCounterManager.CounterListFor(NS_LITERAL_STRING("list-item"));
+      list->SetDirty();
+      CountersDirty();
+      listItemListIsDirty = true;
+    }
     ConstructFramesFromItem(aState, iter, aParentFrame, aFrameItems);
   }
 
   VerifyGridFlexContainerChildren(aParentFrame, aFrameItems);
 
   if (aParentIsWrapperAnonBox) {
     for (nsIFrame* f : aFrameItems) {
       f->SetParentIsWrapperAnonBox();
--- a/layout/base/nsCounterManager.cpp
+++ b/layout/base/nsCounterManager.cpp
@@ -51,17 +51,19 @@ void nsCounterUseNode::Calc(nsCounterLis
   NS_ASSERTION(!aList->IsDirty(), "Why are we calculating with a dirty list?");
   mValueAfter = nsCounterList::ValueBefore(this);
 }
 
 // assign the correct |mValueAfter| value to a node that has been inserted
 // Should be called immediately after calling |Insert|.
 void nsCounterChangeNode::Calc(nsCounterList* aList) {
   NS_ASSERTION(!aList->IsDirty(), "Why are we calculating with a dirty list?");
-  if (mType == RESET || mType == SET) {
+  if (IsContentBasedReset()) {
+    // RecalcAll takes care of this case.
+  } else if (mType == RESET || mType == SET) {
     mValueAfter = mChangeValue;
   } else {
     NS_ASSERTION(mType == INCREMENT, "invalid type");
     mValueAfter = nsCounterManager::IncrementCounter(
         nsCounterList::ValueBefore(this), mChangeValue);
   }
 }
 
@@ -151,31 +153,52 @@ void nsCounterList::SetScope(nsCounterNo
 
   aNode->mScopeStart = nullptr;
   aNode->mScopePrev = nullptr;
 }
 
 void nsCounterList::RecalcAll() {
   mDirty = false;
 
+  // Setup the scope and calculate the default start value for <ol reversed>.
   for (nsCounterNode* node = First(); node; node = Next(node)) {
     SetScope(node);
+    if (node->IsContentBasedReset()) {
+      node->mValueAfter = 1;
+    } else if ((node->mType == nsCounterChangeNode::INCREMENT ||
+                node->mType == nsCounterChangeNode::SET) &&
+               node->mScopeStart &&
+               node->mScopeStart->IsContentBasedReset()) {
+      ++node->mScopeStart->mValueAfter;
+    }
+  }
+
+  for (nsCounterNode* node = First(); node; node = Next(node)) {
+    auto oldValue = node->mValueAfter;
     node->Calc(this);
 
     if (node->mType == nsCounterNode::USE) {
       nsCounterUseNode* useNode = node->UseNode();
       // Null-check mText, since if the frame constructor isn't
       // batching, we could end up here while the node is being
       // constructed.
       if (useNode->mText) {
         nsAutoString text;
         useNode->GetText(text);
         useNode->mText->SetData(text, IgnoreErrors());
       }
     }
+
+    if (oldValue != node->mValueAfter && node->mPseudoFrame &&
+        node->mPseudoFrame->StyleDisplay()->mDisplay == StyleDisplay::ListItem) {
+      auto* shell = node->mPseudoFrame->PresShell();
+      shell->FrameNeedsReflow(node->mPseudoFrame,
+                              nsIPresShell::eStyleChange,
+                              NS_FRAME_IS_DIRTY);
+    }
   }
 }
 
 bool nsCounterManager::AddCounterChanges(nsIFrame* aFrame) {
   const nsStyleContent* styleContent = aFrame->StyleContent();
   if (!styleContent->CounterIncrementCount() &&
       !styleContent->CounterResetCount() &&
       !styleContent->CounterSetCount()) {
--- a/layout/base/nsCounterManager.h
+++ b/layout/base/nsCounterManager.h
@@ -67,16 +67,19 @@ struct nsCounterNode : public nsGenConNo
       : nsGenConNode(aContentIndex),
         mType(aType),
         mValueAfter(0),
         mScopeStart(nullptr),
         mScopePrev(nullptr) {}
 
   // to avoid virtual function calls in the common case
   inline void Calc(nsCounterList* aList);
+
+  // Is this a <ol reversed> RESET node?
+  inline bool IsContentBasedReset();
 };
 
 struct nsCounterUseNode : public nsCounterNode {
   mozilla::CounterStylePtr mCounterStyle;
   nsString mSeparator;
 
   // false for counter(), true for counters()
   bool mAllCounters;
@@ -145,16 +148,21 @@ inline nsCounterChangeNode* nsCounterNod
 
 inline void nsCounterNode::Calc(nsCounterList* aList) {
   if (mType == USE)
     UseNode()->Calc(aList);
   else
     ChangeNode()->Calc(aList);
 }
 
+inline bool nsCounterNode::IsContentBasedReset() {
+  return mType == RESET &&
+         ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min();
+}
+  
 class nsCounterList : public nsGenConList {
  public:
   nsCounterList() : nsGenConList(), mDirty(false) {}
 
   void Insert(nsCounterNode* aNode) {
     nsGenConList::Insert(aNode);
     // Don't SetScope if we're dirty -- we'll reset all the scopes anyway,
     // and we can't usefully compute scopes right now.
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -994,18 +994,18 @@ fuzzy-if(Android,0-11,0-17) fuzzy-if(web
 == 414123.xhtml 414123-ref.xhtml
 == 414638.html 414638-ref.html
 == 414851-1.html 414851-1-ref.html
 == 416106-1.xhtml 416106-1-ref.xhtml
 == 416752-1.html 416752-1-ref.html
 == 417178-1.html 417178-1-ref.html
 == 417246-1.html 417246-1-ref.html
 == 417676.html 417676-ref.html
-asserts(1) == 418574-1.html 418574-1-ref.html # bug 478135
-asserts(1) == 418574-2.html 418574-2-ref.html # bug 478135
+== 418574-1.html 418574-1-ref.html
+== 418574-2.html 418574-2-ref.html
 == 418766-1a.html 418766-1-ref.html
 == 418766-1b.html 418766-1-ref.html
 == 419060.html 419060-ref.html
 == 419285-1.html 419285-1-ref.html
 == 419531-1.html 419531-1-ref.html
 == 420069-1.html 420069-1-ref.html
 == 420069-2.html 420069-2-ref.html
 == 420351-1.html 420351-1-ref.html
--- a/layout/reftests/counters/counter-reset-integer-range-ref.html
+++ b/layout/reftests/counters/counter-reset-integer-range-ref.html
@@ -1,9 +1,9 @@
 <!DOCTYPE HTML>
 <title>Expected integer range</title>
 0
 2147483647
 2147483647
 2147483647
 -2147483647
--2147483647
--2147483647
+0
+0
--- a/layout/reftests/counters/counter-reset-integer-range.html
+++ b/layout/reftests/counters/counter-reset-integer-range.html
@@ -5,10 +5,11 @@
 span::after { content: counter(c); }
 
 </style>
 <span style="counter-reset: c 0"></span>
 <span style="counter-reset: c 2147483647"></span>
 <span style="counter-reset: c 2147483648"></span>
 <span style="counter-reset: c 2147483649"></span>
 <span style="counter-reset: c -2147483647"></span>
+<!-- The next two computes to std::numeric_limits<int32_t>::min() which we use as the "magic" number for the content based <ol reversed> start value.  See https://drafts.csswg.org/css-lists-3/#ua-stylesheet -->
 <span style="counter-reset: c -2147483648"></span>
 <span style="counter-reset: c -2147483649"></span>
--- a/layout/reftests/list-item/reftest.list
+++ b/layout/reftests/list-item/reftest.list
@@ -1,15 +1,15 @@
 fuzzy-if(OSX,0-55,0-4) == numbering-1.html numbering-1-ref.html
 == numbering-2.html numbering-2-ref.html
 fuzzy-if(OSX,0-11,0-1) == numbering-3.html numbering-3-ref.html
 fuzzy-if(OSX,0-76,0-2) == numbering-4.html numbering-4-ref.html
 == numbering-5.html numbering-5-ref.html
 == ol-reversed-1a.html ol-reversed-1-ref.html
-asserts(1) == ol-reversed-1b.html ol-reversed-1-ref.html # bug 478135
+== ol-reversed-1b.html ol-reversed-1-ref.html
 == ol-reversed-1c.html ol-reversed-1-ref.html
 == ol-reversed-2.html ol-reversed-2-ref.html
 == ol-reversed-3.html ol-reversed-3-ref.html
 == bullet-space-1.html bullet-space-1-ref.html
 == bullet-space-2.html bullet-space-2-ref.html
 == bullet-intrinsic-isize-1.html bullet-intrinsic-isize-1-ref.html
 == bullet-intrinsic-isize-2.html bullet-intrinsic-isize-2-ref.html
 == bullet-justify-1.html bullet-justify-1-ref.html
--- a/layout/style/MappedDeclarations.h
+++ b/layout/style/MappedDeclarations.h
@@ -91,16 +91,31 @@ class MappedDeclarations final {
     SetKeywordValueIfUnset(aId, static_cast<int32_t>(aValue));
   }
 
   // Set a property to an integer value
   void SetIntValue(nsCSSPropertyID aId, int32_t aValue) {
     Servo_DeclarationBlock_SetIntValue(mDecl, aId, aValue);
   }
 
+  // Set "counter-reset: list-item <integer>".
+  void SetCounterResetListItem(int32_t aValue) {
+    Servo_DeclarationBlock_SetCounterResetListItem(mDecl, aValue);
+  }
+
+  // Set "counter-set: list-item <integer>".
+  void SetCounterSetListItem(int32_t aValue) {
+    Servo_DeclarationBlock_SetCounterSetListItem(mDecl, aValue);
+  }
+
+  // Set "counter-increment: list-item <integer>".
+  void SetCounterIncrementListItem(int32_t aValue) {
+    Servo_DeclarationBlock_SetCounterIncrementListItem(mDecl, aValue);
+  }
+
   // Set a property to a pixel value
   void SetPixelValue(nsCSSPropertyID aId, float aValue) {
     Servo_DeclarationBlock_SetPixelValue(mDecl, aId, aValue);
   }
 
   void SetPixelValueIfUnset(nsCSSPropertyID aId, float aValue) {
     if (!PropertyIsSet(aId)) {
       SetPixelValue(aId, aValue);
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -695,16 +695,25 @@ void Servo_DeclarationBlock_SetIdentStri
 void Servo_DeclarationBlock_SetKeywordValue(
     const RawServoDeclarationBlock* declarations, nsCSSPropertyID property,
     int32_t value);
 
 void Servo_DeclarationBlock_SetIntValue(
     const RawServoDeclarationBlock* declarations, nsCSSPropertyID property,
     int32_t value);
 
+void Servo_DeclarationBlock_SetCounterResetListItem(
+    const RawServoDeclarationBlock* declarations, int32_t value);
+
+void Servo_DeclarationBlock_SetCounterSetListItem(
+    const RawServoDeclarationBlock* declarations, int32_t value);
+
+void Servo_DeclarationBlock_SetCounterIncrementListItem(
+    const RawServoDeclarationBlock* declarations, int32_t value);
+
 void Servo_DeclarationBlock_SetPixelValue(
     const RawServoDeclarationBlock* declarations, nsCSSPropertyID property,
     float value);
 
 void Servo_DeclarationBlock_SetLengthValue(
     const RawServoDeclarationBlock* declarations, nsCSSPropertyID property,
     float value, nsCSSUnit unit);
 
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -575,16 +575,17 @@ ul, menu, dir {
   padding-inline-start: 40px;
 }
 
 menu[type="context"] {
   display: none !important;
 }
 
 ul, ol, menu {
+  counter-reset: list-item;
   -moz-list-reversed: false;
 }
 
 ol[reversed] {
   -moz-list-reversed: true;
 }
 
 ol {
@@ -778,16 +779,17 @@ video > .caption-box {
   background-color: rgba(0, 0, 0, 0.8);
   font: var(--cue-font-size) sans-serif;
 }
 
 /* details & summary */
 details > summary:first-of-type,
 details > summary:-moz-native-anonymous {
   display: list-item;
+  counter-increment: list-item 0;
   list-style: disclosure-closed inside;
 }
 
 details[open] > summary:first-of-type,
 details[open] > summary:-moz-native-anonymous {
   list-style-type: disclosure-open;
 }
 
--- a/servo/components/style/style_adjuster.rs
+++ b/servo/components/style/style_adjuster.rs
@@ -703,16 +703,57 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
                 return;
             }
             self.style
                 .mutate_inherited_text()
                 .set_line_height(LineHeight::normal());
         }
     }
 
+    /// For HTML elements with 'display:list-item' we add a default 'counter-increment:list-item'
+    /// unless 'counter-increment' already has a value for 'list-item'.
+    ///
+    /// https://drafts.csswg.org/css-lists-3/#declaring-a-list-item
+    #[cfg(feature = "gecko")]
+    fn adjust_for_list_item<E>(&mut self, element: Option<E>)
+    where
+        E: TElement,
+    {
+        use crate::properties::longhands::counter_increment::computed_value::T as ComputedIncrement;
+        use crate::values::CustomIdent;
+        use crate::values::generics::counters::{CounterPair};
+        use crate::values::specified::list::MozListReversed;
+
+        if self.style.get_box().clone_display() != Display::ListItem {
+            return;
+        }
+        if self.style.pseudo.is_some() {
+            return;
+        }
+        if !element.map_or(false, |e| e.is_html_element()) {
+            return;
+        }
+        // Note that we map <li value=INTEGER> to 'counter-set: list-item INTEGER;
+        // counter-increment: list-item 0;' so we'll return here unless the author
+        // explicitly specified something else.
+        let increments = self.style.get_counters().clone_counter_increment();
+        if increments.iter().any(|i| i.name.0 == atom!("list-item")) {
+            return;
+        }
+
+        let reversed = self.style.get_list().clone__moz_list_reversed() == MozListReversed::True;
+        let increment = if reversed { -1 } else { 1 };
+        let list_increment = CounterPair {
+            name: CustomIdent(atom!("list-item")),
+            value: increment,
+        };
+        let increments = increments.iter().cloned().chain(std::iter::once(list_increment));
+        self.style.mutate_counters().set_counter_increment(ComputedIncrement::new(increments.collect()));
+    }
+
     /// Adjusts the style to account for various fixups that don't fit naturally
     /// into the cascade.
     ///
     /// When comparing to Gecko, this is similar to the work done by
     /// `ComputedStyle::ApplyStyleFixups`, plus some parts of
     /// `nsStyleSet::GetContext`.
     pub fn adjust<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>)
     where
@@ -767,12 +808,13 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
         }
         #[cfg(feature = "servo")]
         {
             self.adjust_for_text_decorations_in_effect();
         }
         #[cfg(feature = "gecko")]
         {
             self.adjust_for_appearance(element);
+            self.adjust_for_list_item(element);
         }
         self.set_bits();
     }
 }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -4377,16 +4377,67 @@ pub extern "C" fn Servo_DeclarationBlock
         MozScriptLevel => MozScriptLevel::Relative(value),
     };
     write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
         decls.push(prop, Importance::Normal);
     })
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_DeclarationBlock_SetCounterResetListItem(
+    declarations: &RawServoDeclarationBlock,
+    counter_value: i32,
+) {
+    use style::values::generics::counters::{CounterPair, CounterSetOrReset};
+    use style::properties::{PropertyDeclaration};
+
+    let prop = PropertyDeclaration::CounterReset(CounterSetOrReset::new(vec![CounterPair {
+        name: CustomIdent(atom!("list-item")),
+        value: style::values::specified::Integer::new(counter_value),
+    }]));
+    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
+        decls.push(prop, Importance::Normal);
+    })
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_DeclarationBlock_SetCounterSetListItem(
+    declarations: &RawServoDeclarationBlock,
+    counter_value: i32,
+) {
+    use style::values::generics::counters::{CounterPair, CounterSetOrReset};
+    use style::properties::{PropertyDeclaration};
+
+    let prop = PropertyDeclaration::CounterSet(CounterSetOrReset::new(vec![CounterPair {
+        name: CustomIdent(atom!("list-item")),
+        value: style::values::specified::Integer::new(counter_value),
+    }]));
+    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
+        decls.push(prop, Importance::Normal);
+    })
+}
+
+#[no_mangle]
+pub extern "C" fn Servo_DeclarationBlock_SetCounterIncrementListItem(
+    declarations: &RawServoDeclarationBlock,
+    counter_value: i32,
+) {
+    use style::values::generics::counters::{CounterPair, CounterIncrement};
+    use style::properties::{PropertyDeclaration};
+
+    let prop = PropertyDeclaration::CounterIncrement(CounterIncrement::new(vec![CounterPair {
+        name: CustomIdent(atom!("list-item")),
+        value: style::values::specified::Integer::new(counter_value),
+    }]));
+    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
+        decls.push(prop, Importance::Normal);
+    })
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetPixelValue(
     declarations: &RawServoDeclarationBlock,
     property: nsCSSPropertyID,
     value: f32,
 ) {
     use style::properties::longhands::border_spacing::SpecifiedValue as BorderSpacing;
     use style::properties::{LonghandId, PropertyDeclaration};
     use style::values::generics::length::Size;
deleted file mode 100644
--- a/testing/web-platform/meta/css/CSS2/css1/c561-list-displ-000.xht.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[c561-list-displ-000.xht]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-lists/inheritance.html.ini
@@ -0,0 +1,2 @@
+[inheritance.html]
+  disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1405176
deleted file mode 100644
--- a/testing/web-platform/meta/html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/legend-list-item-numbering.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[legend-list-item-numbering.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-mixed.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grouping-li-reftest-list-owner-mixed.html]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/grouping-content/the-li-element/grouping-li-reftest-list-owner-not-dir.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[grouping-li-reftest-list-owner-not-dir.html]
-  expected: FAIL
--- a/testing/web-platform/tests/css/CSS2/lists/counter-reset-increment-002.xht
+++ b/testing/web-platform/tests/css/CSS2/lists/counter-reset-increment-002.xht
@@ -24,17 +24,16 @@
   {
   counter-reset: list-item -4;
   list-style-type: none;
   }
 
   li:before
   {
   content: counter(list-item) ". ";
-  counter-increment: list-item;
   }
   ]]></style>
 
  </head>
 
  <body>
 
   <p>The two columns of numbers should be <strong>identical</strong>.</p>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/li-list-item-counter-ref.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference:_CSS Lists: 'counter-increment:list-item' on LI</title>
+  <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+body { margin-left: 10em; }
+  </style>
+</head>
+<body>
+
+<ol><li value=0>a<li value=4>b<li value=4>c</ol>
+<ol><li value=0>a<li value=9>b<li value=9>c</ol>
+<ol><li value=-1>a<li value=3>b<li value=2>c</ol>
+<ol><li value=0>a<li value=4>b<li value=4>c</ol>
+<ol><li value=2>a<li value=6>b<li value=8>c</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/li-list-item-counter.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Lists: 'counter-increment:list-item' on LI</title>
+  <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+  <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-increment">
+  <link rel="match" href="li-list-item-counter-ref.html">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+body { margin-left: 10em; }
+li { counter-increment: list-item 0; }
+.dec { counter-increment:list-item -1; }
+.zero { counter-increment:list-item 1 list-item -1; }
+.two { counter-increment:list-item 3 list-item -1; }
+  </style>
+</head>
+<body>
+
+<ol><li>a<li value=4>b<li>c</ol>
+<ol><li>a<li value=4 style="counter-increment:list-item 5">b<li>c</ol>
+<ol><li class=dec>a<li value=4 class=dec>b<li class=dec>c</ol>
+<ol><li class=zero>a<li value=4 class=zero>b<li class=zero>c</ol>
+<ol><li class=two>a<li value=4 class=two>b<li class=two>c</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/li-value-counter-reset-001-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference:_CSS Lists: 'counter-set:list-item' trumps LI @value</title>
+  <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+body { margin-left: 10em; }
+  </style>
+</head>
+<body>
+
+<ol><li>a<li value=99>b</ol>
+<ol><li>a<li value=149>b</ol>
+<ol><li>a<li value=54>b</ol>
+<ol><li>a<li value=149>b</ol>
+<ol><li>a<li value=51>b</ol>
+<ol><li>a<li value=88>b</ol>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-lists/li-value-counter-reset-001.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>CSS Lists: 'counter-set:list-item' trumps LI @value</title>
+  <link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.org">
+  <link rel="help" href="https://drafts.csswg.org/css-lists/#propdef-counter-set">
+  <link rel="match" href="li-value-counter-reset-001-ref.html">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+body { margin-left: 10em; }
+li.set { counter-set: list-item 99; }
+  </style>
+</head>
+<body>
+
+<ol><li>a<li value=4 class=set>b</ol>
+<ol><li>a<li value=4 class=set style="counter-increment:list-item 50">b</ol>
+<ol><li>a<li value=4 style="counter-increment:list-item 50">b</ol>
+<ol><li>a<li class=set style="counter-increment:list-item 50">b</ol>
+<ol><li>a<li style="counter-increment:list-item 50">b</ol>
+<ol><li>a<li value=4 class=set style="counter-set:list-item 88">b</ol>
+
+</body>
+</html>
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -1406,16 +1406,17 @@ STATIC_ATOMS = [
     Atom("kernelUnitLength", "kernelUnitLength"),
     Atom("lengthAdjust", "lengthAdjust"),
     Atom("letter_spacing", "letter-spacing"),
     Atom("lighten", "lighten"),
     Atom("lighting_color", "lighting-color"),
     Atom("limitingConeAngle", "limitingConeAngle"),
     Atom("linear", "linear"),
     Atom("linearGradient", "linearGradient"),
+    Atom("list_item", "list-item"),
     Atom("list_style_type", "list-style-type"),
     Atom("luminanceToAlpha", "luminanceToAlpha"),
     Atom("luminosity", "luminosity"),
     Atom("magnify", "magnify"),
     Atom("marker", "marker"),
     Atom("marker_end", "marker-end"),
     Atom("marker_mid", "marker-mid"),
     Atom("marker_start", "marker-start"),