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