1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/dom/FileSystemTaskBase.h"
8
9 #include "nsNetCID.h"
10 #include "mozilla/dom/File.h"
11 #include "mozilla/dom/FileSystemBase.h"
12 #include "mozilla/dom/FileSystemRequestParent.h"
13 #include "mozilla/dom/FileSystemUtils.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/ipc/BlobParent.h"
16 #include "mozilla/ipc/BackgroundChild.h"
17 #include "mozilla/ipc/BackgroundParent.h"
18 #include "mozilla/ipc/PBackgroundChild.h"
19 #include "mozilla/Unused.h"
20 #include "nsProxyRelease.h"
21
22 namespace mozilla {
23 namespace dom {
24
25 namespace {
26
27 nsresult
FileSystemErrorFromNsError(const nsresult & aErrorValue)28 FileSystemErrorFromNsError(const nsresult& aErrorValue)
29 {
30 uint16_t module = NS_ERROR_GET_MODULE(aErrorValue);
31 if (module == NS_ERROR_MODULE_DOM_FILESYSTEM ||
32 module == NS_ERROR_MODULE_DOM_FILE ||
33 module == NS_ERROR_MODULE_DOM) {
34 return aErrorValue;
35 }
36
37 switch (aErrorValue) {
38 case NS_OK:
39 return NS_OK;
40
41 case NS_ERROR_FILE_INVALID_PATH:
42 case NS_ERROR_FILE_UNRECOGNIZED_PATH:
43 return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR;
44
45 case NS_ERROR_FILE_DESTINATION_NOT_DIR:
46 return NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR;
47
48 case NS_ERROR_FILE_ACCESS_DENIED:
49 case NS_ERROR_FILE_DIR_NOT_EMPTY:
50 return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR;
51
52 case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
53 case NS_ERROR_NOT_AVAILABLE:
54 return NS_ERROR_DOM_FILE_NOT_FOUND_ERR;
55
56 case NS_ERROR_FILE_ALREADY_EXISTS:
57 return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR;
58
59 case NS_ERROR_FILE_NOT_DIRECTORY:
60 return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
61
62 case NS_ERROR_UNEXPECTED:
63 default:
64 return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR;
65 }
66 }
67
68 nsresult
DispatchToIOThread(nsIRunnable * aRunnable)69 DispatchToIOThread(nsIRunnable* aRunnable)
70 {
71 MOZ_ASSERT(aRunnable);
72
73 nsCOMPtr<nsIEventTarget> target
74 = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
75 MOZ_ASSERT(target);
76
77 return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
78 }
79
80 // This runnable is used when an error value is set before doing any real
81 // operation on the I/O thread. In this case we skip all and we directly
82 // communicate the error.
83 class ErrorRunnable final : public CancelableRunnable
84 {
85 public:
ErrorRunnable(FileSystemTaskChildBase * aTask)86 explicit ErrorRunnable(FileSystemTaskChildBase* aTask)
87 : mTask(aTask)
88 {
89 MOZ_ASSERT(aTask);
90 }
91
92 NS_IMETHOD
Run()93 Run() override
94 {
95 MOZ_ASSERT(NS_IsMainThread());
96 MOZ_ASSERT(mTask->HasError());
97
98 mTask->HandlerCallback();
99 return NS_OK;
100 }
101
102 private:
103 RefPtr<FileSystemTaskChildBase> mTask;
104 };
105
106 } // anonymous namespace
107
NS_IMPL_ISUPPORTS(FileSystemTaskChildBase,nsIIPCBackgroundChildCreateCallback)108 NS_IMPL_ISUPPORTS(FileSystemTaskChildBase, nsIIPCBackgroundChildCreateCallback)
109
110 /**
111 * FileSystemTaskBase class
112 */
113
114 FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem)
115 : mErrorValue(NS_OK)
116 , mFileSystem(aFileSystem)
117 {
118 MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
119 aFileSystem->AssertIsOnOwningThread();
120 }
121
~FileSystemTaskChildBase()122 FileSystemTaskChildBase::~FileSystemTaskChildBase()
123 {
124 mFileSystem->AssertIsOnOwningThread();
125 }
126
127 FileSystemBase*
GetFileSystem() const128 FileSystemTaskChildBase::GetFileSystem() const
129 {
130 mFileSystem->AssertIsOnOwningThread();
131 return mFileSystem.get();
132 }
133
134 void
Start()135 FileSystemTaskChildBase::Start()
136 {
137 mFileSystem->AssertIsOnOwningThread();
138
139 mozilla::ipc::PBackgroundChild* actor =
140 mozilla::ipc::BackgroundChild::GetForCurrentThread();
141 if (actor) {
142 ActorCreated(actor);
143 } else {
144 if (NS_WARN_IF(
145 !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
146 MOZ_CRASH();
147 }
148 }
149 }
150
151 void
ActorFailed()152 FileSystemTaskChildBase::ActorFailed()
153 {
154 MOZ_CRASH("Failed to create a PBackgroundChild actor!");
155 }
156
157 void
ActorCreated(mozilla::ipc::PBackgroundChild * aActor)158 FileSystemTaskChildBase::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
159 {
160 if (HasError()) {
161 // In this case we don't want to use IPC at all.
162 RefPtr<ErrorRunnable> runnable = new ErrorRunnable(this);
163 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(runnable);
164 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
165 return;
166 }
167
168 if (mFileSystem->IsShutdown()) {
169 return;
170 }
171
172 nsAutoString serialization;
173 mFileSystem->SerializeDOMPath(serialization);
174
175 ErrorResult rv;
176 FileSystemParams params = GetRequestParams(serialization, rv);
177 if (NS_WARN_IF(rv.Failed())) {
178 rv.SuppressException();
179 return;
180 }
181
182 // Retain a reference so the task object isn't deleted without IPDL's
183 // knowledge. The reference will be released by
184 // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
185 NS_ADDREF_THIS();
186
187 mozilla::ipc::PBackgroundChild* actor =
188 mozilla::ipc::BackgroundChild::GetForCurrentThread();
189 MOZ_ASSERT(actor);
190
191 actor->SendPFileSystemRequestConstructor(this, params);
192 }
193
194 void
SetRequestResult(const FileSystemResponseValue & aValue)195 FileSystemTaskChildBase::SetRequestResult(const FileSystemResponseValue& aValue)
196 {
197 mFileSystem->AssertIsOnOwningThread();
198
199 if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) {
200 FileSystemErrorResponse r = aValue;
201 mErrorValue = r.error();
202 } else {
203 ErrorResult rv;
204 SetSuccessRequestResult(aValue, rv);
205 mErrorValue = rv.StealNSResult();
206 }
207 }
208
209 bool
Recv__delete__(const FileSystemResponseValue & aValue)210 FileSystemTaskChildBase::Recv__delete__(const FileSystemResponseValue& aValue)
211 {
212 mFileSystem->AssertIsOnOwningThread();
213
214 SetRequestResult(aValue);
215 HandlerCallback();
216 return true;
217 }
218
219 void
SetError(const nsresult & aErrorValue)220 FileSystemTaskChildBase::SetError(const nsresult& aErrorValue)
221 {
222 mErrorValue = FileSystemErrorFromNsError(aErrorValue);
223 }
224
225 /**
226 * FileSystemTaskParentBase class
227 */
228
FileSystemTaskParentBase(FileSystemBase * aFileSystem,const FileSystemParams & aParam,FileSystemRequestParent * aParent)229 FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem,
230 const FileSystemParams& aParam,
231 FileSystemRequestParent* aParent)
232 : mErrorValue(NS_OK)
233 , mFileSystem(aFileSystem)
234 , mRequestParent(aParent)
235 , mBackgroundEventTarget(NS_GetCurrentThread())
236 {
237 MOZ_ASSERT(XRE_IsParentProcess(),
238 "Only call from parent process!");
239 MOZ_ASSERT(aFileSystem, "aFileSystem should not be null.");
240 MOZ_ASSERT(aParent);
241 MOZ_ASSERT(mBackgroundEventTarget);
242 AssertIsOnBackgroundThread();
243 }
244
~FileSystemTaskParentBase()245 FileSystemTaskParentBase::~FileSystemTaskParentBase()
246 {
247 // This task can be released on different threads because we dispatch it (as
248 // runnable) to main-thread, I/O and then back to the PBackground thread.
249 NS_ProxyRelease(mBackgroundEventTarget, mFileSystem.forget());
250 NS_ProxyRelease(mBackgroundEventTarget, mRequestParent.forget());
251 }
252
253 void
Start()254 FileSystemTaskParentBase::Start()
255 {
256 AssertIsOnBackgroundThread();
257 mFileSystem->AssertIsOnOwningThread();
258
259 if (NeedToGoToMainThread()) {
260 DebugOnly<nsresult> rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
261 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToCurrentThread failed");
262 return;
263 }
264
265 DebugOnly<nsresult> rv = DispatchToIOThread(this);
266 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchToIOThread failed");
267 }
268
269 void
HandleResult()270 FileSystemTaskParentBase::HandleResult()
271 {
272 AssertIsOnBackgroundThread();
273 mFileSystem->AssertIsOnOwningThread();
274
275 if (mFileSystem->IsShutdown()) {
276 return;
277 }
278
279 MOZ_ASSERT(mRequestParent);
280 Unused << mRequestParent->Send__delete__(mRequestParent, GetRequestResult());
281 }
282
283 FileSystemResponseValue
GetRequestResult() const284 FileSystemTaskParentBase::GetRequestResult() const
285 {
286 AssertIsOnBackgroundThread();
287 mFileSystem->AssertIsOnOwningThread();
288
289 if (HasError()) {
290 return FileSystemErrorResponse(mErrorValue);
291 }
292
293 ErrorResult rv;
294 FileSystemResponseValue value = GetSuccessRequestResult(rv);
295 if (NS_WARN_IF(rv.Failed())) {
296 return FileSystemErrorResponse(rv.StealNSResult());
297 }
298
299 return value;
300 }
301
302 void
SetError(const nsresult & aErrorValue)303 FileSystemTaskParentBase::SetError(const nsresult& aErrorValue)
304 {
305 mErrorValue = FileSystemErrorFromNsError(aErrorValue);
306 }
307
308 bool
NeedToGoToMainThread() const309 FileSystemTaskParentBase::NeedToGoToMainThread() const
310 {
311 return mFileSystem->NeedToGoToMainThread();
312 }
313
314 nsresult
MainThreadWork()315 FileSystemTaskParentBase::MainThreadWork()
316 {
317 MOZ_ASSERT(NS_IsMainThread());
318 return mFileSystem->MainThreadWork();
319 }
320
321 NS_IMETHODIMP
Run()322 FileSystemTaskParentBase::Run()
323 {
324 // This method can run in 3 different threads. Here why:
325 // 1. if we are on the main-thread it's because the task must do something
326 // here. If no errors are returned we go the step 2.
327 // 2. We can be here directly if the task doesn't have nothing to do on the
328 // main-thread. We are are on the I/O thread and we call IOWork().
329 // 3. Both step 1 (in case of error) and step 2 end up here where return the
330 // value back to the PBackground thread.
331 if (NS_IsMainThread()) {
332 MOZ_ASSERT(NeedToGoToMainThread());
333
334 nsresult rv = MainThreadWork();
335 if (NS_WARN_IF(NS_FAILED(rv))) {
336 SetError(rv);
337
338 // Something when wrong. Let's go to the Background thread directly
339 // skipping the I/O thread step.
340 rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
341 if (NS_WARN_IF(NS_FAILED(rv))) {
342 return rv;
343 }
344 }
345
346 // Next step must happen on the I/O thread.
347 rv = DispatchToIOThread(this);
348 if (NS_WARN_IF(NS_FAILED(rv))) {
349 return rv;
350 }
351
352 return NS_OK;
353 }
354
355 // Run I/O thread tasks
356 if (!IsOnBackgroundThread()) {
357 nsresult rv = IOWork();
358 if (NS_WARN_IF(NS_FAILED(rv))) {
359 SetError(rv);
360 }
361
362 // Let's go back to PBackground thread to finish the work.
363 rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
364 if (NS_WARN_IF(NS_FAILED(rv))) {
365 return rv;
366 }
367
368 return NS_OK;
369 }
370
371 // If we are here, it's because the I/O work has been done and we have to
372 // handle the result back via IPC.
373 AssertIsOnBackgroundThread();
374 HandleResult();
375 return NS_OK;
376 }
377
378 } // namespace dom
379 } // namespace mozilla
380