Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/filesystem/GetFilesHelper.cpp
Line
Count
Source (jump to first uncovered line)
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
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "GetFilesHelper.h"
8
#include "mozilla/dom/ContentChild.h"
9
#include "mozilla/dom/ContentParent.h"
10
#include "mozilla/dom/FileBlobImpl.h"
11
#include "mozilla/dom/IPCBlobUtils.h"
12
#include "mozilla/ipc/IPCStreamUtils.h"
13
#include "FileSystemUtils.h"
14
#include "nsProxyRelease.h"
15
16
namespace mozilla {
17
namespace dom {
18
19
namespace {
20
21
// This class is used in the DTOR of GetFilesHelper to release resources in the
22
// correct thread.
23
class ReleaseRunnable final : public Runnable
24
{
25
public:
26
  static void
27
  MaybeReleaseOnMainThread(nsTArray<RefPtr<Promise>>& aPromises,
28
                           nsTArray<RefPtr<GetFilesCallback>>& aCallbacks,
29
                           Sequence<RefPtr<File>>& aFiles,
30
                           already_AddRefed<nsIGlobalObject> aGlobal)
31
0
  {
32
0
    nsCOMPtr<nsIGlobalObject> global(aGlobal);
33
0
    if (NS_IsMainThread()) {
34
0
      return;
35
0
    }
36
0
37
0
    RefPtr<ReleaseRunnable> runnable =
38
0
      new ReleaseRunnable(aPromises, aCallbacks, aFiles, global.forget());
39
0
    FileSystemUtils::DispatchRunnable(nullptr, runnable.forget());
40
0
  }
41
42
  NS_IMETHOD
43
  Run() override
44
0
  {
45
0
    MOZ_ASSERT(NS_IsMainThread());
46
0
47
0
    mPromises.Clear();
48
0
    mCallbacks.Clear();
49
0
    mFiles.Clear();
50
0
    mGlobal = nullptr;
51
0
52
0
    return NS_OK;
53
0
  }
54
55
private:
56
  ReleaseRunnable(nsTArray<RefPtr<Promise>>& aPromises,
57
                  nsTArray<RefPtr<GetFilesCallback>>& aCallbacks,
58
                  Sequence<RefPtr<File>>& aFiles,
59
                  already_AddRefed<nsIGlobalObject> aGlobal)
60
    : Runnable("dom::ReleaseRunnable")
61
0
  {
62
0
    mPromises.SwapElements(aPromises);
63
0
    mCallbacks.SwapElements(aCallbacks);
64
0
    mFiles.SwapElements(aFiles);
65
0
    mGlobal = aGlobal;
66
0
  }
67
68
  nsTArray<RefPtr<Promise>> mPromises;
69
  nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
70
  Sequence<RefPtr<File>> mFiles;
71
  nsCOMPtr<nsIGlobalObject> mGlobal;
72
};
73
74
} // anonymous
75
76
///////////////////////////////////////////////////////////////////////////////
77
// GetFilesHelper Base class
78
79
already_AddRefed<GetFilesHelper>
80
GetFilesHelper::Create(nsIGlobalObject* aGlobal,
81
                       const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
82
                       bool aRecursiveFlag, ErrorResult& aRv)
83
0
{
84
0
  RefPtr<GetFilesHelper> helper;
85
0
86
0
  if (XRE_IsParentProcess()) {
87
0
    helper = new GetFilesHelper(aGlobal, aRecursiveFlag);
88
0
  } else {
89
0
    helper = new GetFilesHelperChild(aGlobal, aRecursiveFlag);
90
0
  }
91
0
92
0
  nsAutoString directoryPath;
93
0
94
0
  for (uint32_t i = 0; i < aFilesOrDirectory.Length(); ++i) {
95
0
    const OwningFileOrDirectory& data = aFilesOrDirectory[i];
96
0
    if (data.IsFile()) {
97
0
      if (!helper->mFiles.AppendElement(data.GetAsFile(), fallible)) {
98
0
        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
99
0
        return nullptr;
100
0
      }
101
0
    } else {
102
0
      MOZ_ASSERT(data.IsDirectory());
103
0
104
0
      // We support the upload of only 1 top-level directory from our
105
0
      // directory picker. This means that we cannot have more than 1
106
0
      // Directory object in aFilesOrDirectory array.
107
0
      MOZ_ASSERT(directoryPath.IsEmpty());
108
0
109
0
      RefPtr<Directory> directory = data.GetAsDirectory();
110
0
      MOZ_ASSERT(directory);
111
0
112
0
      aRv = directory->GetFullRealPath(directoryPath);
113
0
      if (NS_WARN_IF(aRv.Failed())) {
114
0
        return nullptr;
115
0
      }
116
0
    }
117
0
  }
118
0
119
0
  // No directories to explore.
120
0
  if (directoryPath.IsEmpty()) {
121
0
    helper->mListingCompleted = true;
122
0
    return helper.forget();
123
0
  }
124
0
125
0
  MOZ_ASSERT(helper->mFiles.IsEmpty());
126
0
  helper->SetDirectoryPath(directoryPath);
127
0
128
0
  helper->Work(aRv);
129
0
  if (NS_WARN_IF(aRv.Failed())) {
130
0
    return nullptr;
131
0
  }
132
0
133
0
  return helper.forget();
134
0
}
135
136
GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
137
  : Runnable("GetFilesHelper")
138
  , GetFilesHelperBase(aRecursiveFlag)
139
  , mGlobal(aGlobal)
140
  , mListingCompleted(false)
141
  , mErrorResult(NS_OK)
142
  , mMutex("GetFilesHelper::mMutex")
143
  , mCanceled(false)
144
0
{
145
0
}
146
147
GetFilesHelper::~GetFilesHelper()
148
0
{
149
0
  ReleaseRunnable::MaybeReleaseOnMainThread(mPromises, mCallbacks, mFiles,
150
0
                                            mGlobal.forget());
151
0
}
152
153
void
154
GetFilesHelper::AddPromise(Promise* aPromise)
155
0
{
156
0
  MOZ_ASSERT(aPromise);
157
0
158
0
  // Still working.
159
0
  if (!mListingCompleted) {
160
0
    mPromises.AppendElement(aPromise);
161
0
    return;
162
0
  }
163
0
164
0
  MOZ_ASSERT(mPromises.IsEmpty());
165
0
  ResolveOrRejectPromise(aPromise);
166
0
}
167
168
void
169
GetFilesHelper::AddCallback(GetFilesCallback* aCallback)
170
0
{
171
0
  MOZ_ASSERT(aCallback);
172
0
173
0
  // Still working.
174
0
  if (!mListingCompleted) {
175
0
    mCallbacks.AppendElement(aCallback);
176
0
    return;
177
0
  }
178
0
179
0
  MOZ_ASSERT(mCallbacks.IsEmpty());
180
0
  RunCallback(aCallback);
181
0
}
182
183
void
184
GetFilesHelper::Unlink()
185
0
{
186
0
  mGlobal = nullptr;
187
0
  mFiles.Clear();
188
0
  mPromises.Clear();
189
0
  mCallbacks.Clear();
190
0
191
0
  {
192
0
    MutexAutoLock lock(mMutex);
193
0
    mCanceled = true;
194
0
  }
195
0
196
0
  Cancel();
197
0
}
198
199
void
200
GetFilesHelper::Traverse(nsCycleCollectionTraversalCallback &cb)
201
0
{
202
0
  GetFilesHelper* tmp = this;
203
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal);
204
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles);
205
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromises);
206
0
}
207
208
void
209
GetFilesHelper::Work(ErrorResult& aRv)
210
0
{
211
0
  nsCOMPtr<nsIEventTarget> target =
212
0
    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
213
0
  MOZ_ASSERT(target);
214
0
215
0
  aRv = target->Dispatch(this, NS_DISPATCH_NORMAL);
216
0
}
217
218
NS_IMETHODIMP
219
GetFilesHelper::Run()
220
0
{
221
0
  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
222
0
  MOZ_ASSERT(!mListingCompleted);
223
0
224
0
  // First step is to retrieve the list of file paths.
225
0
  // This happens in the I/O thread.
226
0
  if (!NS_IsMainThread()) {
227
0
    RunIO();
228
0
229
0
    // If this operation has been canceled, we don't have to go back to
230
0
    // main-thread.
231
0
    if (IsCanceled()) {
232
0
      return NS_OK;
233
0
    }
234
0
235
0
    RefPtr<Runnable> runnable = this;
236
0
    return FileSystemUtils::DispatchRunnable(nullptr, runnable.forget());
237
0
  }
238
0
239
0
  // We are here, but we should not do anything on this thread because, in the
240
0
  // meantime, the operation has been canceled.
241
0
  if (IsCanceled()) {
242
0
    return NS_OK;
243
0
  }
244
0
245
0
  RunMainThread();
246
0
247
0
  OperationCompleted();
248
0
  return NS_OK;
249
0
}
250
251
void
252
GetFilesHelper::OperationCompleted()
253
0
{
254
0
  // We mark the operation as completed here.
255
0
  mListingCompleted = true;
256
0
257
0
  // Let's process the pending promises.
258
0
  nsTArray<RefPtr<Promise>> promises;
259
0
  promises.SwapElements(mPromises);
260
0
261
0
  for (uint32_t i = 0; i < promises.Length(); ++i) {
262
0
    ResolveOrRejectPromise(promises[i]);
263
0
  }
264
0
265
0
  // Let's process the pending callbacks.
266
0
  nsTArray<RefPtr<GetFilesCallback>> callbacks;
267
0
  callbacks.SwapElements(mCallbacks);
268
0
269
0
  for (uint32_t i = 0; i < callbacks.Length(); ++i) {
270
0
    RunCallback(callbacks[i]);
271
0
  }
272
0
}
273
274
void
275
GetFilesHelper::RunIO()
276
0
{
277
0
  MOZ_ASSERT(!NS_IsMainThread());
278
0
  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
279
0
  MOZ_ASSERT(!mListingCompleted);
280
0
281
0
  nsCOMPtr<nsIFile> file;
282
0
  mErrorResult = NS_NewLocalFile(mDirectoryPath, true, getter_AddRefs(file));
283
0
  if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
284
0
    return;
285
0
  }
