| /* |
| * Copyright (C) 2008 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/core/layout/line/line_box_list.h" |
| |
| #include "third_party/blink/renderer/core/layout/api/line_layout_box.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_box_model.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_item.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/line/inline_text_box.h" |
| #include "third_party/blink/renderer/core/layout/line/root_inline_box.h" |
| #include "third_party/blink/renderer/core/paint/inline_painter.h" |
| #include "third_party/blink/renderer/core/paint/paint_info.h" |
| |
| namespace blink { |
| |
| #if DCHECK_IS_ON() |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::AssertIsEmpty() { |
| DCHECK(!first_); |
| DCHECK(!last_); |
| } |
| #endif |
| |
| const LineBoxList& LineBoxList::Empty() { |
| // Need to use "static" because DISALLOW_NEW. |
| static LineBoxList empty; |
| return empty; |
| } |
| |
| const InlineTextBoxList& InlineTextBoxList::Empty() { |
| // Need to use "static" because DISALLOW_NEW. |
| static InlineTextBoxList empty; |
| return empty; |
| } |
| |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::AppendLineBox(InlineBoxType* box) { |
| if (!first_) { |
| first_ = last_ = box; |
| } else { |
| last_->SetNextForSameLayoutObject(box); |
| box->SetPreviousForSameLayoutObject(last_); |
| last_ = box; |
| } |
| } |
| |
| void LineBoxList::DeleteLineBoxTree() { |
| InlineFlowBox* line = first_; |
| InlineFlowBox* next_line; |
| while (line) { |
| next_line = line->NextForSameLayoutObject(); |
| line->DeleteLine(); |
| line = next_line; |
| } |
| first_ = last_ = nullptr; |
| } |
| |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::ExtractLineBox(InlineBoxType* box) { |
| last_ = box->PrevForSameLayoutObject(); |
| if (box == first_) |
| first_ = nullptr; |
| if (box->PrevForSameLayoutObject()) |
| box->PrevForSameLayoutObject()->SetNextForSameLayoutObject(nullptr); |
| box->SetPreviousForSameLayoutObject(nullptr); |
| for (InlineBoxType* curr = box; curr; curr = curr->NextForSameLayoutObject()) |
| curr->SetExtracted(); |
| } |
| |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::AttachLineBox(InlineBoxType* box) { |
| if (last_) { |
| last_->SetNextForSameLayoutObject(box); |
| box->SetPreviousForSameLayoutObject(last_); |
| } else { |
| first_ = box; |
| } |
| InlineBoxType* last = box; |
| for (InlineBoxType* curr = box; curr; |
| curr = curr->NextForSameLayoutObject()) { |
| curr->SetExtracted(false); |
| last = curr; |
| } |
| last_ = last; |
| } |
| |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::RemoveLineBox(InlineBoxType* box) { |
| if (box == first_) |
| first_ = box->NextForSameLayoutObject(); |
| if (box == last_) |
| last_ = box->PrevForSameLayoutObject(); |
| if (InlineBoxType* next = box->NextForSameLayoutObject()) |
| next->SetPreviousForSameLayoutObject(box->PrevForSameLayoutObject()); |
| if (InlineBoxType* prev = box->PrevForSameLayoutObject()) |
| prev->SetNextForSameLayoutObject(box->NextForSameLayoutObject()); |
| } |
| |
| template <typename InlineBoxType> |
| void InlineBoxList<InlineBoxType>::DeleteLineBoxes() { |
| if (first_) { |
| InlineBoxType* next; |
| for (InlineBoxType* curr = first_; curr; curr = next) { |
| next = curr->NextForSameLayoutObject(); |
| curr->Destroy(); |
| } |
| first_ = nullptr; |
| last_ = nullptr; |
| } |
| } |
| |
| void LineBoxList::DirtyLineBoxes() { |
| for (InlineFlowBox* curr : *this) |
| curr->DirtyLineBoxes(); |
| } |
| |
| bool LineBoxList::RangeIntersectsRect(LineLayoutBoxModel layout_object, |
| LayoutUnit logical_top, |
| LayoutUnit logical_bottom, |
| const CullRect& cull_rect, |
| const LayoutPoint& offset) const { |
| LineLayoutBox block; |
| if (layout_object.IsBox()) |
| block = LineLayoutBox(layout_object); |
| else |
| block = layout_object.ContainingBlock(); |
| LayoutUnit physical_start = block.FlipForWritingMode(logical_top); |
| LayoutUnit physical_end = block.FlipForWritingMode(logical_bottom); |
| LayoutUnit physical_extent = AbsoluteValue(physical_end - physical_start); |
| physical_start = std::min(physical_start, physical_end); |
| |
| if (layout_object.StyleRef().IsHorizontalWritingMode()) { |
| physical_start += offset.Y(); |
| return cull_rect.IntersectsVerticalRange(physical_start, |
| physical_start + physical_extent); |
| } else { |
| physical_start += offset.X(); |
| return cull_rect.IntersectsHorizontalRange( |
| physical_start, physical_start + physical_extent); |
| } |
| } |
| |
| bool LineBoxList::AnyLineIntersectsRect(LineLayoutBoxModel layout_object, |
| const CullRect& cull_rect, |
| const LayoutPoint& offset) const { |
| // We can check the first box and last box and avoid painting/hit testing if |
| // we don't intersect. This is a quick short-circuit that we can take to avoid |
| // walking any lines. |
| // FIXME: This check is flawed in the following extremely obscure way: |
| // if some line in the middle has a huge overflow, it might actually extend |
| // below the last line. |
| RootInlineBox& first_root_box = First()->Root(); |
| RootInlineBox& last_root_box = Last()->Root(); |
| LayoutUnit first_line_top = |
| First()->LogicalTopVisualOverflow(first_root_box.LineTop()); |
| LayoutUnit last_line_bottom = |
| Last()->LogicalBottomVisualOverflow(last_root_box.LineBottom()); |
| |
| return RangeIntersectsRect(layout_object, first_line_top, last_line_bottom, |
| cull_rect, offset); |
| } |
| |
| bool LineBoxList::LineIntersectsDirtyRect(LineLayoutBoxModel layout_object, |
| InlineFlowBox* box, |
| const CullRect& cull_rect, |
| const LayoutPoint& offset) const { |
| RootInlineBox& root = box->Root(); |
| LayoutUnit logical_top = std::min<LayoutUnit>( |
| box->LogicalTopVisualOverflow(root.LineTop()), root.SelectionTop()); |
| LayoutUnit logical_bottom = |
| box->LogicalBottomVisualOverflow(root.LineBottom()); |
| |
| return RangeIntersectsRect(layout_object, logical_top, logical_bottom, |
| cull_rect, offset); |
| } |
| |
| bool LineBoxList::HitTest(LineLayoutBoxModel layout_object, |
| HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction hit_test_action) const { |
| if (hit_test_action != kHitTestForeground) |
| return false; |
| |
| // The only way an inline could hit test like this is if it has a layer. |
| DCHECK(layout_object.IsLayoutBlock() || |
| (layout_object.IsLayoutInline() && layout_object.HasLayer())); |
| |
| // If we have no lines then we have no work to do. |
| if (!First()) |
| return false; |
| |
| const LayoutPoint& point = location_in_container.Point(); |
| IntRect hit_search_bounding_box = location_in_container.EnclosingIntRect(); |
| |
| CullRect cull_rect( |
| First()->IsHorizontal() |
| ? IntRect(point.X().ToInt(), hit_search_bounding_box.Y(), 1, |
| hit_search_bounding_box.Height()) |
| : IntRect(hit_search_bounding_box.X(), point.Y().ToInt(), |
| hit_search_bounding_box.Width(), 1)); |
| |
| if (!AnyLineIntersectsRect(layout_object, cull_rect, accumulated_offset)) |
| return false; |
| |
| // See if our root lines contain the point. If so, then we hit test them |
| // further. Note that boxes can easily overlap, so we can't make any |
| // assumptions based off positions of our first line box or our last line box. |
| for (InlineFlowBox* curr : InReverseOrder()) { |
| RootInlineBox& root = curr->Root(); |
| if (RangeIntersectsRect( |
| layout_object, curr->LogicalTopVisualOverflow(root.LineTop()), |
| curr->LogicalBottomVisualOverflow(root.LineBottom()), cull_rect, |
| accumulated_offset)) { |
| bool inside = |
| curr->NodeAtPoint(result, location_in_container, accumulated_offset, |
| root.LineTop(), root.LineBottom()); |
| if (inside) { |
| layout_object.UpdateHitTestResult( |
| result, |
| location_in_container.Point() - ToLayoutSize(accumulated_offset)); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| void LineBoxList::DirtyLinesFromChangedChild(LineLayoutItem container, |
| LineLayoutItem child, |
| bool can_dirty_ancestors) { |
| if (!container.Parent() || |
| (container.IsLayoutBlock() && |
| (container.SelfNeedsLayout() || !container.IsLayoutBlockFlow()))) |
| return; |
| |
| LineLayoutInline inline_container = container.IsLayoutInline() |
| ? LineLayoutInline(container) |
| : LineLayoutInline(); |
| |
| // If we are attaching children dirtying lines is unnecessary as we will do a |
| // full layout of the inline's contents anyway. |
| if (inline_container && inline_container.GetNode() && |
| inline_container.GetNode()->NeedsAttach()) |
| return; |
| |
| InlineBox* first_box = inline_container |
| ? inline_container.FirstLineBoxIncludingCulling() |
| : First(); |
| |
| // If we have no first line box, then just bail early. |
| if (!first_box) { |
| // For an empty inline, go ahead and propagate the check up to our parent, |
| // unless the parent is already dirty. |
| if (container.IsInline() && !container.AncestorLineBoxDirty() && |
| can_dirty_ancestors) { |
| container.Parent().DirtyLinesFromChangedChild(container); |
| // Mark the container to avoid dirtying the same lines again across |
| // multiple destroy() calls of the same subtree. |
| container.SetAncestorLineBoxDirty(); |
| } |
| return; |
| } |
| |
| // Try to figure out which line box we belong in. First try to find a previous |
| // line box by examining our siblings. If we are a float inside an inline then |
| // check our nearest inline ancestor with siblings. If we didn't find a line |
| // box, then use our parent's first line box. |
| RootInlineBox* box = nullptr; |
| LineLayoutItem curr = child.PreviousSibling(); |
| if (child.IsFloating() && !curr) { |
| DCHECK(child.Parent()); |
| if (child.Parent().IsLayoutInline()) { |
| LineLayoutItem outer_inline = child.Parent(); |
| while (outer_inline && !outer_inline.PreviousSibling() && |
| outer_inline.Parent().IsLayoutInline()) |
| outer_inline = outer_inline.Parent(); |
| if (outer_inline) |
| curr = outer_inline.PreviousSibling(); |
| } |
| } |
| |
| for (; curr; curr = curr.PreviousSibling()) { |
| if (curr.IsFloatingOrOutOfFlowPositioned()) |
| continue; |
| |
| if (curr.IsAtomicInlineLevel()) { |
| InlineBox* wrapper = LineLayoutBox(curr).InlineBoxWrapper(); |
| if (wrapper) |
| box = &wrapper->Root(); |
| } else if (curr.IsText()) { |
| InlineTextBox* text_box = LineLayoutText(curr).LastTextBox(); |
| if (text_box) |
| box = &text_box->Root(); |
| } else if (curr.IsLayoutInline()) { |
| InlineBox* last_sibling_box = |
| LineLayoutInline(curr).LastLineBoxIncludingCulling(); |
| if (last_sibling_box) |
| box = &last_sibling_box->Root(); |
| } |
| |
| if (box) |
| break; |
| } |
| if (!box) { |
| if (inline_container && !inline_container.AlwaysCreateLineBoxes()) { |
| // https://bugs.webkit.org/show_bug.cgi?id=60778 |
| // We may have just removed a <br> with no line box that was our first |
| // child. In this case we won't find a previous sibling, but firstBox can |
| // be pointing to a following sibling. This isn't good enough, since we |
| // won't locate the root line box that encloses the removed <br>. We have |
| // to just over-invalidate a bit and go up to our parent. |
| if (!inline_container.AncestorLineBoxDirty() && can_dirty_ancestors) { |
| inline_container.Parent().DirtyLinesFromChangedChild(inline_container); |
| // Mark the container to avoid dirtying the same lines again across |
| // multiple destroy() calls of the same subtree. |
| inline_container.SetAncestorLineBoxDirty(); |
| } |
| return; |
| } |
| box = &first_box->Root(); |
| } |
| |
| // If we found a line box, then dirty it. |
| if (box) { |
| box->MarkDirty(); |
| |
| // dirty the adjacent lines that might be affected |
| // NOTE: we dirty the previous line because RootInlineBox objects cache |
| // the address of the first object on the next line after a BR, which we may |
| // be invalidating here. For more info, see how LayoutBlock:: |
| // layoutInlineChildren calls setLineBreakInfo with the result of |
| // findNextLineBreak. findNextLineBreak, despite the name, actually returns |
| // the first LayoutObject after the BR. <rdar://problem/3849947> "Typing |
| // after pasting line does not appear until after window resize." |
| if (RootInlineBox* prev_root_box = box->PrevRootBox()) |
| prev_root_box->MarkDirty(); |
| // If |child| or any of its immediately previous siblings with culled |
| // lineboxes is the object after a line-break in |box| or the linebox after |
| // it then that means |child| actually sits on the linebox after |box| (or |
| // is its line-break object) and so we need to dirty it as well. |
| if (RootInlineBox* next_root_box = box->NextRootBox()) |
| next_root_box->MarkDirty(); |
| } |
| } |
| |
| template class InlineBoxList<InlineFlowBox>; |
| template class InlineBoxList<InlineTextBox>; |
| |
| } // namespace blink |