Bug 918703 - Part 2: Correct progress event logic so events are sent in the correct order and with the correct values according to spec. r=baku
authorThomas Wisniewski <wisniewskit@gmail.com>
Fri, 05 Aug 2016 23:47:40 -0400
changeset 308657 3b2b06d9c908ddbc48a4e3e0e3c35b45b1998c8e
parent 308656 647a26ff50125c5ac3c45dfa78dfc96add0d0f1e
child 308658 990954b293823fcae019566e05901da6adef519b
push id30547
push usercbook@mozilla.com
push dateTue, 09 Aug 2016 13:45:15 +0000
treeherdermozilla-central@6cf0089510fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs918703
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 918703 - Part 2: Correct progress event logic so events are sent in the correct order and with the correct values according to spec. r=baku
dom/base/test/fileutils.js
dom/base/test/test_bug435425.html
dom/security/test/cors/test_CrossSiteXHR.html
dom/workers/test/bug1063538_worker.js
dom/workers/test/test_bug1063538.html
dom/xhr/XMLHttpRequestMainThread.cpp
dom/xhr/XMLHttpRequestMainThread.h
dom/xhr/tests/test_xhr_progressevents.html
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/XMLHttpRequest/abort-after-send.htm.ini
testing/web-platform/meta/XMLHttpRequest/abort-during-upload.htm.ini
testing/web-platform/meta/XMLHttpRequest/abort-event-order.htm.ini
testing/web-platform/meta/XMLHttpRequest/event-progress.htm.ini
testing/web-platform/meta/XMLHttpRequest/response-data-progress.htm.ini
testing/web-platform/meta/XMLHttpRequest/send-network-error-async-events.sub.htm.ini
testing/web-platform/meta/XMLHttpRequest/send-timeout-events.htm.ini
testing/web-platform/tests/XMLHttpRequest/abort-during-upload.htm
testing/web-platform/tests/XMLHttpRequest/abort-event-order.htm
testing/web-platform/tests/XMLHttpRequest/event-error-order.sub.html
testing/web-platform/tests/XMLHttpRequest/event-error.html
testing/web-platform/tests/XMLHttpRequest/event-error.sub.html
testing/web-platform/tests/XMLHttpRequest/event-progress.htm
testing/web-platform/tests/XMLHttpRequest/event-timeout-order.htm
testing/web-platform/tests/XMLHttpRequest/resources/trickle.py
testing/web-platform/tests/XMLHttpRequest/resources/xmlhttprequest-event-order.js
testing/web-platform/tests/XMLHttpRequest/response-data-progress.htm
testing/web-platform/tests/XMLHttpRequest/security-consideration.sub.html
testing/web-platform/tests/XMLHttpRequest/send-no-response-event-order.htm
testing/web-platform/tests/XMLHttpRequest/send-sync-response-event-order.htm
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -95,17 +95,17 @@ function getXHRLoadHandler(expectedResul
     is(event.target.status, 200,
        "[XHR] no error in test " + testName);
     // Do not use |is(convertXHRBinary(event.target.responseText), expectedResult, "...");| that may output raw binary data.
     var convertedData = convertXHRBinary(event.target.responseText);
     is(convertedData.length, expectedResult.length,
        "[XHR] Length of result in test " + testName);
     ok(convertedData == expectedResult,
        "[XHR] Content of result in test " + testName);
-    is(event.lengthComputable, true,
+    is(event.lengthComputable, event.total != 0,
        "[XHR] lengthComputable in test " + testName);
     is(event.loaded, expectedLength,
        "[XHR] Loaded length in test " + testName);
     is(event.total, expectedLength,
        "[XHR] Total length in test " + testName);
 
     testHasRun();
   }
--- a/dom/base/test/test_bug435425.html
+++ b/dom/base/test/test_bug435425.html
@@ -19,39 +19,61 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 435425 **/
 
 var xhr = null;
 var upload = null;
 var currentEvents = null;
 var expectedResponseText = null;
 var uploadTotal = 0;
+var currentProgress = -1;
 
 function logEvent(evt) {
   var i = 0;
   while ((currentEvents.length != i) &&
          currentEvents[i].optional &&
          ((currentEvents[i].type != evt.type) ||
           !(evt.target instanceof currentEvents[i].target))) {
     ++i;
   }
+
   if (evt.target instanceof XMLHttpRequestUpload) {
     if (evt.type == "loadstart") {
       uploadTotal = evt.total
     } else {
       if (evt.type == "progress") {
-        ok(evt.lengthComputable, "event(" + evt.type +  ").lengthComputable should be true.");
+        is(evt.lengthComputable, evt.total != 0, "event(" + evt.type +  ").lengthComputable should be " + (evt.total != 0 ? true : false) + ".");
       }
-      is(evt.total, uploadTotal, "event(" + evt.type +  ").total should not change during upload.");
+      if (evt.total != uploadTotal && evt.total != 0) {
+        ok(false, "event(" + evt.type +  ").total should not change during upload except to become 0 on error/abort/timeout.");
+      }
     }
   }
+
+  // There can be any number of repeated progress events, so special-case this.
+  if (evt.type == "progress") {
+    // Progress events can repeat, but their "loaded" value must increase.
+    if (currentProgress >= 0) {
+      ok(currentProgress < evt.loaded, "Progress should increase, got " +
+                                       evt.loaded + " after " + currentProgress);
+      currentProgress = evt.loaded;
+      return; // stay at the currentEvent, since we got progress instead of it.
+    }
+    // Starting a new progress event group.
+    currentProgress = evt.loaded;
+  } else {
+    // Reset the progress indicator on any other event type.
+    currentProgress = -1;
+  }
+
   ok(i != currentEvents.length, "Extra or wrong event?");
   is(evt.type, currentEvents[i].type, "Wrong event!")
   ok(evt.target instanceof currentEvents[i].target,
      "Wrong event target [" + evt.target + "," + evt.type + "]!");
+
   // If we handled non-optional event, remove all optional events before the 
   // handled event and then the non-optional event from the list.
   if (!currentEvents[i].optional) {
     for (;i != -1; --i) {
       currentEvents.shift();
     }
   }
 }
@@ -79,16 +101,17 @@ function openXHR(xhr, method, url, privi
     xhr.open(method, url);
 }
 
 function start(obj) {
   xhr = new XMLHttpRequest();
   upload = xhr.upload;
   currentEvents = obj.expectedEvents;
   expectedResponseText = obj.withUpload;
+  currentProgress = -1;
   xhr.onload =
     function(evt) {
       if (expectedResponseText) {
         is(evt.target.responseText, expectedResponseText, "Wrong responseText");
       }
       logEvent(evt);
     }
   xhr.onerror =
@@ -184,211 +207,219 @@ for (var largeLength = 0; largeLength < 
 
 const XHR = XMLHttpRequest;
 const UPLOAD = XMLHttpRequestUpload;
 
 var tests = 
   [
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "GET", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: none, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: XHR, type: "error", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: small, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: mid, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "progress", optional: false},
                        {target: UPLOAD, type: "load", optional: false},
                        {target: UPLOAD, type: "loadend", optional: false},
-                       {target: XHR, type: "progress", optional: true},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "load", optional: false},
                        {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: true, testRedirectError: false, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
+                       {target: UPLOAD, type: "progress", optional: false},
+                       {target: UPLOAD, type: "abort", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
+                       {target: XHR, type: "progress", optional: false},
                        {target: XHR, type: "abort", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "abort", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: true, testNetworkError: false,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
     { method: "POST", withUpload: large, testAbort: false, testRedirectError: false, testNetworkError: true,
       expectedEvents: [{target: XHR, type: "loadstart", optional: false},
                        {target: UPLOAD, type: "loadstart", optional: false},
-                       {target: UPLOAD, type: "progress", optional: true},
+                       {target: UPLOAD, type: "error", optional: false},
+                       {target: UPLOAD, type: "loadend", optional: false},
                        {target: XHR, type: "error", optional: false},
-                       {target: XHR, type: "loadend", optional: false},
-                       {target: UPLOAD, type: "error", optional: false},
-                       {target: UPLOAD, type: "loadend", optional: false}]},
+                       {target: XHR, type: "loadend", optional: false}]},
 ];
 
 function runTest() {
   var test = tests.shift();
   start(test);
 }
 
 function nextTest() {
-  if (tests.length > 1) {
+  if (tests.length) {
     setTimeout("runTest()", 0);
   } else {
     SimpleTest.finish();
   }
 }
 
 ok("upload" in (new XMLHttpRequest()), "XMLHttpRequest.upload isn't supported!");
 SimpleTest.waitForExplicitFinish();
--- a/dom/security/test/cors/test_CrossSiteXHR.html
+++ b/dom/security/test/cors/test_CrossSiteXHR.html
@@ -754,28 +754,30 @@ function runTest() {
       is(res.didFail, true,
         "should have failed in test for " + test.toSource());
       is(res.status, 0, "wrong status in test for " + test.toSource());
       is(res.statusText, "", "wrong status text for " + test.toSource());
       is(res.responseXML, null,
          "wrong responseXML in test for " + test.toSource());
       is(res.responseText, "",
          "wrong responseText in test for " + test.toSource());
+      var expectedProgressCount = 0;
       if (!res.sendThrew) {
         if (test.username) {
+          expectedProgressCount = 1;
           is(res.events.join(","),
              "opening,rs1,sending,loadstart,rs4,error,loadend",
              "wrong events in test for " + test.toSource());
         } else {
           is(res.events.join(","),
              "opening,rs1,sending,loadstart,rs2,rs4,error,loadend",
              "wrong events in test for " + test.toSource());
         }
       }
-      is(res.progressEvents, 0,
+      is(res.progressEvents, expectedProgressCount,
          "wrong events in test for " + test.toSource());
       if (test.responseHeaders) {
         for (header in test.responseHeaders) {
           is(res.responseHeaders[header], null,
              "wrong response header (" + header + ") in test for " +
              test.toSource());
         }
       }
--- a/dom/workers/test/bug1063538_worker.js
+++ b/dom/workers/test/bug1063538_worker.js
@@ -1,18 +1,25 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var gJar = "jar:http://example.org/tests/dom/base/test/file_bug945152.jar!/data_big.txt";
 var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
+var progressFired = false;
+
+xhr.onloadend = function(e) {
+  postMessage({type: 'finish', progressFired: progressFired });
+  self.close();
+};
 
 xhr.onprogress = function(e) {
+  if (e.loaded > 0) {
+    progressFired = true;
+  }
   xhr.abort();
-  postMessage({type: 'finish' });
-  self.close();
 };
 
 onmessage = function(e) {
   xhr.open("GET", gJar, true);
   xhr.send();
 }
--- a/dom/workers/test/test_bug1063538.html
+++ b/dom/workers/test/test_bug1063538.html
@@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script type="application/javascript">
 
 function runTest() {
   var worker = new Worker("bug1063538_worker.js");
 
   worker.onmessage = function(e) {
     if (e.data.type == 'finish') {
-      ok(true, "Testing done.\n");
+      ok(e.data.progressFired, "Progress was fired.");
       SimpleTest.finish();
     }
   };
 
   worker.postMessage(true);
 }
 
 SimpleTest.waitForExplicitFinish();
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -168,17 +168,17 @@ XMLHttpRequestMainThread::XMLHttpRequest
     mFlagTimedOut(false), mFlagDeleted(false), mFlagSend(false),
     mUploadTransferred(0), mUploadTotal(0), mUploadComplete(true),
     mProgressSinceLastProgressEvent(false),
     mRequestSentTime(0), mTimeoutMilliseconds(0),
     mErrorLoad(false), mWaitingForOnStopRequest(false),
     mProgressTimerIsActive(false),
     mIsHtml(false),
     mWarnAboutSyncHtml(false),
-    mLoadLengthComputable(false), mLoadTotal(0),
+    mLoadTotal(0),
     mIsSystem(false),
     mIsAnon(false),
     mFirstStartRequestSeen(false),
     mInLoadProgressEvent(false),
     mResultJSON(JS::UndefinedValue()),
     mResultArrayBuffer(nullptr),
     mIsMappedArrayBuffer(false),
     mXPCOMifier(nullptr)
@@ -1013,38 +1013,37 @@ XMLHttpRequestMainThread::CloseRequest()
   }
 }
 
 void
 XMLHttpRequestMainThread::CloseRequestWithError(const ProgressEventType aType)
 {
   CloseRequest();
 
-  uint32_t responseLength = mResponseBody.Length();
   ResetResponse();
 
   // If we're in the destructor, don't risk dispatching an event.
   if (mFlagDeleted) {
     mFlagSyncLooping = false;
     return;
   }
 
   if (mState != State::unsent &&
       !(mState == State::opened && !mFlagSend) &&
       mState != State::done) {
     ChangeState(State::done, true);
 
     if (!mFlagSyncLooping) {
-      DispatchProgressEvent(this, aType, mLoadLengthComputable, responseLength,
-                            mLoadTotal);
       if (mUpload && !mUploadComplete) {
         mUploadComplete = true;
-        DispatchProgressEvent(mUpload, aType, true, mUploadTransferred,
-                              mUploadTotal);
+        DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0);
+        DispatchProgressEvent(mUpload, aType, 0, 0);
       }
+      DispatchProgressEvent(this, ProgressEventType::progress, 0, 0);
+      DispatchProgressEvent(this, aType, 0, 0);
     }
   }
 
   // The ChangeState call above calls onreadystatechange handlers which
   // if they load a new url will cause XMLHttpRequestMainThread::Open to clear
   // the abort state bit. If this occurs we're not uninitialized (bug 361773).
   if (mFlagAborted) {
     ChangeState(State::unsent, false);  // IE seems to do it
@@ -1291,34 +1290,44 @@ XMLHttpRequestMainThread::FireReadystate
   event->SetTrusted(true);
   DispatchDOMEvent(nullptr, event, nullptr, nullptr);
   return NS_OK;
 }
 
 void
 XMLHttpRequestMainThread::DispatchProgressEvent(DOMEventTargetHelper* aTarget,
                                                 const ProgressEventType aType,
-                                                bool aLengthComputable,
                                                 int64_t aLoaded, int64_t aTotal)
 {
   NS_ASSERTION(aTarget, "null target");
 
   if (NS_FAILED(CheckInnerWindowCorrectness()) ||
       (!AllowUploadProgress() && aTarget == mUpload)) {
     return;
   }
 
+  // If blocked by CORS, zero-out the stats on progress events
+  // and never fire "progress" or "load" events at all.
+  if (IsDeniedCrossSiteCORSRequest()) {
+    if (aType == ProgressEventType::progress ||
+        aType == ProgressEventType::load) {
+      return;
+    }
+    aLoaded = 0;
+    aTotal = 0;
+  }
+
   if (aType == ProgressEventType::progress) {
     mInLoadProgressEvent = true;
   }
 
   ProgressEventInit init;
   init.mBubbles = false;
   init.mCancelable = false;
-  init.mLengthComputable = aLengthComputable;
+  init.mLengthComputable = aTotal != 0; // XHR spec step 6.1
   init.mLoaded = aLoaded;
   init.mTotal = (aTotal == -1) ? 0 : aTotal;
 
   const nsAString& typeString = ProgressEventTypeStrings[(uint8_t)aType];
   RefPtr<ProgressEvent> event =
     ProgressEvent::Constructor(aTarget, typeString, init);
   event->SetTrusted(true);
 
@@ -1336,18 +1345,17 @@ XMLHttpRequestMainThread::DispatchProgre
       mArrayBufferBuilder.reset();
     }
   }
 
   // If we're sending a load, error, timeout or abort event, then
   // also dispatch the subsequent loadend event.
   if (aType == ProgressEventType::load || aType == ProgressEventType::error ||
       aType == ProgressEventType::timeout || aType == ProgressEventType::abort) {
-    DispatchProgressEvent(aTarget, ProgressEventType::loadend,
-                          aLengthComputable, aLoaded, aTotal);
+    DispatchProgressEvent(aTarget, ProgressEventType::loadend, aLoaded, aTotal);
   }
 }
 
 already_AddRefed<nsIHttpChannel>
 XMLHttpRequestMainThread::GetCurrentHttpChannel()
 {
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
   return httpChannel.forget();
@@ -1757,24 +1765,23 @@ XMLHttpRequestMainThread::OnStartRequest
   // stop the timer and fire any final progress events.
   if (mUpload && !mUploadComplete && !mErrorLoad && !mFlagSynchronous) {
     StopProgressEventTimer();
 
     mUploadTransferred = mUploadTotal;
 
     if (mProgressSinceLastProgressEvent) {
       DispatchProgressEvent(mUpload, ProgressEventType::progress,
-                            mUploadLengthComputable, mUploadTransferred,
-                            mUploadTotal);
+                            mUploadTransferred, mUploadTotal);
       mProgressSinceLastProgressEvent = false;
     }
 
     mUploadComplete = true;
     DispatchProgressEvent(mUpload, ProgressEventType::load,
-                          true, mUploadTotal, mUploadTotal);
+                          mUploadTotal, mUploadTotal);
   }
 
   mContext = ctxt;
   mFlagParseBody = true;
   ChangeState(State::headers_received);
 
   ResetResponse();
 