286
0
287
0
  nsAutoString leafName;
288
0
  mErrorResult = file->GetLeafName(leafName);
289
0
  if (NS_WARN_IF(NS_FAILED(mErrorResult))) {
290
0
    return;
291
0
  }
292
0
293
0
  nsAutoString domPath;
294
0
  domPath.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
295
0
  domPath.Append(leafName);
296
0
297
0
  mErrorResult = ExploreDirectory(domPath, file);
298
0
}
299
300
void
301
GetFilesHelper::RunMainThread()
302
0
{
303
0
  MOZ_ASSERT(NS_IsMainThread());
304
0
  MOZ_ASSERT(!mDirectoryPath.IsEmpty());
305
0
  MOZ_ASSERT(!mListingCompleted);
306
0
307
0
  // If there is an error, do nothing.
308
0
  if (NS_FAILED(mErrorResult)) {
309
0
    return;
310
0
  }
311
0
312
0
  // Create the sequence of Files.
313
0
  for (uint32_t i = 0; i < mTargetBlobImplArray.Length(); ++i) {
314
0
    RefPtr<File> domFile = File::Create(mGlobal, mTargetBlobImplArray[i]);
315
0
    MOZ_ASSERT(domFile);
316
0
317
0
    if (!mFiles.AppendElement(domFile, fallible)) {
318
0
      mErrorResult = NS_ERROR_OUT_OF_MEMORY;
319
0
      mFiles.Clear();
320
0
      return;
321
0
    }
322
0
  }
323
0
}
324
325
nsresult
326
GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
327
0
{
328
0
  MOZ_ASSERT(!NS_IsMainThread());
329
0
  MOZ_ASSERT(aFile);
330
0
331
0
  // We check if this operation has to be terminated at each recursion.
332
0
  if (IsCanceled()) {
333
0
    return NS_OK;
334
0
  }
335
0
336
0
  nsresult rv = AddExploredDirectory(aFile);
337
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
338
0
    return rv;
339
0
  }
