Title: [287777] trunk
Revision
287777
Author
wenson_hs...@apple.com
Date
2022-01-07 12:28:04 -0800 (Fri, 07 Jan 2022)

Log Message

Teach modal container observer to make the body element scrollable if necessary
https://bugs.webkit.org/show_bug.cgi?id=234708
rdar://86960677

Reviewed by Tim Horton.

Source/WebCore:

Add a mechanism to allow ModalContainerObserver to force the body and/or document elements in the main document
to become vertically scrollable, in the case where a modal container has been detected.

In particular, if a modal container has already been detected and hidden away, the frame is non-scrollable, and
the body and/or document element satisfies both conditions:

1. Has a height that is taller than the visible height of the top FrameView
2. Has `overflow-y: hidden;`

...then we'll flag one or both of those elements and force them to be vertically scrollable during style
adjustment (i.e. return `true` from `shouldMakeVerticallyScrollable()`).

Covered by augmenting an existing API test:
ModalContainerObservation.HideUserInteractionBlockingElementAndMakeDocumentScrollable

* page/ModalContainerObserver.cpp:
(WebCore::ModalContainerObserver::setContainer):
(WebCore::ModalContainerObserver::collectClickableElementsTimerFired):
(WebCore::ModalContainerObserver::makeBodyAndDocumentElementScrollableIfNeeded):

Helper method that contains logic for overriding scrollability on the body or html element, if needed.

(WebCore::ModalContainerObserver::clearScrollabilityOverrides):
(WebCore::ModalContainerObserver::hideUserInteractionBlockingElementIfNeeded):

Drive-by fix: `target` is just a raw pointer here, so just assign it directly to `foundElement` instead of
trying to use move semantics.

(WebCore::ModalContainerObserver::revealModalContainer):
(WebCore::ModalContainerObserver::shouldMakeVerticallyScrollable const):

Add a helper method (similar to `shouldHide()`) that can be used to make elements vertically scrollable during
style adjustment time. See above for more details.

(WebCore::ModalContainerObserver::tryToMakeBodyAndDocumentElementScrollableThroughQuirks):
* page/ModalContainerObserver.h:
* style/StyleAdjuster.cpp:
(WebCore::Style::Adjuster::adjust const):

Consult `shouldMakeVerticallyScrollable` in addition to `shouldHide` if `ModalContainerObserver` is present.

Tools:

Adjust an existing API test to exercise the change.

* TestWebKitAPI/Tests/WebKit/modal-container-with-overlay.html:
* TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm:
(TestWebKitAPI::TEST):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (287776 => 287777)


--- trunk/Source/WebCore/ChangeLog	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Source/WebCore/ChangeLog	2022-01-07 20:28:04 UTC (rev 287777)
@@ -1,3 +1,52 @@
+2022-01-07  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Teach modal container observer to make the body element scrollable if necessary
+        https://bugs.webkit.org/show_bug.cgi?id=234708
+        rdar://86960677
+
+        Reviewed by Tim Horton.
+
+        Add a mechanism to allow ModalContainerObserver to force the body and/or document elements in the main document
+        to become vertically scrollable, in the case where a modal container has been detected.
+
+        In particular, if a modal container has already been detected and hidden away, the frame is non-scrollable, and
+        the body and/or document element satisfies both conditions:
+
+        1. Has a height that is taller than the visible height of the top FrameView
+        2. Has `overflow-y: hidden;`
+
+        ...then we'll flag one or both of those elements and force them to be vertically scrollable during style
+        adjustment (i.e. return `true` from `shouldMakeVerticallyScrollable()`).
+
+        Covered by augmenting an existing API test:
+        ModalContainerObservation.HideUserInteractionBlockingElementAndMakeDocumentScrollable
+
+        * page/ModalContainerObserver.cpp:
+        (WebCore::ModalContainerObserver::setContainer):
+        (WebCore::ModalContainerObserver::collectClickableElementsTimerFired):
+        (WebCore::ModalContainerObserver::makeBodyAndDocumentElementScrollableIfNeeded):
+
+        Helper method that contains logic for overriding scrollability on the body or html element, if needed.
+
+        (WebCore::ModalContainerObserver::clearScrollabilityOverrides):
+        (WebCore::ModalContainerObserver::hideUserInteractionBlockingElementIfNeeded):
+
+        Drive-by fix: `target` is just a raw pointer here, so just assign it directly to `foundElement` instead of
+        trying to use move semantics.
+
+        (WebCore::ModalContainerObserver::revealModalContainer):
+        (WebCore::ModalContainerObserver::shouldMakeVerticallyScrollable const):
+
+        Add a helper method (similar to `shouldHide()`) that can be used to make elements vertically scrollable during
+        style adjustment time. See above for more details.
+
+        (WebCore::ModalContainerObserver::tryToMakeBodyAndDocumentElementScrollableThroughQuirks):
+        * page/ModalContainerObserver.h:
+        * style/StyleAdjuster.cpp:
+        (WebCore::Style::Adjuster::adjust const):
+
+        Consult `shouldMakeVerticallyScrollable` in addition to `shouldHide` if `ModalContainerObserver` is present.
+
 2022-01-07  Antti Koivisto  <an...@apple.com>
 
         Make separate invalidation rulesets for negated selectors (inside :not())

