Commit 7e604982 authored by Koji Ishii's avatar Koji Ishii Committed by Commit Bot

Split "ordinal value" logic from LayoutListItem

This patch splits the "ordinal value" logic[1] to a separate class,
ListItemOridinal, so that we can re-use the logic in LayoutNG.

The logic is mostly DOM operations that moving them out of layout is
reasonable from the layering perspective too.

It still relies on layout tree to provide the storage, and events for
insertions and deletions, since any elements with 'display: list-item'
can be list items.

[1] https://html.spec.whatwg.org/multipage/grouping-content.html#ordinal-value

Bug: 591099
Change-Id: Ia61ee764db83203ac8a4006cbab534b5ccf56606
Reviewed-on: https://chromium-review.googlesource.com/678415
Commit-Queue: Koji Ishii <kojii@chromium.org>
Reviewed-by: default avatarEmil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#504338}
parent 43d9c3bb
......@@ -232,6 +232,8 @@ blink_core_sources("html") {
"LinkResource.h",
"LinkStyle.cpp",
"LinkStyle.h",
"ListItemOrdinal.cpp",
"ListItemOrdinal.h",
"ListedElement.cpp",
"ListedElement.h",
"PluginDocument.cpp",
......
......@@ -25,10 +25,10 @@
#include "core/CSSPropertyNames.h"
#include "core/CSSValueKeywords.h"
#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/html/ListItemOrdinal.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/api/LayoutLIItem.h"
namespace blink {
......@@ -83,8 +83,8 @@ void HTMLLIElement::CollectStyleForPresentationAttribute(
void HTMLLIElement::ParseAttribute(const AttributeModificationParams& params) {
if (params.name == valueAttr) {
if (GetLayoutObject() && GetLayoutObject()->IsListItem())
ParseValue(params.new_value);
if (ListItemOrdinal* ordinal = ListItemOrdinal::Get(*this))
ParseValue(params.new_value, ordinal);
} else {
HTMLElement::ParseAttribute(params);
}
......@@ -93,10 +93,7 @@ void HTMLLIElement::ParseAttribute(const AttributeModificationParams& params) {
void HTMLLIElement::AttachLayoutTree(AttachContext& context) {
HTMLElement::AttachLayoutTree(context);
if (GetLayoutObject() && GetLayoutObject()->IsListItem()) {
LayoutLIItem li_layout_item =
LayoutLIItem(ToLayoutListItem(GetLayoutObject()));
if (ListItemOrdinal* ordinal = ListItemOrdinal::Get(*this)) {
DCHECK(!GetDocument().ChildNeedsDistributionRecalc());
// Find the enclosing list node.
......@@ -114,21 +111,21 @@ void HTMLLIElement::AttachLayoutTree(AttachContext& context) {
// inside. We don't want to change our style to say "inside" since that
// would affect nested nodes.
if (!list_node)
li_layout_item.SetNotInList(true);
ordinal->SetNotInList(true);
ParseValue(FastGetAttribute(valueAttr));
ParseValue(FastGetAttribute(valueAttr), ordinal);
}
}
inline void HTMLLIElement::ParseValue(const AtomicString& value) {
DCHECK(GetLayoutObject());
DCHECK(GetLayoutObject()->IsListItem());
void HTMLLIElement::ParseValue(const AtomicString& value,
ListItemOrdinal* ordinal) {
DCHECK(ListItemOrdinal::IsListItem(*this));
int requested_value = 0;
if (ParseHTMLInteger(value, requested_value))
ToLayoutListItem(GetLayoutObject())->SetExplicitValue(requested_value);
ordinal->SetExplicitValue(requested_value, *this);
else
ToLayoutListItem(GetLayoutObject())->ClearExplicitValue();
ordinal->ClearExplicitValue(*this);
}
} // namespace blink
......@@ -27,6 +27,8 @@
namespace blink {
class ListItemOrdinal;
class HTMLLIElement final : public HTMLElement {
DEFINE_WRAPPERTYPEINFO();
......@@ -44,7 +46,7 @@ class HTMLLIElement final : public HTMLElement {
void AttachLayoutTree(AttachContext&) override;
void ParseValue(const AtomicString&);
void ParseValue(const AtomicString&, ListItemOrdinal*);
};
} // namespace blink
......
......@@ -26,6 +26,7 @@
#include "core/CSSValueKeywords.h"
#include "core/HTMLNames.h"
#include "core/frame/UseCounter.h"
#include "core/html/ListItemOrdinal.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/layout/LayoutListItem.h"
......@@ -105,11 +106,11 @@ void HTMLOListElement::UpdateItemValues() {
if (!GetLayoutObject())
return;
UpdateDistribution();
LayoutListItem::UpdateItemValuesForOrderedList(this);
ListItemOrdinal::InvalidateAllItemsForOrderedList(this);
}
void HTMLOListElement::RecalculateItemCount() {
item_count_ = LayoutListItem::ItemCountForOrderedList(this);
item_count_ = ListItemOrdinal::ItemCountForOrderedList(this);
should_recalculate_item_count_ = false;
}
......
This diff is collapsed.
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2009 Apple Inc.
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef ListItemOrdinal_h
#define ListItemOrdinal_h
#include "platform/heap/Persistent.h"
#include "platform/wtf/Optional.h"
namespace blink {
class HTMLOListElement;
class LayoutListItem;
class LayoutObject;
class Node;
// Represents an "ordinal value" and its related algorithms:
// https://html.spec.whatwg.org/multipage/grouping-content.html#ordinal-value
//
// The ordinal value is determined by the DOM tree order. However, since any
// elements with 'display: list-item' can be list items, the layout tree
// provides the storage for the instances of this class, and is responsible for
// firing events for insertions and removals.
class ListItemOrdinal {
public:
ListItemOrdinal();
// Get the corresponding instance for a node.
static ListItemOrdinal* Get(const Node&);
// Get the "ordinal value".
int Value(const Node&) const;
// Get/set/clear the explicit value; i.e., the 'value' attribute of an <li>
// element.
Optional<int> ExplicitValue() const;
void SetExplicitValue(int, const Node&);
void ClearExplicitValue(const Node&);
// Get/set whether this item is in a list or not.
bool NotInList() const { return not_in_list_; }
void SetNotInList(bool);
static bool IsList(const Node&);
static bool IsListItem(const Node&);
static bool IsListItem(const LayoutObject*);
// Compute the total item count of a list.
static unsigned ItemCountForOrderedList(const HTMLOListElement*);
// Invalidate all ordinal values of a list.
static void InvalidateAllItemsForOrderedList(const HTMLOListElement*);
// Invalidate items that are affected by an insertion or a removal.
static void ItemInsertedOrRemoved(const LayoutListItem*);
private:
enum ValueType { kNeedsUpdate, kUpdated, kExplicit };
ValueType Type() const { return static_cast<ValueType>(type_); }
void SetType(ValueType type) const { type_ = type; }
bool HasExplicitValue() const { return type_ == kExplicit; }
static Node* EnclosingList(const Node*);
struct NodeAndOrdinal {
STACK_ALLOCATED();
Persistent<const Node> node;
ListItemOrdinal* ordinal = nullptr;
operator bool() const { return node; }
};
static NodeAndOrdinal NextListItem(const Node* list_node,
const Node* item_node = nullptr);
static NodeAndOrdinal PreviousListItem(const Node* list_node,
const Node* item_node);
static NodeAndOrdinal NextOrdinalItem(bool is_reversed,
const Node* list_node,
const Node* item_node = nullptr);
int CalcValue(const Node&) const;
void InvalidateSelf(const Node&, ValueType = kNeedsUpdate);
static void InvalidateAfter(const Node* list_node, const Node* item_node);
static void InvalidateOrdinalsAfter(bool is_reversed,
const Node* list_node,
const Node* item_node);
mutable int value_ = 0;
mutable unsigned type_ : 2; // ValueType
unsigned not_in_list_ : 1;
};
} // namespace blink
#endif // ListItemOrdinal_h
......@@ -27,6 +27,7 @@
#include "core/dom/ElementTraversal.h"
#include "core/dom/PseudoElement.h"
#include "core/html/HTMLOListElement.h"
#include "core/html/ListItemOrdinal.h"
#include "core/layout/CounterNode.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/LayoutView.h"
......@@ -178,17 +179,17 @@ static bool PlanCounter(LayoutObject& object,
}
if (identifier == "list-item") {
if (object.IsListItem()) {
if (ToLayoutListItem(object).HasExplicitValue()) {
value = ToLayoutListItem(object).ExplicitValue();
is_reset = true;
if (Node* e = object.GetNode()) {
if (ListItemOrdinal* ordinal = ListItemOrdinal::Get(*e)) {
if (const auto& explicit_value = ordinal->ExplicitValue()) {
value = explicit_value.value();
is_reset = true;
return true;
}
value = 1;
is_reset = false;
return true;
}
value = 1;
is_reset = false;
return true;
}
if (Node* e = object.GetNode()) {
if (isHTMLOListElement(*e)) {
value = toHTMLOListElement(e)->StartConsideringItemCount();
is_reset = true;
......
......@@ -25,6 +25,7 @@
#include "core/HTMLNames.h"
#include "core/dom/FlatTreeTraversal.h"
#include "core/html/HTMLLIElement.h"
#include "core/html/HTMLOListElement.h"
#include "core/layout/LayoutListMarker.h"
#include "core/paint/ListItemPainter.h"
......@@ -37,11 +38,7 @@ namespace blink {
using namespace HTMLNames;
LayoutListItem::LayoutListItem(Element* element)
: LayoutBlockFlow(element),
marker_(nullptr),
has_explicit_value_(false),
is_value_up_to_date_(false),
not_in_list_(false) {
: LayoutBlockFlow(element), marker_(nullptr) {
SetInline(false);
SetConsumesSubtreeChangeNotification();
......@@ -88,13 +85,13 @@ void LayoutListItem::WillBeDestroyed() {
void LayoutListItem::InsertedIntoTree() {
LayoutBlockFlow::InsertedIntoTree();
UpdateListMarkerNumbers();
ListItemOrdinal::ItemInsertedOrRemoved(this);
}
void LayoutListItem::WillBeRemovedFromTree() {
LayoutBlockFlow::WillBeRemovedFromTree();
UpdateListMarkerNumbers();
ListItemOrdinal::ItemInsertedOrRemoved(this);
}
void LayoutListItem::SubtreeDidChange() {
......@@ -110,135 +107,9 @@ void LayoutListItem::SubtreeDidChange() {
SetPreferredLogicalWidthsDirty();
}
static bool IsList(const Node& node) {
return isHTMLUListElement(node) || isHTMLOListElement(node);
}
// Returns the enclosing list with respect to the DOM order.
static Node* EnclosingList(const LayoutListItem* list_item) {
Node* list_item_node = list_item->GetNode();
if (!list_item_node)
return nullptr;
Node* first_node = nullptr;
// We use parentNode because the enclosing list could be a ShadowRoot that's
// not Element.
for (Node* parent = FlatTreeTraversal::Parent(*list_item_node); parent;
parent = FlatTreeTraversal::Parent(*parent)) {
if (IsList(*parent))
return parent;
if (!first_node)
first_node = parent;
}
// If there's no actual <ul> or <ol> list element, then the first found
// node acts as our list for purposes of determining what other list items
// should be numbered as part of the same list.
return first_node;
}
// Returns the next list item with respect to the DOM order.
static LayoutListItem* NextListItem(const Node* list_node,
const LayoutListItem* item = nullptr) {
if (!list_node)
return nullptr;
const Node* current = item ? item->GetNode() : list_node;
DCHECK(current);
DCHECK(!current->GetDocument().ChildNeedsDistributionRecalc());
current = LayoutTreeBuilderTraversal::Next(*current, list_node);
while (current) {
if (IsList(*current)) {
// We've found a nested, independent list: nothing to do here.
current =
LayoutTreeBuilderTraversal::NextSkippingChildren(*current, list_node);
continue;
}
LayoutObject* layout_object = current->GetLayoutObject();
if (layout_object && layout_object->IsListItem())
return ToLayoutListItem(layout_object);
// FIXME: Can this be optimized to skip the children of the elements without
// a layoutObject?
current = LayoutTreeBuilderTraversal::Next(*current, list_node);
}
return nullptr;
}
// Returns the previous list item with respect to the DOM order.
static LayoutListItem* PreviousListItem(const Node* list_node,
const LayoutListItem* item) {
Node* current = item->GetNode();
DCHECK(current);
DCHECK(!current->GetDocument().ChildNeedsDistributionRecalc());
for (current = LayoutTreeBuilderTraversal::Previous(*current, list_node);
current && current != list_node;
current = LayoutTreeBuilderTraversal::Previous(*current, list_node)) {
LayoutObject* layout_object = current->GetLayoutObject();
if (!layout_object || (layout_object && !layout_object->IsListItem()))
continue;
Node* other_list = EnclosingList(ToLayoutListItem(layout_object));
// This item is part of our current list, so it's what we're looking for.
if (list_node == other_list)
return ToLayoutListItem(layout_object);
// We found ourself inside another list; lets skip the rest of it.
// Use nextIncludingPseudo() here because the other list itself may actually
// be a list item itself. We need to examine it, so we do this to counteract
// the previousIncludingPseudo() that will be done by the loop.
if (other_list)
current = LayoutTreeBuilderTraversal::Next(*other_list, list_node);
}
return nullptr;
}
void LayoutListItem::UpdateItemValuesForOrderedList(
const HTMLOListElement* list_node) {
DCHECK(list_node);
for (LayoutListItem* list_item = NextListItem(list_node); list_item;
list_item = NextListItem(list_node, list_item))
list_item->UpdateValue();
}
unsigned LayoutListItem::ItemCountForOrderedList(
const HTMLOListElement* list_node) {
DCHECK(list_node);
unsigned item_count = 0;
for (LayoutListItem* list_item = NextListItem(list_node); list_item;
list_item = NextListItem(list_node, list_item))
item_count++;
return item_count;
}
inline int LayoutListItem::CalcValue() const {
if (has_explicit_value_)
return explicit_value_;
Node* list = EnclosingList(this);
HTMLOListElement* o_list_element =
isHTMLOListElement(list) ? toHTMLOListElement(list) : nullptr;
int value_step = 1;
if (o_list_element && o_list_element->IsReversed())
value_step = -1;
// FIXME: This recurses to a possible depth of the length of the list.
// That's not good -- we need to change this to an iterative algorithm.
if (LayoutListItem* previous_item = PreviousListItem(list, this))
return ClampAdd(previous_item->Value(), value_step);
if (o_list_element)
return o_list_element->StartConsideringItemCount();
return 1;
}
void LayoutListItem::UpdateValueNow() const {
value_ = CalcValue();
is_value_up_to_date_ = true;
int LayoutListItem::Value() const {
DCHECK(GetNode());
return ordinal_.Value(*GetNode());
}
bool LayoutListItem::IsEmpty() const {
......@@ -288,15 +159,6 @@ static LayoutObject* GetParentOfFirstLineBox(LayoutBlockFlow* curr,
return nullptr;
}
void LayoutListItem::UpdateValue() {
if (!has_explicit_value_) {
is_value_up_to_date_ = false;
if (marker_)
marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::kListValueChange);
}
}
static LayoutObject* FirstNonMarkerChild(LayoutObject* parent) {
LayoutObject* result = parent->SlowFirstChild();
while (result && result->IsListMarker())
......@@ -475,84 +337,11 @@ const String& LayoutListItem::MarkerText() const {
return g_null_atom.GetString();
}
void LayoutListItem::ExplicitValueChanged() {
if (marker_)
marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::kListValueChange);
Node* list_node = EnclosingList(this);
for (LayoutListItem* item = this; item; item = NextListItem(list_node, item))
item->UpdateValue();
}
void LayoutListItem::SetExplicitValue(int value) {
DCHECK(GetNode());
if (has_explicit_value_ && explicit_value_ == value)
return;
explicit_value_ = value;
value_ = value;
has_explicit_value_ = true;
ExplicitValueChanged();
}
void LayoutListItem::ClearExplicitValue() {
DCHECK(GetNode());
if (!has_explicit_value_)
return;
has_explicit_value_ = false;
is_value_up_to_date_ = false;
ExplicitValueChanged();
}
void LayoutListItem::SetNotInList(bool not_in_list) {
not_in_list_ = not_in_list;
}
static LayoutListItem* PreviousOrNextItem(bool is_list_reversed,
Node* list,
LayoutListItem* item) {
return is_list_reversed ? PreviousListItem(list, item)
: NextListItem(list, item);
}
void LayoutListItem::UpdateListMarkerNumbers() {
// If distribution recalc is needed, updateListMarkerNumber will be re-invoked
// after distribution is calculated.
if (GetNode()->GetDocument().ChildNeedsDistributionRecalc())
return;
Node* list_node = EnclosingList(this);
CHECK(list_node);
bool is_list_reversed = false;
HTMLOListElement* o_list_element =
isHTMLOListElement(list_node) ? toHTMLOListElement(list_node) : 0;
if (o_list_element) {
o_list_element->ItemCountChanged();
is_list_reversed = o_list_element->IsReversed();
}
// FIXME: The n^2 protection below doesn't help if the elements were inserted
// after the the list had already been displayed.
// Avoid an O(n^2) walk over the children below when they're all known to be
// attaching.
if (list_node->NeedsAttach())
void LayoutListItem::OrdinalValueChanged() {
if (!marker_)
return;
for (LayoutListItem* item =
PreviousOrNextItem(is_list_reversed, list_node, this);
item; item = PreviousOrNextItem(is_list_reversed, list_node, item)) {
if (!item->is_value_up_to_date_) {
// If an item has been marked for update before, we can safely
// assume that all the following ones have too.
// This gives us the opportunity to stop here and avoid
// marking the same nodes again.
break;
}
item->UpdateValue();
}
marker_->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::kListValueChange);
}
} // namespace blink
......@@ -24,43 +24,28 @@
#ifndef LayoutListItem_h
#define LayoutListItem_h
#include "core/html/ListItemOrdinal.h"
#include "core/layout/LayoutBlockFlow.h"
namespace blink {
class HTMLOListElement;
class LayoutListMarker;
class LayoutListItem final : public LayoutBlockFlow {
public:
explicit LayoutListItem(Element*);
int Value() const {
if (!is_value_up_to_date_)
UpdateValueNow();
return value_;
}
void UpdateValue();
bool HasExplicitValue() const { return has_explicit_value_; }
int ExplicitValue() const { return explicit_value_; }
void SetExplicitValue(int);
void ClearExplicitValue();
void SetNotInList(bool);
bool NotInList() const { return not_in_list_; }
int Value() const;
const String& MarkerText() const;
void UpdateListMarkerNumbers();
static void UpdateItemValuesForOrderedList(const HTMLOListElement*);
static unsigned ItemCountForOrderedList(const HTMLOListElement*);
bool IsEmpty() const;
LayoutListMarker* Marker() const { return marker_; }
ListItemOrdinal& Ordinal() { return ordinal_; }
void OrdinalValueChanged();
const char* GetName() const override { return "LayoutListItem"; }
private:
......@@ -86,17 +71,9 @@ class LayoutListItem final : public LayoutBlockFlow {
void AddOverflowFromChildren() override;
inline int CalcValue() const;
void UpdateValueNow() const;
void ExplicitValueChanged();
int explicit_value_;
ListItemOrdinal ordinal_;
LayoutListMarker* marker_;
mutable int value_;
bool has_explicit_value_ : 1;
mutable bool is_value_up_to_date_ : 1;
bool not_in_list_ : 1;
};
DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutListItem, IsListItem());
......
......@@ -443,7 +443,7 @@ LayoutListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory()
}
bool LayoutListMarker::IsInside() const {
return list_item_->NotInList() ||
return list_item_->Ordinal().NotInList() ||
Style()->ListStylePosition() == EListStylePosition::kInside;
}
......
......@@ -23,9 +23,7 @@ class LayoutLIItem : public LayoutBoxItem {
LayoutLIItem() {}
void SetNotInList(bool not_in_list) {
return ToListItem()->SetNotInList(not_in_list);
}
ListItemOrdinal& Ordinal() { return ToListItem()->Ordinal(); }
private:
LayoutListItem* ToListItem() { return ToLayoutListItem(GetLayoutObject()); }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment