Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / dom / devicestorage / nsDeviceStorage.cpp
blob703662aa80b7804ceb62fc8ac034c8b394143416
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et 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/. */
7 #include "nsDeviceStorage.h"
9 #include "mozilla/Attributes.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/dom/ContentChild.h"
13 #include "mozilla/dom/DeviceStorageBinding.h"
14 #include "mozilla/dom/DeviceStorageChangeEvent.h"
15 #include "mozilla/dom/DeviceStorageFileSystem.h"
16 #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
17 #include "mozilla/dom/Directory.h"
18 #include "mozilla/dom/FileSystemUtils.h"
19 #include "mozilla/dom/ipc/Blob.h"
20 #include "mozilla/dom/PBrowserChild.h"
21 #include "mozilla/dom/PermissionMessageUtils.h"
22 #include "mozilla/dom/Promise.h"
23 #include "mozilla/dom/ScriptSettings.h"
24 #include "mozilla/EventDispatcher.h"
25 #include "mozilla/EventListenerManager.h"
26 #include "mozilla/LazyIdleThread.h"
27 #include "mozilla/Preferences.h"
28 #include "mozilla/Scoped.h"
29 #include "mozilla/Services.h"
31 #include "nsArrayUtils.h"
32 #include "nsAutoPtr.h"
33 #include "nsGlobalWindow.h"
34 #include "nsServiceManagerUtils.h"
35 #include "nsIFile.h"
36 #include "nsIDirectoryEnumerator.h"
37 #include "nsAppDirectoryServiceDefs.h"
38 #include "nsDirectoryServiceDefs.h"
39 #include "nsIDOMFile.h"
40 #include "nsDOMBlobBuilder.h"
41 #include "nsNetUtil.h"
42 #include "nsCycleCollectionParticipant.h"
43 #include "nsIPrincipal.h"
44 #include "nsJSUtils.h"
45 #include "nsContentUtils.h"
46 #include "nsXULAppAPI.h"
47 #include "DeviceStorageFileDescriptor.h"
48 #include "DeviceStorageRequestChild.h"
49 #include "nsCRT.h"
50 #include "nsIObserverService.h"
51 #include "nsIMIMEService.h"
52 #include "nsCExternalHandlerService.h"
53 #include "nsIPermissionManager.h"
54 #include "nsIStringBundle.h"
55 #include "nsISupportsPrimitives.h"
56 #include "nsIDocument.h"
57 #include "nsPrintfCString.h"
58 #include <algorithm>
59 #include "private/pprio.h"
60 #include "nsContentPermissionHelper.h"
62 #include "mozilla/dom/DeviceStorageBinding.h"
64 // Microsoft's API Name hackery sucks
65 #undef CreateEvent
67 #ifdef MOZ_WIDGET_ANDROID
68 #include "AndroidBridge.h"
69 #endif
71 #ifdef MOZ_WIDGET_GONK
72 #include "nsIVolume.h"
73 #include "nsIVolumeService.h"
74 #endif
76 #define DEVICESTORAGE_PROPERTIES \
77 "chrome://global/content/devicestorage.properties"
78 #define DEFAULT_THREAD_TIMEOUT_MS 30000
80 using namespace mozilla;
81 using namespace mozilla::dom;
82 using namespace mozilla::dom::devicestorage;
83 using namespace mozilla::ipc;
85 #include "nsDirectoryServiceDefs.h"
87 const char* kFileWatcherUpdate = "file-watcher-update";
88 const char* kFileWatcherNotify = "file-watcher-notify";
89 const char *kDownloadWatcherNotify = "download-watcher-notify";
91 namespace mozilla {
92 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
95 StaticAutoPtr<DeviceStorageUsedSpaceCache>
96 DeviceStorageUsedSpaceCache::sDeviceStorageUsedSpaceCache;
98 DeviceStorageUsedSpaceCache::DeviceStorageUsedSpaceCache()
100 MOZ_ASSERT(NS_IsMainThread());
102 mIOThread = new LazyIdleThread(
103 DEFAULT_THREAD_TIMEOUT_MS,
104 NS_LITERAL_CSTRING("DeviceStorageUsedSpaceCache I/O"));
108 DeviceStorageUsedSpaceCache::~DeviceStorageUsedSpaceCache()
112 DeviceStorageUsedSpaceCache*
113 DeviceStorageUsedSpaceCache::CreateOrGet()
115 if (sDeviceStorageUsedSpaceCache) {
116 return sDeviceStorageUsedSpaceCache;
119 MOZ_ASSERT(NS_IsMainThread());
121 sDeviceStorageUsedSpaceCache = new DeviceStorageUsedSpaceCache();
122 ClearOnShutdown(&sDeviceStorageUsedSpaceCache);
123 return sDeviceStorageUsedSpaceCache;
126 already_AddRefed<DeviceStorageUsedSpaceCache::CacheEntry>
127 DeviceStorageUsedSpaceCache::GetCacheEntry(const nsAString& aStorageName)
129 nsTArray<nsRefPtr<CacheEntry>>::size_type numEntries = mCacheEntries.Length();
130 nsTArray<nsRefPtr<CacheEntry>>::index_type i;
131 for (i = 0; i < numEntries; i++) {
132 nsRefPtr<CacheEntry>& cacheEntry = mCacheEntries[i];
133 if (cacheEntry->mStorageName.Equals(aStorageName)) {
134 nsRefPtr<CacheEntry> addRefedCacheEntry = cacheEntry;
135 return addRefedCacheEntry.forget();
138 return nullptr;
141 static int64_t
142 GetFreeBytes(const nsAString& aStorageName)
144 // This function makes the assumption that the various types
145 // are all stored on the same filesystem. So we use pictures.
147 nsRefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_PICTURES),
148 aStorageName));
149 int64_t freeBytes = 0;
150 dsf->GetDiskFreeSpace(&freeBytes);
151 return freeBytes;
154 nsresult
155 DeviceStorageUsedSpaceCache::AccumUsedSizes(const nsAString& aStorageName,
156 uint64_t* aPicturesSoFar,
157 uint64_t* aVideosSoFar,
158 uint64_t* aMusicSoFar,
159 uint64_t* aTotalSoFar)
161 nsRefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
162 if (!cacheEntry || cacheEntry->mDirty) {
163 return NS_ERROR_NOT_AVAILABLE;
165 int64_t freeBytes = GetFreeBytes(cacheEntry->mStorageName);
166 if (freeBytes != cacheEntry->mFreeBytes) {
167 // Free space changed, so our cached results are no longer valid.
168 return NS_ERROR_NOT_AVAILABLE;
171 *aPicturesSoFar += cacheEntry->mPicturesUsedSize;
172 *aVideosSoFar += cacheEntry->mVideosUsedSize;
173 *aMusicSoFar += cacheEntry->mMusicUsedSize;
174 *aTotalSoFar += cacheEntry->mTotalUsedSize;
176 return NS_OK;
179 void
180 DeviceStorageUsedSpaceCache::SetUsedSizes(const nsAString& aStorageName,
181 uint64_t aPictureSize,
182 uint64_t aVideosSize,
183 uint64_t aMusicSize,
184 uint64_t aTotalUsedSize)
186 nsRefPtr<CacheEntry> cacheEntry = GetCacheEntry(aStorageName);
187 if (!cacheEntry) {
188 cacheEntry = new CacheEntry;
189 cacheEntry->mStorageName = aStorageName;
190 mCacheEntries.AppendElement(cacheEntry);
192 cacheEntry->mFreeBytes = GetFreeBytes(cacheEntry->mStorageName);
194 cacheEntry->mPicturesUsedSize = aPictureSize;
195 cacheEntry->mVideosUsedSize = aVideosSize;
196 cacheEntry->mMusicUsedSize = aMusicSize;
197 cacheEntry->mTotalUsedSize = aTotalUsedSize;
198 cacheEntry->mDirty = false;
201 class GlobalDirs
203 private:
204 ~GlobalDirs() {}
205 public:
206 NS_INLINE_DECL_REFCOUNTING(GlobalDirs)
207 #if !defined(MOZ_WIDGET_GONK)
208 nsCOMPtr<nsIFile> pictures;
209 nsCOMPtr<nsIFile> videos;
210 nsCOMPtr<nsIFile> music;
211 nsCOMPtr<nsIFile> sdcard;
212 #endif
213 nsCOMPtr<nsIFile> apps;
214 nsCOMPtr<nsIFile> crashes;
215 nsCOMPtr<nsIFile> overrideRootDir;
218 static StaticRefPtr<GlobalDirs> sDirs;
220 StaticAutoPtr<DeviceStorageTypeChecker>
221 DeviceStorageTypeChecker::sDeviceStorageTypeChecker;
223 DeviceStorageTypeChecker::DeviceStorageTypeChecker()
227 DeviceStorageTypeChecker::~DeviceStorageTypeChecker()
231 DeviceStorageTypeChecker*
232 DeviceStorageTypeChecker::CreateOrGet()
234 if (sDeviceStorageTypeChecker) {
235 return sDeviceStorageTypeChecker;
238 MOZ_ASSERT(NS_IsMainThread());
240 nsCOMPtr<nsIStringBundleService> stringService
241 = mozilla::services::GetStringBundleService();
242 if (!stringService) {
243 return nullptr;
246 nsCOMPtr<nsIStringBundle> filterBundle;
247 if (NS_FAILED(stringService->CreateBundle(DEVICESTORAGE_PROPERTIES,
248 getter_AddRefs(filterBundle)))) {
249 return nullptr;
252 DeviceStorageTypeChecker* result = new DeviceStorageTypeChecker();
253 result->InitFromBundle(filterBundle);
255 sDeviceStorageTypeChecker = result;
256 ClearOnShutdown(&sDeviceStorageTypeChecker);
257 return result;
260 void
261 DeviceStorageTypeChecker::InitFromBundle(nsIStringBundle* aBundle)
263 aBundle->GetStringFromName(
264 NS_ConvertASCIItoUTF16(DEVICESTORAGE_PICTURES).get(),
265 getter_Copies(mPicturesExtensions));
266 aBundle->GetStringFromName(
267 NS_ConvertASCIItoUTF16(DEVICESTORAGE_MUSIC).get(),
268 getter_Copies(mMusicExtensions));
269 aBundle->GetStringFromName(
270 NS_ConvertASCIItoUTF16(DEVICESTORAGE_VIDEOS).get(),
271 getter_Copies(mVideosExtensions));
275 bool
276 DeviceStorageTypeChecker::Check(const nsAString& aType, nsIDOMBlob* aBlob)
278 MOZ_ASSERT(aBlob);
280 nsString mimeType;
281 if (NS_FAILED(aBlob->GetType(mimeType))) {
282 return false;
285 if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
286 return StringBeginsWith(mimeType, NS_LITERAL_STRING("image/"));
289 if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
290 return StringBeginsWith(mimeType, NS_LITERAL_STRING("video/"));
293 if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
294 return StringBeginsWith(mimeType, NS_LITERAL_STRING("audio/"));
297 if (aType.EqualsLiteral(DEVICESTORAGE_APPS) ||
298 aType.EqualsLiteral(DEVICESTORAGE_SDCARD) ||
299 aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
300 // Apps, crashes and sdcard have no restriction on mime types
301 return true;
304 return false;
307 bool
308 DeviceStorageTypeChecker::Check(const nsAString& aType, nsIFile* aFile)
310 MOZ_ASSERT(aFile);
312 nsString path;
313 aFile->GetPath(path);
315 return Check(aType, path);
318 bool
319 DeviceStorageTypeChecker::Check(const nsAString& aType, const nsString& aPath)
321 if (aType.EqualsLiteral(DEVICESTORAGE_APPS) ||
322 aType.EqualsLiteral(DEVICESTORAGE_SDCARD) ||
323 aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
324 // Apps, crashes and sdcard have no restrictions on what file extensions used.
325 return true;
328 int32_t dotIdx = aPath.RFindChar(char16_t('.'));
329 if (dotIdx == kNotFound) {
330 return false;
333 nsAutoString extensionMatch;
334 extensionMatch.Assign('*');
335 extensionMatch.Append(Substring(aPath, dotIdx));
336 extensionMatch.Append(';');
338 if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
339 return CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions);
342 if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
343 return CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions);
346 if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
347 return CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions);
350 return false;
353 void
354 DeviceStorageTypeChecker::GetTypeFromFile(nsIFile* aFile, nsAString& aType)
356 MOZ_ASSERT(aFile);
358 nsString path;
359 aFile->GetPath(path);
361 GetTypeFromFileName(path, aType);
364 void
365 DeviceStorageTypeChecker::GetTypeFromFileName(const nsAString& aFileName,
366 nsAString& aType)
368 aType.AssignLiteral(DEVICESTORAGE_SDCARD);
370 nsString fileName(aFileName);
371 int32_t dotIdx = fileName.RFindChar(char16_t('.'));
372 if (dotIdx == kNotFound) {
373 return;
376 nsAutoString extensionMatch;
377 extensionMatch.Assign('*');
378 extensionMatch.Append(Substring(aFileName, dotIdx));
379 extensionMatch.Append(';');
381 if (CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions)) {
382 aType.AssignLiteral(DEVICESTORAGE_PICTURES);
384 else if (CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions)) {
385 aType.AssignLiteral(DEVICESTORAGE_VIDEOS);
387 else if (CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions)) {
388 aType.AssignLiteral(DEVICESTORAGE_MUSIC);
392 nsresult
393 DeviceStorageTypeChecker::GetPermissionForType(const nsAString& aType,
394 nsACString& aPermissionResult)
396 if (!aType.EqualsLiteral(DEVICESTORAGE_PICTURES) &&
397 !aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) &&
398 !aType.EqualsLiteral(DEVICESTORAGE_MUSIC) &&
399 !aType.EqualsLiteral(DEVICESTORAGE_APPS) &&
400 !aType.EqualsLiteral(DEVICESTORAGE_SDCARD) &&
401 !aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
402 // unknown type
403 return NS_ERROR_FAILURE;
406 aPermissionResult.AssignLiteral("device-storage:");
407 aPermissionResult.Append(NS_ConvertUTF16toUTF8(aType));
408 return NS_OK;
411 nsresult
412 DeviceStorageTypeChecker::GetAccessForRequest(
413 const DeviceStorageRequestType aRequestType, nsACString& aAccessResult)
415 switch(aRequestType) {
416 case DEVICE_STORAGE_REQUEST_READ:
417 case DEVICE_STORAGE_REQUEST_WATCH:
418 case DEVICE_STORAGE_REQUEST_FREE_SPACE:
419 case DEVICE_STORAGE_REQUEST_USED_SPACE:
420 case DEVICE_STORAGE_REQUEST_AVAILABLE:
421 case DEVICE_STORAGE_REQUEST_STATUS:
422 aAccessResult.AssignLiteral("read");
423 break;
424 case DEVICE_STORAGE_REQUEST_WRITE:
425 case DEVICE_STORAGE_REQUEST_APPEND:
426 case DEVICE_STORAGE_REQUEST_DELETE:
427 case DEVICE_STORAGE_REQUEST_FORMAT:
428 case DEVICE_STORAGE_REQUEST_MOUNT:
429 case DEVICE_STORAGE_REQUEST_UNMOUNT:
430 aAccessResult.AssignLiteral("write");
431 break;
432 case DEVICE_STORAGE_REQUEST_CREATE:
433 case DEVICE_STORAGE_REQUEST_CREATEFD:
434 aAccessResult.AssignLiteral("create");
435 break;
436 default:
437 aAccessResult.AssignLiteral("undefined");
439 return NS_OK;
442 static bool IsMediaType(const nsAString& aType)
444 return aType.EqualsLiteral(DEVICESTORAGE_PICTURES) ||
445 aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) ||
446 aType.EqualsLiteral(DEVICESTORAGE_MUSIC) ||
447 aType.EqualsLiteral(DEVICESTORAGE_SDCARD);
450 //static
451 bool
452 DeviceStorageTypeChecker::IsVolumeBased(const nsAString& aType)
454 #ifdef MOZ_WIDGET_GONK
455 // The apps and crashes aren't stored in the same place as the media, so
456 // we only ever return a single apps object, and not an array
457 // with one per volume (as is the case for the remaining
458 // storage types).
459 return IsMediaType(aType);
460 #else
461 return false;
462 #endif
465 //static
466 bool
467 DeviceStorageTypeChecker::IsSharedMediaRoot(const nsAString& aType)
469 // This function determines if aType shares a root directory with the
470 // other media types (so only applies to music, videos, pictures and sdcard).
471 #ifdef MOZ_WIDGET_GONK
472 return IsMediaType(aType);
473 #else
474 // For desktop, if the directories have been overridden, then they share
475 // a common root.
476 return IsMediaType(aType) && sDirs->overrideRootDir;
477 #endif
480 NS_IMPL_ISUPPORTS(FileUpdateDispatcher, nsIObserver)
482 mozilla::StaticRefPtr<FileUpdateDispatcher> FileUpdateDispatcher::sSingleton;
484 FileUpdateDispatcher*
485 FileUpdateDispatcher::GetSingleton()
487 if (sSingleton) {
488 return sSingleton;
491 sSingleton = new FileUpdateDispatcher();
492 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
493 obs->AddObserver(sSingleton, kFileWatcherNotify, false);
494 obs->AddObserver(sSingleton, kDownloadWatcherNotify, false);
495 ClearOnShutdown(&sSingleton);
497 return sSingleton;
500 NS_IMETHODIMP
501 FileUpdateDispatcher::Observe(nsISupports* aSubject,
502 const char* aTopic,
503 const char16_t* aData)
505 nsRefPtr<DeviceStorageFile> dsf;
507 if (!strcmp(aTopic, kDownloadWatcherNotify)) {
508 // aSubject will be an nsISupportsString with the native path to the file
509 // in question.
511 nsCOMPtr<nsISupportsString> supportsString = do_QueryInterface(aSubject);
512 if (!supportsString) {
513 return NS_OK;
515 nsString path;
516 nsresult rv = supportsString->GetData(path);
517 if (NS_WARN_IF(NS_FAILED(rv))) {
518 return NS_OK;
521 // The downloader uses the sdcard storage type.
522 nsString volName;
523 #ifdef MOZ_WIDGET_GONK
524 if (DeviceStorageTypeChecker::IsVolumeBased(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD))) {
525 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
526 if (NS_WARN_IF(!vs)) {
527 return NS_OK;
529 nsCOMPtr<nsIVolume> vol;
530 rv = vs->GetVolumeByPath(path, getter_AddRefs(vol));
531 if (NS_WARN_IF(NS_FAILED(rv))) {
532 return NS_OK;
534 rv = vol->GetName(volName);
535 if (NS_WARN_IF(NS_FAILED(rv))) {
536 return NS_OK;
538 nsString mountPoint;
539 rv = vol->GetMountPoint(mountPoint);
540 if (NS_WARN_IF(NS_FAILED(rv))) {
541 return NS_OK;
543 if (!Substring(path, 0, mountPoint.Length()).Equals(mountPoint)) {
544 return NS_OK;
546 path = Substring(path, mountPoint.Length() + 1);
548 #endif
549 dsf = new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD), volName, path);
551 } else if (!strcmp(aTopic, kFileWatcherNotify)) {
552 dsf = static_cast<DeviceStorageFile*>(aSubject);
553 } else {
554 NS_WARNING("FileUpdateDispatcher: Unrecognized topic");
555 return NS_OK;
558 if (!dsf || !dsf->mFile) {
559 NS_WARNING("FileUpdateDispatcher: Device storage file looks invalid!");
560 return NS_OK;
563 if (XRE_GetProcessType() != GeckoProcessType_Default) {
564 // Child process. Forward the notification to the parent.
565 ContentChild::GetSingleton()
566 ->SendFilePathUpdateNotify(dsf->mStorageType,
567 dsf->mStorageName,
568 dsf->mPath,
569 NS_ConvertUTF16toUTF8(aData));
570 return NS_OK;
573 // Multiple storage types may match the same files. So walk through each of
574 // the storage types, and if the extension matches, tell them about it.
575 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
576 if (DeviceStorageTypeChecker::IsSharedMediaRoot(dsf->mStorageType)) {
577 DeviceStorageTypeChecker* typeChecker
578 = DeviceStorageTypeChecker::CreateOrGet();
579 MOZ_ASSERT(typeChecker);
581 static const nsLiteralString kMediaTypes[] = {
582 NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
583 NS_LITERAL_STRING(DEVICESTORAGE_PICTURES),
584 NS_LITERAL_STRING(DEVICESTORAGE_VIDEOS),
585 NS_LITERAL_STRING(DEVICESTORAGE_MUSIC),
588 for (size_t i = 0; i < MOZ_ARRAY_LENGTH(kMediaTypes); i++) {
589 nsRefPtr<DeviceStorageFile> dsf2;
590 if (typeChecker->Check(kMediaTypes[i], dsf->mPath)) {
591 if (dsf->mStorageType.Equals(kMediaTypes[i])) {
592 dsf2 = dsf;
593 } else {
594 dsf2 = new DeviceStorageFile(kMediaTypes[i],
595 dsf->mStorageName, dsf->mPath);
597 obs->NotifyObservers(dsf2, kFileWatcherUpdate, aData);
600 } else {
601 obs->NotifyObservers(dsf, kFileWatcherUpdate, aData);
603 return NS_OK;
606 class IOEventComplete : public nsRunnable
608 public:
609 IOEventComplete(DeviceStorageFile *aFile, const char *aType)
610 : mFile(aFile)
611 , mType(aType)
615 ~IOEventComplete() {}
617 NS_IMETHOD Run()
619 MOZ_ASSERT(NS_IsMainThread());
620 nsString data;
621 CopyASCIItoUTF16(mType, data);
622 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
624 obs->NotifyObservers(mFile, kFileWatcherNotify, data.get());
626 DeviceStorageUsedSpaceCache* usedSpaceCache
627 = DeviceStorageUsedSpaceCache::CreateOrGet();
628 MOZ_ASSERT(usedSpaceCache);
629 usedSpaceCache->Invalidate(mFile->mStorageName);
630 return NS_OK;
633 private:
634 nsRefPtr<DeviceStorageFile> mFile;
635 nsCString mType;
638 DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
639 const nsAString& aStorageName,
640 const nsAString& aRootDir,
641 const nsAString& aPath)
642 : mStorageType(aStorageType)
643 , mStorageName(aStorageName)
644 , mRootDir(aRootDir)
645 , mPath(aPath)
646 , mEditable(false)
647 , mLength(UINT64_MAX)
648 , mLastModifiedDate(UINT64_MAX)
650 Init();
651 AppendRelativePath(mRootDir);
652 if (!mPath.EqualsLiteral("")) {
653 AppendRelativePath(mPath);
655 NormalizeFilePath();
658 DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
659 const nsAString& aStorageName,
660 const nsAString& aPath)
661 : mStorageType(aStorageType)
662 , mStorageName(aStorageName)
663 , mPath(aPath)
664 , mEditable(false)
665 , mLength(UINT64_MAX)
666 , mLastModifiedDate(UINT64_MAX)
668 Init();
669 AppendRelativePath(aPath);
670 NormalizeFilePath();
673 DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
674 const nsAString& aStorageName)
675 : mStorageType(aStorageType)
676 , mStorageName(aStorageName)
677 , mEditable(false)
678 , mLength(UINT64_MAX)
679 , mLastModifiedDate(UINT64_MAX)
681 Init();
684 void
685 DeviceStorageFile::Dump(const char* label)
687 nsString path;
688 if (mFile) {
689 mFile->GetPath(path);
690 } else {
691 path = NS_LITERAL_STRING("(null)");
693 const char* ptStr;
694 if (XRE_GetProcessType() == GeckoProcessType_Default) {
695 ptStr = "parent";
696 } else {
697 ptStr = "child";
700 printf_stderr("DSF (%s) %s: mStorageType '%s' mStorageName '%s' "
701 "mRootDir '%s' mPath '%s' mFile->GetPath '%s'\n",
702 ptStr, label,
703 NS_LossyConvertUTF16toASCII(mStorageType).get(),
704 NS_LossyConvertUTF16toASCII(mStorageName).get(),
705 NS_LossyConvertUTF16toASCII(mRootDir).get(),
706 NS_LossyConvertUTF16toASCII(mPath).get(),
707 NS_LossyConvertUTF16toASCII(path).get());
710 void
711 DeviceStorageFile::Init()
713 DeviceStorageFile::GetRootDirectoryForType(mStorageType,
714 mStorageName,
715 getter_AddRefs(mFile));
717 DebugOnly<DeviceStorageTypeChecker*> typeChecker
718 = DeviceStorageTypeChecker::CreateOrGet();
719 MOZ_ASSERT(typeChecker);
722 // The OverrideRootDir is needed to facilitate testing of the
723 // device.storage.overrideRootDir preference. The preference is normally
724 // only read once during initialization, but since the test environment has
725 // no convenient way to restart, we use a pref watcher instead.
726 class OverrideRootDir MOZ_FINAL : public nsIObserver
728 ~OverrideRootDir();
730 public:
731 NS_DECL_ISUPPORTS
732 NS_DECL_NSIOBSERVER
734 static OverrideRootDir* GetSingleton();
735 void Init();
736 private:
737 static mozilla::StaticRefPtr<OverrideRootDir> sSingleton;
740 NS_IMPL_ISUPPORTS(OverrideRootDir, nsIObserver)
742 mozilla::StaticRefPtr<OverrideRootDir>
743 OverrideRootDir::sSingleton;
745 OverrideRootDir*
746 OverrideRootDir::GetSingleton()
748 if (sSingleton) {
749 return sSingleton;
751 // Preference changes are automatically forwarded from parent to child
752 // in ContentParent::Observe, so we'll see the change in both the parent
753 // and the child process.
755 sSingleton = new OverrideRootDir();
756 Preferences::AddStrongObserver(sSingleton, "device.storage.overrideRootDir");
757 Preferences::AddStrongObserver(sSingleton, "device.storage.testing");
758 ClearOnShutdown(&sSingleton);
760 return sSingleton;
763 OverrideRootDir::~OverrideRootDir()
765 Preferences::RemoveObserver(this, "device.storage.overrideRootDir");
766 Preferences::RemoveObserver(this, "device.storage.testing");
769 NS_IMETHODIMP
770 OverrideRootDir::Observe(nsISupports *aSubject,
771 const char *aTopic,
772 const char16_t *aData)
774 MOZ_ASSERT(NS_IsMainThread());
776 if (sSingleton) {
777 sSingleton->Init();
779 return NS_OK;
782 void
783 OverrideRootDir::Init()
785 MOZ_ASSERT(NS_IsMainThread());
787 if (!sDirs) {
788 return;
791 if (mozilla::Preferences::GetBool("device.storage.testing", false)) {
792 nsCOMPtr<nsIProperties> dirService
793 = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
794 MOZ_ASSERT(dirService);
795 dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile),
796 getter_AddRefs(sDirs->overrideRootDir));
797 if (sDirs->overrideRootDir) {
798 sDirs->overrideRootDir->AppendRelativeNativePath(
799 NS_LITERAL_CSTRING("device-storage-testing"));
801 } else {
802 // For users running on desktop, it's convenient to be able to override
803 // all of the directories to point to a single tree, much like what happens
804 // on a real device.
805 const nsAdoptingString& overrideRootDir =
806 mozilla::Preferences::GetString("device.storage.overrideRootDir");
807 if (overrideRootDir && overrideRootDir.Length() > 0) {
808 NS_NewLocalFile(overrideRootDir, false,
809 getter_AddRefs(sDirs->overrideRootDir));
810 } else {
811 sDirs->overrideRootDir = nullptr;
815 if (sDirs->overrideRootDir) {
816 if (XRE_GetProcessType() == GeckoProcessType_Default) {
817 // Only the parent process can create directories. In testing, because
818 // the preference is updated after startup, its entirely possible that
819 // the preference updated notification will be received by a child
820 // prior to the parent.
821 nsresult rv
822 = sDirs->overrideRootDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
823 nsString path;
824 sDirs->overrideRootDir->GetPath(path);
825 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) {
826 nsPrintfCString msg("DeviceStorage: Unable to create directory '%s'",
827 NS_LossyConvertUTF16toASCII(path).get());
828 NS_WARNING(msg.get());
831 sDirs->overrideRootDir->Normalize();
835 // Directories which don't depend on a volume should be calculated once
836 // here. Directories which depend on the root directory of a volume
837 // should be calculated in DeviceStorageFile::GetRootDirectoryForType.
838 static void
839 InitDirs()
841 if (sDirs) {
842 return;
844 MOZ_ASSERT(NS_IsMainThread());
845 sDirs = new GlobalDirs;
846 ClearOnShutdown(&sDirs);
848 nsCOMPtr<nsIProperties> dirService
849 = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
850 MOZ_ASSERT(dirService);
852 #if !defined(MOZ_WIDGET_GONK)
854 // Keep MOZ_WIDGET_COCOA above XP_UNIX,
855 // because both are defined in Darwin builds.
856 #if defined (MOZ_WIDGET_COCOA)
857 dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR,
858 NS_GET_IID(nsIFile),
859 getter_AddRefs(sDirs->pictures));
860 dirService->Get(NS_OSX_MOVIE_DOCUMENTS_DIR,
861 NS_GET_IID(nsIFile),
862 getter_AddRefs(sDirs->videos));
863 dirService->Get(NS_OSX_MUSIC_DOCUMENTS_DIR,
864 NS_GET_IID(nsIFile),
865 getter_AddRefs(sDirs->music));
867 // Keep MOZ_WIDGET_ANDROID above XP_UNIX,
868 // because both are defined in Android builds.
869 #elif defined (MOZ_WIDGET_ANDROID)
870 nsAutoString path;
871 if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory(
872 NS_LITERAL_STRING(DEVICESTORAGE_PICTURES), path))) {
873 NS_NewLocalFile(path, /* aFollowLinks */ true,
874 getter_AddRefs(sDirs->pictures));
876 if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory(
877 NS_LITERAL_STRING(DEVICESTORAGE_VIDEOS), path))) {
878 NS_NewLocalFile(path, /* aFollowLinks */ true,
879 getter_AddRefs(sDirs->videos));
881 if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory(
882 NS_LITERAL_STRING(DEVICESTORAGE_MUSIC), path))) {
883 NS_NewLocalFile(path, /* aFollowLinks */ true,
884 getter_AddRefs(sDirs->music));
886 if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory(
887 NS_LITERAL_STRING(DEVICESTORAGE_SDCARD), path))) {
888 NS_NewLocalFile(path, /* aFollowLinks */ true,
889 getter_AddRefs(sDirs->sdcard));
892 #elif defined (XP_UNIX)
893 dirService->Get(NS_UNIX_XDG_PICTURES_DIR,
894 NS_GET_IID(nsIFile),
895 getter_AddRefs(sDirs->pictures));
896 dirService->Get(NS_UNIX_XDG_VIDEOS_DIR,
897 NS_GET_IID(nsIFile),
898 getter_AddRefs(sDirs->videos));
899 dirService->Get(NS_UNIX_XDG_MUSIC_DIR,
900 NS_GET_IID(nsIFile),
901 getter_AddRefs(sDirs->music));
903 #elif defined (XP_WIN)
904 dirService->Get(NS_WIN_PICTURES_DIR,
905 NS_GET_IID(nsIFile),
906 getter_AddRefs(sDirs->pictures));
907 dirService->Get(NS_WIN_VIDEOS_DIR,
908 NS_GET_IID(nsIFile),
909 getter_AddRefs(sDirs->videos));
910 dirService->Get(NS_WIN_MUSIC_DIR,
911 NS_GET_IID(nsIFile),
912 getter_AddRefs(sDirs->music));
913 #endif
915 #ifndef MOZ_WIDGET_ANDROID
916 // Eventually, on desktop, we want to do something smarter -- for example,
917 // detect when an sdcard is inserted, and use that instead of this.
918 dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
919 getter_AddRefs(sDirs->sdcard));
920 if (sDirs->sdcard) {
921 sDirs->sdcard->AppendRelativeNativePath(NS_LITERAL_CSTRING("fake-sdcard"));
923 #endif // !MOZ_WIDGET_ANDROID
924 #endif // !MOZ_WIDGET_GONK
926 #ifdef MOZ_WIDGET_GONK
927 NS_NewLocalFile(NS_LITERAL_STRING("/data"),
928 false,
929 getter_AddRefs(sDirs->apps));
930 #else
931 dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
932 getter_AddRefs(sDirs->apps));
933 if (sDirs->apps) {
934 sDirs->apps->AppendRelativeNativePath(NS_LITERAL_CSTRING("webapps"));
936 #endif
938 if (XRE_GetProcessType() == GeckoProcessType_Default) {
939 NS_GetSpecialDirectory("UAppData", getter_AddRefs(sDirs->crashes));
940 if (sDirs->crashes) {
941 sDirs->crashes->Append(NS_LITERAL_STRING("Crash Reports"));
943 } else {
944 // NS_GetSpecialDirectory("UAppData") fails in content processes because
945 // gAppData from toolkit/xre/nsAppRunner.cpp is not initialized.
946 #ifdef MOZ_WIDGET_GONK
947 NS_NewLocalFile(NS_LITERAL_STRING("/data/b2g/mozilla/Crash Reports"),
948 false,
949 getter_AddRefs(sDirs->crashes));
950 #endif
953 OverrideRootDir::GetSingleton()->Init();
956 void
957 DeviceStorageFile::GetFullPath(nsAString &aFullPath)
959 aFullPath.Truncate();
960 if (!mStorageName.EqualsLiteral("")) {
961 aFullPath.Append('/');
962 aFullPath.Append(mStorageName);
963 aFullPath.Append('/');
965 if (!mRootDir.EqualsLiteral("")) {
966 aFullPath.Append(mRootDir);
967 aFullPath.Append('/');
969 aFullPath.Append(mPath);
973 // Directories which don't depend on a volume should be calculated once
974 // in InitDirs. Directories which depend on the root directory of a volume
975 // should be calculated in this method.
976 void
977 DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType,
978 const nsAString& aStorageName,
979 nsIFile** aFile)
981 nsCOMPtr<nsIFile> f;
982 *aFile = nullptr;
983 bool allowOverride = true;
985 InitDirs();
987 #ifdef MOZ_WIDGET_GONK
988 nsString volMountPoint;
989 if (DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
990 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
991 NS_ENSURE_TRUE_VOID(vs);
992 nsresult rv;
993 nsCOMPtr<nsIVolume> vol;
994 rv = vs->GetVolumeByName(aStorageName, getter_AddRefs(vol));
995 NS_ENSURE_SUCCESS_VOID(rv);
996 vol->GetMountPoint(volMountPoint);
998 #endif
1000 // Picture directory
1001 if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
1002 #ifdef MOZ_WIDGET_GONK
1003 NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
1004 #else
1005 f = sDirs->pictures;
1006 #endif
1009 // Video directory
1010 else if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
1011 #ifdef MOZ_WIDGET_GONK
1012 NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
1013 #else
1014 f = sDirs->videos;
1015 #endif
1018 // Music directory
1019 else if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
1020 #ifdef MOZ_WIDGET_GONK
1021 NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
1022 #else
1023 f = sDirs->music;
1024 #endif
1027 // Apps directory
1028 else if (aStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
1029 f = sDirs->apps;
1030 allowOverride = false;
1033 // default SDCard
1034 else if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
1035 #ifdef MOZ_WIDGET_GONK
1036 NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f));
1037 #else
1038 f = sDirs->sdcard;
1039 #endif
1042 // crash reports directory.
1043 else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
1044 f = sDirs->crashes;
1045 allowOverride = false;
1046 } else {
1047 // Not a storage type that we recognize. Return null
1048 return;
1051 // In testing, we default all device storage types to a temp directory.
1052 // sDirs->overrideRootDir will only have been initialized (in InitDirs)
1053 // if the preference device.storage.testing was set to true, or if
1054 // device.storage.overrideRootDir is set. We can't test the preferences
1055 // directly here, since we may not be on the main thread.
1056 if (allowOverride && sDirs->overrideRootDir) {
1057 f = sDirs->overrideRootDir;
1060 if (f) {
1061 f->Clone(aFile);
1065 //static
1066 already_AddRefed<DeviceStorageFile>
1067 DeviceStorageFile::CreateUnique(nsAString& aFileName,
1068 uint32_t aFileType,
1069 uint32_t aFileAttributes)
1071 DeviceStorageTypeChecker* typeChecker
1072 = DeviceStorageTypeChecker::CreateOrGet();
1073 MOZ_ASSERT(typeChecker);
1075 nsString storageType;
1076 typeChecker->GetTypeFromFileName(aFileName, storageType);
1078 nsString storageName;
1079 nsString storagePath;
1080 if (!nsDOMDeviceStorage::ParseFullPath(aFileName, storageName, storagePath)) {
1081 return nullptr;
1083 if (storageName.IsEmpty()) {
1084 nsDOMDeviceStorage::GetDefaultStorageName(storageType, storageName);
1086 nsRefPtr<DeviceStorageFile> dsf =
1087 new DeviceStorageFile(storageType, storageName, storagePath);
1088 if (!dsf->mFile) {
1089 return nullptr;
1092 nsresult rv = dsf->mFile->CreateUnique(aFileType, aFileAttributes);
1093 NS_ENSURE_SUCCESS(rv, nullptr);
1095 // CreateUnique may cause the filename to change. So we need to update mPath
1096 // to reflect that.
1097 nsString leafName;
1098 dsf->mFile->GetLeafName(leafName);
1100 int32_t lastSlashIndex = dsf->mPath.RFindChar('/');
1101 if (lastSlashIndex == kNotFound) {
1102 dsf->mPath.Assign(leafName);
1103 } else {
1104 // Include the last '/'
1105 dsf->mPath = Substring(dsf->mPath, 0, lastSlashIndex + 1);
1106 dsf->mPath.Append(leafName);
1109 return dsf.forget();
1112 void
1113 DeviceStorageFile::SetPath(const nsAString& aPath) {
1114 mPath.Assign(aPath);
1115 NormalizeFilePath();
1118 void
1119 DeviceStorageFile::SetEditable(bool aEditable) {
1120 mEditable = aEditable;
1123 // we want to make sure that the names of file can't reach
1124 // outside of the type of storage the user asked for.
1125 bool
1126 DeviceStorageFile::IsSafePath()
1128 return IsSafePath(mRootDir) && IsSafePath(mPath);
1131 bool
1132 DeviceStorageFile::IsSafePath(const nsAString& aPath)
1134 nsAString::const_iterator start, end;
1135 aPath.BeginReading(start);
1136 aPath.EndReading(end);
1138 // if the path is a '~' or starts with '~/', return false.
1139 NS_NAMED_LITERAL_STRING(tilde, "~");
1140 NS_NAMED_LITERAL_STRING(tildeSlash, "~/");
1141 if (aPath.Equals(tilde) ||
1142 StringBeginsWith(aPath, tildeSlash)) {
1143 NS_WARNING("Path name starts with tilde!");
1144 return false;
1146 // split on /. if any token is "", ., or .., return false.
1147 NS_ConvertUTF16toUTF8 cname(aPath);
1148 char* buffer = cname.BeginWriting();
1149 const char* token;
1151 while ((token = nsCRT::strtok(buffer, "/", &buffer))) {
1152 if (PL_strcmp(token, "") == 0 ||
1153 PL_strcmp(token, ".") == 0 ||
1154 PL_strcmp(token, "..") == 0 ) {
1155 return false;
1158 return true;
1161 void
1162 DeviceStorageFile::NormalizeFilePath() {
1163 FileSystemUtils::LocalPathToNormalizedPath(mPath, mPath);
1166 void
1167 DeviceStorageFile::AppendRelativePath(const nsAString& aPath) {
1168 if (!mFile) {
1169 return;
1171 if (!IsSafePath(aPath)) {
1172 // All of the APIs (in the child) do checks to verify that the path is
1173 // valid and return PERMISSION_DENIED if a non-safe path is entered.
1174 // This check is done in the parent and prevents a compromised
1175 // child from bypassing the check. It shouldn't be possible for this
1176 // code path to be taken with a non-compromised child.
1177 NS_WARNING("Unsafe path detected - ignoring");
1178 NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get());
1179 return;
1181 nsString localPath;
1182 FileSystemUtils::NormalizedPathToLocalPath(aPath, localPath);
1183 mFile->AppendRelativePath(localPath);
1186 nsresult
1187 DeviceStorageFile::CreateFileDescriptor(FileDescriptor& aFileDescriptor)
1189 ScopedPRFileDesc fd;
1190 nsresult rv = mFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
1191 0660, &fd.rwget());
1192 NS_ENSURE_SUCCESS(rv, rv);
1194 // NOTE: The FileDescriptor::PlatformHandleType constructor returns a dup of
1195 // the file descriptor, so we don't need the original fd after this.
1196 // Our scoped file descriptor will automatically close fd.
1197 aFileDescriptor = FileDescriptor(
1198 FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd)));
1199 return NS_OK;
1202 nsresult
1203 DeviceStorageFile::Write(nsIInputStream* aInputStream)
1205 if (!aInputStream || !mFile) {
1206 return NS_ERROR_FAILURE;
1209 nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
1210 if (NS_WARN_IF(NS_FAILED(rv))) {
1211 return rv;
1214 nsCOMPtr<nsIRunnable> iocomplete = new IOEventComplete(this, "created");
1215 rv = NS_DispatchToMainThread(iocomplete);
1216 if (NS_WARN_IF(NS_FAILED(rv))) {
1217 return rv;
1220 nsCOMPtr<nsIOutputStream> outputStream;
1221 NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
1223 if (!outputStream) {
1224 return NS_ERROR_FAILURE;
1227 return Append(aInputStream, outputStream);
1230 nsresult
1231 DeviceStorageFile::Write(InfallibleTArray<uint8_t>& aBits)
1233 if (!mFile) {
1234 return NS_ERROR_FAILURE;
1237 nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
1238 if (NS_WARN_IF(NS_FAILED(rv))) {
1239 return rv;
1242 nsCOMPtr<nsIRunnable> iocomplete = new IOEventComplete(this, "created");
1243 rv = NS_DispatchToMainThread(iocomplete);
1244 if (NS_WARN_IF(NS_FAILED(rv))) {
1245 return rv;
1248 nsCOMPtr<nsIOutputStream> outputStream;
1249 NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
1251 if (!outputStream) {
1252 return NS_ERROR_FAILURE;
1255 uint32_t wrote;
1256 outputStream->Write((char*) aBits.Elements(), aBits.Length(), &wrote);
1257 outputStream->Close();
1259 iocomplete = new IOEventComplete(this, "modified");
1260 rv = NS_DispatchToMainThread(iocomplete);
1261 if (NS_WARN_IF(NS_FAILED(rv))) {
1262 return rv;
1265 if (aBits.Length() != wrote) {
1266 return NS_ERROR_FAILURE;
1268 return NS_OK;
1271 nsresult
1272 DeviceStorageFile::Append(nsIInputStream* aInputStream)
1274 if (!aInputStream || !mFile) {
1275 return NS_ERROR_FAILURE;
1278 nsCOMPtr<nsIOutputStream> outputStream;
1279 NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND, -1, 0);
1281 if (!outputStream) {
1282 return NS_ERROR_FAILURE;
1285 return Append(aInputStream, outputStream);
1289 nsresult
1290 DeviceStorageFile::Append(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream)
1292 uint64_t bufSize = 0;
1293 aInputStream->Available(&bufSize);
1295 nsCOMPtr<nsIOutputStream> bufferedOutputStream;
1296 nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
1297 aOutputStream,
1298 4096*4);
1299 NS_ENSURE_SUCCESS(rv, rv);
1301 while (bufSize) {
1302 uint32_t wrote;
1303 rv = bufferedOutputStream->WriteFrom(
1304 aInputStream,
1305 static_cast<uint32_t>(std::min<uint64_t>(bufSize, UINT32_MAX)),
1306 &wrote);
1307 if (NS_FAILED(rv)) {
1308 break;
1310 bufSize -= wrote;
1313 nsCOMPtr<nsIRunnable> iocomplete = new IOEventComplete(this, "modified");
1314 rv = NS_DispatchToMainThread(iocomplete);
1315 if (NS_WARN_IF(NS_FAILED(rv))) {
1316 return rv;
1319 bufferedOutputStream->Close();
1320 aOutputStream->Close();
1321 if (NS_WARN_IF(NS_FAILED(rv))) {
1322 return rv;
1324 return NS_OK;
1327 nsresult
1328 DeviceStorageFile::Remove()
1330 MOZ_ASSERT(!NS_IsMainThread());
1332 if (!mFile) {
1333 return NS_ERROR_FAILURE;
1336 bool check;
1337 nsresult rv = mFile->Exists(&check);
1338 if (NS_FAILED(rv)) {
1339 return rv;
1342 if (!check) {
1343 return NS_OK;
1346 rv = mFile->Remove(true);
1347 if (NS_WARN_IF(NS_FAILED(rv))) {
1348 return rv;
1351 nsCOMPtr<nsIRunnable> iocomplete = new IOEventComplete(this, "deleted");
1352 return NS_DispatchToMainThread(iocomplete);
1355 nsresult
1356 DeviceStorageFile::CalculateMimeType()
1358 MOZ_ASSERT(NS_IsMainThread());
1360 nsAutoCString mimeType;
1361 nsCOMPtr<nsIMIMEService> mimeService =
1362 do_GetService(NS_MIMESERVICE_CONTRACTID);
1363 if (mimeService) {
1364 nsresult rv = mimeService->GetTypeFromFile(mFile, mimeType);
1365 if (NS_FAILED(rv)) {
1366 mimeType.Truncate();
1367 return rv;
1371 mMimeType = NS_ConvertUTF8toUTF16(mimeType);
1372 return NS_OK;
1375 nsresult
1376 DeviceStorageFile::CalculateSizeAndModifiedDate()
1378 MOZ_ASSERT(!NS_IsMainThread());
1380 int64_t fileSize;
1381 nsresult rv = mFile->GetFileSize(&fileSize);
1382 NS_ENSURE_SUCCESS(rv, rv);
1384 mLength = fileSize;
1386 PRTime modDate;
1387 rv = mFile->GetLastModifiedTime(&modDate);
1388 NS_ENSURE_SUCCESS(rv, rv);
1390 mLastModifiedDate = modDate;
1391 return NS_OK;
1394 void
1395 DeviceStorageFile::CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
1396 PRTime aSince)
1398 nsString fullRootPath;
1399 mFile->GetPath(fullRootPath);
1400 collectFilesInternal(aFiles, aSince, fullRootPath);
1403 void
1404 DeviceStorageFile::collectFilesInternal(
1405 nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
1406 PRTime aSince,
1407 nsAString& aRootPath)
1409 if (!mFile || !IsAvailable()) {
1410 return;
1413 nsCOMPtr<nsISimpleEnumerator> e;
1414 mFile->GetDirectoryEntries(getter_AddRefs(e));
1416 if (!e) {
1417 return;
1420 nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
1421 nsCOMPtr<nsIFile> f;
1423 while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
1425 PRTime msecs;
1426 f->GetLastModifiedTime(&msecs);
1428 if (msecs < aSince) {
1429 continue;
1432 bool isDir;
1433 f->IsDirectory(&isDir);
1435 bool isFile;
1436 f->IsFile(&isFile);
1438 nsString fullpath;
1439 nsresult rv = f->GetPath(fullpath);
1440 if (NS_FAILED(rv)) {
1441 continue;
1444 if (!StringBeginsWith(fullpath, aRootPath)) {
1445 NS_ERROR("collectFiles returned a path that does not belong!");
1446 continue;
1449 nsAString::size_type len = aRootPath.Length() + 1; // +1 for the trailing /
1450 nsDependentSubstring newPath = Substring(fullpath, len);
1452 if (isDir) {
1453 DeviceStorageFile dsf(mStorageType, mStorageName, mRootDir, newPath);
1454 dsf.collectFilesInternal(aFiles, aSince, aRootPath);
1455 } else if (isFile) {
1456 nsRefPtr<DeviceStorageFile> dsf =
1457 new DeviceStorageFile(mStorageType, mStorageName, mRootDir, newPath);
1458 dsf->CalculateSizeAndModifiedDate();
1459 aFiles.AppendElement(dsf);
1464 void
1465 DeviceStorageFile::AccumDiskUsage(uint64_t* aPicturesSoFar,
1466 uint64_t* aVideosSoFar,
1467 uint64_t* aMusicSoFar,
1468 uint64_t* aTotalSoFar)
1470 if (!IsAvailable()) {
1471 return;
1474 uint64_t pictureUsage = 0, videoUsage = 0, musicUsage = 0, totalUsage = 0;
1476 if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
1477 DeviceStorageUsedSpaceCache* usedSpaceCache =
1478 DeviceStorageUsedSpaceCache::CreateOrGet();
1479 MOZ_ASSERT(usedSpaceCache);
1480 nsresult rv = usedSpaceCache->AccumUsedSizes(mStorageName,
1481 aPicturesSoFar, aVideosSoFar,
1482 aMusicSoFar, aTotalSoFar);
1483 if (NS_SUCCEEDED(rv)) {
1484 return;
1486 AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
1487 &musicUsage, &totalUsage);
1488 usedSpaceCache->SetUsedSizes(mStorageName, pictureUsage, videoUsage,
1489 musicUsage, totalUsage);
1490 } else {
1491 AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage,
1492 &musicUsage, &totalUsage);
1495 *aPicturesSoFar += pictureUsage;
1496 *aVideosSoFar += videoUsage;
1497 *aMusicSoFar += musicUsage;
1498 *aTotalSoFar += totalUsage;
1501 void
1502 DeviceStorageFile::AccumDirectoryUsage(nsIFile* aFile,
1503 uint64_t* aPicturesSoFar,
1504 uint64_t* aVideosSoFar,
1505 uint64_t* aMusicSoFar,
1506 uint64_t* aTotalSoFar)
1508 if (!aFile) {
1509 return;
1512 nsresult rv;
1513 nsCOMPtr<nsISimpleEnumerator> e;
1514 rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
1516 if (NS_FAILED(rv) || !e) {
1517 return;
1520 nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
1521 MOZ_ASSERT(files);
1523 nsCOMPtr<nsIFile> f;
1524 while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
1525 bool isDir;
1526 rv = f->IsDirectory(&isDir);
1527 if (NS_FAILED(rv)) {
1528 continue;
1531 bool isFile;
1532 rv = f->IsFile(&isFile);
1533 if (NS_FAILED(rv)) {
1534 continue;
1537 bool isLink;
1538 rv = f->IsSymlink(&isLink);
1539 if (NS_FAILED(rv)) {
1540 continue;
1543 if (isLink) {
1544 // for now, lets just totally ignore symlinks.
1545 NS_WARNING("DirectoryDiskUsage ignores symlinks");
1546 } else if (isDir) {
1547 AccumDirectoryUsage(f, aPicturesSoFar, aVideosSoFar,
1548 aMusicSoFar, aTotalSoFar);
1549 } else if (isFile) {
1551 int64_t size;
1552 rv = f->GetFileSize(&size);
1553 if (NS_FAILED(rv)) {
1554 continue;
1556 DeviceStorageTypeChecker* typeChecker
1557 = DeviceStorageTypeChecker::CreateOrGet();
1558 MOZ_ASSERT(typeChecker);
1559 nsString type;
1560 typeChecker->GetTypeFromFile(f, type);
1562 if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
1563 *aPicturesSoFar += size;
1565 else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
1566 *aVideosSoFar += size;
1568 else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
1569 *aMusicSoFar += size;
1571 *aTotalSoFar += size;
1576 void
1577 DeviceStorageFile::GetDiskFreeSpace(int64_t* aSoFar)
1579 DeviceStorageTypeChecker* typeChecker
1580 = DeviceStorageTypeChecker::CreateOrGet();
1581 if (!typeChecker) {
1582 return;
1584 if (!mFile || !IsAvailable()) {
1585 return;
1588 int64_t storageAvail = 0;
1589 nsresult rv = mFile->GetDiskSpaceAvailable(&storageAvail);
1590 if (NS_SUCCEEDED(rv)) {
1591 *aSoFar += storageAvail;
1595 bool
1596 DeviceStorageFile::IsAvailable()
1598 nsString status;
1599 GetStatus(status);
1600 return status.EqualsLiteral("available");
1603 void
1604 DeviceStorageFile::DoFormat(nsAString& aStatus)
1606 DeviceStorageTypeChecker* typeChecker
1607 = DeviceStorageTypeChecker::CreateOrGet();
1608 if (!typeChecker) {
1609 return;
1611 if (!typeChecker->IsVolumeBased(mStorageType)) {
1612 aStatus.AssignLiteral("notVolume");
1613 return;
1615 #ifdef MOZ_WIDGET_GONK
1616 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
1617 NS_ENSURE_TRUE_VOID(vs);
1619 nsCOMPtr<nsIVolume> vol;
1620 nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
1621 NS_ENSURE_SUCCESS_VOID(rv);
1622 if (!vol) {
1623 return;
1626 vol->Format();
1628 aStatus.AssignLiteral("formatting");
1629 #endif
1630 return;
1633 void
1634 DeviceStorageFile::DoMount(nsAString& aStatus)
1636 DeviceStorageTypeChecker* typeChecker
1637 = DeviceStorageTypeChecker::CreateOrGet();
1638 if (!typeChecker) {
1639 return;
1641 if (!typeChecker->IsVolumeBased(mStorageType)) {
1642 aStatus.AssignLiteral("notVolume");
1643 return;
1645 #ifdef MOZ_WIDGET_GONK
1646 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
1647 NS_ENSURE_TRUE_VOID(vs);
1649 nsCOMPtr<nsIVolume> vol;
1650 nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
1651 NS_ENSURE_SUCCESS_VOID(rv);
1652 if (!vol) {
1653 return;
1656 vol->Mount();
1658 aStatus.AssignLiteral("mounting");
1659 #endif
1660 return;
1663 void
1664 DeviceStorageFile::DoUnmount(nsAString& aStatus)
1666 DeviceStorageTypeChecker* typeChecker
1667 = DeviceStorageTypeChecker::CreateOrGet();
1668 if (!typeChecker) {
1669 return;
1671 if (!typeChecker->IsVolumeBased(mStorageType)) {
1672 aStatus.AssignLiteral("notVolume");
1673 return;
1675 #ifdef MOZ_WIDGET_GONK
1676 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
1677 NS_ENSURE_TRUE_VOID(vs);
1679 nsCOMPtr<nsIVolume> vol;
1680 nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
1681 NS_ENSURE_SUCCESS_VOID(rv);
1682 if (!vol) {
1683 return;
1686 vol->Unmount();
1688 aStatus.AssignLiteral("unmounting");
1689 #endif
1690 return;
1693 void
1694 DeviceStorageFile::GetStatus(nsAString& aStatus)
1696 aStatus.AssignLiteral("unavailable");
1698 DeviceStorageTypeChecker* typeChecker
1699 = DeviceStorageTypeChecker::CreateOrGet();
1700 if (!typeChecker) {
1701 return;
1703 if (!typeChecker->IsVolumeBased(mStorageType)) {
1704 aStatus.AssignLiteral("available");
1705 return;
1708 #ifdef MOZ_WIDGET_GONK
1709 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
1710 NS_ENSURE_TRUE_VOID(vs);
1712 nsCOMPtr<nsIVolume> vol;
1713 nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
1714 NS_ENSURE_SUCCESS_VOID(rv);
1715 if (!vol) {
1716 return;
1718 bool isMediaPresent;
1719 rv = vol->GetIsMediaPresent(&isMediaPresent);
1720 NS_ENSURE_SUCCESS_VOID(rv);
1721 if (!isMediaPresent) {
1722 return;
1724 bool isSharing;
1725 rv = vol->GetIsSharing(&isSharing);
1726 NS_ENSURE_SUCCESS_VOID(rv);
1727 if (isSharing) {
1728 aStatus.AssignLiteral("shared");
1729 return;
1731 bool isFormatting;
1732 rv = vol->GetIsFormatting(&isFormatting);
1733 NS_ENSURE_SUCCESS_VOID(rv);
1734 if (isFormatting) {
1735 aStatus.AssignLiteral("unavailable");
1736 return;
1738 bool isUnmounting;
1739 rv = vol->GetIsUnmounting(&isUnmounting);
1740 NS_ENSURE_SUCCESS_VOID(rv);
1741 if (isUnmounting) {
1742 aStatus.AssignLiteral("unavailable");
1743 return;
1745 int32_t volState;
1746 rv = vol->GetState(&volState);
1747 NS_ENSURE_SUCCESS_VOID(rv);
1748 if (volState == nsIVolume::STATE_MOUNTED) {
1749 aStatus.AssignLiteral("available");
1751 #endif
1754 void
1755 DeviceStorageFile::GetStorageStatus(nsAString& aStatus)
1757 aStatus.AssignLiteral("undefined");
1759 DeviceStorageTypeChecker* typeChecker
1760 = DeviceStorageTypeChecker::CreateOrGet();
1761 if (!typeChecker) {
1762 return;
1764 if (!typeChecker->IsVolumeBased(mStorageType)) {
1765 aStatus.AssignLiteral("available");
1766 return;
1769 #ifdef MOZ_WIDGET_GONK
1770 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
1771 NS_ENSURE_TRUE_VOID(vs);
1773 nsCOMPtr<nsIVolume> vol;
1774 nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
1775 NS_ENSURE_SUCCESS_VOID(rv);
1776 if (!vol) {
1777 return;
1780 int32_t volState;
1781 rv = vol->GetState(&volState);
1782 NS_ENSURE_SUCCESS_VOID(rv);
1783 aStatus.AssignASCII(mozilla::system::NS_VolumeStateStr(volState));
1784 #endif
1787 NS_IMPL_ISUPPORTS0(DeviceStorageFile)
1789 static void
1790 RegisterForSDCardChanges(nsIObserver* aObserver)
1792 #ifdef MOZ_WIDGET_GONK
1793 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1794 obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false);
1795 #endif
1798 static void
1799 UnregisterForSDCardChanges(nsIObserver* aObserver)
1801 #ifdef MOZ_WIDGET_GONK
1802 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1803 obs->RemoveObserver(aObserver, NS_VOLUME_STATE_CHANGED);
1804 #endif
1807 void
1808 nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aStorageType,
1809 const nsAString& aStorageName)
1811 MOZ_ASSERT(NS_IsMainThread());
1813 nsCOMPtr<nsIFile> f;
1814 DeviceStorageFile::GetRootDirectoryForType(aStorageType,
1815 aStorageName,
1816 getter_AddRefs(f));
1817 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1818 obs->AddObserver(this, kFileWatcherUpdate, false);
1819 obs->AddObserver(this, "disk-space-watcher", false);
1820 mRootDirectory = f;
1821 mStorageType = aStorageType;
1822 mStorageName = aStorageName;
1825 JS::Value
1826 InterfaceToJsval(nsPIDOMWindow* aWindow,
1827 nsISupports* aObject,
1828 const nsIID* aIID)
1830 nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
1831 if (!sgo) {
1832 return JS::NullValue();
1835 JSObject *unrootedScopeObj = sgo->GetGlobalJSObject();
1836 NS_ENSURE_TRUE(unrootedScopeObj, JS::NullValue());
1837 JSRuntime *runtime = JS_GetObjectRuntime(unrootedScopeObj);
1838 JS::Rooted<JS::Value> someJsVal(runtime);
1839 JS::Rooted<JSObject*> scopeObj(runtime, unrootedScopeObj);
1840 nsresult rv;
1842 { // Protect someJsVal from moving GC in ~JSAutoCompartment
1843 AutoJSContext cx;
1844 JSAutoCompartment ac(cx, scopeObj);
1846 rv = nsContentUtils::WrapNative(cx, aObject, aIID, &someJsVal);
1848 if (NS_FAILED(rv)) {
1849 return JS::NullValue();
1852 return someJsVal;
1855 JS::Value
1856 nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
1858 MOZ_ASSERT(NS_IsMainThread());
1859 MOZ_ASSERT(aWindow);
1861 if (!aFile) {
1862 return JSVAL_NULL;
1865 if (aFile->mEditable) {
1866 // TODO - needs janv's file handle support.
1867 return JSVAL_NULL;
1870 nsString fullPath;
1871 aFile->GetFullPath(fullPath);
1873 // This check is useful to know if somewhere the DeviceStorageFile
1874 // has not been properly set. Mimetype is not checked because it can be
1875 // empty.
1876 MOZ_ASSERT(aFile->mLength != UINT64_MAX);
1877 MOZ_ASSERT(aFile->mLastModifiedDate != UINT64_MAX);
1879 nsCOMPtr<nsIDOMBlob> blob = new DOMFile(
1880 new DOMFileImplFile(fullPath, aFile->mMimeType,
1881 aFile->mLength, aFile->mFile,
1882 aFile->mLastModifiedDate));
1883 return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob));
1886 bool
1887 StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString,
1888 JS::MutableHandle<JS::Value> result)
1890 MOZ_ASSERT(NS_IsMainThread());
1891 MOZ_ASSERT(aWindow);
1893 AutoJSAPI jsapi;
1894 if (NS_WARN_IF(!jsapi.Init(aWindow))) {
1895 return false;
1897 JSContext* cx = jsapi.cx();
1899 if (!xpc::StringToJsval(cx, aString, result)) {
1900 return false;
1903 return true;
1906 class DeviceStorageCursorRequest MOZ_FINAL
1907 : public nsIContentPermissionRequest
1909 public:
1910 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1911 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageCursorRequest,
1912 nsIContentPermissionRequest)
1914 NS_FORWARD_NSICONTENTPERMISSIONREQUEST(mCursor->);
1916 DeviceStorageCursorRequest(nsDOMDeviceStorageCursor* aCursor)
1917 : mCursor(aCursor) { }
1919 private:
1920 ~DeviceStorageCursorRequest() {}
1922 nsRefPtr<nsDOMDeviceStorageCursor> mCursor;
1925 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageCursorRequest)
1926 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
1927 NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
1928 NS_INTERFACE_MAP_END
1930 NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageCursorRequest)
1931 NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageCursorRequest)
1933 NS_IMPL_CYCLE_COLLECTION(DeviceStorageCursorRequest,
1934 mCursor)
1937 class PostErrorEvent : public nsRunnable
1939 public:
1940 PostErrorEvent(already_AddRefed<DOMRequest> aRequest, const char* aMessage)
1941 : mRequest(aRequest)
1943 CopyASCIItoUTF16(aMessage, mError);
1946 PostErrorEvent(DOMRequest* aRequest, const char* aMessage)
1947 : mRequest(aRequest)
1949 CopyASCIItoUTF16(aMessage, mError);
1952 ~PostErrorEvent() {}
1954 NS_IMETHOD Run()
1956 MOZ_ASSERT(NS_IsMainThread());
1957 if (!mRequest->GetOwner()) {
1958 return NS_OK;
1960 mRequest->FireError(mError);
1961 mRequest = nullptr;
1962 return NS_OK;
1965 private:
1966 nsRefPtr<DOMRequest> mRequest;
1967 nsString mError;
1970 ContinueCursorEvent::ContinueCursorEvent(already_AddRefed<DOMRequest> aRequest)
1971 : mRequest(aRequest)
1975 ContinueCursorEvent::ContinueCursorEvent(DOMRequest* aRequest)
1976 : mRequest(aRequest)
1980 already_AddRefed<DeviceStorageFile>
1981 ContinueCursorEvent::GetNextFile()
1983 MOZ_ASSERT(NS_IsMainThread());
1985 nsDOMDeviceStorageCursor* cursor
1986 = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
1987 nsString cursorStorageType;
1988 cursor->GetStorageType(cursorStorageType);
1990 DeviceStorageTypeChecker* typeChecker
1991 = DeviceStorageTypeChecker::CreateOrGet();
1992 if (!typeChecker) {
1993 return nullptr;
1996 while (cursor->mFiles.Length() > 0) {
1997 nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
1998 cursor->mFiles.RemoveElementAt(0);
1999 if (!typeChecker->Check(cursorStorageType, file->mFile)) {
2000 continue;
2003 file->CalculateMimeType();
2004 return file.forget();
2007 return nullptr;
2010 ContinueCursorEvent::~ContinueCursorEvent() {}
2012 void
2013 ContinueCursorEvent::Continue()
2015 if (XRE_GetProcessType() == GeckoProcessType_Default) {
2016 DebugOnly<nsresult> rv = NS_DispatchToMainThread(this);
2017 MOZ_ASSERT(NS_SUCCEEDED(rv));
2018 return;
2021 nsRefPtr<DeviceStorageFile> file = GetNextFile();
2023 if (!file) {
2024 // done with enumeration.
2025 DebugOnly<nsresult> rv = NS_DispatchToMainThread(this);
2026 MOZ_ASSERT(NS_SUCCEEDED(rv));
2027 return;
2030 nsDOMDeviceStorageCursor* cursor
2031 = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
2032 nsString cursorStorageType;
2033 cursor->GetStorageType(cursorStorageType);
2035 DeviceStorageRequestChild* child
2036 = new DeviceStorageRequestChild(mRequest, file);
2037 child->SetCallback(cursor);
2038 DeviceStorageGetParams params(cursorStorageType,
2039 file->mStorageName,
2040 file->mRootDir,
2041 file->mPath);
2042 ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child,
2043 params);
2044 mRequest = nullptr;
2047 NS_IMETHODIMP
2048 ContinueCursorEvent::Run()
2050 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2051 if (!window) {
2052 return NS_OK;
2055 nsRefPtr<DeviceStorageFile> file = GetNextFile();
2057 nsDOMDeviceStorageCursor* cursor
2058 = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
2060 AutoJSContext cx;
2061 JS::Rooted<JS::Value> val(cx, nsIFileToJsval(window, file));
2063 if (file) {
2064 cursor->mOkToCallContinue = true;
2065 cursor->FireSuccess(val);
2066 } else {
2067 cursor->FireDone();
2069 mRequest = nullptr;
2070 return NS_OK;
2073 class InitCursorEvent : public nsRunnable
2075 public:
2076 InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile)
2077 : mFile(aFile)
2078 , mRequest(aRequest)
2082 ~InitCursorEvent() {}
2084 NS_IMETHOD Run() {
2085 MOZ_ASSERT(!NS_IsMainThread());
2087 if (mFile->mFile) {
2088 bool check;
2089 mFile->mFile->IsDirectory(&check);
2090 if (!check) {
2091 nsCOMPtr<nsIRunnable> event =
2092 new PostErrorEvent(mRequest.forget(),
2093 POST_ERROR_EVENT_FILE_NOT_ENUMERABLE);
2094 return NS_DispatchToMainThread(event);
2098 nsDOMDeviceStorageCursor* cursor
2099 = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
2100 mFile->CollectFiles(cursor->mFiles, cursor->mSince);
2102 nsRefPtr<ContinueCursorEvent> event
2103 = new ContinueCursorEvent(mRequest.forget());
2104 event->Continue();
2106 return NS_OK;
2110 private:
2111 nsRefPtr<DeviceStorageFile> mFile;
2112 nsRefPtr<DOMRequest> mRequest;
2115 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorageCursor)
2116 NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
2117 NS_INTERFACE_MAP_END_INHERITING(DOMCursor)
2119 NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorageCursor, DOMCursor)
2120 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMCursor)
2122 nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsPIDOMWindow* aWindow,
2123 nsIPrincipal* aPrincipal,
2124 DeviceStorageFile* aFile,
2125 PRTime aSince)
2126 : DOMCursor(aWindow, nullptr)
2127 , mOkToCallContinue(false)
2128 , mSince(aSince)
2129 , mFile(aFile)
2130 , mPrincipal(aPrincipal)
2134 nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
2138 void
2139 nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType)
2141 aType = mFile->mStorageType;
2144 NS_IMETHODIMP
2145 nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes)
2147 nsCString type;
2148 nsresult rv =
2149 DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
2150 NS_ENSURE_SUCCESS(rv, rv);
2152 nsTArray<nsString> emptyOptions;
2153 return nsContentPermissionUtils::CreatePermissionArray(type,
2154 NS_LITERAL_CSTRING("read"),
2155 emptyOptions,
2156 aTypes);
2159 NS_IMETHODIMP
2160 nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
2162 NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
2163 return NS_OK;
2166 NS_IMETHODIMP
2167 nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow * *aRequestingWindow)
2169 NS_IF_ADDREF(*aRequestingWindow = GetOwner());
2170 return NS_OK;
2173 NS_IMETHODIMP
2174 nsDOMDeviceStorageCursor::GetElement(nsIDOMElement * *aRequestingElement)
2176 *aRequestingElement = nullptr;
2177 return NS_OK;
2180 NS_IMETHODIMP
2181 nsDOMDeviceStorageCursor::Cancel()
2183 nsCOMPtr<nsIRunnable> event
2184 = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
2185 return NS_DispatchToMainThread(event);
2188 NS_IMETHODIMP
2189 nsDOMDeviceStorageCursor::Allow(JS::HandleValue aChoices)
2191 MOZ_ASSERT(aChoices.isUndefined());
2193 if (!mFile->IsSafePath()) {
2194 nsCOMPtr<nsIRunnable> r
2195 = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED);
2196 return NS_DispatchToMainThread(r);
2199 if (XRE_GetProcessType() != GeckoProcessType_Default) {
2200 PDeviceStorageRequestChild* child
2201 = new DeviceStorageRequestChild(this, mFile);
2202 DeviceStorageEnumerationParams params(mFile->mStorageType,
2203 mFile->mStorageName,
2204 mFile->mRootDir,
2205 mSince);
2206 ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child,
2207 params);
2208 return NS_OK;
2211 nsCOMPtr<nsIEventTarget> target
2212 = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
2213 MOZ_ASSERT(target);
2215 nsCOMPtr<nsIRunnable> event = new InitCursorEvent(this, mFile);
2216 target->Dispatch(event, NS_DISPATCH_NORMAL);
2217 return NS_OK;
2220 void
2221 nsDOMDeviceStorageCursor::Continue(ErrorResult& aRv)
2223 if (!mOkToCallContinue) {
2224 aRv.Throw(NS_ERROR_UNEXPECTED);
2225 return;
2228 if (mResult != JSVAL_VOID) {
2229 // We call onsuccess multiple times. Clear the last
2230 // result.
2231 mResult = JSVAL_VOID;
2232 mDone = false;
2235 nsRefPtr<ContinueCursorEvent> event = new ContinueCursorEvent(this);
2236 event->Continue();
2238 mOkToCallContinue = false;
2241 void
2242 nsDOMDeviceStorageCursor::RequestComplete()
2244 MOZ_ASSERT(!mOkToCallContinue);
2245 mOkToCallContinue = true;
2248 class PostAvailableResultEvent : public nsRunnable
2250 public:
2251 PostAvailableResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
2252 : mFile(aFile)
2253 , mRequest(aRequest)
2255 MOZ_ASSERT(mRequest);
2258 ~PostAvailableResultEvent() {}
2260 NS_IMETHOD Run()
2262 MOZ_ASSERT(NS_IsMainThread());
2263 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2264 if (!window) {
2265 return NS_OK;
2268 nsString state = NS_LITERAL_STRING("unavailable");
2269 if (mFile) {
2270 mFile->GetStatus(state);
2273 AutoJSContext cx;
2274 JS::Rooted<JS::Value> result(cx);
2275 StringToJsval(window, state, &result);
2276 mRequest->FireSuccess(result);
2277 mRequest = nullptr;
2278 return NS_OK;
2281 private:
2282 nsRefPtr<DeviceStorageFile> mFile;
2283 nsRefPtr<DOMRequest> mRequest;
2286 class PostStatusResultEvent : public nsRunnable
2288 public:
2289 PostStatusResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
2290 : mFile(aFile)
2291 , mRequest(aRequest)
2293 MOZ_ASSERT(mRequest);
2296 ~PostStatusResultEvent() {}
2298 NS_IMETHOD Run()
2300 MOZ_ASSERT(NS_IsMainThread());
2301 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2302 if (!window) {
2303 return NS_OK;
2306 nsString state = NS_LITERAL_STRING("undefined");
2307 if (mFile) {
2308 mFile->GetStorageStatus(state);
2311 AutoJSContext cx;
2312 JS::Rooted<JS::Value> result(cx);
2313 StringToJsval(window, state, &result);
2314 mRequest->FireSuccess(result);
2315 mRequest = nullptr;
2316 return NS_OK;
2319 private:
2320 nsRefPtr<DeviceStorageFile> mFile;
2321 nsRefPtr<DOMRequest> mRequest;
2324 class PostFormatResultEvent : public nsRunnable
2326 public:
2327 PostFormatResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
2328 : mFile(aFile)
2329 , mRequest(aRequest)
2331 MOZ_ASSERT(mRequest);
2334 ~PostFormatResultEvent() {}
2336 NS_IMETHOD Run()
2338 MOZ_ASSERT(NS_IsMainThread());
2339 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2340 if (!window) {
2341 return NS_OK;
2344 nsString state = NS_LITERAL_STRING("unavailable");
2345 if (mFile) {
2346 mFile->DoFormat(state);
2349 AutoJSContext cx;
2350 JS::Rooted<JS::Value> result(cx);
2351 StringToJsval(window, state, &result);
2352 mRequest->FireSuccess(result);
2353 mRequest = nullptr;
2354 return NS_OK;
2357 private:
2358 nsRefPtr<DeviceStorageFile> mFile;
2359 nsRefPtr<DOMRequest> mRequest;
2362 class PostMountResultEvent : public nsRunnable
2364 public:
2365 PostMountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
2366 : mFile(aFile)
2367 , mRequest(aRequest)
2369 MOZ_ASSERT(mRequest);
2372 ~PostMountResultEvent() {}
2374 NS_IMETHOD Run()
2376 MOZ_ASSERT(NS_IsMainThread());
2377 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2378 if (!window) {
2379 return NS_OK;
2382 nsString state = NS_LITERAL_STRING("unavailable");
2383 if (mFile) {
2384 mFile->DoMount(state);
2387 AutoJSContext cx;
2388 JS::Rooted<JS::Value> result(cx);
2389 StringToJsval(window, state, &result);
2390 mRequest->FireSuccess(result);
2391 mRequest = nullptr;
2392 return NS_OK;
2395 private:
2396 nsRefPtr<DeviceStorageFile> mFile;
2397 nsRefPtr<DOMRequest> mRequest;
2400 class PostUnmountResultEvent : public nsRunnable
2402 public:
2403 PostUnmountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest)
2404 : mFile(aFile)
2405 , mRequest(aRequest)
2407 MOZ_ASSERT(mRequest);
2410 ~PostUnmountResultEvent() {}
2412 NS_IMETHOD Run()
2414 MOZ_ASSERT(NS_IsMainThread());
2415 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2416 if (!window) {
2417 return NS_OK;
2420 nsString state = NS_LITERAL_STRING("unavailable");
2421 if (mFile) {
2422 mFile->DoUnmount(state);
2425 AutoJSContext cx;
2426 JS::Rooted<JS::Value> result(cx);
2427 StringToJsval(window, state, &result);
2428 mRequest->FireSuccess(result);
2429 mRequest = nullptr;
2430 return NS_OK;
2433 private:
2434 nsRefPtr<DeviceStorageFile> mFile;
2435 nsRefPtr<DOMRequest> mRequest;
2438 class PostResultEvent : public nsRunnable
2440 public:
2441 PostResultEvent(already_AddRefed<DOMRequest> aRequest,
2442 DeviceStorageFile* aFile)
2443 : mFile(aFile)
2444 , mRequest(aRequest)
2446 MOZ_ASSERT(mRequest);
2449 PostResultEvent(already_AddRefed<DOMRequest> aRequest,
2450 const nsAString & aPath)
2451 : mPath(aPath)
2452 , mRequest(aRequest)
2454 MOZ_ASSERT(mRequest);
2457 PostResultEvent(already_AddRefed<DOMRequest> aRequest,
2458 const uint64_t aValue)
2459 : mValue(aValue)
2460 , mRequest(aRequest)
2462 MOZ_ASSERT(mRequest);
2465 ~PostResultEvent() {}
2467 NS_IMETHOD Run()
2469 MOZ_ASSERT(NS_IsMainThread());
2470 nsCOMPtr<nsPIDOMWindow> window = mRequest->GetOwner();
2471 if (!window) {
2472 return NS_OK;
2475 AutoJSContext cx;
2476 JS::Rooted<JS::Value> result(cx, JSVAL_NULL);
2478 if (mFile) {
2479 result = nsIFileToJsval(window, mFile);
2480 } else if (mPath.Length()) {
2481 StringToJsval(window, mPath, &result);
2483 else {
2484 result = JS_NumberValue(double(mValue));
2487 mRequest->FireSuccess(result);
2488 mRequest = nullptr;
2489 return NS_OK;
2492 private:
2493 nsRefPtr<DeviceStorageFile> mFile;
2494 nsString mPath;
2495 uint64_t mValue;
2496 nsRefPtr<DOMRequest> mRequest;
2499 class CreateFdEvent : public nsRunnable
2501 public:
2502 CreateFdEvent(DeviceStorageFileDescriptor* aDSFileDescriptor,
2503 already_AddRefed<DOMRequest> aRequest)
2504 : mDSFileDescriptor(aDSFileDescriptor)
2505 , mRequest(aRequest)
2507 MOZ_ASSERT(mDSFileDescriptor);
2508 MOZ_ASSERT(mRequest);
2511 NS_IMETHOD Run()
2513 MOZ_ASSERT(!NS_IsMainThread());
2515 DeviceStorageFile* dsFile = mDSFileDescriptor->mDSFile;
2516 MOZ_ASSERT(dsFile);
2518 nsString fullPath;
2519 dsFile->GetFullPath(fullPath);
2520 MOZ_ASSERT(!fullPath.IsEmpty());
2522 bool check = false;
2523 dsFile->mFile->Exists(&check);
2524 if (check) {
2525 nsCOMPtr<nsIRunnable> event =
2526 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS);
2527 return NS_DispatchToMainThread(event);
2530 nsresult rv = dsFile->CreateFileDescriptor(mDSFileDescriptor->mFileDescriptor);
2532 if (NS_FAILED(rv)) {
2533 dsFile->mFile->Remove(false);
2535 nsCOMPtr<nsIRunnable> event =
2536 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
2537 return NS_DispatchToMainThread(event);
2540 nsCOMPtr<nsIRunnable> event =
2541 new PostResultEvent(mRequest.forget(), fullPath);
2542 return NS_DispatchToMainThread(event);
2545 private:
2546 nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
2547 nsRefPtr<DOMRequest> mRequest;
2550 class WriteFileEvent : public nsRunnable
2552 public:
2553 WriteFileEvent(DOMFileImpl* aBlobImpl,
2554 DeviceStorageFile *aFile,
2555 already_AddRefed<DOMRequest> aRequest,
2556 int32_t aRequestType)
2557 : mBlobImpl(aBlobImpl)
2558 , mFile(aFile)
2559 , mRequest(aRequest)
2560 , mRequestType(aRequestType)
2562 MOZ_ASSERT(mFile);
2563 MOZ_ASSERT(mRequest);
2564 MOZ_ASSERT(mRequestType);
2567 ~WriteFileEvent() {}
2569 NS_IMETHOD Run()
2571 MOZ_ASSERT(!NS_IsMainThread());
2573 nsCOMPtr<nsIInputStream> stream;
2574 mBlobImpl->GetInternalStream(getter_AddRefs(stream));
2576 bool check = false;
2577 mFile->mFile->Exists(&check);
2578 nsresult rv;
2580 if (mRequestType == DEVICE_STORAGE_REQUEST_APPEND) {
2581 if (!check) {
2582 nsCOMPtr<nsIRunnable> event =
2583 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
2584 return NS_DispatchToMainThread(event);
2586 rv = mFile->Append(stream);
2588 else if (mRequestType == DEVICE_STORAGE_REQUEST_CREATE) {
2589 if (check) {
2590 nsCOMPtr<nsIRunnable> event =
2591 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS);
2592 return NS_DispatchToMainThread(event);
2594 rv = mFile->Write(stream);
2595 if (NS_FAILED(rv)) {
2596 mFile->mFile->Remove(false);
2598 } else {
2599 nsCOMPtr<nsIRunnable> event =
2600 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
2601 return NS_DispatchToMainThread(event);
2604 if (NS_FAILED(rv)) {
2605 nsCOMPtr<nsIRunnable> event =
2606 new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
2607 return NS_DispatchToMainThread(event);
2610 nsString fullPath;
2611 mFile->GetFullPath(fullPath);
2612 nsCOMPtr<nsIRunnable> event =
2613 new PostResultEvent(mRequest.forget(), fullPath);
2614 return NS_DispatchToMainThread(event);
2617 private:
2618 nsRefPtr<DOMFileImpl> mBlobImpl;
2619 nsRefPtr<DeviceStorageFile> mFile;
2620 nsRefPtr<DOMRequest> mRequest;
2621 int32_t mRequestType;
2625 class ReadFileEvent : public nsRunnable
2627 public:
2628 ReadFileEvent(DeviceStorageFile* aFile,
2629 already_AddRefed<DOMRequest> aRequest)
2630 : mFile(aFile)
2631 , mRequest(aRequest)
2633 MOZ_ASSERT(mFile);
2634 MOZ_ASSERT(mRequest);
2635 mFile->CalculateMimeType();
2638 ~ReadFileEvent() {}
2640 NS_IMETHOD Run()
2642 MOZ_ASSERT(!NS_IsMainThread());
2644 nsCOMPtr<nsIRunnable> r;
2645 if (!mFile->mEditable) {
2646 bool check = false;
2647 mFile->mFile->Exists(&check);
2648 if (!check) {
2649 r = new PostErrorEvent(mRequest.forget(),
2650 POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
2654 if (!r) {
2655 nsresult rv = mFile->CalculateSizeAndModifiedDate();
2656 if (NS_FAILED(rv)) {
2657 r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN);
2661 if (!r) {
2662 r = new PostResultEvent(mRequest.forget(), mFile);
2664 return NS_DispatchToMainThread(r);
2667 private:
2668 nsRefPtr<DeviceStorageFile> mFile;
2669 nsRefPtr<DOMRequest> mRequest;
2672 class DeleteFileEvent : public nsRunnable
2674 public:
2675 DeleteFileEvent(DeviceStorageFile* aFile,
2676 already_AddRefed<DOMRequest> aRequest)
2677 : mFile(aFile)
2678 , mRequest(aRequest)
2680 MOZ_ASSERT(mFile);
2681 MOZ_ASSERT(mRequest);
2684 ~DeleteFileEvent() {}
2686 NS_IMETHOD Run()
2688 MOZ_ASSERT(!NS_IsMainThread());
2689 mFile->Remove();
2691 nsCOMPtr<nsIRunnable> r;
2692 bool check = false;
2693 mFile->mFile->Exists(&check);
2694 if (check) {
2695 r = new PostErrorEvent(mRequest.forget(),
2696 POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
2698 else {
2699 nsString fullPath;
2700 mFile->GetFullPath(fullPath);
2701 r = new PostResultEvent(mRequest.forget(), fullPath);
2703 return NS_DispatchToMainThread(r);
2706 private:
2707 nsRefPtr<DeviceStorageFile> mFile;
2708 nsRefPtr<DOMRequest> mRequest;
2711 class UsedSpaceFileEvent : public nsRunnable
2713 public:
2714 UsedSpaceFileEvent(DeviceStorageFile* aFile,
2715 already_AddRefed<DOMRequest> aRequest)
2716 : mFile(aFile)
2717 , mRequest(aRequest)
2719 MOZ_ASSERT(mFile);
2720 MOZ_ASSERT(mRequest);
2723 ~UsedSpaceFileEvent() {}
2725 NS_IMETHOD Run()
2727 MOZ_ASSERT(!NS_IsMainThread());
2729 uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0;
2730 mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
2731 &musicUsage, &totalUsage);
2732 nsCOMPtr<nsIRunnable> r;
2733 if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
2734 r = new PostResultEvent(mRequest.forget(), picturesUsage);
2736 else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
2737 r = new PostResultEvent(mRequest.forget(), videosUsage);
2739 else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
2740 r = new PostResultEvent(mRequest.forget(), musicUsage);
2741 } else {
2742 r = new PostResultEvent(mRequest.forget(), totalUsage);
2744 return NS_DispatchToMainThread(r);
2747 private:
2748 nsRefPtr<DeviceStorageFile> mFile;
2749 nsRefPtr<DOMRequest> mRequest;
2752 class FreeSpaceFileEvent : public nsRunnable
2754 public:
2755 FreeSpaceFileEvent(DeviceStorageFile* aFile,
2756 already_AddRefed<DOMRequest> aRequest)
2757 : mFile(aFile)
2758 , mRequest(aRequest)
2760 MOZ_ASSERT(mFile);
2761 MOZ_ASSERT(mRequest);
2764 ~FreeSpaceFileEvent() {}
2766 NS_IMETHOD Run()
2768 MOZ_ASSERT(!NS_IsMainThread());
2770 int64_t freeSpace = 0;
2771 if (mFile) {
2772 mFile->GetDiskFreeSpace(&freeSpace);
2775 nsCOMPtr<nsIRunnable> r;
2776 r = new PostResultEvent(mRequest.forget(),
2777 static_cast<uint64_t>(freeSpace));
2778 return NS_DispatchToMainThread(r);
2781 private:
2782 nsRefPtr<DeviceStorageFile> mFile;
2783 nsRefPtr<DOMRequest> mRequest;
2786 class DeviceStorageRequest MOZ_FINAL
2787 : public nsIContentPermissionRequest
2788 , public nsIRunnable
2790 public:
2792 DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
2793 nsPIDOMWindow* aWindow,
2794 nsIPrincipal* aPrincipal,
2795 DeviceStorageFile* aFile,
2796 DOMRequest* aRequest,
2797 nsDOMDeviceStorage* aDeviceStorage)
2798 : mRequestType(aRequestType)
2799 , mWindow(aWindow)
2800 , mPrincipal(aPrincipal)
2801 , mFile(aFile)
2802 , mRequest(aRequest)
2803 , mDeviceStorage(aDeviceStorage)
2805 MOZ_ASSERT(mWindow);
2806 MOZ_ASSERT(mPrincipal);
2807 MOZ_ASSERT(mFile);
2808 MOZ_ASSERT(mRequest);
2809 MOZ_ASSERT(mDeviceStorage);
2812 DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
2813 nsPIDOMWindow* aWindow,
2814 nsIPrincipal* aPrincipal,
2815 DeviceStorageFile* aFile,
2816 DOMRequest* aRequest,
2817 nsIDOMBlob* aBlob = nullptr)
2818 : mRequestType(aRequestType)
2819 , mWindow(aWindow)
2820 , mPrincipal(aPrincipal)
2821 , mFile(aFile)
2822 , mRequest(aRequest)
2823 , mBlob(aBlob)
2825 MOZ_ASSERT(mWindow);
2826 MOZ_ASSERT(mPrincipal);
2827 MOZ_ASSERT(mFile);
2828 MOZ_ASSERT(mRequest);
2831 DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
2832 nsPIDOMWindow* aWindow,
2833 nsIPrincipal* aPrincipal,
2834 DeviceStorageFile* aFile,
2835 DOMRequest* aRequest,
2836 DeviceStorageFileDescriptor* aDSFileDescriptor)
2837 : mRequestType(aRequestType)
2838 , mWindow(aWindow)
2839 , mPrincipal(aPrincipal)
2840 , mFile(aFile)
2841 , mRequest(aRequest)
2842 , mDSFileDescriptor(aDSFileDescriptor)
2844 MOZ_ASSERT(mRequestType == DEVICE_STORAGE_REQUEST_CREATEFD);
2845 MOZ_ASSERT(mWindow);
2846 MOZ_ASSERT(mPrincipal);
2847 MOZ_ASSERT(mFile);
2848 MOZ_ASSERT(mRequest);
2849 MOZ_ASSERT(mDSFileDescriptor);
2852 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
2853 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest,
2854 nsIContentPermissionRequest)
2856 NS_IMETHOD Run()
2858 MOZ_ASSERT(NS_IsMainThread());
2860 if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
2861 Allow(JS::UndefinedHandleValue);
2862 return NS_OK;
2865 return nsContentPermissionUtils::AskPermission(this, mWindow);
2868 NS_IMETHODIMP GetTypes(nsIArray** aTypes)
2870 nsCString type;
2871 nsresult rv =
2872 DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
2873 if (NS_FAILED(rv)) {
2874 return rv;
2877 nsCString access;
2878 rv = DeviceStorageTypeChecker::GetAccessForRequest(
2879 DeviceStorageRequestType(mRequestType), access);
2880 if (NS_FAILED(rv)) {
2881 return rv;
2884 nsTArray<nsString> emptyOptions;
2885 return nsContentPermissionUtils::CreatePermissionArray(type, access, emptyOptions, aTypes);
2888 NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
2890 NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
2891 return NS_OK;
2894 NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow)
2896 NS_IF_ADDREF(*aRequestingWindow = mWindow);
2897 return NS_OK;
2900 NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement)
2902 *aRequestingElement = nullptr;
2903 return NS_OK;
2906 NS_IMETHOD Cancel()
2908 nsCOMPtr<nsIRunnable> event
2909 = new PostErrorEvent(mRequest.forget(),
2910 POST_ERROR_EVENT_PERMISSION_DENIED);
2911 return NS_DispatchToMainThread(event);
2914 NS_IMETHOD Allow(JS::HandleValue aChoices)
2916 MOZ_ASSERT(NS_IsMainThread());
2917 MOZ_ASSERT(aChoices.isUndefined());
2919 if (!mRequest) {
2920 return NS_ERROR_FAILURE;
2923 nsCOMPtr<nsIRunnable> r;
2925 switch(mRequestType) {
2926 case DEVICE_STORAGE_REQUEST_CREATEFD:
2928 if (!mFile->mFile) {
2929 return NS_ERROR_FAILURE;
2932 DeviceStorageTypeChecker* typeChecker
2933 = DeviceStorageTypeChecker::CreateOrGet();
2934 if (!typeChecker) {
2935 return NS_OK;
2938 if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
2939 r = new PostErrorEvent(mRequest.forget(),
2940 POST_ERROR_EVENT_ILLEGAL_TYPE);
2941 return NS_DispatchToCurrentThread(r);
2944 if (XRE_GetProcessType() != GeckoProcessType_Default) {
2946 DeviceStorageCreateFdParams params;
2947 params.type() = mFile->mStorageType;
2948 params.storageName() = mFile->mStorageName;
2949 params.relpath() = mFile->mPath;
2951 mFile->Dump("DeviceStorageCreateFdParams");
2953 PDeviceStorageRequestChild* child
2954 = new DeviceStorageRequestChild(mRequest, mFile,
2955 mDSFileDescriptor.get());
2956 ContentChild::GetSingleton()
2957 ->SendPDeviceStorageRequestConstructor(child, params);
2958 return NS_OK;
2960 mDSFileDescriptor->mDSFile = mFile;
2961 r = new CreateFdEvent(mDSFileDescriptor.get(), mRequest.forget());
2962 break;
2965 case DEVICE_STORAGE_REQUEST_CREATE:
2967 if (!mBlob || !mFile->mFile) {
2968 return NS_ERROR_FAILURE;
2971 DeviceStorageTypeChecker* typeChecker
2972 = DeviceStorageTypeChecker::CreateOrGet();
2973 if (!typeChecker) {
2974 return NS_OK;
2977 if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) ||
2978 !typeChecker->Check(mFile->mStorageType, mBlob)) {
2979 r = new PostErrorEvent(mRequest.forget(),
2980 POST_ERROR_EVENT_ILLEGAL_TYPE);
2981 return NS_DispatchToCurrentThread(r);
2984 if (XRE_GetProcessType() != GeckoProcessType_Default) {
2985 BlobChild* actor
2986 = ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlob);
2987 if (!actor) {
2988 return NS_ERROR_FAILURE;
2991 DeviceStorageAddParams params;
2992 params.blobChild() = actor;
2993 params.type() = mFile->mStorageType;
2994 params.storageName() = mFile->mStorageName;
2995 params.relpath() = mFile->mPath;
2997 PDeviceStorageRequestChild* child
2998 = new DeviceStorageRequestChild(mRequest, mFile);
2999 ContentChild::GetSingleton()
3000 ->SendPDeviceStorageRequestConstructor(child, params);
3001 return NS_OK;
3004 DOMFile* blob = static_cast<DOMFile*>(mBlob.get());
3005 r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(),
3006 mRequestType);
3007 break;
3010 case DEVICE_STORAGE_REQUEST_APPEND:
3012 if (!mBlob || !mFile->mFile) {
3013 return NS_ERROR_FAILURE;
3016 DeviceStorageTypeChecker* typeChecker
3017 = DeviceStorageTypeChecker::CreateOrGet();
3018 if (!typeChecker) {
3019 return NS_OK;
3022 if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) ||
3023 !typeChecker->Check(mFile->mStorageType, mBlob)) {
3024 r = new PostErrorEvent(mRequest.forget(),
3025 POST_ERROR_EVENT_ILLEGAL_TYPE);
3026 return NS_DispatchToCurrentThread(r);
3029 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3030 BlobChild* actor
3031 = ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlob);
3032 if (!actor) {
3033 return NS_ERROR_FAILURE;
3036 DeviceStorageAppendParams params;
3037 params.blobChild() = actor;
3038 params.type() = mFile->mStorageType;
3039 params.storageName() = mFile->mStorageName;
3040 params.relpath() = mFile->mPath;
3042 PDeviceStorageRequestChild* child
3043 = new DeviceStorageRequestChild(mRequest, mFile);
3044 ContentChild::GetSingleton()
3045 ->SendPDeviceStorageRequestConstructor(child, params);
3046 return NS_OK;
3049 DOMFile* blob = static_cast<DOMFile*>(mBlob.get());
3050 r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(),
3051 mRequestType);
3052 break;
3055 case DEVICE_STORAGE_REQUEST_READ:
3056 case DEVICE_STORAGE_REQUEST_WRITE:
3058 if (!mFile->mFile) {
3059 return NS_ERROR_FAILURE;
3062 DeviceStorageTypeChecker* typeChecker
3063 = DeviceStorageTypeChecker::CreateOrGet();
3064 if (!typeChecker) {
3065 return NS_OK;
3068 if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
3069 r = new PostErrorEvent(mRequest.forget(),
3070 POST_ERROR_EVENT_ILLEGAL_TYPE);
3071 return NS_DispatchToCurrentThread(r);
3074 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3075 PDeviceStorageRequestChild* child
3076 = new DeviceStorageRequestChild(mRequest, mFile);
3077 DeviceStorageGetParams params(mFile->mStorageType,
3078 mFile->mStorageName,
3079 mFile->mRootDir,
3080 mFile->mPath);
3081 ContentChild::GetSingleton()
3082 ->SendPDeviceStorageRequestConstructor(child, params);
3083 return NS_OK;
3086 r = new ReadFileEvent(mFile, mRequest.forget());
3087 break;
3090 case DEVICE_STORAGE_REQUEST_DELETE:
3092 if (!mFile->mFile) {
3093 return NS_ERROR_FAILURE;
3096 DeviceStorageTypeChecker* typeChecker
3097 = DeviceStorageTypeChecker::CreateOrGet();
3098 if (!typeChecker) {
3099 return NS_OK;
3102 if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) {
3103 r = new PostErrorEvent(mRequest.forget(),
3104 POST_ERROR_EVENT_ILLEGAL_TYPE);
3105 return NS_DispatchToCurrentThread(r);
3108 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3109 PDeviceStorageRequestChild* child
3110 = new DeviceStorageRequestChild(mRequest, mFile);
3111 DeviceStorageDeleteParams params(mFile->mStorageType,
3112 mFile->mStorageName,
3113 mFile->mPath);
3114 ContentChild::GetSingleton()
3115 ->SendPDeviceStorageRequestConstructor(child, params);
3116 return NS_OK;
3118 r = new DeleteFileEvent(mFile, mRequest.forget());
3119 break;
3122 case DEVICE_STORAGE_REQUEST_FREE_SPACE:
3124 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3125 PDeviceStorageRequestChild* child
3126 = new DeviceStorageRequestChild(mRequest, mFile);
3127 DeviceStorageFreeSpaceParams params(mFile->mStorageType,
3128 mFile->mStorageName);
3129 ContentChild::GetSingleton()
3130 ->SendPDeviceStorageRequestConstructor(child, params);
3131 return NS_OK;
3133 r = new FreeSpaceFileEvent(mFile, mRequest.forget());
3134 break;
3137 case DEVICE_STORAGE_REQUEST_USED_SPACE:
3139 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3140 PDeviceStorageRequestChild* child
3141 = new DeviceStorageRequestChild(mRequest, mFile);
3142 DeviceStorageUsedSpaceParams params(mFile->mStorageType,
3143 mFile->mStorageName);
3144 ContentChild::GetSingleton()
3145 ->SendPDeviceStorageRequestConstructor(child, params);
3146 return NS_OK;
3148 // this needs to be dispatched to only one (1)
3149 // thread or we will do more work than required.
3150 DeviceStorageUsedSpaceCache* usedSpaceCache
3151 = DeviceStorageUsedSpaceCache::CreateOrGet();
3152 MOZ_ASSERT(usedSpaceCache);
3153 r = new UsedSpaceFileEvent(mFile, mRequest.forget());
3154 usedSpaceCache->Dispatch(r);
3155 return NS_OK;
3158 case DEVICE_STORAGE_REQUEST_AVAILABLE:
3160 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3161 PDeviceStorageRequestChild* child
3162 = new DeviceStorageRequestChild(mRequest, mFile);
3163 DeviceStorageAvailableParams params(mFile->mStorageType,
3164 mFile->mStorageName);
3165 ContentChild::GetSingleton()
3166 ->SendPDeviceStorageRequestConstructor(child, params);
3167 return NS_OK;
3169 r = new PostAvailableResultEvent(mFile, mRequest);
3170 return NS_DispatchToCurrentThread(r);
3173 case DEVICE_STORAGE_REQUEST_STATUS:
3175 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3176 PDeviceStorageRequestChild* child
3177 = new DeviceStorageRequestChild(mRequest, mFile);
3178 DeviceStorageStatusParams params(mFile->mStorageType,
3179 mFile->mStorageName);
3180 ContentChild::GetSingleton()
3181 ->SendPDeviceStorageRequestConstructor(child, params);
3182 return NS_OK;
3184 r = new PostStatusResultEvent(mFile, mRequest);
3185 return NS_DispatchToCurrentThread(r);
3188 case DEVICE_STORAGE_REQUEST_WATCH:
3190 mDeviceStorage->mAllowedToWatchFile = true;
3191 return NS_OK;
3194 case DEVICE_STORAGE_REQUEST_FORMAT:
3196 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3197 PDeviceStorageRequestChild* child
3198 = new DeviceStorageRequestChild(mRequest, mFile);
3199 DeviceStorageFormatParams params(mFile->mStorageType,
3200 mFile->mStorageName);
3201 ContentChild::GetSingleton()
3202 ->SendPDeviceStorageRequestConstructor(child, params);
3203 return NS_OK;
3205 r = new PostFormatResultEvent(mFile, mRequest);
3206 return NS_DispatchToCurrentThread(r);
3209 case DEVICE_STORAGE_REQUEST_MOUNT:
3211 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3212 PDeviceStorageRequestChild* child
3213 = new DeviceStorageRequestChild(mRequest, mFile);
3214 DeviceStorageMountParams params(mFile->mStorageType,
3215 mFile->mStorageName);
3216 ContentChild::GetSingleton()
3217 ->SendPDeviceStorageRequestConstructor(child, params);
3218 return NS_OK;
3220 r = new PostMountResultEvent(mFile, mRequest);
3221 return NS_DispatchToCurrentThread(r);
3224 case DEVICE_STORAGE_REQUEST_UNMOUNT:
3226 if (XRE_GetProcessType() != GeckoProcessType_Default) {
3227 PDeviceStorageRequestChild* child
3228 = new DeviceStorageRequestChild(mRequest, mFile);
3229 DeviceStorageUnmountParams params(mFile->mStorageType,
3230 mFile->mStorageName);
3231 ContentChild::GetSingleton()
3232 ->SendPDeviceStorageRequestConstructor(child, params);
3233 return NS_OK;
3235 r = new PostUnmountResultEvent(mFile, mRequest);
3236 return NS_DispatchToCurrentThread(r);
3240 if (r) {
3241 nsCOMPtr<nsIEventTarget> target
3242 = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
3243 MOZ_ASSERT(target);
3244 target->Dispatch(r, NS_DISPATCH_NORMAL);
3247 return NS_OK;
3250 private:
3251 ~DeviceStorageRequest() {}
3253 int32_t mRequestType;
3254 nsCOMPtr<nsPIDOMWindow> mWindow;
3255 nsCOMPtr<nsIPrincipal> mPrincipal;
3256 nsRefPtr<DeviceStorageFile> mFile;
3258 nsRefPtr<DOMRequest> mRequest;
3259 nsCOMPtr<nsIDOMBlob> mBlob;
3260 nsRefPtr<nsDOMDeviceStorage> mDeviceStorage;
3261 nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
3264 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest)
3265 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
3266 NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
3267 NS_INTERFACE_MAP_ENTRY(nsIRunnable)
3268 NS_INTERFACE_MAP_END
3270 NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageRequest)
3271 NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageRequest)
3273 NS_IMPL_CYCLE_COLLECTION(DeviceStorageRequest,
3274 mRequest,
3275 mWindow,
3276 mBlob,
3277 mDeviceStorage)
3280 NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorage)
3281 NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorage)
3282 NS_INTERFACE_MAP_ENTRY(nsIObserver)
3283 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
3285 NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
3286 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
3288 nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindow* aWindow)
3289 : DOMEventTargetHelper(aWindow)
3290 , mIsShareable(false)
3291 , mIsWatchingFile(false)
3292 , mAllowedToWatchFile(false)
3296 /* virtual */ JSObject*
3297 nsDOMDeviceStorage::WrapObject(JSContext* aCx)
3299 return DeviceStorageBinding::Wrap(aCx, this);
3302 nsresult
3303 nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType,
3304 const nsAString &aVolName)
3306 DebugOnly<FileUpdateDispatcher*> observer
3307 = FileUpdateDispatcher::GetSingleton();
3308 MOZ_ASSERT(observer);
3310 MOZ_ASSERT(aWindow);
3312 SetRootDirectoryForType(aType, aVolName);
3313 if (!mRootDirectory) {
3314 return NS_ERROR_NOT_AVAILABLE;
3316 if (!mStorageName.IsEmpty()) {
3317 RegisterForSDCardChanges(this);
3319 #ifdef MOZ_WIDGET_GONK
3320 if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
3321 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
3322 if (NS_WARN_IF(!vs)) {
3323 return NS_ERROR_FAILURE;
3325 nsresult rv;
3326 nsCOMPtr<nsIVolume> vol;
3327 rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol));
3328 if (NS_WARN_IF(NS_FAILED(rv))) {
3329 return rv;
3331 bool isFake;
3332 rv = vol->GetIsFake(&isFake);
3333 if (NS_WARN_IF(NS_FAILED(rv))) {
3334 return rv;
3336 mIsShareable = !isFake;
3338 #endif
3341 // Grab the principal of the document
3342 nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
3343 if (!doc) {
3344 return NS_ERROR_FAILURE;
3346 mPrincipal = doc->NodePrincipal();
3348 // the 'apps' type is special. We only want this exposed
3349 // if the caller has the "webapps-manage" permission.
3350 if (aType.EqualsLiteral(DEVICESTORAGE_APPS)) {
3351 nsCOMPtr<nsIPermissionManager> permissionManager
3352 = services::GetPermissionManager();
3353 NS_ENSURE_TRUE(permissionManager, NS_ERROR_FAILURE);
3355 uint32_t permission;
3356 nsresult rv
3357 = permissionManager->TestPermissionFromPrincipal(mPrincipal,
3358 "webapps-manage",
3359 &permission);
3361 if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
3362 return NS_ERROR_NOT_AVAILABLE;
3366 return NS_OK;
3369 nsDOMDeviceStorage::~nsDOMDeviceStorage()
3373 void
3374 nsDOMDeviceStorage::Shutdown()
3376 MOZ_ASSERT(NS_IsMainThread());
3378 if (mFileSystem) {
3379 mFileSystem->Shutdown();
3380 mFileSystem = nullptr;
3383 if (!mStorageName.IsEmpty()) {
3384 UnregisterForSDCardChanges(this);
3387 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
3388 obs->RemoveObserver(this, kFileWatcherUpdate);
3389 obs->RemoveObserver(this, "disk-space-watcher");
3392 StaticAutoPtr<nsTArray<nsString>> nsDOMDeviceStorage::sVolumeNameCache;
3394 // static
3395 void
3396 nsDOMDeviceStorage::GetOrderedVolumeNames(
3397 nsDOMDeviceStorage::VolumeNameArray &aVolumeNames)
3399 if (sVolumeNameCache && sVolumeNameCache->Length() > 0) {
3400 aVolumeNames.AppendElements(*sVolumeNameCache);
3401 return;
3403 #ifdef MOZ_WIDGET_GONK
3404 nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
3405 if (vs) {
3406 nsCOMPtr<nsIArray> volNames;
3407 vs->GetVolumeNames(getter_AddRefs(volNames));
3408 uint32_t length = -1;
3409 volNames->GetLength(&length);
3410 for (uint32_t i = 0; i < length; i++) {
3411 nsCOMPtr<nsISupportsString> str = do_QueryElementAt(volNames, i);
3412 if (str) {
3413 nsAutoString s;
3414 if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) {
3415 aVolumeNames.AppendElement(s);
3420 // If the volume sdcard exists, then we want it to be first.
3422 VolumeNameArray::index_type sdcardIndex;
3423 sdcardIndex = aVolumeNames.IndexOf(NS_LITERAL_STRING("sdcard"));
3424 if (sdcardIndex != VolumeNameArray::NoIndex && sdcardIndex > 0) {
3425 aVolumeNames.RemoveElementAt(sdcardIndex);
3426 aVolumeNames.InsertElementAt(0, NS_LITERAL_STRING("sdcard"));
3429 #endif
3430 if (aVolumeNames.IsEmpty()) {
3431 aVolumeNames.AppendElement(EmptyString());
3433 sVolumeNameCache = new nsTArray<nsString>;
3434 sVolumeNameCache->AppendElements(aVolumeNames);
3437 // static
3438 void
3439 nsDOMDeviceStorage::CreateDeviceStorageFor(nsPIDOMWindow* aWin,
3440 const nsAString &aType,
3441 nsDOMDeviceStorage** aStore)
3443 nsString storageName;
3444 if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
3445 // The storage name will be the empty string
3446 storageName.Truncate();
3447 } else {
3448 GetDefaultStorageName(aType, storageName);
3451 nsRefPtr<nsDOMDeviceStorage> ds = new nsDOMDeviceStorage(aWin);
3452 if (NS_FAILED(ds->Init(aWin, aType, storageName))) {
3453 *aStore = nullptr;
3454 return;
3456 NS_ADDREF(*aStore = ds.get());
3459 // static
3460 void
3461 nsDOMDeviceStorage::CreateDeviceStoragesFor(
3462 nsPIDOMWindow* aWin,
3463 const nsAString &aType,
3464 nsTArray<nsRefPtr<nsDOMDeviceStorage> > &aStores)
3466 nsresult rv;
3468 if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
3469 nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage(aWin);
3470 rv = storage->Init(aWin, aType, EmptyString());
3471 if (NS_SUCCEEDED(rv)) {
3472 aStores.AppendElement(storage);
3474 return;
3476 VolumeNameArray volNames;
3477 GetOrderedVolumeNames(volNames);
3479 VolumeNameArray::size_type numVolumeNames = volNames.Length();
3480 for (VolumeNameArray::index_type i = 0; i < numVolumeNames; i++) {
3481 nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage(aWin);
3482 rv = storage->Init(aWin, aType, volNames[i]);
3483 if (NS_FAILED(rv)) {
3484 break;
3486 aStores.AppendElement(storage);
3490 // static
3491 bool
3492 nsDOMDeviceStorage::ParseFullPath(const nsAString& aFullPath,
3493 nsAString& aOutStorageName,
3494 nsAString& aOutStoragePath)
3496 aOutStorageName.Truncate();
3497 aOutStoragePath.Truncate();
3499 NS_NAMED_LITERAL_STRING(slash, "/");
3501 nsDependentSubstring storageName;
3503 if (StringBeginsWith(aFullPath, slash)) {
3504 int32_t slashIndex = aFullPath.FindChar('/', 1);
3505 if (slashIndex == kNotFound) {
3506 // names of the form /filename are illegal
3507 return false;
3509 storageName.Rebind(aFullPath, 1, slashIndex - 1);
3510 aOutStoragePath = Substring(aFullPath, slashIndex + 1);
3511 } else {
3512 aOutStoragePath = aFullPath;
3514 // If no volume name was specified in aFullPath, then aOutStorageName
3515 // will wind up being the empty string. It's up to the caller to figure
3516 // out which storage name to actually use.
3517 aOutStorageName = storageName;
3518 return true;
3521 already_AddRefed<nsDOMDeviceStorage>
3522 nsDOMDeviceStorage::GetStorage(const nsAString& aFullPath,
3523 nsAString& aOutStoragePath)
3525 nsString storageName;
3526 if (!ParseFullPath(aFullPath, storageName, aOutStoragePath)) {
3527 return nullptr;
3529 nsRefPtr<nsDOMDeviceStorage> ds;
3530 if (storageName.IsEmpty()) {
3531 ds = this;
3532 } else {
3533 ds = GetStorageByName(storageName);
3535 return ds.forget();
3538 already_AddRefed<nsDOMDeviceStorage>
3539 nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName)
3541 MOZ_ASSERT(NS_IsMainThread());
3543 nsRefPtr<nsDOMDeviceStorage> ds;
3545 if (mStorageName.Equals(aStorageName)) {
3546 ds = this;
3547 return ds.forget();
3549 VolumeNameArray volNames;
3550 GetOrderedVolumeNames(volNames);
3551 VolumeNameArray::size_type numVolumes = volNames.Length();
3552 VolumeNameArray::index_type i;
3553 for (i = 0; i < numVolumes; i++) {
3554 if (volNames[i].Equals(aStorageName)) {
3555 ds = new nsDOMDeviceStorage(GetOwner());
3556 nsresult rv = ds->Init(GetOwner(), mStorageType, aStorageName);
3557 if (NS_FAILED(rv)) {
3558 return nullptr;
3560 return ds.forget();
3563 return nullptr;
3566 // static
3567 void
3568 nsDOMDeviceStorage::GetDefaultStorageName(const nsAString& aStorageType,
3569 nsAString& aStorageName)
3571 // See if the preferred volume is available.
3572 nsRefPtr<nsDOMDeviceStorage> ds;
3573 nsAdoptingString prefStorageName =
3574 mozilla::Preferences::GetString("device.storage.writable.name");
3575 if (prefStorageName) {
3576 aStorageName = prefStorageName;
3577 return;
3580 // No preferred storage, we'll use the first one (which should be sdcard).
3582 VolumeNameArray volNames;
3583 GetOrderedVolumeNames(volNames);
3584 if (volNames.Length() > 0) {
3585 aStorageName = volNames[0];
3586 return;
3589 // No volumes available, return the empty string. This is normal for
3590 // b2g-desktop.
3591 aStorageName.Truncate();
3594 bool
3595 nsDOMDeviceStorage::IsAvailable()
3597 nsRefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
3598 return dsf->IsAvailable();
3601 NS_IMETHODIMP
3602 nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval)
3604 ErrorResult rv;
3605 nsRefPtr<DOMRequest> request = Add(aBlob, rv);
3606 request.forget(_retval);
3607 return rv.ErrorCode();
3610 already_AddRefed<DOMRequest>
3611 nsDOMDeviceStorage::Add(nsIDOMBlob* aBlob, ErrorResult& aRv)
3613 if (!aBlob) {
3614 return nullptr;
3617 nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
3618 if (!mimeSvc) {
3619 aRv.Throw(NS_ERROR_FAILURE);
3620 return nullptr;
3623 // if mimeType isn't set, we will not get a correct
3624 // extension, and AddNamed() will fail. This will post an
3625 // onerror to the requestee.
3626 nsString mimeType;
3627 aBlob->GetType(mimeType);
3629 nsCString extension;
3630 mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
3631 EmptyCString(), extension);
3632 // if extension is null here, we will ignore it for now.
3633 // AddNamed() will check the file path and fail. This
3634 // will post an onerror to the requestee.
3636 // possible race here w/ unique filename
3637 char buffer[32];
3638 NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);
3640 nsAutoCString path;
3641 path.Assign(nsDependentCString(buffer));
3642 path.Append('.');
3643 path.Append(extension);
3645 return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), aRv);
3648 NS_IMETHODIMP
3649 nsDOMDeviceStorage::AddNamed(nsIDOMBlob *aBlob,
3650 const nsAString & aPath,
3651 nsIDOMDOMRequest * *_retval)
3653 ErrorResult rv;
3654 nsRefPtr<DOMRequest> request = AddNamed(aBlob, aPath, rv);
3655 request.forget(_retval);
3656 return rv.ErrorCode();
3659 already_AddRefed<DOMRequest>
3660 nsDOMDeviceStorage::AddNamed(nsIDOMBlob* aBlob, const nsAString& aPath,
3661 ErrorResult& aRv)
3663 return AddOrAppendNamed(aBlob, aPath,
3664 DEVICE_STORAGE_REQUEST_CREATE, aRv);
3667 already_AddRefed<DOMRequest>
3668 nsDOMDeviceStorage::AppendNamed(nsIDOMBlob* aBlob, const nsAString& aPath,
3669 ErrorResult& aRv)
3671 return AddOrAppendNamed(aBlob, aPath,
3672 DEVICE_STORAGE_REQUEST_APPEND, aRv);
3676 already_AddRefed<DOMRequest>
3677 nsDOMDeviceStorage::AddOrAppendNamed(nsIDOMBlob* aBlob, const nsAString& aPath,
3678 const int32_t aRequestType, ErrorResult& aRv)
3680 MOZ_ASSERT(NS_IsMainThread());
3682 // if the blob is null here, bail
3683 if (!aBlob) {
3684 return nullptr;
3687 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3688 if (!win) {
3689 aRv.Throw(NS_ERROR_UNEXPECTED);
3690 return nullptr;
3693 DeviceStorageTypeChecker* typeChecker
3694 = DeviceStorageTypeChecker::CreateOrGet();
3695 if (!typeChecker) {
3696 aRv.Throw(NS_ERROR_FAILURE);
3697 return nullptr;
3700 nsCOMPtr<nsIRunnable> r;
3701 nsresult rv;
3703 if (IsFullPath(aPath)) {
3704 nsString storagePath;
3705 nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
3706 if (!ds) {
3707 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3708 r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
3709 rv = NS_DispatchToCurrentThread(r);
3710 if (NS_FAILED(rv)) {
3711 aRv.Throw(rv);
3713 return request.forget();
3716 return ds->AddOrAppendNamed(aBlob, storagePath,
3717 aRequestType, aRv);
3720 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3722 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3723 mStorageName,
3724 aPath);
3725 if (!dsf->IsSafePath()) {
3726 r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
3727 } else if (!typeChecker->Check(mStorageType, dsf->mFile) ||
3728 !typeChecker->Check(mStorageType, aBlob)) {
3729 r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE);
3730 } else if (aRequestType == DEVICE_STORAGE_REQUEST_APPEND ||
3731 aRequestType == DEVICE_STORAGE_REQUEST_CREATE) {
3732 r = new DeviceStorageRequest(DeviceStorageRequestType(aRequestType),
3733 win, mPrincipal, dsf, request, aBlob);
3734 } else {
3735 aRv.Throw(NS_ERROR_UNEXPECTED);
3736 return nullptr;
3739 rv = NS_DispatchToCurrentThread(r);
3740 if (NS_FAILED(rv)) {
3741 aRv.Throw(rv);
3743 return request.forget();
3746 NS_IMETHODIMP
3747 nsDOMDeviceStorage::Get(const nsAString& aPath, nsIDOMDOMRequest** aRetval)
3749 ErrorResult rv;
3750 nsRefPtr<DOMRequest> request = Get(aPath, rv);
3751 request.forget(aRetval);
3752 return rv.ErrorCode();
3755 NS_IMETHODIMP
3756 nsDOMDeviceStorage::GetEditable(const nsAString& aPath,
3757 nsIDOMDOMRequest** aRetval)
3759 ErrorResult rv;
3760 nsRefPtr<DOMRequest> request = GetEditable(aPath, rv);
3761 request.forget(aRetval);
3762 return rv.ErrorCode();
3765 already_AddRefed<DOMRequest>
3766 nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
3767 ErrorResult& aRv)
3769 MOZ_ASSERT(NS_IsMainThread());
3771 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3772 if (!win) {
3773 aRv.Throw(NS_ERROR_UNEXPECTED);
3774 return nullptr;
3777 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3779 if (IsFullPath(aPath)) {
3780 nsString storagePath;
3781 nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
3782 if (!ds) {
3783 nsCOMPtr<nsIRunnable> r =
3784 new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
3785 nsresult rv = NS_DispatchToCurrentThread(r);
3786 if (NS_FAILED(rv)) {
3787 aRv.Throw(rv);
3789 return request.forget();
3791 ds->GetInternal(win, storagePath, request, aEditable);
3792 return request.forget();
3794 GetInternal(win, aPath, request, aEditable);
3795 return request.forget();
3798 void
3799 nsDOMDeviceStorage::GetInternal(nsPIDOMWindow *aWin,
3800 const nsAString& aPath,
3801 DOMRequest* aRequest,
3802 bool aEditable)
3804 MOZ_ASSERT(NS_IsMainThread());
3806 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3807 mStorageName,
3808 aPath);
3809 dsf->SetEditable(aEditable);
3811 nsCOMPtr<nsIRunnable> r;
3812 if (!dsf->IsSafePath()) {
3813 r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
3814 } else {
3815 r = new DeviceStorageRequest(aEditable ? DEVICE_STORAGE_REQUEST_WRITE
3816 : DEVICE_STORAGE_REQUEST_READ,
3817 aWin, mPrincipal, dsf, aRequest);
3819 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(r);
3820 MOZ_ASSERT(NS_SUCCEEDED(rv));
3823 NS_IMETHODIMP
3824 nsDOMDeviceStorage::Delete(const nsAString& aPath, nsIDOMDOMRequest** aRetval)
3826 ErrorResult rv;
3827 nsRefPtr<DOMRequest> request = Delete(aPath, rv);
3828 request.forget(aRetval);
3829 return rv.ErrorCode();
3832 already_AddRefed<DOMRequest>
3833 nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
3835 MOZ_ASSERT(NS_IsMainThread());
3837 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3838 if (!win) {
3839 aRv.Throw(NS_ERROR_UNEXPECTED);
3840 return nullptr;
3843 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3845 if (IsFullPath(aPath)) {
3846 nsString storagePath;
3847 nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
3848 if (!ds) {
3849 nsCOMPtr<nsIRunnable> r =
3850 new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
3851 nsresult rv = NS_DispatchToCurrentThread(r);
3852 if (NS_FAILED(rv)) {
3853 aRv.Throw(rv);
3855 return request.forget();
3857 ds->DeleteInternal(win, storagePath, request);
3858 return request.forget();
3860 DeleteInternal(win, aPath, request);
3861 return request.forget();
3864 void
3865 nsDOMDeviceStorage::DeleteInternal(nsPIDOMWindow *aWin,
3866 const nsAString& aPath,
3867 DOMRequest* aRequest)
3869 MOZ_ASSERT(NS_IsMainThread());
3871 nsCOMPtr<nsIRunnable> r;
3872 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3873 mStorageName,
3874 aPath);
3875 if (!dsf->IsSafePath()) {
3876 r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED);
3877 } else {
3878 r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_DELETE,
3879 aWin, mPrincipal, dsf, aRequest);
3881 DebugOnly<nsresult> rv = NS_DispatchToCurrentThread(r);
3882 MOZ_ASSERT(NS_SUCCEEDED(rv));
3885 NS_IMETHODIMP
3886 nsDOMDeviceStorage::FreeSpace(nsIDOMDOMRequest** aRetval)
3888 ErrorResult rv;
3889 nsRefPtr<DOMRequest> request = FreeSpace(rv);
3890 request.forget(aRetval);
3891 return rv.ErrorCode();
3894 already_AddRefed<DOMRequest>
3895 nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
3897 MOZ_ASSERT(NS_IsMainThread());
3899 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3900 if (!win) {
3901 aRv.Throw(NS_ERROR_UNEXPECTED);
3902 return nullptr;
3905 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3907 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3908 mStorageName);
3909 nsCOMPtr<nsIRunnable> r
3910 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FREE_SPACE,
3911 win, mPrincipal, dsf, request);
3912 nsresult rv = NS_DispatchToCurrentThread(r);
3913 if (NS_FAILED(rv)) {
3914 aRv.Throw(rv);
3916 return request.forget();
3919 NS_IMETHODIMP
3920 nsDOMDeviceStorage::UsedSpace(nsIDOMDOMRequest** aRetval)
3922 ErrorResult rv;
3923 nsRefPtr<DOMRequest> request = UsedSpace(rv);
3924 request.forget(aRetval);
3925 return rv.ErrorCode();
3928 already_AddRefed<DOMRequest>
3929 nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
3931 MOZ_ASSERT(NS_IsMainThread());
3933 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3934 if (!win) {
3935 aRv.Throw(NS_ERROR_UNEXPECTED);
3936 return nullptr;
3939 DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache
3940 = DeviceStorageUsedSpaceCache::CreateOrGet();
3941 MOZ_ASSERT(usedSpaceCache);
3943 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3945 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3946 mStorageName);
3947 nsCOMPtr<nsIRunnable> r
3948 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_USED_SPACE,
3949 win, mPrincipal, dsf, request);
3950 nsresult rv = NS_DispatchToCurrentThread(r);
3951 if (NS_FAILED(rv)) {
3952 aRv.Throw(rv);
3954 return request.forget();
3957 NS_IMETHODIMP
3958 nsDOMDeviceStorage::Available(nsIDOMDOMRequest** aRetval)
3960 ErrorResult rv;
3961 nsRefPtr<DOMRequest> request = Available(rv);
3962 request.forget(aRetval);
3963 return rv.ErrorCode();
3966 already_AddRefed<DOMRequest>
3967 nsDOMDeviceStorage::Available(ErrorResult& aRv)
3969 MOZ_ASSERT(NS_IsMainThread());
3971 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3972 if (!win) {
3973 aRv.Throw(NS_ERROR_UNEXPECTED);
3974 return nullptr;
3977 nsRefPtr<DOMRequest> request = new DOMRequest(win);
3979 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
3980 mStorageName);
3981 nsCOMPtr<nsIRunnable> r
3982 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_AVAILABLE,
3983 win, mPrincipal, dsf, request);
3984 nsresult rv = NS_DispatchToCurrentThread(r);
3985 if (NS_FAILED(rv)) {
3986 aRv.Throw(rv);
3988 return request.forget();
3991 already_AddRefed<DOMRequest>
3992 nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
3994 MOZ_ASSERT(NS_IsMainThread());
3996 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
3997 if (!win) {
3998 aRv.Throw(NS_ERROR_UNEXPECTED);
3999 return nullptr;
4002 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4004 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4005 mStorageName);
4006 nsCOMPtr<nsIRunnable> r
4007 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_STATUS,
4008 win, mPrincipal, dsf, request);
4009 nsresult rv = NS_DispatchToCurrentThread(r);
4010 if (NS_FAILED(rv)) {
4011 aRv.Throw(rv);
4013 return request.forget();
4016 already_AddRefed<DOMRequest>
4017 nsDOMDeviceStorage::Format(ErrorResult& aRv)
4019 MOZ_ASSERT(NS_IsMainThread());
4021 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4022 if (!win) {
4023 aRv.Throw(NS_ERROR_UNEXPECTED);
4024 return nullptr;
4027 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4029 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4030 mStorageName);
4031 nsCOMPtr<nsIRunnable> r
4032 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FORMAT,
4033 win, mPrincipal, dsf, request);
4034 nsresult rv = NS_DispatchToCurrentThread(r);
4035 if (NS_FAILED(rv)) {
4036 aRv.Throw(rv);
4038 return request.forget();
4041 already_AddRefed<DOMRequest>
4042 nsDOMDeviceStorage::Mount(ErrorResult& aRv)
4044 MOZ_ASSERT(NS_IsMainThread());
4046 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4047 if (!win) {
4048 aRv.Throw(NS_ERROR_UNEXPECTED);
4049 return nullptr;
4052 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4054 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4055 mStorageName);
4056 nsCOMPtr<nsIRunnable> r
4057 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_MOUNT,
4058 win, mPrincipal, dsf, request);
4059 nsresult rv = NS_DispatchToCurrentThread(r);
4060 if (NS_FAILED(rv)) {
4061 aRv.Throw(rv);
4063 return request.forget();
4066 already_AddRefed<DOMRequest>
4067 nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
4069 MOZ_ASSERT(NS_IsMainThread());
4071 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4072 if (!win) {
4073 aRv.Throw(NS_ERROR_UNEXPECTED);
4074 return nullptr;
4077 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4079 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4080 mStorageName);
4081 nsCOMPtr<nsIRunnable> r
4082 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_UNMOUNT,
4083 win, mPrincipal, dsf, request);
4084 nsresult rv = NS_DispatchToCurrentThread(r);
4085 if (NS_FAILED(rv)) {
4086 aRv.Throw(rv);
4088 return request.forget();
4091 NS_IMETHODIMP
4092 nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
4093 DeviceStorageFileDescriptor* aDSFileDescriptor,
4094 nsIDOMDOMRequest** aRequest)
4096 MOZ_ASSERT(NS_IsMainThread());
4097 MOZ_ASSERT(aDSFileDescriptor);
4099 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4100 if (!win) {
4101 return NS_ERROR_UNEXPECTED;
4104 DeviceStorageTypeChecker* typeChecker
4105 = DeviceStorageTypeChecker::CreateOrGet();
4106 if (!typeChecker) {
4107 return NS_ERROR_FAILURE;
4110 nsCOMPtr<nsIRunnable> r;
4111 nsresult rv;
4113 if (IsFullPath(aPath)) {
4114 nsString storagePath;
4115 nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
4116 if (!ds) {
4117 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4118 r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN);
4119 rv = NS_DispatchToCurrentThread(r);
4120 if (NS_FAILED(rv)) {
4121 return rv;
4123 request.forget(aRequest);
4124 return NS_OK;
4126 return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRequest);
4129 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4131 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4132 mStorageName,
4133 aPath);
4134 if (!dsf->IsSafePath()) {
4135 r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED);
4136 } else if (!typeChecker->Check(mStorageType, dsf->mFile)) {
4137 r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE);
4138 } else {
4139 r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_CREATEFD,
4140 win, mPrincipal, dsf, request,
4141 aDSFileDescriptor);
4144 rv = NS_DispatchToCurrentThread(r);
4145 if (NS_FAILED(rv)) {
4146 return rv;
4148 request.forget(aRequest);
4149 return NS_OK;
4152 bool
4153 nsDOMDeviceStorage::Default()
4155 nsString defaultStorageName;
4156 GetDefaultStorageName(mStorageType, defaultStorageName);
4157 return mStorageName.Equals(defaultStorageName);
4160 bool
4161 nsDOMDeviceStorage::CanBeFormatted()
4163 // Currently, any volume which can be shared can also be formatted.
4164 return mIsShareable;
4167 bool
4168 nsDOMDeviceStorage::CanBeMounted()
4170 // Currently, any volume which can be shared can also be mounted/unmounted.
4171 return mIsShareable;
4174 bool
4175 nsDOMDeviceStorage::CanBeShared()
4177 return mIsShareable;
4180 already_AddRefed<Promise>
4181 nsDOMDeviceStorage::GetRoot(ErrorResult& aRv)
4183 if (!mFileSystem) {
4184 mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName);
4185 mFileSystem->Init(this);
4187 return mozilla::dom::Directory::GetRoot(mFileSystem, aRv);
4190 NS_IMETHODIMP
4191 nsDOMDeviceStorage::GetDefault(bool* aDefault)
4193 *aDefault = Default();
4194 return NS_OK;
4197 NS_IMETHODIMP
4198 nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName)
4200 aStorageName = mStorageName;
4201 return NS_OK;
4204 already_AddRefed<DOMCursor>
4205 nsDOMDeviceStorage::Enumerate(const nsAString& aPath,
4206 const EnumerationParameters& aOptions,
4207 ErrorResult& aRv)
4209 return EnumerateInternal(aPath, aOptions, false, aRv);
4212 already_AddRefed<DOMCursor>
4213 nsDOMDeviceStorage::EnumerateEditable(const nsAString& aPath,
4214 const EnumerationParameters& aOptions,
4215 ErrorResult& aRv)
4217 return EnumerateInternal(aPath, aOptions, true, aRv);
4221 already_AddRefed<DOMCursor>
4222 nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
4223 const EnumerationParameters& aOptions,
4224 bool aEditable, ErrorResult& aRv)
4226 MOZ_ASSERT(NS_IsMainThread());
4228 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4229 if (!win) {
4230 aRv.Throw(NS_ERROR_UNEXPECTED);
4231 return nullptr;
4234 PRTime since = 0;
4235 if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
4236 since = PRTime(aOptions.mSince.Value().TimeStamp());
4239 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4240 mStorageName,
4241 aPath,
4242 EmptyString());
4243 dsf->SetEditable(aEditable);
4245 nsRefPtr<nsDOMDeviceStorageCursor> cursor
4246 = new nsDOMDeviceStorageCursor(win, mPrincipal, dsf, since);
4247 nsRefPtr<DeviceStorageCursorRequest> r
4248 = new DeviceStorageCursorRequest(cursor);
4250 if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
4251 r->Allow(JS::UndefinedHandleValue);
4252 return cursor.forget();
4255 nsContentPermissionUtils::AskPermission(r, win);
4257 return cursor.forget();
4260 #ifdef MOZ_WIDGET_GONK
4261 void
4262 nsDOMDeviceStorage::DispatchStatusChangeEvent(nsAString& aStatus)
4264 if (aStatus == mLastStatus) {
4265 // We've already sent this status, don't bother sending it again.
4266 return;
4268 mLastStatus = aStatus;
4270 DeviceStorageChangeEventInit init;
4271 init.mBubbles = true;
4272 init.mCancelable = false;
4273 init.mPath = mStorageName;
4274 init.mReason = aStatus;
4276 nsRefPtr<DeviceStorageChangeEvent> event =
4277 DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("change"),
4278 init);
4279 event->SetTrusted(true);
4281 bool ignore;
4282 DispatchEvent(event, &ignore);
4285 void
4286 nsDOMDeviceStorage::DispatchStorageStatusChangeEvent(nsAString& aStorageStatus)
4288 if (aStorageStatus == mLastStorageStatus) {
4289 // We've already sent this status, don't bother sending it again.
4290 return;
4292 mLastStorageStatus = aStorageStatus;
4294 DeviceStorageChangeEventInit init;
4295 init.mBubbles = true;
4296 init.mCancelable = false;
4297 init.mPath = mStorageName;
4298 init.mReason = aStorageStatus;
4300 nsRefPtr<DeviceStorageChangeEvent> event =
4301 DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("storage-state-change"),
4302 init);
4303 event->SetTrusted(true);
4305 bool ignore;
4306 DispatchEvent(event, &ignore);
4308 #endif
4310 NS_IMETHODIMP
4311 nsDOMDeviceStorage::Observe(nsISupports *aSubject,
4312 const char *aTopic,
4313 const char16_t *aData)
4315 MOZ_ASSERT(NS_IsMainThread());
4317 if (!strcmp(aTopic, kFileWatcherUpdate)) {
4319 DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
4320 Notify(NS_ConvertUTF16toUTF8(aData).get(), file);
4321 return NS_OK;
4323 if (!strcmp(aTopic, "disk-space-watcher")) {
4324 // 'disk-space-watcher' notifications are sent when there is a modification
4325 // of a file in a specific location while a low device storage situation
4326 // exists or after recovery of a low storage situation. For Firefox OS,
4327 // these notifications are specific for apps storage.
4328 nsRefPtr<DeviceStorageFile> file =
4329 new DeviceStorageFile(mStorageType, mStorageName);
4330 if (!NS_strcmp(aData, MOZ_UTF16("full"))) {
4331 Notify("low-disk-space", file);
4332 } else if (!NS_strcmp(aData, MOZ_UTF16("free"))) {
4333 Notify("available-disk-space", file);
4335 return NS_OK;
4338 #ifdef MOZ_WIDGET_GONK
4339 else if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
4340 // We invalidate the used space cache for the volume that actually changed
4341 // state.
4342 nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
4343 if (!vol) {
4344 return NS_OK;
4346 nsString volName;
4347 vol->GetName(volName);
4349 DeviceStorageUsedSpaceCache* usedSpaceCache
4350 = DeviceStorageUsedSpaceCache::CreateOrGet();
4351 MOZ_ASSERT(usedSpaceCache);
4352 usedSpaceCache->Invalidate(volName);
4354 if (!volName.Equals(mStorageName)) {
4355 // Not our volume - we can ignore.
4356 return NS_OK;
4359 nsRefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
4360 nsString status, storageStatus;
4362 // Get Status (one of "available, unavailable, shared")
4363 dsf->GetStatus(status);
4364 DispatchStatusChangeEvent(status);
4366 // Get real volume status (defined in dom/system/gonk/nsIVolume.idl)
4367 dsf->GetStorageStatus(storageStatus);
4368 DispatchStorageStatusChangeEvent(storageStatus);
4369 return NS_OK;
4371 #endif
4372 return NS_OK;
4375 nsresult
4376 nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
4378 if (!mAllowedToWatchFile) {
4379 return NS_OK;
4382 if (!mStorageType.Equals(aFile->mStorageType) ||
4383 !mStorageName.Equals(aFile->mStorageName)) {
4384 // Ignore this
4385 return NS_OK;
4388 DeviceStorageChangeEventInit init;
4389 init.mBubbles = true;
4390 init.mCancelable = false;
4391 aFile->GetFullPath(init.mPath);
4392 init.mReason.AssignWithConversion(aReason);
4394 nsRefPtr<DeviceStorageChangeEvent> event =
4395 DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("change"),
4396 init);
4397 event->SetTrusted(true);
4399 bool ignore;
4400 DispatchEvent(event, &ignore);
4401 return NS_OK;
4404 NS_IMETHODIMP
4405 nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
4406 nsIDOMEventListener *aListener,
4407 bool aUseCapture,
4408 bool aWantsUntrusted,
4409 uint8_t aArgc)
4411 MOZ_ASSERT(NS_IsMainThread());
4413 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4414 if (!win) {
4415 return NS_ERROR_UNEXPECTED;
4418 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4419 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4420 mStorageName);
4421 nsCOMPtr<nsIRunnable> r
4422 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
4423 win, mPrincipal, dsf, request, this);
4424 nsresult rv = NS_DispatchToCurrentThread(r);
4425 if (NS_WARN_IF(NS_FAILED(rv))) {
4426 return rv;
4429 return DOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture,
4430 aWantsUntrusted, aArgc);
4433 void
4434 nsDOMDeviceStorage::AddEventListener(const nsAString & aType,
4435 EventListener *aListener,
4436 bool aUseCapture,
4437 const Nullable<bool>& aWantsUntrusted,
4438 ErrorResult& aRv)
4440 MOZ_ASSERT(NS_IsMainThread());
4442 nsCOMPtr<nsPIDOMWindow> win = GetOwner();
4443 if (!win) {
4444 aRv.Throw(NS_ERROR_UNEXPECTED);
4445 return;
4448 nsRefPtr<DOMRequest> request = new DOMRequest(win);
4449 nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
4450 mStorageName);
4451 nsCOMPtr<nsIRunnable> r
4452 = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH,
4453 win, mPrincipal, dsf, request, this);
4454 nsresult rv = NS_DispatchToCurrentThread(r);
4455 if (NS_WARN_IF(NS_FAILED(rv))) {
4456 return;
4458 DOMEventTargetHelper::AddEventListener(aType, aListener, aUseCapture,
4459 aWantsUntrusted, aRv);
4462 NS_IMETHODIMP
4463 nsDOMDeviceStorage::AddSystemEventListener(const nsAString & aType,
4464 nsIDOMEventListener *aListener,
4465 bool aUseCapture,
4466 bool aWantsUntrusted,
4467 uint8_t aArgc)
4469 if (!mIsWatchingFile) {
4470 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
4471 obs->AddObserver(this, kFileWatcherUpdate, false);
4472 mIsWatchingFile = true;
4475 return nsDOMDeviceStorage::AddEventListener(aType, aListener, aUseCapture,
4476 aWantsUntrusted, aArgc);
4479 NS_IMETHODIMP
4480 nsDOMDeviceStorage::RemoveEventListener(const nsAString & aType,
4481 nsIDOMEventListener *aListener,
4482 bool aUseCapture)
4484 DOMEventTargetHelper::RemoveEventListener(aType, aListener, false);
4486 if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
4487 mIsWatchingFile = false;
4488 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
4489 obs->RemoveObserver(this, kFileWatcherUpdate);
4491 return NS_OK;
4494 void
4495 nsDOMDeviceStorage::RemoveEventListener(const nsAString& aType,
4496 EventListener* aListener,
4497 bool aCapture,
4498 ErrorResult& aRv)
4500 DOMEventTargetHelper::RemoveEventListener(aType, aListener, aCapture, aRv);
4502 if (mIsWatchingFile && !HasListenersFor(nsGkAtoms::onchange)) {
4503 mIsWatchingFile = false;
4504 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
4505 obs->RemoveObserver(this, kFileWatcherUpdate);
4509 NS_IMETHODIMP
4510 nsDOMDeviceStorage::RemoveSystemEventListener(const nsAString & aType,
4511 nsIDOMEventListener *aListener,
4512 bool aUseCapture)
4514 return nsDOMDeviceStorage::RemoveEventListener(aType, aListener, aUseCapture);
4517 NS_IMETHODIMP
4518 nsDOMDeviceStorage::DispatchEvent(nsIDOMEvent *aEvt,
4519 bool *aRetval)
4521 return DOMEventTargetHelper::DispatchEvent(aEvt, aRetval);
4524 EventTarget*
4525 nsDOMDeviceStorage::GetTargetForDOMEvent()
4527 return DOMEventTargetHelper::GetTargetForDOMEvent();
4530 EventTarget *
4531 nsDOMDeviceStorage::GetTargetForEventTargetChain()
4533 return DOMEventTargetHelper::GetTargetForEventTargetChain();
4536 nsresult
4537 nsDOMDeviceStorage::PreHandleEvent(EventChainPreVisitor& aVisitor)
4539 return DOMEventTargetHelper::PreHandleEvent(aVisitor);
4542 nsresult
4543 nsDOMDeviceStorage::WillHandleEvent(EventChainPostVisitor& aVisitor)
4545 return DOMEventTargetHelper::WillHandleEvent(aVisitor);
4548 nsresult
4549 nsDOMDeviceStorage::PostHandleEvent(EventChainPostVisitor& aVisitor)
4551 return DOMEventTargetHelper::PostHandleEvent(aVisitor);
4554 nsresult
4555 nsDOMDeviceStorage::DispatchDOMEvent(WidgetEvent* aEvent,
4556 nsIDOMEvent* aDOMEvent,
4557 nsPresContext* aPresContext,
4558 nsEventStatus* aEventStatus)
4560 return DOMEventTargetHelper::DispatchDOMEvent(aEvent,
4561 aDOMEvent,
4562 aPresContext,
4563 aEventStatus);
4566 EventListenerManager*
4567 nsDOMDeviceStorage::GetOrCreateListenerManager()
4569 return DOMEventTargetHelper::GetOrCreateListenerManager();
4572 EventListenerManager*
4573 nsDOMDeviceStorage::GetExistingListenerManager() const
4575 return DOMEventTargetHelper::GetExistingListenerManager();
4578 nsIScriptContext *
4579 nsDOMDeviceStorage::GetContextForEventHandlers(nsresult *aRv)
4581 return DOMEventTargetHelper::GetContextForEventHandlers(aRv);
4584 JSContext *
4585 nsDOMDeviceStorage::GetJSContextForEventHandlers()
4587 return DOMEventTargetHelper::GetJSContextForEventHandlers();
4590 NS_IMPL_EVENT_HANDLER(nsDOMDeviceStorage, change)