Changeset 263659 in webkit


Ignore:
Timestamp:
Jun 29, 2020 5:15:06 AM (4 years ago)
Author:
svillar@igalia.com
Message:

[css-flexbox] WebKit mistakenly lets pointer events (click/hover/etc) pass through flex items, if they have negative margin
https://bugs.webkit.org/show_bug.cgi?id=185771

Reviewed by Javier Fernandez.

LayoutTests/imported/w3c:

  • web-platform-tests/css/css-flexbox/hittest-overlapping-margin-expected.txt: Replaced FAIL by PASS expectation.
  • web-platform-tests/css/css-flexbox/hittest-overlapping-order-expected.txt: Ditto.

Source/WebCore:

When multiple child elements of a flexbox overlap (for example, due to negative margins), the element drawn in the
foreground may not actually capture the hit if the element underneath it is hit-tested despite being occluded.
This is because painting of flexbox children is done in order modified document order instead of raw document order.

In order to achieve this we should inspect flex items in reverse order modified document order. As the OrderIterator
cannot go backwards, we cache the reverse order of items when doing the layout in order to have fast hit testing in
flexbox containers.

As this behaviour is different to the one implemented in RenderBlock a new virtual method to perform hit testing of children
was extracted from RenderBlock:nodeAtPoint() to a new method called RenderBlock::hitTestChildren. The RenderBlock
implementation is identical to the current one but flexbox containers overwrite it.

Two WPT flexbox hittests are passing now thanks to this patch.

  • rendering/RenderBlock.cpp:

(WebCore::RenderBlock::hitTestChildren): Implementation of the new virtual method extracted from nodeAtPoint.
(WebCore::RenderBlock::nodeAtPoint): Moved code to hit test children to hitTestChildren()

  • rendering/RenderBlock.h: Added hitTestChildren new virtual method.
  • rendering/RenderFlexibleBox.cpp:

(WebCore::RenderFlexibleBox::hitTestChildren): Implemented.
(WebCore::RenderFlexibleBox::layoutFlexItems): Cache the reverse of the order iterator to be used by hit testing.

  • rendering/RenderFlexibleBox.h: Added hitTestChildren.
