Bug 658178 - 'Make XHR2 response/responseType work in Web Workers'. r=jst+sicking+mrbkap.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 07 Nov 2011 17:01:29 -0800
changeset 79927 29a1638f2e623aace87bbfacb79b2f369082f44a
parent 79926 d67299b21838af096baf7b5c2bb574d46d1c7169
child 79928 410deae0fe1367a86474af0d380956b2daa6e410
push id21442
push userbturner@mozilla.com
push dateTue, 08 Nov 2011 01:10:10 +0000
treeherdermozilla-central@29a1638f2e62 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs658178
milestone10.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 658178 - 'Make XHR2 response/responseType work in Web Workers'. r=jst+sicking+mrbkap.
content/base/src/nsContentUtils.cpp
content/base/src/nsXMLHttpRequest.cpp
dom/workers/WorkerPrivate.cpp
dom/workers/XMLHttpRequest.cpp
dom/workers/XMLHttpRequest.h
dom/workers/XMLHttpRequestPrivate.cpp
dom/workers/XMLHttpRequestPrivate.h
dom/workers/test/Makefile.in
dom/workers/test/test_xhr2.html
dom/workers/test/xhr2_worker.js
js/src/jsapi.h
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5437,20 +5437,25 @@ DebugWrapperTraceCallback(PRUint32 langI
   callback->NoteScriptChild(langID, p);
 }
 
 // static
 void
 nsContentUtils::CheckCCWrapperTraversal(nsISupports* aScriptObjectHolder,
                                         nsWrapperCache* aCache)
 {
+  JSObject* wrapper = aCache->GetWrapper();
+  if (!wrapper) {
+    return;
+  }
+
   nsXPCOMCycleCollectionParticipant* participant;
   CallQueryInterface(aScriptObjectHolder, &participant);
 
-  DebugWrapperTraversalCallback callback(aCache->GetWrapper());
+  DebugWrapperTraversalCallback callback(wrapper);
 
   participant->Traverse(aScriptObjectHolder, callback);
   NS_ASSERTION(callback.mFound,
                "Cycle collection participant didn't traverse to preserved "
                "wrapper! This will probably crash.");
 
   callback.mFound = false;
   participant->Trace(aScriptObjectHolder, DebugWrapperTraceCallback, &callback);
@@ -5902,19 +5907,21 @@ nsContentUtils::ReleaseWrapper(nsISuppor
 }
 
 // static
 void
 nsContentUtils::TraceWrapper(nsWrapperCache* aCache, TraceCallback aCallback,
                              void *aClosure)
 {
   if (aCache->PreservingWrapper()) {
-    aCallback(nsIProgrammingLanguage::JAVASCRIPT,
-              aCache->GetWrapperPreserveColor(),
-              "Preserved wrapper", aClosure);
+    JSObject *wrapper = aCache->GetWrapperPreserveColor();
+    if (wrapper) {
+      aCallback(nsIProgrammingLanguage::JAVASCRIPT, wrapper,
+                "Preserved wrapper", aClosure);
+    }
   }
   else {
     JSObject *expando = aCache->GetExpandoObjectPreserveColor();
     if (expando) {
       aCallback(nsIProgrammingLanguage::JAVASCRIPT, expando, "Expando object",
                 aClosure);
     }
   }
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -448,16 +448,24 @@ nsXMLHttpRequest::~nsXMLHttpRequest()
                 XML_HTTP_REQUEST_SENT |
                 XML_HTTP_REQUEST_LOADING)) {
     Abort();
   }
 
   NS_ABORT_IF_FALSE(!(mState & XML_HTTP_REQUEST_SYNCLOOPING), "we rather crash than hang");
   mState &= ~XML_HTTP_REQUEST_SYNCLOOPING;
 
+  // This can happen if the XHR was only used by C++ (and so never created a JS
+  // wrapper) that also made an ArrayBuffer.
+  if (PreservingWrapper()) {
+    nsContentUtils::ReleaseWrapper(
+      static_cast<nsIDOMEventTarget*>(
+        static_cast<nsDOMEventTargetHelper*>(this)), this);
+  }
+
   nsLayoutStatics::Release();
 }
 
 void
 nsXMLHttpRequest::RootResultArrayBuffer()
 {
   nsContentUtils::PreserveWrapper(
     static_cast<nsIDOMEventTarget*>(
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -533,40 +533,48 @@ struct MainThreadChromeWorkerStructuredC
 {
   static JSObject*
   Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32 aTag,
        uint32 aData, void* aClosure)
   {
     AssertIsOnMainThread();
 
     JSObject* clone =
+      MainThreadWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
+                                                     aClosure);
+    if (clone) {
+      return clone;
+    }
+
+    clone =
       ChromeWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData,
                                                  aClosure);
     if (clone) {
       return clone;
     }
 
     JS_ClearPendingException(aCx);
     return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nsnull);
   }
 
   static JSBool
   Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSObject* aObj,
         void* aClosure)
   {
     AssertIsOnMainThread();
 
-    JSBool ok =
-      ChromeWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj, aClosure);
-    if (ok) {
-      return ok;
+    if (MainThreadWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
+                                                        aClosure) ||
+        ChromeWorkerStructuredCloneCallbacks::Write(aCx, aWriter, aObj,
+                                                    aClosure) ||
+        NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull)) {
+      return true;
     }
 
