XMLHttpRequest should not prevent entering the back/forward cache
https://bugs.webkit.org/show_bug.cgi?id=203107
<rdar://problem/56438647>
Reviewed by Youenn Fablet.
LayoutTests/imported/w3c:
Rebaseline a new WPT tests that are passing now that we properly check that the
Document is fully active in open().
* web-platform-tests/xhr/open-url-multi-window-2-expected.txt:
* web-platform-tests/xhr/open-url-multi-window-5-expected.txt:
* web-platform-tests/xhr/open-url-multi-window-6-expected.txt:
Source/WebCore:
Improve XMLHttpRequest for back/forward cache suspension:
1. We no longer cancel pending loads in the suspend() method as this may
fire events.
2. Simplify XMLHttpRequestProgressEventThrottle to use SuspendableTimers
to dispatch events that are deferred by suspension or throttling.
Test: http/tests/navigation/page-cache-xhr-in-loading-iframe.html
* xml/XMLHttpRequest.cpp:
(WebCore::XMLHttpRequest::XMLHttpRequest):
(WebCore::XMLHttpRequest::open):
Add check to throw a InvalidStateError if the associated document is not fully active,
as per https://xhr.spec.whatwg.org/#dom-xmlhttprequest-open (Step 2). This avoids
dispatching events after ActiveDOMObject::stop() has been called and brings a few more
passes on WPT tests.
(WebCore::XMLHttpRequest::dispatchEvent):
(WebCore::XMLHttpRequest::suspend):
(WebCore::XMLHttpRequest::resume):
(WebCore::XMLHttpRequest::shouldPreventEnteringBackForwardCache_DEPRECATED const): Deleted.
(WebCore::XMLHttpRequest::resumeTimerFired): Deleted.
* xml/XMLHttpRequest.h:
* xml/XMLHttpRequestProgressEventThrottle.cpp:
(WebCore::XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEvent):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchEventWhenPossible):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchProgressEvent):
(WebCore::XMLHttpRequestProgressEventThrottle::flushProgressEvent):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired):
(WebCore::XMLHttpRequestProgressEventThrottle::suspend):
(WebCore::XMLHttpRequestProgressEventThrottle::resume):
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchEvent): Deleted.
(WebCore::XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents): Deleted.
(WebCore::XMLHttpRequestProgressEventThrottle::fired): Deleted.
(WebCore::XMLHttpRequestProgressEventThrottle::hasEventToDispatch const): Deleted.
* xml/XMLHttpRequestProgressEventThrottle.h:
LayoutTests:
Add more test coverage.
* TestExpectations:
* fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt:
* fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html:
* http/tests/navigation/page-cache-xhr-in-loading-iframe-expected.txt: Added.
* http/tests/navigation/page-cache-xhr-in-loading-iframe.html: Added.
* http/tests/navigation/resources/page-cache-xhr-in-loading-iframe.html: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251366 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 5a6369e..e56bbb2 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,20 @@
+2019-10-21 Chris Dumez <cdumez@apple.com>
+
+ XMLHttpRequest should not prevent entering the back/forward cache
+ https://bugs.webkit.org/show_bug.cgi?id=203107
+ <rdar://problem/56438647>
+
+ Reviewed by Youenn Fablet.
+
+ Add more test coverage.
+
+ * TestExpectations:
+ * fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt:
+ * fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html:
+ * http/tests/navigation/page-cache-xhr-in-loading-iframe-expected.txt: Added.
+ * http/tests/navigation/page-cache-xhr-in-loading-iframe.html: Added.
+ * http/tests/navigation/resources/page-cache-xhr-in-loading-iframe.html: Added.
+
2019-10-21 Alicia Boya García <aboya@igalia.com>
[MSE][GStreamer] Revert WebKitMediaSrc rework temporarily
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index 33bba44..d56d319 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -268,6 +268,7 @@
imported/w3c/web-platform-tests/service-workers/service-worker/fetch-cors-xhr.https.html [ DumpJSConsoleLogInStdErr ]
fast/files/file-reader-back-forward-cache.html [ DumpJSConsoleLogInStdErr ]
fast/history/page-cache-createImageBitmap.html [ DumpJSConsoleLogInStdErr ]
+http/tests/navigation/page-cache-xhr-in-loading-iframe.html [ DumpJSConsoleLogInStdErr ]
webkit.org/b/202495 imported/w3c/web-platform-tests/shadow-dom/directionality-002.tentative.html [ ImageOnlyFailure ]
diff --git a/LayoutTests/fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt b/LayoutTests/fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt
index 6fc31c2..68b5713 100644
--- a/LayoutTests/fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt
+++ b/LayoutTests/fast/dom/xmlhttprequest-constructor-in-detached-document-expected.txt
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 14: InvalidStateError: Document is not fully active
Text for bug 25290: Crash when constructing XMLHttpRequest in a detached document.
PASS
diff --git a/LayoutTests/fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html b/LayoutTests/fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html
index d02ff26..dd3688e 100644
--- a/LayoutTests/fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html
+++ b/LayoutTests/fast/xmlhttprequest/xmlhttprequest-open-after-iframe-onload-remove-self.html
@@ -8,7 +8,9 @@
function onFrameLoad(frame) {
var client = frame.contentWindow.client();
frame.parentNode.removeChild(frame);
- client.open("GET", "DoesNotExist.txt");
+ try {
+ client.open("GET", "DoesNotExist.txt");
+ } catch(e) { }
if (window.testRunner)
testRunner.notifyDone();
}
diff --git a/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe-expected.txt b/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe-expected.txt
new file mode 100644
index 0000000..9b7a68d
--- /dev/null
+++ b/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe-expected.txt
@@ -0,0 +1,15 @@
+Tests that a page with a loading iframe that has a pending XHR is able to enter the back/forward cache.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+pageshow - not from cache
+* iframe starting XHR
+pagehide - entering cache
+pageshow - from cache
+PASS Page did enter and was restored from the page cache
+PASS XHR finished after restoring from the cache
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe.html b/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe.html
new file mode 100644
index 0000000..48847ac
--- /dev/null
+++ b/LayoutTests/http/tests/navigation/page-cache-xhr-in-loading-iframe.html
@@ -0,0 +1,42 @@
+<!-- webkit-test-runner [ enableBackForwardCache=true ] -->
+<!DOCTYPE html>
+<html>
+<body>
+<script src="/js-test-resources/js-test.js"></script>
+<script>
+description('Tests that a page with a loading iframe that has a pending XHR is able to enter the back/forward cache.');
+window.jsTestIsAsync = true;
+
+window.addEventListener("pageshow", function(event) {
+ debug("pageshow - " + (event.persisted ? "" : "not ") + "from cache");
+
+ if (event.persisted) {
+ testPassed("Page did enter and was restored from the page cache");
+ iframe.contentWindow.shouldFinishJSOnXHRLoad = true;
+ }
+}, false);
+
+window.addEventListener("pagehide", function(event) {
+ debug("pagehide - " + (event.persisted ? "" : "not ") + "entering cache");
+ if (!event.persisted) {
+ testFailed("Page did not enter the page cache.");
+ finishJSTest();
+ }
+}, false);
+
+function navigate()
+{
+ window.location.href = "/navigation/resources/page-cache-helper.html";
+}
+
+window.addEventListener('load', function() {
+ setTimeout(function() {
+ iframe = document.createElement("iframe");
+ iframe.src = "/navigation/resources/page-cache-xhr-in-loading-iframe.html";
+ document.body.appendChild(iframe);
+ }, 0);
+}, false);
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/navigation/resources/page-cache-xhr-in-loading-iframe.html b/LayoutTests/http/tests/navigation/resources/page-cache-xhr-in-loading-iframe.html
new file mode 100644
index 0000000..6a97d67
--- /dev/null
+++ b/LayoutTests/http/tests/navigation/resources/page-cache-xhr-in-loading-iframe.html
@@ -0,0 +1,31 @@
+<script>
+
+shouldFinishJSOnXHRLoad = false;
+
+function doXHR()
+{
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", "/navigation/resources/slow-resource.pl?delay=1000");
+ xhr.addEventListener("load", () => {
+ if (shouldFinishJSOnXHRLoad) {
+ parent.testPassed("XHR finished after restoring from the cache");
+ parent.restoredFromCache = false;
+ parent.finishJSTest();
+ } else
+ doXHR();
+ });
+ xhr.addEventListener("error", () => {
+ doXHR();
+ });
+ xhr.addEventListener("progress", () => {
+ });
+
+ xhr.send();
+}
+
+parent.debug("* iframe starting XHR");
+doXHR();
+
+parent.navigate();
+
+</script>
diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog
index 9175da5..b43dc4d 100644
--- a/LayoutTests/imported/w3c/ChangeLog
+++ b/LayoutTests/imported/w3c/ChangeLog
@@ -1,3 +1,18 @@
+2019-10-21 Chris Dumez <cdumez@apple.com>
+
+ XMLHttpRequest should not prevent entering the back/forward cache
+ https://bugs.webkit.org/show_bug.cgi?id=203107
+ <rdar://problem/56438647>
+
+ Reviewed by Youenn Fablet.
+
+ Rebaseline a new WPT tests that are passing now that we properly check that the
+ Document is fully active in open().
+
+ * web-platform-tests/xhr/open-url-multi-window-2-expected.txt:
+ * web-platform-tests/xhr/open-url-multi-window-5-expected.txt:
+ * web-platform-tests/xhr/open-url-multi-window-6-expected.txt:
+
2019-10-18 Said Abou-Hallawa <sabouhallawa@apple.com>
[SVG2]: Remove the SVGExternalResourcesRequired interface
diff --git a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-2-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-2-expected.txt
index e525698..f107bb0 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-2-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-2-expected.txt
@@ -1,5 +1,3 @@
-FAIL XMLHttpRequest: open() resolving URLs (multi-Window; 2; evil) assert_throws: open() when associated document's IFRAME is removed function "function () {
- client.open("GET", "folder.txt")
- }" did not throw
+PASS XMLHttpRequest: open() resolving URLs (multi-Window; 2; evil)
diff --git a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-5-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-5-expected.txt
index 6b36111..fa78a2e 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-5-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-5-expected.txt
@@ -1,3 +1,3 @@
-FAIL XMLHttpRequest: open() resolving URLs (multi-Window; 5) assert_throws: function "function () { client.open("GET", "...") }" did not throw
+PASS XMLHttpRequest: open() resolving URLs (multi-Window; 5)
diff --git a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-6-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-6-expected.txt
index ac86536..5a45792 100644
--- a/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-6-expected.txt
+++ b/LayoutTests/imported/w3c/web-platform-tests/xhr/open-url-multi-window-6-expected.txt
@@ -1,3 +1,3 @@
-FAIL XMLHttpRequest: open() in document that is not fully active (but may be active) should throw assert_throws: function "function () { client.open("GET", "...") }" threw object "SyntaxError: The string did not match the expected pattern." that is not a DOMException InvalidStateError: property "code" is equal to 12, expected 11
+PASS XMLHttpRequest: open() in document that is not fully active (but may be active) should throw
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 5ff2b6e..c5e6f65 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,50 @@
+2019-10-21 Chris Dumez <cdumez@apple.com>
+
+ XMLHttpRequest should not prevent entering the back/forward cache
+ https://bugs.webkit.org/show_bug.cgi?id=203107
+ <rdar://problem/56438647>
+
+ Reviewed by Youenn Fablet.
+
+ Improve XMLHttpRequest for back/forward cache suspension:
+ 1. We no longer cancel pending loads in the suspend() method as this may
+ fire events.
+ 2. Simplify XMLHttpRequestProgressEventThrottle to use SuspendableTimers
+ to dispatch events that are deferred by suspension or throttling.
+
+ Test: http/tests/navigation/page-cache-xhr-in-loading-iframe.html
+
+ * xml/XMLHttpRequest.cpp:
+ (WebCore::XMLHttpRequest::XMLHttpRequest):
+ (WebCore::XMLHttpRequest::open):
+ Add check to throw a InvalidStateError if the associated document is not fully active,
+ as per https://xhr.spec.whatwg.org/#dom-xmlhttprequest-open (Step 2). This avoids
+ dispatching events after ActiveDOMObject::stop() has been called and brings a few more
+ passes on WPT tests.
+
+ (WebCore::XMLHttpRequest::dispatchEvent):
+ (WebCore::XMLHttpRequest::suspend):
+ (WebCore::XMLHttpRequest::resume):
+ (WebCore::XMLHttpRequest::shouldPreventEnteringBackForwardCache_DEPRECATED const): Deleted.
+ (WebCore::XMLHttpRequest::resumeTimerFired): Deleted.
+ * xml/XMLHttpRequest.h:
+ * xml/XMLHttpRequestProgressEventThrottle.cpp:
+ (WebCore::XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEvent):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchEventWhenPossible):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchProgressEvent):
+ (WebCore::XMLHttpRequestProgressEventThrottle::flushProgressEvent):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired):
+ (WebCore::XMLHttpRequestProgressEventThrottle::suspend):
+ (WebCore::XMLHttpRequestProgressEventThrottle::resume):
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchEvent): Deleted.
+ (WebCore::XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents): Deleted.
+ (WebCore::XMLHttpRequestProgressEventThrottle::fired): Deleted.
+ (WebCore::XMLHttpRequestProgressEventThrottle::hasEventToDispatch const): Deleted.
+ * xml/XMLHttpRequestProgressEventThrottle.h:
+
2019-10-21 Alicia Boya García <aboya@igalia.com>
[MSE][GStreamer] Revert WebKitMediaSrc rework temporarily
diff --git a/Source/WebCore/xml/XMLHttpRequest.cpp b/Source/WebCore/xml/XMLHttpRequest.cpp
index 67ad293..152274f 100644
--- a/Source/WebCore/xml/XMLHttpRequest.cpp
+++ b/Source/WebCore/xml/XMLHttpRequest.cpp
@@ -117,11 +117,9 @@
, m_uploadComplete(false)
, m_wasAbortedByClient(false)
, m_responseCacheIsValid(false)
- , m_dispatchErrorOnResuming(false)
, m_readyState(static_cast<unsigned>(UNSENT))
, m_responseType(static_cast<unsigned>(ResponseType::EmptyString))
- , m_progressEventThrottle(this)
- , m_resumeTimer(*this, &XMLHttpRequest::resumeTimerFired)
+ , m_progressEventThrottle(*this)
, m_networkErrorTimer(*this, &XMLHttpRequest::networkErrorTimerFired)
, m_timeoutTimer(*this, &XMLHttpRequest::didReachTimeout)
, m_maximumIntervalForUserGestureForwarding(maximumIntervalForUserGestureForwarding)
@@ -337,6 +335,11 @@
ExceptionOr<void> XMLHttpRequest::open(const String& method, const URL& url, bool async)
{
+ auto* context = scriptExecutionContext();
+ bool contextIsDocument = is<Document>(*context);
+ if (contextIsDocument && !downcast<Document>(*context).isFullyActive())
+ return Exception { InvalidStateError, "Document is not fully active"_s };
+
if (!isValidHTTPToken(method))
return Exception { SyntaxError };
@@ -346,19 +349,19 @@
if (!url.isValid())
return Exception { SyntaxError };
- if (!async && scriptExecutionContext()->isDocument()) {
+ if (!async && contextIsDocument) {
// Newer functionality is not available to synchronous requests in window contexts, as a spec-mandated
// attempt to discourage synchronous XHR use. responseType is one such piece of functionality.
// We'll only disable this functionality for HTTP(S) requests since sync requests for local protocols
// such as file: and data: still make sense to allow.
if (url.protocolIsInHTTPFamily() && responseType() != ResponseType::EmptyString) {
- logConsoleError(scriptExecutionContext(), "Synchronous HTTP(S) requests made from the window context cannot have XMLHttpRequest.responseType set.");
+ logConsoleError(context, "Synchronous HTTP(S) requests made from the window context cannot have XMLHttpRequest.responseType set.");
return Exception { InvalidAccessError };
}
// Similarly, timeouts are disabled for synchronous requests as well.
if (m_timeoutMilliseconds > 0) {
- logConsoleError(scriptExecutionContext(), "Synchronous XMLHttpRequests must not have a timeout value set.");
+ logConsoleError(context, "Synchronous XMLHttpRequests must not have a timeout value set.");
return Exception { InvalidAccessError };
}
}
@@ -378,7 +381,7 @@
clearRequest();
m_url = url;
- scriptExecutionContext()->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(m_url, ContentSecurityPolicy::InsecureRequestType::Load);
+ context->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(m_url, ContentSecurityPolicy::InsecureRequestType::Load);
m_async = async;
@@ -1091,6 +1094,8 @@
void XMLHttpRequest::dispatchEvent(Event& event)
{
+ RELEASE_ASSERT(!scriptExecutionContext()->activeDOMObjectsAreSuspended());
+
if (m_userGestureToken && m_userGestureToken->hasExpired(m_maximumIntervalForUserGestureForwarding))
m_userGestureToken = nullptr;
@@ -1141,55 +1146,19 @@
dispatchErrorEvents(eventNames().timeoutEvent);
}
-// FIXME: This should never prevent entering the back/forward cache.
-bool XMLHttpRequest::shouldPreventEnteringBackForwardCache_DEPRECATED() const
-{
- // If the load event has not fired yet, cancelling the load in suspend() may cause
- // the load event to be fired and arbitrary JS execution, which would be unsafe.
- // Therefore, we prevent suspending in this case.
- return m_loader && !document()->loadEventFinished();
-}
-
const char* XMLHttpRequest::activeDOMObjectName() const
{
return "XMLHttpRequest";
}
-void XMLHttpRequest::suspend(ReasonForSuspension reason)
+void XMLHttpRequest::suspend(ReasonForSuspension)
{
m_progressEventThrottle.suspend();
-
- if (m_resumeTimer.isActive()) {
- m_resumeTimer.stop();
- m_dispatchErrorOnResuming = true;
- }
-
- if (reason == ReasonForSuspension::BackForwardCache && m_loader) {
- // Going into the BackForwardCache, abort the request and dispatch a network error on resuming.
- genericError();
- m_dispatchErrorOnResuming = true;
- bool aborted = internalAbort();
- // It should not be possible to restart the load when aborting in suspend() because
- // we are not allowed to execute in JS in suspend().
- ASSERT_UNUSED(aborted, aborted);
- }
}
void XMLHttpRequest::resume()
{
m_progressEventThrottle.resume();
-
- // We are not allowed to execute arbitrary JS in resume() so dispatch
- // the error event in a timer.
- if (m_dispatchErrorOnResuming && !m_resumeTimer.isActive())
- m_resumeTimer.startOneShot(0_s);
-}
-
-void XMLHttpRequest::resumeTimerFired()
-{
- ASSERT(m_dispatchErrorOnResuming);
- m_dispatchErrorOnResuming = false;
- dispatchErrorEvents(eventNames().errorEvent);
}
void XMLHttpRequest::stop()
diff --git a/Source/WebCore/xml/XMLHttpRequest.h b/Source/WebCore/xml/XMLHttpRequest.h
index 52008c7..df32ab0 100644
--- a/Source/WebCore/xml/XMLHttpRequest.h
+++ b/Source/WebCore/xml/XMLHttpRequest.h
@@ -137,7 +137,6 @@
// ActiveDOMObject
void contextDestroyed() override;
- bool shouldPreventEnteringBackForwardCache_DEPRECATED() const override;
void suspend(ReasonForSuspension) override;
void resume() override;
void stop() override;
@@ -189,7 +188,6 @@
using EventTarget::dispatchEvent;
void dispatchEvent(Event&) override;
- void resumeTimerFired();
Ref<TextResourceDecoder> createDecoder() const;
void networkErrorTimerFired();
@@ -203,7 +201,6 @@
unsigned m_uploadComplete : 1;
unsigned m_wasAbortedByClient : 1;
unsigned m_responseCacheIsValid : 1;
- unsigned m_dispatchErrorOnResuming : 1;
unsigned m_readyState : 3; // State
unsigned m_responseType : 3; // ResponseType
@@ -238,7 +235,6 @@
mutable String m_allResponseHeaders;
- Timer m_resumeTimer;
Timer m_networkErrorTimer;
Timer m_timeoutTimer;
diff --git a/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.cpp b/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.cpp
index f8f9f5e..7745f4f 100644
--- a/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.cpp
+++ b/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.cpp
@@ -35,11 +35,13 @@
const Seconds XMLHttpRequestProgressEventThrottle::minimumProgressEventDispatchingInterval { 50_ms }; // 50 ms per specification.
-XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle(EventTarget* target)
+XMLHttpRequestProgressEventThrottle::XMLHttpRequestProgressEventThrottle(EventTarget& target)
: m_target(target)
- , m_dispatchDeferredEventsTimer(*this, &XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents)
+ , m_dispatchThrottledProgressEventTimer(target.scriptExecutionContext(), *this, &XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired)
+ , m_dispatchDeferredEventsAfterResumingTimer(target.scriptExecutionContext(), *this, &XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming)
{
- ASSERT(target);
+ m_dispatchThrottledProgressEventTimer.suspendIfNeeded();
+ m_dispatchDeferredEventsAfterResumingTimer.suspendIfNeeded();
}
XMLHttpRequestProgressEventThrottle::~XMLHttpRequestProgressEventThrottle() = default;
@@ -50,29 +52,23 @@
m_loaded = loaded;
m_total = total;
- if (!m_target->hasEventListeners(eventNames().progressEvent))
+ if (!m_target.hasEventListeners(eventNames().progressEvent))
return;
-
- if (m_deferEvents) {
- // Only store the latest progress event while suspended.
- m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total);
- return;
- }
- if (!isActive()) {
+ if (!m_shouldDeferEventsDueToSuspension && !m_dispatchThrottledProgressEventTimer.isActive()) {
// The timer is not active so the least frequent event for now is every byte. Just dispatch the event.
// We should not have any throttled progress event.
- ASSERT(!m_hasThrottledProgressEvent);
+ ASSERT(!m_hasPendingThrottledProgressEvent);
- dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total));
- startRepeating(minimumProgressEventDispatchingInterval);
- m_hasThrottledProgressEvent = false;
+ dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, lengthComputable, loaded, total));
+ m_dispatchThrottledProgressEventTimer.startRepeating(minimumProgressEventDispatchingInterval);
+ m_hasPendingThrottledProgressEvent = false;
return;
}
// The timer is already active so minimumProgressEventDispatchingInterval is the least frequent event.
- m_hasThrottledProgressEvent = true;
+ m_hasPendingThrottledProgressEvent = true;
}
void XMLHttpRequestProgressEventThrottle::dispatchReadyStateChangeEvent(Event& event, ProgressEventAction progressEventAction)
@@ -80,19 +76,19 @@
if (progressEventAction == FlushProgressEvent)
flushProgressEvent();
- dispatchEvent(event);
+ dispatchEventWhenPossible(event);
}
-void XMLHttpRequestProgressEventThrottle::dispatchEvent(Event& event)
+void XMLHttpRequestProgressEventThrottle::dispatchEventWhenPossible(Event& event)
{
- if (m_deferEvents) {
- if (m_deferredEvents.size() > 1 && event.type() == eventNames().readystatechangeEvent && event.type() == m_deferredEvents.last()->type()) {
+ if (m_shouldDeferEventsDueToSuspension) {
+ if (m_eventsDeferredDueToSuspension.size() > 1 && event.type() == eventNames().readystatechangeEvent && event.type() == m_eventsDeferredDueToSuspension.last()->type()) {
// Readystatechange events are state-less so avoid repeating two identical events in a row on resume.
return;
}
- m_deferredEvents.append(event);
+ m_eventsDeferredDueToSuspension.append(event);
} else
- m_target->dispatchEvent(event);
+ m_target.dispatchEvent(event);
}
void XMLHttpRequestProgressEventThrottle::dispatchProgressEvent(const AtomString& type)
@@ -105,103 +101,62 @@
m_total = 0;
}
- if (m_target->hasEventListeners(type))
- dispatchEvent(XMLHttpRequestProgressEvent::create(type, m_lengthComputable, m_loaded, m_total));
+ if (m_target.hasEventListeners(type))
+ dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(type, m_lengthComputable, m_loaded, m_total));
}
void XMLHttpRequestProgressEventThrottle::flushProgressEvent()
{
- if (m_deferEvents && m_deferredProgressEvent) {
- // Move the progress event to the queue, to get it in the right order on resume.
- m_deferredEvents.append(m_deferredProgressEvent.releaseNonNull());
+ if (!m_hasPendingThrottledProgressEvent)
return;
- }
- if (!hasEventToDispatch())
- return;
- Ref<Event> event = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total);
- m_hasThrottledProgressEvent = false;
-
+ m_hasPendingThrottledProgressEvent = false;
// We stop the timer as this is called when no more events are supposed to occur.
- stop();
+ m_dispatchThrottledProgressEventTimer.cancel();
- dispatchEvent(WTFMove(event));
+ dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total));
}
-void XMLHttpRequestProgressEventThrottle::dispatchDeferredEvents()
+void XMLHttpRequestProgressEventThrottle::dispatchDeferredEventsAfterResuming()
{
- ASSERT(m_deferEvents);
- m_deferEvents = false;
+ ASSERT(m_shouldDeferEventsDueToSuspension);
+ m_shouldDeferEventsDueToSuspension = false;
// Take over the deferred events before dispatching them which can potentially add more.
- auto deferredEvents = WTFMove(m_deferredEvents);
+ auto eventsDeferredDueToSuspension = WTFMove(m_eventsDeferredDueToSuspension);
- RefPtr<Event> deferredProgressEvent = WTFMove(m_deferredProgressEvent);
+ flushProgressEvent();
- for (auto& deferredEvent : deferredEvents)
- dispatchEvent(deferredEvent);
-
- // The progress event will be in the m_deferredEvents vector if the load was finished while suspended.
- // If not, just send the most up-to-date progress on resume.
- if (deferredProgressEvent)
- dispatchEvent(*deferredProgressEvent);
+ for (auto& deferredEvent : eventsDeferredDueToSuspension)
+ dispatchEventWhenPossible(deferredEvent);
}
-void XMLHttpRequestProgressEventThrottle::fired()
+void XMLHttpRequestProgressEventThrottle::dispatchThrottledProgressEventTimerFired()
{
- ASSERT(isActive());
- if (!hasEventToDispatch()) {
+ ASSERT(m_dispatchThrottledProgressEventTimer.isActive());
+ if (!m_hasPendingThrottledProgressEvent) {
// No progress event was queued since the previous dispatch, we can safely stop the timer.
- stop();
+ m_dispatchThrottledProgressEventTimer.cancel();
return;
}
- dispatchEvent(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total));
- m_hasThrottledProgressEvent = false;
-}
-
-bool XMLHttpRequestProgressEventThrottle::hasEventToDispatch() const
-{
- return m_hasThrottledProgressEvent && isActive();
+ dispatchEventWhenPossible(XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total));
+ m_hasPendingThrottledProgressEvent = false;
}
void XMLHttpRequestProgressEventThrottle::suspend()
{
- // If re-suspended before deferred events have been dispatched, just stop the dispatch
- // and continue the last suspend.
- if (m_dispatchDeferredEventsTimer.isActive()) {
- ASSERT(m_deferEvents);
- m_dispatchDeferredEventsTimer.stop();
- return;
- }
- ASSERT(!m_deferredProgressEvent);
- ASSERT(m_deferredEvents.isEmpty());
- ASSERT(!m_deferEvents);
-
- m_deferEvents = true;
- // If we have a progress event waiting to be dispatched,
- // just defer it.
- if (hasEventToDispatch()) {
- m_deferredProgressEvent = XMLHttpRequestProgressEvent::create(eventNames().progressEvent, m_lengthComputable, m_loaded, m_total);
- m_hasThrottledProgressEvent = false;
- }
- stop();
+ m_shouldDeferEventsDueToSuspension = true;
}
void XMLHttpRequestProgressEventThrottle::resume()
{
- ASSERT(!m_hasThrottledProgressEvent);
-
- if (m_deferredEvents.isEmpty() && !m_deferredProgressEvent) {
- m_deferEvents = false;
+ if (m_eventsDeferredDueToSuspension.isEmpty() && !m_hasPendingThrottledProgressEvent) {
+ m_shouldDeferEventsDueToSuspension = false;
return;
}
- // Do not dispatch events inline here, since ScriptExecutionContext is iterating over
- // the list of active DOM objects to resume them, and any activated JS event-handler
- // could insert new active DOM objects to the list.
- // m_deferEvents is kept true until all deferred events have been dispatched.
- m_dispatchDeferredEventsTimer.startOneShot(0_s);
+ m_dispatchDeferredEventsAfterResumingTimer.startOneShot(0_s);
}
} // namespace WebCore
diff --git a/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.h b/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.h
index 195316c..a83cf3d 100644
--- a/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.h
+++ b/Source/WebCore/xml/XMLHttpRequestProgressEventThrottle.h
@@ -26,7 +26,7 @@
#pragma once
-#include "Timer.h"
+#include "SuspendableTimer.h"
#include <wtf/Forward.h>
#include <wtf/Vector.h>
@@ -42,9 +42,9 @@
// This implements the XHR2 progress event dispatching: "dispatch a progress event called progress
// about every 50ms or for every byte received, whichever is least frequent".
-class XMLHttpRequestProgressEventThrottle : public TimerBase {
+class XMLHttpRequestProgressEventThrottle {
public:
- explicit XMLHttpRequestProgressEventThrottle(EventTarget*);
+ explicit XMLHttpRequestProgressEventThrottle(EventTarget&);
virtual ~XMLHttpRequestProgressEventThrottle();
void dispatchThrottledProgressEvent(bool lengthComputable, unsigned long long loaded, unsigned long long total);
@@ -57,26 +57,25 @@
private:
static const Seconds minimumProgressEventDispatchingInterval;
- void fired() override;
- void dispatchDeferredEvents();
+ void dispatchThrottledProgressEventTimerFired();
+ void dispatchDeferredEventsAfterResuming();
void flushProgressEvent();
- void dispatchEvent(Event&);
-
- bool hasEventToDispatch() const;
+ void dispatchEventWhenPossible(Event&);
// Weak pointer to our XMLHttpRequest object as it is the one holding us.
- EventTarget* m_target;
+ EventTarget& m_target;
unsigned long long m_loaded { 0 };
unsigned long long m_total { 0 };
RefPtr<Event> m_deferredProgressEvent;
- Vector<Ref<Event>> m_deferredEvents;
- Timer m_dispatchDeferredEventsTimer;
+ Vector<Ref<Event>> m_eventsDeferredDueToSuspension;
+ SuspendableTimer m_dispatchThrottledProgressEventTimer;
+ SuspendableTimer m_dispatchDeferredEventsAfterResumingTimer;
- bool m_hasThrottledProgressEvent { false };
+ bool m_hasPendingThrottledProgressEvent { false };
bool m_lengthComputable { false };
- bool m_deferEvents { false };
+ bool m_shouldDeferEventsDueToSuspension { false };
};
} // namespace WebCore