Integrate resize event with HTML5 event loop
https://bugs.webkit.org/show_bug.cgi?id=202964

Reviewed by Geoffrey Garen.

Source/WebCore:

Dispatch resize events in "run the resize steps" during the "update the rendering":
https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering

Exisitng code in WebCore which was dispatching or scheduling dispatching of resize events now simply sets
a flag on document and schedules a rendering update. In Page::updateRendering, we fire resize events on
any documents with this flag set.

Test: fast/events/resize-subframe-in-rendering-update.html

* dom/Document.cpp:
(WebCore::Document::setNeedsDOMWindowResizeEvent): Added.
(WebCore::Document::setNeedsVisualViewportResize): Added.
(WebCore::Document::runResizeSteps): Added. https://drafts.csswg.org/cssom-view/#run-the-resize-steps
* dom/Document.h:
* page/DOMWindow.cpp:
(WebCore::DOMWindow::resizeTo const):
* page/FrameView.cpp:
(WebCore::FrameView::sendResizeEventIfNeeded): Now sets m_needsDOMWindowResizeEvent on Document instead of
enqueuing a resize event.
* page/Page.cpp:
(WebCore::Page::updateRendering): Call runResizeSteps on each document.
(WebCore::Page::collectDocuments): Added.
* page/Page.h:
* page/VisualViewport.cpp:
(WebCore::VisualViewport::enqueueResizeEvent):

LayoutTests:

Added a regression test and fixed an existing test to work with the new behavior.

* fast/events/resize-subframe-in-rendering-update-expected.txt: Added.
* fast/events/resize-subframe-in-rendering-update.html: Added.
* fast/shadow-dom/trusted-event-scoped-flags.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251269 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index feb08ea..1cb4a106 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,16 @@
+2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Integrate resize event with HTML5 event loop
+        https://bugs.webkit.org/show_bug.cgi?id=202964
+
+        Reviewed by Geoffrey Garen.
+
+        Added a regression test and fixed an existing test to work with the new behavior.
+
+        * fast/events/resize-subframe-in-rendering-update-expected.txt: Added.
+        * fast/events/resize-subframe-in-rendering-update.html: Added.
+        * fast/shadow-dom/trusted-event-scoped-flags.html:
+
 2019-10-17  Tim Horton  <timothy_horton@apple.com>
 
         Land a missing test baseline
