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()