-    JS_ClearPendingException(aCx);
-    return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nsnull);
+    return false;
   }
 
   static void
   Error(JSContext* aCx, uint32 aErrorId)
   {
     AssertIsOnMainThread();
 
     NS_DOMStructuredCloneError(aCx, aErrorId);
--- a/dom/workers/XMLHttpRequest.cpp
+++ b/dom/workers/XMLHttpRequest.cpp
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "XMLHttpRequest.h"
 
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 
+#include "Exceptions.h"
 #include "WorkerPrivate.h"
 #include "XMLHttpRequestPrivate.h"
 
 #include "WorkerInlines.h"
 
 #define PROPERTY_FLAGS \
   JSPROP_ENUMERATE | JSPROP_SHARED
 
@@ -270,20 +271,22 @@ class XMLHttpRequest
 
   enum SLOT {
     SLOT_channel = 0,
     SLOT_responseXML,
     SLOT_responseText,
     SLOT_status,
     SLOT_statusText,
     SLOT_readyState,
+    SLOT_response,
     SLOT_multipart,
     SLOT_mozBackgroundRequest,
     SLOT_withCredentials,
     SLOT_upload,
+    SLOT_responseType,
 
     SLOT_COUNT
   };
 
   enum {
     UNSENT = 0,
     OPENED = 1,
     HEADERS_RECEIVED = 2,
@@ -337,16 +340,17 @@ public:
       return false; \
     } \
   }
 
     HANDLE_STATE_VALUE(mResponseText, SLOT_responseText)
     HANDLE_STATE_VALUE(mStatus, SLOT_status)
     HANDLE_STATE_VALUE(mStatusText, SLOT_statusText)
     HANDLE_STATE_VALUE(mReadyState, SLOT_readyState)
+    HANDLE_STATE_VALUE(mResponse, SLOT_response)
 
 #undef HANDLE_STATE_VALUE
 
     return true;
   }
 
 private:
   // No instance of this class should ever be created so these are explicitly
@@ -390,29 +394,36 @@ private:
   static JSBool
   Construct(JSContext* aCx, uintN aArgc, jsval* aVp)
   {
     JSObject* obj = JS_NewObject(aCx, &sClass, NULL, NULL);
     if (!obj) {
       return false;
     }
 
+    JSString* textStr = JS_NewStringCopyN(aCx, "text", 4);
+    if (!textStr) {
+      return false;
+    }
+
     jsval emptyString = JS_GetEmptyStringValue(aCx);
     jsval zero = INT_TO_JSVAL(0);
 
     if (!JS_SetReservedSlot(aCx, obj, SLOT_channel, JSVAL_NULL) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_responseXML, JSVAL_NULL) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_responseText, emptyString) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_status, zero) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_statusText, emptyString) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_readyState, zero) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_multipart, JSVAL_FALSE) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_mozBackgroundRequest, JSVAL_FALSE) ||
         !JS_SetReservedSlot(aCx, obj, SLOT_withCredentials, JSVAL_FALSE) ||
-        !JS_SetReservedSlot(aCx, obj, SLOT_upload, JSVAL_NULL)) {
+        !JS_SetReservedSlot(aCx, obj, SLOT_upload, JSVAL_NULL) ||
+        !JS_SetReservedSlot(aCx, obj, SLOT_responseType,
+                            STRING_TO_JSVAL(textStr))) {
       return false;
     }
 
     WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
     XMLHttpRequestPrivate* priv = new XMLHttpRequestPrivate(obj, workerPrivate);
     if (!SetJSPrivateSafeish(aCx, obj, priv)) {
       delete priv;
       return false;
@@ -457,17 +468,17 @@ private:
 
     jsval rval;
     if (!JS_GetReservedSlot(aCx, aObj, slot, &rval)) {
       return false;
     }
 
     if (JSVAL_IS_VOID(rval)) {
       // Throw an exception.
-      JS_ReportError(aCx, "Unable to retrieve %s property", name);
+      exceptions::ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR);
       return false;
     }
 
     *aVp = rval;
     return true;
   }
 
   static JSBool