diff --git a/LayoutTests/fast/events/resize-subframe-in-rendering-update-expected.txt b/LayoutTests/fast/events/resize-subframe-in-rendering-update-expected.txt
new file mode 100644
index 0000000..27af3b1
--- /dev/null
+++ b/LayoutTests/fast/events/resize-subframe-in-rendering-update-expected.txt
@@ -0,0 +1,16 @@
+This tests that resize event is not dispatched as a part of updateLayout
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+iframeB.style.width = "200px"; updateLayout(iframeB)
+iframeA.style.width = "200px"; updateLayout(iframeA)
+iframeAA.style.width = "200px"; updateLayout(iframeAA)
+PASS logs.length is 0
+After requestAnimationFrame
+PASS logs.length is 3
+PASS logs.join(", ") is "A, AA, B"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/events/resize-subframe-in-rendering-update.html b/LayoutTests/fast/events/resize-subframe-in-rendering-update.html
new file mode 100644
index 0000000..4c207fd
--- /dev/null
+++ b/LayoutTests/fast/events/resize-subframe-in-rendering-update.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test.js"></script>
+<script>
+
+description('This tests that resize event is not dispatched as a part of updateLayout');
+jsTestIsAsync = true;
+
+function createIframe(parentDocument)
+{
+    const iframe = document.createElement('iframe');
+    iframe.style.width = '100px';
+    iframe.style.height = '100px';
+    parentDocument.body.appendChild(iframe);
+    iframe.contentDocument.body.innerHTML = '<span>hello, world</span>';
+    return iframe;
+}
+
+function updateLayout(iframe)
+{
+    iframe.contentDocument.querySelector("span").getBoundingClientRect();
+}
+
+const iframeA = createIframe(document);
+const iframeAA = createIframe(iframeA.contentDocument);
+const iframeB = createIframe(document);
+
+const logs = [];
+requestAnimationFrame(() => {
+    setTimeout(() => {
+        iframeAA.contentWindow.addEventListener('resize', () => logs.push('AA'));
+        iframeA.contentWindow.addEventListener('resize', () => logs.push('A'));
+        iframeB.contentWindow.addEventListener('resize', () => logs.push('B'));
+
+        evalAndLog('iframeB.style.width = "200px"; updateLayout(iframeB)');
+        evalAndLog('iframeA.style.width = "200px"; updateLayout(iframeA)');
+        evalAndLog('iframeAA.style.width = "200px"; updateLayout(iframeAA)');
+        shouldBe('logs.length', '0');
+        setTimeout(() => {
+            debug('After 0s setTimeout');
+            shouldBe('logs.length', '0');
+        }, 0);
+        requestAnimationFrame(() => {
+            debug('After requestAnimationFrame');
+            shouldBe('logs.length', '3');
+            shouldBeEqualToString('logs.join(", ")', 'A, AA, B');
+            iframeA.remove();
+            iframeB.remove();
+            finishJSTest();
+        });
+    }, 0);
+});
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html b/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html
index 60f4902..a6a6b95 100644
--- a/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html
+++ b/LayoutTests/fast/shadow-dom/trusted-event-scoped-flags.html
@@ -110,17 +110,19 @@
     iframe.style.height = '100px';
 
     iframe.onload = function () {
-        iframe.contentDocument.body.getBoundingClientRect();
-        log(iframe.contentWindow, "resize");
-        setTimeout(function () {
-            iframe.style.width = '200px';
-            iframe.style.height = '200px';
-            iframe.contentDocument.body.getBoundingClientRect();
+        requestAnimationFrame(function () {
             setTimeout(function () {
-                checkFlags('', {eventType: 'resize', composed: false});
-                finishJSTest();
+                iframe.contentDocument.body.getBoundingClientRect();
+                log(iframe.contentWindow, "resize");
+                iframe.style.width = '200px';
+                iframe.style.height = '200px';
+                iframe.contentDocument.body.getBoundingClientRect();
+                requestAnimationFrame(function () {
+                    checkFlags('', {eventType: 'resize', composed: false});
+                    finishJSTest();
+                });
             }, 0);
-        }, 0);
+        });
     }
 
     document.body.appendChild(iframe);
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index a850492..d8af05f 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,36 @@
+2019-10-17  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Integrate resize event with HTML5 event loop
+        https://bugs.webkit.org/show_bug.cgi?id=202964
+
+        Reviewed by Geoffrey Garen.
+
+        Dispatch resize events in "run the resize steps" during the "update the rendering":
+        https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
+
+        Exisitng code in WebCore which was dispatching or scheduling dispatching of resize events now simply sets
+        a flag on document and schedules a rendering update. In Page::updateRendering, we fire resize events on
+        any documents with this flag set.
+
+        Test: fast/events/resize-subframe-in-rendering-update.html
+
+        * dom/Document.cpp:
+        (WebCore::Document::setNeedsDOMWindowResizeEvent): Added.
+        (WebCore::Document::setNeedsVisualViewportResize): Added.
+        (WebCore::Document::runResizeSteps): Added. https://drafts.csswg.org/cssom-view/#run-the-resize-steps
+        * dom/Document.h:
+        * page/DOMWindow.cpp:
+        (WebCore::DOMWindow::resizeTo const):
+        * page/FrameView.cpp:
+        (WebCore::FrameView::sendResizeEventIfNeeded): Now sets m_needsDOMWindowResizeEvent on Document instead of
+        enqueuing a resize event.
+        * page/Page.cpp:
+        (WebCore::Page::updateRendering): Call runResizeSteps on each document.
+        (WebCore::Page::collectDocuments): Added.
+        * page/Page.h:
+        * page/VisualViewport.cpp:
+        (WebCore::VisualViewport::enqueueResizeEvent):
+
 2019-10-17  Chris Dumez  <cdumez@apple.com>
 
         Unreviewed, rolling out r251255.
diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp
index 4d40c40..c91bc198 100644
--- a/Source/WebCore/dom/Document.cpp
+++ b/Source/WebCore/dom/Document.cpp
@@ -222,6 +222,7 @@
 #include "ValidationMessageClient.h"
 #include "VisibilityChangeClient.h"
 #include "VisitedLinkState.h"
+#include "VisualViewport.h"
 #include "WebAnimation.h"
 #include "WheelEvent.h"
 #include "WindowEventLoop.h"
@@ -3960,6 +3961,35 @@
     }
 }
 
+void Document::setNeedsDOMWindowResizeEvent()
+{
+    m_needsDOMWindowResizeEvent = true;
+    scheduleTimedRenderingUpdate();
+}
+
+void Document::setNeedsVisualViewportResize()
+{
+    m_needsVisualViewportResizeEvent = true;
+    scheduleTimedRenderingUpdate();
+}
+
+// https://drafts.csswg.org/cssom-view/#run-the-resize-steps
+void Document::runResizeSteps()
+{
+    // FIXME: The order of dispatching is not specified: https://github.com/WICG/visual-viewport/issues/65.
+    if (m_needsDOMWindowResizeEvent) {
+        LOG(Events, "Document %p sending resize events to window", this);
+        m_needsDOMWindowResizeEvent = false;
+        dispatchWindowEvent(Event::create(eventNames().resizeEvent, Event::CanBubble::No, Event::IsCancelable::No));
+    }
+    if (m_needsVisualViewportResizeEvent) {
+        LOG(Events, "Document %p sending resize events to visualViewport", this);
+        m_needsVisualViewportResizeEvent = false;
+        if (auto* window = domWindow())
+            window->visualViewport().dispatchEvent(Event::create(eventNames().resizeEvent, Event::CanBubble::No, Event::IsCancelable::No));
+    }
+}
+
 void Document::addAudioProducer(MediaProducer& audioProducer)
 {
     m_audioProducers.add(audioProducer);
diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h
index 566a4f8..ea16ce7 100644
--- a/Source/WebCore/dom/Document.h
+++ b/Source/WebCore/dom/Document.h
@@ -1358,6 +1358,10 @@
     bool hasStyleWithViewportUnits() const { return m_hasStyleWithViewportUnits; }
     void updateViewportUnitsOnResize();
 
+    void setNeedsDOMWindowResizeEvent();
+    void setNeedsVisualViewportResize();
+    void runResizeSteps();
+
     WEBCORE_EXPORT void addAudioProducer(MediaProducer&);
     WEBCORE_EXPORT void removeAudioProducer(MediaProducer&);
     MediaProducer::MediaStateFlags mediaState() const { return m_mediaState; }
@@ -2007,6 +2011,8 @@
     bool m_hasPreparedForDestruction { false };
 
     bool m_hasStyleWithViewportUnits { false };
+    bool m_needsDOMWindowResizeEvent { false };
+    bool m_needsVisualViewportResizeEvent { false };
     bool m_isTimerThrottlingEnabled { false };
     bool m_isSuspended { false };
 
diff --git a/Source/WebCore/page/FrameView.cpp b/Source/WebCore/page/FrameView.cpp
index 78c3db4..015a6d1 100644
--- a/Source/WebCore/page/FrameView.cpp
+++ b/Source/WebCore/page/FrameView.cpp
@@ -3376,21 +3376,10 @@
     }
 #endif
 
+    LOG(Events, "FrameView %p sendResizeEventIfNeeded scheduling resize event for document %p, size %dx%d", this, frame().document(), currentSize.width(), currentSize.height());
+    frame().document()->setNeedsDOMWindowResizeEvent();
+
     bool isMainFrame = frame().isMainFrame();
