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 "FileReader.h"
33
34#include "EventNames.h"
35#include "File.h"
36#include "Logging.h"
37#include "ProgressEvent.h"
38#include "ScriptExecutionContext.h"
39#include <JavaScriptCore/ArrayBuffer.h>
40#include <wtf/IsoMallocInlines.h>
41#include <wtf/text/CString.h>
42
43namespace WebCore {
44
45WTF_MAKE_ISO_ALLOCATED_IMPL(FileReader);
46
47// Fire the progress event at least every 50ms.
48static const auto progressNotificationInterval = 50_ms;
49
50Ref<FileReader> FileReader::create(ScriptExecutionContext& context)
51{
52 auto fileReader = adoptRef(*new FileReader(context));
53 fileReader->suspendIfNeeded();
54 return fileReader;
55}
56
57FileReader::FileReader(ScriptExecutionContext& context)
58 : ActiveDOMObject(&context)
59{
60}
61
62FileReader::~FileReader()
63{
64 if (m_loader)
65 m_loader->cancel();
66}
67
68bool FileReader::canSuspendForDocumentSuspension() const
69{
70 return !hasPendingActivity();
71}
72
73const char* FileReader::activeDOMObjectName() const
74{
75 return "FileReader";
76}
77
78void FileReader::stop()
79{
80 if (m_loader) {
81 m_loader->cancel();
82 m_loader = nullptr;
83 }
84 m_state = DONE;
85 m_loadingActivity = nullptr;
86}
87
88ExceptionOr<void> FileReader::readAsArrayBuffer(Blob* blob)
89{
90 if (!blob)
91 return { };
92
93 LOG(FileAPI, "FileReader: reading as array buffer: %s %s\n", blob->url().string().utf8().data(), is<File>(*blob) ? downcast<File>(*blob).path().utf8().data() : "");
94
95 return readInternal(*blob, FileReaderLoader::ReadAsArrayBuffer);
96}
97
98ExceptionOr<void> FileReader::readAsBinaryString(Blob* blob)
99{
100 if (!blob)
101 return { };
102
103 LOG(FileAPI, "FileReader: reading as binary: %s %s\n", blob->url().string().utf8().data(), is<File>(*blob) ? downcast<File>(*blob).path().utf8().data() : "");
104
105 return readInternal(*blob, FileReaderLoader::ReadAsBinaryString);
106}
107
108ExceptionOr<void> FileReader::readAsText(Blob* blob, const String& encoding)
109{
110 if (!blob)
111 return { };
112
113 LOG(FileAPI, "FileReader: reading as text: %s %s\n", blob->url().string().utf8().data(), is<File>(*blob) ? downcast<File>(*blob).path().utf8().data() : "");
114
115 m_encoding = encoding;
116 return readInternal(*blob, FileReaderLoader::ReadAsText);
117}
118
119ExceptionOr<void> FileReader::readAsDataURL(Blob* blob)
120{
121 if (!blob)
122 return { };
123
124 LOG(FileAPI, "FileReader: reading as data URL: %s %s\n", blob->url().string().utf8().data(), is<File>(*blob) ? downcast<File>(*blob).path().utf8().data() : "");
125
126 return readInternal(*blob, FileReaderLoader::ReadAsDataURL);
127}
128
129ExceptionOr<void> FileReader::readInternal(Blob& blob, FileReaderLoader::ReadType type)
130{
131 // If multiple concurrent read methods are called on the same FileReader, InvalidStateError should be thrown when the state is LOADING.
132 if (m_state == LOADING)
133 return Exception { InvalidStateError };
134
135 m_loadingActivity = makePendingActivity(*this);
136
137 m_blob = &blob;
138 m_readType = type;
139 m_state = LOADING;
140 m_error = nullptr;
141
142 m_loader = std::make_unique<FileReaderLoader>(m_readType, static_cast<FileReaderLoaderClient*>(this));
143 m_loader->setEncoding(m_encoding);
144 m_loader->setDataType(m_blob->type());
145 m_loader->start(scriptExecutionContext(), blob);
146
147 return { };
148}
149
150void FileReader::abort()
151{
152 LOG(FileAPI, "FileReader: aborting\n");
153
154 if (m_aborting || m_state != LOADING)
155 return;
156 m_aborting = true;
157
158 // 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.
159 scriptExecutionContext()->postTask([this, protectedThis = makeRef(*this)] (ScriptExecutionContext&) {
160 if (isContextStopped())
161 return;
162
163 ASSERT(m_state != DONE);
164
165 stop();
166 m_aborting = false;
167
168 m_error = FileError::create(FileError::ABORT_ERR);
169
170 fireEvent(eventNames().errorEvent);
171 fireEvent(eventNames().abortEvent);
172 fireEvent(eventNames().loadendEvent);
173 });
174}
175
176void FileReader::didStartLoading()
177{
178 fireEvent(eventNames().loadstartEvent);
179}
180
181void FileReader::didReceiveData()
182{
183 auto now = MonotonicTime::now();
184 if (std::isnan(m_lastProgressNotificationTime)) {
185 m_lastProgressNotificationTime = now;
186 return;
187 }
188 if (now - m_lastProgressNotificationTime > progressNotificationInterval) {
189 fireEvent(eventNames().progressEvent);
190 m_lastProgressNotificationTime = now;
191 }
192}
193
194void FileReader::didFinishLoading()
195{
196 if (m_aborting)
197 return;
198
199 ASSERT(m_state != DONE);
200 m_state = DONE;
201
202 fireEvent(eventNames().progressEvent);
203 fireEvent(eventNames().loadEvent);
204 fireEvent(eventNames().loadendEvent);
205
206 m_loadingActivity = nullptr;
207}
208
209void FileReader::didFail(int errorCode)
210{
211 // If we're aborting, do not proceed with normal error handling since it is covered in aborting code.
212 if (m_aborting)
213 return;
214
215 ASSERT(m_state != DONE);
216 m_state = DONE;
217
218 m_error = FileError::create(static_cast<FileError::ErrorCode>(errorCode));
219 fireEvent(eventNames().errorEvent);
220 fireEvent(eventNames().loadendEvent);
221
222 m_loadingActivity = nullptr;
223}
224
225void FileReader::fireEvent(const AtomicString& type)
226{
227 dispatchEvent(ProgressEvent::create(type, true, m_loader ? m_loader->bytesLoaded() : 0, m_loader ? m_loader->totalBytes() : 0));
228}
229
230Optional<Variant<String, RefPtr<JSC::ArrayBuffer>>> FileReader::result() const
231{
232 if (!m_loader || m_error)
233 return WTF::nullopt;
234 if (m_readType == FileReaderLoader::ReadAsArrayBuffer) {
235 auto result = m_loader->arrayBufferResult();
236 if (!result)
237 return WTF::nullopt;
238 return { result };
239 }
240 String result = m_loader->stringResult();
241 if (result.isNull())
242 return WTF::nullopt;
243 return { WTFMove(result) };
244}
245
246} // namespace WebCore
247