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 "core/dom/CrossThreadTask.h"
36 #include "core/dom/Document.h"
37 #include "core/dom/ExceptionCode.h"
38 #include "core/dom/ExecutionContext.h"
39 #include "core/events/ProgressEvent.h"
40 #include "core/fileapi/File.h"
41 #include "core/frame/LocalFrame.h"
42 #include "core/inspector/InspectorInstrumentation.h"
43 #include "core/workers/WorkerClients.h"
44 #include "core/workers/WorkerGlobalScope.h"
45 #include "platform/Logging.h"
46 #include "platform/Supplementable.h"
47 #include "wtf/ArrayBuffer.h"
48 #include "wtf/CurrentTime.h"
49 #include "wtf/Deque.h"
50 #include "wtf/HashSet.h"
51 #include "wtf/text/CString.h"
58 const CString utf8BlobUUID(Blob* blob)
60 return blob->uuid().utf8();
63 const CString utf8FilePath(Blob* blob)
65 return blob->hasBackingFile() ? toFile(blob)->path().utf8() : "";
71 // Embedders like chromium limit the number of simultaneous requests to avoid
72 // excessive IPC congestion. We limit this to 100 per thread to throttle the
73 // requests (the value is arbitrarily chosen).
74 static const size_t kMaxOutstandingRequestsPerThread = 100;
75 static const double progressNotificationIntervalMS = 50;
77 // FIXME: Oilpan: if ExecutionContext is moved to the heap, consider
78 // making this object an ExecutionContext supplement (only.)
79 class FileReader::ThrottlingController FINAL : public NoBaseWillBeGarbageCollectedFinalized<FileReader::ThrottlingController>, public WillBeHeapSupplement<LocalFrame>, public WillBeHeapSupplement<WorkerClients> {
80 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(FileReader::ThrottlingController);
82 static ThrottlingController* from(ExecutionContext* context)
87 if (context->isDocument()) {
88 Document* document = toDocument(context);
89 if (!document->frame())
92 ThrottlingController* controller = static_cast<ThrottlingController*>(WillBeHeapSupplement<LocalFrame>::from(document->frame(), supplementName()));
96 controller = new ThrottlingController();
97 WillBeHeapSupplement<LocalFrame>::provideTo(*document->frame(), supplementName(), adoptPtrWillBeNoop(controller));
100 ASSERT(!isMainThread());
101 ASSERT(context->isWorkerGlobalScope());
102 WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
103 ThrottlingController* controller = static_cast<ThrottlingController*>(WillBeHeapSupplement<WorkerClients>::from(workerGlobalScope->clients(), supplementName()));
107 controller = new ThrottlingController();
108 WillBeHeapSupplement<WorkerClients>::provideTo(*workerGlobalScope->clients(), supplementName(), adoptPtrWillBeNoop(controller));
112 ~ThrottlingController() { }
114 enum FinishReaderType { DoNotRunPendingReaders, RunPendingReaders };
116 static void pushReader(ExecutionContext* context, FileReader* reader)
118 ThrottlingController* controller = from(context);
122 reader->m_asyncOperationId = InspectorInstrumentation::traceAsyncOperationStarting(context, "FileReader");
123 controller->pushReader(reader);
126 static FinishReaderType removeReader(ExecutionContext* context, FileReader* reader)
128 ThrottlingController* controller = from(context);
130 return DoNotRunPendingReaders;
132 return controller->removeReader(reader);
135 static void finishReader(ExecutionContext* context, FileReader* reader, FinishReaderType nextStep)
137 InspectorInstrumentation::traceAsyncOperationCompleted(context, reader->m_asyncOperationId);
139 ThrottlingController* controller = from(context);
143 controller->finishReader(reader, nextStep);
146 void trace(Visitor* visitor)
149 visitor->trace(m_pendingReaders);
150 visitor->trace(m_runningReaders);
152 WillBeHeapSupplement<LocalFrame>::trace(visitor);
153 WillBeHeapSupplement<WorkerClients>::trace(visitor);
157 ThrottlingController()
158 : m_maxRunningReaders(kMaxOutstandingRequestsPerThread)
162 void pushReader(FileReader* reader)
164 if (m_pendingReaders.isEmpty()
165 && m_runningReaders.size() < m_maxRunningReaders) {
166 reader->executePendingRead();
167 ASSERT(!m_runningReaders.contains(reader));
168 m_runningReaders.add(reader);
171 m_pendingReaders.append(reader);
175 FinishReaderType removeReader(FileReader* reader)
177 WillBeHeapHashSet<RawPtrWillBeMember<FileReader> >::const_iterator hashIter = m_runningReaders.find(reader);
178 if (hashIter != m_runningReaders.end()) {
179 m_runningReaders.remove(hashIter);
180 return RunPendingReaders;
182 WillBeHeapDeque<RawPtrWillBeMember<FileReader> >::const_iterator dequeEnd = m_pendingReaders.end();
183 for (WillBeHeapDeque<RawPtrWillBeMember<FileReader> >::const_iterator it = m_pendingReaders.begin(); it != dequeEnd; ++it) {
185 m_pendingReaders.remove(it);
189 return DoNotRunPendingReaders;
192 void finishReader(FileReader* reader, FinishReaderType nextStep)
194 if (nextStep == RunPendingReaders)
198 void executeReaders()
200 while (m_runningReaders.size() < m_maxRunningReaders) {
201 if (m_pendingReaders.isEmpty())
203 FileReader* reader = m_pendingReaders.takeFirst();
204 reader->executePendingRead();
205 m_runningReaders.add(reader);
209 static const char* supplementName() { return "FileReaderThrottlingController"; }
211 const size_t m_maxRunningReaders;
212 WillBeHeapDeque<RawPtrWillBeMember<FileReader> > m_pendingReaders;
213 WillBeHeapHashSet<RawPtrWillBeMember<FileReader> > m_runningReaders;
216 PassRefPtrWillBeRawPtr<FileReader> FileReader::create(ExecutionContext* context)
218 RefPtrWillBeRawPtr<FileReader> fileReader(adoptRefWillBeNoop(new FileReader(context)));
219 fileReader->suspendIfNeeded();
220 return fileReader.release();
223 FileReader::FileReader(ExecutionContext* context)
224 : ActiveDOMObject(context)
226 , m_loadingState(LoadingStateNone)
227 , m_readType(FileReaderLoader::ReadAsBinaryString)
228 , m_lastProgressNotificationTimeMS(0)
229 , m_asyncOperationId(0)
231 ScriptWrappable::init(this);
234 FileReader::~FileReader()
239 const AtomicString& FileReader::interfaceName() const
241 return EventTargetNames::FileReader;
244 void FileReader::stop()
246 if (hasPendingActivity())
247 ThrottlingController::finishReader(executionContext(), this, ThrottlingController::removeReader(executionContext(), this));
251 bool FileReader::hasPendingActivity() const
253 return m_state == LOADING;
256 void FileReader::readAsArrayBuffer(Blob* blob, ExceptionState& exceptionState)
259 exceptionState.throwTypeError("The argument is not a Blob.");
263 WTF_LOG(FileAPI, "FileReader: reading as array buffer: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
265 readInternal(blob, FileReaderLoader::ReadAsArrayBuffer, exceptionState);
268 void FileReader::readAsBinaryString(Blob* blob, ExceptionState& exceptionState)
271 exceptionState.throwTypeError("The argument is not a Blob.");
275 WTF_LOG(FileAPI, "FileReader: reading as binary: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
277 readInternal(blob, FileReaderLoader::ReadAsBinaryString, exceptionState);
280 void FileReader::readAsText(Blob* blob, const String& encoding, ExceptionState& exceptionState)
283 exceptionState.throwTypeError("The argument is not a Blob.");
287 WTF_LOG(FileAPI, "FileReader: reading as text: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
289 m_encoding = encoding;
290 readInternal(blob, FileReaderLoader::ReadAsText, exceptionState);
293 void FileReader::readAsText(Blob* blob, ExceptionState& exceptionState)
295 readAsText(blob, String(), exceptionState);
298 void FileReader::readAsDataURL(Blob* blob, ExceptionState& exceptionState)
301 exceptionState.throwTypeError("The argument is not a Blob.");
305 WTF_LOG(FileAPI, "FileReader: reading as data URL: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
307 readInternal(blob, FileReaderLoader::ReadAsDataURL, exceptionState);
310 void FileReader::readInternal(Blob* blob, FileReaderLoader::ReadType type, ExceptionState& exceptionState)
312 // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
313 if (m_state == LOADING) {
314 exceptionState.throwDOMException(InvalidStateError, "The object is already busy reading Blobs.");
318 if (blob->hasBeenClosed()) {
319 exceptionState.throwDOMException(InvalidStateError, String(blob->isFile() ? "File" : "Blob") + " has been closed.");
323 if (!ThrottlingController::from(executionContext())) {
324 exceptionState.throwDOMException(AbortError, "Reading from a Document-detached FileReader is not supported.");
328 // "Snapshot" the Blob data rather than the Blob itself as ongoing
329 // read operations should not be affected if close() is called on
330 // the Blob being read.
331 m_blobDataHandle = blob->blobDataHandle();
332 m_blobType = blob->type();
335 m_loadingState = LoadingStatePending;
337 ThrottlingController::pushReader(executionContext(), this);
340 void FileReader::executePendingRead()
342 ASSERT(m_loadingState == LoadingStatePending);
343 m_loadingState = LoadingStateLoading;
345 m_loader = adoptPtr(new FileReaderLoader(m_readType, this));
346 m_loader->setEncoding(m_encoding);
347 m_loader->setDataType(m_blobType);
348 m_loader->start(executionContext(), m_blobDataHandle);
349 m_blobDataHandle = nullptr;
352 static void delayedAbort(ExecutionContext*, FileReader* reader)
357 void FileReader::abort()
359 WTF_LOG(FileAPI, "FileReader: aborting\n");
361 if (m_loadingState != LoadingStateLoading
362 && m_loadingState != LoadingStatePending) {
365 m_loadingState = LoadingStateAborted;
367 // 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.
368 executionContext()->postTask(
369 createCrossThreadTask(&delayedAbort, AllowAccessLater(this)));
372 void FileReader::doAbort()
374 ASSERT(m_state != DONE);
378 m_error = FileError::create(FileError::ABORT_ERR);
380 // Unregister the reader.
381 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
383 fireEvent(EventTypeNames::error);
384 fireEvent(EventTypeNames::abort);
385 fireEvent(EventTypeNames::loadend);
387 // All possible events have fired and we're done, no more pending activity.
388 ThrottlingController::finishReader(executionContext(), this, finalStep);
391 void FileReader::terminate()
398 m_loadingState = LoadingStateNone;
401 void FileReader::didStartLoading()
403 fireEvent(EventTypeNames::loadstart);
406 void FileReader::didReceiveData()
408 // Fire the progress event at least every 50ms.
409 double now = currentTimeMS();
410 if (!m_lastProgressNotificationTimeMS)
411 m_lastProgressNotificationTimeMS = now;
412 else if (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS) {
413 fireEvent(EventTypeNames::progress);
414 m_lastProgressNotificationTimeMS = now;
418 void FileReader::didFinishLoading()
420 if (m_loadingState == LoadingStateAborted)
422 ASSERT(m_loadingState == LoadingStateLoading);
424 // It's important that we change m_loadingState before firing any events
425 // since any of the events could call abort(), which internally checks
426 // if we're still loading (therefore we need abort process) or not.
427 m_loadingState = LoadingStateNone;
429 fireEvent(EventTypeNames::progress);
431 ASSERT(m_state != DONE);
434 // Unregister the reader.
435 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
437 fireEvent(EventTypeNames::load);
438 fireEvent(EventTypeNames::loadend);
440 // All possible events have fired and we're done, no more pending activity.
441 ThrottlingController::finishReader(executionContext(), this, finalStep);
444 void FileReader::didFail(FileError::ErrorCode errorCode)
446 if (m_loadingState == LoadingStateAborted)
448 ASSERT(m_loadingState == LoadingStateLoading);
449 m_loadingState = LoadingStateNone;
451 ASSERT(m_state != DONE);
454 m_error = FileError::create(static_cast<FileError::ErrorCode>(errorCode));
456 // Unregister the reader.
457 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
459 fireEvent(EventTypeNames::error);
460 fireEvent(EventTypeNames::loadend);
462 // All possible events have fired and we're done, no more pending activity.
463 ThrottlingController::finishReader(executionContext(), this, finalStep);
466 void FileReader::fireEvent(const AtomicString& type)
468 InspectorInstrumentationCookie cookie = InspectorInstrumentation::traceAsyncCallbackStarting(executionContext(), m_asyncOperationId);
470 dispatchEvent(ProgressEvent::create(type, false, 0, 0));
471 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
475 if (m_loader->totalBytes() >= 0)
476 dispatchEvent(ProgressEvent::create(type, true, m_loader->bytesLoaded(), m_loader->totalBytes()));
478 dispatchEvent(ProgressEvent::create(type, false, m_loader->bytesLoaded(), 0));
480 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
483 PassRefPtr<ArrayBuffer> FileReader::arrayBufferResult() const
485 if (!m_loader || m_error)
487 return m_loader->arrayBufferResult();
490 String FileReader::stringResult()
492 if (!m_loader || m_error)
494 return m_loader->stringResult();
497 void FileReader::trace(Visitor* visitor)
499 visitor->trace(m_error);
500 EventTargetWithInlineData::trace(visitor);