@@ -1993,18 +2000,23 @@ XMLHttpRequestMainThread::OnStopRequest(
   if (mXMLParserStreamListener && mFlagParseBody) {
     mXMLParserStreamListener->OnStopRequest(request, ctxt, status);
   }
 
   mXMLParserStreamListener = nullptr;
   mContext = nullptr;
 
   if (NS_SUCCEEDED(status) &&
+      (mResponseType == XMLHttpRequestResponseType::_empty ||
+       mResponseType == XMLHttpRequestResponseType::Text)) {
+    mLoadTotal = mResponseBody.Length();
+  } else if (NS_SUCCEEDED(status) &&
       (mResponseType == XMLHttpRequestResponseType::Blob ||
        mResponseType == XMLHttpRequestResponseType::Moz_blob)) {
+    ErrorResult rv;
     if (!mDOMBlob) {
       CreateDOMBlob(request);
     }
     if (mDOMBlob) {
       mResponseBlob = mDOMBlob;
       mDOMBlob = nullptr;
     } else {
       // mBlobSet can be null if the channel is non-file non-cacheable
@@ -2012,33 +2024,39 @@ XMLHttpRequestMainThread::OnStopRequest(
       if (!mBlobSet) {
         mBlobSet = new BlobSet();
       }
       // Smaller files may be written in cache map instead of separate files.
       // Also, no-store response cannot be written in persistent cache.
       nsAutoCString contentType;
       mChannel->GetContentType(contentType);
 
-      ErrorResult rv;
       mResponseBlob = mBlobSet->GetBlobInternal(GetOwner(), contentType, rv);
       mBlobSet = nullptr;
 
       if (NS_WARN_IF(rv.Failed())) {
         return rv.StealNSResult();
       }
     }
+
+    mLoadTotal = mResponseBlob->GetSize(rv);
+    if (NS_WARN_IF(rv.Failed())) {
+      status = rv.StealNSResult();
+    }
+
     NS_ASSERTION(mResponseBody.IsEmpty(), "mResponseBody should be empty");
     NS_ASSERTION(mResponseText.IsEmpty(), "mResponseText should be empty");
   } else if (NS_SUCCEEDED(status) &&
              ((mResponseType == XMLHttpRequestResponseType::Arraybuffer &&
                !mIsMappedArrayBuffer) ||
               mResponseType == XMLHttpRequestResponseType::Moz_chunked_arraybuffer)) {
     // set the capacity down to the actual length, to realloc back
     // down to the actual size
-    if (!mArrayBufferBuilder.setCapacity(mArrayBufferBuilder.length())) {
+    mLoadTotal = mArrayBufferBuilder.length();
+    if (!mArrayBufferBuilder.setCapacity(mLoadTotal)) {
       // this should never happen!
       status = NS_ERROR_UNEXPECTED;
     }
   }
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
   NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
 
@@ -2060,75 +2078,96 @@ XMLHttpRequestMainThread::OnStopRequest(
   // If we're uninitialized at this point, we encountered an error
   // earlier and listeners have already been notified. Also we do
   // not want to do this if we already completed.
   if (mState == State::unsent || mState == State::done) {
     return NS_OK;
   }
 
   if (!mResponseXML) {
+    mFlagParseBody = false;
     ChangeStateToDone();
     return NS_OK;
   }
+
   if (mIsHtml) {
     NS_ASSERTION(!mFlagSyncLooping,
       "We weren't supposed to support HTML parsing with XHR!");
     nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(mResponseXML);
     EventListenerManager* manager =
       eventTarget->GetOrCreateListenerManager();
     manager->AddEventListenerByType(new nsXHRParseEndListener(this),
                                     kLiteralString_DOMContentLoaded,
                                     TrustedEventsAtSystemGroupBubble());
     return NS_OK;
+  } else {
+    mFlagParseBody = false;
   }
+
   // We might have been sent non-XML data. If that was the case,
   // we should null out the document member. The idea in this
   // check here is that if there is no document element it is not
   // an XML document. We might need a fancier check...
   if (!mResponseXML->GetRootElement()) {
     mResponseXML = nullptr;
   }
   ChangeStateToDone();
   return NS_OK;
 }
 
 void
+XMLHttpRequestMainThread::OnBodyParseEnd()
+{
+  mFlagParseBody = false;
+  ChangeStateToDone();
+}
+
+void
 XMLHttpRequestMainThread::ChangeStateToDone()
 {
   StopProgressEventTimer();
 
   MOZ_ASSERT(!mFlagParseBody,
              "ChangeStateToDone() called before async HTML parsing is done.");
 
-  // Fire the final progress event required by spec, if we haven't already.
-  if (mProgressSinceLastProgressEvent && !mErrorLoad && !mFlagSynchronous) {
-    mLoadTotal = mLoadTransferred;
-    DispatchProgressEvent(this, ProgressEventType::progress,
-                          mLoadLengthComputable, mLoadTransferred,
-                          mLoadTotal);
-    mProgressSinceLastProgressEvent = false;
-  }
-
-  ChangeState(State::done, true);
-
   mFlagSend = false;
 
   if (mTimeoutTimer) {
     mTimeoutTimer->Cancel();
   }
 
+  mLoadTotal = mLoadTransferred;
+
+  // Per spec, fire the last download progress event, if any,
+  // before readystatechange=4/done. (Note that 0-sized responses
+  // will have not sent a progress event yet, so one must be sent here).
+  if (!mFlagSynchronous &&
+      (!mLoadTransferred || mProgressSinceLastProgressEvent)) {
+    DispatchProgressEvent(this, ProgressEventType::progress,
+                          mLoadTransferred, mLoadTotal);
+    mProgressSinceLastProgressEvent = false;
+  }
+
+  // Per spec, fire readystatechange=4/done before final error events.
+  ChangeState(State::done, true);
+
+  // Per spec, if we failed in the upload phase, fire a final progress, error,
+  // and loadend event for the upload after readystatechange=4/done.
+  if (!mFlagSynchronous && mUpload && !mUploadComplete) {
+    DispatchProgressEvent(mUpload, ProgressEventType::progress, 0, 0);
+    DispatchProgressEvent(mUpload, ProgressEventType::error, 0, 0);
+  }
+
+  // Per spec, fire download's load/error and loadend events after
+  // readystatechange=4/done (and of course all upload events).
   DispatchProgressEvent(this,
-                        mErrorLoad ? ProgressEventType::error : ProgressEventType::load,
-                        !mErrorLoad,
-                        mLoadTransferred,
-                        mErrorLoad ? 0 : mLoadTransferred);
-  if (mErrorLoad && mUpload && !mUploadComplete) {
-    DispatchProgressEvent(mUpload, ProgressEventType::error, true,
-                          mUploadTransferred, mUploadTotal);
-  }
+                        mErrorLoad ? ProgressEventType::error :
+                                     ProgressEventType::load,
+                        mErrorLoad ? 0 : mLoadTransferred,
+                        mErrorLoad ? 0 : mLoadTotal);
 
   if (mErrorLoad) {
     // By nulling out channel here we make it so that Send() can test
     // for that and throw. Also calling the various status
     // methods/members will not throw.
     // This matches what IE does.
     mChannel = nullptr;
   }
@@ -2532,17 +2571,16 @@ XMLHttpRequestMainThread::SendInternal(c
     }
   }
 
   mUploadTransferred = 0;
   mUploadTotal = 0;
   // By default we don't have any upload, so mark upload complete.
   mUploadComplete = true;
   mErrorLoad = false;
-  mLoadLengthComputable = false;
   mLoadTotal = 0;
   if (aBody && httpChannel &&
       !method.LowerCaseEqualsLiteral("get") &&
       !method.LowerCaseEqualsLiteral("head")) {
 
     nsAutoCString charset;
     nsAutoCString defaultContentType;
     nsCOMPtr<nsIInputStream> postDataStream;
@@ -2865,19 +2903,20 @@ XMLHttpRequestMainThread::SendInternal(c
     // 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();
 
     // Upload phase beginning; start the progress event timer if necessary.
     if (mUpload && mUpload->HasListenersFor(nsGkAtoms::onprogress)) {
       StartProgressEventTimer();
     }
-    DispatchProgressEvent(this, ProgressEventType::loadstart, false, 0, 0);
+    // Dispatch loadstart events
+    DispatchProgressEvent(this, ProgressEventType::loadstart, 0, 0);
     if (mUpload && !mUploadComplete) {
-      DispatchProgressEvent(mUpload, ProgressEventType::loadstart, true,
+      DispatchProgressEvent(mUpload, ProgressEventType::loadstart,
                             0, mUploadTotal);
     }
   }
 
   if (!mChannel) {
     // Per spec, silently fail on async request failures; throw for sync.
     if (mFlagSynchronous) {
       return NS_ERROR_FAILURE;
@@ -3253,25 +3292,23 @@ XMLHttpRequestMainThread::OnProgress(nsI
   // So, try to remove the headers, if possible.
   bool lengthComputable = (aProgressMax != -1);
   if (InUploadPhase()) {
     int64_t loaded = aProgress;
     if (lengthComputable) {
       int64_t headerSize = aProgressMax - mUploadTotal;
       loaded -= headerSize;
     }
-    mUploadLengthComputable = lengthComputable;
     mUploadTransferred = loaded;
     mProgressSinceLastProgressEvent = true;
 
     if (!mFlagSynchronous && !mProgressTimerIsActive) {
       StartProgressEventTimer();
     }
   } else {
-    mLoadLengthComputable = lengthComputable;
     mLoadTotal = lengthComputable ? aProgressMax : 0;
     mLoadTransferred = aProgress;
     // OnDataAvailable() handles mProgressSinceLastProgressEvent
     // for the download phase.
   }
 
   if (mProgressEventSink) {
     mProgressEventSink->OnProgress(aRequest, aContext, aProgress,
@@ -3467,23 +3504,21 @@ XMLHttpRequestMainThread::HandleProgress
 
   if (!mProgressSinceLastProgressEvent || mErrorLoad) {
     return;
   }
 
   if (InUploadPhase()) {
     if (mUpload && !mUploadComplete) {
       DispatchProgressEvent(mUpload, ProgressEventType::progress,
-                            mUploadLengthComputable, mUploadTransferred,
-                            mUploadTotal);
+                            mUploadTransferred, mUploadTotal);
     }
   } else {
     DispatchProgressEvent(this, ProgressEventType::progress,
-                          mLoadLengthComputable, mLoadTransferred,
-                          mLoadTotal);
+                          mLoadTransferred, mLoadTotal);
   }
 
   mProgressSinceLastProgressEvent = false;
 
   StartProgressEventTimer();
 }
 
 void
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -467,17 +467,16 @@ public:
                JS::MutableHandle<JS::Value> aRetval,
                ErrorResult& aRv) override;
 
   // This fires a trusted readystatechange event, which is not cancelable and
   // doesn't bubble.
   nsresult FireReadystatechangeEvent();
   void DispatchProgressEvent(DOMEventTargetHelper* aTarget,
                              const ProgressEventType aType,
-                             bool aLengthComputable,
                              int64_t aLoaded, int64_t aTotal);
 
   // This is called by the factory constructor.
   nsresult Init();
 
   nsresult init(nsIPrincipal* principal,
                 nsPIDOMWindowInner* globalObject,
                 nsIURI* baseURI);
@@ -528,16 +527,17 @@ protected:
   nsIURI *GetBaseURI();
 
   already_AddRefed<nsIHttpChannel> GetCurrentHttpChannel();
   already_AddRefed<nsIJARChannel> GetCurrentJARChannel();
 
   bool IsSystemXHR() const;
   bool InUploadPhase() const;
 
+  void OnBodyParseEnd();
   void ChangeStateToDone();
 
   void StartProgressEventTimer();
   void StopProgressEventTimer();
 
   nsresult OnRedirectVerifyCallback(nsresult result);
 
   nsresult OpenInternal(const nsACString& aMethod,
@@ -648,34 +648,32 @@ protected:
   // finishes downloading (or an error/abort has occurred during either phase).
   // Used to guard against the user trying to alter headers/etc when it's too
   // late, and ensure the XHR only handles one in-flight request at once.
   bool mFlagSend;
 
   RefPtr<XMLHttpRequestUpload> mUpload;
   int64_t mUploadTransferred;
   int64_t mUploadTotal;
-  bool mUploadLengthComputable;
   bool mUploadComplete;
   bool mProgressSinceLastProgressEvent;
 
   // Timeout support
   PRTime mRequestSentTime;
   uint32_t mTimeoutMilliseconds;
   nsCOMPtr<nsITimer> mTimeoutTimer;
   void StartTimeoutTimer();
   void HandleTimeoutCallback();
 
   bool mErrorLoad;
   bool mWaitingForOnStopRequest;
   bool mProgressTimerIsActive;
   bool mIsHtml;
   bool mWarnAboutMultipartHtml;
   bool mWarnAboutSyncHtml;
-  bool mLoadLengthComputable;
   int64_t mLoadTotal; // 0 if not known.
   // Amount of script-exposed (i.e. after undoing gzip compresion) data
   // received.
   uint64_t mDataAvailable;
   // Number of HTTP message body bytes received so far. This quantity is
   // in the same units as Content-Length and mLoadTotal, and hence counts
   // compressed bytes when the channel has gzip Content-Encoding. If the
   // channel does not have Content-Encoding, this will be the same as
@@ -797,17 +795,17 @@ private:
 class nsXHRParseEndListener : public nsIDOMEventListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_IMETHOD HandleEvent(nsIDOMEvent *event) override
   {
     nsCOMPtr<nsIXMLHttpRequest> xhr = do_QueryReferent(mXHR);
     if (xhr) {
-      static_cast<XMLHttpRequestMainThread*>(xhr.get())->ChangeStateToDone();
+      static_cast<XMLHttpRequestMainThread*>(xhr.get())->OnBodyParseEnd();
     }
     mXHR = nullptr;
     return NS_OK;
   }
   explicit nsXHRParseEndListener(nsIXMLHttpRequest* aXHR)
     : mXHR(do_GetWeakReference(aXHR)) {}
 private:
   virtual ~nsXHRParseEndListener() {}
--- a/dom/xhr/tests/test_xhr_progressevents.html
+++ b/dom/xhr/tests/test_xhr_progressevents.html
@@ -1,14 +1,14 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for XMLHttpRequest Progress Events</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="gen.next();">
 <pre id=l></pre>
 <script type="application/javascript;version=1.7">
 SimpleTest.waitForExplicitFinish();
 
 var gen = runTests();
@@ -28,17 +28,17 @@ function getEvent(e) {
 
 function startsWith(a, b) {
   return a.substr(0, b.length) === b;
 }
 
 function updateProgress(e, data, testName) {
   var test = " while running " + testName;
   is(e.type, "progress", "event type" + test);
-  
+
   let response;
   if (data.nodata) {
     is(e.target.response, null, "response should be null" + test);
     response = null;
   }
   else if (data.text) {
     is(typeof e.target.response, "string", "response should be a string" + test);
     response = e.target.response;
@@ -69,17 +69,17 @@ function updateProgress(e, data, testNam
   ok(e.loaded - data.receivedBytes <= data.pendingBytes,
      "event.loaded didn't increase too much" + test);
 
   if (!data.nodata && !data.blob) {
     var newData;
     ok(startsWith(response, data.receivedResult),
        "response strictly grew" + test);
     newData = response.substr(data.receivedResult.length);
-  
+
     if (!data.encoded) {
       ok(newData.length > 0, "sanity check for progress" + test);
     }
     ok(startsWith(data.pendingResult, newData), "new data matches expected" + test);
   }
 
   is(e.lengthComputable, "total" in data, "lengthComputable" + test);
   if ("total" in data) {
@@ -194,30 +194,30 @@ function runTests() {
                       receivedBytes: 0,
                       total: test.total,
                       encoded: test.encoded,
                       nodata: responseType.nodata,
                       chunked: responseType.chunked,
                       text: responseType.text,
                       blob: responseType.blob,
                       file: test.file };
-  
+
         xhr.onreadystatechange = null;
         if (testState.file)
           xhr.open("GET", test.file);
         else
           xhr.open("POST", "progressserver.sjs?open&" + test.open);
         xhr.responseType = responseType.type;
         xhr.send("ready");
         xhr.onreadystatechange = getEvent;
 
         let e = yield undefined;
         is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name);
         is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name);
-  
+
         e = yield undefined;
         is(e.type, "readystatechange", "should readystate to loading starting " + testState.name);
         is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name);
         if (typeof testState.total == "undefined")
           delete testState.total;
       }
       if ("file" in test) {
         testState.pendingBytes = testState.total;
@@ -233,29 +233,29 @@ function runTests() {
         is(e.type, "readystatechange", "should readystate to done closing " + testState.name);
         is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name);
         log("readystate to 4");
 
         if (responseType.chunked) {
           xhr.responseType;
           is(xhr.response, null, "chunked data has null response for " + testState.name);
         }
-      
+
         e = yield undefined;
         is(e.type, "load", "should fire load closing " + testState.name);
-        is(e.lengthComputable, true, "length should be computable during load closing " + testState.name);
+        is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during load closing " + testState.name);
         log("got load");
 
         if (responseType.chunked) {
           is(xhr.response, null, "chunked data has null response for " + testState.name);
         }
-      
+
         e = yield undefined;
         is(e.type, "loadend", "should fire loadend closing " + testState.name);
-        is(e.lengthComputable, true, "length should be computable during loadend closing " + testState.name);
+        is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during loadend closing " + testState.name);
         log("got loadend");
 
         // if we closed the connection using an explicit request, make sure that goes through before
         // running the next test in order to avoid reordered requests from closing the wrong
         // connection.
         if (xhrClose && xhrClose.readyState != xhrClose.DONE) {
           log("wait for closeConn to finish");
           xhrClose.onloadend = getEvent;
@@ -293,38 +293,38 @@ function runTests() {
           testState.pendingResult += "utf16" in test ? test.utf16 : test.data;
         }
         else {
           testState.pendingResult += test.data;
         }
         testState.pendingBytes = test.data.length;
         sendData(test.data);
       }
-  
+
       while(testState.pendingBytes) {
         log("waiting for more bytes: " + testState.pendingBytes);
         e = yield undefined;
         // Readystate can fire several times between each progress event.
         if (e.type === "readystatechange")
           continue;
-  
+
         updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]");
         if (responseType.chunked) {
           testState.receivedResult = "";
         }
       }
 
       if (!testState.nodata && !testState.blob) {
         is(testState.pendingResult, "",
            "should have consumed the expected result");
       }
 
       log("done with this test");
     }
-  
+
     is(testState.name, "", "forgot to close last test");
   }
 
   SimpleTest.finish();
   yield undefined;
 }
 
 </script>
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -12637,18 +12637,22 @@
         "path": "XMLHttpRequest/data-uri.htm",
         "url": "/XMLHttpRequest/data-uri.htm"
       },
       {
         "path": "XMLHttpRequest/event-abort.htm",
         "url": "/XMLHttpRequest/event-abort.htm"
       },
       {
-        "path": "XMLHttpRequest/event-error.html",
-        "url": "/XMLHttpRequest/event-error.html"
+        "path": "XMLHttpRequest/event-error.sub.html",
+        "url": "/XMLHttpRequest/event-error.sub.html"
+      },
+      {
+        "path": "XMLHttpRequest/event-error-order.sub.html",
+        "url": "/XMLHttpRequest/event-error-order.sub.html"
       },
       {
         "path": "XMLHttpRequest/event-load.htm",
         "url": "/XMLHttpRequest/event-load.htm"
       },
       {
         "path": "XMLHttpRequest/event-loadend.htm",
         "url": "/XMLHttpRequest/event-loadend.htm"
@@ -12669,16 +12673,20 @@
         "path": "XMLHttpRequest/event-readystatechange-loaded.htm",
         "url": "/XMLHttpRequest/event-readystatechange-loaded.htm"
       },
       {
         "path": "XMLHttpRequest/event-timeout.htm",
         "url": "/XMLHttpRequest/event-timeout.htm"
       },
       {
+        "path": "XMLHttpRequest/event-timeout-order.htm",
+        "url": "/XMLHttpRequest/event-timeout-order.htm"
+      },
+      {
         "path": "XMLHttpRequest/event-upload-progress-crossorigin.sub.htm",
         "url": "/XMLHttpRequest/event-upload-progress-crossorigin.sub.htm"
       },
       {
         "path": "XMLHttpRequest/event-upload-progress.htm",
         "url": "/XMLHttpRequest/event-upload-progress.htm"
       },
       {
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/abort-after-send.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[abort-after-send.htm]
-  type: testharness
-  [XMLHttpRequest: abort() after send()]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/abort-during-upload.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[abort-during-upload.htm]
-  type: testharness
-  [XMLHttpRequest: abort() while sending data]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/abort-event-order.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[abort-event-order.htm]
-  type: testharness
-  [XMLHttpRequest: The abort() method: abort and loadend events]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/event-progress.htm.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[event-progress.htm]
-  type: testharness
-  expected:
-    if asan or (debug and (os == "linux")): OK
-    if debug and (os == "mac"): OK
-    if debug and (os == "win"): OK
-    TIMEOUT
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/response-data-progress.htm.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[response-data-progress.htm]
-  type: testharness
-  expected: TIMEOUT
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-network-error-async-events.sub.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[send-network-error-async-events.sub.htm]
-  type: testharness
-  [XmlHttpRequest: The send() method: Fire a progress event named error when Network error happens (synchronous flag is unset)]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/XMLHttpRequest/send-timeout-events.htm.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[send-timeout-events.htm]
-  type: testharness
-  [XMLHttpRequest: The send() method: timeout is not 0 ]
-    expected: FAIL
-
--- a/testing/web-platform/tests/XMLHttpRequest/abort-during-upload.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/abort-during-upload.htm
@@ -1,42 +1,30 @@
 <!doctype html>
 <html>
   <head>
     <title>XMLHttpRequest: abort() while sending data</title>
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-abort()-method" data-tested-assertations="following-sibling::ol/li[4]/ol/li[7] following-sibling::ol/li[4]/ol/li[7]/ol/li[2] following-sibling::ol/li[4]/ol/li[7]/ol/li[3] following-sibling::ol/li[4]/ol/li[7]/ol/li[4]" />
     <link rel="help" href="https://xhr.spec.whatwg.org/#make-upload-progress-notifications" data-tested-assertations="following::ul[1]/li[1] following::ul[1]/li[2]/ol[1]/li[2] following::ul[1]/li[2]/ol[1]/li[3] following::ul[1]/li[2]/ol[1]/li[4]" />
   </head>
   <body>
     <div id="log"></div>
     <script>
       var test = async_test(document.title, {timeout:1100})
-      var result = []
-      var expected = ['progress on XHR Upload', 'abort on XHR Upload', 'loadend on XHR Upload', 'progress on XHR', 'abort on XHR', 'loadend on XHR']
-      function logEvt (e) {
-        var str = e.type+' on '
-        str += e.target instanceof XMLHttpRequest ? 'XHR' : 'XHR Upload'
-        result.push(str)
-      }
       test.step(function() {
         var client = new XMLHttpRequest()
+        prepare_xhr_for_event_order_test(client);
         client.open("POST", "resources/delay.py?ms=1000")
-        client.addEventListener('progress', logEvt)
-        client.addEventListener('abort', logEvt)
-        client.addEventListener('loadend', function (e) {
-          logEvt(e)
+        client.addEventListener("loadend", function(e) {
           test.step(function() {
-            assert_equals(client.readyState, 4)
-            assert_array_equals(result, expected)
+            assert_xhr_event_order_matches([1, "loadstart(0,0,false)", "upload.loadstart(0,9999,true)", 4, "upload.progress(0,0,false)", "upload.abort(0,0,false)", "upload.loadend(0,0,false)", "progress(0,0,false)", "abort(0,0,false)", "loadend(0,0,false)"]);
             test.done()
           })
-        })
-        client.upload.addEventListener('loadend', logEvt)
-        client.upload.addEventListener('progress', logEvt)
-        client.upload.addEventListener('abort', logEvt)
+        });
         client.send((new Array(10000)).join('a'))
         client.abort()
       })
     </script>
   </body>
 </html>
--- a/testing/web-platform/tests/XMLHttpRequest/abort-event-order.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/abort-event-order.htm
@@ -1,62 +1,49 @@
 <!DOCTYPE html>
 <html>
 <head>
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-abort()-method" data-tested-assertations="following-sibling::ol/li[4]/ol/li[3] following-sibling::ol/li[4]/ol/li[5] following-sibling::ol/li[4]/ol/li[6] following-sibling::ol/li[4]/ol/li[7]/ol/li[3] following-sibling::ol/li[4]/ol/li[7]/ol/li[4] following-sibling::ol/li[5]" />
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
     <title>XMLHttpRequest: The abort() method: abort and loadend events</title>
 </head>
 
 <body>
     <div id="log"></div>
 
     <script type="text/javascript">
         var test = async_test();
 
         test.step(function()
         {
             var xhr = new XMLHttpRequest();
-            var expect = [1, 4, "upload.abort", "upload.loadend", "abort", "loadend"];
-            var actual = [];
+            prepare_xhr_for_event_order_test(xhr);
 
-            xhr.onreadystatechange = function()
-            {
-                test.step(function()
-                {
-                     actual.push(xhr.readyState);
-                });
-            };
-            xhr.onloadstart = function()
-            {
+            xhr.addEventListener("loadstart", function() {
                 test.step(function()
                 {
                     var readyState = xhr.readyState;
                     if (readyState == 1)
                     {
                         xhr.abort();
                         VerifyResult();
-                    }else{
+                    } else {
                         assert_unreached('Loadstart event should not fire in readyState '+readyState);
                     }
                 });
-            };
-
-            xhr.onloadend          = function(e){ actual.push(e.type); };
-            xhr.onabort            = function(e){ actual.push(e.type); };
-
-            xhr.upload.onloadend   = function(e){ actual.push("upload." + e.type); };
-            xhr.upload.onabort     = function(e){ actual.push("upload." + e.type); };
+            });
 
             function VerifyResult()
             {
                 test.step(function()
                 {
-                    assert_array_equals(actual, expect);
+                    assert_xhr_event_order_matches([1, "loadstart(0,0,false)", 4, "upload.progress(0,0,false)", "upload.abort(0,0,false)", "upload.loadend(0,0,false)", "progress(0,0,false)", "abort(0,0,false)", "loadend(0,0,false)"]);
+
                     assert_equals(xhr.readyState, 0, 'state should be UNSENT');
                     test.done();
                 });
             };
 
             xhr.open("POST", "./resources/content.py", true);
             xhr.send("Test Message");
         });
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/event-error-order.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="assert" content="Check the order of events fired when the request has failed.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
+    <title>XMLHttpRequest: event - error (order of events)</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        var test = async_test();
+
+        test.step(function()
+        {
+            var xhr = new XMLHttpRequest();
+            prepare_xhr_for_event_order_test(xhr);
+
+            xhr.addEventListener("loadend", function() {
+                test.step(function() {
+                    // no progress events due to CORS failure
+                    assert_xhr_event_order_matches([1, "loadstart(0,0,false)", "upload.loadstart(0,12,true)", 2, 4, "upload.error(0,0,false)", "upload.loadend(0,0,false)", "error(0,0,false)", "loadend(0,0,false)"]);
+                    test.done();
+                });
+            });
+
+            xhr.open("POST", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}", true);
+            xhr.send("Test Message");
+        });
+    </script>
+</body>
+</html>
--- a/testing/web-platform/tests/XMLHttpRequest/event-error.html
+++ b/testing/web-platform/tests/XMLHttpRequest/event-error.html
@@ -13,13 +13,13 @@
 async_test(function (t) {
   var client = new XMLHttpRequest();
   client.onerror = t.step_func(function(e) {
     assert_true(e instanceof ProgressEvent);
     assert_equals(e.type, "error");
     t.done();
   });
 
-  client.open("GET", "http://example.nonexist");
+  client.open("GET", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}");
   client.send("null");
 }, document.title);
 
 </script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/event-error.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>XMLHttpRequest Test: event - error</title>
+<link rel="author" title="Intel" href="http://www.intel.com">
+<meta name="assert" content="Check if event onerror is fired When the request has failed.">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="log"></div>
+
+<script>
+
+async_test(function (t) {
+  var client = new XMLHttpRequest();
+  client.onerror = t.step_func(function(e) {
+    assert_true(e instanceof ProgressEvent);
+    assert_equals(e.type, "error");
+    t.done();
+  });
+
+  client.open("GET", "http://nonexistent-origin.{{host}}:{{ports[http][0]}}");
+  client.send("null");
+}, document.title);
+
+</script>
--- a/testing/web-platform/tests/XMLHttpRequest/event-progress.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/event-progress.htm
@@ -18,12 +18,12 @@
       assert_true(e instanceof ProgressEvent);
       assert_equals(e.type, "progress");
       test.done();
     });
     client.onreadystatechange = test.step_func(function() {
       if (client.readyState === 4)
         assert_unreached("onprogress not called.");
     });
-    client.open("GET", "resources/trickle.py");
+    client.open("GET", "resources/trickle.py?count=4&delay=150");
     client.send(null);
   });
 </script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/event-timeout-order.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta name="assert" content="Check the order of events fired when the request has failed.">
