[XHR] Abort method execution when m_loader->cancel() in internalAbort() caused reentry
https://bugs.webkit.org/show_bug.cgi?id=126975
Patch by Youenn Fablet <youennf@gmail.com> on 2014-10-14
Reviewed by Alexey Proskuryakov.
Source/WebCore:
Merging https://chromium.googlesource.com/chromium/blink/+/0d75daf2053631518606ae15daaece701a25b2c4
Ensuring new test from https://codereview.chromium.org/76133002/ is passing.
Test: http/tests/xmlhttprequest/reentrant-cancel-abort.html
* xml/XMLHttpRequest.cpp:
(WebCore::XMLHttpRequest::open): exit early if internalAbort asks so
(WebCore::XMLHttpRequest::abort): exit early if internalAbort asks so
(WebCore::XMLHttpRequest::internalAbort): ask calling function to exit early if a new loader is created during the cancellation of the loader (potential reentrant case through window.onload callback)
(WebCore::XMLHttpRequest::didTimeout): exit early if internalAbort asks so
* xml/XMLHttpRequest.h:
LayoutTests:
Adding reentrant-cancel-abort.html (from https://codereview.chromium.org/76133002/)
that crashes without the patch
Updated reentrant-cancel.html test to expect the exception
that is now hit in initSend function (since a XHR open() call is aborted)
* http/tests/xmlhttprequest/reentrant-cancel-abort-expected.txt: Added.
* http/tests/xmlhttprequest/reentrant-cancel-abort.html: Added.
* http/tests/xmlhttprequest/reentrant-cancel.html:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@174684 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 4c715f1..1a3f210 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,19 @@
+2014-10-14 Youenn Fablet <youennf@gmail.com>
+
+ [XHR] Abort method execution when m_loader->cancel() in internalAbort() caused reentry
+ https://bugs.webkit.org/show_bug.cgi?id=126975
+
+ Reviewed by Alexey Proskuryakov.
+
+ Adding reentrant-cancel-abort.html (from https://codereview.chromium.org/76133002/)
+ that crashes without the patch
+ Updated reentrant-cancel.html test to expect the exception
+ that is now hit in initSend function (since a XHR open() call is aborted)
+
+ * http/tests/xmlhttprequest/reentrant-cancel-abort-expected.txt: Added.
+ * http/tests/xmlhttprequest/reentrant-cancel-abort.html: Added.
+ * http/tests/xmlhttprequest/reentrant-cancel.html:
+
2014-10-14 Alejandro G. Castro <alex@igalia.com>
Add test to check stretchy value is case sensitive
diff --git a/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort-expected.txt b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort-expected.txt
new file mode 100644
index 0000000..1dc7ec5
--- /dev/null
+++ b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort-expected.txt
@@ -0,0 +1,5 @@
+insertedText
+Reentrancy, cancellation and explicit abort. Check that we don't crash and report the expected abort event.
+DOMContentLoaded
+PASS
+
diff --git a/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort.html b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort.html
new file mode 100644
index 0000000..ae8cd55
--- /dev/null
+++ b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel-abort.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<html>
+<body>
+<script>
+if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
+
+function log(str)
+{
+ document.body.appendChild(document.createTextNode(str));
+ document.body.appendChild(document.createElement("br"));
+}
+
+function addElement(e)
+{
+ var txt = (e && e.type) || "insertedText";
+ log(txt);
+}
+document.addEventListener("DOMContentLoaded", addElement, false);
+
+var abortDispatched = false;
+function reportResult()
+{
+ log(abortDispatched ? "PASS" : "FAIL");
+ testRunner.notifyDone();
+}
+
+window.onload = function () {
+ xhr.open("GET", "", true);
+ setTimeout(reportResult, 100);
+};
+
+var xhr = new XMLHttpRequest();
+xhr.onabort = function () {
+ abortDispatched = true;
+};
+
+function sendAndAbort()
+{
+ xhr.open("GET", "", true);
+ xhr.send();
+ xhr.abort();
+}
+window.addEventListener("DOMSubtreeModified", sendAndAbort);
+addElement();
+</script>
+Reentrancy, cancellation and explicit abort. Check that we don't crash
+and report the expected abort event.<br/>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel.html b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel.html
index ab9e7aa..73f55c1 100644
--- a/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel.html
+++ b/LayoutTests/http/tests/xmlhttprequest/reentrant-cancel.html
@@ -1,6 +1,8 @@
<script>
-if (window.testRunner)
+if (window.testRunner) {
testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+}
function addElement() {
document.documentElement.appendChild(document.createTextNode('X'));
@@ -12,7 +14,17 @@
function sendXHR()
{
xhr.open("GET", "", true);
- xhr.send();
+ try {
+ xhr.send();
+ }
+ catch(e) {
+ // In case of reentrant call, the call to "open" will be aborted by reentrant call.
+ // The call to "send" following the "open" aborted call will throw an exception (XHR not in open mode)
+ if (e.name != "InvalidStateError") {
+ console.log("FAILED: Was expecting an InvalidStateError exception");
+ }
+ testRunner.notifyDone();
+ }
}
window.addEventListener("DOMSubtreeModified", sendXHR);
addElement();
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 4e838b0..703acf1 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,22 @@
+2014-10-14 Youenn Fablet <youennf@gmail.com>
+
+ [XHR] Abort method execution when m_loader->cancel() in internalAbort() caused reentry
+ https://bugs.webkit.org/show_bug.cgi?id=126975
+
+ Reviewed by Alexey Proskuryakov.
+
+ Merging https://chromium.googlesource.com/chromium/blink/+/0d75daf2053631518606ae15daaece701a25b2c4
+ Ensuring new test from https://codereview.chromium.org/76133002/ is passing.
+
+ Test: http/tests/xmlhttprequest/reentrant-cancel-abort.html
+
+ * xml/XMLHttpRequest.cpp:
+ (WebCore::XMLHttpRequest::open): exit early if internalAbort asks so
+ (WebCore::XMLHttpRequest::abort): exit early if internalAbort asks so
+ (WebCore::XMLHttpRequest::internalAbort): ask calling function to exit early if a new loader is created during the cancellation of the loader (potential reentrant case through window.onload callback)
+ (WebCore::XMLHttpRequest::didTimeout): exit early if internalAbort asks so
+ * xml/XMLHttpRequest.h:
+
2014-10-14 Alejandro G. Castro <alex@igalia.com>
Multiple refactors in RenderMathMLOperator
diff --git a/Source/WebCore/xml/XMLHttpRequest.cpp b/Source/WebCore/xml/XMLHttpRequest.cpp
index 83031b8..558fde1 100644
--- a/Source/WebCore/xml/XMLHttpRequest.cpp
+++ b/Source/WebCore/xml/XMLHttpRequest.cpp
@@ -463,7 +463,9 @@
void XMLHttpRequest::open(const String& method, const URL& url, bool async, ExceptionCode& ec)
{
- internalAbort();
+ if (!internalAbort())
+ return;
+
State previousState = m_state;
m_state = UNSENT;
m_error = false;
@@ -805,7 +807,8 @@
bool sendFlag = m_loader;
- internalAbort();
+ if (!internalAbort())
+ return;
clearResponseBuffers();
@@ -823,26 +826,35 @@
dispatchErrorEvents(eventNames().abortEvent);
}
-void XMLHttpRequest::internalAbort()
+bool XMLHttpRequest::internalAbort()
{
- bool hadLoader = m_loader;
-
m_error = true;
// FIXME: when we add the support for multi-part XHR, we will have to think be careful with this initialization.
m_receivedLength = 0;
- if (hadLoader) {
- m_loader->cancel();
- m_loader = 0;
- }
-
m_decoder = 0;
InspectorInstrumentation::didFailXHRLoading(scriptExecutionContext(), this);
- if (hadLoader)
- dropProtection();
+ if (!m_loader)
+ return true;
+
+ // Cancelling m_loader may trigger a window.onload callback which can call open() on the same xhr.
+ // This would create internalAbort reentrant call.
+ // m_loader is set to null before being cancelled to exit early in any reentrant internalAbort() call.
+ RefPtr<ThreadableLoader> loader = m_loader.release();
+ loader->cancel();
+
+ // If window.onload callback calls open() and send() on the same xhr, m_loader is now set to a new value.
+ // The function calling internalAbort() should abort to let the open() and send() calls continue properly.
+ // We ask the function calling internalAbort() to exit by returning false.
+ // Save this information to a local variable since we are going to drop protection.
+ bool newLoadStarted = m_loader;
+
+ dropProtection();
+
+ return !newLoadStarted;
}
void XMLHttpRequest::clearResponse()
@@ -1218,7 +1230,8 @@
{
// internalAbort() calls dropProtection(), which may release the last reference.
Ref<XMLHttpRequest> protect(*this);
- internalAbort();
+ if (!internalAbort())
+ return;
clearResponse();
clearRequest();
diff --git a/Source/WebCore/xml/XMLHttpRequest.h b/Source/WebCore/xml/XMLHttpRequest.h
index 951bd40..c5e13de 100644
--- a/Source/WebCore/xml/XMLHttpRequest.h
+++ b/Source/WebCore/xml/XMLHttpRequest.h
@@ -194,7 +194,11 @@
void changeState(State newState);
void callReadyStateChangeListener();
void dropProtection();
- void internalAbort();
+
+ // Returns false when cancelling the loader within internalAbort() triggers an event whose callback creates a new loader.
+ // In that case, the function calling internalAbort should exit.
+ bool internalAbort();
+
void clearResponse();
void clearResponseBuffers();
void clearRequest();