2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "core/fileapi/FileReader.h"
34 #include "bindings/core/v8/ExceptionState.h"
35 #include "bindings/core/v8/UnionTypesCore.h"
36 #include "core/dom/CrossThreadTask.h"
37 #include "core/dom/DOMArrayBuffer.h"
38 #include "core/dom/Document.h"
39 #include "core/dom/ExceptionCode.h"
40 #include "core/dom/ExecutionContext.h"
41 #include "core/events/ProgressEvent.h"
42 #include "core/fileapi/File.h"
43 #include "core/inspector/InspectorInstrumentation.h"
44 #include "platform/Logging.h"
45 #include "platform/Supplementable.h"
46 #include "wtf/CurrentTime.h"
47 #include "wtf/Deque.h"
48 #include "wtf/HashSet.h"
49 #include "wtf/text/CString.h"
56 const CString
utf8BlobUUID(Blob
* blob
)
58 return blob
->uuid().utf8();
61 const CString
utf8FilePath(Blob
* blob
)
63 return blob
->hasBackingFile() ? toFile(blob
)->path().utf8() : "";
69 // Embedders like chromium limit the number of simultaneous requests to avoid
70 // excessive IPC congestion. We limit this to 100 per thread to throttle the
71 // requests (the value is arbitrarily chosen).
72 static const size_t kMaxOutstandingRequestsPerThread
= 100;
73 static const double progressNotificationIntervalMS
= 50;
75 class FileReader::ThrottlingController final
: public NoBaseWillBeGarbageCollectedFinalized
<FileReader::ThrottlingController
>, public WillBeHeapSupplement
<ExecutionContext
> {
76 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(FileReader::ThrottlingController
);
78 static ThrottlingController
* from(ExecutionContext
* context
)
83 ThrottlingController
* controller
= static_cast<ThrottlingController
*>(WillBeHeapSupplement
<ExecutionContext
>::from(*context
, supplementName()));
85 controller
= new ThrottlingController();
86 WillBeHeapSupplement
<ExecutionContext
>::provideTo(*context
, supplementName(), adoptPtrWillBeNoop(controller
));
91 ~ThrottlingController() { }
93 enum FinishReaderType
{ DoNotRunPendingReaders
, RunPendingReaders
};
95 static void pushReader(ExecutionContext
* context
, FileReader
* reader
)
97 ThrottlingController
* controller
= from(context
);
101 reader
->m_asyncOperationId
= InspectorInstrumentation::traceAsyncOperationStarting(context
, "FileReader");
102 controller
->pushReader(reader
);
105 static FinishReaderType
removeReader(ExecutionContext
* context
, FileReader
* reader
)
107 ThrottlingController
* controller
= from(context
);
109 return DoNotRunPendingReaders
;
111 return controller
->removeReader(reader
);
114 static void finishReader(ExecutionContext
* context
, FileReader
* reader
, FinishReaderType nextStep
)
116 InspectorInstrumentation::traceAsyncOperationCompleted(context
, reader
->m_asyncOperationId
);
118 ThrottlingController
* controller
= from(context
);
122 controller
->finishReader(reader
, nextStep
);
125 DEFINE_INLINE_TRACE()
128 visitor
->trace(m_pendingReaders
);
129 visitor
->trace(m_runningReaders
);
131 WillBeHeapSupplement
<ExecutionContext
>::trace(visitor
);
135 ThrottlingController()
136 : m_maxRunningReaders(kMaxOutstandingRequestsPerThread
)
140 void pushReader(FileReader
* reader
)
142 if (m_pendingReaders
.isEmpty()
143 && m_runningReaders
.size() < m_maxRunningReaders
) {
144 reader
->executePendingRead();
145 ASSERT(!m_runningReaders
.contains(reader
));
146 m_runningReaders
.add(reader
);
149 m_pendingReaders
.append(reader
);
153 FinishReaderType
removeReader(FileReader
* reader
)
155 FileReaderHashSet::const_iterator hashIter
= m_runningReaders
.find(reader
);
156 if (hashIter
!= m_runningReaders
.end()) {
157 m_runningReaders
.remove(hashIter
);
158 return RunPendingReaders
;
160 FileReaderDeque::const_iterator dequeEnd
= m_pendingReaders
.end();
161 for (FileReaderDeque::const_iterator it
= m_pendingReaders
.begin(); it
!= dequeEnd
; ++it
) {
163 m_pendingReaders
.remove(it
);
167 return DoNotRunPendingReaders
;
170 void finishReader(FileReader
* reader
, FinishReaderType nextStep
)
172 if (nextStep
== RunPendingReaders
)
176 void executeReaders()
178 while (m_runningReaders
.size() < m_maxRunningReaders
) {
179 if (m_pendingReaders
.isEmpty())
181 FileReader
* reader
= m_pendingReaders
.takeFirst();
182 reader
->executePendingRead();
183 m_runningReaders
.add(reader
);
187 static const char* supplementName() { return "FileReaderThrottlingController"; }
189 const size_t m_maxRunningReaders
;
191 using FileReaderDeque
= PersistentHeapDequeWillBeHeapDeque
<Member
<FileReader
>>;
192 using FileReaderHashSet
= PersistentHeapHashSetWillBeHeapHashSet
<Member
<FileReader
>>;
194 FileReaderDeque m_pendingReaders
;
195 FileReaderHashSet m_runningReaders
;
198 FileReader
* FileReader::create(ExecutionContext
* context
)
200 FileReader
* fileReader
= new FileReader(context
);
201 fileReader
->suspendIfNeeded();
205 FileReader::FileReader(ExecutionContext
* context
)
206 : ActiveDOMObject(context
)
208 , m_loadingState(LoadingStateNone
)
209 , m_readType(FileReaderLoader::ReadAsBinaryString
)
210 , m_lastProgressNotificationTimeMS(0)
211 , m_asyncOperationId(0)
215 FileReader::~FileReader()
220 const AtomicString
& FileReader::interfaceName() const
222 return EventTargetNames::FileReader
;
225 void FileReader::stop()
227 // The delayed abort task tidies up and advances to the DONE state.
228 if (m_loadingState
== LoadingStateAborted
)
231 if (hasPendingActivity())
232 ThrottlingController::finishReader(executionContext(), this, ThrottlingController::removeReader(executionContext(), this));
236 bool FileReader::hasPendingActivity() const
238 return m_state
== LOADING
;
241 void FileReader::readAsArrayBuffer(Blob
* blob
, ExceptionState
& exceptionState
)
244 WTF_LOG(FileAPI
, "FileReader: reading as array buffer: %s %s\n", utf8BlobUUID(blob
).data(), utf8FilePath(blob
).data());
246 readInternal(blob
, FileReaderLoader::ReadAsArrayBuffer
, exceptionState
);
249 void FileReader::readAsBinaryString(Blob
* blob
, ExceptionState
& exceptionState
)
252 WTF_LOG(FileAPI
, "FileReader: reading as binary: %s %s\n", utf8BlobUUID(blob
).data(), utf8FilePath(blob
).data());
254 readInternal(blob
, FileReaderLoader::ReadAsBinaryString
, exceptionState
);
257 void FileReader::readAsText(Blob
* blob
, const String
& encoding
, ExceptionState
& exceptionState
)
260 WTF_LOG(FileAPI
, "FileReader: reading as text: %s %s\n", utf8BlobUUID(blob
).data(), utf8FilePath(blob
).data());
262 m_encoding
= encoding
;
263 readInternal(blob
, FileReaderLoader::ReadAsText
, exceptionState
);
266 void FileReader::readAsText(Blob
* blob
, ExceptionState
& exceptionState
)
268 readAsText(blob
, String(), exceptionState
);
271 void FileReader::readAsDataURL(Blob
* blob
, ExceptionState
& exceptionState
)
274 WTF_LOG(FileAPI
, "FileReader: reading as data URL: %s %s\n", utf8BlobUUID(blob
).data(), utf8FilePath(blob
).data());
276 readInternal(blob
, FileReaderLoader::ReadAsDataURL
, exceptionState
);
279 void FileReader::readInternal(Blob
* blob
, FileReaderLoader::ReadType type
, ExceptionState
& exceptionState
)
281 // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
282 if (m_state
== LOADING
) {
283 exceptionState
.throwDOMException(InvalidStateError
, "The object is already busy reading Blobs.");
287 if (blob
->hasBeenClosed()) {
288 exceptionState
.throwDOMException(InvalidStateError
, String(blob
->isFile() ? "File" : "Blob") + " has been closed.");
292 ExecutionContext
* context
= executionContext();
294 exceptionState
.throwDOMException(AbortError
, "Reading from a detached FileReader is not supported.");
298 // A document loader will not load new resources once the Document has detached from its frame.
299 if (context
->isDocument() && !toDocument(context
)->frame()) {
300 exceptionState
.throwDOMException(AbortError
, "Reading from a Document-detached FileReader is not supported.");
304 // "Snapshot" the Blob data rather than the Blob itself as ongoing
305 // read operations should not be affected if close() is called on
306 // the Blob being read.
307 m_blobDataHandle
= blob
->blobDataHandle();
308 m_blobType
= blob
->type();
311 m_loadingState
= LoadingStatePending
;
313 ASSERT(ThrottlingController::from(context
));
314 ThrottlingController::pushReader(context
, this);
317 void FileReader::executePendingRead()
319 ASSERT(m_loadingState
== LoadingStatePending
);
320 m_loadingState
= LoadingStateLoading
;
322 m_loader
= FileReaderLoader::create(m_readType
, this);
323 m_loader
->setEncoding(m_encoding
);
324 m_loader
->setDataType(m_blobType
);
325 m_loader
->start(executionContext(), m_blobDataHandle
);
326 m_blobDataHandle
= nullptr;
329 static void delayedAbort(FileReader
* reader
)
334 void FileReader::abort()
336 WTF_LOG(FileAPI
, "FileReader: aborting\n");
338 if (m_loadingState
!= LoadingStateLoading
339 && m_loadingState
!= LoadingStatePending
) {
342 m_loadingState
= LoadingStateAborted
;
344 // Schedule to have the abort done later since abort() might be called from the event handler and we do not want the resource loading code to be in the stack.
345 executionContext()->postTask(
346 FROM_HERE
, createSameThreadTask(&delayedAbort
, this));
349 void FileReader::doAbort()
351 ASSERT(m_state
!= DONE
);
355 m_error
= FileError::create(FileError::ABORT_ERR
);
357 // Unregister the reader.
358 ThrottlingController::FinishReaderType finalStep
= ThrottlingController::removeReader(executionContext(), this);
360 fireEvent(EventTypeNames::error
);
361 fireEvent(EventTypeNames::abort
);
362 fireEvent(EventTypeNames::loadend
);
364 // All possible events have fired and we're done, no more pending activity.
365 ThrottlingController::finishReader(executionContext(), this, finalStep
);
368 void FileReader::result(StringOrArrayBuffer
& resultAttribute
) const
370 if (!m_loader
|| m_error
)
373 if (m_readType
== FileReaderLoader::ReadAsArrayBuffer
)
374 resultAttribute
.setArrayBuffer(m_loader
->arrayBufferResult());
376 resultAttribute
.setString(m_loader
->stringResult());
379 void FileReader::terminate()
386 m_loadingState
= LoadingStateNone
;
389 void FileReader::didStartLoading()
391 fireEvent(EventTypeNames::loadstart
);
394 void FileReader::didReceiveData()
396 // Fire the progress event at least every 50ms.
397 double now
= currentTimeMS();
398 if (!m_lastProgressNotificationTimeMS
) {
399 m_lastProgressNotificationTimeMS
= now
;
400 } else if (now
- m_lastProgressNotificationTimeMS
> progressNotificationIntervalMS
) {
401 fireEvent(EventTypeNames::progress
);
402 m_lastProgressNotificationTimeMS
= now
;
406 void FileReader::didFinishLoading()
408 if (m_loadingState
== LoadingStateAborted
)
410 ASSERT(m_loadingState
== LoadingStateLoading
);
412 // It's important that we change m_loadingState before firing any events
413 // since any of the events could call abort(), which internally checks
414 // if we're still loading (therefore we need abort process) or not.
415 m_loadingState
= LoadingStateNone
;
417 fireEvent(EventTypeNames::progress
);
419 ASSERT(m_state
!= DONE
);
422 // Unregister the reader.
423 ThrottlingController::FinishReaderType finalStep
= ThrottlingController::removeReader(executionContext(), this);
425 fireEvent(EventTypeNames::load
);
426 fireEvent(EventTypeNames::loadend
);
428 // All possible events have fired and we're done, no more pending activity.
429 ThrottlingController::finishReader(executionContext(), this, finalStep
);
432 void FileReader::didFail(FileError::ErrorCode errorCode
)
434 if (m_loadingState
== LoadingStateAborted
)
436 ASSERT(m_loadingState
== LoadingStateLoading
);
437 m_loadingState
= LoadingStateNone
;
439 ASSERT(m_state
!= DONE
);
442 m_error
= FileError::create(static_cast<FileError::ErrorCode
>(errorCode
));
444 // Unregister the reader.
445 ThrottlingController::FinishReaderType finalStep
= ThrottlingController::removeReader(executionContext(), this);
447 fireEvent(EventTypeNames::error
);
448 fireEvent(EventTypeNames::loadend
);
450 // All possible events have fired and we're done, no more pending activity.
451 ThrottlingController::finishReader(executionContext(), this, finalStep
);
454 void FileReader::fireEvent(const AtomicString
& type
)
456 InspectorInstrumentationCookie cookie
= InspectorInstrumentation::traceAsyncCallbackStarting(executionContext(), m_asyncOperationId
);
458 dispatchEvent(ProgressEvent::create(type
, false, 0, 0));
459 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie
);
463 if (m_loader
->totalBytes() >= 0)
464 dispatchEvent(ProgressEvent::create(type
, true, m_loader
->bytesLoaded(), m_loader
->totalBytes()));
466 dispatchEvent(ProgressEvent::create(type
, false, m_loader
->bytesLoaded(), 0));
468 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie
);
471 DEFINE_TRACE(FileReader
)
473 visitor
->trace(m_error
);
474 RefCountedGarbageCollectedEventTargetWithInlineData
<FileReader
>::trace(visitor
);
475 ActiveDOMObject::trace(visitor
);