int LayoutTextControl::firstLineBoxBaseline() const { int result = LayoutBlock::firstLineBoxBaseline(); if (result != -1) return result; // When the text is empty, |LayoutBlock::firstLineBoxBaseline()| cannot // compute the baseline because lineboxes do not exist. Element* innerEditor = innerEditorElement(); if (!innerEditor || !innerEditor->layoutObject()) return -1; LayoutBlock* innerEditorLayoutObject = toLayoutBlock(innerEditor->layoutObject()); const SimpleFontData* fontData = innerEditorLayoutObject->style(true)->font().primaryFont(); DCHECK(fontData); if (!fontData) return -1; LayoutUnit baseline(fontData->getFontMetrics().ascent(AlphabeticBaseline)); for (LayoutObject* box = innerEditorLayoutObject; box && box != this; box = box->parent()) { if (box->isBox()) baseline += toLayoutBox(box)->logicalTop(); } return baseline.toInt(); }
TEST_F(VisualRectMappingTest, LayoutInline) { document().setBaseURLOverride(KURL(ParsedURLString, "http://test.com")); setBodyInnerHTML( "<style>body { margin: 0; }</style>" "<div id='container' style='overflow: scroll; width: 50px; height: 50px'>" " <span><img style='width: 20px; height: 100px'></span>" " <span id=leaf></span></div>"); LayoutBlock* container = toLayoutBlock(getLayoutObjectByElementId("container")); LayoutObject* leaf = container->lastChild(); container->setScrollTop(LayoutUnit(50)); LayoutRect originalRect(0, 60, 20, 80); LayoutRect rect = originalRect; EXPECT_TRUE(leaf->mapToVisualRectInAncestorSpace(container, rect)); rect.move(-container->scrolledContentOffset()); EXPECT_EQ(rect, LayoutRect(0, 10, 20, 80)); rect = originalRect; EXPECT_TRUE(leaf->mapToVisualRectInAncestorSpace(&layoutView(), rect)); EXPECT_EQ(rect, LayoutRect(0, 10, 20, 40)); checkPaintInvalidationStateRectMapping(rect, originalRect, *leaf, layoutView(), layoutView()); rect = LayoutRect(0, 60, 80, 0); EXPECT_TRUE( leaf->mapToVisualRectInAncestorSpace(container, rect, EdgeInclusive)); rect.move(-container->scrolledContentOffset()); EXPECT_EQ(rect, LayoutRect(0, 10, 80, 0)); }
LayoutUnit SnapToLinesLayouter::computeInitialPositionAdjustment(LayoutUnit& step) const { // 6. Let line position be the text track cue computed line position. // 7. Round line position to an integer by adding 0.5 and then flooring it. LayoutUnit linePosition = floorf(m_linePosition + 0.5f); WritingMode writingMode = m_cueBox.style()->writingMode(); // 8. Vertical Growing Left: Add one to line position then negate it. if (writingMode == RightToLeftWritingMode) linePosition = -(linePosition + 1); // 9. Let position be the result of multiplying step and line position. LayoutUnit position = step * linePosition; // 10. Vertical Growing Left: Decrease position by the width of the // bounding box of the boxes in boxes, then increase position by step. if (writingMode == RightToLeftWritingMode) { position -= m_cueBox.size().width(); position += step; } // 11. If line position is less than zero... if (linePosition < 0) { LayoutBlock* parentBlock = m_cueBox.containingBlock(); // Horizontal / Vertical: ... then increase position by the // height / width of the video's rendering area ... position += blink::isHorizontalWritingMode(writingMode) ? parentBlock->size().height() : parentBlock->size().width(); // ... and negate step. step = -step; } return position; }
TEST_F(VisualRectMappingTest, LayoutText) { setBodyInnerHTML( "<style>body { margin: 0; }</style>" "<div id='container' style='overflow: scroll; width: 50px; height: 50px'>" " <span><img style='width: 20px; height: 100px'></span>" " text text text text text text text" "</div>"); LayoutBlock* container = toLayoutBlock(getLayoutObjectByElementId("container")); LayoutText* text = toLayoutText(container->lastChild()); container->setScrollTop(LayoutUnit(50)); LayoutRect originalRect(0, 60, 20, 80); LayoutRect rect = originalRect; EXPECT_TRUE(text->mapToVisualRectInAncestorSpace(container, rect)); rect.move(-container->scrolledContentOffset()); EXPECT_EQ(rect, LayoutRect(0, 10, 20, 80)); rect = originalRect; EXPECT_TRUE(text->mapToVisualRectInAncestorSpace(&layoutView(), rect)); EXPECT_EQ(rect, LayoutRect(0, 10, 20, 40)); checkPaintInvalidationStateRectMapping(rect, originalRect, *text, layoutView(), layoutView()); rect = LayoutRect(0, 60, 80, 0); EXPECT_TRUE( text->mapToVisualRectInAncestorSpace(container, rect, EdgeInclusive)); rect.move(-container->scrolledContentOffset()); EXPECT_EQ(rect, LayoutRect(0, 10, 80, 0)); }
void findGoodTouchTargets(const IntRect& touchBoxInRootFrame, LocalFrame* mainFrame, Vector<IntRect>& goodTargets, WillBeHeapVector<RawPtrWillBeMember<Node>>& highlightNodes) { goodTargets.clear(); int touchPointPadding = ceil(std::max(touchBoxInRootFrame.width(), touchBoxInRootFrame.height()) * 0.5); IntPoint touchPoint = touchBoxInRootFrame.center(); IntPoint contentsPoint = mainFrame->view()->rootFrameToContents(touchPoint); HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint(contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ListBased, LayoutSize(touchPointPadding, touchPointPadding)); const WillBeHeapListHashSet<RefPtrWillBeMember<Node>>& hitResults = result.listBasedTestResult(); // Blacklist nodes that are container of disambiguated nodes. // It is not uncommon to have a clickable <div> that contains other clickable objects. // This heuristic avoids excessive disambiguation in that case. WillBeHeapHashSet<RawPtrWillBeMember<Node>> blackList; for (const auto& hitResult : hitResults) { // Ignore any Nodes that can't be clicked on. LayoutObject* layoutObject = hitResult.get()->layoutObject(); if (!layoutObject || !hitResult.get()->willRespondToMouseClickEvents()) continue; // Blacklist all of the Node's containers. for (LayoutBlock* container = layoutObject->containingBlock(); container; container = container->containingBlock()) { Node* containerNode = container->node(); if (!containerNode) continue; if (!blackList.add(containerNode).isNewEntry) break; } } WillBeHeapHashMap<RawPtrWillBeMember<Node>, TouchTargetData> touchTargets; float bestScore = 0; for (const auto& hitResult : hitResults) { for (Node* node = hitResult.get(); node; node = node->parentNode()) { if (blackList.contains(node)) continue; if (node->isDocumentNode() || isHTMLHtmlElement(*node) || isHTMLBodyElement(*node)) break; if (node->willRespondToMouseClickEvents()) { TouchTargetData& targetData = touchTargets.add(node, TouchTargetData()).storedValue->value; targetData.windowBoundingBox = boundingBoxForEventNodes(node); targetData.score = scoreTouchTarget(touchPoint, touchPointPadding, targetData.windowBoundingBox); bestScore = std::max(bestScore, targetData.score); break; } } } for (const auto& touchTarget : touchTargets) { // Currently the scoring function uses the overlap area with the fat point as the score. // We ignore the candidates that has less than 1/2 overlap (we consider not really ambiguous enough) than the best candidate to avoid excessive popups. if (touchTarget.value.score < bestScore * 0.5) continue; goodTargets.append(touchTarget.value.windowBoundingBox); highlightNodes.append(touchTarget.key); } }
IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const { LayoutBlock* caretPainter = caretLayoutObject(node); if (!caretPainter) return IntRect(); LayoutRect localRect(rect); caretPainter->flipForWritingMode(localRect); return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); }
TEST_F(VisualRectMappingTest, ContainerOverflowHidden) { setBodyInnerHTML( "<div id='container' style='position: absolute; top: 111px; left: 222px;" " border: 10px solid red; overflow: hidden; width: 50px; height: " "80px;'>" " <div id='target' style='box-shadow: 40px 20px black; width: 100px; " "height: 90px'></div>" "</div>"); LayoutBlock* container = toLayoutBlock(getLayoutObjectByElementId("container")); EXPECT_EQ(LayoutUnit(), container->scrollTop()); EXPECT_EQ(LayoutUnit(), container->scrollLeft()); container->setScrollTop(LayoutUnit(27)); container->setScrollLeft(LayoutUnit(28)); document().view()->updateAllLifecyclePhases(); LayoutBlock* target = toLayoutBlock(getLayoutObjectByElementId("target")); LayoutRect targetVisualRect = target->localVisualRect(); // 140 = width(100) + box_shadow_offset_x(40) // 110 = height(90) + box_shadow_offset_y(20) EXPECT_EQ(LayoutRect(0, 0, 140, 110), targetVisualRect); LayoutRect rect = targetVisualRect; EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(target, rect)); EXPECT_EQ(LayoutRect(0, 0, 140, 110), rect); rect = targetVisualRect; EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(container, rect)); // Rect is not clipped by container's overflow clip. EXPECT_EQ(LayoutRect(10, 10, 140, 110), rect); }
PositionWithAffinity LayoutSVGInlineText::positionForPoint(const LayoutPoint& point) { if (!firstTextBox() || !textLength()) return createPositionWithAffinity(0, DOWNSTREAM); ASSERT(m_scalingFactor); float baseline = m_scaledFont.fontMetrics().floatAscent() / m_scalingFactor; LayoutBlock* containingBlock = this->containingBlock(); ASSERT(containingBlock); // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. FloatPoint absolutePoint(point); absolutePoint.moveBy(containingBlock->location()); float closestDistance = std::numeric_limits<float>::max(); float closestDistancePosition = 0; const SVGTextFragment* closestDistanceFragment = 0; SVGInlineTextBox* closestDistanceBox = 0; AffineTransform fragmentTransform; for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); Vector<SVGTextFragment>& fragments = textBox->textFragments(); unsigned textFragmentsSize = fragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = fragments.at(i); FloatRect fragmentRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentRect = fragmentTransform.mapRect(fragmentRect); float distance = 0; if (!fragmentRect.contains(absolutePoint)) distance = squaredDistanceToClosestPoint(fragmentRect, absolutePoint); if (distance <= closestDistance) { closestDistance = distance; closestDistanceBox = textBox; closestDistanceFragment = &fragment; closestDistancePosition = fragmentRect.x(); } } } if (!closestDistanceFragment) return createPositionWithAffinity(0, DOWNSTREAM); int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, absolutePoint.x() - closestDistancePosition, true); return createPositionWithAffinity(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM); }
void LayoutRubyRun::addChild(LayoutObject* child, LayoutObject* beforeChild) { ASSERT(child); if (child->isRubyText()) { if (!beforeChild) { // LayoutRuby has already ascertained that we can add the child here. ASSERT(!hasRubyText()); // prepend ruby texts as first child LayoutBlockFlow::addChild(child, firstChild()); } else if (beforeChild->isRubyText()) { // New text is inserted just before another. // In this case the new text takes the place of the old one, and // the old text goes into a new run that is inserted as next sibling. ASSERT(beforeChild->parent() == this); LayoutObject* ruby = parent(); ASSERT(ruby->isRuby()); LayoutBlock* newRun = staticCreateRubyRun(ruby); ruby->addChild(newRun, nextSibling()); // Add the new ruby text and move the old one to the new run // Note: Doing it in this order and not using LayoutRubyRun's methods, // in order to avoid automatic removal of the ruby run in case there is no // other child besides the old ruby text. LayoutBlockFlow::addChild(child, beforeChild); LayoutBlockFlow::removeChild(beforeChild); newRun->addChild(beforeChild); } else if (hasRubyBase()) { // Insertion before a ruby base object. // In this case we need insert a new run before the current one and split the base. LayoutObject* ruby = parent(); LayoutRubyRun* newRun = staticCreateRubyRun(ruby); ruby->addChild(newRun, this); newRun->addChild(child); // Make sure we don't leave anything in the percentage descendant // map before moving the children to the new base. if (hasPercentHeightDescendants()) clearPercentHeightDescendants(); rubyBaseSafe()->moveChildren(newRun->rubyBaseSafe(), beforeChild); } } else { // child is not a text -> insert it into the base // (append it instead if beforeChild is the ruby text) LayoutRubyBase* base = rubyBaseSafe(); if (beforeChild == base) beforeChild = base->firstChild(); if (beforeChild && beforeChild->isRubyText()) beforeChild = 0; ASSERT(!beforeChild || beforeChild->isDescendantOf(base)); base->addChild(child, beforeChild); } }
TEST_F(LayoutObjectTest, PaintingLayerOfOverflowClipLayerUnderColumnSpanAll) { setBodyInnerHTML( "<div id='columns' style='columns: 3'>" " <div style='column-span: all'>" " <div id='overflow-clip-layer' style='height: 100px; overflow: " "hidden'></div>" " </div>" "</div>"); LayoutObject* overflowClipObject = getLayoutObjectByElementId("overflow-clip-layer"); LayoutBlock* columns = toLayoutBlock(getLayoutObjectByElementId("columns")); EXPECT_EQ(columns->layer(), overflowClipObject->paintingLayer()); }
TEST_F(LayoutObjectTest, MapToVisibleRectInContainerSpace) { setBodyInnerHTML( "<div id='container' style='overflow: scroll; will-change: transform; width: 50px; height: 50px'>" " <span><img style='width: 20px; height: 100px'></span>" " text text text text text text text" "</div>"); LayoutBlock* container = toLayoutBlock(document().getElementById("container")->layoutObject()); LayoutText* text = toLayoutText(container->lastChild()); container->setScrollTop(50); LayoutRect rect(0, 60, 20, 20); text->mapToVisibleRectInAncestorSpace(container, rect, nullptr); EXPECT_TRUE(rect == LayoutRect(0, 10, 20, 20)); }
void BlockPainter::paintContinuationOutlines(const PaintInfo& info, const LayoutPoint& paintOffset) { LayoutInline* inlineCont = m_layoutBlock.inlineElementContinuation(); if (inlineCont && inlineCont->style()->hasOutline() && inlineCont->style()->visibility() == VISIBLE) { LayoutInline* inlineLayoutObject = toLayoutInline(inlineCont->node()->layoutObject()); LayoutBlock* cb = m_layoutBlock.containingBlock(); bool inlineEnclosedInSelfPaintingLayer = false; for (LayoutBoxModelObject* box = inlineLayoutObject; box != cb; box = box->parent()->enclosingBoxModelObject()) { if (box->hasSelfPaintingLayer()) { inlineEnclosedInSelfPaintingLayer = true; break; } } // Do not add continuations for outline painting by our containing block if we are a relative positioned // anonymous block (i.e. have our own layer), paint them straightaway instead. This is because a block depends on layoutObjects in its continuation table being // in the same layer. if (!inlineEnclosedInSelfPaintingLayer && !m_layoutBlock.hasLayer()) { cb->addContinuationWithOutline(inlineLayoutObject); } else if (!inlineLayoutObject->firstLineBox() || (!inlineEnclosedInSelfPaintingLayer && m_layoutBlock.hasLayer())) { // The outline might be painted multiple times if multiple blocks have the same inline element continuation, and the inline has a self-painting layer. ScopeRecorder scopeRecorder(*info.context); InlinePainter(*inlineLayoutObject).paintOutline(info, paintOffset - m_layoutBlock.locationOffset() + inlineLayoutObject->containingBlock()->location()); } } ContinuationOutlineTableMap* table = continuationOutlineTable(); if (table->isEmpty()) return; OwnPtr<ListHashSet<LayoutInline*>> continuations = table->take(&m_layoutBlock); if (!continuations) return; LayoutPoint accumulatedPaintOffset = paintOffset; // Paint each continuation outline. ListHashSet<LayoutInline*>::iterator end = continuations->end(); for (ListHashSet<LayoutInline*>::iterator it = continuations->begin(); it != end; ++it) { // Need to add in the coordinates of the intervening blocks. LayoutInline* flow = *it; LayoutBlock* block = flow->containingBlock(); for ( ; block && block != &m_layoutBlock; block = block->containingBlock()) accumulatedPaintOffset.moveBy(block->location()); ASSERT(block); InlinePainter(*flow).paintOutline(info, accumulatedPaintOffset); } }
static bool isIndependentDescendant(const LayoutBlock* layoutObject) { ASSERT(isPotentialClusterRoot(layoutObject)); LayoutBlock* containingBlock = layoutObject->containingBlock(); return layoutObject->isLayoutView() || layoutObject->isFloating() || layoutObject->isOutOfFlowPositioned() || layoutObject->isTableCell() || layoutObject->isTableCaption() || layoutObject->isFlexibleBoxIncludingDeprecated() || (containingBlock && containingBlock->isHorizontalWritingMode() != layoutObject->isHorizontalWritingMode()) || layoutObject->style()->isDisplayReplacedType() || layoutObject->isTextArea() || layoutObject->style()->userModify() != READ_ONLY; }
PositionWithAffinity LayoutSVGInlineText::positionForPoint(const LayoutPoint& point) { if (!hasTextBoxes() || !textLength()) return createPositionWithAffinity(0); ASSERT(m_scalingFactor); float baseline = m_scaledFont.getFontMetrics().floatAscent() / m_scalingFactor; LayoutBlock* containingBlock = this->containingBlock(); ASSERT(containingBlock); // Map local point to absolute point, as the character origins stored in the text fragments use absolute coordinates. FloatPoint absolutePoint(point); absolutePoint.moveBy(containingBlock->location()); float closestDistance = std::numeric_limits<float>::max(); float closestDistancePosition = 0; const SVGTextFragment* closestDistanceFragment = nullptr; SVGInlineTextBox* closestDistanceBox = nullptr; for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { if (!box->isSVGInlineTextBox()) continue; SVGInlineTextBox* textBox = toSVGInlineTextBox(box); for (const SVGTextFragment& fragment : textBox->textFragments()) { FloatRect fragmentRect = fragment.boundingBox(baseline); float distance = 0; if (!fragmentRect.contains(absolutePoint)) distance = fragmentRect.squaredDistanceTo(absolutePoint); if (distance <= closestDistance) { closestDistance = distance; closestDistanceBox = textBox; closestDistanceFragment = &fragment; closestDistancePosition = fragmentRect.x(); } } } if (!closestDistanceFragment) return createPositionWithAffinity(0); int offset = closestDistanceBox->offsetForPositionInFragment(*closestDistanceFragment, LayoutUnit(absolutePoint.x() - closestDistancePosition), true); return createPositionWithAffinity(offset + closestDistanceBox->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : TextAffinity::Downstream); }
static inline LayoutRect visualOverflowRectWithPaintOffset(const LayoutBlock& layoutBox, const LayoutPoint& paintOffset) { if (!RuntimeEnabledFeatures::slimmingPaintEnabled()) return LayoutRect(); LayoutRect bounds = layoutBox.visualOverflowRect(); bounds.moveBy(paintOffset); return bounds; }
void LayoutTextControl::styleDidChange(StyleDifference diff, const ComputedStyle* oldStyle) { LayoutBlockFlow::styleDidChange(diff, oldStyle); Element* innerEditor = innerEditorElement(); if (!innerEditor) return; LayoutBlock* innerEditorLayoutObject = toLayoutBlock(innerEditor->layoutObject()); if (innerEditorLayoutObject) { // We may have set the width and the height in the old style in layout(). // Reset them now to avoid getting a spurious layout hint. innerEditorLayoutObject->mutableStyleRef().setHeight(Length()); innerEditorLayoutObject->mutableStyleRef().setWidth(Length()); innerEditorLayoutObject->setStyle(createInnerEditorStyle(styleRef())); innerEditor->setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::Control)); } textFormControlElement()->updatePlaceholderVisibility(false); }
void CaretBase::invalidateLocalCaretRect(Node* node, const LayoutRect& rect) { LayoutBlock* caretPainter = caretLayoutObject(node); if (!caretPainter) return; // FIXME: Need to over-paint 1 pixel to workaround some rounding problems. // https://bugs.webkit.org/show_bug.cgi?id=108283 LayoutRect inflatedRect = rect; inflatedRect.inflate(1); // FIXME: We should use mapLocalToContainer() since we know we're not un-rooted. mapCaretRectToCaretPainter(node->layoutObject(), caretPainter, inflatedRect); // FIXME: We should not allow paint invalidation out of paint invalidation state. crbug.com/457415 DisablePaintInvalidationStateAsserts disabler; caretPainter->invalidatePaintRectangle(inflatedRect); }
TEST_F(VisualRectMappingTest, ContainerAndTargetDifferentFlippedWritingMode) { setBodyInnerHTML( "<div id='container' style='writing-mode: vertical-rl; position: " "absolute; top: 111px; left: 222px;" " border: solid red; border-width: 10px 20px 30px 40px;" " overflow: scroll; width: 50px; height: 80px'>" " <div id='target' style='writing-mode: vertical-lr; box-shadow: 40px " "20px black; width: 100px; height: 90px'></div>" " <div style='width: 100px; height: 100px'></div>" "</div>"); LayoutBlock* container = toLayoutBlock(getLayoutObjectByElementId("container")); EXPECT_EQ(LayoutUnit(), container->scrollTop()); // The initial scroll offset is to the left-most because of flipped blocks // writing mode. // 150 = total_layout_overflow(100 + 100) - width(50) EXPECT_EQ(LayoutUnit(150), container->scrollLeft()); container->setScrollTop(LayoutUnit(7)); container->setScrollLeft( LayoutUnit(142)); // Scroll to the right by 8 pixels. document().view()->updateAllLifecyclePhases(); LayoutBlock* target = toLayoutBlock(getLayoutObjectByElementId("target")); LayoutRect targetVisualRect = target->localVisualRect(); // 140 = width(100) + box_shadow_offset_x(40) // 110 = height(90) + box_shadow_offset_y(20) EXPECT_EQ(LayoutRect(0, 0, 140, 110), targetVisualRect); LayoutRect rect = targetVisualRect; EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(target, rect)); // This rect is in physical coordinates of target. EXPECT_EQ(LayoutRect(0, 0, 140, 110), rect); rect = targetVisualRect; EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(container, rect)); rect.move(-container->scrolledContentOffset()); // -2 = target_physical_x(100) + container_border_left(40) - scroll_left(142) // 3 = target_y(0) + container_border_top(10) - scroll_top(7) // Rect is not clipped by container's overflow clip. EXPECT_EQ(LayoutRect(-2, 3, 140, 110), rect); }
void LayoutMultiColumnFlowThread::calculateColumnCountAndWidth(LayoutUnit& width, unsigned& count) const { LayoutBlock* columnBlock = multiColumnBlockFlow(); const ComputedStyle* columnStyle = columnBlock->style(); LayoutUnit availableWidth = columnBlock->contentLogicalWidth(); LayoutUnit columnGap = columnBlock->columnGap(); LayoutUnit computedColumnWidth = max<LayoutUnit>(1, LayoutUnit(columnStyle->columnWidth())); unsigned computedColumnCount = max<int>(1, columnStyle->columnCount()); ASSERT(!columnStyle->hasAutoColumnCount() || !columnStyle->hasAutoColumnWidth()); if (columnStyle->hasAutoColumnWidth() && !columnStyle->hasAutoColumnCount()) { count = computedColumnCount; width = std::max<LayoutUnit>(0, (availableWidth - ((count - 1) * columnGap)) / count); } else if (!columnStyle->hasAutoColumnWidth() && columnStyle->hasAutoColumnCount()) { count = std::max<LayoutUnit>(1, (availableWidth + columnGap) / (computedColumnWidth + columnGap)); width = ((availableWidth + columnGap) / count) - columnGap; } else { count = std::max<LayoutUnit>(std::min<LayoutUnit>(computedColumnCount, (availableWidth + columnGap) / (computedColumnWidth + columnGap)), 1); width = ((availableWidth + columnGap) / count) - columnGap; } }
void LayoutRubyRun::removeChild(LayoutObject* child) { // If the child is a ruby text, then merge the ruby base with the base of // the right sibling run, if possible. if (!beingDestroyed() && !documentBeingDestroyed() && child->isRubyText()) { LayoutRubyBase* base = rubyBase(); LayoutObject* rightNeighbour = nextSibling(); if (base && rightNeighbour && rightNeighbour->isRubyRun()) { // Ruby run without a base can happen only at the first run. LayoutRubyRun* rightRun = toLayoutRubyRun(rightNeighbour); if (rightRun->hasRubyBase()) { LayoutRubyBase* rightBase = rightRun->rubyBaseSafe(); // Collect all children in a single base, then swap the bases. rightBase->moveChildren(base); moveChildTo(rightRun, base); rightRun->moveChildTo(this, rightBase); // The now empty ruby base will be removed below. ASSERT(!rubyBase()->firstChild()); } } } LayoutBlockFlow::removeChild(child); if (!beingDestroyed() && !documentBeingDestroyed()) { // Check if our base (if any) is now empty. If so, destroy it. LayoutBlock* base = rubyBase(); if (base && !base->firstChild()) { LayoutBlockFlow::removeChild(base); base->deleteLineBoxTree(); base->destroy(); } // If any of the above leaves the run empty, destroy it as well. if (!hasRubyText() && !hasRubyBase()) { deleteLineBoxTree(); destroy(); } } }
TEST_F(VisualRectMappingTest, SelfFlippedWritingMode) { setBodyInnerHTML( "<div id='target' style='writing-mode: vertical-rl; box-shadow: 40px " "20px black;" " width: 100px; height: 50px; position: absolute; top: 111px; left: " "222px'>" "</div>"); LayoutBlock* target = toLayoutBlock(getLayoutObjectByElementId("target")); LayoutRect visualRect = target->localVisualRect(); // -40 = -box_shadow_offset_x(40) (with target's top-right corner as the // origin) // 140 = width(100) + box_shadow_offset_x(40) // 70 = height(50) + box_shadow_offset_y(20) EXPECT_EQ(LayoutRect(-40, 0, 140, 70), visualRect); LayoutRect rect = visualRect; // TODO(wkorman): The calls to flipForWritingMode() here and in other test // cases below are necessary because mapToVisualRectInAncestorSpace() // currently expects the input rect to be in "physical coordinates" (*not* // "physical coordinates with flipped block-flow direction"), see // LayoutBoxModelObject.h. target->flipForWritingMode(rect); EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(target, rect)); // This rect is in physical coordinates of target. EXPECT_EQ(LayoutRect(0, 0, 140, 70), rect); rect = visualRect; target->flipForWritingMode(rect); EXPECT_TRUE(target->mapToVisualRectInAncestorSpace(&layoutView(), rect)); EXPECT_EQ(LayoutRect(222, 111, 140, 70), rect); checkPaintInvalidationStateRectMapping(rect, visualRect, *target, layoutView(), layoutView()); }
void LayoutRubyBase::moveBlockChildren(LayoutRubyBase* toBase, LayoutObject* beforeChild) { ASSERT(!childrenInline()); ASSERT_ARG(toBase, toBase); if (!firstChild()) return; if (toBase->childrenInline()) toBase->makeChildrenNonInline(); // If an anonymous block would be put next to another such block, then merge those. LayoutObject* firstChildHere = firstChild(); LayoutObject* lastChildThere = toBase->lastChild(); if (firstChildHere->isAnonymousBlock() && firstChildHere->childrenInline() && lastChildThere && lastChildThere->isAnonymousBlock() && lastChildThere->childrenInline()) { LayoutBlock* anonBlockHere = toLayoutBlock(firstChildHere); LayoutBlock* anonBlockThere = toLayoutBlock(lastChildThere); anonBlockHere->moveAllChildrenTo(anonBlockThere, anonBlockThere->children()); anonBlockHere->deleteLineBoxTree(); anonBlockHere->destroy(); } // Move all remaining children normally. moveChildrenTo(toBase, firstChild(), beforeChild); }
TEST_F(VisualRectMappingTest, LayoutView) { document().setBaseURLOverride(KURL(ParsedURLString, "http://test.com")); setBodyInnerHTML( "<style>body { margin: 0; }</style>" "<div id=frameContainer>" " <iframe src='http://test.com' width='50' height='50' " "frameBorder='0'></iframe>" "</div>"); setChildFrameHTML( "<style>body { margin: 0; }</style><span><img style='width: 20px; " "height: 100px'></span>text text text"); document().view()->updateAllLifecyclePhases(); LayoutBlock* frameContainer = toLayoutBlock(getLayoutObjectByElementId("frameContainer")); LayoutBlock* frameBody = toLayoutBlock(childDocument().body()->layoutObject()); LayoutText* frameText = toLayoutText(frameBody->lastChild()); // This case involves clipping: frame height is 50, y-coordinate of result // rect is 13, so height should be clipped to (50 - 13) == 37. childDocument().view()->setScrollOffset(ScrollOffset(0, 47), ProgrammaticScroll); LayoutRect originalRect(4, 60, 20, 80); LayoutRect rect = originalRect; EXPECT_TRUE(frameText->mapToVisualRectInAncestorSpace(frameContainer, rect)); EXPECT_EQ(rect, LayoutRect(4, 13, 20, 37)); rect = originalRect; EXPECT_TRUE(frameText->mapToVisualRectInAncestorSpace(&layoutView(), rect)); EXPECT_EQ(rect, LayoutRect(4, 13, 20, 37)); checkPaintInvalidationStateRectMapping(rect, originalRect, *frameText, layoutView(), layoutView()); rect = LayoutRect(4, 60, 0, 80); EXPECT_TRUE(frameText->mapToVisualRectInAncestorSpace(frameContainer, rect, EdgeInclusive)); EXPECT_EQ(rect, LayoutRect(4, 13, 0, 37)); }
LayoutObject* LayoutFullScreen::wrapLayoutObject(LayoutObject* object, LayoutObject* parent, Document* document) { // FIXME: We should not modify the structure of the layout tree during // layout. crbug.com/370459 DeprecatedDisableModifyLayoutTreeStructureAsserts disabler; LayoutFullScreen* fullscreenLayoutObject = LayoutFullScreen::createAnonymous(document); fullscreenLayoutObject->updateStyle(); if (parent && !parent->isChildAllowed(fullscreenLayoutObject, fullscreenLayoutObject->styleRef())) { fullscreenLayoutObject->destroy(); return nullptr; } if (object) { // |object->parent()| can be null if the object is not yet attached // to |parent|. if (LayoutObject* parent = object->parent()) { LayoutBlock* containingBlock = object->containingBlock(); ASSERT(containingBlock); // Since we are moving the |object| to a new parent |fullscreenLayoutObject|, // the line box tree underneath our |containingBlock| is not longer valid. containingBlock->deleteLineBoxTree(); parent->addChild(fullscreenLayoutObject, object); object->remove(); // Always just do a full layout to ensure that line boxes get deleted properly. // Because objects moved from |parent| to |fullscreenLayoutObject|, we want to // make new line boxes instead of leaving the old ones around. parent->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::Fullscreen); containingBlock->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::Fullscreen); } fullscreenLayoutObject->addChild(object); fullscreenLayoutObject->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(LayoutInvalidationReason::Fullscreen); } ASSERT(document); Fullscreen::from(*document).setFullScreenLayoutObject(fullscreenLayoutObject); return fullscreenLayoutObject; }
TEST_F(VisualRectMappingTest, LayoutViewDisplayNone) { document().setBaseURLOverride(KURL(ParsedURLString, "http://test.com")); setBodyInnerHTML( "<style>body { margin: 0; }</style>" "<div id=frameContainer>" " <iframe id='frame' src='http://test.com' width='50' height='50' " "frameBorder='0'></iframe>" "</div>"); setChildFrameHTML( "<style>body { margin: 0; }</style><div " "style='width:100px;height:100px;'></div>"); document().view()->updateAllLifecyclePhases(); LayoutBlock* frameContainer = toLayoutBlock(getLayoutObjectByElementId("frameContainer")); LayoutBlock* frameBody = toLayoutBlock(childDocument().body()->layoutObject()); LayoutBlock* frameDiv = toLayoutBlock(frameBody->lastChild()); // This part is copied from the LayoutView test, just to ensure that the // mapped rect is valid before display:none is set on the iframe. childDocument().view()->setScrollOffset(ScrollOffset(0, 47), ProgrammaticScroll); LayoutRect originalRect(4, 60, 20, 80); LayoutRect rect = originalRect; EXPECT_TRUE(frameDiv->mapToVisualRectInAncestorSpace(frameContainer, rect)); EXPECT_EQ(rect, LayoutRect(4, 13, 20, 37)); Element* frameElement = document().getElementById("frame"); frameElement->setInlineStyleProperty(CSSPropertyDisplay, "none"); document().view()->updateAllLifecyclePhases(); rect = originalRect; EXPECT_FALSE(frameDiv->mapToVisualRectInAncestorSpace(&layoutView(), rect)); EXPECT_EQ(rect, LayoutRect()); }
TEST_F(VisualRectMappingTest, ContainerOfAbsoluteAbovePaintInvalidationContainer) { enableCompositing(); document().frame()->settings()->setPreferCompositingToLCDTextEnabled(true); setBodyInnerHTML( "<div id='container' style='position: absolute; top: 88px; left: 99px'>" " <div style='height: 222px'></div>" // This div makes stacking-context composited. " <div style='position: absolute; width: 1px; height: 1px; " "background:yellow; will-change: transform'></div>" // This stacking context is paintInvalidationContainer of the absolute // child, but not a container of it. " <div id='stacking-context' style='opacity: 0.9'>" " <div id='absolute' style='position: absolute; top: 50px; left: " "50px; width: 50px; height: 50px; background: green'></div>" " </div>" "</div>"); LayoutBlock* stackingContext = toLayoutBlock(getLayoutObjectByElementId("stacking-context")); LayoutBlock* absolute = toLayoutBlock(getLayoutObjectByElementId("absolute")); LayoutBlock* container = toLayoutBlock(getLayoutObjectByElementId("container")); EXPECT_EQ(stackingContext, &absolute->containerForPaintInvalidation()); EXPECT_EQ(container, absolute->container()); LayoutRect absoluteVisualRect = absolute->localVisualRect(); EXPECT_EQ(LayoutRect(0, 0, 50, 50), absoluteVisualRect); LayoutRect rect = absoluteVisualRect; EXPECT_TRUE(absolute->mapToVisualRectInAncestorSpace(stackingContext, rect)); // -172 = top(50) - y_offset_of_stacking_context(222) EXPECT_EQ(LayoutRect(50, -172, 50, 50), rect); checkPaintInvalidationStateRectMapping(rect, absoluteVisualRect, *absolute, layoutView(), *stackingContext); }
// Override ContainingBlockContext based on the properties of a containing block // that was previously walked in a subtree other than the current subtree being // walked. Used for out-of-flow positioned descendants of multi-column spanner // when the containing block is not in the normal tree walk order. // For example: // <div id="columns" style="columns: 2"> // <div id="relative" style="position: relative"> // <div id="spanner" style="column-span: all"> // <div id="absolute" style="position: absolute"></div> // </div> // </div> // <div> // The real containing block of "absolute" is "relative" which is not in the // tree-walk order of "columns" -> spanner placeholder -> spanner -> absolute. // Here we rebuild a ContainingBlockContext based on the properties of // "relative" for "absolute". static void overrideContaineringBlockContextFromRealContainingBlock( const LayoutBlock& containingBlock, PaintPropertyTreeBuilderContext::ContainingBlockContext& context) { const auto* properties = containingBlock.paintProperties()->localBorderBoxProperties(); DCHECK(properties); context.transform = properties->propertyTreeState.transform(); context.paintOffset = properties->paintOffset; context.shouldFlattenInheritedTransform = context.transform && context.transform->flattensInheritedTransform(); context.renderingContextID = context.transform ? context.transform->renderingContextID() : 0; context.clip = properties->propertyTreeState.clip(); context.scroll = const_cast<ScrollPaintPropertyNode*>( properties->propertyTreeState.scroll()); }
// We need to balance the benefit of subtree optimization and the cost of subtree display items. // Only output subtree information if the block has multiple children or multiple line boxes. static bool needsSubtreeRecorder(const LayoutBlock& layoutBlock) { return (layoutBlock.firstChild() && layoutBlock.firstChild()->nextSibling()) || (layoutBlock.isLayoutBlockFlow() && toLayoutBlockFlow(layoutBlock).firstLineBox() && toLayoutBlockFlow(layoutBlock).firstLineBox()->nextLineBox()); }
LayoutAnalyzer::BlockScope::BlockScope(const LayoutBlock& block) : m_block(block) , m_width(block.frameRect().width()) , m_height(block.frameRect().height()) { }
LayoutUnit LayoutTableCaption::containingBlockLogicalWidthForContent() const { LayoutBlock* cb = containingBlock(); return cb->logicalWidth(); }