340
0
341
0
  nsCOMPtr<nsIDirectoryEnumerator> entries;
342
0
  rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
343
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
344
0
    return rv;
345
0
  }
346
0
347
0
  for (;;) {
348
0
    nsCOMPtr<nsIFile> currFile;
349
0
    if (NS_WARN_IF(NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile)))) || !currFile) {
350
0
      break;
351
0
    }
352
0
    bool isLink, isSpecial, isFile, isDir;
353
0
    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
354
0
                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
355
0
        isSpecial) {
356
0
      continue;
357
0
    }
358
0
359
0
    if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
360
0
                   NS_FAILED(currFile->IsDirectory(&isDir))) ||
361
0
        !(isFile || isDir)) {
362
0
      continue;
363
0
    }
364
0
365
0
    // We don't want to explore loops of links.
366
0
    if (isDir && isLink && !ShouldFollowSymLink(currFile)) {
367
0
      continue;
368
0
    }
369
0
370
0
    // The new domPath
371
0
    nsAutoString domPath;
372
0
    domPath.Assign(aDOMPath);
373
0
    if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
374
0
      domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
375
0
    }
376
0
377
0
    nsAutoString leafName;
378
0
    if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
379
0
      continue;
380
0
    }
381
0
    domPath.Append(leafName);
