From c8f3d0d0688bd87bf4c92e16e94e4f75a9b6f7bd Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 20 May 2011 10:18:45 -0700 Subject: [PATCH] Bug 648997: Implement BlobBuilder spec as mozBlobBuilder. r=sicking. Note that there is one key difference between this implementation and the spec. In this patch mozBlobBuilder.getBlob("content/type"); returns a Blob and clears the mozBlobBuilder. In the spec the BlobBuilder is not cleared. Thus, let bb = new mozBlobBuilder(); mozBlobBuilder.append("foo"); let blob1 = mozBlobBuilder.getBlob("content/type"); // blob1 contains "foo" mozBlobBuilder.append("bar"); let blob2 = mozBlobBuilder.getBlob("content/type"); // blob2 contains "bar", the spec says it should contain "foobar". IMO, the spec behavior optimizes for the wrong case. BlobBuilder will probably be used mostly as a one-shot API. Additionally, the spec requires the BlobBuilder to hang on to potentially large amounts of memory between the getBlob() call and when the BlobBuilder is GCd. These issues have been raised on the listserv. --- content/base/public/nsDOMFile.h | 7 +- content/base/public/nsIDOMFile.idl | 23 +- content/base/src/Makefile.in | 1 + content/base/src/nsDOMBlobBuilder.cpp | 399 ++++++++++++++++++++++ content/base/src/nsDOMFile.cpp | 11 +- content/base/test/Makefile.in | 2 + content/base/test/fileutils.js | 272 +++++++++++++++ content/base/test/test_blobbuilder.html | 135 ++++++++ content/base/test/test_fileapi_slice.html | 268 +-------------- dom/base/nsDOMClassInfo.cpp | 9 +- dom/base/nsDOMClassInfoClasses.h | 1 + 11 files changed, 839 insertions(+), 289 deletions(-) create mode 100644 content/base/src/nsDOMBlobBuilder.cpp create mode 100644 content/base/test/fileutils.js create mode 100644 content/base/test/test_blobbuilder.html diff --git a/content/base/public/nsDOMFile.h b/content/base/public/nsDOMFile.h index f0fd79c7cad8..2f5a20020f46 100644 --- a/content/base/public/nsDOMFile.h +++ b/content/base/public/nsDOMFile.h @@ -40,6 +40,7 @@ #define nsDOMFile_h__ #include "nsICharsetDetectionObserver.h" +#include "nsIFile.h" #include "nsIDOMFile.h" #include "nsIDOMFileList.h" #include "nsIDOMFileError.h" @@ -56,9 +57,12 @@ class nsIFile; class nsIInputStream; class nsIClassInfo; +class nsIBlobBuilder; + +nsresult NS_NewBlobBuilder(nsISupports* *aSupports); +void ParseSize(PRInt64 aSize, PRInt64& aStart, PRInt64& aEnd); class nsDOMFile : public nsIDOMFile, - public nsIDOMBlob_MOZILLA_2_0_BRANCH, public nsIXHRSendable, public nsICharsetDetectionObserver, public nsIJSNativeInitializer @@ -67,7 +71,6 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMBLOB NS_DECL_NSIDOMFILE - NS_DECL_NSIDOMBLOB_MOZILLA_2_0_BRANCH NS_DECL_NSIXHRSENDABLE nsDOMFile(nsIFile *aFile, const nsAString& aContentType) diff --git a/content/base/public/nsIDOMFile.idl b/content/base/public/nsIDOMFile.idl index 57506b6acf5a..f317562ae759 100644 --- a/content/base/public/nsIDOMFile.idl +++ b/content/base/public/nsIDOMFile.idl @@ -37,37 +37,33 @@ #include "domstubs.idl" +%{C++ +#include "jsapi.h" +%} + interface nsIDOMFileError; interface nsIInputStream; interface nsIURI; interface nsIPrincipal; interface nsIDOMBlob; -[scriptable, uuid(5822776a-049c-4de7-adb6-dd9efc39d082)] +[scriptable, uuid(d5237f31-443a-460b-9e42-449a135346f0)] interface nsIDOMBlob : nsISupports { readonly attribute unsigned long long size; readonly attribute DOMString type; - [noscript] nsIDOMBlob slice(in unsigned long long start, - in unsigned long long length, - [optional] in DOMString contentType); - [noscript] readonly attribute nsIInputStream internalStream; // The caller is responsible for releasing the internalUrl from the // moz-filedata: protocol handler [noscript] DOMString getInternalUrl(in nsIPrincipal principal); -}; -[scriptable, uuid(cb5b4191-a555-4e57-b8d2-88091184b59f)] -interface nsIDOMBlob_MOZILLA_2_0_BRANCH : nsISupports -{ [optional_argc] nsIDOMBlob mozSlice(in long long start, [optional] in long long end, [optional] in DOMString contentType); }; -[scriptable, uuid(ae1405b0-e411-481e-9606-b29ec7982687)] +[scriptable, uuid(91c9ebd9-2a4a-4a38-9412-ef492a2799be)] interface nsIDOMFile : nsIDOMBlob { readonly attribute DOMString name; @@ -84,3 +80,10 @@ interface nsIDOMFile : nsIDOMBlob DOMString getAsDataURL(); // raises(FileException) on retrieval DOMString getAsBinary(); // raises(FileException) on retrieval }; + +[scriptable, uuid(c4a77171-039b-4f84-97f9-820fb51626af)] +interface nsIDOMBlobBuilder : nsISupports +{ + nsIDOMBlob getBlob([optional] in DOMString contentType); + [implicit_jscontext] void append(in jsval data); +}; diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index d469d120d7f1..5f10bacb9703 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -92,6 +92,7 @@ CPPSRCS = \ nsDataDocumentContentPolicy.cpp \ nsDOMAttribute.cpp \ nsDOMAttributeMap.cpp \ + nsDOMBlobBuilder.cpp \ nsDOMDocumentType.cpp \ nsDOMEventTargetWrapperCache.cpp \ nsDOMFile.cpp \ diff --git a/content/base/src/nsDOMBlobBuilder.cpp b/content/base/src/nsDOMBlobBuilder.cpp new file mode 100644 index 000000000000..3aad463f982b --- /dev/null +++ b/content/base/src/nsDOMBlobBuilder.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla File API. + * + * The Initial Developer of the Original Code is + * Kyle Huey + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "jstypedarray.h" +#include "nsAutoPtr.h" +#include "nsDOMClassInfo.h" +#include "nsDOMFile.h" +#include "nsIMultiplexInputStream.h" +#include "nsStringStream.h" +#include "nsTArray.h" +#include "nsJSUtils.h" +#include "nsContentUtils.h" +#include "CheckedInt.h" + +// XXXkhuey shamelessly stolen from VideoUtils.h. We should patch NSPR. +#define PR_INT64_MAX (~((PRInt64)(1) << 63)) +#define PR_INT64_MIN (-PR_INT64_MAX - 1) + +using namespace mozilla; + +class nsDOMMultipartBlob : public nsDOMFile +{ +public: + nsDOMMultipartBlob(nsTArray > aBlobs, + const nsAString& aContentType) + : nsDOMFile(nsnull, aContentType), + mBlobs(aBlobs) + { + mIsFullFile = false; + mStart = 0; + mLength = 0; + } + + NS_IMETHOD GetSize(PRUint64*); + NS_IMETHOD GetInternalStream(nsIInputStream**); + NS_IMETHOD MozSlice(PRInt64 aStart, PRInt64 aEnd, + const nsAString& aContentType, PRUint8 optional_argc, + nsIDOMBlob **aBlob); + +protected: + nsTArray > mBlobs; +}; + +NS_IMETHODIMP +nsDOMMultipartBlob::GetSize(PRUint64* aLength) +{ + nsresult rv; + *aLength = 0; + + if (mLength) { + *aLength = mLength; + return NS_OK; + } + + CheckedUint64 length = 0; + + PRUint32 i; + PRUint32 len = mBlobs.Length(); + for (i = 0; i < len; i++) { + nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); + PRUint64 l = 0; + + rv = blob->GetSize(&l); + NS_ENSURE_SUCCESS(rv, rv); + + length += l; + } + + if (!length.valid()) + return NS_ERROR_FAILURE; + + mLength = length.value(); + *aLength = mLength; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMMultipartBlob::GetInternalStream(nsIInputStream** aStream) +{ + nsresult rv; + *aStream = nsnull; + + nsCOMPtr stream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + NS_ENSURE_TRUE(stream, NS_ERROR_FAILURE); + + PRUint32 i; + for (i = 0; i < mBlobs.Length(); i++) { + nsCOMPtr scratchStream; + nsIDOMBlob* blob = mBlobs.ElementAt(i).get(); + + rv = blob->GetInternalStream(getter_AddRefs(scratchStream)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stream->AppendStream(scratchStream); + NS_ENSURE_SUCCESS(rv, rv); + } + + return CallQueryInterface(stream, aStream); +} + +NS_IMETHODIMP +nsDOMMultipartBlob::MozSlice(PRInt64 aStart, PRInt64 aEnd, + const nsAString& aContentType, + PRUint8 optional_argc, + nsIDOMBlob **aBlob) +{ + nsresult rv; + *aBlob = nsnull; + + // Truncate aStart and aEnd so that we stay within this file. + PRUint64 thisLength; + rv = GetSize(&thisLength); + NS_ENSURE_SUCCESS(rv, rv); + + if (!optional_argc) { + aEnd = (PRInt64)thisLength; + } + + ParseSize((PRInt64)thisLength, aStart, aEnd); + + // If we clamped to nothing we create an empty blob + nsTArray > blobs; + + PRInt64 length = aEnd - aStart; + PRUint64 finalLength = length; + PRUint64 skipStart = aStart; + + NS_ABORT_IF_FALSE(aStart + length <= thisLength, "Er, what?"); + + // Prune the list of blobs if we can + PRUint32 i; + for (i = 0; length && skipStart && i < mBlobs.Length(); i++) { + nsIDOMBlob* blob = mBlobs[i].get(); + + PRUint64 l; + rv = blob->GetSize(&l); + NS_ENSURE_SUCCESS(rv, rv); + + if (skipStart < l) { + PRInt64 upperBound = NS_MIN(l - skipStart, length); + + nsCOMPtr firstBlob; + rv = mBlobs.ElementAt(i)->MozSlice(skipStart, skipStart + upperBound, + aContentType, 2, + getter_AddRefs(firstBlob)); + NS_ENSURE_SUCCESS(rv, rv); + + // Avoid wrapping a single blob inside an nsDOMMultipartBlob + if (length == upperBound) { + firstBlob.forget(aBlob); + return NS_OK; + } + + blobs.AppendElement(firstBlob); + length -= upperBound; + i++; + break; + } + skipStart -= l; + } + + // Now append enough blobs until we're done + for (; length && i < mBlobs.Length(); i++) { + nsIDOMBlob* blob = mBlobs[i].get(); + + PRUint64 l; + rv = blob->GetSize(&l); + NS_ENSURE_SUCCESS(rv, rv); + + if (length < l) { + nsCOMPtr lastBlob; + rv = mBlobs.ElementAt(i)->MozSlice(0, length, aContentType, 2, + getter_AddRefs(lastBlob)); + NS_ENSURE_SUCCESS(rv, rv); + + blobs.AppendElement(lastBlob); + } else { + blobs.AppendElement(blob); + } + length -= NS_MIN(l, length); + } + + // we can create our blob now + nsCOMPtr blob = new nsDOMMultipartBlob(blobs, aContentType); + blob.forget(aBlob); + return NS_OK; +} + +class nsDOMBlobBuilder : public nsIDOMBlobBuilder +{ +public: + nsDOMBlobBuilder() + : mData(nsnull), mDataLen(0), mDataBufferLen(0) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMBLOBBUILDER +protected: + nsresult AppendVoidPtr(void* aData, PRUint32 aLength); + nsresult AppendString(JSString* aString, JSContext* aCx); + nsresult AppendBlob(nsIDOMBlob* aBlob); + nsresult AppendArrayBuffer(js::ArrayBuffer* aBuffer); + + bool ExpandBufferSize(PRUint64 aSize) + { + if (mDataBufferLen >= mDataLen + aSize) { + mDataLen += aSize; + return true; + } + + // Start at 1 or we'll loop forever. + CheckedUint32 bufferLen = NS_MAX(mDataBufferLen, 1); + while (bufferLen.valid() && bufferLen.value() < mDataLen + aSize) + bufferLen *= 2; + + if (!bufferLen.valid()) + return false; + + // PR_ memory functions are still fallible + void* data = PR_Realloc(mData, bufferLen.value()); + if (!data) + return false; + + mData = data; + mDataBufferLen = bufferLen.value(); + mDataLen += aSize; + return true; + } + + void Flush() { + if (mData) { + // If we have some data, create a blob for it + // and put it on the stack + + nsCOMPtr blob = + new nsDOMMemoryFile(mData, mDataLen, EmptyString(), EmptyString()); + mBlobs.AppendElement(blob); + mData = nsnull; // The nsDOMMemoryFile takes ownership of the buffer + mDataLen = 0; + mDataBufferLen = 0; + } + } + + nsTArray > mBlobs; + void* mData; + PRUint64 mDataLen; + PRUint64 mDataBufferLen; +}; + +DOMCI_DATA(MozBlobBuilder, nsDOMBlobBuilder) + +NS_IMPL_ADDREF(nsDOMBlobBuilder) +NS_IMPL_RELEASE(nsDOMBlobBuilder) +NS_INTERFACE_MAP_BEGIN(nsDOMBlobBuilder) + NS_INTERFACE_MAP_ENTRY(nsIDOMBlobBuilder) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozBlobBuilder) +NS_INTERFACE_MAP_END + +nsresult +nsDOMBlobBuilder::AppendVoidPtr(void* aData, PRUint32 aLength) +{ + NS_ENSURE_ARG_POINTER(aData); + + PRUint64 offset = mDataLen; + + if (!ExpandBufferSize(aLength)) + return NS_ERROR_OUT_OF_MEMORY; + + memcpy((char*)mData + offset, aData, aLength); + return NS_OK; +} + +nsresult +nsDOMBlobBuilder::AppendString(JSString* aString, JSContext* aCx) +{ + nsDependentJSString xpcomStr; + if (!xpcomStr.init(aCx, aString)) { + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + NS_ConvertUTF16toUTF8 utf8Str(xpcomStr); + + return AppendVoidPtr((void*)utf8Str.Data(), + utf8Str.Length()); +} + +nsresult +nsDOMBlobBuilder::AppendBlob(nsIDOMBlob* aBlob) +{ + NS_ENSURE_ARG_POINTER(aBlob); + + Flush(); + mBlobs.AppendElement(aBlob); + + return NS_OK; +} + +nsresult +nsDOMBlobBuilder::AppendArrayBuffer(js::ArrayBuffer* aBuffer) +{ + return AppendVoidPtr(aBuffer->data, aBuffer->byteLength); +} + +/* nsIDOMBlob getBlob ([optional] in DOMString contentType); */ +NS_IMETHODIMP +nsDOMBlobBuilder::GetBlob(const nsAString& aContentType, + nsIDOMBlob** aBlob) +{ + NS_ENSURE_ARG(aBlob); + + Flush(); + + nsCOMPtr blob = new nsDOMMultipartBlob(mBlobs, + aContentType); + blob.forget(aBlob); + + // NB: This is a willful violation of the spec. The spec says that + // the existing contents of the BlobBuilder should be included + // in the next blob produced. This seems silly and has been raised + // on the WHATWG listserv. + mBlobs.Clear(); + + return NS_OK; +} + +/* [implicit_jscontext] void append (in jsval data); */ +NS_IMETHODIMP +nsDOMBlobBuilder::Append(const jsval& aData, JSContext* aCx) +{ + // We need to figure out what our jsval is + + // Is it an object? + if (JSVAL_IS_OBJECT(aData)) { + JSObject* obj = JSVAL_TO_OBJECT(aData); + NS_ASSERTION(obj, "Er, what?"); + + // Is it a Blob? + nsCOMPtr blob = do_QueryInterface( + nsContentUtils::XPConnect()-> + GetNativeOfWrapper(aCx, obj)); + if (blob) + return AppendBlob(blob); + + // Is it an array buffer? + if (js_IsArrayBuffer(obj)) { + js::ArrayBuffer* buffer = js::ArrayBuffer::fromJSObject(obj); + if (buffer) + return AppendArrayBuffer(buffer); + } + } + + // If it's not a Blob or an ArrayBuffer, coerce it to a string + JSString* str = JS_ValueToString(aCx, aData); + NS_ENSURE_TRUE(str, NS_ERROR_FAILURE); + + return AppendString(str, aCx); +} + +nsresult NS_NewBlobBuilder(nsISupports* *aSupports) +{ + nsDOMBlobBuilder* builder = new nsDOMBlobBuilder(); + return CallQueryInterface(builder, aSupports); +} diff --git a/content/base/src/nsDOMFile.cpp b/content/base/src/nsDOMFile.cpp index abeac86768bd..24b306f0be7a 100644 --- a/content/base/src/nsDOMFile.cpp +++ b/content/base/src/nsDOMFile.cpp @@ -49,7 +49,6 @@ #include "nsIConverterInputStream.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" -#include "nsIFile.h" #include "nsIFileStreams.h" #include "nsIInputStream.h" #include "nsIIPCSerializable.h" @@ -140,7 +139,6 @@ DOMCI_DATA(Blob, nsDOMFile) NS_INTERFACE_MAP_BEGIN(nsDOMFile) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMFile) NS_INTERFACE_MAP_ENTRY(nsIDOMBlob) - NS_INTERFACE_MAP_ENTRY(nsIDOMBlob_MOZILLA_2_0_BRANCH) NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMFile, mIsFullFile) NS_INTERFACE_MAP_ENTRY(nsIXHRSendable) NS_INTERFACE_MAP_ENTRY(nsICharsetDetectionObserver) @@ -293,13 +291,6 @@ ParseSize(PRInt64 aSize, PRInt64& aStart, PRInt64& aEnd) } } -NS_IMETHODIMP -nsDOMFile::Slice(PRUint64 aStart, PRUint64 aLength, - const nsAString& aContentType, nsIDOMBlob **aBlob) -{ - return MozSlice(aStart, aStart + aLength, aContentType, 2, aBlob); -} - NS_IMETHODIMP nsDOMFile::MozSlice(PRInt64 aStart, PRInt64 aEnd, const nsAString& aContentType, PRUint8 optional_argc, @@ -307,7 +298,7 @@ nsDOMFile::MozSlice(PRInt64 aStart, PRInt64 aEnd, { *aBlob = nsnull; - // Truncate aLength and aStart so that we stay within this file. + // Truncate aStart and aEnd so that we stay within this file. PRUint64 thisLength; nsresult rv = GetSize(&thisLength); NS_ENSURE_SUCCESS(rv, rv); diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in index 708be50a9b93..c74b1f568bf0 100644 --- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -481,6 +481,8 @@ _TEST_FILES2 = \ bug638112-response.txt \ bug638112.sjs \ test_bug656283.html \ + test_blobbuilder.html \ + fileutils.js \ $(NULL) # This test fails on the Mac for some reason diff --git a/content/base/test/fileutils.js b/content/base/test/fileutils.js new file mode 100644 index 000000000000..b32dbe3f654a --- /dev/null +++ b/content/base/test/fileutils.js @@ -0,0 +1,272 @@ +// Utility functions +var testRanCounter = 0; +var expectedTestCount = 0; + +function testHasRun() { + //alert(testRanCounter); + ++testRanCounter; + if (testRanCounter == expectedTestCount) { + SimpleTest.finish(); + } +} + + +function testFile(file, contents, test) { + SimpleTest.requestLongerTimeout(2); + + // Load file using FileReader + var r = new FileReader(); + r.onload = getFileReaderLoadHandler(contents, contents.length, "FileReader.readAsBinaryString of " + test); + r.readAsBinaryString(file); + expectedTestCount++; + + // Load file using URL.createObjectURL and XMLHttpRequest + var xhr = new XMLHttpRequest; + xhr.open("GET", URL.createObjectURL(file)); + xhr.onload = getXHRLoadHandler(contents, contents.length, false, + "XMLHttpRequest load of " + test); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + xhr.send(); + expectedTestCount++; + + // Send file to server using FormData and XMLHttpRequest + xhr = new XMLHttpRequest(); + xhr.onload = function(event) { + checkMPSubmission(JSON.parse(event.target.responseText), + [{ name: "hello", value: "world"}, + { name: "myfile", + value: contents, + fileName: file.name || "", + contentType: file.type || "application/octet-stream" }]); + testHasRun(); + } + xhr.open("POST", "../../html/content/test/form_submit_server.sjs"); + var fd = new FormData; + fd.append("hello", "world"); + fd.append("myfile", file); + xhr.send(fd); + expectedTestCount++; + + // Send file to server using plain XMLHttpRequest + var xhr = new XMLHttpRequest; + xhr.open("POST", "file_XHRSendData.sjs"); + xhr.onload = function (event) { + is(event.target.getResponseHeader("Result-Content-Type"), + file.type ? file.type : null, + "request content-type in XMLHttpRequest send of " + test); + is(event.target.getResponseHeader("Result-Content-Length"), + file.size, + "request content-length in XMLHttpRequest send of " + test); + }; + xhr.addEventListener("load", + getXHRLoadHandler(contents, contents.length, true, + "XMLHttpRequest send of " + test), + false); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + xhr.send(file); + expectedTestCount++; +} + +function getFileReaderLoadHandler(expectedResult, expectedLength, testName) { + return function (event) { + is(event.target.readyState, FileReader.DONE, + "[FileReader] readyState in test " + testName); + is(event.target.error, null, + "[FileReader] no error in test " + testName); + // Do not use |is(event.target.result, expectedResult, "...");| that may output raw binary data. + is(event.target.result.length, expectedResult.length, + "[FileReader] Length of result in test " + testName); + ok(event.target.result == expectedResult, + "[FileReader] Content of result in test " + testName); + is(event.lengthComputable, true, + "[FileReader] lengthComputable in test " + testName); + is(event.loaded, expectedLength, + "[FileReader] Loaded length in test " + testName); + is(event.total, expectedLength, + "[FileReader] Total length in test " + testName); + testHasRun(); + } +} + +function getXHRLoadHandler(expectedResult, expectedLength, statusWorking, testName) { + return function (event) { + is(event.target.readyState, 4, + "[XHR] readyState in test " + testName); + if (statusWorking) { + is(event.target.status, 200, + "[XHR] no error in test " + testName); + } + else { + todo(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, + "[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(); + } +} + +function convertXHRBinary(s) { + var res = ""; + for (var i = 0; i < s.length; ++i) { + res += String.fromCharCode(s.charCodeAt(i) & 255); + } + return res; +} + +function testHasRun() { + //alert(testRanCounter); + ++testRanCounter; + if (testRanCounter == expectedTestCount) { + SimpleTest.finish(); + } +} + +function createFileWithData(fileData) { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + var dirSvc = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties); + var testFile = dirSvc.get("ProfD", Components.interfaces.nsIFile); + testFile.append("fileAPItestfile2-" + fileNum); + fileNum++; + var outStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + var fileList = document.getElementById('fileList'); + fileList.value = testFile.path; + + return fileList.files[0]; +} + +function gc() { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .garbageCollect(); +} + +function checkMPSubmission(sub, expected) { + function getPropCount(o) { + var x, l = 0; + for (x in o) ++l; + return l; + } + + is(sub.length, expected.length, + "Correct number of items"); + var i; + for (i = 0; i < expected.length; ++i) { + if (!("fileName" in expected[i])) { + is(sub[i].headers["Content-Disposition"], + "form-data; name=\"" + expected[i].name + "\"", + "Correct name (A)"); + is (getPropCount(sub[i].headers), 1, + "Wrong number of headers (A)"); + } + else { + is(sub[i].headers["Content-Disposition"], + "form-data; name=\"" + expected[i].name + "\"; filename=\"" + + expected[i].fileName + "\"", + "Correct name (B)"); + is(sub[i].headers["Content-Type"], + expected[i].contentType, + "Correct content type (B)"); + is (getPropCount(sub[i].headers), 2, + "Wrong number of headers (B)"); + } + // Do not use |is(sub[i].body, expected[i].value, "...");| that may output raw binary data. + is(sub[i].body.length, expected[i].value.length, + "Length of correct value"); + ok(sub[i].body == expected[i].value, + "Content of correct value"); + } +} + +function testSlice(file, size, type, contents, fileType) { + is(file.type, type, fileType + " file is correct type"); + is(file.size, size, fileType + " file is correct size"); + ok(file instanceof File, fileType + " file is a File"); + ok(file instanceof Blob, fileType + " file is also a Blob"); + + var slice = file.mozSlice(0, size); + ok(slice instanceof Blob, fileType + " fullsize slice is a Blob"); + ok(!(slice instanceof File), fileType + " fullsize slice is not a File"); + + slice = file.mozSlice(0, 1234); + ok(slice instanceof Blob, fileType + " sized slice is a Blob"); + ok(!(slice instanceof File), fileType + " sized slice is not a File"); + + slice = file.mozSlice(0, size, "foo/bar"); + is(slice.type, "foo/bar", fileType + " fullsize slice foo/bar type"); + + slice = file.mozSlice(0, 5432, "foo/bar"); + is(slice.type, "foo/bar", fileType + " sized slice foo/bar type"); + + is(slice.mozSlice(0, 10).type, "", fileType + " slice-slice type"); + is(slice.mozSlice(0, 10).size, 10, fileType + " slice-slice size"); + is(slice.mozSlice(0, 10, "hello/world").type, "hello/world", fileType + " slice-slice hello/world type"); + is(slice.mozSlice(0, 10, "hello/world").size, 10, fileType + " slice-slice hello/world size"); + + var indexes = [[0, size, size], + [0, 1234, 1234], + [size-500, size, 500], + [size-500, size+500, 500], + [size+500, size+1500, 0], + [0, 0, 0], + [1000, 1000, 0], + [size, size, 0], + [0, undefined, size], + [100, undefined, size-100], + [-100, undefined, 100], + [100, -100, size-200], + [-size-100, undefined, size], + [-2*size-100, 500, 500], + [0, -size-100, 0], + [100, -size-100, 0], + [50, -size+100, 50], + [0, 33000, 33000], + [1000, 34000, 33000], + ]; + + for (var i = 0; i < indexes.length; ++i) { + var sliceContents; + var testName; + if (indexes[i][1] == undefined) { + slice = file.mozSlice(indexes[i][0]); + sliceContents = contents.slice(indexes[i][0]); + testName = fileType + " slice(" + indexes[i][0] + ")"; + } + else { + slice = file.mozSlice(indexes[i][0], indexes[i][1]); + sliceContents = contents.slice(indexes[i][0], indexes[i][1]); + testName = fileType + " slice(" + indexes[i][0] + ", " + indexes[i][1] + ")"; + } + is(slice.type, "", testName + " type"); + is(slice.size, indexes[i][2], testName + " size"); + is(sliceContents.length, indexes[i][2], testName + " data size"); + testFile(slice, sliceContents, testName); + } + + // Slice of slice + var slice = file.mozSlice(0, 40000); + testFile(slice.mozSlice(5000, 42000), contents.slice(5000, 40000), "file slice slice"); + + // ...of slice of slice + slice = slice.mozSlice(5000, 42000).mozSlice(400, 700); + SpecialPowers.gc(); + testFile(slice, contents.slice(5400, 5700), "file slice slice slice"); +} + diff --git a/content/base/test/test_blobbuilder.html b/content/base/test/test_blobbuilder.html new file mode 100644 index 000000000000..32e8d8c1e0f6 --- /dev/null +++ b/content/base/test/test_blobbuilder.html @@ -0,0 +1,135 @@ + + + + + Test for Bug 648997 + + + + + + +Mozilla Bug 648997 +

