⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 287860 in webkit


Ignore:
Timestamp:
Jan 10, 2022, 2:58:49 PM (5 years ago)
Author:
Wenson Hsieh
Message:

Modal container observer should classify elements that are styled like clickable controls
https://bugs.webkit.org/show_bug.cgi?id=235022

Reviewed by Tim Horton.

Source/WebCore:

Broaden the criteria when considering whether or not an element inside of a detected modal container is a
"clickable control". In the case where there are event listeners on the modal container, an element inside of
the modal container that has cursor: pointer; may trigger an action on the modal container when clicked, even
if it does not have event listeners itself. Handle this scenario by considering the element to be a "clickable
control", and extract text from the element for the purposes of control classification.

Test: ModalContainerObservation.DetectControlsWithEventListenersOnModalContainer

  • page/ModalContainerObserver.cpp:

(WebCore::listensToUserActivation):

Factor out this logic into a separate helper function.

(WebCore::isClickableControl):
(WebCore::ModalContainerObserver::collectClickableElements):

Tools:

Add a new API test to exercise the change, and adjust the test harness to allow tests using the harness to
additionally add an event listener on the modal container.

  • TestWebKitAPI/Tests/WebKit/modal-container-custom.html:
  • TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm:

(TestWebKitAPI::TEST):

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r287855 r287860  
     12022-01-10  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Modal container observer should classify elements that are styled like clickable controls
     4        https://bugs.webkit.org/show_bug.cgi?id=235022
     5
     6        Reviewed by Tim Horton.
     7
     8        Broaden the criteria when considering whether or not an element inside of a detected modal container is a
     9        "clickable control". In the case where there are event listeners on the modal container, an element inside of
     10        the modal container that has `cursor: pointer;` may trigger an action on the modal container when clicked, even
     11        if it does not have event listeners itself. Handle this scenario by considering the element to be a "clickable
     12        control", and extract text from the element for the purposes of control classification.
     13
     14        Test: ModalContainerObservation.DetectControlsWithEventListenersOnModalContainer
     15
     16        * page/ModalContainerObserver.cpp:
     17        (WebCore::listensToUserActivation):
     18
     19        Factor out this logic into a separate helper function.
     20
     21        (WebCore::isClickableControl):
     22        (WebCore::ModalContainerObserver::collectClickableElements):
     23
    1242022-01-10  Eric Carlson  <eric.carlson@apple.com>
    225
  • trunk/Source/WebCore/page/ModalContainerObserver.cpp

    r287849 r287860  
    6060
    6161static constexpr size_t maxLengthForClickableElementText = 100;
     62static constexpr double maxWidthForElementsThatLookClickable = 200;
     63static constexpr double maxHeightForElementsThatLookClickable = 100;
    6264
    6365bool ModalContainerObserver::isNeededFor(const Document& document)
     
    296298}
    297299
    298 static bool isClickableControl(const HTMLElement& element)
     300static bool listensForUserActivation(const Element& element)
     301{
     302    return element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent)
     303        || element.hasEventListeners(eventNames().touchstartEvent) || element.hasEventListeners(eventNames().touchendEvent)
     304        || element.hasEventListeners(eventNames().pointerdownEvent) || element.hasEventListeners(eventNames().pointerupEvent);
     305}
     306
     307enum class ContainerListensForUserActivation : bool { No, Yes };
     308static bool isClickableControl(const HTMLElement& element, ContainerListensForUserActivation containerListensForUserActivation)
    299309{
    300310    if (element.isActuallyDisabled())
     
    327337    }
    328338
    329     return element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent)
    330         || element.hasEventListeners(eventNames().touchstartEvent) || element.hasEventListeners(eventNames().touchendEvent)
    331         || element.hasEventListeners(eventNames().pointerdownEvent) || element.hasEventListeners(eventNames().pointerupEvent);
     339    if (listensForUserActivation(element))
     340        return true;
     341
     342    if (containerListensForUserActivation == ContainerListensForUserActivation::No)
     343        return false;
     344
     345    auto rendererAndRect = element.boundingAbsoluteRectWithoutLayout();
     346    if (!rendererAndRect)
     347        return false;
     348
     349    auto [renderer, rect] = *rendererAndRect;
     350    if (!renderer || rect.isEmpty())
     351        return false;
     352
     353    // If the modal container itself has event listeners for user activation, continue looking for elements that look like
     354    // clickable elements (e.g. small nodes with pointer-style cursor).
     355    if (renderer->style().cursor() == CursorType::Pointer) {
     356        if (rect.width() <= maxWidthForElementsThatLookClickable && rect.height() <= maxHeightForElementsThatLookClickable)
     357            return true;
     358    }
     359
     360    return false;
    332361}
    333362
     
    710739        return { };
    711740
     741    auto containerListensForUserActivation = listensForUserActivation(*containerForControls) ? ContainerListensForUserActivation::Yes : ContainerListensForUserActivation::No;
    712742    Vector<Ref<HTMLElement>> clickableControls;
    713743    for (auto& child : descendantsOfType<HTMLElement>(*containerForControls)) {
    714         if (isClickableControl(child))
     744        if (isClickableControl(child, containerListensForUserActivation))
    715745            clickableControls.append(child);
    716746    }
  • trunk/Tools/ChangeLog

    r287859 r287860  
     12022-01-10  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        Modal container observer should classify elements that are styled like clickable controls
     4        https://bugs.webkit.org/show_bug.cgi?id=235022
     5
     6        Reviewed by Tim Horton.
     7
     8        Add a new API test to exercise the change, and adjust the test harness to allow tests using the harness to
     9        additionally add an event listener on the modal container.
     10
     11        * TestWebKitAPI/Tests/WebKit/modal-container-custom.html:
     12        * TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm:
     13        (TestWebKitAPI::TEST):
     14
    1152022-01-10  Jonathan Bedard  <jbedard@apple.com>
    216
  • trunk/Tools/TestWebKitAPI/Tests/WebKit/modal-container-custom.html

    r287420 r287860  
    3030    fixedContainer.style.display = "block";
    3131}
     32
     33function showWithEventListener(markup, eventType, callback) {
     34    const fixedContainer = document.getElementById("fixed");
     35    fixedContainer.addEventListener(eventType, callback);
     36    fixedContainer.innerHTML = markup;
     37    fixedContainer.style.display = "block";
     38}
    3239</script>
    3340</head>
  • trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm

    r287849 r287860  
    329329}
    330330
     331TEST(ModalContainerObservation, DetectControlsWithEventListenersOnModalContainer)
     332{
     333    auto webView = createModalContainerWebView();
     334    [webView loadBundlePage:@"modal-container-custom"];
     335    auto script = @"showWithEventListener(`<div>Hello world <span style='cursor: pointer;'>yes</span></div>`, 'click', () => window.testPassed = true)";
     336    [webView evaluate:script andDecidePolicy:_WKModalContainerDecisionHideAndAllow];
     337    [webView waitForNextPresentationUpdate];
     338    EXPECT_FALSE([[webView contentsAsString] containsString:@"Hello world"]);
     339    EXPECT_EQ([webView lastModalContainerInfo].availableTypes, _WKModalContainerControlTypePositive);
     340    EXPECT_TRUE([[webView objectByEvaluatingJavaScript:@"window.testPassed"] boolValue]);
     341}
     342
    331343} // namespace TestWebKitAPI
Note: See TracChangeset for help on using the changeset viewer.