Modified: trunk/Source/WebCore/page/ModalContainerObserver.cpp (287776 => 287777)


--- trunk/Source/WebCore/page/ModalContainerObserver.cpp	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Source/WebCore/page/ModalContainerObserver.cpp	2022-01-07 20:28:04 UTC (rev 287777)
@@ -262,8 +262,12 @@
         if (!container)
             return;
 
-        if (auto observer = container->document().modalContainerObserverIfExists(); observer && container == observer->container())
-            observer->hideUserInteractionBlockingElementIfNeeded();
+        auto observer = container->document().modalContainerObserverIfExists();
+        if (!observer || container != observer->container())
+            return;
+
+        observer->hideUserInteractionBlockingElementIfNeeded();
+        observer->makeBodyAndDocumentElementScrollableIfNeeded();
     });
 }
 
@@ -538,8 +542,10 @@
                 if (decision == ModalContainerDecision::Show)
                     return;
 
-                if (RefPtr controlToClick = classifiedControls.controlToClick(decision))
+                if (RefPtr controlToClick = classifiedControls.controlToClick(decision)) {
+                    observer->clearScrollabilityOverrides(*document);
                     controlToClick->dispatchSimulatedClick(nullptr, SendMouseUpDownEvents, DoNotShowPressedLook);
+                }
 
                 decisionScope.continueHidingModalContainerAfterScope();
             });
@@ -547,13 +553,63 @@
     });
 }
 