@@ -530,29 +541,35 @@ private:
     int32 slot = JSID_TO_INT(aIdval);                                          \
                                                                                \
     XMLHttpRequestPrivate* priv =                                              \
       GetInstancePrivate(aCx, aObj, sProperties[slot].name);                   \
     if (!priv) {                                                               \
       return false;                                                            \
     }                                                                          \
                                                                                \
+    jsval oldVal;                                                              \
+    if (!JS_GetReservedSlot(aCx, aObj, slot, &oldVal)) {                       \
+      return false;                                                            \
+    }                                                                          \
+                                                                               \
     jsval rval = *aVp;                                                         \
-    if (!priv->Set##_name (aCx, &rval) ||                                      \
+    if (!priv->Set##_name (aCx, oldVal, &rval) ||                              \
         !JS_SetReservedSlot(aCx, aObj, slot, rval)) {                          \
       return false;                                                            \
     }                                                                          \
                                                                                \
     *aVp = rval;                                                               \
     return true;                                                               \
   }
 
   IMPL_SETTER(Multipart)
   IMPL_SETTER(MozBackgroundRequest)
   IMPL_SETTER(WithCredentials)
+  IMPL_SETTER(ResponseType)
 
 #undef IMPL_SETTER
 
   static JSBool
   GetEventListener(JSContext* aCx, JSObject* aObj, jsid aIdval, jsval* aVp)
   {
     JS_ASSERT(JSID_IS_INT(aIdval));
     JS_ASSERT(JSID_TO_INT(aIdval) >= 0 && JSID_TO_INT(aIdval) < STRING_COUNT);
@@ -778,23 +795,27 @@ JSPropertySpec XMLHttpRequest::sProperti
     js_GetterOnlyPropertyStub },
 
   GENERIC_READONLY_PROPERTY(channel)
   GENERIC_READONLY_PROPERTY(responseXML)
   GENERIC_READONLY_PROPERTY(responseText)
   GENERIC_READONLY_PROPERTY(status)
   GENERIC_READONLY_PROPERTY(statusText)
   GENERIC_READONLY_PROPERTY(readyState)
+  GENERIC_READONLY_PROPERTY(response)
 
-  { "multipart", 7, PROPERTY_FLAGS, GetProperty, SetMultipart },
-  { "mozBackgroundRequest", 8, PROPERTY_FLAGS, GetProperty,
-    SetMozBackgroundRequest },
-  { "withCredentials", 9, PROPERTY_FLAGS, GetProperty, SetWithCredentials },
-  { "upload", SLOT_upload, PROPERTY_FLAGS, GetUpload, 
+  { "multipart", SLOT_multipart, PROPERTY_FLAGS, GetProperty, SetMultipart },
+  { "mozBackgroundRequest", SLOT_mozBackgroundRequest, PROPERTY_FLAGS,
+    GetProperty, SetMozBackgroundRequest },
+  { "withCredentials", SLOT_withCredentials, PROPERTY_FLAGS, GetProperty,
+    SetWithCredentials },
+  { "upload", SLOT_upload, PROPERTY_FLAGS, GetUpload,
     js_GetterOnlyPropertyStub },
+  { "responseType", SLOT_responseType, PROPERTY_FLAGS, GetProperty,
+    SetResponseType },
   { sEventStrings[STRING_onreadystatechange], STRING_onreadystatechange,
     PROPERTY_FLAGS, GetEventListener, SetEventListener },
   { sEventStrings[STRING_onabort], STRING_onabort, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
   { sEventStrings[STRING_onerror], STRING_onerror, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
   { sEventStrings[STRING_onload], STRING_onload, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
--- a/dom/workers/XMLHttpRequest.h
+++ b/dom/workers/XMLHttpRequest.h
@@ -51,20 +51,22 @@ bool
 InitClasses(JSContext* aCx, JSObject* aGlobal, JSObject* aProto);
 
 struct StateData
 {
   jsval mResponseText;
   jsval mStatus;
   jsval mStatusText;
   jsval mReadyState;
+  jsval mResponse;
   bool mResponseTextException;
   bool mStatusException;
   bool mStatusTextException;
   bool mReadyStateException;
+  bool mResponseException;
 };
 
 bool
 UpdateXHRState(JSContext* aCx, JSObject* aObj, bool aIsUpload,
                const StateData& aNewState);
 
 } // namespace xhr
 
--- a/dom/workers/XMLHttpRequestPrivate.cpp
+++ b/dom/workers/XMLHttpRequestPrivate.cpp
@@ -36,17 +36,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "XMLHttpRequestPrivate.h"
 
 #include "nsIDOMEvent.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMProgressEvent.h"
-#include "nsIJSContextStack.h"
 #include "nsIRunnable.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsIXPConnect.h"
 
 #include "jstypedarray.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
@@ -83,17 +82,16 @@ public:
   PRUint64 mLastUploadTotal;
   bool mIsSyncXHR;
   bool mLastLengthComputable;
   bool mLastUploadLengthComputable;
   bool mSeenLoadStart;
   bool mSeenUploadLoadStart;
 
   // Only touched on the main thread.
-  nsString mPreviousResponseText;
   nsCString mPreviousStatusText;
   PRUint32 mSyncQueueKey;
   PRUint32 mSyncEventResponseSyncQueueKey;
   bool mUploadEventListenersAttached;
   bool mMainThreadSeenLoadStart;
 
 public:
   NS_DECL_ISUPPORTS
@@ -153,17 +151,16 @@ public:
   bool
   AddRemoveEventListeners(bool aUpload, bool aAdd);
 
   void
   Reset()
   {
     AssertIsOnMainThread();
 
-    mPreviousResponseText.Truncate();
     mPreviousStatusText.Truncate();
 
     if (mUploadEventListenersAttached) {
       AddRemoveEventListeners(true, false);
     }
   }
 
   PRUint32
@@ -434,70 +431,98 @@ public:
   }
 };
 
 NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener)
 
 class EventRunnable : public MainThreadProxyRunnable
 {
   nsString mType;
-  nsString mResponseText;
+  nsString mResponseType;
+  JSAutoStructuredCloneBuffer mResponseBuffer;
+  nsTArray<nsCOMPtr<nsISupports> > mClonedObjects;
+  jsval mResponse;
   nsCString mStatusText;
   PRUint64 mLoaded;
   PRUint64 mTotal;
   PRUint32 mChannelId;
   PRUint32 mStatus;
   PRUint16 mReadyState;
   bool mUploadEvent;
   bool mProgressEvent;
   bool mLengthComputable;
   bool mResponseTextException;
   bool mStatusException;
   bool mStatusTextException;
   bool mReadyStateException;
+  bool mResponseException;
 
 public:
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType,
                 bool aLengthComputable, PRUint64 aLoaded, PRUint64 aTotal)
   : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