382
0
383
0
    if (isFile) {
384
0
      RefPtr<BlobImpl> blobImpl = new FileBlobImpl(currFile);
385
0
      blobImpl->SetDOMPath(domPath);
386
0
387
0
      if (!mTargetBlobImplArray.AppendElement(blobImpl, fallible)) {
388
0
        return NS_ERROR_OUT_OF_MEMORY;
389
0
      }
390
0
391
0
      continue;
392
0
    }
393
0
394
0
    MOZ_ASSERT(isDir);
395
0
    if (!mRecursiveFlag) {
396
0
      continue;
397
0
    }
398
0
399
0
    // Recursive.
400
0
    rv = ExploreDirectory(domPath, currFile);
401
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
402
0
      return rv;
403
0
    }
404
0
  }
405
0
406
0
  return NS_OK;
407
0
}
408
409
nsresult
410
GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir)
411
0
{
412
0
  nsresult rv;
413
0
414
#ifdef DEBUG
415
  bool isDir;
416
  rv = aDir->IsDirectory(&isDir);
417
  if (NS_WARN_IF(NS_FAILED(rv))) {
418
    return rv;
419
  }
420
421
  MOZ_ASSERT(isDir, "Why are we here?");
422
#endif
423
424
0
  bool isLink;
425
0
  rv = aDir->IsSymlink(&isLink);
426
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
427
0
    return rv;
428
0
  }
429
0
430
0
  nsAutoString path;
431
0
  if (!isLink) {
432
0
    rv = aDir->GetPath(path);
433
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
434
0
      return rv;
435
0
    }
436
0
  } else {
437
0
    rv = aDir->GetTarget(path);
438
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
439
0
      return rv;
440
0
    }
441
0
  }
442
0
443
0
  mExploredDirectories.PutEntry(path);
444
0
  return NS_OK;
445
0
}
446
447
bool
448
GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir)
449
0
{
450
#ifdef DEBUG
451
  bool isLink, isDir;
452
  if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) ||
453
                 NS_FAILED(aDir->IsDirectory(&isDir)))) {
454
    return false;
455
  }
456
457
  MOZ_ASSERT(isLink && isDir, "Why are we here?");
458
#endif
459
460
0
  nsAutoString targetPath;
461
0
  if (NS_WARN_IF(NS_FAILED(aDir->GetTarget(targetPath)))) {
462
0
    return false;
463
0
  }
464
0
465
0
  return !mExploredDirectories.Contains(targetPath);