Location:
trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/imported/w3c/ChangeLog

    r263647 r263659  
     12020-06-09  Sergio Villar Senin  <svillar@igalia.com>
     2
     3        [css-flexbox] WebKit mistakenly lets pointer events (click/hover/etc) pass through flex items, if they have negative margin
     4        https://bugs.webkit.org/show_bug.cgi?id=185771
     5
     6        Reviewed by Javier Fernandez.
     7
     8        * web-platform-tests/css/css-flexbox/hittest-overlapping-margin-expected.txt: Replaced FAIL by PASS expectation.
     9        * web-platform-tests/css/css-flexbox/hittest-overlapping-order-expected.txt: Ditto.
     10
    1112020-06-29  Rob Buis  <rbuis@igalia.com>
    212
  • trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/hittest-overlapping-margin-expected.txt

    r261859 r263659  
    11foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo
    22
    3 FAIL Flexboxes should perform hit testing in reverse paint order for overlapping elements: negative margin case (crbug.com/844505) assert_equals: expected "DIV" but got "A"
     3PASS Flexboxes should perform hit testing in reverse paint order for overlapping elements: negative margin case (crbug.com/844505)
    44
  • trunk/LayoutTests/imported/w3c/web-platform-tests/css/css-flexbox/hittest-overlapping-order-expected.txt

    r261859 r263659  
    11foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoo
    22
    3 FAIL Flexboxes should perform hit testing in reverse paint order for overlapping elements: flex order case (crbug.com/844505) assert_equals: expected "DIV" but got "A"
     3PASS Flexboxes should perform hit testing in reverse paint order for overlapping elements: flex order case (crbug.com/844505)
    44
  • trunk/Source/WebCore/ChangeLog

    r263658 r263659  
     12020-06-09  Sergio Villar Senin  <svillar@igalia.com>
     2
     3        [css-flexbox] WebKit mistakenly lets pointer events (click/hover/etc) pass through flex items, if they have negative margin
     4        https://bugs.webkit.org/show_bug.cgi?id=185771
     5
     6        Reviewed by Javier Fernandez.
     7
     8        When multiple child elements of a flexbox overlap (for example, due to negative margins), the element drawn in the
     9        foreground may not actually capture the hit if the element underneath it is hit-tested despite being occluded.
     10        This is because painting of flexbox children is done in order modified document order instead of raw document order.
     11
     12        In order to achieve this we should inspect flex items in reverse order modified document order. As the OrderIterator
     13        cannot go backwards, we cache the reverse order of items when doing the layout in order to have fast hit testing in
     14        flexbox containers.
     15
     16        As this behaviour is different to the one implemented in RenderBlock a new virtual method to perform hit testing of children
     17        was extracted from RenderBlock:nodeAtPoint() to a new method called RenderBlock::hitTestChildren. The RenderBlock
     18        implementation is identical to the current one but flexbox containers overwrite it.
     19
     20        Two WPT flexbox hittests are passing now thanks to this patch.
     21
     22        * rendering/RenderBlock.cpp:
     23        (WebCore::RenderBlock::hitTestChildren): Implementation of the new virtual method extracted from nodeAtPoint.
     24        (WebCore::RenderBlock::nodeAtPoint): Moved code to hit test children to hitTestChildren()
     25        * rendering/RenderBlock.h: Added hitTestChildren new virtual method.
     26        * rendering/RenderFlexibleBox.cpp:
     27        (WebCore::RenderFlexibleBox::hitTestChildren): Implemented.
     28        (WebCore::RenderFlexibleBox::layoutFlexItems): Cache the reverse of the order iterator to be used by hit testing.
     29        * rendering/RenderFlexibleBox.h: Added hitTestChildren.
     30
    1312020-06-29  Xabier Rodriguez Calvar  <calvaris@igalia.com>
    232
  • trunk/Source/WebCore/rendering/RenderBlock.cpp

    r262481 r263659  
    20042004}
    20052005
     2006bool RenderBlock::hitTestChildren(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& adjustedLocation, HitTestAction hitTestAction)
     2007{
     2008    // Hit test descendants first.
     2009    const LayoutSize localOffset = toLayoutSize(adjustedLocation);
     2010    const LayoutSize scrolledOffset(localOffset - toLayoutSize(scrollPosition()));
     2011
     2012    if (hitTestAction == HitTestFloat && hitTestFloats(request, result, locationInContainer, toLayoutPoint(scrolledOffset)))
     2013        return true;
     2014    if (hitTestContents(request, result, locationInContainer, toLayoutPoint(scrolledOffset), hitTestAction)) {
     2015        updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - localOffset));
     2016        return true;
     2017    }
     2018    return false;
     2019}
     2020
    20062021bool RenderBlock::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
    20072022{
    2008     LayoutPoint adjustedLocation(accumulatedOffset + location());
    2009     LayoutSize localOffset = toLayoutSize(adjustedLocation);
     2023    const LayoutPoint adjustedLocation(accumulatedOffset + location());
     2024    const LayoutSize localOffset = toLayoutSize(adjustedLocation);
    20102025
    20112026    if (!isRenderView()) {
     
    20552070    bool useClip = (hasControlClip() || useOverflowClip);
    20562071    bool checkChildren = !useClip || (hasControlClip() ? locationInContainer.intersects(controlClipRect(adjustedLocation)) : locationInContainer.intersects(overflowClipRect(adjustedLocation, nullptr, IncludeOverlayScrollbarSize)));
    2057     if (checkChildren) {
    2058         // Hit test descendants first.
    2059         LayoutSize scrolledOffset(localOffset - toLayoutSize(scrollPosition()));
    2060 
    2061         if (hitTestAction == HitTestFloat && hitTestFloats(request, result, locationInContainer, toLayoutPoint(scrolledOffset)))
    2062             return true;
    2063         if (hitTestContents(request, result, locationInContainer, toLayoutPoint(scrolledOffset), hitTestAction)) {
    2064             updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - localOffset));
    2065             return true;
    2066         }
    2067     }
     2072    if (checkChildren && hitTestChildren(request, result, locationInContainer, adjustedLocation, hitTestAction))
     2073        return true;
    20682074
    20692075    if (!checkChildren && hitTestExcludedChildrenInBorder(request, result, locationInContainer, adjustedLocation, hitTestAction))
  • trunk/Source/WebCore/rendering/RenderBlock.h

    r260173 r263659  
    453453    // FIXME-BLOCKFLOW: Remove virtualization when all callers have moved to RenderBlockFlow
    454454    virtual bool hitTestFloats(const HitTestRequest&, HitTestResult&, const HitTestLocation&, const LayoutPoint&) { return false; }
     455    virtual bool hitTestChildren(const HitTestRequest&, HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& adjustedLocation, HitTestAction);
    455456    virtual bool hitTestInlineChildren(const HitTestRequest&, HitTestResult&, const HitTestLocation&, const LayoutPoint&, HitTestAction) { return false; }
    456457    bool hitTestExcludedChildrenInBorder(const HitTestRequest&, HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction);
  • trunk/Source/WebCore/rendering/RenderFlexibleBox.cpp

    r263399 r263659  
    3333
    3434#include "FlexibleBoxAlgorithm.h"
     35#include "HitTestResult.h"
    3536#include "LayoutRepainter.h"
    3637#include "RenderChildIterator.h"
     
    253254        }
    254255    }
     256}
     257
     258bool RenderFlexibleBox::hitTestChildren(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& adjustedLocation, HitTestAction hitTestAction)
     259{
     260    if (hitTestAction != HitTestForeground)
     261        return false;
     262
     263    LayoutPoint scrolledOffset = hasOverflowClip() ? adjustedLocation - toLayoutSize(scrollPosition()) : adjustedLocation;
     264
     265    for (auto* child : m_reversedOrderIteratorForHitTesting) {
     266        if (child->hasSelfPaintingLayer())
     267            continue;
     268        auto childPoint = flipForWritingModeForChild(child, scrolledOffset);
     269        if (child->hitTest(request, result, locationInContainer, childPoint)) {
     270            updateHitTestResult(result, flipForWritingMode(toLayoutPoint(locationInContainer.point() - adjustedLocation)));
     271            return true;
     272        }
     273    }
     274
     275    return false;
    255276}
    256277
     
    873894    // should work off this list of a subset.
    874895    // TODO(cbiesinger): That second part is not yet true.
     896    // Also initialize the reversed order iterator that would be eventually used for hit testing.
    875897    Vector<FlexItem> allItems;
     898    m_reversedOrderIteratorForHitTesting.clear();
    876899    m_orderIterator.first();
    877900    for (RenderBox* child = m_orderIterator.currentChild(); child; child = m_orderIterator.next()) {
     
    882905            continue;
    883906        }
     907        m_reversedOrderIteratorForHitTesting.append(child);
    884908        allItems.append(constructFlexItem(*child, relayoutChildren));
    885909    }
     910    m_reversedOrderIteratorForHitTesting.reverse();
    886911   
    887912    const LayoutUnit lineBreakLength = mainAxisContentExtent(LayoutUnit::max());
  • trunk/Source/WebCore/rendering/RenderFlexibleBox.h

    r252716 r263659  
    5858
    5959    void styleDidChange(StyleDifference, const RenderStyle*) override;
     60    bool hitTestChildren(const HitTestRequest&, HitTestResult&, const HitTestLocation&, const LayoutPoint& adjustedLocation, HitTestAction) override;
    6061    void paintChildren(PaintInfo& forSelf, const LayoutPoint&, PaintInfo& forChild, bool usePrintRect) override;
    6162
     
    209210   
    210211    mutable OrderIterator m_orderIterator { *this };
     212    Vector<RenderBox*> m_reversedOrderIteratorForHitTesting;
    211213    int m_numberOfInFlowChildrenOnFirstLine { -1 };
    212214   
Note: See TracChangeset for help on using the changeset viewer.