+    <script src="/resources/testharness.js"></script>
+    <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
+    <title>XMLHttpRequest: event - timeout (order of events)</title>
+</head>
+
+<body>
+    <div id="log"></div>
+
+    <script type="text/javascript">
+        var test = async_test();
+
+        test.step(function()
+        {
+            var xhr = new XMLHttpRequest();
+            prepare_xhr_for_event_order_test(xhr);
+            xhr.addEventListener("loadend", function() {
+                test.step(function() {
+                    assert_xhr_event_order_matches([1, "loadstart(0,0,false)", "upload.loadstart(0,12,true)", 4, "upload.progress(0,0,false)", "upload.timeout(0,0,false)", "upload.loadend(0,0,false)", "progress(0,0,false)", "timeout(0,0,false)", "loadend(0,0,false)"]);
+                    test.done();
+                });
+            });
+
+            xhr.timeout = 5;
+            xhr.open("POST", "resources/delay.py?ms=20000");
+            xhr.send("Test Message");
+            setTimeout(test.step_func(function () {
+              assert_unreached("ontimeout not called.");
+            }), 10);
+        });
+    </script>
+</body>
+</html>
--- a/testing/web-platform/tests/XMLHttpRequest/resources/trickle.py
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/trickle.py
@@ -1,12 +1,15 @@
 import time
 
 def main(request, response):