466
0
}
467
468
void
469
GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
470
0
{
471
0
  MOZ_ASSERT(NS_IsMainThread());
472
0
  MOZ_ASSERT(mListingCompleted);
473
0
  MOZ_ASSERT(aPromise);
474
0
475
0
  // Error propagation.
476
0
  if (NS_FAILED(mErrorResult)) {
477
0
    aPromise->MaybeReject(mErrorResult);
478
0
    return;
479
0
  }
480
0
481
0
  aPromise->MaybeResolve(mFiles);
482
0
}
483
484
void
485
GetFilesHelper::RunCallback(GetFilesCallback* aCallback)
486
0
{
487
0
  MOZ_ASSERT(NS_IsMainThread());
488
0
  MOZ_ASSERT(mListingCompleted);
489
0
  MOZ_ASSERT(aCallback);
490
0
491
0
  aCallback->Callback(mErrorResult, mFiles);
492
0
}
493
494
///////////////////////////////////////////////////////////////////////////////
495
// GetFilesHelperChild class
496
497
void
498
GetFilesHelperChild::Work(ErrorResult& aRv)
499
0
{
500
0
  ContentChild* cc = ContentChild::GetSingleton();
501
0
  if (NS_WARN_IF(!cc)) {
502
0
    aRv.Throw(NS_ERROR_FAILURE);
503
0
    return;
504
0
  }
505
0
506
0
  aRv = nsContentUtils::GenerateUUIDInPlace(mUUID);
507
0
  if (NS_WARN_IF(aRv.Failed())) {
508
0
    return;
509
0
  }
510
0
511
0
  mPendingOperation = true;
512
0
  cc->CreateGetFilesRequest(mDirectoryPath, mRecursiveFlag, mUUID, this);
513
0
}
514
515
void
516
GetFilesHelperChild::Cancel()
517
0
{
518
0
  if (!mPendingOperation) {
519
0
    return;
520
0
  }
521
0
522
0
  ContentChild* cc = ContentChild::GetSingleton();
523
0
  if (NS_WARN_IF(!cc)) {
524
0
    return;
525
0
  }
526
0
527
0
  mPendingOperation = false;
528
0
  cc->DeleteGetFilesRequest(mUUID, this);
529
0
}
530
531
bool
532
GetFilesHelperChild::AppendBlobImpl(BlobImpl* aBlobImpl)
533
0
{
534
0
  MOZ_ASSERT(mPendingOperation);
535
0
  MOZ_ASSERT(aBlobImpl);
536
0
  MOZ_ASSERT(aBlobImpl->IsFile());
537
0
538
0
  RefPtr<File> file = File::Create(mGlobal, aBlobImpl);
539
0
  MOZ_ASSERT(file);
540
0
541
0
  return mFiles.AppendElement(file, fallible);
542
0
}
543
544
void
545
GetFilesHelperChild::Finished(nsresult aError)
546
0
{
547
0
  MOZ_ASSERT(mPendingOperation);
548
0
  MOZ_ASSERT(NS_SUCCEEDED(mErrorResult));
549
0
550
0
  mPendingOperation = false;
551
0
  mErrorResult = aError;
552
0
553
0
  OperationCompleted();
554
0
}
555
556
///////////////////////////////////////////////////////////////////////////////
557
// GetFilesHelperParent class
558
559
class GetFilesHelperParentCallback final : public GetFilesCallback
560
{
561
public:
562
  explicit GetFilesHelperParentCallback(GetFilesHelperParent* aParent)
563
    : mParent(aParent)
564
0
  {
565
0
    MOZ_ASSERT(aParent);
566
0
  }
567
568
  void
569
  Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
570
0
  {
571
0
    if (NS_FAILED(aStatus)) {
572
0
      mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
573
0
                                                             GetFilesResponseFailure(aStatus));
574
0
      return;
575
0
    }
576
0
577
0
    GetFilesResponseSuccess success;
578
0
579
0
    nsTArray<IPCBlob>& ipcBlobs = success.blobs();
580
0
    ipcBlobs.SetLength(aFiles.Length());
581
0
582
0
    for (uint32_t i = 0; i < aFiles.Length(); ++i) {
583
0
      nsresult rv = IPCBlobUtils::Serialize(aFiles[i]->Impl(),
584
0
                                            mParent->mContentParent,
585
0
                                            ipcBlobs[i]);
586
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
587
0
        mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
588
0
                                                               GetFilesResponseFailure(NS_ERROR_OUT_OF_MEMORY));
589
0
        return;
590
0
      }
591
0
    }
592
0
593
0
    mParent->mContentParent->SendGetFilesResponseAndForget(mParent->mUUID,
594
0
                                                           success);
595
0
  }
596
597
private:
598
  // Raw pointer because this callback is kept alive by this parent object.
599
  GetFilesHelperParent* mParent;
600
};
601
602
GetFilesHelperParent::GetFilesHelperParent(const nsID& aUUID,
603
                                           ContentParent* aContentParent,
604
                                           bool aRecursiveFlag)
605
  : GetFilesHelper(nullptr, aRecursiveFlag)
606
  , mContentParent(aContentParent)
607
  , mUUID(aUUID)
608
0
{}
609
610
GetFilesHelperParent::~GetFilesHelperParent()
611
0
{
612
0
  NS_ReleaseOnMainThreadSystemGroup(
613
0
    "GetFilesHelperParent::mContentParent", mContentParent.forget());
614
0
}
615
616
/* static */ already_AddRefed<GetFilesHelperParent>
617
GetFilesHelperParent::Create(const nsID& aUUID, const nsAString& aDirectoryPath,
618
                             bool aRecursiveFlag, ContentParent* aContentParent,
619
                             ErrorResult& aRv)
620
0
{
621
0
  MOZ_ASSERT(aContentParent);
622
0
623
0
  RefPtr<GetFilesHelperParent> helper =
624
0
    new GetFilesHelperParent(aUUID, aContentParent, aRecursiveFlag);
625
0
  helper->SetDirectoryPath(aDirectoryPath);
626
0
627
0
  helper->Work(aRv);
628
0
  if (NS_WARN_IF(aRv.Failed())) {
629
0
    return nullptr;
630
0
  }
631
0
632
0
  RefPtr<GetFilesHelperParentCallback> callback =
633
0
    new GetFilesHelperParentCallback(helper);
634
0
  helper->AddCallback(callback);
635
0
636
0
  return helper.forget();
637
0
}
638
639
} // dom namespace
640
} // mozilla namespace