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 "modules/filesystem/FileWriter.h" 33 34 #include "bindings/v8/ExceptionState.h" 35 #include "core/dom/ExceptionCode.h" 36 #include "core/dom/ProgressEvent.h" 37 #include "core/fileapi/Blob.h" 38 #include "core/fileapi/FileError.h" 39 #include "modules/filesystem/AsyncFileWriter.h" 40 #include "wtf/CurrentTime.h" 41 42 namespace WebCore { 43 44 static const int kMaxRecursionDepth = 3; 45 static const double progressNotificationIntervalMS = 50; 46 47 PassRefPtr<FileWriter> FileWriter::create(ScriptExecutionContext* context) 48 { 49 RefPtr<FileWriter> fileWriter(adoptRef(new FileWriter(context))); 50 fileWriter->suspendIfNeeded(); 51 return fileWriter.release(); 52 } 53 54 FileWriter::FileWriter(ScriptExecutionContext* context) 55 : ActiveDOMObject(context) 56 , m_readyState(INIT) 57 , m_operationInProgress(OperationNone) 58 , m_queuedOperation(OperationNone) 59 , m_bytesWritten(0) 60 , m_bytesToWrite(0) 61 , m_truncateLength(-1) 62 , m_numAborts(0) 63 , m_recursionDepth(0) 64 , m_lastProgressNotificationTimeMS(0) 65 { 66 ScriptWrappable::init(this); 67 } 68 69 FileWriter::~FileWriter() 70 { 71 ASSERT(!m_recursionDepth); 72 if (m_readyState == WRITING) 73 stop(); 74 } 75 76 const AtomicString& FileWriter::interfaceName() const 77 { 78 return eventNames().interfaceForFileWriter; 79 } 80 81 bool FileWriter::canSuspend() const 82 { 83 // FIXME: It is not currently possible to suspend a FileWriter, so pages with FileWriter can not go into page cache. 84 return false; 85 } 86 87 void FileWriter::stop() 88 { 89 // Make sure we've actually got something to stop, and haven't already called abort(). 90 if (!writer() || m_readyState != WRITING) 91 return; 92 doOperation(OperationAbort); 93 m_readyState = DONE; 94 } 95 96 void FileWriter::write(Blob* data, ExceptionState& es) 97 { 98 ASSERT(writer()); 99 ASSERT(data); 100 ASSERT(m_truncateLength == -1); 101 if (m_readyState == WRITING) { 102 setError(FileError::INVALID_STATE_ERR, es); 103 return; 104 } 105 if (!data) { 106 setError(FileError::TYPE_MISMATCH_ERR, es); 107 return; 108 } 109 if (m_recursionDepth > kMaxRecursionDepth) { 110 setError(FileError::SECURITY_ERR, es); 111 return; 112 } 113 114 m_blobBeingWritten = data; 115 m_readyState = WRITING; 116 m_bytesWritten = 0; 117 m_bytesToWrite = data->size(); 118 ASSERT(m_queuedOperation == OperationNone); 119 if (m_operationInProgress != OperationNone) { 120 // We must be waiting for an abort to complete, since m_readyState wasn't WRITING. 121 ASSERT(m_operationInProgress == OperationAbort); 122 m_queuedOperation = OperationWrite; 123 } else 124 doOperation(OperationWrite); 125 126 fireEvent(eventNames().writestartEvent); 127 } 128 129 void FileWriter::seek(long long position, ExceptionState& es) 130 { 131 ASSERT(writer()); 132 if (m_readyState == WRITING) { 133 setError(FileError::INVALID_STATE_ERR, es); 134 return; 135 } 136 137 ASSERT(m_truncateLength == -1); 138 ASSERT(m_queuedOperation == OperationNone); 139 seekInternal(position); 140 } 141 142 void FileWriter::truncate(long long position, ExceptionState& es) 143 { 144 ASSERT(writer()); 145 ASSERT(m_truncateLength == -1); 146 if (m_readyState == WRITING || position < 0) { 147 setError(FileError::INVALID_STATE_ERR, es); 148 return; 149 } 150 if (m_recursionDepth > kMaxRecursionDepth) { 151 setError(FileError::SECURITY_ERR, es); 152 return; 153 } 154 155 m_readyState = WRITING; 156 m_bytesWritten = 0; 157 m_bytesToWrite = 0; 158 m_truncateLength = position; 159 ASSERT(m_queuedOperation == OperationNone); 160 if (m_operationInProgress != OperationNone) { 161 // We must be waiting for an abort to complete, since m_readyState wasn't WRITING. 162 ASSERT(m_operationInProgress == OperationAbort); 163 m_queuedOperation = OperationTruncate; 164 } else 165 doOperation(OperationTruncate); 166 fireEvent(eventNames().writestartEvent); 167 } 168 169 void FileWriter::abort(ExceptionState& es) 170 { 171 ASSERT(writer()); 172 if (m_readyState != WRITING) 173 return; 174 ++m_numAborts; 175 176 doOperation(OperationAbort); 177 signalCompletion(FileError::ABORT_ERR); 178 } 179 180 void FileWriter::didWrite(long long bytes, bool complete) 181 { 182 if (m_operationInProgress == OperationAbort) { 183 completeAbort(); 184 return; 185 } 186 ASSERT(m_readyState == WRITING); 187 ASSERT(m_truncateLength == -1); 188 ASSERT(m_operationInProgress == OperationWrite); 189 ASSERT(!m_bytesToWrite || bytes + m_bytesWritten > 0); 190 ASSERT(bytes + m_bytesWritten <= m_bytesToWrite); 191 m_bytesWritten += bytes; 192 ASSERT((m_bytesWritten == m_bytesToWrite) || !complete); 193 setPosition(position() + bytes); 194 if (position() > length()) 195 setLength(position()); 196 if (complete) { 197 m_blobBeingWritten.clear(); 198 m_operationInProgress = OperationNone; 199 } 200 201 int numAborts = m_numAborts; 202 // We could get an abort in the handler for this event. If we do, it's 203 // already handled the cleanup and signalCompletion call. 204 double now = currentTimeMS(); 205 if (complete || !m_lastProgressNotificationTimeMS || (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS)) { 206 m_lastProgressNotificationTimeMS = now; 207 fireEvent(eventNames().progressEvent); 208 } 209 210 if (complete) { 211 if (numAborts == m_numAborts) 212 signalCompletion(FileError::OK); 213 unsetPendingActivity(this); 214 } 215 } 216 217 void FileWriter::didTruncate() 218 { 219 if (m_operationInProgress == OperationAbort) { 220 completeAbort(); 221 return; 222 } 223 ASSERT(m_operationInProgress == OperationTruncate); 224 ASSERT(m_truncateLength >= 0); 225 setLength(m_truncateLength); 226 if (position() > length()) 227 setPosition(length()); 228 m_operationInProgress = OperationNone; 229 signalCompletion(FileError::OK); 230 unsetPendingActivity(this); 231 } 232 233 void FileWriter::didFail(FileError::ErrorCode code) 234 { 235 ASSERT(m_operationInProgress != OperationNone); 236 ASSERT(code != FileError::OK); 237 if (m_operationInProgress == OperationAbort) { 238 completeAbort(); 239 return; 240 } 241 ASSERT(code != FileError::ABORT_ERR); 242 ASSERT(m_queuedOperation == OperationNone); 243 ASSERT(m_readyState == WRITING); 244 m_blobBeingWritten.clear(); 245 m_operationInProgress = OperationNone; 246 signalCompletion(code); 247 unsetPendingActivity(this); 248 } 249 250 void FileWriter::completeAbort() 251 { 252 ASSERT(m_operationInProgress == OperationAbort); 253 m_operationInProgress = OperationNone; 254 Operation operation = m_queuedOperation; 255 m_queuedOperation = OperationNone; 256 doOperation(operation); 257 unsetPendingActivity(this); 258 } 259 260 void FileWriter::doOperation(Operation operation) 261 { 262 switch (operation) { 263 case OperationWrite: 264 ASSERT(m_operationInProgress == OperationNone); 265 ASSERT(m_truncateLength == -1); 266 ASSERT(m_blobBeingWritten.get()); 267 ASSERT(m_readyState == WRITING); 268 setPendingActivity(this); 269 writer()->write(position(), m_blobBeingWritten.get()); 270 break; 271 case OperationTruncate: 272 ASSERT(m_operationInProgress == OperationNone); 273 ASSERT(m_truncateLength >= 0); 274 ASSERT(m_readyState == WRITING); 275 setPendingActivity(this); 276 writer()->truncate(m_truncateLength); 277 break; 278 case OperationNone: 279 ASSERT(m_operationInProgress == OperationNone); 280 ASSERT(m_truncateLength == -1); 281 ASSERT(!m_blobBeingWritten.get()); 282 ASSERT(m_readyState == DONE); 283 break; 284 case OperationAbort: 285 if (m_operationInProgress == OperationWrite || m_operationInProgress == OperationTruncate) 286 writer()->abort(); 287 else if (m_operationInProgress != OperationAbort) 288 operation = OperationNone; 289 m_queuedOperation = OperationNone; 290 m_blobBeingWritten.clear(); 291 m_truncateLength = -1; 292 break; 293 } 294 ASSERT(m_queuedOperation == OperationNone); 295 m_operationInProgress = operation; 296 } 297 298 void FileWriter::signalCompletion(FileError::ErrorCode code) 299 { 300 m_readyState = DONE; 301 m_truncateLength = -1; 302 if (FileError::OK != code) { 303 m_error = FileError::create(code); 304 if (FileError::ABORT_ERR == code) 305 fireEvent(eventNames().abortEvent); 306 else 307 fireEvent(eventNames().errorEvent); 308 } else 309 fireEvent(eventNames().writeEvent); 310 fireEvent(eventNames().writeendEvent); 311 } 312 313 void FileWriter::fireEvent(const AtomicString& type) 314 { 315 ++m_recursionDepth; 316 dispatchEvent(ProgressEvent::create(type, true, m_bytesWritten, m_bytesToWrite)); 317 --m_recursionDepth; 318 ASSERT(m_recursionDepth >= 0); 319 } 320 321 void FileWriter::setError(FileError::ErrorCode errorCode, ExceptionState& es) 322 { 323 ASSERT(errorCode); 324 FileError::throwDOMException(es, errorCode); 325 m_error = FileError::create(errorCode); 326 } 327 328 } // namespace WebCore 329