+    chunk = "TEST_TRICKLE\n"
     delay = float(request.GET.first("ms", 500)) / 1E3
     count = int(request.GET.first("count", 50))
+    if "specifylength" in request.GET:
+        response.headers.set("Content-Length", count * len(chunk))
     time.sleep(delay)
     response.headers.set("Content-type", "text/plain")
     response.write_status_headers()
     time.sleep(delay);
     for i in xrange(count):
-        response.writer.write_content("TEST_TRICKLE\n")
+        response.writer.write_content(chunk)
         time.sleep(delay)
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/XMLHttpRequest/resources/xmlhttprequest-event-order.js
@@ -0,0 +1,33 @@
+(function(global) {
+  var recorded_xhr_events = [];
+
+  function record_xhr_event(e) {
+    var prefix = e.target instanceof XMLHttpRequestUpload ? "upload." : "";
+    recorded_xhr_events.push((prefix || "") + e.type + "(" + e.loaded + "," + e.total + "," + e.lengthComputable + ")");
+  }
+
+  global.prepare_xhr_for_event_order_test = function(xhr) {
+    xhr.addEventListener("readystatechange", function(e) {
+      recorded_xhr_events.push(xhr.readyState);
+    });
+    var events = ["loadstart", "progress", "abort", "timeout", "error", "load", "loadend"];
+    for(var i=0; i<events.length; ++i) {
+      xhr.addEventListener(events[i], record_xhr_event);
+    }
+    if ("upload" in xhr) {
+      for(var i=0; i<events.length; ++i) {
+        xhr.upload.addEventListener(events[i], record_xhr_event);
+      }
+    }
+  }
+
+  global.assert_xhr_event_order_matches = function(expected) {
+    try {
+      assert_array_equals(recorded_xhr_events, expected);
+    } catch(e) {
+      e.message += "\nRecorded events were:" + recorded_xhr_events.join(", ");
+      e.message += "\nExpected events were:" + expected.join(", ");
+      throw e;
+    }
+  }
+}(this));
--- a/testing/web-platform/tests/XMLHttpRequest/response-data-progress.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/response-data-progress.htm
@@ -10,36 +10,42 @@
     <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onprogress" data-tested-assertations="/../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-progress" data-tested-assertations="/../.." />
 </head>
 
 <div id="log"></div>
 
 <script>
 
