blob: bc28e972d5aba743c58880c43cb1d670e0297e5c [file] [log] [blame]
/*
* Copyright (C) 1997 Martin Jones (mjones@kde.org)
* (C) 1997 Torben Weis (weis@kde.org)
* (C) 1998 Waldo Bastian (bastian@kde.org)
* (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2009, 2013 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.
*/
#ifndef LayoutTableSection_h
#define LayoutTableSection_h
#include "core/CoreExport.h"
#include "core/layout/LayoutTable.h"
#include "wtf/Vector.h"
namespace blink {
// This variable is used to balance the memory consumption vs the paint invalidation time on big tables.
const float gMaxAllowedOverflowingCellRatioForFastPaintPath = 0.1f;
enum CollapsedBorderSide {
CBSBefore,
CBSAfter,
CBSStart,
CBSEnd
};
// Helper class for paintObject.
class CellSpan {
STACK_ALLOCATED();
public:
CellSpan(unsigned start, unsigned end)
: m_start(start)
, m_end(end)
{
}
unsigned start() const { return m_start; }
unsigned end() const { return m_end; }
void decreaseStart() { --m_start; }
void increaseEnd() { ++m_end; }
void ensureConsistency(const unsigned);
private:
unsigned m_start;
unsigned m_end;
};
class LayoutTableCell;
class LayoutTableRow;
// LayoutTableSection is used to represent table row group (display:
// table-row-group), header group (display: table-header-group) and footer group
// (display: table-footer-group).
//
// The object holds the internal representation of the rows (m_grid). See
// recalcCells() below for some extra explanation.
//
// A lot of the complexity in this class is related to handling rowspan, colspan
// or just non-regular tables.
//
// Example of rowspan / colspan leading to overlapping cells (rowspan and
// colspan are overlapping):
// <table>
// <tr>
// <td>first row</td>
// <td rowspan="2">rowspan</td>
// </tr>
// <tr>
// <td colspan="2">colspan</td>
// </tr>
// </table>
//
// Example of non-regular table (missing one cell in the first row):
// <!DOCTYPE html>
// <table>
// <tr><td>First row only child.</td></tr>
// <tr>
// <td>Second row first child</td>
// <td>Second row second child</td>
// </tr>
// </table>
//
// LayoutTableSection is responsible for laying out LayoutTableRows and
// LayoutTableCells (see layoutRows()). However it is not their containing
// block, the enclosing LayoutTable (this object's parent()) is. This is why
// this class inherits from LayoutBox and not LayoutBlock.
class CORE_EXPORT LayoutTableSection final : public LayoutBox {
public:
LayoutTableSection(Element*);
~LayoutTableSection() override;
LayoutTableRow* firstRow() const;
LayoutTableRow* lastRow() const;
const LayoutObjectChildList* children() const { return &m_children; }
LayoutObjectChildList* children() { return &m_children; }
void addChild(LayoutObject* child, LayoutObject* beforeChild = nullptr) override;
int firstLineBoxBaseline() const override;
void addCell(LayoutTableCell*, LayoutTableRow*);
int calcRowLogicalHeight();
void layoutRows();
void computeOverflowFromCells();
LayoutTable* table() const { return toLayoutTable(parent()); }
typedef Vector<LayoutTableCell*, 2> SpanningLayoutTableCells;
// CellStruct represents the cells that occupy an (N, M) position in the
// table grid.
struct CellStruct {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
// All the cells that fills this grid "slot".
// Due to colspan / rowpsan, it is possible to have overlapping cells
// (see class comment about an example).
// This Vector is sorted in DOM order.
Vector<LayoutTableCell*, 1> cells;
bool inColSpan; // true for columns after the first in a colspan
CellStruct()
: inColSpan(false)
{
}
// This is the cell in the grid "slot" that is on top of the others
// (aka the last cell in DOM order for this slot).
//
// This is the cell originating from this slot if it exists.
//
// The concept of a primary cell is dubious at most as it doesn't
// correspond to a DOM or rendering concept. Also callers should be
// careful about assumptions about it. For example, even though the
// primary cell is visibly the top most, it is not guaranteed to be
// the only one visible for this slot due to different visual
// overflow rectangles.
LayoutTableCell* primaryCell()
{
return hasCells() ? cells[cells.size() - 1] : 0;
}
const LayoutTableCell* primaryCell() const
{
return hasCells() ? cells[cells.size() - 1] : 0;
}
bool hasCells() const { return cells.size() > 0; }
};
// The index is effective column index.
typedef Vector<CellStruct> Row;
struct RowStruct {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
RowStruct()
: rowLayoutObject(nullptr)
, baseline(-1)
{
}
Row row;
LayoutTableRow* rowLayoutObject;
int baseline;
Length logicalHeight;
};
struct SpanningRowsHeight {
STACK_ALLOCATED();
WTF_MAKE_NONCOPYABLE(SpanningRowsHeight);
public:
SpanningRowsHeight()
: totalRowsHeight(0)
, spanningCellHeightIgnoringBorderSpacing(0)
, isAnyRowWithOnlySpanningCells(false)
{
}
Vector<int> rowHeight;
int totalRowsHeight;
int spanningCellHeightIgnoringBorderSpacing;
bool isAnyRowWithOnlySpanningCells;
};
const BorderValue& borderAdjoiningTableStart() const
{
if (hasSameDirectionAs(table()))
return style()->borderStart();
return style()->borderEnd();
}
const BorderValue& borderAdjoiningTableEnd() const
{
if (hasSameDirectionAs(table()))
return style()->borderEnd();
return style()->borderStart();
}
const BorderValue& borderAdjoiningStartCell(const LayoutTableCell*) const;
const BorderValue& borderAdjoiningEndCell(const LayoutTableCell*) const;
const LayoutTableCell* firstRowCellAdjoiningTableStart() const;
const LayoutTableCell* firstRowCellAdjoiningTableEnd() const;
CellStruct& cellAt(unsigned row, unsigned effectiveColumn) { return m_grid[row].row[effectiveColumn]; }
const CellStruct& cellAt(unsigned row, unsigned effectiveColumn) const { return m_grid[row].row[effectiveColumn]; }
LayoutTableCell* primaryCellAt(unsigned row, unsigned effectiveColumn)
{
CellStruct& c = m_grid[row].row[effectiveColumn];
return c.primaryCell();
}
const LayoutTableCell* primaryCellAt(unsigned row, unsigned effectiveColumn) const { return const_cast<LayoutTableSection*>(this)->primaryCellAt(row, effectiveColumn); }
LayoutTableRow* rowLayoutObjectAt(unsigned row) { return m_grid[row].rowLayoutObject; }
const LayoutTableRow* rowLayoutObjectAt(unsigned row) const { return m_grid[row].rowLayoutObject; }
void appendEffectiveColumn(unsigned pos);
void splitEffectiveColumn(unsigned pos, unsigned first);
enum BlockBorderSide { BorderBefore, BorderAfter };
int calcBlockDirectionOuterBorder(BlockBorderSide) const;
enum InlineBorderSide { BorderStart, BorderEnd };
int calcInlineDirectionOuterBorder(InlineBorderSide) const;
void recalcOuterBorder();
int outerBorderBefore() const { return m_outerBorderBefore; }
int outerBorderAfter() const { return m_outerBorderAfter; }
int outerBorderStart() const { return m_outerBorderStart; }
int outerBorderEnd() const { return m_outerBorderEnd; }
unsigned numRows() const { return m_grid.size(); }
unsigned numEffectiveColumns() const;
// recalcCells() is used when we are not sure about the section's structure
// and want to do an expensive (but safe) reconstruction of m_grid from
// scratch.
// An example of this is inserting a new cell in the middle of an existing
// row or removing a row.
//
// Accessing m_grid when m_needsCellRecalc is set is UNSAFE as pointers can
// be left dangling. Thus care should be taken in the code to check
// m_needsCellRecalc before accessing m_grid.
void recalcCells();
void recalcCellsIfNeeded()
{
if (m_needsCellRecalc)
recalcCells();
}
bool needsCellRecalc() const { return m_needsCellRecalc; }
void setNeedsCellRecalc();
int rowBaseline(unsigned row) { return m_grid[row].baseline; }
void rowLogicalHeightChanged(LayoutTableRow*);
void removeCachedCollapsedBorders(const LayoutTableCell*);
// Returns true if any collapsed borders of the cell changed.
bool setCachedCollapsedBorder(const LayoutTableCell*, CollapsedBorderSide, const CollapsedBorderValue&);
// Returns null if the border is not cached (there is no such collapsed border or the border is invisible).
const CollapsedBorderValue* cachedCollapsedBorder(const LayoutTableCell*, CollapsedBorderSide) const;
// distributeExtraLogicalHeightToRows methods return the *consumed* extra logical height.
// FIXME: We may want to introduce a structure holding the in-flux layout information.
int distributeExtraLogicalHeightToRows(int extraLogicalHeight);
static LayoutTableSection* createAnonymousWithParent(const LayoutObject*);
LayoutBox* createAnonymousBoxWithSameTypeAs(const LayoutObject* parent) const override
{
return createAnonymousWithParent(parent);
}
void paint(const PaintInfo&, const LayoutPoint&) const override;
// Flip the rect so it aligns with the coordinates used by the rowPos and columnPos vectors.
LayoutRect logicalRectForWritingModeAndDirection(const LayoutRect&) const;
CellSpan dirtiedRows(const LayoutRect& paintInvalidationRect) const;
CellSpan dirtiedEffectiveColumns(const LayoutRect& paintInvalidationRect) const;
const HashSet<LayoutTableCell*>& overflowingCells() const { return m_overflowingCells; }
bool hasMultipleCellLevels() const { return m_hasMultipleCellLevels; }
const char* name() const override { return "LayoutTableSection"; }
// Whether a section has opaque background depends on many factors, e.g. border spacing,
// border collapsing, missing cells, etc.
// For simplicity, just conservatively assume all table sections are not opaque.
bool foregroundIsKnownToBeOpaqueInRect(const LayoutRect&, unsigned) const override { return false; }
bool backgroundIsKnownToBeOpaqueInRect(const LayoutRect&) const override { return false; }
protected:
void styleDidChange(StyleDifference, const ComputedStyle* oldStyle) override;
bool nodeAtPoint(HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction) override;
private:
LayoutObjectChildList* virtualChildren() override { return children(); }
const LayoutObjectChildList* virtualChildren() const override { return children(); }
bool isOfType(LayoutObjectType type) const override { return type == LayoutObjectTableSection || LayoutBox::isOfType(type); }
void willBeRemovedFromTree() override;
void layout() override;
void imageChanged(WrappedImagePtr, const IntRect* = nullptr) override;
int borderSpacingForRow(unsigned row) const { return m_grid[row].rowLayoutObject ? table()->vBorderSpacing() : 0; }
void ensureRows(unsigned);
bool rowHasOnlySpanningCells(unsigned);
unsigned calcRowHeightHavingOnlySpanningCells(unsigned, int&, unsigned, unsigned&, Vector<int>&);
void updateRowsHeightHavingOnlySpanningCells(LayoutTableCell*, struct SpanningRowsHeight&, unsigned&, Vector<int>&);
void populateSpanningRowsHeightFromCell(LayoutTableCell*, struct SpanningRowsHeight&);
void distributeExtraRowSpanHeightToPercentRows(LayoutTableCell*, float, int&, Vector<int>&);
void distributeWholeExtraRowSpanHeightToPercentRows(LayoutTableCell*, float, int&, Vector<int>&);
void distributeExtraRowSpanHeightToAutoRows(LayoutTableCell*, int, int&, Vector<int>&);
void distributeExtraRowSpanHeightToRemainingRows(LayoutTableCell*, int, int&, Vector<int>&);
void distributeRowSpanHeightToRows(SpanningLayoutTableCells& rowSpanCells);
void distributeExtraLogicalHeightToPercentRows(int& extraLogicalHeight, int totalPercent);
void distributeExtraLogicalHeightToAutoRows(int& extraLogicalHeight, unsigned autoRowsCount);
void distributeRemainingExtraLogicalHeight(int& extraLogicalHeight);
void updateBaselineForCell(LayoutTableCell*, unsigned row, int& baselineDescent);
bool hasOverflowingCell() const { return m_overflowingCells.size() || m_forceSlowPaintPathWithOverflowingCell; }
void computeOverflowFromCells(unsigned totalRows, unsigned nEffCols);
CellSpan fullTableRowSpan() const { return CellSpan(0, m_grid.size()); }
CellSpan fullTableEffectiveColumnSpan() const { return CellSpan(0, table()->numEffectiveColumns()); }
// These two functions take a rectangle as input that has been flipped by logicalRectForWritingModeAndDirection.
// The returned span of rows or columns is end-exclusive, and empty if start==end.
CellSpan spannedRows(const LayoutRect& flippedRect) const;
CellSpan spannedEffectiveColumns(const LayoutRect& flippedRect) const;
void setLogicalPositionForCell(LayoutTableCell*, unsigned effectiveColumn) const;
LayoutObjectChildList m_children;
// The representation of the rows and their cells (CellStruct).
Vector<RowStruct> m_grid;
// The logical offset of each row from the top of the section.
//
// Note that this Vector has one more entry than the number of rows so that
// we can keep track of the final size of the section
// (m_rowPos[m_grid.size() + 1]).
//
// To know a row's height at |rowIndex|, use the formula:
// m_rowPos[rowIndex + 1] - m_rowPos[rowIndex]
Vector<int> m_rowPos;
// The current insertion position in the grid.
// The position is used when inserting a new cell into the section to
// know where it should be inserted and expand our internal structure.
//
// The reason for them is that we process cells as we discover them
// during parsing or during recalcCells (ie in DOM order). This means
// that we can discover changes in the structure later (e.g. due to
// colspans, extra cells, ...).
//
// Do not use outside of recalcCells and addChild.
unsigned m_cCol;
unsigned m_cRow;
int m_outerBorderStart;
int m_outerBorderEnd;
int m_outerBorderBefore;
int m_outerBorderAfter;
bool m_needsCellRecalc;
// This HashSet holds the overflowing cells for faster painting.
// If we have more than gMaxAllowedOverflowingCellRatio * total cells, it will be empty
// and m_forceSlowPaintPathWithOverflowingCell will be set to save memory.
HashSet<LayoutTableCell*> m_overflowingCells;
bool m_forceSlowPaintPathWithOverflowingCell;
// This boolean tracks if we have cells overlapping due to rowspan / colspan
// (see class comment above about when it could appear).
//
// The use is to disable a painting optimization where we just paint the
// invalidated cells.
bool m_hasMultipleCellLevels;
// This map holds the collapsed border values for cells with collapsed borders.
// It is held at LayoutTableSection level to spare memory consumption by table cells.
// Invisible borders are never stored in this map.
using CellsCollapsedBordersMap = HashMap<std::pair<const LayoutTableCell*, int>, CollapsedBorderValue>;
CellsCollapsedBordersMap m_cellsCollapsedBorders;
};
DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutTableSection, isTableSection());
} // namespace blink
#endif // LayoutTableSection_h