imported patch xhr.patch
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 06 Nov 2017 16:43:01 +0100
changeset 1347368 38935de5fec186806d20e4ef1b5d84de38200c43
parent 1343126 2535bad09d720e71a982f3f70dd6925f66ab8ec7
child 1347369 ca5008e34e35901c9868b17bea1648b38557c601
child 1347454 36cc3fcae9e7bc043c5ca34a60caf80398b0097f
push id234444
push useramarchesini@mozilla.com
push dateThu, 09 Nov 2017 23:23:17 +0000
treeherdertry@ea5024d7f3f3 [default view] [failures only]
milestone58.0a1
imported patch xhr.patch
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
dom/xhr/tests/mochitest.ini
dom/xhr/tests/test_nestedSyncXHR.html
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -183,16 +183,17 @@ XMLHttpRequestMainThread::sDontWarnAbout
 
 XMLHttpRequestMainThread::XMLHttpRequestMainThread()
   : mResponseBodyDecodedPos(0),
     mResponseCharset(nullptr),
     mResponseType(XMLHttpRequestResponseType::_empty),
     mRequestObserver(nullptr),
     mState(State::unsent),
     mStyleBackend(StyleBackendType::None),
+    mSyncLoopId(0),
     mFlagSynchronous(false), mFlagAborted(false), mFlagParseBody(false),
     mFlagSyncLooping(false), mFlagBackgroundRequest(false),
     mFlagHadUploadListenersOnSend(false), mFlagACwithCredentials(false),
     mFlagTimedOut(false), mFlagDeleted(false), mFlagSend(false),
     mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true),
     mProgressSinceLastProgressEvent(false),
     mRequestSentTime(0), mTimeoutMilliseconds(0),
     mErrorLoad(ErrorType::eOK), mErrorParsingXML(false),