-var test = async_test();
-
-test.step(function() {
+function doTest(test, expectedLengthComputable, expectedTotal, url) {
     var client = new XMLHttpRequest();
     var lastSize = 0;
 
-    client.onprogress = test.step_func(function() {
+    client.onprogress = test.step_func(function(e) {
+        assert_equals(e.total, expectedTotal);
+        assert_equals(e.lengthComputable, expectedLengthComputable);
+
         var currentSize = client.responseText.length;
 
         if (lastSize > 0 && currentSize > lastSize) {
             // growth from a positive size to bigger!
-
             test.done();
         }
 
         lastSize = currentSize;
     });
 
     client.onreadystatechange = test.step_func(function() {
         if (client.readyState === 4) {
             assert_unreached("onprogress not called multiple times, or response body did not grow.");
         }
     });
 
-    client.open("GET", "resources/trickle.py?count=1000");
+    client.open("GET", url);
     client.send(null);
-});
+    return client;
+}
+
+async_test(function () { doTest(this, false, 0, "resources/trickle.py?count=6&delay=150"); },
+           document.title + ', unknown content-length');
+async_test(function () { doTest(this, true, 78, "resources/trickle.py?count=6&delay=150&specifylength=1"); },
+           document.title + ', known content-length');
 </script>
--- a/testing/web-platform/tests/XMLHttpRequest/security-consideration.sub.html
+++ b/testing/web-platform/tests/XMLHttpRequest/security-consideration.sub.html
@@ -16,15 +16,21 @@
         xhr.onprogress = this.unreached_func("MUST NOT dispatch progress event.");
         xhr.onload = this.unreached_func("MUST NOT dispatch load event.");
         xhr.onerror = this.step_func(function(pe) {
           assert_equals(pe.type, "error");
           assert_equals(pe.loaded, 0, "loaded is zero.");
           assert_false(pe.lengthComputable, "lengthComputable is false.");
           assert_equals(pe.total, 0, "total is zero.");
         });
-        xhr.onloadend = this.step_func_done();
+        xhr.onloadend = this.step_func(function(pe) {
+          assert_equals(pe.type, "loadend");
+          assert_equals(pe.loaded, 0, "loaded is zero.");
+          assert_false(pe.lengthComputable, "lengthComputable is false.");
+          assert_equals(pe.total, 0, "total is zero.");
+          this.done();
+        });
         xhr.open("GET", "http://{{host}}:{{ports[http][1]}}/XMLHttpRequest/resources/img.jpg", true);
         xhr.send(null);
       })
     </script>
   </body>
 </html>
