--- a/dom/base/FileReader.cpp
+++ b/dom/base/FileReader.cpp
@@ -1,44 +1,44 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FileReader.h"
-#include "nsContentCID.h"
-#include "nsContentUtils.h"
-#include "nsDOMClassInfoID.h"
-#include "nsError.h"
-#include "nsIFile.h"
-#include "nsNetCID.h"
-#include "nsNetUtil.h"
+#include "nsIEventTarget.h"
+#include "nsIGlobalObject.h"
+#include "nsITimer.h"
+#include "nsITransport.h"
+#include "nsIStreamTransportService.h"
-#include "nsXPCOM.h"
-#include "nsIDOMEventListener.h"
-#include "nsJSEnvironment.h"
-#include "nsCycleCollectionParticipant.h"
#include "mozilla/Base64.h"
+#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileReaderBinding.h"
#include "mozilla/dom/ProgressEvent.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMJSUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
#include "xpcpublic.h"
-#include "nsDOMJSUtils.h"
-#include "jsfriendapi.h"
-
-#include "nsITransport.h"
-#include "nsIStreamTransportService.h"
+#include "WorkerPrivate.h"
+#include "WorkerScope.h"
namespace mozilla {
namespace dom {
+using namespace workers;
+
#define ABORT_STR "abort"
#define LOAD_STR "load"
#define LOADSTART_STR "loadstart"
#define LOADEND_STR "loadend"
#define ERROR_STR "error"
#define PROGRESS_STR "progress"
const uint64_t kUnknownSize = uint64_t(-1);
@@ -51,17 +51,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBlob)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProgressNotifier)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mError)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(FileReader,
DOMEventTargetHelper)
- tmp->mResultArrayBuffer = nullptr;
+ tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBlob)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mProgressNotifier)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mError)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(FileReader,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mResultArrayBuffer)
@@ -71,54 +71,81 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(FileReader, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(FileReader, DOMEventTargetHelper)
+class MOZ_RAII FileReaderDecreaseBusyCounter
+{
+ RefPtr<FileReader> mFileReader;
+public:
+ explicit FileReaderDecreaseBusyCounter(FileReader* aFileReader)
+ : mFileReader(aFileReader)
+ {}
+
+ ~FileReaderDecreaseBusyCounter()
+ {
+ mFileReader->DecreaseBusyCounter();
+ }
+};
+
void
FileReader::RootResultArrayBuffer()
{
mozilla::HoldJSObjects(this);
}
//FileReader constructors/initializers
-FileReader::FileReader(nsPIDOMWindow* aWindow)
+FileReader::FileReader(nsPIDOMWindow* aWindow,
+ WorkerPrivate* aWorkerPrivate)
: DOMEventTargetHelper(aWindow)
, mFileData(nullptr)
, mDataLen(0)
, mDataFormat(FILE_AS_BINARY)
, mResultArrayBuffer(nullptr)
, mProgressEventWasDelayed(false)
, mTimerIsActive(false)
, mReadyState(EMPTY)
, mTotal(0)
, mTransferred(0)
+ , mTarget(do_GetCurrentThread())
+ , mBusyCount(0)
+ , mWorkerPrivate(aWorkerPrivate)
{
+ MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerPrivate && !aWindow);
+ MOZ_ASSERT_IF(NS_IsMainThread(), !mWorkerPrivate);
SetDOMStringToNull(mResult);
}
FileReader::~FileReader()
{
+ Shutdown();
FreeFileData();
- mResultArrayBuffer = nullptr;
- DropJSObjects(this);
}
/* static */ already_AddRefed<FileReader>
FileReader::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
{
// The owner can be null when this object is used by chrome code.
nsCOMPtr<nsPIDOMWindow> owner = do_QueryInterface(aGlobal.GetAsSupports());
- RefPtr<FileReader> fileReader = new FileReader(owner);
+ WorkerPrivate* workerPrivate = nullptr;
- if (!owner && nsContentUtils::IsCallerChrome()) {
+ if (!NS_IsMainThread()) {
+ JSContext* cx = aGlobal.Context();
+ workerPrivate = GetWorkerPrivateFromContext(cx);
+ MOZ_ASSERT(workerPrivate);
+ }
+
+ RefPtr<FileReader> fileReader = new FileReader(owner, workerPrivate);
+
+ if (!owner && nsContentUtils::ThreadsafeIsCallerChrome()) {
// Instead of grabbing some random global from the context stack,
// let's use the default one (junk scope) for now.
// We should move away from this Init...
fileReader->BindToOwner(xpc::NativeGlobal(xpc::PrivilegedJunkScope()));
}
return fileReader.forget();
}
@@ -210,17 +237,27 @@ FileReader::DoOnLoadEnd(nsresult aStatus
aSuccessEvent = NS_LITERAL_STRING(LOAD_STR);
aTerminationEvent = NS_LITERAL_STRING(LOADEND_STR);
nsresult rv = NS_OK;
switch (mDataFormat) {
case FILE_AS_ARRAYBUFFER: {
AutoJSAPI jsapi;
- if (NS_WARN_IF(!jsapi.Init(DOMEventTargetHelper::GetParentObject()))) {
+ nsCOMPtr<nsIGlobalObject> globalObject;
+
+ if (NS_IsMainThread()) {
+ globalObject = do_QueryInterface(GetParentObject());
+ } else {
+ MOZ_ASSERT(mWorkerPrivate);
+ MOZ_ASSERT(mBusyCount);
+ globalObject = mWorkerPrivate->GlobalScope();
+ }
+
+ if (!globalObject || !jsapi.Init(globalObject)) {
FreeFileData();
return NS_ERROR_FAILURE;
}
RootResultArrayBuffer();
mResultArrayBuffer = JS_NewArrayBufferWithContents(jsapi.cx(), mDataLen, mFileData);
if (!mResultArrayBuffer) {
JS_ClearPendingException(jsapi.cx());
@@ -251,48 +288,68 @@ FileReader::DoOnLoadEnd(nsresult aStatus
mResult.SetIsVoid(false);
FreeFileData();
return rv;
}
nsresult
-FileReader::DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount)
+FileReader::DoAsyncWait()
{
- MOZ_ASSERT(aStream);
+ nsresult rv = IncreaseBusyCounter();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = mAsyncStream->AsyncWait(this,
+ /* aFlags*/ 0,
+ /* aRequestedCount */ 0,
+ mTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DecreaseBusyCounter();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FileReader::DoReadData(uint64_t aCount)
+{
+ MOZ_ASSERT(mAsyncStream);
if (mDataFormat == FILE_AS_BINARY) {
//Continuously update our binary string as data comes in
uint32_t oldLen = mResult.Length();
NS_ASSERTION(mResult.Length() == mDataLen, "unexpected mResult length");
if (uint64_t(oldLen) + aCount > UINT32_MAX)
return NS_ERROR_OUT_OF_MEMORY;
char16_t *buf = nullptr;
mResult.GetMutableData(&buf, oldLen + aCount, fallible);
NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
uint32_t bytesRead = 0;
- aStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
- &bytesRead);
+ mAsyncStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
+ &bytesRead);
NS_ASSERTION(bytesRead == aCount, "failed to read data");
}
else {
//Update memory buffer to reflect the contents of the file
if (mDataLen + aCount > UINT32_MAX) {
// PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
return NS_ERROR_OUT_OF_MEMORY;
}
if (mDataFormat != FILE_AS_ARRAYBUFFER) {
mFileData = (char *) realloc(mFileData, mDataLen + aCount);
NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
}
uint32_t bytesRead = 0;
- aStream->Read(mFileData + mDataLen, aCount, &bytesRead);
+ mAsyncStream->Read(mFileData + mDataLen, aCount, &bytesRead);
NS_ASSERTION(bytesRead == aCount, "failed to read data");
}
mDataLen += aCount;
return NS_OK;
}
// Helper methods
@@ -356,20 +413,17 @@ FileReader::ReadFileContent(Blob& aBlob,
mAsyncStream = do_QueryInterface(wrapper);
MOZ_ASSERT(mAsyncStream);
mTotal = mBlob->GetSize(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
- aRv = mAsyncStream->AsyncWait(this,
- /* aFlags*/ 0,
- /* aRequestedCount */ 0,
- NS_GetCurrentThread());
+ aRv = DoAsyncWait();
if (NS_WARN_IF(aRv.Failed())) {
return;
}
//FileReader should be in loading state here
mReadyState = LOADING;
DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
@@ -462,16 +516,17 @@ FileReader::StartProgressEventTimer()
if (!mProgressNotifier) {
mProgressNotifier = do_CreateInstance(NS_TIMER_CONTRACTID);
}
if (mProgressNotifier) {
mProgressEventWasDelayed = false;
mTimerIsActive = true;
mProgressNotifier->Cancel();
+ mProgressNotifier->SetTarget(mTarget);
mProgressNotifier->InitWithCallback(this, NS_PROGRESS_EVENT_INTERVAL,
nsITimer::TYPE_ONE_SHOT);
}
}
void
FileReader::ClearProgressEventTimer()
{
@@ -545,28 +600,30 @@ FileReader::Notify(nsITimer* aTimer)
// InputStreamCallback
NS_IMETHODIMP
FileReader::OnInputStreamReady(nsIAsyncInputStream* aStream)
{
if (mReadyState != LOADING || aStream != mAsyncStream) {
return NS_OK;
}
+ // We use this class to decrease the busy counter at the end of this method.
+ // In theory we can do it immediatelly but, for debugging reasons, we want to
+ // be 100% sure we have a feature when OnLoadEnd() is called.
+ FileReaderDecreaseBusyCounter RAII(this);
+
uint64_t aCount;
nsresult rv = aStream->Available(&aCount);
if (NS_SUCCEEDED(rv) && aCount) {
- rv = DoReadData(aStream, aCount);
+ rv = DoReadData(aCount);
}
if (NS_SUCCEEDED(rv)) {
- rv = aStream->AsyncWait(this,
- /* aFlags*/ 0,
- /* aRequestedCount */ 0,
- NS_GetCurrentThread());
+ rv = DoAsyncWait();
}
if (NS_FAILED(rv) || !aCount) {
if (rv == NS_BASE_STREAM_CLOSED) {
rv = NS_OK;
}
return OnLoadEnd(rv);
}
@@ -638,10 +695,61 @@ FileReader::Abort(ErrorResult& aRv)
//Clean up memory buffer
FreeFileData();
// Dispatch the events
DispatchProgressEvent(NS_LITERAL_STRING(ABORT_STR));
DispatchProgressEvent(NS_LITERAL_STRING(LOADEND_STR));
}
+nsresult
+FileReader::IncreaseBusyCounter()
+{
+ if (mWorkerPrivate && mBusyCount++ == 0 &&
+ !mWorkerPrivate->AddFeature(mWorkerPrivate->GetJSContext(), this)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void
+FileReader::DecreaseBusyCounter()
+{
+ MOZ_ASSERT_IF(mWorkerPrivate, mBusyCount);
+ if (mWorkerPrivate && --mBusyCount == 0) {
+ mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+ }
+}
+
+bool
+FileReader::Notify(JSContext* aCx, Status aStatus)
+{
+ MOZ_ASSERT(mWorkerPrivate);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ if (aStatus > Running) {
+ Shutdown();
+ }
+
+ return true;
+}
+
+void
+FileReader::Shutdown()
+{
+ mResultArrayBuffer = nullptr;
+ DropJSObjects(this);
+
+ if (mAsyncStream) {
+ mAsyncStream->Close();
+ mAsyncStream = nullptr;
+ }
+
+ if (mWorkerPrivate && mBusyCount != 0) {
+ mWorkerPrivate->RemoveFeature(mWorkerPrivate->GetJSContext(), this);
+ mWorkerPrivate = nullptr;
+ mBusyCount = 0;
+ }
+}
+
} // dom namespace
} // mozilla namespace
--- a/dom/base/FileReader.h
+++ b/dom/base/FileReader.h
@@ -4,57 +4,67 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_FileReader_h
#define mozilla_dom_FileReader_h
#include "mozilla/Attributes.h"
#include "mozilla/DOMEventTargetHelper.h"
-#include "mozilla/dom/DOMError.h"
-#include "nsCOMPtr.h"
#include "nsIAsyncInputStream.h"
-#include "nsIStreamListener.h"
-#include "nsISupportsUtils.h"
#include "nsIInterfaceRequestor.h"
-#include "nsITimer.h"
-#include "nsJSUtils.h"
+#include "nsCOMPtr.h"
#include "nsString.h"
-#include "nsTArray.h"
#include "nsWeakReference.h"
-#include "prtime.h"
+#include "WorkerFeature.h"
#define NS_PROGRESS_EVENT_INTERVAL 50
+class nsITimer;
+class nsIEventTarget;
+
namespace mozilla {
namespace dom {
class Blob;
+class DOMError;
+
+namespace workers {
+class WorkerPrivate;
+}
extern const uint64_t kUnknownSize;
+class FileReaderDecreaseBusyCounter;
+
class FileReader final : public DOMEventTargetHelper,
public nsIInterfaceRequestor,
public nsSupportsWeakReference,
public nsIInputStreamCallback,
- public nsITimerCallback
+ public nsITimerCallback,
+ public workers::WorkerFeature
{
+ friend class FileReaderDecreaseBusyCounter;
+
public:
- explicit FileReader(nsPIDOMWindow* aWindow);
+ FileReader(nsPIDOMWindow* aWindow,
+ workers::WorkerPrivate* aWorkerPrivate);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIINPUTSTREAMCALLBACK
NS_DECL_NSIINTERFACEREQUESTOR
- NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader, DOMEventTargetHelper)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(FileReader,
+ DOMEventTargetHelper)
- virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
// WebIDL
static already_AddRefed<FileReader>
Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
void ReadAsArrayBuffer(JSContext* aCx, Blob& aBlob, ErrorResult& aRv)
{
ReadFileContent(aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
}
@@ -91,16 +101,19 @@ public:
IMPL_EVENT_HANDLER(error)
IMPL_EVENT_HANDLER(loadend)
void ReadAsBinaryString(Blob& aBlob, ErrorResult& aRv)
{
ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
}
+ // WorkerFeature
+ bool Notify(JSContext* aCx, workers::Status) override;
+
private:
virtual ~FileReader();
// This must be in sync with dom/webidl/FileReader.webidl
enum eReadyState {
EMPTY = 0,
LOADING = 1,
DONE = 2
@@ -126,28 +139,36 @@ private:
nsresult OnLoadEnd(nsresult aStatus);
void StartProgressEventTimer();
void ClearProgressEventTimer();
void DispatchError(nsresult rv, nsAString& finalEvent);
nsresult DispatchProgressEvent(const nsAString& aType);
- nsresult DoReadData(nsIAsyncInputStream* aStream, uint64_t aCount);
+ nsresult DoAsyncWait();
+ nsresult DoReadData(uint64_t aCount);
nsresult DoOnLoadEnd(nsresult aStatus, nsAString& aSuccessEvent,
nsAString& aTerminationEvent);
void FreeFileData()
{
- free(mFileData);
- mFileData = nullptr;
- mDataLen = 0;
+ if (mFileData) {
+ free(mFileData);
+ mFileData = nullptr;
+ mDataLen = 0;
+ }
}
+ nsresult IncreaseBusyCounter();
+ void DecreaseBusyCounter();
+
+ void Shutdown();
+
char *mFileData;
RefPtr<Blob> mBlob;
nsCString mCharset;
uint32_t mDataLen;
eDataFormat mDataFormat;
nsString mResult;
@@ -161,14 +182,21 @@ private:
nsCOMPtr<nsIAsyncInputStream> mAsyncStream;
RefPtr<DOMError> mError;
eReadyState mReadyState;
uint64_t mTotal;
uint64_t mTransferred;
+
+ nsCOMPtr<nsIEventTarget> mTarget;
+
+ uint64_t mBusyCount;
+
+ // Kept alive with a WorkerFeature.
+ workers::WorkerPrivate* mWorkerPrivate;
};
} // dom namespace
} // mozilla namespace
#endif // mozilla_dom_FileReader_h
--- a/dom/webidl/FileReader.webidl
+++ b/dom/webidl/FileReader.webidl
@@ -6,17 +6,17 @@
* The origin of this IDL file is
* http://dev.w3.org/2006/webapi/FileAPI/#dfn-filereader
*
* Copyright © 2013 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
[Constructor,
- Exposed=(Window,System)]
+ Exposed=(Window,Worker,System)]
interface FileReader : EventTarget {
// async read methods
[Throws]
void readAsArrayBuffer(Blob blob);
[Throws]
void readAsText(Blob blob, optional DOMString label = "");
[Throws]
void readAsDataURL(Blob blob);
copy from dom/base/test/fileapi_chromeScript.js
copy to dom/workers/test/fileapi_chromeScript.js
--- a/dom/workers/test/mochitest.ini
+++ b/dom/workers/test/mochitest.ini
@@ -114,16 +114,18 @@ support-files =
sharedworker_performance_user_timing.js
referrer.sjs
performance_observer.html
sharedWorker_ports.js
sharedWorker_lifetime.js
worker_referrer.js
websocket_https.html
websocket_https_worker.js
+ worker_fileReader.js
+ fileapi_chromeScript.js
[test_404.html]
[test_atob.html]
[test_blobConstructor.html]
[test_blobWorkers.html]
[test_bug949946.html]
[test_bug978260.html]
[test_bug998474.html]
@@ -231,8 +233,9 @@ skip-if = buildapp == 'b2g'
[test_xhr_system.html]
skip-if = buildapp == 'b2g'
[test_xhr_timeout.html]
skip-if = (os == "win") || (os == "mac") || toolkit == 'android' #bug 798220
[test_xhrAbort.html]
[test_referrer.html]
[test_sharedWorker_ports.html]
[test_sharedWorker_lifetime.html]
+[test_fileReader.html]
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -110,16 +110,18 @@ var interfaceNamesInGlobalScope =
"ExtendableEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"ExtendableMessageEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"FetchEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!
"File",
// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
"FileReaderSync",
// IMPORTANT: Do not change this list without review from a DOM peer!
"FormData",
// IMPORTANT: Do not change this list without review from a DOM peer!
"Headers",
// IMPORTANT: Do not change this list without review from a DOM peer!
"IDBCursor",
// IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/test_fileReader.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for FileReader in workers</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<script type="text/javascript;version=1.7">
+
+const minFileSize = 20000;
+SimpleTest.waitForExplicitFinish();
+
+// Create strings containing data we'll test with. We'll want long
+// strings to ensure they span multiple buffers while loading
+var testTextData = "asd b\tlah\u1234w\u00a0r";
+while (testTextData.length < minFileSize) {
+ testTextData = testTextData + testTextData;
+}
+
+var testASCIIData = "abcdef 123456\n";
+while (testASCIIData.length < minFileSize) {
+ testASCIIData = testASCIIData + testASCIIData;
+}
+
+var testBinaryData = "";
+for (var i = 0; i < 256; i++) {
+ testBinaryData += String.fromCharCode(i);
+}
+while (testBinaryData.length < minFileSize) {
+ testBinaryData = testBinaryData + testBinaryData;
+}
+
+var dataurldata0 = testBinaryData.substr(0, testBinaryData.length -
+ testBinaryData.length % 3);
+var dataurldata1 = testBinaryData.substr(0, testBinaryData.length - 2 -
+ testBinaryData.length % 3);
+var dataurldata2 = testBinaryData.substr(0, testBinaryData.length - 1 -
+ testBinaryData.length % 3);
+
+
+//Set up files for testing
+var openerURL = SimpleTest.getTestFileURL("fileapi_chromeScript.js");
+var opener = SpecialPowers.loadChromeScript(openerURL);
+opener.addMessageListener("files.opened", onFilesOpened);
+opener.sendAsyncMessage("files.open", [
+ testASCIIData,
+ testBinaryData,
+ null,
+ convertToUTF8(testTextData),
+ convertToUTF16(testTextData),
+ "",
+ dataurldata0,
+ dataurldata1,
+ dataurldata2,
+]);
+
+function onFilesOpened(message) {
+ var worker = new Worker('worker_fileReader.js');
+ worker.postMessage({ blobs: message,
+ testTextData: testTextData,
+ testASCIIData: testASCIIData,
+ testBinaryData: testBinaryData,
+ dataurldata0: dataurldata0,
+ dataurldata1: dataurldata1,
+ dataurldata2: dataurldata2 });
+
+ worker.onmessage = function(e) {
+ var msg = e.data;
+ if (msg.type == 'finish') {
+ SimpleTest.finish();
+ return;
+ }
+
+ if (msg.type == 'check') {
+ ok(msg.status, msg.msg);
+ return;
+ }
+
+ ok(false, "Unknown message.");
+ }
+}
+
+function convertToUTF16(s) {
+ res = "";
+ for (var i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+</script>
+</body>
+</html>
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -102,16 +102,18 @@ var interfaceNamesInGlobalScope =
"DOMStringList",
// IMPORTANT: Do not change this list without review from a DOM peer!
"Event",
// IMPORTANT: Do not change this list without review from a DOM peer!
"EventTarget",
// IMPORTANT: Do not change this list without review from a DOM peer!
"File",
// IMPORTANT: Do not change this list without review from a DOM peer!
+ "FileReader",
+// IMPORTANT: Do not change this list without review from a DOM peer!
"FileReaderSync",
// IMPORTANT: Do not change this list without review from a DOM peer!
"FormData",
// IMPORTANT: Do not change this list without review from a DOM peer!
"Headers",
// IMPORTANT: Do not change this list without review from a DOM peer!
"IDBCursor",
// IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/worker_fileReader.js
@@ -0,0 +1,417 @@
+var testRanCounter = 0;
+var expectedTestCount = 0;
+var testSetupFinished = false;
+
+function ok(a, msg) {
+ postMessage({type: 'check', status: !!a, msg: msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({type: 'finish'});
+}
+
+function convertToUTF16(s) {
+ res = "";
+ for (var i = 0; i < s.length; ++i) {
+ c = s.charCodeAt(i);
+ res += String.fromCharCode(c & 255, c >>> 8);
+ }
+ return res;
+}
+
+function convertToUTF8(s) {
+ return unescape(encodeURIComponent(s));
+}
+
+function convertToDataURL(s) {
+ return "data:application/octet-stream;base64," + btoa(s);
+}
+
+onmessage = function(message) {
+ is(FileReader.EMPTY, 0, "correct EMPTY value");
+ is(FileReader.LOADING, 1, "correct LOADING value");
+ is(FileReader.DONE, 2, "correct DONE value");
+
+ // List of blobs.
+ var asciiFile = message.data.blobs.shift();
+ var binaryFile = message.data.blobs.shift();
+ var nonExistingFile = message.data.blobs.shift();
+ var utf8TextFile = message.data.blobs.shift();
+ var utf16TextFile = message.data.blobs.shift();
+ var emptyFile = message.data.blobs.shift();
+ var dataUrlFile0 = message.data.blobs.shift();
+ var dataUrlFile1 = message.data.blobs.shift();
+ var dataUrlFile2 = message.data.blobs.shift();
+
+ // List of buffers for testing.
+ var testTextData = message.data.testTextData;
+ var testASCIIData = message.data.testASCIIData;
+ var testBinaryData = message.data.testBinaryData;
+ var dataurldata0 = message.data.dataurldata0;
+ var dataurldata1 = message.data.dataurldata1;
+ var dataurldata2 = message.data.dataurldata2;
+
+ // Test that plain reading works and fires events as expected, both
+ // for text and binary reading
+
+ var onloadHasRunText = false;
+ var onloadStartHasRunText = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial text readyState");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "plain reading");
+ r.addEventListener("load", function() { onloadHasRunText = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunText = true }, false);
+ r.readAsText(asciiFile);
+ is(r.readyState, FileReader.LOADING, "correct loading text readyState");
+ is(onloadHasRunText, false, "text loading must be async");
+ is(onloadStartHasRunText, true, "text loadstart should fire sync");
+ expectedTestCount++;
+
+ var onloadHasRunBinary = false;
+ var onloadStartHasRunBinary = false;
+ r = new FileReader();
+ is(r.readyState, FileReader.EMPTY, "correct initial binary readyState");
+ r.addEventListener("load", function() { onloadHasRunBinary = true }, false);
+ r.addEventListener("loadstart", function() { onloadStartHasRunBinary = true }, false);
+ r.readAsBinaryString(binaryFile);
+ r.onload = getLoadHandler(testBinaryData, testBinaryData.length, "binary reading");
+ is(r.readyState, FileReader.LOADING, "correct loading binary readyState");
+ 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();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "no encoding reading");
+ r.readAsText(asciiFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "iso8859 reading");
+ r.readAsText(asciiFile, "iso-8859-1");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF8(testTextData).length,
+ "utf8 reading");
+ r.readAsText(utf8TextFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.readAsText(utf16TextFile, "utf-16");
+ r.onload = getLoadHandler(testTextData,
+ convertToUTF16(testTextData).length,
+ "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!)
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty no encoding reading");
+ r.readAsText(emptyFile, "");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf8 reading");
+ r.readAsText(emptyFile, "utf8");
+ expectedTestCount++;
+
+ r = new FileReader();
+ r.onload = getLoadHandler("", 0, "empty utf16 reading");
+ r.readAsText(emptyFile, "utf-16");
+ expectedTestCount++;
+
+ r = new FileReader();
+ 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,
+ testASCIIData.length,
+ "to-be-reused reading text")
+ var makeAnotherReadListener = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener, false);
+ r.onload = getLoadHandler(testASCIIData,
+ testASCIIData.length,
+ "reused reading text");
+ r.readAsText(asciiFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener, false);
+ r.readAsText(asciiFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "to-be-reused reading binary")
+ var makeAnotherReadListener2 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener2, false);
+ r.onload = getLoadHandler(testBinaryData,
+ testBinaryData.length,
+ "reused reading binary");
+ r.readAsBinaryString(binaryFile);
+ };
+ r.addEventListener("load", makeAnotherReadListener2, false);
+ r.readAsBinaryString(binaryFile);
+ expectedTestCount += 2;
+
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "to-be-reused reading data url")
+ var makeAnotherReadListener3 = function(event) {
+ r = event.target;
+ r.removeEventListener("load", makeAnotherReadListener3, false);
+ r.onload = getLoadHandler(convertToDataURL(testBinaryData),
+ testBinaryData.length,
+ "reused reading data url");
+ r.readAsDataURL(binaryFile);
+ };
+ 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
+ is(dataurldata0.length % 3, 0, "Want to test data with length % 3 == 0");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata0),
+ dataurldata0.length,
+ "dataurl reading, %3 = 0");
+ r.readAsDataURL(dataUrlFile0);
+ expectedTestCount++;
+
+ is(dataurldata1.length % 3, 1, "Want to test data with length % 3 == 1");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata1),
+ dataurldata1.length,
+ "dataurl reading, %3 = 1");
+ r.readAsDataURL(dataUrlFile1);
+ expectedTestCount++;
+
+ is(dataurldata2.length % 3, 2, "Want to test data with length % 3 == 2");
+ r = new FileReader();
+ r.onload = getLoadHandler(convertToDataURL(dataurldata2),
+ dataurldata2.length,
+ "dataurl reading, %3 = 2");
+ r.readAsDataURL(dataUrlFile2),
+ expectedTestCount++;
+
+
+ // Test abort()
+ var abortHasRun = false;
+ var loadEndHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(abortHasRun, false, "abort should only fire once");
+ is(loadEndHasRun, false, "loadend shouldn't have fired yet");
+ abortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onloadend = function (event) {
+ is(abortHasRun, true, "abort should fire before loadend");
+ is(loadEndHasRun, false, "loadend should only fire once");
+ loadEndHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ r.onerror = function() { ok(false, "error should not fire for aborted reads") };
+ r.onprogress = function() { ok(false, "progress should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(abortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.abort();
+ is(abortHasRun, true, "abort should fire sync");
+ is(loadEndHasRun, true, "loadend should fire sync");
+
+ // Test calling readAsX to cause abort()
+ var reuseAbortHasRun = false;
+ r = new FileReader();
+ r.onabort = function (event) {
+ is(reuseAbortHasRun, false, "abort should only fire once");
+ reuseAbortHasRun = true;
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onabort");
+ is(event.target.error.name, "AbortError", "error set to AbortError for aborted reads");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ }
+ r.onload = function() { ok(false, "load should not fire for aborted reads") };
+ var abortThrew = false;
+ try {
+ r.abort();
+ } catch(e) {
+ abortThrew = true;
+ }
+ is(abortThrew, true, "abort() must throw if not loading");
+ is(reuseAbortHasRun, false, "abort() is a no-op unless loading");
+ r.readAsText(asciiFile);
+ r.readAsText(asciiFile);
+ is(reuseAbortHasRun, true, "abort should fire sync");
+ r.onload = getLoadHandler(testASCIIData, testASCIIData.length, "reuse-as-abort reading");
+ expectedTestCount++;
+
+
+ // Test reading from nonexistent files
+ r = new FileReader();
+ var didThrow = false;
+ r.onerror = function (event) {
+ is(event.target.readyState, FileReader.DONE, "should be DONE while firing onerror");
+ is(event.target.error.name, "NotFoundError", "error set to NotFoundError for nonexistent files");
+ is(event.target.result, null, "file data should be null on aborted reads");
+ testHasRun();
+ };
+ r.onload = function (event) {
+ is(false, "nonexistent file shouldn't load! (FIXME: bug 1122788)");
+ testHasRun();
+ };
+ try {
+ r.readAsDataURL(nonExistingFile);
+ expectedTestCount++;
+ } catch(ex) {
+ didThrow = true;
+ }
+ // Once this test passes, we should test that onerror gets called and
+ // that the FileReader object is in the right state during that call.
+ is(didThrow, false, "shouldn't throw when opening nonexistent file, should fire error instead");
+
+
+ function getLoadHandler(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.target.result, expectedResult,
+ "result 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);
+ 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;
+ 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();
+ }
+ }
+
+ function testHasRun() {
+ //alert(testRanCounter);
+ ++testRanCounter;
+ if (testRanCounter == expectedTestCount) {
+ is(testSetupFinished, true, "test setup should have finished; check for exceptions");
+ is(onloadHasRunText, true, "onload text should have fired by now");
+ is(onloadHasRunBinary, true, "onload binary should have fired by now");
+ finish();
+ }
+ }
+
+ testSetupFinished = true;
+}
--- a/testing/web-platform/meta/FileAPI/idlharness.worker.js.ini
+++ b/testing/web-platform/meta/FileAPI/idlharness.worker.js.ini
@@ -36,141 +36,8 @@
[FileList interface: existence and properties of interface prototype object's "constructor" property]
expected: FAIL
[FileList interface: operation item(unsigned long)]
expected: FAIL
[FileList interface: attribute length]
expected: FAIL
-
- [FileReader interface: existence and properties of interface object]
- expected: FAIL
-
- [FileReader interface object length]
- expected: FAIL
-
- [FileReader interface: existence and properties of interface prototype object]
- expected: FAIL
-
- [FileReader interface: existence and properties of interface prototype object's "constructor" property]
- expected: FAIL
-
- [FileReader interface: operation readAsArrayBuffer(Blob)]
- expected: FAIL
-
- [FileReader interface: operation readAsText(Blob,DOMString)]
- expected: FAIL
-
- [FileReader interface: operation readAsDataURL(Blob)]
- expected: FAIL
-
- [FileReader interface: operation abort()]
- expected: FAIL
-
- [FileReader interface: constant EMPTY on interface object]
- expected: FAIL
-
- [FileReader interface: constant EMPTY on interface prototype object]
- expected: FAIL
-
- [FileReader interface: constant LOADING on interface object]
- expected: FAIL
-
- [FileReader interface: constant LOADING on interface prototype object]
- expected: FAIL
-
- [FileReader interface: constant DONE on interface object]
- expected: FAIL
-
- [FileReader interface: constant DONE on interface prototype object]
- expected: FAIL
-
- [FileReader interface: attribute readyState]
- expected: FAIL
-
- [FileReader interface: attribute result]
- expected: FAIL
-
- [FileReader interface: attribute error]
- expected: FAIL
-
- [FileReader interface: attribute onloadstart]
- expected: FAIL
-
- [FileReader interface: attribute onprogress]
- expected: FAIL
-
- [FileReader interface: attribute onload]
- expected: FAIL
-
- [FileReader interface: attribute onabort]
- expected: FAIL
-
- [FileReader interface: attribute onerror]
- expected: FAIL
-
- [FileReader interface: attribute onloadend]
- expected: FAIL
-
- [FileReader must be primary interface of new FileReader()]
- expected: FAIL
-
- [Stringification of new FileReader()]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "readAsArrayBuffer" with the proper type (0)]
- expected: FAIL
-
- [FileReader interface: calling readAsArrayBuffer(Blob) on new FileReader() with too few arguments must throw TypeError]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "readAsText" with the proper type (1)]
- expected: FAIL
-
- [FileReader interface: calling readAsText(Blob,DOMString) on new FileReader() with too few arguments must throw TypeError]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "readAsDataURL" with the proper type (2)]
- expected: FAIL
-
- [FileReader interface: calling readAsDataURL(Blob) on new FileReader() with too few arguments must throw TypeError]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "abort" with the proper type (3)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "EMPTY" with the proper type (4)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "LOADING" with the proper type (5)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "DONE" with the proper type (6)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "readyState" with the proper type (7)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "result" with the proper type (8)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "error" with the proper type (9)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onloadstart" with the proper type (10)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onprogress" with the proper type (11)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onload" with the proper type (12)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onabort" with the proper type (13)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onerror" with the proper type (14)]
- expected: FAIL
-
- [FileReader interface: new FileReader() must inherit property "onloadend" with the proper type (15)]
- expected: FAIL
-
--- a/xpcom/io/nsStreamUtils.cpp
+++ b/xpcom/io/nsStreamUtils.cpp
@@ -7,31 +7,34 @@
#include "mozilla/Mutex.h"
#include "mozilla/Attributes.h"
#include "nsStreamUtils.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsIPipe.h"
#include "nsICloneableInputStream.h"
#include "nsIEventTarget.h"
-#include "nsIRunnable.h"
+#include "nsICancelableRunnable.h"
#include "nsISafeOutputStream.h"
#include "nsString.h"
#include "nsIAsyncInputStream.h"
#include "nsIAsyncOutputStream.h"
#include "nsIBufferedStreams.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
using namespace mozilla;
//-----------------------------------------------------------------------------
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
class nsInputStreamReadyEvent final
- : public nsIRunnable
+ : public nsICancelableRunnable
, public nsIInputStreamCallback
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
nsInputStreamReadyEvent(nsIInputStreamCallback* aCallback,
nsIEventTarget* aTarget)
: mCallback(aCallback)
@@ -90,29 +93,38 @@ public:
if (mStream) {
mCallback->OnInputStreamReady(mStream);
}
mCallback = nullptr;
}
return NS_OK;
}
+ NS_IMETHOD Cancel() override
+ {
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
private:
nsCOMPtr<nsIAsyncInputStream> mStream;
nsCOMPtr<nsIInputStreamCallback> mCallback;
nsCOMPtr<nsIEventTarget> mTarget;
};
-NS_IMPL_ISUPPORTS(nsInputStreamReadyEvent, nsIRunnable,
- nsIInputStreamCallback)
+NS_IMPL_ISUPPORTS(nsInputStreamReadyEvent, nsICancelableRunnable,
+ nsIRunnable, nsIInputStreamCallback)
//-----------------------------------------------------------------------------
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
class nsOutputStreamReadyEvent final
- : public nsIRunnable
+ : public nsICancelableRunnable
, public nsIOutputStreamCallback
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
nsOutputStreamReadyEvent(nsIOutputStreamCallback* aCallback,
nsIEventTarget* aTarget)
: mCallback(aCallback)
@@ -171,24 +183,30 @@ public:
if (mStream) {
mCallback->OnOutputStreamReady(mStream);
}
mCallback = nullptr;
}
return NS_OK;
}
+ NS_IMETHOD Cancel() override
+ {
+ mCallback = nullptr;
+ return NS_OK;
+ }
+
private:
nsCOMPtr<nsIAsyncOutputStream> mStream;
nsCOMPtr<nsIOutputStreamCallback> mCallback;
nsCOMPtr<nsIEventTarget> mTarget;
};
-NS_IMPL_ISUPPORTS(nsOutputStreamReadyEvent, nsIRunnable,
- nsIOutputStreamCallback)
+NS_IMPL_ISUPPORTS(nsOutputStreamReadyEvent, nsICancelableRunnable,
+ nsIRunnable, nsIOutputStreamCallback)
//-----------------------------------------------------------------------------
already_AddRefed<nsIInputStreamCallback>
NS_NewInputStreamReadyEvent(nsIInputStreamCallback* aCallback,
nsIEventTarget* aTarget)
{
NS_ASSERTION(aCallback, "null callback");
@@ -211,17 +229,17 @@ NS_NewOutputStreamReadyEvent(nsIOutputSt
//-----------------------------------------------------------------------------
// NS_AsyncCopy implementation
// abstract stream copier...
class nsAStreamCopier
: public nsIInputStreamCallback
, public nsIOutputStreamCallback
- , public nsIRunnable
+ , public nsICancelableRunnable
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
nsAStreamCopier()
: mLock("nsAStreamCopier.mLock")
, mCallback(nullptr)
, mProgressCallback(nullptr)
@@ -428,16 +446,18 @@ public:
if (mEventIsPending) {
mEventIsPending = false;
PostContinuationEvent_Locked();
}
return NS_OK;
}
+ NS_IMETHOD Cancel() MOZ_MUST_OVERRIDE override = 0;
+
nsresult PostContinuationEvent()
{
// we cannot post a continuation event if there is currently
// an event in process. doing so could result in Process being
// run simultaneously on multiple threads, so we mark the event
// as pending, and if an event is already in process then we
// just let that existing event take care of posting the real
// continuation event.
@@ -484,16 +504,17 @@ protected:
virtual ~nsAStreamCopier()
{
}
};
NS_IMPL_ISUPPORTS(nsAStreamCopier,
nsIInputStreamCallback,
nsIOutputStreamCallback,
+ nsICancelableRunnable,
nsIRunnable)
class nsStreamCopierIB final : public nsAStreamCopier
{
public:
nsStreamCopierIB() : nsAStreamCopier()
{
}
@@ -522,28 +543,34 @@ public:
state->mSinkCondition = rv;
} else if (*aCountWritten == 0) {
state->mSinkCondition = NS_BASE_STREAM_CLOSED;
}
return state->mSinkCondition;
}
- uint32_t DoCopy(nsresult* aSourceCondition, nsresult* aSinkCondition)
+ uint32_t DoCopy(nsresult* aSourceCondition,
+ nsresult* aSinkCondition) override
{
ReadSegmentsState state;
state.mSink = mSink;
state.mSinkCondition = NS_OK;
uint32_t n;
*aSourceCondition =
mSource->ReadSegments(ConsumeInputBuffer, &state, mChunkSize, &n);
*aSinkCondition = state.mSinkCondition;
return n;
}
+
+ NS_IMETHOD Cancel() override
+ {
+ return NS_OK;
+ }
};
class nsStreamCopierOB final : public nsAStreamCopier
{
public:
nsStreamCopierOB() : nsAStreamCopier()
{
}
@@ -572,28 +599,34 @@ public:
state->mSourceCondition = rv;
} else if (*aCountRead == 0) {
state->mSourceCondition = NS_BASE_STREAM_CLOSED;
}
return state->mSourceCondition;
}
- uint32_t DoCopy(nsresult* aSourceCondition, nsresult* aSinkCondition)
+ uint32_t DoCopy(nsresult* aSourceCondition,
+ nsresult* aSinkCondition) override
{
WriteSegmentsState state;
state.mSource = mSource;
state.mSourceCondition = NS_OK;
uint32_t n;
*aSinkCondition =
mSink->WriteSegments(FillOutputBuffer, &state, mChunkSize, &n);
*aSourceCondition = state.mSourceCondition;
return n;
}
+
+ NS_IMETHOD Cancel() override
+ {
+ return NS_OK;
+ }
};
//-----------------------------------------------------------------------------
nsresult
NS_AsyncCopy(nsIInputStream* aSource,
nsIOutputStream* aSink,
nsIEventTarget* aTarget,
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -122,20 +122,33 @@ public:
}
void* Alloc(size_t aSize);
void Free(void* aPtr);
};
} // namespace
-class nsTimerEvent : public nsRunnable
+// This is a nsICancelableRunnable because we can dispatch it to Workers and
+// those can be shut down at any time, and in these cases, Cancel() is called
+// instead of Run().
+class nsTimerEvent : public nsCancelableRunnable
{
public:
- NS_IMETHOD Run();
+ NS_IMETHOD Run() override;
+
+ NS_IMETHOD Cancel() override
+ {
+ // Since nsTimerImpl is not thread-safe, we should release |mTimer|
+ // here in the target thread to avoid race condition. Otherwise,
+ // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
+ // timer thread and result in race condition.
+ mTimer = nullptr;
+ return NS_OK;
+ }
nsTimerEvent()
: mTimer()
, mGeneration(0)
{
MOZ_COUNT_CTOR(nsTimerEvent);
// Note: We override operator new for this class, and the override is
@@ -248,35 +261,34 @@ nsTimerEvent::DeleteAllocatorIfNeeded()
delete sAllocator;
sAllocator = nullptr;
}
}
NS_IMETHODIMP
nsTimerEvent::Run()
{
+ MOZ_ASSERT(mTimer);
+
if (mGeneration != mTimer->GetGeneration()) {
return NS_OK;
}
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
TimeStamp now = TimeStamp::Now();
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
this, (now - mInitTime).ToMilliseconds()));
}
mTimer->Fire();
- // Since nsTimerImpl is not thread-safe, we should release |mTimer|
- // here in the target thread to avoid race condition. Otherwise,
- // ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
- // timer thread and result in race condition.
- mTimer = nullptr;
- return NS_OK;
+ // We call Cancel() to correctly release mTimer.
+ // Read more in the Cancel() implementation.
+ return Cancel();
}
nsresult
TimerThread::Init()
{
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
("TimerThread::Init [%d]\n", mInitialized));
--- a/xpcom/threads/nsICancelableRunnable.idl
+++ b/xpcom/threads/nsICancelableRunnable.idl
@@ -11,14 +11,15 @@
*/
[scriptable, uuid(de93dc4c-5eea-4eb7-b6d1-dbf1e0cef65c)]
interface nsICancelableRunnable : nsIRunnable
{
/**
* Cancels a pending task. If the task has already been executed this will
* be a no-op. Calling this method twice is considered an error.
+ * If cancel() is called, run() will not be called.
*
* @throws NS_ERROR_UNEXPECTED
* Indicates that the runnable has already been canceled.
*/
void cancel();
};