+ +
+
+
+ + diff --git a/content/base/test/test_fileapi_slice.html b/content/base/test/test_fileapi_slice.html index e971927acfb0..6f2eb75d36f7 100644 --- a/content/base/test/test_fileapi_slice.html +++ b/content/base/test/test_fileapi_slice.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=575946 Test for Bug 575946 + @@ -23,8 +24,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=575946
 
 
diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 2160c23a4212..533c45440318 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -1331,6 +1331,8 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CLASSINFO_DATA(XULCommandEvent, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(MozBlobBuilder, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) NS_DEFINE_CLASSINFO_DATA(CommandEvent, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -1573,6 +1575,7 @@ static const nsConstructorFuncMapData kConstructorFuncMap[] = NS_DEFINE_CONSTRUCTOR_FUNC_DATA(Worker, nsDOMWorker::NewWorker) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(ChromeWorker, nsDOMWorker::NewChromeWorker) NS_DEFINE_CONSTRUCTOR_FUNC_DATA(File, nsDOMFile::NewFile) + NS_DEFINE_CONSTRUCTOR_FUNC_DATA(MozBlobBuilder, NS_NewBlobBuilder) }; nsIXPConnect *nsDOMClassInfo::sXPConnect = nsnull; @@ -3999,12 +4002,10 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_BEGIN(Blob, nsIDOMBlob) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBlob) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMBlob_MOZILLA_2_0_BRANCH) DOM_CLASSINFO_MAP_END DOM_CLASSINFO_MAP_BEGIN(File, nsIDOMFile) DOM_CLASSINFO_MAP_ENTRY(nsIDOMBlob) - DOM_CLASSINFO_MAP_ENTRY(nsIDOMBlob_MOZILLA_2_0_BRANCH) DOM_CLASSINFO_MAP_ENTRY(nsIDOMFile) DOM_CLASSINFO_MAP_END @@ -4029,6 +4030,10 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMStringMap) DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(MozBlobBuilder, nsIDOMBlobBuilder) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMBlobBuilder) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN_NO_CLASS_IF(ModalContentWindow, nsIDOMWindow) DOM_CLASSINFO_MAP_ENTRY(nsIDOMWindow) DOM_CLASSINFO_MAP_ENTRY(nsIDOMJSWindow) diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 7af45cf02cda..84798435bb2d 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -406,6 +406,7 @@ DOMCI_CLASS(FileException) DOMCI_CLASS(FileError) DOMCI_CLASS(FileReader) DOMCI_CLASS(MozURLProperty) +DOMCI_CLASS(MozBlobBuilder) DOMCI_CLASS(DOMStringMap)