-    bool canSendResizeEventSynchronously = isMainFrame && !m_shouldAutoSize;
-
-    LOG(Events, "FrameView %p sendResizeEventIfNeeded sending resize event, size %dx%d (canSendResizeEventSynchronously %d)", this, currentSize.width(), currentSize.height(), canSendResizeEventSynchronously);
-
-    Ref<Event> resizeEvent = Event::create(eventNames().resizeEvent, Event::CanBubble::No, Event::IsCancelable::No);
-    if (canSendResizeEventSynchronously)
-        frame().document()->dispatchWindowEvent(resizeEvent);
-    else {
-        // FIXME: Queueing this event for an unpredictable time in the future seems
-        // intrinsically racy. By the time this resize event fires, the frame might
-        // be resized again, so we could end up with two resize events for the same size.
-        frame().document()->enqueueWindowEvent(WTFMove(resizeEvent));
-    }
-
     if (InspectorInstrumentation::hasFrontends() && isMainFrame) {
         if (Page* page = frame().page()) {
             if (InspectorClient* inspectorClient = page->inspectorController().inspectorClient())
diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp
index 3f1467b..161b8a2 100644
--- a/Source/WebCore/page/Page.cpp
+++ b/Source/WebCore/page/Page.cpp
@@ -1294,19 +1294,16 @@
 
     SetForScope<bool> change(m_inUpdateRendering, true);
 
-    Vector<RefPtr<Document>> documents;
+    layoutIfNeeded();
 
-    // The requestAnimationFrame callbacks may change the frame hierarchy of the page
-    forEachDocument([&documents] (Document& document) {
-        documents.append(&document);
-    });
-
-    // FIXME: Run the resize steps
+    for (auto& document : collectDocuments())
+        document->runResizeSteps();
 
     // FIXME: Run the scroll steps
 
     // FIXME: Evaluate media queries and report changes.
 
+    Vector<Ref<Document>> documents = collectDocuments(); // The requestAnimationFrame callbacks may change the frame hierarchy of the page
     for (auto& document : documents) {
         DOMHighResTimeStamp timestamp = document->domWindow()->nowTimestamp();
         document->updateAnimationsAndSendEvents(timestamp);
@@ -2873,6 +2870,18 @@
     }
 }
 
+Vector<Ref<Document>> Page::collectDocuments()
+{
+    Vector<Ref<Document>> documents;
+    for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
+        auto* document = frame->document();
+        if (!document)
+            continue;
+        documents.append(*document);
+    }
+    return documents;
+}
+
 void Page::applicationWillResignActive()
 {
     forEachDocument([&] (Document& document) {
diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h
index 831f0a0..3462741 100644
--- a/Source/WebCore/page/Page.h
+++ b/Source/WebCore/page/Page.h
@@ -750,6 +750,7 @@
     void handleLowModePowerChange(bool);
 
     void forEachDocument(const WTF::Function<void(Document&)>&);
+    Vector<Ref<Document>> collectDocuments();
 
     enum class TimerThrottlingState { Disabled, Enabled, EnabledIncreasing };
     void hiddenPageDOMTimerThrottlingStateChanged();
diff --git a/Source/WebCore/page/VisualViewport.cpp b/Source/WebCore/page/VisualViewport.cpp
index bb07f84..cc28edc 100644
--- a/Source/WebCore/page/VisualViewport.cpp
+++ b/Source/WebCore/page/VisualViewport.cpp
@@ -185,8 +185,7 @@
     auto* frame = this->frame();
     if (!frame)
         return;
-
-    frame->document()->eventQueue().enqueueResizeEvent(*this, Event::CanBubble::No, Event::IsCancelable::No);
+    frame->document()->setNeedsVisualViewportResize();
 }
 
 void VisualViewport::enqueueScrollEvent()