Bug 658683: Make xhr.response not create a new ArrayBuffer every time it is accessed. r=sicking
authorShawn Gong <sgong@gmail.com>
Mon, 23 May 2011 18:09:28 -0700
changeset 70163 f041ff7205636d5318d4c4975662277f72e20cba
parent 70162 80540db3a269f3e8eecdf7e6c57f728a5831d9f4
child 70164 e709174263f3aedae9ec081f0d1bf84d4e4399c3
push id99
push usereakhgari@mozilla.com
push dateTue, 24 May 2011 18:03:59 +0000
treeherdermozilla-aurora@26d6981b3d6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs658683
milestone6.0a1
Bug 658683: Make xhr.response not create a new ArrayBuffer every time it is accessed. r=sicking
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
content/base/test/test_XHR.html
xpcom/glue/nsCycleCollectionParticipant.h
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -421,40 +421,56 @@ NS_IMPL_RELEASE_INHERITED(nsXMLHttpReque
 nsXMLHttpRequest::nsXMLHttpRequest()
   : mResponseType(XML_HTTP_RESPONSE_TYPE_DEFAULT),
     mRequestObserver(nsnull), mState(XML_HTTP_REQUEST_UNSENT),
     mUploadTransferred(0), mUploadTotal(0), mUploadComplete(PR_TRUE),
     mUploadProgress(0), mUploadProgressMax(0),
     mErrorLoad(PR_FALSE), mTimerIsActive(PR_FALSE),
     mProgressEventWasDelayed(PR_FALSE),
     mLoadLengthComputable(PR_FALSE), mLoadTotal(0),
-    mFirstStartRequestSeen(PR_FALSE)
+    mFirstStartRequestSeen(PR_FALSE),
+    mResultArrayBuffer(nsnull) 
 {
   mResponseBodyUnicode.SetIsVoid(PR_TRUE);
   nsLayoutStatics::AddRef();
 }
 
 nsXMLHttpRequest::~nsXMLHttpRequest()
 {
+  UnrootResultArrayBuffer();
+
   if (mListenerManager) {
     mListenerManager->Disconnect();
   }
 
   if (mState & (XML_HTTP_REQUEST_STOPPED |
                 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;
 
   nsLayoutStatics::Release();
 }
 
+void 	
+nsXMLHttpRequest::RootResultArrayBuffer()
+{
+  NS_HOLD_JS_OBJECTS(this, nsXMLHttpRequest);
+}
+
+void
+nsXMLHttpRequest::UnrootResultArrayBuffer()
+{
+  NS_DROP_JS_OBJECTS(this, nsXMLHttpRequest);
+  mResultArrayBuffer = nsnull;
+}
+
 /**
  * This Init method is called from the factory constructor.
  */
 nsresult
 nsXMLHttpRequest::Init()
 {
   // Set the original mScriptContext and mPrincipal, if available.
   // Get JSContext from stack.
@@ -549,16 +565,17 @@ nsXMLHttpRequest::SetRequestObserver(nsI
 {
   mRequestObserver = aObserver;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXMLHttpRequest)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXMLHttpRequest,
                                                   nsXHREventTarget)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mReadRequest)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mResponseXML)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCORSPreflightChannel)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnUploadProgressListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnReadystatechangeListener)
@@ -567,19 +584,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannelEventSink)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mProgressEventSink)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mUpload,
                                                        nsIXMLHttpRequestUpload)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXMLHttpRequest,
                                                 nsXHREventTarget)
+  tmp->UnrootResultArrayBuffer();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mReadRequest)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mResponseXML)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCORSPreflightChannel)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnUploadProgressListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnReadystatechangeListener)
@@ -587,16 +604,24 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mXMLParserStreamListener)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannelEventSink)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mProgressEventSink)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mUpload)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsXMLHttpRequest,
+                                               nsXHREventTarget)
+  if(tmp->mResultArrayBuffer) {
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(tmp->mResultArrayBuffer,
+                                               "mResultArrayBuffer")
+  }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 DOMCI_DATA(XMLHttpRequest, nsXMLHttpRequest)
 
 // QueryInterface implementation for nsXMLHttpRequest
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsXMLHttpRequest)
   NS_INTERFACE_MAP_ENTRY(nsIXMLHttpRequest)
   NS_INTERFACE_MAP_ENTRY(nsIJSXMLHttpRequest)
   NS_INTERFACE_MAP_ENTRY(nsIDOMLoadListener)
   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
@@ -834,37 +859,31 @@ NS_IMETHODIMP nsXMLHttpRequest::GetRespo
   if (mState & (XML_HTTP_REQUEST_DONE |
                 XML_HTTP_REQUEST_LOADING)) {
     rv = ConvertBodyToText(aResponseText);
   }
 
   return rv;
 }
 
