blob: df1a7ede8281ab2152ddef55899702c008748532 [file] [log] [blame]
// 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.
#include "third_party/blink/renderer/core/dom/whitespace_attacher.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
namespace blink {
WhitespaceAttacher::~WhitespaceAttacher() {
if (last_text_node_ && last_text_node_needs_reattach_)
ReattachWhitespaceSiblings(nullptr);
}
void WhitespaceAttacher::DidReattach(Node* node, LayoutObject* prev_in_flow) {
DCHECK(node);
DCHECK(node->IsTextNode() || node->IsElementNode());
// See Invariants in whitespace_attacher.h
DCHECK(!last_display_contents_ || !last_text_node_needs_reattach_);
ForceLastTextNodeNeedsReattach();
// No subsequent text nodes affected.
if (!last_text_node_)
return;
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object)
layout_object = prev_in_flow;
// Only in-flow boxes affect subsequent whitespace.
if (layout_object && !layout_object->IsFloatingOrOutOfFlowPositioned())
ReattachWhitespaceSiblings(layout_object);
}
void WhitespaceAttacher::DidReattachText(Text* text) {
DCHECK(text);
if (text->data().IsEmpty())
return;
DidReattach(text, text->GetLayoutObject());
SetLastTextNode(text);
if (!text->GetLayoutObject())
last_text_node_needs_reattach_ = true;
}
void WhitespaceAttacher::DidReattachElement(Element* element,
LayoutObject* prev_in_flow) {
DCHECK(element);
DidReattach(element, prev_in_flow);
}
void WhitespaceAttacher::DidVisitText(Text* text) {
DCHECK(text);
if (text->data().IsEmpty())
return;
if (!last_text_node_ || !last_text_node_needs_reattach_) {
SetLastTextNode(text);
if (reattach_all_whitespace_nodes_ && text->ContainsOnlyWhitespaceOrEmpty())
last_text_node_needs_reattach_ = true;
return;
}
// At this point we have a last_text_node_ which needs re-attachment.
// If last_text_node_needs_reattach_ is true, we traverse into
// display:contents elements to find the first preceding in-flow sibling, at
// which point we do the re-attachment (covered by LastTextNodeNeedsReattach()
// check in Element::NeedsRebuildLayoutTree()). DidVisitElement() below
// returns early for display:contents when last_text_node_needs_reattach_ is
// non-null.
DCHECK(!last_display_contents_);
if (LayoutObject* text_layout_object = text->GetLayoutObject()) {
ReattachWhitespaceSiblings(text_layout_object);
} else {
if (last_text_node_->ContainsOnlyWhitespaceOrEmpty())
last_text_node_->ReattachLayoutTreeIfNeeded(Node::AttachContext());
}
SetLastTextNode(text);
if (reattach_all_whitespace_nodes_ && text->ContainsOnlyWhitespaceOrEmpty())
last_text_node_needs_reattach_ = true;
}
void WhitespaceAttacher::DidVisitElement(Element* element) {
DCHECK(element);
LayoutObject* layout_object = element->GetLayoutObject();
if (!layout_object) {
// Don't set last_display_contents_ when we have a text node which needs to
// be re-attached. See the comments in DidVisitText() above.
if (last_text_node_needs_reattach_)
return;
if (element->HasDisplayContentsStyle())
last_display_contents_ = element;
return;
}
if (!last_text_node_ || !last_text_node_needs_reattach_) {
SetLastTextNode(nullptr);
return;
}
if (layout_object->IsFloatingOrOutOfFlowPositioned())
return;
ReattachWhitespaceSiblings(layout_object);
}
void WhitespaceAttacher::ReattachWhitespaceSiblings(
LayoutObject* previous_in_flow) {
DCHECK(!last_display_contents_);
DCHECK(last_text_node_);
DCHECK(last_text_node_needs_reattach_);
ScriptForbiddenScope forbid_script;
Node::AttachContext context;
context.previous_in_flow = previous_in_flow;
context.use_previous_in_flow = true;
for (Node* sibling = last_text_node_; sibling;
sibling = LayoutTreeBuilderTraversal::NextLayoutSibling(*sibling)) {
LayoutObject* sibling_layout_object = sibling->GetLayoutObject();
if (sibling->IsTextNode() &&
ToText(sibling)->ContainsOnlyWhitespaceOrEmpty()) {
bool had_layout_object = !!sibling_layout_object;
ToText(sibling)->ReattachLayoutTreeIfNeeded(context);
sibling_layout_object = sibling->GetLayoutObject();
// If sibling's layout object status didn't change we don't need to
// continue checking other siblings since their layout object status
// won't change either.
if (!!sibling_layout_object == had_layout_object)
break;
if (sibling_layout_object)
context.previous_in_flow = sibling_layout_object;
} else if (sibling_layout_object &&
!sibling_layout_object->IsFloatingOrOutOfFlowPositioned()) {
break;
}
}
SetLastTextNode(nullptr);
}
void WhitespaceAttacher::ForceLastTextNodeNeedsReattach() {
// If an element got re-attached, the need for a subsequent whitespace node
// LayoutObject may have changed. Make sure we try a re-attach when we
// encounter the next in-flow.
if (last_text_node_needs_reattach_)
return;
if (last_display_contents_)
UpdateLastTextNodeFromDisplayContents();
if (last_text_node_)
last_text_node_needs_reattach_ = true;
}
void WhitespaceAttacher::UpdateLastTextNodeFromDisplayContents() {
DCHECK(last_display_contents_);
DCHECK(last_display_contents_->HasDisplayContentsStyle());
Element* contents_element = last_display_contents_.Release();
Node* sibling =
LayoutTreeBuilderTraversal::FirstLayoutChild(*contents_element);
if (!sibling)
sibling = LayoutTreeBuilderTraversal::NextLayoutSibling(*contents_element);
if (!sibling) {
DCHECK(!last_text_node_);
return;
}
DCHECK(!sibling->IsElementNode() ||
!ToElement(sibling)->HasDisplayContentsStyle());
for (; sibling && sibling != last_text_node_;
sibling = LayoutTreeBuilderTraversal::NextLayoutSibling(*sibling)) {
LayoutObject* layout_object = sibling->GetLayoutObject();
if (sibling->IsTextNode()) {
Text* text = ToText(sibling);
if (text->ContainsOnlyWhitespaceOrEmpty()) {
last_text_node_ = text;
return;
}
}
if (layout_object && !layout_object->IsFloatingOrOutOfFlowPositioned()) {
last_text_node_ = nullptr;
break;
}
}
}
} // namespace blink