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 typedef PersistentHeapDequeWillBeHeapDeque<Member<FileReader> > FileReaderDeque;
78 typedef PersistentHeapHashSetWillBeHeapHashSet<Member<FileReader> > FileReaderHashSet;
80 // FIXME: Oilpan: if ExecutionContext is moved to the heap, consider
81 // making this object an ExecutionContext supplement (only.)
82 class FileReader::ThrottlingController final : public NoBaseWillBeGarbageCollectedFinalized<FileReader::ThrottlingController>, public WillBeHeapSupplement<LocalFrame>, public WillBeHeapSupplement<WorkerClients> {
83 WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(FileReader::ThrottlingController);
85 static ThrottlingController* from(ExecutionContext* context)
90 if (context->isDocument()) {
91 Document* document = toDocument(context);
92 if (!document->frame())
95 ThrottlingController* controller = static_cast<ThrottlingController*>(WillBeHeapSupplement<LocalFrame>::from(document->frame(), supplementName()));
99 controller = new ThrottlingController();
100 WillBeHeapSupplement<LocalFrame>::provideTo(*document->frame(), supplementName(), adoptPtrWillBeNoop(controller));
103 ASSERT(!isMainThread());
104 ASSERT(context->isWorkerGlobalScope());
105 WorkerGlobalScope* workerGlobalScope = toWorkerGlobalScope(context);
106 ThrottlingController* controller = static_cast<ThrottlingController*>(WillBeHeapSupplement<WorkerClients>::from(workerGlobalScope->clients(), supplementName()));
110 controller = new ThrottlingController();
111 WillBeHeapSupplement<WorkerClients>::provideTo(*workerGlobalScope->clients(), supplementName(), adoptPtrWillBeNoop(controller));
115 ~ThrottlingController() { }
117 enum FinishReaderType { DoNotRunPendingReaders, RunPendingReaders };
119 static void pushReader(ExecutionContext* context, FileReader* reader)
121 ThrottlingController* controller = from(context);
125 reader->m_asyncOperationId = InspectorInstrumentation::traceAsyncOperationStarting(context, "FileReader");
126 controller->pushReader(reader);
129 static FinishReaderType removeReader(ExecutionContext* context, FileReader* reader)
131 ThrottlingController* controller = from(context);
133 return DoNotRunPendingReaders;
135 return controller->removeReader(reader);
138 static void finishReader(ExecutionContext* context, FileReader* reader, FinishReaderType nextStep)
140 InspectorInstrumentation::traceAsyncOperationCompleted(context, reader->m_asyncOperationId);
142 ThrottlingController* controller = from(context);
146 controller->finishReader(reader, nextStep);
149 void trace(Visitor* visitor)
152 visitor->trace(m_pendingReaders);
153 visitor->trace(m_runningReaders);
155 WillBeHeapSupplement<LocalFrame>::trace(visitor);
156 WillBeHeapSupplement<WorkerClients>::trace(visitor);
160 ThrottlingController()
161 : m_maxRunningReaders(kMaxOutstandingRequestsPerThread)
165 void pushReader(FileReader* reader)
167 if (m_pendingReaders.isEmpty()
168 && m_runningReaders.size() < m_maxRunningReaders) {
169 reader->executePendingRead();
170 ASSERT(!m_runningReaders.contains(reader));
171 m_runningReaders.add(reader);
174 m_pendingReaders.append(reader);
178 FinishReaderType removeReader(FileReader* reader)
180 FileReaderHashSet::const_iterator hashIter = m_runningReaders.find(reader);
181 if (hashIter != m_runningReaders.end()) {
182 m_runningReaders.remove(hashIter);
183 return RunPendingReaders;
185 FileReaderDeque::const_iterator dequeEnd = m_pendingReaders.end();
186 for (FileReaderDeque::const_iterator it = m_pendingReaders.begin(); it != dequeEnd; ++it) {
188 m_pendingReaders.remove(it);
192 return DoNotRunPendingReaders;
195 void finishReader(FileReader* reader, FinishReaderType nextStep)
197 if (nextStep == RunPendingReaders)
201 void executeReaders()
203 while (m_runningReaders.size() < m_maxRunningReaders) {
204 if (m_pendingReaders.isEmpty())
206 FileReader* reader = m_pendingReaders.takeFirst();
207 reader->executePendingRead();
208 m_runningReaders.add(reader);
212 static const char* supplementName() { return "FileReaderThrottlingController"; }
214 const size_t m_maxRunningReaders;
215 FileReaderDeque m_pendingReaders;
216 FileReaderHashSet m_runningReaders;
219 FileReader* FileReader::create(ExecutionContext* context)
221 FileReader* fileReader = new FileReader(context);
222 fileReader->suspendIfNeeded();
226 FileReader::FileReader(ExecutionContext* context)
227 : ActiveDOMObject(context)
229 , m_loadingState(LoadingStateNone)
230 , m_readType(FileReaderLoader::ReadAsBinaryString)
231 , m_lastProgressNotificationTimeMS(0)
232 , m_asyncOperationId(0)
236 FileReader::~FileReader()
241 const AtomicString& FileReader::interfaceName() const
243 return EventTargetNames::FileReader;
246 void FileReader::stop()
248 // The delayed abort task tidies up and advances to the DONE state.
249 if (m_loadingState == LoadingStateAborted)
252 if (hasPendingActivity())
253 ThrottlingController::finishReader(executionContext(), this, ThrottlingController::removeReader(executionContext(), this));
257 bool FileReader::hasPendingActivity() const
259 return m_state == LOADING;
262 void FileReader::readAsArrayBuffer(Blob* blob, ExceptionState& exceptionState)
265 WTF_LOG(FileAPI, "FileReader: reading as array buffer: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
267 readInternal(blob, FileReaderLoader::ReadAsArrayBuffer, exceptionState);
270 void FileReader::readAsBinaryString(Blob* blob, ExceptionState& exceptionState)
273 WTF_LOG(FileAPI, "FileReader: reading as binary: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
275 readInternal(blob, FileReaderLoader::ReadAsBinaryString, exceptionState);
278 void FileReader::readAsText(Blob* blob, const String& encoding, ExceptionState& exceptionState)
281 WTF_LOG(FileAPI, "FileReader: reading as text: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
283 m_encoding = encoding;
284 readInternal(blob, FileReaderLoader::ReadAsText, exceptionState);
287 void FileReader::readAsText(Blob* blob, ExceptionState& exceptionState)
289 readAsText(blob, String(), exceptionState);
292 void FileReader::readAsDataURL(Blob* blob, ExceptionState& exceptionState)
295 WTF_LOG(FileAPI, "FileReader: reading as data URL: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
297 readInternal(blob, FileReaderLoader::ReadAsDataURL, exceptionState);
300 void FileReader::readInternal(Blob* blob, FileReaderLoader::ReadType type, ExceptionState& exceptionState)
302 // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
303 if (m_state == LOADING) {
304 exceptionState.throwDOMException(InvalidStateError, "The object is already busy reading Blobs.");
308 if (blob->hasBeenClosed()) {
309 exceptionState.throwDOMException(InvalidStateError, String(blob->isFile() ? "File" : "Blob") + " has been closed.");
313 if (!ThrottlingController::from(executionContext())) {
314 exceptionState.throwDOMException(AbortError, "Reading from a Document-detached FileReader is not supported.");
318 // "Snapshot" the Blob data rather than the Blob itself as ongoing
319 // read operations should not be affected if close() is called on
320 // the Blob being read.
321 m_blobDataHandle = blob->blobDataHandle();
322 m_blobType = blob->type();
325 m_loadingState = LoadingStatePending;
327 ThrottlingController::pushReader(executionContext(), this);
330 void FileReader::executePendingRead()
332 ASSERT(m_loadingState == LoadingStatePending);
333 m_loadingState = LoadingStateLoading;
335 m_loader = adoptPtr(new FileReaderLoader(m_readType, this));
336 m_loader->setEncoding(m_encoding);
337 m_loader->setDataType(m_blobType);
338 m_loader->start(executionContext(), m_blobDataHandle);
339 m_blobDataHandle = nullptr;
342 static void delayedAbort(ExecutionContext*, FileReader* reader)
347 void FileReader::abort()
349 WTF_LOG(FileAPI, "FileReader: aborting\n");
351 if (m_loadingState != LoadingStateLoading
352 && m_loadingState != LoadingStatePending) {
355 m_loadingState = LoadingStateAborted;
357 // 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.
358 executionContext()->postTask(
359 createCrossThreadTask(&delayedAbort, AllowAccessLater(this)));
362 void FileReader::doAbort()
364 ASSERT(m_state != DONE);
368 m_error = FileError::create(FileError::ABORT_ERR);
370 // Unregister the reader.
371 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
373 fireEvent(EventTypeNames::error);
374 fireEvent(EventTypeNames::abort);
375 fireEvent(EventTypeNames::loadend);
377 // All possible events have fired and we're done, no more pending activity.
378 ThrottlingController::finishReader(executionContext(), this, finalStep);
381 void FileReader::terminate()
388 m_loadingState = LoadingStateNone;
391 void FileReader::didStartLoading()
393 fireEvent(EventTypeNames::loadstart);
396 void FileReader::didReceiveData()
398 // Fire the progress event at least every 50ms.
399 double now = currentTimeMS();
400 if (!m_lastProgressNotificationTimeMS)
401 m_lastProgressNotificationTimeMS = now;
402 else if (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS) {
403 fireEvent(EventTypeNames::progress);
404 m_lastProgressNotificationTimeMS = now;
408 void FileReader::didFinishLoading()
410 if (m_loadingState == LoadingStateAborted)
412 ASSERT(m_loadingState == LoadingStateLoading);
414 // It's important that we change m_loadingState before firing any events
415 // since any of the events could call abort(), which internally checks
416 // if we're still loading (therefore we need abort process) or not.
417 m_loadingState = LoadingStateNone;
419 fireEvent(EventTypeNames::progress);
421 ASSERT(m_state != DONE);
424 // Unregister the reader.
425 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
427 fireEvent(EventTypeNames::load);
428 fireEvent(EventTypeNames::loadend);
430 // All possible events have fired and we're done, no more pending activity.
431 ThrottlingController::finishReader(executionContext(), this, finalStep);
434 void FileReader::didFail(FileError::ErrorCode errorCode)
436 if (m_loadingState == LoadingStateAborted)
438 ASSERT(m_loadingState == LoadingStateLoading);
439 m_loadingState = LoadingStateNone;
441 ASSERT(m_state != DONE);
444 m_error = FileError::create(static_cast<FileError::ErrorCode>(errorCode));
446 // Unregister the reader.
447 ThrottlingController::FinishReaderType finalStep = ThrottlingController::removeReader(executionContext(), this);
449 fireEvent(EventTypeNames::error);
450 fireEvent(EventTypeNames::loadend);
452 // All possible events have fired and we're done, no more pending activity.
453 ThrottlingController::finishReader(executionContext(), this, finalStep);
456 void FileReader::fireEvent(const AtomicString& type)
458 InspectorInstrumentationCookie cookie = InspectorInstrumentation::traceAsyncCallbackStarting(executionContext(), m_asyncOperationId);
460 dispatchEvent(ProgressEvent::create(type, false, 0, 0));
461 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
465 if (m_loader->totalBytes() >= 0)
466 dispatchEvent(ProgressEvent::create(type, true, m_loader->bytesLoaded(), m_loader->totalBytes()));
468 dispatchEvent(ProgressEvent::create(type, false, m_loader->bytesLoaded(), 0));
470 InspectorInstrumentation::traceAsyncCallbackCompleted(cookie);
473 PassRefPtr<ArrayBuffer> FileReader::arrayBufferResult() const
475 if (!m_loader || m_error)
477 return m_loader->arrayBufferResult();
480 String FileReader::stringResult()
482 if (!m_loader || m_error)
484 return m_loader->stringResult();
487 void FileReader::trace(Visitor* visitor)
489 visitor->trace(m_error);
490 EventTargetWithInlineData::trace(visitor);