-    mLoaded(aLoaded), mTotal(aTotal), mChannelId(aProxy->mInnerChannelId),
-    mStatus(0), mReadyState(0), mUploadEvent(aUploadEvent),
-    mProgressEvent(true), mLengthComputable(aLengthComputable),
-    mResponseTextException(false), mStatusException(false),
-    mStatusTextException(false), mReadyStateException(false)
+    mResponse(JSVAL_VOID), mLoaded(aLoaded), mTotal(aTotal),
+    mChannelId(aProxy->mInnerChannelId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(true),
+    mLengthComputable(aLengthComputable), mResponseTextException(false),
+    mStatusException(false), mStatusTextException(false),
+    mReadyStateException(false), mResponseException(false)
   { }
 
   EventRunnable(Proxy* aProxy, bool aUploadEvent, const nsString& aType)
   : MainThreadProxyRunnable(aProxy->mWorkerPrivate, aProxy), mType(aType),
-    mLoaded(0), mTotal(0), mChannelId(aProxy->mInnerChannelId), mStatus(0),
-    mReadyState(0), mUploadEvent(aUploadEvent), mProgressEvent(false),
-    mLengthComputable(0), mResponseTextException(false),
-    mStatusException(false), mStatusTextException(false),
-    mReadyStateException(false)
+    mResponse(JSVAL_VOID), mLoaded(0), mTotal(0),
+    mChannelId(aProxy->mInnerChannelId), mStatus(0), mReadyState(0),
+    mUploadEvent(aUploadEvent), mProgressEvent(false), mLengthComputable(0),
+    mResponseTextException(false), mStatusException(false),
+    mStatusTextException(false), mReadyStateException(false),
+    mResponseException(false)
   { }
 
   bool
   PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     nsRefPtr<nsXMLHttpRequest>& xhr = mProxy->mXHR;
     NS_ASSERTION(xhr, "Must have an XHR here!");
 
-    if (NS_SUCCEEDED(xhr->GetResponseText(mResponseText))) {
-      if (mResponseText == mProxy->mPreviousResponseText) {
-        mResponseText.SetIsVoid(true);
+    if (NS_FAILED(xhr->GetResponseType(mResponseType))) {
+      NS_ERROR("This should never fail!");
+    }
+
+    jsval response;
+    if (NS_SUCCEEDED(xhr->GetResponse(aCx, &response))) {
+      if (JSVAL_IS_UNIVERSAL(response)) {
+        mResponse = response;
       }
       else {
-        mProxy->mPreviousResponseText = mResponseText;
+        // Anything subject to GC must be cloned.
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(true) :
+          WorkerStructuredCloneCallbacks(true);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+
+        if (mResponseBuffer.write(aCx, response, callbacks, &clonedObjects)) {
+          mClonedObjects.SwapElements(clonedObjects);
+        }
+        else {
+          NS_ASSERTION(JS_IsExceptionPending(aCx),
+                       "This should really never fail unless OOM!");
+          mResponseException = true;
+        }
       }
-      mResponseTextException = false;
     }
     else {
-      mResponseTextException = true;
+      mResponseException = true;
     }
 
+    nsString responseText;
+    mResponseTextException = NS_FAILED(xhr->GetResponseText(responseText));
+
     mStatusException = NS_FAILED(xhr->GetStatus(&mStatus));
 
     if (NS_SUCCEEDED(xhr->GetStatusText(mStatusText))) {
       if (mStatusText == mProxy->mPreviousStatusText) {
         mStatusText.SetIsVoid(true);
       }
       else {
         mProxy->mPreviousStatusText = mStatusText;
@@ -561,31 +586,54 @@ public:
                        mProxy->mXMLHttpRequestPrivate->GetJSObject();
     if (!target) {
       NS_ASSERTION(mUploadEvent, "How else is this possible?!");
       return true;
     }
 
     xhr::StateData state;
 
+    state.mResponseException = mResponseException;
+    if (!mResponseException) {
+      if (mResponseBuffer.data()) {
+        NS_ASSERTION(JSVAL_IS_VOID(mResponse), "Huh?!");
+
+        JSStructuredCloneCallbacks* callbacks =
+          aWorkerPrivate->IsChromeWorker() ?
+          ChromeWorkerStructuredCloneCallbacks(false) :
+          WorkerStructuredCloneCallbacks(false);
+
+        nsTArray<nsCOMPtr<nsISupports> > clonedObjects;
+        clonedObjects.SwapElements(mClonedObjects);
+
+        jsval response;
+        if (!mResponseBuffer.read(aCx, &response, callbacks, &clonedObjects)) {
+          return false;
+        }
+
+        mResponseBuffer.clear();
+        state.mResponse = response;
+      }
+      else {
+        state.mResponse = mResponse;
+      }
+    }
+
+    // This logic is all based on the assumption that mResponseTextException
+    // should be set if the responseType isn't "text". Otherwise we're going to
+    // hand out the wrong result if someone gets the responseText property.
     state.mResponseTextException = mResponseTextException;
-    if (mResponseTextException || mResponseText.IsVoid()) {
-      state.mResponseText = JSVAL_VOID;
-    }
-    else if (mResponseText.IsEmpty()) {
-      state.mResponseText = JS_GetEmptyStringValue(aCx);
+    if (!mResponseTextException) {
+      NS_ASSERTION(JSVAL_IS_STRING(state.mResponse) ||
+                   JSVAL_IS_NULL(state.mResponse),
+                   "Bad response!");
+      state.mResponseText = state.mResponse;
     }
     else {
-      JSString* responseText = JS_NewUCStringCopyN(aCx, mResponseText.get(),
-                                                   mResponseText.Length());
-      if (!responseText) {
-        return false;
-      }
-      mResponseText.Truncate();
-      state.mResponseText = STRING_TO_JSVAL(responseText);
+      state.mResponseText = JSVAL_VOID;
     }
 
     state.mStatusException = mStatusException;
     state.mStatus = mStatusException ? JSVAL_VOID : INT_TO_JSVAL(mStatus);
 
     state.mStatusTextException = mStatusTextException;
     if (mStatusTextException || mStatusText.IsVoid()) {
       state.mStatusText = JSVAL_VOID;
@@ -626,16 +674,29 @@ public:
       return false;
     }
 
     bool dummy;
     if (!events::DispatchEventToTarget(aCx, target, event, &dummy)) {
       JS_ReportPendingException(aCx);
     }
 
+    // After firing the event set mResponse to JSVAL_NULL for chunked response
+    // types.
+    if (StringBeginsWith(mResponseType, NS_LITERAL_STRING("moz-chunked-"))) {
+      xhr::StateData newState = {
+        JSVAL_NULL, JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, JSVAL_NULL,
+        false, false, false, false, false
+      };
+
+      if (!xhr::UpdateXHRState(aCx, target, mUploadEvent, newState)) {
+        return false;
+      }
+    }
+
     return true;
   }
 };
 
 class WorkerThreadProxySyncRunnable : public nsRunnable
 {
 protected:
   WorkerPrivate* mWorkerPrivate;
@@ -778,16 +839,44 @@ public:
   intN
   MainThreadRun()
   {
     nsresult rv = mProxy->mXHR->SetWithCredentials(mValue);
     return GetDOMExceptionCodeFromResult(rv);
   }
 };
 
+class SetResponseTypeRunnable : public WorkerThreadProxySyncRunnable
+{
+  nsString mResponseType;
+
+public:
+  SetResponseTypeRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy,
+                          const nsAString& aResponseType)
+  : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy),
+    mResponseType(aResponseType)
+  { }
+
+  intN
+  MainThreadRun()
+  {
+    nsresult rv = mProxy->mXHR->SetResponseType(mResponseType);
+    mResponseType.Truncate();
+    if (NS_SUCCEEDED(rv)) {
+      rv = mProxy->mXHR->GetResponseType(mResponseType);
+    }
+    return GetDOMExceptionCodeFromResult(rv);
+  }
+
+  void
+  GetResponseType(nsAString& aResponseType) {
+    aResponseType.Assign(mResponseType);
+  }
+};
+
 class AbortRunnable : public WorkerThreadProxySyncRunnable
 {
 public:
   AbortRunnable(WorkerPrivate* aWorkerPrivate, Proxy* aProxy)
   : WorkerThreadProxySyncRunnable(aWorkerPrivate, aProxy)
   { }
 
   intN
@@ -906,20 +995,22 @@ public:
     if (mWithCredentials) {
       rv = mProxy->mXHR->SetWithCredentials(mWithCredentials);
       if (NS_FAILED(rv)) {
         return GetDOMExceptionCodeFromResult(rv);
       }
     }
 
     mProxy->mInnerChannelId++;
-    mProxy->mPreviousResponseText.Truncate();
     mProxy->mPreviousStatusText.Truncate();
 
     rv = mProxy->mXHR->Open(mMethod, mURL, true, mUser, mPassword, 1);
+    if (NS_SUCCEEDED(rv)) {
+      rv = mProxy->mXHR->SetResponseType(NS_LITERAL_STRING("text"));
+    }
     return GetDOMExceptionCodeFromResult(rv);
   }
 };
 
 class SendRunnable : public WorkerThreadProxySyncRunnable
 {
   JSAutoStructuredCloneBuffer mBody;
   PRUint32 mSyncQueueKey;
@@ -1170,17 +1261,20 @@ Proxy::HandleEvent(nsIDOMEvent* aEvent)
     }
     runnable = new EventRunnable(this, !!uploadTarget, type, lengthComputable,
                                  loaded, total);
   }
   else {
     runnable = new EventRunnable(this, !!uploadTarget, type);
   }
 