--- a/testing/web-platform/tests/XMLHttpRequest/send-no-response-event-order.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/send-no-response-event-order.htm
@@ -6,56 +6,40 @@
     <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-loadstart" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-loadend" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following-sibling::ol/li[9]/ol/li[2]" />
     <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[10] following::a[contains(@href,'#switch-done')]/.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#switch-done" data-tested-assertations="following::ol[1]/li[3] following::ol[1]/li[4] following::ol[1]/li[6] following::ol[1]/li[7]" />
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-response-attribute" data-tested-assertations="following-sibling::ol/li[1]" />
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
     <title>XMLHttpRequest: The send() method: event order when there is no response entity body</title>
 </head>
 
 <body>
     <div id="log"></div>
 
     <script type="text/javascript">
         var test = async_test();
 
         test.step(function()
         {
             var xhr = new XMLHttpRequest();
-            var expect = ["loadstart", 4, "load", "loadend"];
-            var actual = [];
+            prepare_xhr_for_event_order_test(xhr);
 
-            xhr.onreadystatechange = test.step_func(function()
-            {
-                test.step(function()
-                {
-                    if (xhr.readyState == 3)
-                    {
-                        assert_equals(xhr.response, "");
-                    }
-                    else if (xhr.readyState == 4)
-                    {
-                        actual.push(xhr.readyState);
-                    }
-                });
-            });
+            xhr.addEventListener("readystatechange", test.step_func(function() {
+                if (xhr.readyState == 3) {
+                    assert_equals(xhr.response, "");
+                }
+            }));
 
-            xhr.onloadstart        = test.step_func(function(e){ actual.push(e.type); });
-            xhr.onload             = test.step_func(function(e){ actual.push(e.type); });
-            xhr.onloadend          = test.step_func(function(e){
-                actual.push(e.type);
-                assert_array_equals(actual, expect);
+            xhr.addEventListener("loadend", test.step_func(function(e) {
+                assert_xhr_event_order_matches([1, "loadstart(0,0,false)", 2, "progress(0,0,false)", 4,"load(0,0,false)", "loadend(0,0,false)"]);
                 test.done();
-            });
-
-            xhr.upload.onloadstart = test.step_func(function(e){ assert_unreached('upload.'+e.type); });
-            xhr.upload.onload      = test.step_func(function(e){ assert_unreached('upload.'+e.type); });
-            xhr.upload.onloadend   = test.step_func(function(e){ assert_unreached('upload.'+e.type); });
+            }));
 
             xhr.open("POST", "./resources/content.py", true);
             xhr.send();
         });
     </script>
 </body>
 </html>