-void ModalContainerObserver::hideUserInteractionBlockingElementIfNeeded()
+void ModalContainerObserver::makeBodyAndDocumentElementScrollableIfNeeded()
 {
-    if (m_userInteractionBlockingElement) {
-        ASSERT_NOT_REACHED();
+    if (!container())
         return;
+
+    Ref document = container()->document();
+    RefPtr view = document->view();
+    if (!view || view->isScrollable())
+        return;
+
+    document->updateLayoutIgnorePendingStylesheets();
+
+    auto visibleHeight = view->visibleSize().height();
+    auto shouldMakeElementScrollable = [visibleHeight] (Element* element) {
+        if (!element)
+            return false;
+
+        auto renderer = element->renderer();
+        if (!renderer || renderer->style().overflowY() != Overflow::Hidden)
+            return false;
+
+        return element->boundingClientRect().height() > visibleHeight;
+    };
+
+    if (!m_makeBodyElementScrollable) {
+        if (RefPtr body = document->body(); shouldMakeElementScrollable(body.get())) {
+            m_makeBodyElementScrollable = true;
+            body->invalidateStyle();
+        }
     }
 
+    if (!m_makeDocumentElementScrollable) {
+        if (RefPtr documentElement = document->documentElement(); shouldMakeElementScrollable(documentElement.get())) {
+            m_makeDocumentElementScrollable = true;
+            documentElement->invalidateStyle();
+        }
+    }
+}
+
+void ModalContainerObserver::clearScrollabilityOverrides(Document& document)
+{
+    if (std::exchange(m_makeBodyElementScrollable, false)) {
+        if (auto element = document.body())
+            element->invalidateStyle();
+    }
+
+    if (std::exchange(m_makeDocumentElementScrollable, false)) {
+        if (auto element = document.documentElement())
+            element->invalidateStyle();
+    }
+}
+
+void ModalContainerObserver::hideUserInteractionBlockingElementIfNeeded()
+{
+    if (m_userInteractionBlockingElement)
+        return;
+
     RefPtr container = this->container();
     if (!container) {
         ASSERT_NOT_REACHED();
@@ -593,7 +649,7 @@
             return;
 
         if (!foundElement)
-            foundElement = WTFMove(target);
+            foundElement = target;
     }
 
     m_userInteractionBlockingElement = foundElement.get();
@@ -603,8 +659,10 @@
 void ModalContainerObserver::revealModalContainer()
 {
     auto [container, frameOwner] = std::exchange(m_containerAndFrameOwnerForControls, { });
-    if (container)
+    if (container) {
         container->invalidateStyle();
+        clearScrollabilityOverrides(container->document());
+    }
 
     if (auto element = std::exchange(m_userInteractionBlockingElement, { }))
         element->invalidateStyle();
@@ -660,4 +718,15 @@
     return { WTFMove(classifiableControls), WTFMove(controlTextsToClassify) };
 }
 
+bool ModalContainerObserver::shouldMakeVerticallyScrollable(const Element& element) const
+{
+    if (m_makeBodyElementScrollable && element.document().body() == &element)
+        return true;
+
+    if (m_makeDocumentElementScrollable && element.document().documentElement() == &element)
+        return true;
+
+    return false;
+}
+
 } // namespace WebCore

Modified: trunk/Source/WebCore/page/ModalContainerObserver.h (287776 => 287777)


--- trunk/Source/WebCore/page/ModalContainerObserver.h	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Source/WebCore/page/ModalContainerObserver.h	2022-01-07 20:28:04 UTC (rev 287777)
@@ -50,6 +50,7 @@
     ModalContainerObserver();
     ~ModalContainerObserver();
 
+    bool shouldMakeVerticallyScrollable(const Element&) const;
     inline bool shouldHide(const Element&) const;
     void updateModalContainerIfNeeded(const FrameView&);
 
@@ -64,6 +65,9 @@
     void setContainer(Element&, HTMLFrameOwnerElement* = nullptr);
     void searchForModalContainerOnBehalfOfFrameOwnerIfNeeded(HTMLFrameOwnerElement&);
 
+    void makeBodyAndDocumentElementScrollableIfNeeded();
+    void clearScrollabilityOverrides(Document&);
+
     Element* container() const;
     HTMLFrameOwnerElement* frameOwnerForControls() const;
 
@@ -78,6 +82,8 @@
     Timer m_collectClickableElementsTimer;
     bool m_collectingClickableElements { false };
     bool m_hasAttemptedToFulfillPolicy { false };
+    bool m_makeBodyElementScrollable { false };
+    bool m_makeDocumentElementScrollable { false };
 };
 
 inline void ModalContainerObserver::overrideSearchTermForTesting(const String& searchTerm)

Modified: trunk/Source/WebCore/style/StyleAdjuster.cpp (287776 => 287777)


