From dbd7d35169270005c7b9e7129f6e230ecf315636 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Mon, 7 Nov 2011 17:01:29 -0800 Subject: [PATCH] Bug 658178 - 'Make XHR2 response/responseType work in Web Workers'. r=jst+sicking+mrbkap. --- content/base/src/nsContentUtils.cpp | 15 +- content/base/src/nsXMLHttpRequest.cpp | 8 + dom/workers/WorkerPrivate.cpp | 20 ++- dom/workers/XMLHttpRequest.cpp | 37 +++- dom/workers/XMLHttpRequest.h | 2 + dom/workers/XMLHttpRequestPrivate.cpp | 236 +++++++++++++++++++++----- dom/workers/XMLHttpRequestPrivate.h | 9 +- dom/workers/test/Makefile.in | 2 + dom/workers/test/test_xhr2.html | 38 +++++ dom/workers/test/xhr2_worker.js | 155 +++++++++++++++++ js/src/jsapi.h | 10 ++ 11 files changed, 472 insertions(+), 60 deletions(-) create mode 100644 dom/workers/test/test_xhr2.html create mode 100644 dom/workers/test/xhr2_worker.js diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index b0ea141e9d2..be0e7b5aae2 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -5442,10 +5442,15 @@ 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, @@ -5907,9 +5912,11 @@ 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(); diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index 8ed06bdc448..570ef6bcbb7 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -453,6 +453,14 @@ nsXMLHttpRequest::~nsXMLHttpRequest() 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( + static_cast(this)), this); + } + nsLayoutStatics::Release(); } diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 2538b64b0a5..136ac5b4870 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -538,6 +538,13 @@ struct MainThreadChromeWorkerStructuredCloneCallbacks AssertIsOnMainThread(); JSObject* clone = + MainThreadWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData, + aClosure); + if (clone) { + return clone; + } + + clone = ChromeWorkerStructuredCloneCallbacks::Read(aCx, aReader, aTag, aData, aClosure); if (clone) { @@ -554,14 +561,15 @@ struct MainThreadChromeWorkerStructuredCloneCallbacks { 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 diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 902c91e5b8d..3e723c60154 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -42,6 +42,7 @@ #include "jscntxt.h" #include "jsfriendapi.h" +#include "Exceptions.h" #include "WorkerPrivate.h" #include "XMLHttpRequestPrivate.h" @@ -275,10 +276,12 @@ class XMLHttpRequest SLOT_status, SLOT_statusText, SLOT_readyState, + SLOT_response, SLOT_multipart, SLOT_mozBackgroundRequest, SLOT_withCredentials, SLOT_upload, + SLOT_responseType, SLOT_COUNT }; @@ -342,6 +345,7 @@ public: 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 @@ -395,6 +399,11 @@ private: return false; } + JSString* textStr = JS_NewStringCopyN(aCx, "text", 4); + if (!textStr) { + return false; + } + jsval emptyString = JS_GetEmptyStringValue(aCx); jsval zero = INT_TO_JSVAL(0); @@ -407,7 +416,9 @@ private: !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; } @@ -462,7 +473,7 @@ private: 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; } @@ -535,8 +546,13 @@ private: 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; \ } \ @@ -548,6 +564,7 @@ private: IMPL_SETTER(Multipart) IMPL_SETTER(MozBackgroundRequest) IMPL_SETTER(WithCredentials) + IMPL_SETTER(ResponseType) #undef IMPL_SETTER @@ -783,13 +800,17 @@ JSPropertySpec XMLHttpRequest::sProperties[] = { 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, diff --git a/dom/workers/XMLHttpRequest.h b/dom/workers/XMLHttpRequest.h index 9e2415857dd..601d1f79539 100644 --- a/dom/workers/XMLHttpRequest.h +++ b/dom/workers/XMLHttpRequest.h @@ -56,10 +56,12 @@ struct StateData jsval mStatus; jsval mStatusText; jsval mReadyState; + jsval mResponse; bool mResponseTextException; bool mStatusException; bool mStatusTextException; bool mReadyStateException; + bool mResponseException; }; bool diff --git a/dom/workers/XMLHttpRequestPrivate.cpp b/dom/workers/XMLHttpRequestPrivate.cpp index 06cf63f536d..19f37916288 100644 --- a/dom/workers/XMLHttpRequestPrivate.cpp +++ b/dom/workers/XMLHttpRequestPrivate.cpp @@ -41,7 +41,6 @@ #include "nsIDOMEvent.h" #include "nsIDOMEventListener.h" #include "nsIDOMProgressEvent.h" -#include "nsIJSContextStack.h" #include "nsIRunnable.h" #include "nsIXMLHttpRequest.h" #include "nsIXPConnect.h" @@ -88,7 +87,6 @@ public: bool mSeenUploadLoadStart; // Only touched on the main thread. - nsString mPreviousResponseText; nsCString mPreviousStatusText; PRUint32 mSyncQueueKey; PRUint32 mSyncEventResponseSyncQueueKey; @@ -158,7 +156,6 @@ public: { AssertIsOnMainThread(); - mPreviousResponseText.Truncate(); mPreviousStatusText.Truncate(); if (mUploadEventListenersAttached) { @@ -439,7 +436,10 @@ NS_IMPL_ISUPPORTS2(LoadStartDetectionRunnable, nsIRunnable, nsIDOMEventListener) class EventRunnable : public MainThreadProxyRunnable { nsString mType; - nsString mResponseText; + nsString mResponseType; + JSAutoStructuredCloneBuffer mResponseBuffer; + nsTArray > mClonedObjects; + jsval mResponse; nsCString mStatusText; PRUint64 mLoaded; PRUint64 mTotal; @@ -453,25 +453,28 @@ class EventRunnable : public MainThreadProxyRunnable 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 @@ -480,19 +483,41 @@ public: nsRefPtr& 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 > 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))) { @@ -566,21 +591,44 @@ public: xhr::StateData state; - state.mResponseTextException = mResponseTextException; - if (mResponseTextException || mResponseText.IsVoid()) { - state.mResponseText = JSVAL_VOID; + state.mResponseException = mResponseException; + if (!mResponseException) { + if (mResponseBuffer.data()) { + NS_ASSERTION(JSVAL_IS_VOID(mResponse), "Huh?!"); + + JSStructuredCloneCallbacks* callbacks = + aWorkerPrivate->IsChromeWorker() ? + ChromeWorkerStructuredCloneCallbacks(false) : + WorkerStructuredCloneCallbacks(false); + + nsTArray > clonedObjects; + clonedObjects.SwapElements(mClonedObjects); + + jsval response; + if (!mResponseBuffer.read(aCx, &response, callbacks, &clonedObjects)) { + return false; + } + + mResponseBuffer.clear(); + state.mResponse = response; + } + else { + state.mResponse = mResponse; + } } - else if (mResponseText.IsEmpty()) { - state.mResponseText = JS_GetEmptyStringValue(aCx); + + // 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) { + 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; @@ -631,6 +679,19 @@ public: 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; } }; @@ -783,6 +844,34 @@ public: } }; +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: @@ -911,10 +1000,12 @@ public: } 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); } }; @@ -1175,7 +1266,10 @@ Proxy::HandleEvent(nsIDOMEvent* aEvent) runnable = new EventRunnable(this, !!uploadTarget, type); } - runnable->Dispatch(nsnull); + { + RuntimeService::AutoSafeJSContext cx; + runnable->Dispatch(cx); + } if (!uploadTarget) { if (type.EqualsASCII(sEventStrings[STRING_loadstart])) { @@ -1276,7 +1370,7 @@ XMLHttpRequestPrivate::Notify(JSContext* aCx, Status aStatus) } bool -XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval *aVp) +XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval aOldVal, jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); @@ -1306,7 +1400,8 @@ XMLHttpRequestPrivate::SetMultipart(JSContext* aCx, jsval *aVp) } bool -XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval *aVp) +XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval aOldVal, + jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); @@ -1336,7 +1431,8 @@ XMLHttpRequestPrivate::SetMozBackgroundRequest(JSContext* aCx, jsval *aVp) } bool -XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval *aVp) +XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval aOldVal, + jsval *aVp) { mWorkerPrivate->AssertIsOnWorkerThread(); @@ -1365,6 +1461,68 @@ XMLHttpRequestPrivate::SetWithCredentials(JSContext* aCx, jsval *aVp) 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 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) { @@ -1680,8 +1838,8 @@ XMLHttpRequestPrivate::MaybeDispatchPrematureAbortEvents(JSContext* aCx, 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) { diff --git a/dom/workers/XMLHttpRequestPrivate.h b/dom/workers/XMLHttpRequestPrivate.h index fdce82552dd..aa921645993 100644 --- a/dom/workers/XMLHttpRequestPrivate.h +++ b/dom/workers/XMLHttpRequestPrivate.h @@ -111,13 +111,16 @@ public: 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); diff --git a/dom/workers/test/Makefile.in b/dom/workers/test/Makefile.in index 990d9431d46..448065ed666 100644 --- a/dom/workers/test/Makefile.in +++ b/dom/workers/test/Makefile.in @@ -104,6 +104,8 @@ _TEST_FILES = \ throwingOnerror_worker.js \ test_xhr.html \ xhr_worker.js \ + test_xhr2.html \ + xhr2_worker.js \ test_xhrAbort.html \ xhrAbort_worker.js \ testXHR.txt \ diff --git a/dom/workers/test/test_xhr2.html b/dom/workers/test/test_xhr2.html new file mode 100644 index 00000000000..5ee9159001e --- /dev/null +++ b/dom/workers/test/test_xhr2.html @@ -0,0 +1,38 @@ + + + + + + Test for DOM Worker Threads XHR (Bug 450452 ) + + + + +DOM Worker Threads XHR (Bug 450452) +

+ +
+
+
+ + + diff --git a/dom/workers/test/xhr2_worker.js b/dom/workers/test/xhr2_worker.js new file mode 100644 index 00000000000..75cda69689a --- /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()"); + } +} diff --git a/js/src/jsapi.h b/js/src/jsapi.h index be52d5fc586..fd03c0f683e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1647,6 +1647,16 @@ extern JS_PUBLIC_DATA(jsid) JSID_EMPTY; # 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. */