--- a/testing/web-platform/tests/XMLHttpRequest/send-sync-response-event-order.htm
+++ b/testing/web-platform/tests/XMLHttpRequest/send-sync-response-event-order.htm
@@ -1,13 +1,14 @@
 <!DOCTYPE html>
 <html>
 <head>
     <script src="/resources/testharness.js"></script>
     <script src="/resources/testharnessreport.js"></script>
+    <script src="resources/xmlhttprequest-event-order.js"></script>
     <title>XMLHttpRequest: The send() method: event order when synchronous flag is set</title>
     <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onloadstart" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#handler-xhr-onloadend" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-loadstart" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#event-xhr-loadend" data-tested-assertations="../.." />
     <link rel="help" href="https://xhr.spec.whatwg.org/#the-send()-method" data-tested-assertations="following-sibling::ol/li[9]" />
     <link rel="help" href="https://xhr.spec.whatwg.org/#same-origin-request-steps" data-tested-assertations="following::DL[1]/DT[1]"/>
     <link rel="help" href="https://xhr.spec.whatwg.org/#infrastructure-for-the-send()-method" data-tested-assertations="following::dt[11] following::a[contains(@href,'#switch-done')]/.." />
@@ -16,36 +17,19 @@
 </head>
 
 <body>
     <div id="log"></div>
 
     <script type="text/javascript">
         test(function () {
             var xhr = new XMLHttpRequest();
-            var expect = [4, "load", "loadend"];
-            var actual = [];
-
-            xhr.onreadystatechange = function()
-            {
-                if (xhr.readyState == 4)
-                {
-                    actual.push(xhr.readyState);
-                }
-            };
-
-            xhr.onloadstart        = function(e){ actual.push(e.type); };
-            xhr.onload             = function(e){ actual.push(e.type); };
-            xhr.onloadend          = function(e){ actual.push(e.type); };
-
-            xhr.upload.onload      = function(e){ actual.push("upload." + e.type); };
-            xhr.upload.onloadstart = function(e){ actual.push("upload." + e.type); };
-            xhr.upload.onloadend   = function(e){ actual.push("upload." + e.type);};
+            prepare_xhr_for_event_order_test(xhr);
 
             xhr.open("POST", "./resources/content.py", false);
             xhr.send("Test Message");
 
             assert_equals(xhr.response, "Test Message");
-            assert_array_equals(actual, expect);
+            assert_xhr_event_order_matches([1, 4, "load(12,12,true)", "loadend(12,12,true)"]);
         });
     </script>
 </body>
 </html>