Home | History | Annotate | Download | only in fileapi
      1 /*
      2  * Copyright (C) 2010 Google Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 #include "config.h"
     32 #include "core/fileapi/FileReader.h"
     33 
     34 #include "bindings/v8/ExceptionState.h"
     35 #include "core/dom/CrossThreadTask.h"
     36 #include "core/dom/ExceptionCode.h"
     37 #include "core/dom/ExecutionContext.h"
     38 #include "core/events/ProgressEvent.h"
     39 #include "core/fileapi/File.h"
     40 #include "platform/Logging.h"
     41 #include "wtf/ArrayBuffer.h"
     42 #include "wtf/CurrentTime.h"
     43 #include "wtf/Deque.h"
     44 #include "wtf/HashSet.h"
     45 #include "wtf/ThreadSpecific.h"
     46 #include "wtf/Threading.h"
     47 #include "wtf/text/CString.h"
     48 
     49 namespace WebCore {
     50 
     51 namespace {
     52 
     53 #if !LOG_DISABLED
     54 const CString utf8BlobUUID(Blob* blob)
     55 {
     56     return blob->uuid().utf8();
     57 }
     58 
     59 const CString utf8FilePath(Blob* blob)
     60 {
     61     return blob->hasBackingFile() ? toFile(blob)->path().utf8() : "";
     62 }
     63 #endif
     64 
     65 } // namespace
     66 
     67 // Embedders like chromium limit the number of simultaneous requests to avoid
     68 // excessive IPC congestion. We limit this to 100 per thread to throttle the
     69 // requests (the value is arbitrarily chosen).
     70 static const size_t kMaxOutstandingRequestsPerThread = 100;
     71 static const double progressNotificationIntervalMS = 50;
     72 
     73 class FileReader::ThrottlingController {
     74 public:
     75     ThrottlingController() : m_maxRunningReaders(kMaxOutstandingRequestsPerThread) { }
     76     ~ThrottlingController() { }
     77 
     78     enum FinishReaderType { DoNotRunPendingReaders, RunPendingReaders };
     79 
     80     void pushReader(FileReader* reader)
     81     {
     82         reader->setPendingActivity(reader);
     83         if (m_pendingReaders.isEmpty()
     84             && m_runningReaders.size() < m_maxRunningReaders) {
     85             reader->executePendingRead();
     86             ASSERT(!m_runningReaders.contains(reader));
     87             m_runningReaders.add(reader);
     88             return;
     89         }
     90         m_pendingReaders.append(reader);
     91         executeReaders();
     92     }
     93 
     94     FinishReaderType removeReader(FileReader* reader)
     95     {
     96         HashSet<FileReader*>::const_iterator hashIter = m_runningReaders.find(reader);
     97         if (hashIter != m_runningReaders.end()) {
     98             m_runningReaders.remove(hashIter);
     99             return RunPendingReaders;
    100         }
    101         Deque<FileReader*>::const_iterator dequeEnd = m_pendingReaders.end();
    102         for (Deque<FileReader*>::const_iterator it = m_pendingReaders.begin(); it != dequeEnd; ++it) {
    103             if (*it == reader) {
    104                 m_pendingReaders.remove(it);
    105                 break;
    106             }
    107         }
    108         return DoNotRunPendingReaders;
    109     }
    110 
    111     void finishReader(FileReader* reader, FinishReaderType nextStep)
    112     {
    113         reader->unsetPendingActivity(reader);
    114         if (nextStep == RunPendingReaders)
    115             executeReaders();
    116     }
    117 
    118 private:
    119     void executeReaders()
    120     {
    121         while (m_runningReaders.size() < m_maxRunningReaders) {
    122             if (m_pendingReaders.isEmpty())
    123                 return;
    124             FileReader* reader = m_pendingReaders.takeFirst();
    125             reader->executePendingRead();
    126             m_runningReaders.add(reader);
    127         }
    128     }
    129 
    130     const size_t m_maxRunningReaders;
    131     Deque<FileReader*> m_pendingReaders;
    132     HashSet<FileReader*> m_runningReaders;
    133 };
    134 
    135 PassRefPtrWillBeRawPtr<FileReader> FileReader::create(ExecutionContext* context)
    136 {
    137     RefPtrWillBeRawPtr<FileReader> fileReader(adoptRefWillBeRefCountedGarbageCollected(new FileReader(context)));
    138     fileReader->suspendIfNeeded();
    139     return fileReader.release();
    140 }
    141 
    142 FileReader::FileReader(ExecutionContext* context)
    143     : ActiveDOMObject(context)
    144     , m_state(EMPTY)
    145     , m_loadingState(LoadingStateNone)
    146     , m_readType(FileReaderLoader::ReadAsBinaryString)
    147     , m_lastProgressNotificationTimeMS(0)
    148 {
    149     ScriptWrappable::init(this);
    150 }
    151 
    152 FileReader::~FileReader()
    153 {
    154     terminate();
    155 }
    156 
    157 const AtomicString& FileReader::interfaceName() const
    158 {
    159     return EventTargetNames::FileReader;
    160 }
    161 
    162 void FileReader::stop()
    163 {
    164     if (m_loadingState == LoadingStateLoading || m_loadingState == LoadingStatePending)
    165         throttlingController()->finishReader(this, throttlingController()->removeReader(this));
    166     terminate();
    167 }
    168 
    169 void FileReader::readAsArrayBuffer(Blob* blob, ExceptionState& exceptionState)
    170 {
    171     if (!blob) {
    172         exceptionState.throwTypeError("The argument is not a Blob.");
    173         return;
    174     }
    175 
    176     WTF_LOG(FileAPI, "FileReader: reading as array buffer: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
    177 
    178     readInternal(blob, FileReaderLoader::ReadAsArrayBuffer, exceptionState);
    179 }
    180 
    181 void FileReader::readAsBinaryString(Blob* blob, ExceptionState& exceptionState)
    182 {
    183     if (!blob) {
    184         exceptionState.throwTypeError("The argument is not a Blob.");
    185         return;
    186     }
    187 
    188     WTF_LOG(FileAPI, "FileReader: reading as binary: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
    189 
    190     readInternal(blob, FileReaderLoader::ReadAsBinaryString, exceptionState);
    191 }
    192 
    193 void FileReader::readAsText(Blob* blob, const String& encoding, ExceptionState& exceptionState)
    194 {
    195     if (!blob) {
    196         exceptionState.throwTypeError("The argument is not a Blob.");
    197         return;
    198     }
    199 
    200     WTF_LOG(FileAPI, "FileReader: reading as text: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
    201 
    202     m_encoding = encoding;
    203     readInternal(blob, FileReaderLoader::ReadAsText, exceptionState);
    204 }
    205 
    206 void FileReader::readAsText(Blob* blob, ExceptionState& exceptionState)
    207 {
    208     readAsText(blob, String(), exceptionState);
    209 }
    210 
    211 void FileReader::readAsDataURL(Blob* blob, ExceptionState& exceptionState)
    212 {
    213     if (!blob) {
    214         exceptionState.throwTypeError("The argument is not a Blob.");
    215         return;
    216     }
    217 
    218     WTF_LOG(FileAPI, "FileReader: reading as data URL: %s %s\n", utf8BlobUUID(blob).data(), utf8FilePath(blob).data());
    219 
    220     readInternal(blob, FileReaderLoader::ReadAsDataURL, exceptionState);
    221 }
    222 
    223 void FileReader::readInternal(Blob* blob, FileReaderLoader::ReadType type, ExceptionState& exceptionState)
    224 {
    225     // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
    226     if (m_state == LOADING) {
    227         exceptionState.throwDOMException(InvalidStateError, "The object is already busy reading Blobs.");
    228         return;
    229     }
    230 
    231     if (blob->hasBeenClosed()) {
    232         exceptionState.throwDOMException(InvalidStateError, String(blob->isFile() ? "File" : "Blob") + " has been closed.");
    233         return;
    234     }
    235 
    236     // "Snapshot" the Blob data rather than the Blob itself as ongoing
    237     // read operations should not be affected if close() is called on
    238     // the Blob being read.
    239     m_blobDataHandle = blob->blobDataHandle();
    240     m_blobType = blob->type();
    241     m_readType = type;
    242     m_state = LOADING;
    243     m_loadingState = LoadingStatePending;
    244     m_error = nullptr;
    245     throttlingController()->pushReader(this);
    246 }
    247 
    248 void FileReader::executePendingRead()
    249 {
    250     ASSERT(m_loadingState == LoadingStatePending);
    251     m_loadingState = LoadingStateLoading;
    252 
    253     m_loader = adoptPtr(new FileReaderLoader(m_readType, this));
    254     m_loader->setEncoding(m_encoding);
    255     m_loader->setDataType(m_blobType);
    256     m_loader->start(executionContext(), m_blobDataHandle);
    257     m_blobDataHandle = nullptr;
    258 }
    259 
    260 static void delayedAbort(ExecutionContext*, FileReader* reader)
    261 {
    262     reader->doAbort();
    263 }
    264 
    265 void FileReader::abort()
    266 {
    267     WTF_LOG(FileAPI, "FileReader: aborting\n");
    268 
    269     if (m_loadingState != LoadingStateLoading
    270         && m_loadingState != LoadingStatePending) {
    271         return;
    272     }
    273     m_loadingState = LoadingStateAborted;
    274 
    275     // 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.
    276     executionContext()->postTask(
    277         createCallbackTask(&delayedAbort, AllowAccessLater(this)));
    278 }
    279 
    280 void FileReader::doAbort()
    281 {
    282     ASSERT(m_state != DONE);
    283 
    284     terminate();
    285 
    286     m_error = FileError::create(FileError::ABORT_ERR);
    287 
    288     // Unregister the reader.
    289     ThrottlingController::FinishReaderType finalStep = throttlingController()->removeReader(this);
    290 
    291     fireEvent(EventTypeNames::error);
    292     fireEvent(EventTypeNames::abort);
    293     fireEvent(EventTypeNames::loadend);
    294 
    295     // All possible events have fired and we're done, no more pending activity.
    296     throttlingController()->finishReader(this, finalStep);
    297 }
    298 
    299 void FileReader::terminate()
    300 {
    301     if (m_loader) {
    302         m_loader->cancel();
    303         m_loader = nullptr;
    304     }
    305     m_state = DONE;
    306     m_loadingState = LoadingStateNone;
    307 }
    308 
    309 void FileReader::didStartLoading()
    310 {
    311     fireEvent(EventTypeNames::loadstart);
    312 }
    313 
    314 void FileReader::didReceiveData()
    315 {
    316     // Fire the progress event at least every 50ms.
    317     double now = currentTimeMS();
    318     if (!m_lastProgressNotificationTimeMS)
    319         m_lastProgressNotificationTimeMS = now;
    320     else if (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS) {
    321         fireEvent(EventTypeNames::progress);
    322         m_lastProgressNotificationTimeMS = now;
    323     }
    324 }
    325 
    326 void FileReader::didFinishLoading()
    327 {
    328     if (m_loadingState == LoadingStateAborted)
    329         return;
    330     ASSERT(m_loadingState == LoadingStateLoading);
    331 
    332     // It's important that we change m_loadingState before firing any events
    333     // since any of the events could call abort(), which internally checks
    334     // if we're still loading (therefore we need abort process) or not.
    335     m_loadingState = LoadingStateNone;
    336 
    337     fireEvent(EventTypeNames::progress);
    338 
    339     ASSERT(m_state != DONE);
    340     m_state = DONE;
    341 
    342     // Unregister the reader.
    343     ThrottlingController::FinishReaderType finalStep = throttlingController()->removeReader(this);
    344 
    345     fireEvent(EventTypeNames::load);
    346     fireEvent(EventTypeNames::loadend);
    347 
    348     // All possible events have fired and we're done, no more pending activity.
    349     throttlingController()->finishReader(this, finalStep);
    350 }
    351 
    352 void FileReader::didFail(FileError::ErrorCode errorCode)
    353 {
    354     if (m_loadingState == LoadingStateAborted)
    355         return;
    356     ASSERT(m_loadingState == LoadingStateLoading);
    357     m_loadingState = LoadingStateNone;
    358 
    359     ASSERT(m_state != DONE);
    360     m_state = DONE;
    361 
    362     m_error = FileError::create(static_cast<FileError::ErrorCode>(errorCode));
    363 
    364     // Unregister the reader.
    365     ThrottlingController::FinishReaderType finalStep = throttlingController()->removeReader(this);
    366 
    367     fireEvent(EventTypeNames::error);
    368     fireEvent(EventTypeNames::loadend);
    369 
    370     // All possible events have fired and we're done, no more pending activity.
    371     throttlingController()->finishReader(this, finalStep);
    372 }
    373 
    374 void FileReader::fireEvent(const AtomicString& type)
    375 {
    376     if (!m_loader) {
    377         dispatchEvent(ProgressEvent::create(type, false, 0, 0));
    378         return;
    379     }
    380 
    381     if (m_loader->totalBytes() >= 0)
    382         dispatchEvent(ProgressEvent::create(type, true, m_loader->bytesLoaded(), m_loader->totalBytes()));
    383     else
    384         dispatchEvent(ProgressEvent::create(type, false, m_loader->bytesLoaded(), 0));
    385 }
    386 
    387 ThreadSpecific<FileReader::ThrottlingController>& FileReader::throttlingController()
    388 {
    389     AtomicallyInitializedStatic(ThreadSpecific<FileReader::ThrottlingController>*, controller = new ThreadSpecific<FileReader::ThrottlingController>);
    390     return *controller;
    391 }
    392 
    393 PassRefPtr<ArrayBuffer> FileReader::arrayBufferResult() const
    394 {
    395     if (!m_loader || m_error)
    396         return nullptr;
    397     return m_loader->arrayBufferResult();
    398 }
    399 
    400 String FileReader::stringResult()
    401 {
    402     if (!m_loader || m_error)
    403         return String();
    404     return m_loader->stringResult();
    405 }
    406 
    407 void FileReader::trace(Visitor* visitor)
    408 {
    409     visitor->trace(m_error);
    410     EventTargetWithInlineData::trace(visitor);
    411 }
    412 
    413 } // namespace WebCore
    414