-  runnable->Dispatch(nsnull);
+  {
+    RuntimeService::AutoSafeJSContext cx;
+    runnable->Dispatch(cx);
+  }
 
   if (!uploadTarget) {
     if (type.EqualsASCII(sEventStrings[STRING_loadstart])) {
       NS_ASSERTION(!mMainThreadSeenLoadStart, "Huh?!");
       mMainThreadSeenLoadStart = true;
     }
     else if (mMainThreadSeenLoadStart &&
              type.EqualsASCII(sEventStrings[STRING_loadend])) {
@@ -1271,17 +1365,17 @@ XMLHttpRequestPrivate::Notify(JSContext*
     mCanceled = true;
     ReleaseProxy();
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool multipart;
@@ -1301,17 +1395,18 @@ XMLHttpRequestPrivate::SetMultipart(JSCo
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal,
+                                               jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool backgroundRequest;
@@ -1331,17 +1426,18 @@ XMLHttpRequestPrivate::SetMozBackgroundR
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
-XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval *aVp)
+XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval aOldVal,
+                                          jsval *aVp)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
   JSBool withCredentials;
@@ -1361,16 +1457,78 @@ XMLHttpRequestPrivate::SetWithCredential
   if (!runnable->Dispatch(aCx)) {
     return false;
   }
 
   return true;
 }
 
 bool
+XMLHttpRequestPrivate::SetResponseType(JSContext* aCx, jsval aOldVal,
+                                       jsval *aVp)
+{
+  mWorkerPrivate->AssertIsOnWorkerThread();
+
+  if (mCanceled) {
+    return false;
+  }
+
+  if (!mProxy || SendInProgress()) {
+    ThrowDOMExceptionForCode(aCx, INVALID_STATE_ERR);
+    return false;
+  }
+
+  JSString* jsstr = JS_ValueToString(aCx, *aVp);
+  if (!jsstr) {
+    return false;
+  }
+
+  nsDependentJSString responseType;
+  if (!responseType.init(aCx, jsstr)) {
+    return false;
+  }
+
+  // "document" is fine for the main thread but not for a worker. Short-circuit
+  // that here.
+  if (responseType.EqualsLiteral("document")) {
+    *aVp = aOldVal;
+    return true;
+  }
+
+  nsRefPtr<SetResponseTypeRunnable> runnable =
+    new SetResponseTypeRunnable(mWorkerPrivate, mProxy, responseType);
+  if (!runnable->Dispatch(aCx)) {
+    return false;
+  }
+
+  nsString acceptedResponseType;
+  runnable->GetResponseType(acceptedResponseType);
+
+
+  if (acceptedResponseType == responseType) {
+    // Leave *aVp unchanged.
+  }
+  else if (acceptedResponseType.IsEmpty()) {
+    // Empty string.
+    *aVp = JS_GetEmptyStringValue(aCx);
+  }
+  else {
+    // Some other string.
+    jsstr = JS_NewUCStringCopyN(aCx, acceptedResponseType.get(),
+                                acceptedResponseType.Length());
+    if (!jsstr) {
+      return false;
+    }
+    *aVp = STRING_TO_JSVAL(jsstr);
+  }
+
+  return true;
+}
+
+bool
 XMLHttpRequestPrivate::Abort(JSContext* aCx)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   if (mCanceled) {
     return false;
   }
 
@@ -1675,18 +1833,18 @@ XMLHttpRequestPrivate::OverrideMimeType(
 bool
 XMLHttpRequestPrivate::MaybeDispatchPrematureAbortEvents(JSContext* aCx,
                                                          bool aFromOpen)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
   NS_ASSERTION(mProxy, "Must have a proxy here!");
 
   xhr::StateData state = {
-    JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, INT_TO_JSVAL(4),
-    false, false, false, false
+    JSVAL_VOID, JSVAL_VOID, JSVAL_VOID, INT_TO_JSVAL(4), JSVAL_VOID,
+    false, false, false, false, false
   };
 
   if (mProxy->mSeenUploadLoadStart) {
     JSObject* target = mProxy->mXMLHttpRequestPrivate->GetUploadJSObject();
     NS_ASSERTION(target, "Must have a target!");
 
     if (!xhr::UpdateXHRState(aCx, target, true, state) ||
         !DispatchPrematureAbortEvent(aCx, target, STRING_abort, true) ||
--- a/dom/workers/XMLHttpRequestPrivate.h
+++ b/dom/workers/XMLHttpRequestPrivate.h
@@ -106,23 +106,26 @@ public:
   using events::EventTarget::TraceInstance;
   using events::EventTarget::GetEventListenerOnEventTarget;
   using events::EventTarget::SetEventListenerOnEventTarget;
 
   bool
   Notify(JSContext* aCx, Status aStatus);
 
   bool
-  SetMultipart(JSContext* aCx, jsval *aVp);
+  SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
-  SetMozBackgroundRequest(JSContext* aCx, jsval *aVp);
+  SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
-  SetWithCredentials(JSContext* aCx, jsval *aVp);
+  SetWithCredentials(JSContext* aCx, jsval aOldVal, jsval *aVp);
+
+  bool
+  SetResponseType(JSContext* aCx, jsval aOldVal, jsval *aVp);
 
   bool
   Abort(JSContext* aCx);
 
   JSString*
   GetAllResponseHeaders(JSContext* aCx);
 
   JSString*
--- a/dom/workers/test/Makefile.in
+++ b/dom/workers/test/Makefile.in
@@ -99,16 +99,18 @@ include $(topsrcdir)/config/rules.mk
   threadErrors_worker3.js \
   threadErrors_worker4.js \
   test_threadTimeouts.html \
   threadTimeouts_worker.js \
   test_throwingOnerror.html \
   throwingOnerror_worker.js \
   test_xhr.html \
   xhr_worker.js \
+  test_xhr2.html \
+  xhr2_worker.js \
   test_xhrAbort.html \
   xhrAbort_worker.js \
   testXHR.txt \
   test_fibonacci.html \
   fibonacci_worker.js \
   test_newError.html \
   newError_worker.js \
   test_chromeWorker.html \
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_xhr2.html
@@ -0,0 +1,38 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests of DOM Worker Threads XHR(Bug 450452 )
+-->
+<head>
+  <title>Test for DOM Worker Threads XHR (Bug 450452 )</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450452">DOM Worker Threads XHR (Bug 450452)</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  var worker = new Worker("xhr2_worker.js");
+
+  worker.onmessage = function(event) {
+    is(event.data, "done", "Got correct result");
+    SimpleTest.finish();
+  }
+  worker.postMessage("testXHR.txt");
+
+  SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/xhr2_worker.js
@@ -0,0 +1,155 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function(event) {
+  const url = event.data;
+
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", url, false);
+  xhr.send();
+
+  const refText = xhr.responseText;
+
+  function getResponse(type) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url, false);
+    if (type !== undefined) {
+      xhr.responseType = type;
+    }
+    xhr.send();
+    return xhr.response;
+  }
+
+  if (getResponse() != refText) {
+    throw new Error("unset responseType failed");
+  }
+
+  if (getResponse("") != refText) {
+    throw new Error("'' responseType failed");
+  }
+
+  if (getResponse("text") != refText) {
+    throw new Error("'text' responseType failed");
+  }
+
+  var array = new Uint8Array(getResponse("arraybuffer"));
+  if (String.fromCharCode.apply(String, array) != refText) {
+    throw new Error("'arraybuffer' responseType failed");
+  }
+
+  var blob = getResponse("blob");
+  if (new FileReaderSync().readAsText(blob) != refText) {
+    throw new Error("'blob' responseType failed");
+  }
+
+  // Make sure that we get invalid state exceptions when getting the wrong
+  // property.
+
+  function testResponseTextException(type) {
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", url, false);
+    xhr.responseType = type;
+    xhr.send();
+
+    var exception;
+
+    try {
+      xhr.responseText;
+    }
+    catch(e) {
+      exception = e;
+    }
+
+    if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+      throw new Error("Failed to throw when getting responseText on '" + type +
+                      "' type");
+    }
+  }
+
+  testResponseTextException("arraybuffer");
+  testResponseTextException("blob");
+
+  // Make sure "document" works, but returns text.
+  xhr = new XMLHttpRequest();
+
+  if (xhr.responseType != "text") {
+    throw new Error("Default value for responseType is wrong!");
+  }
+
+  xhr.open("GET", url, false);
+  xhr.responseType = "document";
+  xhr.send();
+
+  if (xhr.responseText != refText) {
+    throw new Error("'document' type not working correctly");
+  }
+
+  // Make sure setting responseType before open or after send fails.
+  var exception;
+
+  xhr = new XMLHttpRequest();
+  try {
+    xhr.responseType = "arraybuffer";
+  }
+  catch(e) {
+    exception = e;
+  }
+
+  if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+    throw new Error("Failed to throw when setting responseType before " +
+                    "calling open()");
+  }
+
+  xhr.open("GET", url);
+  xhr.responseType = "text";
+  xhr.onload = function(event) {
+    if (event.target.response != refText) {
+      throw new Error("Bad response!");
+    }
+
+    xhr = new XMLHttpRequest();
+    xhr.open("GET", url);
+    xhr.responseType = "moz-chunked-text";
+
+    var lastIndex = 0;
+    xhr.onprogress = function(event) {
+      if (refText.substr(lastIndex, xhr.response.length) != xhr.response) {
+        throw new Error("Bad chunk!");
+      }
+
+      lastIndex += xhr.response.length;
+    };
+
+    xhr.onload = function(event) {
+      if (lastIndex != refText.length) {
+        throw new Error("Didn't see all the data!");
+      }
+
+      setTimeout(function() {
+        if (xhr.response !== null) {
+          throw new Error("Should have gotten null response outside of event!");
+        }
+        postMessage("done");
+      }, 0);
+    }
+
+    xhr.send(null);
+  };
+  xhr.send();
+
+  exception = null;
+
+  try {
+    xhr.responseType = "arraybuffer";
+  }
+  catch(e) {
+    exception = e;
+  }
+
+  if (!exception || exception.code != DOMException.INVALID_STATE_ERR) {
+    throw new Error("Failed to throw when setting responseType after " +
+                    "calling send()");
+  }
+}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1642,16 +1642,26 @@ JSID_IS_EMPTY(jsid id)
 #ifdef JS_USE_JSID_STRUCT_TYPES
 extern JS_PUBLIC_DATA(jsid) JSID_VOID;
 extern JS_PUBLIC_DATA(jsid) JSID_EMPTY;
 #else
 # define JSID_VOID ((jsid)JSID_TYPE_VOID)
 # define JSID_EMPTY ((jsid)JSID_TYPE_OBJECT)
 #endif
 
+/*
+ * Returns true iff the given jsval is immune to GC and can be used across
+ * multiple JSRuntimes without requiring any conversion API.
+ */
+static JS_ALWAYS_INLINE JSBool
+JSVAL_IS_UNIVERSAL(jsval v)
+{
+    return !JSVAL_IS_GCTHING(v);
+}
+
 /************************************************************************/
 
 /* Lock and unlock the GC thing held by a jsval. */
 #define JSVAL_LOCK(cx,v)        (JSVAL_IS_GCTHING(v)                          \
                                  ? JS_LockGCThing(cx, JSVAL_TO_GCTHING(v))    \
                                  : JS_TRUE)
 #define JSVAL_UNLOCK(cx,v)      (JSVAL_IS_GCTHING(v)                          \
                                  ? JS_UnlockGCThing(cx, JSVAL_TO_GCTHING(v))  \