Fixed bug 632255: implemented FileReader.readAsArrayBuffer() API. r=sicking

This commit is contained in:
Shawn Gong 2011-06-30 17:50:44 -07:00
parent d0405fa2a0
commit 71e98c36a4
5 changed files with 175 additions and 13 deletions

View File

@ -41,9 +41,11 @@ interface nsIDOMEventListener;
interface nsIDOMBlob;
interface nsIDOMFileError;
[scriptable, uuid(f186170f-f07c-4f0b-9e3c-08f7dd496e74)]
[scriptable, uuid(3d77e784-1459-4206-b8a2-0855d826f569)]
interface nsIDOMFileReader : nsISupports
{
[implicit_jscontext]
void readAsArrayBuffer(in nsIDOMBlob filedata);
void readAsBinaryString(in nsIDOMBlob filedata);
void readAsText(in nsIDOMBlob filedata, [optional] in DOMString encoding);
void readAsDataURL(in nsIDOMBlob file);
@ -55,7 +57,8 @@ interface nsIDOMFileReader : nsISupports
const unsigned short DONE = 2;
readonly attribute unsigned short readyState;
readonly attribute DOMString result;
[implicit_jscontext]
readonly attribute jsval result;
readonly attribute nsIDOMFileError error;
};

View File

@ -78,6 +78,9 @@
#include "nsIScriptObjectPrincipal.h"
#include "nsFileDataProtocolHandler.h"
#include "mozilla/Preferences.h"
#include "xpcprivate.h"
#include "xpcquickstubs.h"
#include "jstypedarray.h"
using namespace mozilla;
@ -104,12 +107,22 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMFileReader,
nsXHREventTarget)
tmp->mResultArrayBuffer = nsnull;
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mFile)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mProgressNotifier)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsDOMFileReader,
nsXHREventTarget)
if(tmp->mResultArrayBuffer) {
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_CALLBACK(tmp->mResultArrayBuffer,
"mResultArrayBuffer")
}
NS_IMPL_CYCLE_COLLECTION_TRACE_END
DOMCI_DATA(FileReader, nsDOMFileReader)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMFileReader)
@ -126,6 +139,14 @@ NS_INTERFACE_MAP_END_INHERITING(nsXHREventTarget)
NS_IMPL_ADDREF_INHERITED(nsDOMFileReader, nsXHREventTarget)
NS_IMPL_RELEASE_INHERITED(nsDOMFileReader, nsXHREventTarget)
void
nsDOMFileReader::RootResultArrayBuffer()
{
nsContentUtils::PreserveWrapper(
static_cast<nsIDOMEventTarget*>(
static_cast<nsDOMEventTargetHelper*>(this)), this);
}
//nsICharsetDetectionObserver
NS_IMETHODIMP
@ -143,9 +164,11 @@ nsDOMFileReader::nsDOMFileReader()
mReadyState(nsIDOMFileReader::EMPTY),
mProgressEventWasDelayed(PR_FALSE),
mTimerIsActive(PR_FALSE),
mReadTotal(0), mReadTransferred(0)
mReadTotal(0), mReadTransferred(0),
mResultArrayBuffer(nsnull)
{
nsLayoutStatics::AddRef();
SetDOMStringToNull(mResult);
}
nsDOMFileReader::~nsDOMFileReader()
@ -239,9 +262,22 @@ nsDOMFileReader::GetReadyState(PRUint16 *aReadyState)
}
NS_IMETHODIMP
nsDOMFileReader::GetResult(nsAString& aResult)
nsDOMFileReader::GetResult(JSContext* aCx, jsval* aResult)
{
aResult = mResult;
if (mDataFormat == FILE_AS_ARRAYBUFFER) {
if (mReadyState == nsIDOMFileReader::DONE && mResultArrayBuffer) {
JSObject* tmp = mResultArrayBuffer;
*aResult = OBJECT_TO_JSVAL(tmp);
} else {
*aResult = JSVAL_NULL;
}
return NS_OK;
}
nsString tmpResult = mResult;
if (!xpc_qsStringToJsval(aCx, tmpResult, aResult)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
@ -252,23 +288,29 @@ nsDOMFileReader::GetError(nsIDOMFileError** aError)
return NS_OK;
}
NS_IMETHODIMP
nsDOMFileReader::ReadAsArrayBuffer(nsIDOMBlob* aFile, JSContext* aCx)
{
return ReadFileContent(aCx, aFile, EmptyString(), FILE_AS_ARRAYBUFFER);
}
NS_IMETHODIMP
nsDOMFileReader::ReadAsBinaryString(nsIDOMBlob* aFile)
{
return ReadFileContent(aFile, EmptyString(), FILE_AS_BINARY);
return ReadFileContent(nsnull, aFile, EmptyString(), FILE_AS_BINARY);
}
NS_IMETHODIMP
nsDOMFileReader::ReadAsText(nsIDOMBlob* aFile,
const nsAString &aCharset)
{
return ReadFileContent(aFile, aCharset, FILE_AS_TEXT);
return ReadFileContent(nsnull, aFile, aCharset, FILE_AS_TEXT);
}
NS_IMETHODIMP
nsDOMFileReader::ReadAsDataURL(nsIDOMBlob* aFile)
{
return ReadFileContent(aFile, EmptyString(), FILE_AS_DATAURL);
return ReadFileContent(nsnull, aFile, EmptyString(), FILE_AS_DATAURL);
}
NS_IMETHODIMP
@ -286,6 +328,7 @@ nsDOMFileReader::Abort()
//Revert status, result and readystate attributes
SetDOMStringToNull(mResult);
mResultArrayBuffer = nsnull;
mReadyState = nsIDOMFileReader::DONE;
mError = new nsDOMFileError(nsIDOMFileError::ABORT_ERR);
@ -388,6 +431,14 @@ nsDOMFileReader::OnDataAvailable(nsIRequest *aRequest,
&bytesRead);
NS_ASSERTION(bytesRead == aCount, "failed to read data");
}
else if (mDataFormat == FILE_AS_ARRAYBUFFER) {
JSObject* abuf = js::ArrayBuffer::getArrayBuffer(mResultArrayBuffer);
NS_ASSERTION(abuf, "What happened?");
PRUint32 bytesRead = 0;
aInputStream->Read((char*)JS_GetArrayBufferData(abuf) + aOffset, aCount, &bytesRead);
NS_ASSERTION(bytesRead == aCount, "failed to read data");
}
else {
//Update memory buffer to reflect the contents of the file
mFileData = (char *)PR_Realloc(mFileData, aOffset + aCount);
@ -443,6 +494,8 @@ nsDOMFileReader::OnStopRequest(nsIRequest *aRequest,
nsresult rv = NS_OK;
switch (mDataFormat) {
case FILE_AS_ARRAYBUFFER:
break; //Already accumulated mResultArrayBuffer
case FILE_AS_BINARY:
break; //Already accumulated mResult
case FILE_AS_TEXT:
@ -472,7 +525,8 @@ nsDOMFileReader::OnStopRequest(nsIRequest *aRequest,
// Helper methods
nsresult
nsDOMFileReader::ReadFileContent(nsIDOMBlob* aFile,
nsDOMFileReader::ReadFileContent(JSContext* aCx,
nsIDOMBlob* aFile,
const nsAString &aCharset,
eDataFormat aDataFormat)
{
@ -517,6 +571,15 @@ nsDOMFileReader::ReadFileContent(nsIDOMBlob* aFile,
//FileReader should be in loading state here
mReadyState = nsIDOMFileReader::LOADING;
DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
if (mDataFormat == FILE_AS_ARRAYBUFFER) {
RootResultArrayBuffer();
mResultArrayBuffer = js_CreateArrayBuffer(aCx, mReadTotal);
if (!mResultArrayBuffer) {
NS_WARNING("Failed to create JS array buffer");
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}

View File

@ -80,7 +80,6 @@ public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMFILEREADER
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMFileReader, nsXHREventTarget)
NS_FORWARD_NSIXMLHTTPREQUESTEVENTTARGET(nsXHREventTarget::);
@ -109,14 +108,19 @@ public:
nsresult Init();
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMFileReader,
nsXHREventTarget)
void RootResultArrayBuffer();
protected:
enum eDataFormat {
FILE_AS_ARRAYBUFFER,
FILE_AS_BINARY,
FILE_AS_TEXT,
FILE_AS_DATAURL
};
nsresult ReadFileContent(nsIDOMBlob *aFile, const nsAString &aCharset, eDataFormat aDataFormat);
nsresult ReadFileContent(JSContext* aCx, nsIDOMBlob *aFile, const nsAString &aCharset, eDataFormat aDataFormat);
nsresult GetAsText(const nsACString &aCharset,
const char *aFileData, PRUint32 aDataLen, nsAString &aResult);
nsresult GetAsDataURL(nsIDOMBlob *aFile, const char *aFileData, PRUint32 aDataLen, nsAString &aResult);
@ -151,6 +155,8 @@ protected:
PRUint64 mReadTotal;
PRUint64 mReadTransferred;
JSObject* mResultArrayBuffer;
};
#endif

View File

@ -98,6 +98,18 @@ is(onloadHasRunBinary, false, "binary loading must be async");
is(onloadStartHasRunBinary, true, "binary loadstart should fire sync");
expectedTestCount++;
var onloadHasRunArrayBuffer = false;
var onloadStartHasRunArrayBuffer = false;
r = new FileReader();
is(r.readyState, FileReader.EMPTY, "correct initial arrayBuffer readyState");
r.addEventListener("load", function() { onloadHasRunArrayBuffer = true }, false);
r.addEventListener("loadstart", function() { onloadStartHasRunArrayBuffer = true }, false);
r.readAsArrayBuffer(binaryFile);
r.onload = getLoadHandlerForArrayBuffer(testBinaryData, testBinaryData.length, "array buffer reading");
is(r.readyState, FileReader.LOADING, "correct loading arrayBuffer readyState");
is(onloadHasRunArrayBuffer, false, "arrayBuffer loading must be async");
is(onloadStartHasRunArrayBuffer, true, "arrayBuffer loadstart should fire sync");
expectedTestCount++;
// Test a variety of encodings, and make sure they work properly
r = new FileReader();
@ -124,6 +136,14 @@ r.onload = getLoadHandler(testTextData,
"utf16 reading");
expectedTestCount++;
// Test get result without reading
r = new FileReader();
is(r.readyState, FileReader.EMPTY,
"readyState in test reader get result without reading");
is(r.error, null,
"no error in test reader get result without reading");
is(r.result, null,
"result in test reader get result without reading");
// Test loading an empty file works (and doesn't crash!)
var emptyFile = createFileWithData("");
@ -148,12 +168,16 @@ r.onload = getLoadHandler("", 0, "empty binary string reading");
r.readAsBinaryString(emptyFile);
expectedTestCount++;
r = new FileReader();
r.onload = getLoadHandlerForArrayBuffer("", 0, "empty array buffer reading");
r.readAsArrayBuffer(emptyFile);
expectedTestCount++;
r = new FileReader();
r.onload = getLoadHandler(convertToDataURL(""), 0, "empt binary string reading");
r.readAsDataURL(emptyFile);
expectedTestCount++;
// Test reusing a FileReader to read multiple times
r = new FileReader();
r.onload = getLoadHandler(testASCIIData,
@ -203,6 +227,39 @@ r.addEventListener("load", makeAnotherReadListener3, false);
r.readAsDataURL(binaryFile);
expectedTestCount += 2;
r = new FileReader();
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
testBinaryData.length,
"to-be-reused reading arrayBuffer")
var makeAnotherReadListener4 = function(event) {
r = event.target;
r.removeEventListener("load", makeAnotherReadListener4, false);
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
testBinaryData.length,
"reused reading arrayBuffer");
r.readAsArrayBuffer(binaryFile);
};
r.addEventListener("load", makeAnotherReadListener4, false);
r.readAsArrayBuffer(binaryFile);
expectedTestCount += 2;
// Test first reading as ArrayBuffer then read as something else
// (BinaryString) and doesn't crash
r = new FileReader();
r.onload = getLoadHandlerForArrayBuffer(testBinaryData,
testBinaryData.length,
"to-be-reused reading arrayBuffer")
var makeAnotherReadListener5 = function(event) {
r = event.target;
r.removeEventListener("load", makeAnotherReadListener5, false);
r.onload = getLoadHandler(testBinaryData,
testBinaryData.length,
"reused reading binary string");
r.readAsBinaryString(binaryFile);
};
r.addEventListener("load", makeAnotherReadListener5, false);
r.readAsArrayBuffer(binaryFile);
expectedTestCount += 2;
//Test data-URI encoding on differing file sizes
dataurldata = testBinaryData.substr(0, testBinaryData.length -
@ -322,9 +379,37 @@ function getLoadHandler(expectedResult, expectedLength, testName) {
is(event.lengthComputable, true,
"lengthComputable in test " + testName);
is(event.loaded, expectedLength,
"lengthComputable in test " + testName);
"loaded in test " + testName);
is(event.total, expectedLength,
"total in test " + testName);
testHasRun();
}
}
function getLoadHandlerForArrayBuffer(expectedResult, expectedLength, testName) {
return function (event) {
is(event.target.readyState, FileReader.DONE,
"readyState in test " + testName);
is(event.target.error, null,
"no error in test " + testName);
is(event.lengthComputable, true,
"lengthComputable in test " + testName);
is(event.loaded, expectedLength,
"loaded in test " + testName);
is(event.total, expectedLength,
"total in test " + testName);
is(event.target.result.byteLength, expectedLength,
"array buffer size in test " + testName);
var u8v = new Uint8Array(event.target.result);
is(String.fromCharCode.apply(String, u8v), expectedResult,
"array buffer contents in test " + testName);
u8v = null;
SpecialPowers.gc();
is(event.target.result.byteLength, expectedLength,
"array buffer size after gc in test " + testName);
u8v = new Uint8Array(event.target.result);
is(String.fromCharCode.apply(String, u8v), expectedResult,
"array buffer contents after gc in test " + testName);
testHasRun();
}
}

View File

@ -398,6 +398,9 @@ members = [
'nsIBoxObject.width',
'nsIBoxObject.height',
# FileReader
'nsIDOMFileReader.*',
# XHR
'nsIXMLHttpRequest.*',
# nsIXMLHttpRequest.channel is not used on the web, and more
@ -472,6 +475,8 @@ irregularFilenames = {
'nsIDOMCanvasGradient': 'nsIDOMCanvasRenderingContext2D',
'nsIDOMCanvasPattern': 'nsIDOMCanvasRenderingContext2D',
'nsIDOMBlob': 'nsIDOMFile',
'nsIWebGLTexture': 'nsIDOMWebGLRenderingContext',
'nsIWebGLBuffer': 'nsIDOMWebGLRenderingContext',
'nsIWebGLProgram': 'nsIDOMWebGLRenderingContext',