TEST_F(LayoutObjectTest, ContainingBlockAbsoluteLayoutObjectShouldBeNonStaticallyPositionedBlockAncestor) { setBodyInnerHTML("<div style='position:relative'><bar style='position:absolute'></bar></div>"); LayoutObject* containingBlocklayoutObject = document().body()->layoutObject()->slowFirstChild(); LayoutObject* layoutObject = containingBlocklayoutObject->slowFirstChild(); EXPECT_EQ(layoutObject->containingBlock(), containingBlocklayoutObject); }
// Find the next layout object that has the multicol container in its containing block chain, skipping nested multicol containers. static LayoutObject* nextInPreOrderAfterChildrenSkippingOutOfFlow(LayoutMultiColumnFlowThread* flowThread, LayoutObject* descendant) { ASSERT(descendant->isDescendantOf(flowThread)); LayoutObject* object = descendant->nextInPreOrderAfterChildren(flowThread); while (object) { // Walk through the siblings and find the first one which is either in-flow or has this // flow thread as its containing block flow thread. if (!object->isOutOfFlowPositioned()) break; if (object->containingBlock()->flowThreadContainingBlock() == flowThread) { // This out-of-flow object is still part of the flow thread, because its containing // block (probably relatively positioned) is part of the flow thread. break; } object = object->nextInPreOrderAfterChildren(flowThread); } if (!object) return nullptr; #if ENABLE(ASSERT) // Make sure that we didn't stumble into an inner multicol container. for (LayoutObject* walker = object->parent(); walker && walker != flowThread; walker = walker->parent()) ASSERT(!isMultiColumnContainer(*walker)); #endif return object; }
TEST_F(LayoutObjectTest, ContainingBlockStaticLayoutObjectShouldBeParent) { setBodyInnerHTML("<foo style='position:static'></foo>"); LayoutObject* bodyLayoutObject = document().body()->layoutObject(); LayoutObject* layoutObject = bodyLayoutObject->slowFirstChild(); EXPECT_EQ(layoutObject->containingBlock(), bodyLayoutObject); }
void LayoutMultiColumnFlowThread::skipColumnSpanner(LayoutBox* layoutObject, LayoutUnit logicalTopInFlowThread) { ASSERT(layoutObject->isColumnSpanAll()); LayoutMultiColumnSpannerPlaceholder* placeholder = layoutObject->spannerPlaceholder(); LayoutBox* previousColumnBox = placeholder->previousSiblingMultiColumnBox(); if (previousColumnBox && previousColumnBox->isLayoutMultiColumnSet()) { LayoutMultiColumnSet* columnSet = toLayoutMultiColumnSet(previousColumnBox); if (logicalTopInFlowThread < columnSet->logicalTopInFlowThread()) logicalTopInFlowThread = columnSet->logicalTopInFlowThread(); // Negative margins may cause this. columnSet->endFlow(logicalTopInFlowThread); } LayoutBox* nextColumnBox = placeholder->nextSiblingMultiColumnBox(); if (nextColumnBox && nextColumnBox->isLayoutMultiColumnSet()) { LayoutMultiColumnSet* nextSet = toLayoutMultiColumnSet(nextColumnBox); m_lastSetWorkedOn = nextSet; nextSet->beginFlow(logicalTopInFlowThread); } // We'll lay out of spanners after flow thread layout has finished (during layout of the spanner // placeholders). There may be containing blocks for out-of-flow positioned descendants of the // spanner in the flow thread, so that out-of-flow objects inside the spanner will be laid out // as part of flow thread layout (even if the spanner itself won't). We need to add such // out-of-flow positioned objects to their containing blocks now, or they'll never get laid // out. Since it's non-trivial to determine if we need this, and where such out-of-flow objects // might be, just go through the whole subtree. for (LayoutObject* descendant = layoutObject->slowFirstChild(); descendant; descendant = descendant->nextInPreOrder()) { if (descendant->isBox() && descendant->isOutOfFlowPositioned()) descendant->containingBlock()->insertPositionedObject(toLayoutBox(descendant)); } }
TEST_F(LayoutObjectTest, ContainingBlockAbsoluteLayoutObjectShouldBeLayoutView) { setBodyInnerHTML("<foo style='position:absolute'></foo>"); LayoutObject* layoutObject = document().body()->layoutObject()->slowFirstChild(); EXPECT_EQ(layoutObject->containingBlock(), layoutView()); }
// Abs x/y position of the caret ignoring transforms. // TODO(yosin) navigation with transforms should be smarter. static LayoutUnit lineDirectionPointForBlockDirectionNavigationOf( const VisiblePosition& visiblePosition) { if (visiblePosition.isNull()) return LayoutUnit(); LayoutObject* layoutObject; LayoutRect localRect = localCaretRectOfPosition( visiblePosition.toPositionWithAffinity(), layoutObject); if (localRect.isEmpty() || !layoutObject) return LayoutUnit(); // This ignores transforms on purpose, for now. Vertical navigation is done // without consulting transforms, so that 'up' in transformed text is 'up' // relative to the text, not absolute 'up'. FloatPoint caretPoint = layoutObject->localToAbsolute(FloatPoint(localRect.location())); LayoutObject* containingBlock = layoutObject->containingBlock(); if (!containingBlock) { // Just use ourselves to determine the writing mode if we have no containing // block. containingBlock = layoutObject; } return LayoutUnit(containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y()); }
static const DeprecatedPaintLayer* findParentLayerOnClippingContainerChain(const DeprecatedPaintLayer* layer) { LayoutObject* current = layer->layoutObject(); while (current) { if (current->style()->position() == FixedPosition) { for (current = current->parent(); current && !current->canContainFixedPositionObjects(); current = current->parent()) { // All types of clips apply to fixed-position descendants of other fixed-position elements. // Note: it's unclear whether this is what the spec says. Firefox does not clip, but Chrome does. if (current->style()->position() == FixedPosition && current->hasClipOrOverflowClip()) { ASSERT(current->hasLayer()); return static_cast<const LayoutBoxModelObject*>(current)->layer(); } // CSS clip applies to fixed position elements even for ancestors that are not what the // fixed element is positioned with respect to. if (current->hasClip()) { ASSERT(current->hasLayer()); return static_cast<const LayoutBoxModelObject*>(current)->layer(); } } } else { current = current->containingBlock(); } if (current->hasLayer()) return static_cast<const LayoutBoxModelObject*>(current)->layer(); // Having clip or overflow clip forces the LayoutObject to become a layer. ASSERT(!current->hasClipOrOverflowClip()); } ASSERT_NOT_REACHED(); return nullptr; }
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); } }
TEST_F(LayoutObjectTest, ContainingBlockAbsoluteLayoutObjectShouldNotBeNonStaticlyPositionedInlineAncestor) { setBodyInnerHTML("<span style='position:relative'><bar style='position:absolute'></bar></span>"); LayoutObject* bodyLayoutObject = document().body()->layoutObject(); LayoutObject* layoutObject = bodyLayoutObject->slowFirstChild()->slowFirstChild(); // Sanity check: Make sure we don't generate anonymous objects. EXPECT_EQ(nullptr, bodyLayoutObject->slowFirstChild()->nextSibling()); EXPECT_EQ(nullptr, layoutObject->slowFirstChild()); EXPECT_EQ(nullptr, layoutObject->nextSibling()); EXPECT_EQ(layoutObject->containingBlock(), bodyLayoutObject); }
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(); }
int VisiblePosition::lineDirectionPointForBlockDirectionNavigation() const { LayoutObject* layoutObject; LayoutRect localRect = localCaretRect(layoutObject); if (localRect.isEmpty() || !layoutObject) return 0; // This ignores transforms on purpose, for now. Vertical navigation is done // without consulting transforms, so that 'up' in transformed text is 'up' // relative to the text, not absolute 'up'. FloatPoint caretPoint = layoutObject->localToAbsolute(FloatPoint(localRect.location())); LayoutObject* containingBlock = layoutObject->containingBlock(); if (!containingBlock) containingBlock = layoutObject; // Just use ourselves to determine the writing mode if we have no containing block. return containingBlock->isHorizontalWritingMode() ? caretPoint.x() : caretPoint.y(); }
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(); }
// When processing layout objects to remove or when processing layout objects that have just been // inserted, certain types of objects should be skipped. static bool shouldSkipInsertedOrRemovedChild(LayoutMultiColumnFlowThread* flowThread, const LayoutObject& child) { if (child.isSVG() && !child.isSVGRoot()) { // Don't descend into SVG objects. What's in there is of no interest, and there might even // be a foreignObject there with column-span:all, which doesn't apply to us. return true; } if (child.isLayoutFlowThread()) { // Found an inner flow thread. We need to skip it and its descendants. return true; } if (child.isLayoutMultiColumnSet() || child.isLayoutMultiColumnSpannerPlaceholder()) { // Column sets and spanner placeholders in a child multicol context don't affect the parent // flow thread. return true; } if (child.isOutOfFlowPositioned() && child.containingBlock()->flowThreadContainingBlock() != flowThread) { // Out-of-flow with its containing block on the outside of the multicol container. return true; } return false; }