| /* |
| * 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, 2007, 2008, 2009, 2010, 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. |
| */ |
| |
| #include "core/layout/LayoutTableRow.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutAnalyzer.h" |
| #include "core/layout/LayoutState.h" |
| #include "core/layout/LayoutTableCell.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/SubtreeLayoutScope.h" |
| #include "core/paint/TableRowPainter.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| LayoutTableRow::LayoutTableRow(Element* element) |
| : LayoutTableBoxComponent(element), row_index_(kUnsetRowIndex) { |
| // init LayoutObject attributes |
| SetInline(false); // our object is not Inline |
| } |
| |
| void LayoutTableRow::WillBeRemovedFromTree() { |
| LayoutTableBoxComponent::WillBeRemovedFromTree(); |
| |
| Section()->SetNeedsCellRecalc(); |
| } |
| |
| void LayoutTableRow::StyleDidChange(StyleDifference diff, |
| const ComputedStyle* old_style) { |
| DCHECK_EQ(Style()->Display(), EDisplay::kTableRow); |
| |
| LayoutTableBoxComponent::StyleDidChange(diff, old_style); |
| PropagateStyleToAnonymousChildren(); |
| |
| if (!old_style) |
| return; |
| |
| if (Section() && Style()->LogicalHeight() != old_style->LogicalHeight()) |
| Section()->RowLogicalHeightChanged(this); |
| |
| if (!Parent()) |
| return; |
| LayoutTable* table = this->Table(); |
| if (!table) |
| return; |
| |
| LayoutTableBoxComponent::InvalidateCollapsedBordersOnStyleChange( |
| *this, *table, diff, *old_style); |
| |
| if (LayoutTableBoxComponent::DoCellsHaveDirtyWidth(*this, *table, diff, |
| *old_style)) { |
| // If the border width changes on a row, we need to make sure the cells in |
| // the row know to lay out again. |
| // This only happens when borders are collapsed, since they end up affecting |
| // the border sides of the cell itself. |
| for (LayoutBox* child_box = FirstChildBox(); child_box; |
| child_box = child_box->NextSiblingBox()) { |
| if (!child_box->IsTableCell()) |
| continue; |
| // TODO(dgrogan) Add a layout test showing that setChildNeedsLayout is |
| // needed instead of setNeedsLayout. |
| child_box->SetChildNeedsLayout(); |
| child_box->SetPreferredLogicalWidthsDirty(kMarkOnlyThis); |
| } |
| // Most table componenents can rely on LayoutObject::styleDidChange |
| // to mark the container chain dirty. But LayoutTableSection seems |
| // to never clear its dirty bit, which stops the propagation. So |
| // anything under LayoutTableSection has to restart the propagation |
| // at the table. |
| // TODO(dgrogan): Make LayoutTableSection clear its dirty bit. |
| table->SetPreferredLogicalWidthsDirty(); |
| } |
| } |
| |
| const BorderValue& LayoutTableRow::BorderAdjoiningStartCell( |
| const LayoutTableCell* cell) const { |
| #if DCHECK_IS_ON() |
| DCHECK(cell->IsFirstOrLastCellInRow()); |
| #endif |
| // FIXME: https://webkit.org/b/79272 - Add support for mixed directionality at |
| // the cell level. |
| return Style()->BorderStart(); |
| } |
| |
| const BorderValue& LayoutTableRow::BorderAdjoiningEndCell( |
| const LayoutTableCell* cell) const { |
| #if DCHECK_IS_ON() |
| DCHECK(cell->IsFirstOrLastCellInRow()); |
| #endif |
| // FIXME: https://webkit.org/b/79272 - Add support for mixed directionality at |
| // the cell level. |
| return Style()->BorderEnd(); |
| } |
| |
| void LayoutTableRow::AddChild(LayoutObject* child, LayoutObject* before_child) { |
| if (!child->IsTableCell()) { |
| LayoutObject* last = before_child; |
| if (!last) |
| last = LastCell(); |
| if (last && last->IsAnonymous() && last->IsTableCell() && |
| !last->IsBeforeOrAfterContent()) { |
| LayoutTableCell* last_cell = ToLayoutTableCell(last); |
| if (before_child == last_cell) |
| before_child = last_cell->FirstChild(); |
| last_cell->AddChild(child, before_child); |
| return; |
| } |
| |
| if (before_child && !before_child->IsAnonymous() && |
| before_child->Parent() == this) { |
| LayoutObject* cell = before_child->PreviousSibling(); |
| if (cell && cell->IsTableCell() && cell->IsAnonymous()) { |
| cell->AddChild(child); |
| return; |
| } |
| } |
| |
| // If beforeChild is inside an anonymous cell, insert into the cell. |
| if (last && !last->IsTableCell() && last->Parent() && |
| last->Parent()->IsAnonymous() && |
| !last->Parent()->IsBeforeOrAfterContent()) { |
| last->Parent()->AddChild(child, before_child); |
| return; |
| } |
| |
| LayoutTableCell* cell = LayoutTableCell::CreateAnonymousWithParent(this); |
| AddChild(cell, before_child); |
| cell->AddChild(child); |
| return; |
| } |
| |
| if (before_child && before_child->Parent() != this) |
| before_child = SplitAnonymousBoxesAroundChild(before_child); |
| |
| LayoutTableCell* cell = ToLayoutTableCell(child); |
| |
| DCHECK(!before_child || before_child->IsTableCell()); |
| LayoutTableBoxComponent::AddChild(cell, before_child); |
| |
| // Generated content can result in us having a null section so make sure to |
| // null check our parent. |
| if (Parent()) { |
| Section()->AddCell(cell, this); |
| // When borders collapse, adding a cell can affect the the width of |
| // neighboring cells. |
| LayoutTable* enclosing_table = Table(); |
| if (enclosing_table && enclosing_table->CollapseBorders()) { |
| if (LayoutTableCell* previous_cell = cell->PreviousCell()) |
| previous_cell->SetNeedsLayoutAndPrefWidthsRecalc( |
| LayoutInvalidationReason::kTableChanged); |
| if (LayoutTableCell* next_cell = cell->NextCell()) |
| next_cell->SetNeedsLayoutAndPrefWidthsRecalc( |
| LayoutInvalidationReason::kTableChanged); |
| } |
| } |
| |
| if (before_child || NextRow()) |
| Section()->SetNeedsCellRecalc(); |
| } |
| |
| void LayoutTableRow::UpdateLayout() { |
| DCHECK(NeedsLayout()); |
| LayoutAnalyzer::Scope analyzer(*this); |
| bool paginated = View()->GetLayoutState()->IsPaginated(); |
| |
| for (LayoutTableCell* cell = FirstCell(); cell; cell = cell->NextCell()) { |
| SubtreeLayoutScope layouter(*cell); |
| cell->SetLogicalTop(LogicalTop()); |
| if (!cell->NeedsLayout()) |
| Section()->MarkChildForPaginationRelayoutIfNeeded(*cell, layouter); |
| if (cell->NeedsLayout()) |
| cell->UpdateLayout(); |
| if (paginated) |
| Section()->UpdateFragmentationInfoForChild(*cell); |
| } |
| |
| overflow_.reset(); |
| AddVisualEffectOverflow(); |
| // We do not call addOverflowFromCell here. The cell are laid out to be |
| // measured above and will be sized correctly in a follow-up phase. |
| |
| // We only ever need to issue paint invalidations if our cells didn't, which |
| // means that they didn't need layout, so we know that our bounds didn't |
| // change. This code is just making up for the fact that we did not invalidate |
| // paints in setStyle() because we had a layout hint. |
| if (SelfNeedsLayout()) { |
| for (LayoutTableCell* cell = FirstCell(); cell; cell = cell->NextCell()) { |
| // FIXME: Is this needed when issuing paint invalidations after layout? |
| cell->SetShouldDoFullPaintInvalidation(); |
| } |
| } |
| |
| // LayoutTableSection::layoutRows will set our logical height and width later, |
| // so it calls updateLayerTransform(). |
| ClearNeedsLayout(); |
| } |
| |
| // Hit Testing |
| bool LayoutTableRow::NodeAtPoint(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction action) { |
| // Table rows cannot ever be hit tested. Effectively they do not exist. |
| // Just forward to our children always. |
| for (LayoutTableCell* cell = LastCell(); cell; cell = cell->PreviousCell()) { |
| // FIXME: We have to skip over inline flows, since they can show up inside |
| // table rows at the moment (a demoted inline <form> for example). If we |
| // ever implement a table-specific hit-test method (which we should do for |
| // performance reasons anyway), then we can remove this check. |
| if (!cell->HasSelfPaintingLayer()) { |
| LayoutPoint cell_point = |
| FlipForWritingModeForChild(cell, accumulated_offset); |
| if (cell->NodeAtPoint(result, location_in_container, cell_point, |
| action)) { |
| UpdateHitTestResult( |
| result, location_in_container.Point() - ToLayoutSize(cell_point)); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| LayoutBox::PaginationBreakability LayoutTableRow::GetPaginationBreakability() |
| const { |
| PaginationBreakability breakability = |
| LayoutTableBoxComponent::GetPaginationBreakability(); |
| if (breakability == kAllowAnyBreaks) { |
| // Even if the row allows us to break inside, we will want to prevent that |
| // if we have a header group that wants to appear at the top of each page. |
| if (const LayoutTableSection* header = Table()->Header()) |
| breakability = header->GetPaginationBreakability(); |
| } |
| return breakability; |
| } |
| |
| void LayoutTableRow::Paint(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) const { |
| TableRowPainter(*this).Paint(paint_info, paint_offset); |
| } |
| |
| LayoutTableRow* LayoutTableRow::CreateAnonymous(Document* document) { |
| LayoutTableRow* layout_object = new LayoutTableRow(nullptr); |
| layout_object->SetDocumentForAnonymous(document); |
| return layout_object; |
| } |
| |
| LayoutTableRow* LayoutTableRow::CreateAnonymousWithParent( |
| const LayoutObject* parent) { |
| LayoutTableRow* new_row = |
| LayoutTableRow::CreateAnonymous(&parent->GetDocument()); |
| RefPtr<ComputedStyle> new_style = |
| ComputedStyle::CreateAnonymousStyleWithDisplay(parent->StyleRef(), |
| EDisplay::kTableRow); |
| new_row->SetStyle(std::move(new_style)); |
| return new_row; |
| } |
| |
| void LayoutTableRow::ComputeOverflow() { |
| ClearAllOverflows(); |
| AddVisualEffectOverflow(); |
| for (LayoutTableCell* cell = FirstCell(); cell; cell = cell->NextCell()) |
| AddOverflowFromCell(cell); |
| } |
| |
| void LayoutTableRow::AddOverflowFromCell(const LayoutTableCell* cell) { |
| // Non-row-spanning-cells don't create overflow (they are fully contained |
| // within this row). |
| // TODO(crbug.com/603993): This seems incorrect because cell may have visual |
| // effect overflow that should be included in this row. |
| if (cell->RowSpan() == 1) |
| return; |
| |
| // Cells only generates visual overflow. |
| LayoutRect cell_visual_overflow_rect = |
| cell->VisualOverflowRectForPropagation(StyleRef()); |
| |
| // The cell and the row share the section's coordinate system. However |
| // the visual overflow should be determined in the coordinate system of |
| // the row, that's why we shift it below. |
| cell_visual_overflow_rect.MoveBy(-Location()); |
| AddContentsVisualOverflow(cell_visual_overflow_rect); |
| |
| // Table row paints its background behind cells. If the cell spans multiple |
| // rows, the row's visual rect should be expanded to cover the cell. |
| if (StyleRef().HasBackground()) { |
| LayoutRect cell_background_rect = cell->FrameRect(); |
| cell_background_rect.MoveBy(-Location()); |
| AddSelfVisualOverflow(cell_background_rect); |
| } |
| } |
| |
| bool LayoutTableRow::IsFirstRowInSectionAfterHeader() const { |
| // If there isn't room on the page for at least one content row after the |
| // header group, then we won't repeat the header on each page. |
| // https://drafts.csswg.org/css-tables-3/#repeated-headers reads like |
| // it wants us to drop headers on only the pages that a single row |
| // won't fit but we avoid the complexity of that reading until it |
| // is clarified. Tracked by crbug.com/675904 |
| if (RowIndex()) |
| return false; |
| LayoutTableSection* header = Table()->Header(); |
| return header && Table()->SectionAbove(Section()) == header && |
| header->GetPaginationBreakability() != kAllowAnyBreaks; |
| } |
| |
| } // namespace blink |