Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / webkit / browser / fileapi / file_system_context.cc
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "webkit/browser/fileapi/file_system_context.h"
6
7 #include "base/bind.h"
8 #include "base/single_thread_task_runner.h"
9 #include "base/stl_util.h"
10 #include "base/task_runner_util.h"
11 #include "net/url_request/url_request.h"
12 #include "url/gurl.h"
13 #include "webkit/browser/blob/file_stream_reader.h"
14 #include "webkit/browser/fileapi/copy_or_move_file_validator.h"
15 #include "webkit/browser/fileapi/external_mount_points.h"
16 #include "webkit/browser/fileapi/file_permission_policy.h"
17 #include "webkit/browser/fileapi/file_stream_writer.h"
18 #include "webkit/browser/fileapi/file_system_file_util.h"
19 #include "webkit/browser/fileapi/file_system_operation.h"
20 #include "webkit/browser/fileapi/file_system_operation_runner.h"
21 #include "webkit/browser/fileapi/file_system_options.h"
22 #include "webkit/browser/fileapi/file_system_quota_client.h"
23 #include "webkit/browser/fileapi/file_system_url.h"
24 #include "webkit/browser/fileapi/isolated_context.h"
25 #include "webkit/browser/fileapi/isolated_file_system_backend.h"
26 #include "webkit/browser/fileapi/mount_points.h"
27 #include "webkit/browser/fileapi/quota/quota_reservation.h"
28 #include "webkit/browser/fileapi/sandbox_file_system_backend.h"
29 #include "webkit/browser/quota/quota_manager_proxy.h"
30 #include "webkit/browser/quota/special_storage_policy.h"
31 #include "webkit/common/fileapi/file_system_info.h"
32 #include "webkit/common/fileapi/file_system_util.h"
33
34 using quota::QuotaClient;
35
36 namespace fileapi {
37
38 namespace {
39
40 QuotaClient* CreateQuotaClient(
41     FileSystemContext* context,
42     bool is_incognito) {
43   return new FileSystemQuotaClient(context, is_incognito);
44 }
45
46
47 void DidGetMetadataForResolveURL(
48     const base::FilePath& path,
49     const FileSystemContext::ResolveURLCallback& callback,
50     const FileSystemInfo& info,
51     base::File::Error error,
52     const base::File::Info& file_info) {
53   if (error != base::File::FILE_OK) {
54     if (error == base::File::FILE_ERROR_NOT_FOUND) {
55       callback.Run(base::File::FILE_OK, info, path,
56                    FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
57     } else {
58       callback.Run(error, FileSystemInfo(), base::FilePath(),
59                    FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
60     }
61     return;
62   }
63   callback.Run(error, info, path, file_info.is_directory ?
64       FileSystemContext::RESOLVED_ENTRY_DIRECTORY :
65       FileSystemContext::RESOLVED_ENTRY_FILE);
66 }
67
68 void RelayResolveURLCallback(
69     scoped_refptr<base::MessageLoopProxy> message_loop,
70     const FileSystemContext::ResolveURLCallback& callback,
71     base::File::Error result,
72     const FileSystemInfo& info,
73     const base::FilePath& file_path,
74     FileSystemContext::ResolvedEntryType type) {
75   message_loop->PostTask(
76       FROM_HERE, base::Bind(callback, result, info, file_path, type));
77 }
78
79 }  // namespace
80
81 // static
82 int FileSystemContext::GetPermissionPolicy(FileSystemType type) {
83   switch (type) {
84     case kFileSystemTypeTemporary:
85     case kFileSystemTypePersistent:
86     case kFileSystemTypeSyncable:
87       return FILE_PERMISSION_SANDBOX;
88
89     case kFileSystemTypeDrive:
90     case kFileSystemTypeNativeForPlatformApp:
91     case kFileSystemTypeNativeLocal:
92     case kFileSystemTypeCloudDevice:
93     case kFileSystemTypeProvided:
94     case kFileSystemTypeDeviceMediaAsFileStorage:
95       return FILE_PERMISSION_USE_FILE_PERMISSION;
96
97     case kFileSystemTypeRestrictedNativeLocal:
98       return FILE_PERMISSION_READ_ONLY |
99              FILE_PERMISSION_USE_FILE_PERMISSION;
100
101     case kFileSystemTypeDeviceMedia:
102     case kFileSystemTypeIphoto:
103     case kFileSystemTypeItunes:
104     case kFileSystemTypeNativeMedia:
105     case kFileSystemTypePicasa:
106       return FILE_PERMISSION_USE_FILE_PERMISSION;
107
108     // Following types are only accessed via IsolatedFileSystem, and
109     // don't have their own permission policies.
110     case kFileSystemTypeDragged:
111     case kFileSystemTypeForTransientFile:
112     case kFileSystemTypePluginPrivate:
113       return FILE_PERMISSION_ALWAYS_DENY;
114
115     // Following types only appear as mount_type, and will not be
116     // queried for their permission policies.
117     case kFileSystemTypeIsolated:
118     case kFileSystemTypeExternal:
119       return FILE_PERMISSION_ALWAYS_DENY;
120
121     // Following types should not be used to access files by FileAPI clients.
122     case kFileSystemTypeTest:
123     case kFileSystemTypeSyncableForInternalSync:
124     case kFileSystemInternalTypeEnumEnd:
125     case kFileSystemInternalTypeEnumStart:
126     case kFileSystemTypeUnknown:
127       return FILE_PERMISSION_ALWAYS_DENY;
128   }
129   NOTREACHED();
130   return FILE_PERMISSION_ALWAYS_DENY;
131 }
132
133 FileSystemContext::FileSystemContext(
134     base::SingleThreadTaskRunner* io_task_runner,
135     base::SequencedTaskRunner* file_task_runner,
136     ExternalMountPoints* external_mount_points,
137     quota::SpecialStoragePolicy* special_storage_policy,
138     quota::QuotaManagerProxy* quota_manager_proxy,
139     ScopedVector<FileSystemBackend> additional_backends,
140     const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers,
141     const base::FilePath& partition_path,
142     const FileSystemOptions& options)
143     : io_task_runner_(io_task_runner),
144       default_file_task_runner_(file_task_runner),
145       quota_manager_proxy_(quota_manager_proxy),
146       sandbox_delegate_(new SandboxFileSystemBackendDelegate(
147           quota_manager_proxy,
148           file_task_runner,
149           partition_path,
150           special_storage_policy,
151           options)),
152       sandbox_backend_(new SandboxFileSystemBackend(
153           sandbox_delegate_.get())),
154       isolated_backend_(new IsolatedFileSystemBackend()),
155       plugin_private_backend_(new PluginPrivateFileSystemBackend(
156           file_task_runner,
157           partition_path,
158           special_storage_policy,
159           options)),
160       additional_backends_(additional_backends.Pass()),
161       auto_mount_handlers_(auto_mount_handlers),
162       external_mount_points_(external_mount_points),
163       partition_path_(partition_path),
164       is_incognito_(options.is_incognito()),
165       operation_runner_(new FileSystemOperationRunner(this)) {
166   RegisterBackend(sandbox_backend_.get());
167   RegisterBackend(isolated_backend_.get());
168   RegisterBackend(plugin_private_backend_.get());
169
170   for (ScopedVector<FileSystemBackend>::const_iterator iter =
171           additional_backends_.begin();
172        iter != additional_backends_.end(); ++iter) {
173     RegisterBackend(*iter);
174   }
175
176   if (quota_manager_proxy) {
177     // Quota client assumes all backends have registered.
178     quota_manager_proxy->RegisterClient(CreateQuotaClient(
179             this, options.is_incognito()));
180   }
181
182   sandbox_backend_->Initialize(this);
183   isolated_backend_->Initialize(this);
184   plugin_private_backend_->Initialize(this);
185   for (ScopedVector<FileSystemBackend>::const_iterator iter =
186           additional_backends_.begin();
187        iter != additional_backends_.end(); ++iter) {
188     (*iter)->Initialize(this);
189   }
190
191   // Additional mount points must be added before regular system-wide
192   // mount points.
193   if (external_mount_points)
194     url_crackers_.push_back(external_mount_points);
195   url_crackers_.push_back(ExternalMountPoints::GetSystemInstance());
196   url_crackers_.push_back(IsolatedContext::GetInstance());
197 }
198
199 bool FileSystemContext::DeleteDataForOriginOnFileTaskRunner(
200     const GURL& origin_url) {
201   DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
202   DCHECK(origin_url == origin_url.GetOrigin());
203
204   bool success = true;
205   for (FileSystemBackendMap::iterator iter = backend_map_.begin();
206        iter != backend_map_.end();
207        ++iter) {
208     FileSystemBackend* backend = iter->second;
209     if (!backend->GetQuotaUtil())
210       continue;
211     if (backend->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner(
212             this, quota_manager_proxy(), origin_url, iter->first)
213             != base::File::FILE_OK) {
214       // Continue the loop, but record the failure.
215       success = false;
216     }
217   }
218
219   return success;
220 }
221
222 scoped_refptr<QuotaReservation>
223 FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
224     const GURL& origin_url,
225     FileSystemType type) {
226   DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
227   FileSystemBackend* backend = GetFileSystemBackend(type);
228   if (!backend || !backend->GetQuotaUtil())
229     return scoped_refptr<QuotaReservation>();
230   return backend->GetQuotaUtil()->CreateQuotaReservationOnFileTaskRunner(
231       origin_url, type);
232 }
233
234 void FileSystemContext::Shutdown() {
235   if (!io_task_runner_->RunsTasksOnCurrentThread()) {
236     io_task_runner_->PostTask(
237         FROM_HERE, base::Bind(&FileSystemContext::Shutdown,
238                               make_scoped_refptr(this)));
239     return;
240   }
241   operation_runner_->Shutdown();
242 }
243
244 FileSystemQuotaUtil*
245 FileSystemContext::GetQuotaUtil(FileSystemType type) const {
246   FileSystemBackend* backend = GetFileSystemBackend(type);
247   if (!backend)
248     return NULL;
249   return backend->GetQuotaUtil();
250 }
251
252 AsyncFileUtil* FileSystemContext::GetAsyncFileUtil(
253     FileSystemType type) const {
254   FileSystemBackend* backend = GetFileSystemBackend(type);
255   if (!backend)
256     return NULL;
257   return backend->GetAsyncFileUtil(type);
258 }
259
260 CopyOrMoveFileValidatorFactory*
261 FileSystemContext::GetCopyOrMoveFileValidatorFactory(
262     FileSystemType type, base::File::Error* error_code) const {
263   DCHECK(error_code);
264   *error_code = base::File::FILE_OK;
265   FileSystemBackend* backend = GetFileSystemBackend(type);
266   if (!backend)
267     return NULL;
268   return backend->GetCopyOrMoveFileValidatorFactory(
269       type, error_code);
270 }
271
272 FileSystemBackend* FileSystemContext::GetFileSystemBackend(
273     FileSystemType type) const {
274   FileSystemBackendMap::const_iterator found = backend_map_.find(type);
275   if (found != backend_map_.end())
276     return found->second;
277   NOTREACHED() << "Unknown filesystem type: " << type;
278   return NULL;
279 }
280
281 bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const {
282   FileSystemBackendMap::const_iterator found = backend_map_.find(type);
283   return found != backend_map_.end() && found->second->GetQuotaUtil();
284 }
285
286 const UpdateObserverList* FileSystemContext::GetUpdateObservers(
287     FileSystemType type) const {
288   FileSystemBackend* backend = GetFileSystemBackend(type);
289   if (backend->GetQuotaUtil())
290     return backend->GetQuotaUtil()->GetUpdateObservers(type);
291   return NULL;
292 }
293
294 const AccessObserverList* FileSystemContext::GetAccessObservers(
295     FileSystemType type) const {
296   FileSystemBackend* backend = GetFileSystemBackend(type);
297   if (backend->GetQuotaUtil())
298     return backend->GetQuotaUtil()->GetAccessObservers(type);
299   return NULL;
300 }
301
302 void FileSystemContext::GetFileSystemTypes(
303     std::vector<FileSystemType>* types) const {
304   types->clear();
305   for (FileSystemBackendMap::const_iterator iter = backend_map_.begin();
306        iter != backend_map_.end(); ++iter)
307     types->push_back(iter->first);
308 }
309
310 ExternalFileSystemBackend*
311 FileSystemContext::external_backend() const {
312   return static_cast<ExternalFileSystemBackend*>(
313       GetFileSystemBackend(kFileSystemTypeExternal));
314 }
315
316 void FileSystemContext::OpenFileSystem(
317     const GURL& origin_url,
318     FileSystemType type,
319     OpenFileSystemMode mode,
320     const OpenFileSystemCallback& callback) {
321   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
322   DCHECK(!callback.is_null());
323
324   if (!FileSystemContext::IsSandboxFileSystem(type)) {
325     // Disallow opening a non-sandboxed filesystem.
326     callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
327     return;
328   }
329
330   FileSystemBackend* backend = GetFileSystemBackend(type);
331   if (!backend) {
332     callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
333     return;
334   }
335
336   backend->ResolveURL(
337       CreateCrackedFileSystemURL(origin_url, type, base::FilePath()),
338       mode,
339       callback);
340 }
341
342 void FileSystemContext::ResolveURL(
343     const FileSystemURL& url,
344     const ResolveURLCallback& callback) {
345   DCHECK(!callback.is_null());
346
347   // If not on IO thread, forward before passing the task to the backend.
348   if (!io_task_runner_->RunsTasksOnCurrentThread()) {
349     ResolveURLCallback relay_callback =
350         base::Bind(&RelayResolveURLCallback,
351                    base::MessageLoopProxy::current(), callback);
352     io_task_runner_->PostTask(
353         FROM_HERE,
354         base::Bind(&FileSystemContext::ResolveURL, this, url, relay_callback));
355     return;
356   }
357
358   FileSystemBackend* backend = GetFileSystemBackend(url.type());
359   if (!backend) {
360     callback.Run(base::File::FILE_ERROR_SECURITY,
361                  FileSystemInfo(), base::FilePath(),
362                  FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
363     return;
364   }
365
366   backend->ResolveURL(
367       url,
368       OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
369       base::Bind(&FileSystemContext::DidOpenFileSystemForResolveURL,
370                  this,
371                  url,
372                  callback));
373 }
374
375 void FileSystemContext::AttemptAutoMountForURLRequest(
376     const net::URLRequest* url_request,
377     const std::string& storage_domain,
378     const StatusCallback& callback) {
379   FileSystemURL filesystem_url(url_request->url());
380   if (filesystem_url.type() == kFileSystemTypeExternal) {
381     for (size_t i = 0; i < auto_mount_handlers_.size(); i++) {
382       if (auto_mount_handlers_[i].Run(url_request, filesystem_url,
383                                       storage_domain, callback)) {
384         return;
385       }
386     }
387   }
388   callback.Run(base::File::FILE_ERROR_NOT_FOUND);
389 }
390
391 void FileSystemContext::DeleteFileSystem(
392     const GURL& origin_url,
393     FileSystemType type,
394     const StatusCallback& callback) {
395   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
396   DCHECK(origin_url == origin_url.GetOrigin());
397   DCHECK(!callback.is_null());
398
399   FileSystemBackend* backend = GetFileSystemBackend(type);
400   if (!backend) {
401     callback.Run(base::File::FILE_ERROR_SECURITY);
402     return;
403   }
404   if (!backend->GetQuotaUtil()) {
405     callback.Run(base::File::FILE_ERROR_INVALID_OPERATION);
406     return;
407   }
408
409   base::PostTaskAndReplyWithResult(
410       default_file_task_runner(),
411       FROM_HERE,
412       // It is safe to pass Unretained(quota_util) since context owns it.
413       base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileTaskRunner,
414                  base::Unretained(backend->GetQuotaUtil()),
415                  make_scoped_refptr(this),
416                  base::Unretained(quota_manager_proxy()),
417                  origin_url,
418                  type),
419       callback);
420 }
421
422 scoped_ptr<webkit_blob::FileStreamReader>
423 FileSystemContext::CreateFileStreamReader(
424     const FileSystemURL& url,
425     int64 offset,
426     const base::Time& expected_modification_time) {
427   if (!url.is_valid())
428     return scoped_ptr<webkit_blob::FileStreamReader>();
429   FileSystemBackend* backend = GetFileSystemBackend(url.type());
430   if (!backend)
431     return scoped_ptr<webkit_blob::FileStreamReader>();
432   return backend->CreateFileStreamReader(
433       url, offset, expected_modification_time, this);
434 }
435
436 scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter(
437     const FileSystemURL& url,
438     int64 offset) {
439   if (!url.is_valid())
440     return scoped_ptr<FileStreamWriter>();
441   FileSystemBackend* backend = GetFileSystemBackend(url.type());
442   if (!backend)
443     return scoped_ptr<FileStreamWriter>();
444   return backend->CreateFileStreamWriter(url, offset, this);
445 }
446
447 scoped_ptr<FileSystemOperationRunner>
448 FileSystemContext::CreateFileSystemOperationRunner() {
449   return make_scoped_ptr(new FileSystemOperationRunner(this));
450 }
451
452 FileSystemURL FileSystemContext::CrackURL(const GURL& url) const {
453   return CrackFileSystemURL(FileSystemURL(url));
454 }
455
456 FileSystemURL FileSystemContext::CreateCrackedFileSystemURL(
457     const GURL& origin,
458     FileSystemType type,
459     const base::FilePath& path) const {
460   return CrackFileSystemURL(FileSystemURL(origin, type, path));
461 }
462
463 #if defined(OS_CHROMEOS)
464 void FileSystemContext::EnableTemporaryFileSystemInIncognito() {
465   sandbox_backend_->set_enable_temporary_file_system_in_incognito(true);
466 }
467 #endif
468
469 bool FileSystemContext::CanServeURLRequest(const FileSystemURL& url) const {
470   // We never support accessing files in isolated filesystems via an URL.
471   if (url.mount_type() == kFileSystemTypeIsolated)
472     return false;
473 #if defined(OS_CHROMEOS)
474   if (url.type() == kFileSystemTypeTemporary &&
475       sandbox_backend_->enable_temporary_file_system_in_incognito()) {
476     return true;
477   }
478 #endif
479   return !is_incognito_ || !FileSystemContext::IsSandboxFileSystem(url.type());
480 }
481
482 bool FileSystemContext::ShouldFlushOnWriteCompletion(
483     FileSystemType type) const {
484   if (IsSandboxFileSystem(type)) {
485     // Disable Flush() for each write operation on SandboxFileSystems since it
486     // hurts the performance, assuming the FileSystems are stored in a local
487     // disk, we don't need to keep calling fsync() for it.
488     // On the other hand, other FileSystems that may stored on a removable media
489     // should be Flush()ed as soon as a write operation is completed, so that
490     // written data is saved over sudden media removal.
491     return false;
492   }
493   return true;
494 }
495
496 void FileSystemContext::OpenPluginPrivateFileSystem(
497     const GURL& origin_url,
498     FileSystemType type,
499     const std::string& filesystem_id,
500     const std::string& plugin_id,
501     OpenFileSystemMode mode,
502     const StatusCallback& callback) {
503   DCHECK(plugin_private_backend_);
504   plugin_private_backend_->OpenPrivateFileSystem(
505       origin_url, type, filesystem_id, plugin_id, mode, callback);
506 }
507
508 FileSystemContext::~FileSystemContext() {
509 }
510
511 void FileSystemContext::DeleteOnCorrectThread() const {
512   if (!io_task_runner_->RunsTasksOnCurrentThread() &&
513       io_task_runner_->DeleteSoon(FROM_HERE, this)) {
514     return;
515   }
516   delete this;
517 }
518
519 FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
520     const FileSystemURL& url, base::File::Error* error_code) {
521   if (!url.is_valid()) {
522     if (error_code)
523       *error_code = base::File::FILE_ERROR_INVALID_URL;
524     return NULL;
525   }
526
527   FileSystemBackend* backend = GetFileSystemBackend(url.type());
528   if (!backend) {
529     if (error_code)
530       *error_code = base::File::FILE_ERROR_FAILED;
531     return NULL;
532   }
533
534   base::File::Error fs_error = base::File::FILE_OK;
535   FileSystemOperation* operation =
536       backend->CreateFileSystemOperation(url, this, &fs_error);
537
538   if (error_code)
539     *error_code = fs_error;
540   return operation;
541 }
542
543 FileSystemURL FileSystemContext::CrackFileSystemURL(
544     const FileSystemURL& url) const {
545   if (!url.is_valid())
546     return FileSystemURL();
547
548   // The returned value in case there is no crackers which can crack the url.
549   // This is valid situation for non isolated/external file systems.
550   FileSystemURL current = url;
551
552   // File system may be mounted multiple times (e.g., an isolated filesystem on
553   // top of an external filesystem). Hence cracking needs to be iterated.
554   for (;;) {
555     FileSystemURL cracked = current;
556     for (size_t i = 0; i < url_crackers_.size(); ++i) {
557       if (!url_crackers_[i]->HandlesFileSystemMountType(current.type()))
558         continue;
559       cracked = url_crackers_[i]->CrackFileSystemURL(current);
560       if (cracked.is_valid())
561         break;
562     }
563     if (cracked == current)
564       break;
565     current = cracked;
566   }
567   return current;
568 }
569
570 void FileSystemContext::RegisterBackend(FileSystemBackend* backend) {
571   const FileSystemType mount_types[] = {
572     kFileSystemTypeTemporary,
573     kFileSystemTypePersistent,
574     kFileSystemTypeIsolated,
575     kFileSystemTypeExternal,
576   };
577   // Register file system backends for public mount types.
578   for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) {
579     if (backend->CanHandleType(mount_types[j])) {
580       const bool inserted = backend_map_.insert(
581           std::make_pair(mount_types[j], backend)).second;
582       DCHECK(inserted);
583     }
584   }
585   // Register file system backends for internal types.
586   for (int t = kFileSystemInternalTypeEnumStart + 1;
587        t < kFileSystemInternalTypeEnumEnd; ++t) {
588     FileSystemType type = static_cast<FileSystemType>(t);
589     if (backend->CanHandleType(type)) {
590       const bool inserted = backend_map_.insert(
591           std::make_pair(type, backend)).second;
592       DCHECK(inserted);
593     }
594   }
595 }
596
597 void FileSystemContext::DidOpenFileSystemForResolveURL(
598     const FileSystemURL& url,
599     const FileSystemContext::ResolveURLCallback& callback,
600     const GURL& filesystem_root,
601     const std::string& filesystem_name,
602     base::File::Error error) {
603   DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
604
605   if (error != base::File::FILE_OK) {
606     callback.Run(error, FileSystemInfo(), base::FilePath(),
607                  FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
608     return;
609   }
610
611   fileapi::FileSystemInfo info(
612       filesystem_name, filesystem_root, url.mount_type());
613
614   // Extract the virtual path not containing a filesystem type part from |url|.
615   base::FilePath parent = CrackURL(filesystem_root).virtual_path();
616   base::FilePath child = url.virtual_path();
617   base::FilePath path;
618
619   if (parent.empty()) {
620     path = child;
621   } else if (parent != child) {
622     bool result = parent.AppendRelativePath(child, &path);
623     DCHECK(result);
624   }
625
626   operation_runner()->GetMetadata(
627       url, base::Bind(&DidGetMetadataForResolveURL, path, callback, info));
628 }
629
630 }  // namespace fileapi