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)
233 FileReader::~FileReader()
238 const AtomicString& FileReader::interfaceName() const
240 return EventTargetNames::FileReader;
243 void FileReader::stop()
245 // The delayed abort task tidies up and advances to the DONE state.
246 if (m_loadingState == LoadingStateAborted)
249 if (hasPendingActivity())
250 ThrottlingController::finishReader(executionContext(), this, ThrottlingController::removeReader(executionContext(), this));
254 bool FileReader::hasPendingActivity() const
256 return m_state == LOADING;
259 void FileReader::readAsArrayBuffer(Blob* blob, ExceptionState& exceptionState)
262 WTF_LOG(FileAPI, "FileReader: reading as array buffer: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
264 readInternal(blob, FileReaderLoader::ReadAsArrayBuffer, exceptionState);
267 void FileReader::readAsBinaryString(Blob* blob, ExceptionState& exceptionState)
270 WTF_LOG(FileAPI, "FileReader: reading as binary: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
272 readInternal(blob, FileReaderLoader::ReadAsBinaryString, exceptionState);
275 void FileReader::readAsText(Blob* blob, const String& encoding, ExceptionState& exceptionState)
278 WTF_LOG(FileAPI, "FileReader: reading as text: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
280 m_encoding = encoding;
281 readInternal(blob, FileReaderLoader::ReadAsText, exceptionState);
284 void FileReader::readAsText(Blob* blob, ExceptionState& exceptionState)
286 readAsText(blob, String(), exceptionState);
289 void FileReader::readAsDataURL(Blob* blob, ExceptionState& exceptionState)
292 WTF_LOG(FileAPI, "FileReader: reading as data URL: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
294 readInternal(blob, FileReaderLoader::ReadAsDataURL, exceptionState);
297 void FileReader::readInternal(Blob* blob, FileReaderLoader::ReadType type, ExceptionState& exceptionState)
299 // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
300 if (m_state == LOADING) {
301 exceptionState.throwDOMException(InvalidStateError, "The object is already busy reading Blobs.");
305 if (blob->hasBeenClosed()) {
306 exceptionState.throwDOMException(InvalidStateError, String(blob->isFile() ? "File" : "Blob") + " has been closed.");
310 if (!ThrottlingController::from(executionContext())) {
311 exceptionState.throwDOMException(AbortError, "Reading from a Document-detached FileReader is not supported.");
315 // "Snapshot" the Blob data rather than the Blob itself as ongoing
316 // read operations should not be affected if close() is called on
317 // the Blob being read.
318 m_blobDataHandle = blob->blobDataHandle();
319 m_blobType = blob->type();
322 m_loadingState = LoadingStatePending;
324 ThrottlingController::pushReader(executionContext(), this);
327 void FileReader::executePendingRead()
329 ASSERT(m_loadingState == LoadingStatePending);
330 m_loadingState = LoadingStateLoading;
332 m_loader = adoptPtr(new FileReaderLoader(m_readType, this));
333 m_loader->setEncoding(m_encoding);
334 m_loader->setDataType(m_blobType);
335 m_loader->start(executionContext(), m_blobDataHandle);
336 m_blobDataHandle = nullptr;
339 static void delayedAbort(ExecutionContext*, FileReader* reader)
344 void FileReader::abort()
346 WTF_LOG(FileAPI, "FileReader: aborting\n");
348 if (m_loadingState != LoadingStateLoading
349 && m_loadingState != LoadingStatePending) {
352 m_loadingState = LoadingStateAborted;
354 // 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.
355 executionContext()->postTask(
356 createCrossThreadTask(&delayedAbort, AllowAccessLater(this)));
359 void FileReader::doAbort()
361 ASSERT(m_state != DONE);
365 m_error = FileError::create(FileError::ABORT_ERR);
367 // Unregister the reader.
368 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
370 fireEvent(EventTypeNames::error);
371 fireEvent(EventTypeNames::abort);
372 fireEvent(EventTypeNames::loadend);
374 // All possible events have fired and we're done, no more pending activity.
375 ThrottlingController::finishReader(executionContext(), this, finalStep);
378 void FileReader::terminate()
385 m_loadingState = LoadingStateNone;
388 void FileReader::didStartLoading()
390 fireEvent(EventTypeNames::loadstart);
393 void FileReader::didReceiveData()
395 // Fire the progress event at least every 50ms.
396 double now = currentTimeMS();
397 if (!m_lastProgressNotificationTimeMS)
398 m_lastProgressNotificationTimeMS = now;
399 else if (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS) {
400 fireEvent(EventTypeNames::progress);
401 m_lastProgressNotificationTimeMS = now;
405 void FileReader::didFinishLoading()
407 if (m_loadingState == LoadingStateAborted)
409 ASSERT(m_loadingState == LoadingStateLoading);
411 // It's important that we change m_loadingState before firing any events
412 // since any of the events could call abort(), which internally checks
413 // if we're still loading (therefore we need abort process) or not.
414 m_loadingState = LoadingStateNone;
416 fireEvent(EventTypeNames::progress);
418 ASSERT(m_state != DONE);
421 // Unregister the reader.
422 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
424 fireEvent(EventTypeNames::load);
425 fireEvent(EventTypeNames::loadend);
427 // All possible events have fired and we're done, no more pending activity.
428 ThrottlingController::finishReader(executionContext(), this, finalStep);
431 void FileReader::didFail(FileError::ErrorCode errorCode)
433 if (m_loadingState == LoadingStateAborted)
435 ASSERT(m_loadingState == LoadingStateLoading);
436 m_loadingState = LoadingStateNone;
438 ASSERT(m_state != DONE);
441 m_error = FileError::create(static_cast<FileError::ErrorCode>(errorCode));
443 // Unregister the reader.
444 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
446 fireEvent(EventTypeNames::error);
447 fireEvent(EventTypeNames::loadend);
449 // All possible events have fired and we're done, no more pending activity.
450 ThrottlingController::finishReader(executionContext(), this, finalStep);
453 void FileReader::fireEvent(const AtomicString& type)
455 InspectorInstrumentationCookie cookie = InspectorInstrumentation::traceAsyncCallbackStarting(executionContext(), m_asyncOperationId);
457 dispatchEvent(ProgressEvent::create(type, false, 0, 0));
458 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
462 if (m_loader->totalBytes() >= 0)
463 dispatchEvent(ProgressEvent::create(type, true, m_loader->bytesLoaded(), m_loader->totalBytes()));
465 dispatchEvent(ProgressEvent::create(type, false, m_loader->bytesLoaded(), 0));
467 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
470 PassRefPtr<ArrayBuffer> FileReader::arrayBufferResult() const
472 if (!m_loader || m_error)
474 return m_loader->arrayBufferResult();
477 String FileReader::stringResult()
479 if (!m_loader || m_error)
481 return m_loader->stringResult();
484 void FileReader::trace(Visitor* visitor)
486 visitor->trace(m_error);
487 EventTargetWithInlineData::trace(visitor);