-nsresult nsXMLHttpRequest::GetResponseArrayBuffer(jsval *aResult)
+nsresult nsXMLHttpRequest::CreateResponseArrayBuffer(JSContext *aCx)
 {
-  JSContext *cx = nsContentUtils::GetCurrentJSContext();
-  if (!cx)
+  if (!aCx)
     return NS_ERROR_FAILURE;
 
-  if (!(mState & (XML_HTTP_REQUEST_DONE |
-                  XML_HTTP_REQUEST_LOADING))) {
-    *aResult = JSVAL_NULL;
-    return NS_OK;
+  mResultArrayBuffer = nsnull;
+  PRInt32 dataLen = mResponseBody.Length();
+  mResultArrayBuffer = js_CreateArrayBuffer(aCx, dataLen);
+  if (!mResultArrayBuffer) {
+    return NS_ERROR_FAILURE;
   }
-
-  PRInt32 dataLen = mResponseBody.Length();
-  JSObject *obj = js_CreateArrayBuffer(cx, dataLen);
-  if (!obj)
-    return NS_ERROR_FAILURE;
-
-  *aResult = OBJECT_TO_JSVAL(obj);
+  RootResultArrayBuffer();
 
   if (dataLen > 0) {
-    js::ArrayBuffer *abuf = js::ArrayBuffer::fromJSObject(obj);
+    js::ArrayBuffer *abuf = js::ArrayBuffer::fromJSObject(mResultArrayBuffer);
     NS_ASSERTION(abuf, "What happened?");
     memcpy(abuf->data, mResponseBody.BeginReading(), dataLen);
   }
 
   return NS_OK;
 }
 
 /* attribute AString responseType; */
@@ -949,17 +968,21 @@ NS_IMETHODIMP nsXMLHttpRequest::GetRespo
       if (buf) {
         str.ForgetSharedBuffer();
       }
     }
     break;
 
   case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER:
     if (mState & XML_HTTP_REQUEST_DONE) {
-      rv = GetResponseArrayBuffer(aResult);
+      if (!mResultArrayBuffer) {  
+         rv = CreateResponseArrayBuffer(aCx);
+         NS_ENSURE_SUCCESS(rv, rv);
+      }
+      *aResult = OBJECT_TO_JSVAL(mResultArrayBuffer);
     } else {
       *aResult = JSVAL_NULL;
     }
     break;
 
   case XML_HTTP_RESPONSE_TYPE_BLOB:
     if (mState & XML_HTTP_REQUEST_DONE && mResponseBlob) {
       JSObject* scope = JS_GetScopeChain(aCx);
@@ -1068,17 +1091,18 @@ nsXMLHttpRequest::Abort()
     mCORSPreflightChannel->Cancel(NS_BINDING_ABORTED);
   }
   mResponseXML = nsnull;
   PRUint32 responseLength = mResponseBody.Length();
   mResponseBody.Truncate();
   mResponseBodyUnicode.SetIsVoid(PR_TRUE);
   mResponseBlob = nsnull;
   mState |= XML_HTTP_REQUEST_ABORTED;
-
+  mResultArrayBuffer = nsnull;
+  
   if (!(mState & (XML_HTTP_REQUEST_UNSENT |
                   XML_HTTP_REQUEST_OPENED |
                   XML_HTTP_REQUEST_DONE))) {
     ChangeState(XML_HTTP_REQUEST_DONE, PR_TRUE);
   }
 
   if (!(mState & XML_HTTP_REQUEST_SYNCLOOPING)) {
     NS_NAMED_LITERAL_STRING(abortStr, ABORT_STR);
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -203,33 +203,34 @@ public:
                           aLoaded, aLengthComputable ? aTotal : LL_MAXUINT);
   }
 
   // This is called by the factory constructor.
   nsresult Init();
 
   void SetRequestObserver(nsIRequestObserver* aObserver);
 
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsXMLHttpRequest,
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsXMLHttpRequest,
                                            nsXHREventTarget)
-
   PRBool AllowUploadProgress();
-
+  void RootResultArrayBuffer();
+  void UnrootResultArrayBuffer();
+  
 protected:
   friend class nsMultipartProxyListener;
 
   nsresult DetectCharset(nsACString& aCharset);
   nsresult ConvertBodyToText(nsAString& aOutBuffer);
   static NS_METHOD StreamReaderFunc(nsIInputStream* in,
                 void* closure,
                 const char* fromRawSegment,
                 PRUint32 toOffset,
                 PRUint32 count,
                 PRUint32 *writeCount);