--- trunk/Source/WebCore/style/StyleAdjuster.cpp	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Source/WebCore/style/StyleAdjuster.cpp	2022-01-07 20:28:04 UTC (rev 287777)
@@ -553,8 +553,12 @@
 #endif
 
     if (m_element) {
-        if (auto observer = m_element->document().modalContainerObserver(); observer && observer->shouldHide(*m_element))
-            style.setDisplay(DisplayType::None);
+        if (auto observer = m_element->document().modalContainerObserverIfExists()) {
+            if (observer->shouldHide(*m_element))
+                style.setDisplay(DisplayType::None);
+            if (observer->shouldMakeVerticallyScrollable(*m_element))
+                style.setOverflowY(Overflow::Auto);
+        }
     }
 
     adjustForSiteSpecificQuirks(style);

Modified: trunk/Tools/ChangeLog (287776 => 287777)


--- trunk/Tools/ChangeLog	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Tools/ChangeLog	2022-01-07 20:28:04 UTC (rev 287777)
@@ -1,3 +1,17 @@
+2022-01-07  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Teach modal container observer to make the body element scrollable if necessary
+        https://bugs.webkit.org/show_bug.cgi?id=234708
+        rdar://86960677
+
+        Reviewed by Tim Horton.
+
+        Adjust an existing API test to exercise the change.
+
+        * TestWebKitAPI/Tests/WebKit/modal-container-with-overlay.html:
+        * TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm:
+        (TestWebKitAPI::TEST):
+
 2022-01-07  Ryan Haddad  <ryanhad...@apple.com>
 
         REGRESSION: TestWebKitAPI.PublicSuffix.IsPublicSuffix is failing on some bots

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKit/modal-container-with-overlay.html (287776 => 287777)


--- trunk/Tools/TestWebKitAPI/Tests/WebKit/modal-container-with-overlay.html	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKit/modal-container-with-overlay.html	2022-01-07 20:28:04 UTC (rev 287777)
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html>
+<html style="overflow: hidden;">
 <head>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <style>
@@ -9,6 +9,10 @@
     font-family: system-ui;
 }
 
+body {
+    height: 3000px;
+}
+
 button {
     padding: 6px;
     border-radius: 6px;
@@ -58,7 +62,7 @@
     internals.overrideModalContainerSearchTermForTesting("hello world");
 </script>
 </head>
-<body>
+<body style="overflow: hidden;">
 <p id="content">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.</p>
 <div id="overlay"></div>
 <div id="fixed">
@@ -67,6 +71,8 @@
 </div>
 <script>
 button.addEventListener("click", () => {
+    document.body.style.removeProperty("overflow");
+    document.documentElement.style.removeProperty("overflow");
     fixed.remove();
     overlay.remove();
 });

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm (287776 => 287777)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm	2022-01-07 20:20:58 UTC (rev 287776)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ModalContainerObservation.mm	2022-01-07 20:28:04 UTC (rev 287777)
@@ -256,7 +256,7 @@
     EXPECT_EQ([webView lastModalContainerInfo].availableTypes, _WKModalContainerControlTypePositive | _WKModalContainerControlTypeNegative);
 }
 
-TEST(ModalContainerObservation, HideUserInteractionBlockingElement)
+TEST(ModalContainerObservation, HideUserInteractionBlockingElementAndMakeDocumentScrollable)
 {
     auto webView = createModalContainerWebView();
     [webView loadBundlePage:@"modal-container-with-overlay" andDecidePolicy:_WKModalContainerDecisionHideAndIgnore];
@@ -266,6 +266,8 @@
 
     NSString *hitTestedText = [webView stringByEvaluatingJavaScript:@"document.elementFromPoint(50, 50).textContent"];
     EXPECT_TRUE([hitTestedText containsString:@"Lorem"]);
+    EXPECT_WK_STREQ("auto", [webView stringByEvaluatingJavaScript:@"getComputedStyle(document.documentElement).overflowY"]);
+    EXPECT_WK_STREQ("auto", [webView stringByEvaluatingJavaScript:@"getComputedStyle(document.body).overflowY"]);
 }
 
 TEST(ModalContainerObservation, IgnoreFixedDocumentElement)
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to