bool Text::textLayoutObjectIsNeeded(const ComputedStyle& style, const LayoutObject& parent) { if (!parent.canHaveChildren()) return false; if (isEditingText()) return true; if (!length()) return false; if (style.display() == NONE) return false; if (!containsOnlyWhitespace()) return true; if (!canHaveWhitespaceChildren(parent, this)) return false; // pre-wrap in SVG never makes layoutObject. if (style.whiteSpace() == PRE_WRAP && parent.isSVG()) return false; // pre/pre-wrap/-bb-pre-wrap-text/pre-line always make layoutObjects. if (style.preserveNewline()) return true; // childNeedsDistributionRecalc() here is rare, only happens JS calling surroundContents() etc. from DOMNodeInsertedIntoDocument etc. if (document().childNeedsDistributionRecalc()) return true; const LayoutObject* prev = LayoutTreeBuilderTraversal::previousSiblingLayoutObject(*this); if (prev && prev->isBR()) // <span><br/> <br/></span> return false; if (parent.isLayoutInline()) { // <span><div/> <div/></span> if (prev && !prev->isInline() && !prev->isOutOfFlowPositioned()) return false; } else { if (parent.isLayoutBlock() && !parent.childrenInline() && (!prev || !prev->isInline())) return false; // Avoiding creation of a layoutObject for the text node is a non-essential memory optimization. // So to avoid blowing up on very wide DOMs, we limit the number of siblings to visit. unsigned maxSiblingsToVisit = 50; LayoutObject* first = parent.slowFirstChild(); while (first && first->isFloatingOrOutOfFlowPositioned() && maxSiblingsToVisit--) first = first->nextSibling(); if (!first || first == layoutObject() || LayoutTreeBuilderTraversal::nextSiblingLayoutObject(*this) == first) { // Whitespace at the start of a block just goes away. Don't even // make a layout object for this text. return false; } } return true; }
float TextAutosizer::inflate(LayoutObject* parent, InflateBehavior behavior, float multiplier) { Cluster* cluster = currentCluster(); bool hasTextChild = false; LayoutObject* child = nullptr; if (parent->isLayoutBlock() && (parent->childrenInline() || behavior == DescendToInnerBlocks)) child = toLayoutBlock(parent)->firstChild(); else if (parent->isLayoutInline()) child = toLayoutInline(parent)->firstChild(); while (child) { if (child->isText()) { hasTextChild = true; // We only calculate this multiplier on-demand to ensure the parent block of this text // has entered layout. if (!multiplier) multiplier = cluster->m_flags & SUPPRESSING ? 1.0f : clusterMultiplier(cluster); applyMultiplier(child, multiplier); // FIXME: Investigate why MarkOnlyThis is sufficient. if (parent->isLayoutInline()) child->setPreferredLogicalWidthsDirty(MarkOnlyThis); } else if (child->isLayoutInline()) { multiplier = inflate(child, behavior, multiplier); } else if (child->isLayoutBlock() && behavior == DescendToInnerBlocks && !classifyBlock(child, INDEPENDENT | EXPLICIT_WIDTH | SUPPRESSING)) { multiplier = inflate(child, behavior, multiplier); } child = child->nextSibling(); } if (hasTextChild) { applyMultiplier(parent, multiplier); // Parent handles line spacing. } else if (!parent->isListItem()) { // For consistency, a block with no immediate text child should always have a // multiplier of 1. applyMultiplier(parent, 1); } if (parent->isListItem()) { float multiplier = clusterMultiplier(cluster); applyMultiplier(parent, multiplier); // The list item has to be treated special because we can have a tree such that you have // a list item for a form inside it. The list marker then ends up inside the form and when // we try to get the clusterMultiplier we have the wrong cluster root to work from and get // the wrong value. LayoutListItem* item = toLayoutListItem(parent); if (LayoutListMarker* marker = item->marker()) { applyMultiplier(marker, multiplier); marker->setPreferredLogicalWidthsDirty(MarkOnlyThis); } } return multiplier; }
TEST_F(PaintContainmentTest, InlinePaintContainment) { setBodyInnerHTML("<div><span id='test' style='contain: paint'>Foo</span></div>"); Element* span = document().getElementById(AtomicString("test")); ASSERT(span); // The inline should have been coerced into a block in StyleAdjuster. LayoutObject* obj = span->layoutObject(); ASSERT(obj && obj->isLayoutBlock()); LayoutBlock& layoutBlock = toLayoutBlock(*obj); checkIsClippingStackingContextAndContainer(layoutBlock); }
TEST_F(PaintContainmentTest, BlockPaintContainment) { setBodyInnerHTML("<div id='div' style='contain: paint'></div>"); Element* div = document().getElementById(AtomicString("div")); ASSERT(div); LayoutObject* obj = div->layoutObject(); ASSERT(obj && obj->isLayoutBlock()); LayoutBlock& block = toLayoutBlock(*obj); EXPECT_TRUE(block.createsNewFormattingContext()); EXPECT_FALSE(block.canBeScrolledAndHasScrollableArea()); checkIsClippingStackingContextAndContainer(block); }
LayoutBlock* CaretBase::caretLayoutObject(Node* node) { if (!node) return nullptr; LayoutObject* layoutObject = node->layoutObject(); if (!layoutObject) return nullptr; // if caretNode is a block and caret is inside it then caret should be painted by that block bool paintedByBlock = layoutObject->isLayoutBlock() && caretRendersInsideNode(node); return paintedByBlock ? toLayoutBlock(layoutObject) : layoutObject->containingBlock(); }
bool TextAutosizer::clusterHasEnoughTextToAutosize(Cluster* cluster, const LayoutBlock* widthProvider) { if (cluster->m_hasEnoughTextToAutosize != UnknownAmountOfText) return cluster->m_hasEnoughTextToAutosize == HasEnoughText; const LayoutBlock* root = cluster->m_root; if (!widthProvider) widthProvider = clusterWidthProvider(root); // TextAreas and user-modifiable areas get a free pass to autosize regardless of text content. if (root->isTextArea() || (root->style() && root->style()->userModify() != READ_ONLY)) { cluster->m_hasEnoughTextToAutosize = HasEnoughText; return true; } if (cluster->m_flags & SUPPRESSING) { cluster->m_hasEnoughTextToAutosize = NotEnoughText; return false; } // 4 lines of text is considered enough to autosize. float minimumTextLengthToAutosize = widthFromBlock(widthProvider) * 4; float length = 0; LayoutObject* descendant = root->firstChild(); while (descendant) { if (descendant->isLayoutBlock()) { if (classifyBlock(descendant, INDEPENDENT | SUPPRESSING)) { descendant = descendant->nextInPreOrderAfterChildren(root); continue; } } else if (descendant->isText()) { // Note: Using text().stripWhiteSpace().length() instead of resolvedTextLength() because // the lineboxes will not be built until layout. These values can be different. // Note: This is an approximation assuming each character is 1em wide. length += toLayoutText(descendant)->text().stripWhiteSpace().length() * descendant->style()->specifiedFontSize(); if (length >= minimumTextLengthToAutosize) { cluster->m_hasEnoughTextToAutosize = HasEnoughText; return true; } } descendant = descendant->nextInPreOrder(root); } cluster->m_hasEnoughTextToAutosize = NotEnoughText; return false; }
LayoutBlock* CaretBase::caretLayoutObject(Node* node) { if (!node) return nullptr; LayoutObject* layoutObject = node->layoutObject(); if (!layoutObject) return nullptr; // if caretNode is a block and caret is inside it then caret should be painted // by that block bool paintedByBlock = layoutObject->isLayoutBlock() && caretRendersInsideNode(node); // TODO(yoichio): This function is called at least // DocumentLifeCycle::LayoutClean but caretRendersInsideNode above can // layout. Thus |node->layoutObject()| can be changed then this is bad // design. We should make caret painting algorithm clean. CHECK_EQ(layoutObject, node->layoutObject()) << "Layout tree should not changed"; return paintedByBlock ? toLayoutBlock(layoutObject) : layoutObject->containingBlock(); }