@@ -1424,24 +1425,30 @@ XMLHttpRequestMainThread::DispatchOrStor
 
   bool dummy;
   aTarget->DispatchEvent(aEvent, &dummy);
 }
 
 void
 XMLHttpRequestMainThread::SuspendEventDispatching()
 {
-  MOZ_ASSERT(!mEventDispatchingSuspended);
+  // mEventDispatchingSuspended can be already set to true if we are in a nested
+  // event loop.
   mEventDispatchingSuspended = true;
 }
 
 void
 XMLHttpRequestMainThread::ResumeEventDispatching()
 {
-  MOZ_ASSERT(mEventDispatchingSuspended);
+  if (!mEventDispatchingSuspended) {
+    // mEventDispatchingSuspended can be already set to false if we are in a
+    // nested event loop.
+    return;
+  }
+
   mEventDispatchingSuspended = false;
 
   nsTArray<PendingEvent> pendingEvents;
   pendingEvents.SwapElements(mPendingEvents);
 
   for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
     bool dummy;
     pendingEvents[i].mTarget->DispatchEvent(pendingEvents[i].mEvent, &dummy);
@@ -2835,29 +2842,29 @@ XMLHttpRequestMainThread::Send(nsIVarian
   nsString string;
   string.Adopt(data, len);
 
   BodyExtractor<const nsAString> body(&string);
   return SendInternal(&body);
 }
 
 void
-XMLHttpRequestMainThread::UnsuppressEventHandlingAndResume()
+XMLHttpRequestMainThread::UnsuppressEventHandlingAndResume(nsIDocument* aSuspendedDoc,
+                                                           already_AddRefed<nsIRunnable> aResumeTimeoutRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mFlagSynchronous);
-
-  if (mSuspendedDoc) {
-    mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
-    mSuspendedDoc = nullptr;
-  }
-
-  if (mResumeTimeoutRunnable) {
-    DispatchToMainThread(mResumeTimeoutRunnable.forget());
-    mResumeTimeoutRunnable = nullptr;
+
+  nsCOMPtr<nsIRunnable> resumeTimeoutRunnable = Move(aResumeTimeoutRunnable);
+
+  if (aSuspendedDoc) {
+    aSuspendedDoc->UnsuppressEventHandlingAndFireEvents(true);
+  }
+
+  if (resumeTimeoutRunnable) {
+    DispatchToMainThread(resumeTimeoutRunnable.forget());
   }
 }
 
 void
 XMLHttpRequestMainThread::Send(JSContext* aCx,
                                const Nullable<DocumentOrBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString>& aData,
                                ErrorResult& aRv)
 {
@@ -3047,54 +3054,64 @@ XMLHttpRequestMainThread::SendInternal(c
   mWaitingForOnStopRequest = true;
 
   // Step 8
   mFlagSend = true;
 
   // If we're synchronous, spin an event loop here and wait
   if (mFlagSynchronous) {
     mFlagSyncLooping = true;
+    int32_t syncLoopId = ++mSyncLoopId;
+
+    nsCOMPtr<nsIDocument> suspendedDoc;
+    nsCOMPtr<nsIRunnable> resumeTimeoutRunnable;
 
     if (GetOwner()) {
       if (nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetOwner()->GetOuterWindow()->GetTop()) {
         if (nsCOMPtr<nsPIDOMWindowInner> topInner = topWindow->GetCurrentInnerWindow()) {
-          mSuspendedDoc = topWindow->GetExtantDoc();
-          if (mSuspendedDoc) {
-            mSuspendedDoc->SuppressEventHandling();
+          suspendedDoc = topWindow->GetExtantDoc();
+          if (suspendedDoc) {
+            suspendedDoc->SuppressEventHandling();
           }
           topInner->Suspend();
-          mResumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
+          resumeTimeoutRunnable = new nsResumeTimeoutsEvent(topInner);
         }
       }
     }
 
     SuspendEventDispatching();
     StopProgressEventTimer();
 
     SyncTimeoutType syncTimeoutType = MaybeStartSyncTimeoutTimer();
     if (syncTimeoutType == eErrorOrExpired) {
       Abort();
       rv = NS_ERROR_DOM_NETWORK_ERR;
     }
 
     if (NS_SUCCEEDED(rv)) {
-      nsAutoSyncOperation sync(mSuspendedDoc);
+      nsAutoSyncOperation sync(suspendedDoc);
       if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) {
         rv = NS_ERROR_UNEXPECTED;
       }
 
       // Time expired... We should throw.
       if (syncTimeoutType == eTimerStarted && !mSyncTimeoutTimer) {
         rv = NS_ERROR_DOM_NETWORK_ERR;
       }
 
+      // Maybe we are here because there was another nested sync XHR loop.
+      if (mSyncLoopId != syncLoopId) {
+        rv = NS_ERROR_DOM_NETWORK_ERR;
+      }
+
       CancelSyncTimeoutTimer();
     }
 
-    UnsuppressEventHandlingAndResume();
+    UnsuppressEventHandlingAndResume(suspendedDoc,
+                                     resumeTimeoutRunnable.forget());
     ResumeEventDispatching();
   } else {
     // Now that we've successfully opened the channel, we can change state.  Note
     // that this needs to come after the AsyncOpen() and rv check, because this
     // can run script that would try to restart this request, and that could end
     // up doing our AsyncOpen on a null channel if the reentered AsyncOpen fails.
     StopProgressEventTimer();
 
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -314,17 +314,18 @@ private:
   bool IsCrossSiteCORSRequest() const;
   bool IsDeniedCrossSiteCORSRequest();
 
   // Tell our channel what network interface ID we were told to use.
   // If it's an HTTP channel and we were told to use a non-default
   // interface ID.
   void PopulateNetworkInterfaceId();
 
-  void UnsuppressEventHandlingAndResume();
+  void UnsuppressEventHandlingAndResume(nsIDocument* aSuspendedDoc,
+                                        already_AddRefed<nsIRunnable> aResumeTimeoutRunnable);
 
   // Check pref "dom.mapped_arraybuffer.enabled" to make sure ArrayBuffer is
   // supported.
   static bool IsMappedArrayBufferEnabled();
 
   // Check pref "dom.xhr.lowercase_header.enabled" to make sure lowercased
   // response header is supported.
   static bool IsLowercaseResponseHeader();
@@ -687,16 +688,18 @@ protected:
 
   nsCOMPtr<nsIURI> mBaseURI;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   State mState;
 
   StyleBackendType mStyleBackend;
 
+  int32_t mSyncLoopId;
+
   bool mFlagSynchronous;
   bool mFlagAborted;
   bool mFlagParseBody;
   bool mFlagSyncLooping;
   bool mFlagBackgroundRequest;
   bool mFlagHadUploadListenersOnSend;
   bool mFlagACwithCredentials;
   bool mFlagTimedOut;
@@ -716,19 +719,16 @@ protected:
 
   // Timeout support
   PRTime mRequestSentTime;
   uint32_t mTimeoutMilliseconds;
   nsCOMPtr<nsITimer> mTimeoutTimer;
   void StartTimeoutTimer();
   void HandleTimeoutCallback();
 
-  nsCOMPtr<nsIDocument> mSuspendedDoc;
-  nsCOMPtr<nsIRunnable> mResumeTimeoutRunnable;
-
   nsCOMPtr<nsITimer> mSyncTimeoutTimer;
 
   enum SyncTimeoutType {
     eErrorOrExpired,
     eTimerStarted,
     eNoTimerNeeded
   };
 
--- a/dom/xhr/tests/mochitest.ini
+++ b/dom/xhr/tests/mochitest.ini
@@ -108,8 +108,9 @@ skip-if = (buildapp == 'b2g') # b2g-debu
 [test_XHR_timeout.html]
 skip-if = buildapp == 'b2g' || (android_version == '18' && debug) # b2g(flaky on B2G, bug 960743) b2g-debug(flaky on B2G, bug 960743) b2g-desktop(flaky on B2G, bug 960743)
 support-files = test_XHR_timeout.js
 [test_xhr_withCredentials.html]
 [test_XHRDocURI.html]
 [test_XHRResponseURL.html]
 [test_XHRSendData.html]
 [test_sync_xhr_document_write_with_iframe.html]
+[test_nestedSyncXHR.html]
new file mode 100644
--- /dev/null
+++ b/dom/xhr/tests/test_nestedSyncXHR.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for sync XHR into sync XHRs</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <script type="application/javascript">
+
+function test_syncVsSync() {
+  var xhr = new XMLHttpRequest();
+
+  var frame = document.createElement('frame');
+  frame.addEventListener('load', function() {
+    xhr.open('POST', location, false);
+    xhr.send('X');
+  }, { once: true });
+
+  document.documentElement.appendChild(frame);
+  xhr.open('POST', location, false);
+
+  try {
+    xhr.send('X');
+    ok(false, "The first XHR should be aborted (sync vs sync).");
+  } catch(e) {
+    ok(true, "The first XHR should be aborted (sync vs sync).");
+  }
+
+  frame.remove();
+}
+
+function test_syncVsAsync() {
+  var xhr = new XMLHttpRequest();
+
+  var frame = document.createElement('frame');
+  frame.addEventListener('load', function() {
+    xhr.open('POST', location, false);
+    xhr.send('X');
+  }, { once: true });
+
+  document.documentElement.appendChild(frame);
+  xhr.open('POST', location, false);
+
+  try {
+    xhr.send('X');
+    ok(false, "The first XHR should be aborted (sync vs async).");
+  } catch(e) {
+    ok(true, "The first XHR should be aborted (sync vs async).");
+  }
+
+  frame.remove();
+}
+
+test_syncVsSync()
+test_syncVsAsync()
+
+  </script>
+</body>
+</html>