Make requestIdleCallback suspendable
https://bugs.webkit.org/show_bug.cgi?id=203023
Reviewed by Chris Dumez.
Source/WebCore:
Make requestIdleCallback suspendable by making WindowEventLoop itself suspendable.
Because WindowEventLoop can be shared across documents, we don't want to make it an ActiveDOMObject.
Instead, we would make CachedFrameBase::restore and CachedFrame manually invoke suspend & resume.
Test: requestidlecallback/requestidlecallback-in-page-cache.html
* dom/Document.h:
(WebCore::Document::eventLoopIfExists): Added. This should probably go away once most of the event loop
is implemented since we're almost always going to have this object then.
* dom/WindowEventLoop.cpp:
(WebCore::WindowEventLoop::queueTask): Because m_tasks may contain tasks of suspended documents,
we check m_activeTaskCount, which is only positive when there is a task for non-suspended documents,
to decide whether we schedule a callback or not.
(WebCore::WindowEventLoop::suspend): Added. No-op for now.
(WebCore::WindowEventLoop::resume): Added. Schedule a callback if there is a task associated with
this document.
(WebCore::WindowEventLoop::run): Skip a task for a suspended document, and add it back to m_tasks along
with other tasks that got scheduled by running the current working set of tasks.
* dom/WindowEventLoop.h:
* history/CachedFrame.cpp:
(WebCore::CachedFrameBase::restore):
(WebCore::CachedFrame::CachedFrame):
LayoutTests:
* requestidlecallback/requestidlecallback-in-page-cache-expected.txt: Added.
* requestidlecallback/requestidlecallback-in-page-cache.html: Added.
* requestidlecallback/resources: Added.
* requestidlecallback/resources/page-cache-helper.html: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251258 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 3241276..fd3edf1 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,15 @@
+2019-10-17 Ryosuke Niwa <rniwa@webkit.org>
+
+ Make requestIdleCallback suspendable
+ https://bugs.webkit.org/show_bug.cgi?id=203023
+
+ Reviewed by Chris Dumez.
+
+ * requestidlecallback/requestidlecallback-in-page-cache-expected.txt: Added.
+ * requestidlecallback/requestidlecallback-in-page-cache.html: Added.
+ * requestidlecallback/resources: Added.
+ * requestidlecallback/resources/page-cache-helper.html: Added.
+
2019-10-17 Dirk Schulze <krit@webkit.org>
transform-box: content-box, stroke-box missing
diff --git a/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache-expected.txt b/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache-expected.txt
new file mode 100644
index 0000000..254eefd
--- /dev/null
+++ b/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache-expected.txt
@@ -0,0 +1,13 @@
+This tests that when requestIdleCallback is not enabled, requestIdleCallback and IdleDeadline are not defined.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS event.persisted is true
+PASS logs.length is 0
+PASS logs.length is 7
+PASS logs.join(", ") is "A1, B1, A2, B2, A3, B3, A4"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache.html b/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache.html
new file mode 100644
index 0000000..761cf7a
--- /dev/null
+++ b/LayoutTests/requestidlecallback/requestidlecallback-in-page-cache.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:RequestIdleCallbackEnabled=true enableBackForwardCache=true ] -->
+<html>
+<body>
+<script src="../resources/js-test.js"></script>
+<script>
+
+description('This tests that when requestIdleCallback is not enabled, requestIdleCallback and IdleDeadline are not defined.');
+jsTestIsAsync = true;
+
+const iframe = document.createElement('iframe');
+document.body.appendChild(iframe);
+
+let isInitialLoad = true;
+const logs = [];
+if (window.testRunner)
+ setTimeout(() => testRunner.notifyDone(), 3000);
+
+window.addEventListener("pageshow", function(event) {
+ if (isInitialLoad) {
+ isInitialLoad = false;
+ return;
+ }
+
+ if (window.testRunner)
+ setTimeout(() => testRunner.notifyDone(), 3000);
+
+ shouldBeTrue('event.persisted');
+ shouldBe('logs.length', '0');
+ iframe.contentWindow.requestIdleCallback(() => logs.push('B3'));
+ requestIdleCallback(() => logs.push('A4'));
+ requestIdleCallback(() => {
+ shouldBe('logs.length', '7');
+ shouldBeEqualToString('logs.join(", ")', 'A1, B1, A2, B2, A3, B3, A4');
+ finishJSTest();
+ });
+});
+
+window.addEventListener("pagehide", function(event) {
+ requestIdleCallback(() => logs.push('A1'));
+ iframe.contentWindow.requestIdleCallback(() => logs.push('B1'));
+ requestIdleCallback(() => logs.push('A2'));
+ iframe.contentWindow.requestIdleCallback(() => logs.push('B2'));
+ requestIdleCallback(() => logs.push('A3'));
+});
+
+onload = () => {
+ setTimeout(() => {
+ window.location = 'resources/page-cache-helper.html';
+ }, 0);
+}
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/requestidlecallback/resources/page-cache-helper.html b/LayoutTests/requestidlecallback/resources/page-cache-helper.html
new file mode 100644
index 0000000..4d2c787
--- /dev/null
+++ b/LayoutTests/requestidlecallback/resources/page-cache-helper.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+
+window.onload = () => {
+ requestIdleCallback(() => {
+ setTimeout(() => {
+ history.back();
+ }, 0);
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 186110d..d74dfd8 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,34 @@
+2019-10-17 Ryosuke Niwa <rniwa@webkit.org>
+
+ Make requestIdleCallback suspendable
+ https://bugs.webkit.org/show_bug.cgi?id=203023
+
+ Reviewed by Chris Dumez.
+
+ Make requestIdleCallback suspendable by making WindowEventLoop itself suspendable.
+ Because WindowEventLoop can be shared across documents, we don't want to make it an ActiveDOMObject.
+
+ Instead, we would make CachedFrameBase::restore and CachedFrame manually invoke suspend & resume.
+
+ Test: requestidlecallback/requestidlecallback-in-page-cache.html
+
+ * dom/Document.h:
+ (WebCore::Document::eventLoopIfExists): Added. This should probably go away once most of the event loop
+ is implemented since we're almost always going to have this object then.
+ * dom/WindowEventLoop.cpp:
+ (WebCore::WindowEventLoop::queueTask): Because m_tasks may contain tasks of suspended documents,
+ we check m_activeTaskCount, which is only positive when there is a task for non-suspended documents,
+ to decide whether we schedule a callback or not.
+ (WebCore::WindowEventLoop::suspend): Added. No-op for now.
+ (WebCore::WindowEventLoop::resume): Added. Schedule a callback if there is a task associated with
+ this document.
+ (WebCore::WindowEventLoop::run): Skip a task for a suspended document, and add it back to m_tasks along
+ with other tasks that got scheduled by running the current working set of tasks.
+ * dom/WindowEventLoop.h:
+ * history/CachedFrame.cpp:
+ (WebCore::CachedFrameBase::restore):
+ (WebCore::CachedFrame::CachedFrame):
+
2019-10-17 Chris Dumez <cdumez@apple.com>
Don't put pages that have not reached the non-visually empty layout milestone in the back/forward cache
diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h
index 986f9b7..566a4f8 100644
--- a/Source/WebCore/dom/Document.h
+++ b/Source/WebCore/dom/Document.h
@@ -1061,6 +1061,7 @@
WEBCORE_EXPORT void postTask(Task&&) final; // Executes the task on context's thread asynchronously.
WindowEventLoop& eventLoop();
+ WindowEventLoop* eventLoopIfExists() { return m_eventLoop.get(); }
ScriptedAnimationController* scriptedAnimationController() { return m_scriptedAnimationController.get(); }
void suspendScriptedAnimationControllerCallbacks();
diff --git a/Source/WebCore/dom/WindowEventLoop.cpp b/Source/WebCore/dom/WindowEventLoop.cpp
index d188ad6..698d463 100644
--- a/Source/WebCore/dom/WindowEventLoop.cpp
+++ b/Source/WebCore/dom/WindowEventLoop.cpp
@@ -41,25 +41,49 @@
void WindowEventLoop::queueTask(TaskSource source, Document& document, TaskFunction&& task)
{
- if (m_tasks.isEmpty()) {
+ if (!m_activeTaskCount) {
callOnMainThread([eventLoop = makeRef(*this)] () {
eventLoop->run();
});
}
+ ++m_activeTaskCount;
m_tasks.append(Task { source, WTFMove(task), document.identifier() });
}
+void WindowEventLoop::suspend(Document&)
+{
+}
+
+void WindowEventLoop::resume(Document& document)
+{
+ if (!m_documentIdentifiersForSuspendedTasks.contains(document.identifier()))
+ return;
+
+ callOnMainThread([eventLoop = makeRef(*this)] () {
+ eventLoop->run();
+ });
+}
+
void WindowEventLoop::run()
{
+ m_activeTaskCount = 0;
Vector<Task> tasks = WTFMove(m_tasks);
- ASSERT(m_tasks.isEmpty());
+ m_documentIdentifiersForSuspendedTasks.clear();
+ Vector<Task> remainingTasks;
for (auto& task : tasks) {
auto* document = Document::allDocumentsMap().get(task.documentIdentifier);
if (!document || document->activeDOMObjectsAreStopped())
continue;
- // Skip tasks associated with suspended documents.
+ if (document->activeDOMObjectsAreSuspended()) {
+ m_documentIdentifiersForSuspendedTasks.add(task.documentIdentifier);
+ remainingTasks.append(WTFMove(task));
+ continue;
+ }
task.task();
}
+ for (auto& task : m_tasks)
+ remainingTasks.append(WTFMove(task));
+ m_tasks = WTFMove(remainingTasks);
}
} // namespace WebCore
diff --git a/Source/WebCore/dom/WindowEventLoop.h b/Source/WebCore/dom/WindowEventLoop.h
index 714de66..5e38bf5 100644
--- a/Source/WebCore/dom/WindowEventLoop.h
+++ b/Source/WebCore/dom/WindowEventLoop.h
@@ -27,7 +27,8 @@
#include "DocumentIdentifier.h"
#include <memory>
-#include <wtf/HashMap.h>
+#include <wtf/HashSet.h>
+#include <wtf/Vector.h>
namespace WebCore {
@@ -47,6 +48,9 @@
void queueTask(TaskSource, Document&, TaskFunction&&);
+ void suspend(Document&);
+ void resume(Document&);
+
private:
WindowEventLoop();
@@ -60,6 +64,8 @@
// Use a global queue instead of multiple task queues since HTML5 spec allows UA to pick arbitrary queue.
Vector<Task> m_tasks;
+ size_t m_activeTaskCount { 0 };
+ HashSet<DocumentIdentifier> m_documentIdentifiersForSuspendedTasks;
};
} // namespace WebCore
diff --git a/Source/WebCore/history/CachedFrame.cpp b/Source/WebCore/history/CachedFrame.cpp
index 87ab173..1e8c02e 100644
--- a/Source/WebCore/history/CachedFrame.cpp
+++ b/Source/WebCore/history/CachedFrame.cpp
@@ -47,6 +47,7 @@
#include "ScriptController.h"
#include "SerializedScriptValue.h"
#include "StyleTreeResolver.h"
+#include "WindowEventLoop.h"
#include <wtf/RefCountedLeakCounter.h>
#include <wtf/text/CString.h>
@@ -106,6 +107,9 @@
if (m_document->svgExtensions())
m_document->accessSVGExtensions().unpauseAnimations();
+ if (auto* eventLoop = m_document->eventLoopIfExists())
+ eventLoop->resume(*m_document);
+
m_document->resume(ReasonForSuspension::BackForwardCache);
// It is necessary to update any platform script objects after restoring the
@@ -171,6 +175,9 @@
// Active DOM objects must be suspended before we cache the frame script data.
m_document->suspend(ReasonForSuspension::BackForwardCache);
+ if (auto* eventLoop = m_document->eventLoopIfExists())
+ eventLoop->suspend(*m_document);
+
m_cachedFrameScriptData = makeUnique<ScriptCachedFrameData>(frame);
m_document->domWindow()->suspendForBackForwardCache();