-  nsresult GetResponseArrayBuffer(jsval *aResult);
+  nsresult CreateResponseArrayBuffer(JSContext* aCx);
   void CreateResponseBlob(nsIRequest *request);
   // Change the state of the object with this. The broadcast argument
   // determines if the onreadystatechange listener should be called.
   nsresult ChangeState(PRUint32 aState, PRBool aBroadcast = PR_TRUE);
   nsresult RequestCompleted();
   nsresult GetLoadGroup(nsILoadGroup **aLoadGroup);
   nsIURI *GetBaseURI();
 
@@ -340,16 +341,18 @@ protected:
   PRPackedBool mLoadLengthComputable;
   PRUint64 mLoadTotal; // 0 if not known.
   nsCOMPtr<nsITimer> mProgressNotifier;
 
   PRPackedBool mFirstStartRequestSeen;
   
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIChannel> mNewRedirectChannel;
+  
+  JSObject* mResultArrayBuffer;
 };
 
 // helper class to expose a progress DOM Event
 
 class nsXMLHttpProgressEvent : public nsIDOMProgressEvent,
                                public nsIDOMLSProgressEvent,
                                public nsIDOMNSEvent,
                                public nsIPrivateDOMEvent
--- a/content/base/test/test_XHR.html
+++ b/content/base/test/test_XHR.html
@@ -105,16 +105,26 @@ is(xhr.response, "hello pass\n", "wrong 
 // test response (responseType='arraybuffer')
 function arraybuffer_equals_to(ab, s) {
   is(ab.byteLength, s.length, "wrong arraybuffer byteLength");
 
   u8v = new Uint8Array(ab);
   is(String.fromCharCode.apply(String, u8v), s, "wrong values");
 }
 
+// test if two arraybuffers are identical
+function is_identical_arraybuffer(ab1, ab2) {
+  is(ab1.byteLength, ab2.byteLength, "arraybuffer byteLengths not equal");
+
+  u8v1 = new Uint8Array(ab1);
+  u8v2 = new Uint8Array(ab2);
+  is(String.fromCharCode.apply(String, u8v1),
+     String.fromCharCode.apply(String, u8v2), "wrong values");
+}
+
 // with a simple text file
 xhr = new XMLHttpRequest();
 xhr.open("GET", 'file_XHR_pass2.txt', false); 
 xhr.responseType = 'arraybuffer';
 xhr.send(null);
 is(xhr.status, 200, "wrong status");
 checkResponseTextAccessThrows(xhr);
 checkResponseXMLAccessThrows(xhr);
@@ -129,16 +139,28 @@ xhr.responseType = 'arraybuffer';
 xhr.send(null)
 is(xhr.status, 200, "wrong status");
 checkResponseTextAccessThrows(xhr);
 checkResponseXMLAccessThrows(xhr);
 ab = xhr.response;
 ok(ab != null, "should have a non-null arraybuffer");
 arraybuffer_equals_to(ab, "\xaa\xee\0\x03\xff\xff\xff\xff\xbb\xbb\xbb\xbb");
 
+// test array buffer GetResult returns the same object
+xhr = new XMLHttpRequest();
+xhr.open("GET", 'file_XHR_binary1.bin', false); 
+xhr.responseType = 'arraybuffer';
+xhr.send(null)
+is(xhr.status, 200, "wrong status");
+checkResponseTextAccessThrows(xhr);
+checkResponseXMLAccessThrows(xhr);
+ab = xhr.response;
+ab2 = xhr.response;
+is_identical_arraybuffer(ab, ab2);
+
 // test response (responseType='blob')
 var onloadCount = 0;
 function checkOnloadCount() {
   if (++onloadCount >= 2) SimpleTest.finish();
 };
 
 // with a simple text file
 xhr = new XMLHttpRequest();
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -431,16 +431,30 @@ public:
                                                TraceCallback aCallback,        \
                                                void *aClosure)                 \
   {                                                                            \
     nsISupports *s = static_cast<nsISupports*>(p);                             \
     NS_ASSERTION(CheckForRightISupports(s),                                    \
                  "not the nsISupports pointer we expect");                     \
     _class *tmp = Downcast(s);
 
+#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(_class, _base_class)    \
+  void                                                                         \
+  NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace(void *p,                        \
+                                               TraceCallback aCallback,        \
+                                               void *aClosure)                 \
+  {                                                                            \
+    nsISupports *s = static_cast<nsISupports*>(p);                             \
+    NS_ASSERTION(CheckForRightISupports(s),                                    \
+                 "not the nsISupports pointer we expect");                     \
+    _class *tmp = static_cast<_class*>(Downcast(s));                           \
+    NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Trace(s,                       \
+                                                      aCallback,               \
+                                                      aClosure);
+
 #define NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(_class)                    \
   void                                                                         \
   NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace(void *p,                        \
                                                TraceCallback aCallback,        \
                                                void *aClosure)                 \
   {                                                                            \
     _class *tmp = static_cast<_class*>(p);