/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsDeviceStorage.h" #include "mozilla/Attributes.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/DeviceStorageBinding.h" #include "mozilla/dom/DeviceStorageChangeEvent.h" #include "mozilla/dom/DeviceStorageFileSystem.h" #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h" #include "mozilla/dom/Directory.h" #include "mozilla/dom/FileSystemUtils.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/PBrowserChild.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/LazyIdleThread.h" #include "mozilla/Preferences.h" #include "mozilla/Scoped.h" #include "mozilla/Services.h" #include "nsArrayUtils.h" #include "nsAutoPtr.h" #include "nsGlobalWindow.h" #include "nsServiceManagerUtils.h" #include "nsIFile.h" #include "nsIDirectoryEnumerator.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" #include "nsNetUtil.h" #include "nsCycleCollectionParticipant.h" #include "nsIPrincipal.h" #include "nsJSUtils.h" #include "nsContentUtils.h" #include "nsXULAppAPI.h" #include "DeviceStorageFileDescriptor.h" #include "DeviceStorageRequestChild.h" #include "nsCRT.h" #include "nsIObserverService.h" #include "nsIMIMEService.h" #include "nsCExternalHandlerService.h" #include "nsIPermissionManager.h" #include "nsIStringBundle.h" #include "nsISupportsPrimitives.h" #include "nsIDocument.h" #include "nsPrintfCString.h" #include #include "private/pprio.h" #include "nsContentPermissionHelper.h" #include "mozilla/dom/DeviceStorageBinding.h" // Microsoft's API Name hackery sucks #undef CreateEvent #ifdef MOZ_WIDGET_ANDROID #include "AndroidBridge.h" #endif #ifdef MOZ_WIDGET_GONK #include "nsIVolume.h" #include "nsIVolumeService.h" #endif #define DEVICESTORAGE_PROPERTIES \ "chrome://global/content/devicestorage.properties" #define DEFAULT_THREAD_TIMEOUT_MS 30000 #define PREF_STORAGE_WRITABLE_NAME \ "device.storage.writable.name" #define STORAGE_CHANGE_EVENT "change" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::devicestorage; using namespace mozilla::ipc; #include "nsDirectoryServiceDefs.h" const char* kFileWatcherUpdate = "file-watcher-update"; const char* kFileWatcherNotify = "file-watcher-notify"; const char *kDownloadWatcherNotify = "download-watcher-notify"; namespace mozilla { MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close); } StaticAutoPtr DeviceStorageUsedSpaceCache::sDeviceStorageUsedSpaceCache; DeviceStorageUsedSpaceCache::DeviceStorageUsedSpaceCache() { MOZ_ASSERT(NS_IsMainThread()); mIOThread = new LazyIdleThread( DEFAULT_THREAD_TIMEOUT_MS, NS_LITERAL_CSTRING("DeviceStorageUsedSpaceCache I/O")); } DeviceStorageUsedSpaceCache::~DeviceStorageUsedSpaceCache() { } DeviceStorageUsedSpaceCache* DeviceStorageUsedSpaceCache::CreateOrGet() { if (sDeviceStorageUsedSpaceCache) { return sDeviceStorageUsedSpaceCache; } MOZ_ASSERT(NS_IsMainThread()); sDeviceStorageUsedSpaceCache = new DeviceStorageUsedSpaceCache(); ClearOnShutdown(&sDeviceStorageUsedSpaceCache); return sDeviceStorageUsedSpaceCache; } already_AddRefed DeviceStorageUsedSpaceCache::GetCacheEntry(const nsAString& aStorageName) { nsTArray>::size_type numEntries = mCacheEntries.Length(); nsTArray>::index_type i; for (i = 0; i < numEntries; i++) { nsRefPtr& cacheEntry = mCacheEntries[i]; if (cacheEntry->mStorageName.Equals(aStorageName)) { nsRefPtr addRefedCacheEntry = cacheEntry; return addRefedCacheEntry.forget(); } } return nullptr; } static int64_t GetFreeBytes(const nsAString& aStorageName) { // This function makes the assumption that the various types // are all stored on the same filesystem. So we use pictures. nsRefPtr dsf(new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_PICTURES), aStorageName)); int64_t freeBytes = 0; dsf->GetDiskFreeSpace(&freeBytes); return freeBytes; } nsresult DeviceStorageUsedSpaceCache::AccumUsedSizes(const nsAString& aStorageName, uint64_t* aPicturesSoFar, uint64_t* aVideosSoFar, uint64_t* aMusicSoFar, uint64_t* aTotalSoFar) { nsRefPtr cacheEntry = GetCacheEntry(aStorageName); if (!cacheEntry || cacheEntry->mDirty) { return NS_ERROR_NOT_AVAILABLE; } int64_t freeBytes = GetFreeBytes(cacheEntry->mStorageName); if (freeBytes != cacheEntry->mFreeBytes) { // Free space changed, so our cached results are no longer valid. return NS_ERROR_NOT_AVAILABLE; } *aPicturesSoFar += cacheEntry->mPicturesUsedSize; *aVideosSoFar += cacheEntry->mVideosUsedSize; *aMusicSoFar += cacheEntry->mMusicUsedSize; *aTotalSoFar += cacheEntry->mTotalUsedSize; return NS_OK; } void DeviceStorageUsedSpaceCache::SetUsedSizes(const nsAString& aStorageName, uint64_t aPictureSize, uint64_t aVideosSize, uint64_t aMusicSize, uint64_t aTotalUsedSize) { nsRefPtr cacheEntry = GetCacheEntry(aStorageName); if (!cacheEntry) { cacheEntry = new CacheEntry; cacheEntry->mStorageName = aStorageName; mCacheEntries.AppendElement(cacheEntry); } cacheEntry->mFreeBytes = GetFreeBytes(cacheEntry->mStorageName); cacheEntry->mPicturesUsedSize = aPictureSize; cacheEntry->mVideosUsedSize = aVideosSize; cacheEntry->mMusicUsedSize = aMusicSize; cacheEntry->mTotalUsedSize = aTotalUsedSize; cacheEntry->mDirty = false; } class GlobalDirs { private: ~GlobalDirs() {} public: NS_INLINE_DECL_REFCOUNTING(GlobalDirs) #if !defined(MOZ_WIDGET_GONK) nsCOMPtr pictures; nsCOMPtr videos; nsCOMPtr music; nsCOMPtr sdcard; #endif nsCOMPtr apps; nsCOMPtr crashes; nsCOMPtr overrideRootDir; }; static StaticRefPtr sDirs; StaticAutoPtr DeviceStorageTypeChecker::sDeviceStorageTypeChecker; DeviceStorageTypeChecker::DeviceStorageTypeChecker() { } DeviceStorageTypeChecker::~DeviceStorageTypeChecker() { } DeviceStorageTypeChecker* DeviceStorageTypeChecker::CreateOrGet() { if (sDeviceStorageTypeChecker) { return sDeviceStorageTypeChecker; } MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr stringService = mozilla::services::GetStringBundleService(); if (!stringService) { return nullptr; } nsCOMPtr filterBundle; if (NS_FAILED(stringService->CreateBundle(DEVICESTORAGE_PROPERTIES, getter_AddRefs(filterBundle)))) { return nullptr; } DeviceStorageTypeChecker* result = new DeviceStorageTypeChecker(); result->InitFromBundle(filterBundle); sDeviceStorageTypeChecker = result; ClearOnShutdown(&sDeviceStorageTypeChecker); return result; } void DeviceStorageTypeChecker::InitFromBundle(nsIStringBundle* aBundle) { aBundle->GetStringFromName( NS_ConvertASCIItoUTF16(DEVICESTORAGE_PICTURES).get(), getter_Copies(mPicturesExtensions)); aBundle->GetStringFromName( NS_ConvertASCIItoUTF16(DEVICESTORAGE_MUSIC).get(), getter_Copies(mMusicExtensions)); aBundle->GetStringFromName( NS_ConvertASCIItoUTF16(DEVICESTORAGE_VIDEOS).get(), getter_Copies(mVideosExtensions)); } bool DeviceStorageTypeChecker::Check(const nsAString& aType, Blob* aBlob) { MOZ_ASSERT(aBlob); nsString mimeType; aBlob->GetType(mimeType); if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) { return StringBeginsWith(mimeType, NS_LITERAL_STRING("image/")); } if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) { return StringBeginsWith(mimeType, NS_LITERAL_STRING("video/")); } if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) { return StringBeginsWith(mimeType, NS_LITERAL_STRING("audio/")); } if (aType.EqualsLiteral(DEVICESTORAGE_APPS) || aType.EqualsLiteral(DEVICESTORAGE_SDCARD) || aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) { // Apps, crashes and sdcard have no restriction on mime types return true; } return false; } bool DeviceStorageTypeChecker::Check(const nsAString& aType, nsIFile* aFile) { if (!aFile) { return false; } nsString path; aFile->GetPath(path); return Check(aType, path); } bool DeviceStorageTypeChecker::Check(const nsAString& aType, const nsString& aPath) { if (aType.EqualsLiteral(DEVICESTORAGE_APPS) || aType.EqualsLiteral(DEVICESTORAGE_SDCARD) || aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) { // Apps, crashes and sdcard have no restrictions on what file extensions used. return true; } int32_t dotIdx = aPath.RFindChar(char16_t('.')); if (dotIdx == kNotFound) { return false; } nsAutoString extensionMatch; extensionMatch.Assign('*'); extensionMatch.Append(Substring(aPath, dotIdx)); extensionMatch.Append(';'); if (aType.EqualsLiteral(DEVICESTORAGE_PICTURES)) { return CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions); } if (aType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) { return CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions); } if (aType.EqualsLiteral(DEVICESTORAGE_MUSIC)) { return CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions); } return false; } void DeviceStorageTypeChecker::GetTypeFromFile(nsIFile* aFile, nsAString& aType) { MOZ_ASSERT(aFile); nsString path; aFile->GetPath(path); GetTypeFromFileName(path, aType); } void DeviceStorageTypeChecker::GetTypeFromFileName(const nsAString& aFileName, nsAString& aType) { aType.AssignLiteral(DEVICESTORAGE_SDCARD); nsString fileName(aFileName); int32_t dotIdx = fileName.RFindChar(char16_t('.')); if (dotIdx == kNotFound) { return; } nsAutoString extensionMatch; extensionMatch.Assign('*'); extensionMatch.Append(Substring(aFileName, dotIdx)); extensionMatch.Append(';'); if (CaseInsensitiveFindInReadable(extensionMatch, mPicturesExtensions)) { aType.AssignLiteral(DEVICESTORAGE_PICTURES); } else if (CaseInsensitiveFindInReadable(extensionMatch, mVideosExtensions)) { aType.AssignLiteral(DEVICESTORAGE_VIDEOS); } else if (CaseInsensitiveFindInReadable(extensionMatch, mMusicExtensions)) { aType.AssignLiteral(DEVICESTORAGE_MUSIC); } } nsresult DeviceStorageTypeChecker::GetPermissionForType(const nsAString& aType, nsACString& aPermissionResult) { if (!aType.EqualsLiteral(DEVICESTORAGE_PICTURES) && !aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) && !aType.EqualsLiteral(DEVICESTORAGE_MUSIC) && !aType.EqualsLiteral(DEVICESTORAGE_APPS) && !aType.EqualsLiteral(DEVICESTORAGE_SDCARD) && !aType.EqualsLiteral(DEVICESTORAGE_CRASHES)) { // unknown type return NS_ERROR_FAILURE; } aPermissionResult.AssignLiteral("device-storage:"); aPermissionResult.Append(NS_ConvertUTF16toUTF8(aType)); return NS_OK; } nsresult DeviceStorageTypeChecker::GetAccessForRequest( const DeviceStorageRequestType aRequestType, nsACString& aAccessResult) { switch(aRequestType) { case DEVICE_STORAGE_REQUEST_READ: case DEVICE_STORAGE_REQUEST_WATCH: case DEVICE_STORAGE_REQUEST_FREE_SPACE: case DEVICE_STORAGE_REQUEST_USED_SPACE: case DEVICE_STORAGE_REQUEST_AVAILABLE: case DEVICE_STORAGE_REQUEST_STATUS: aAccessResult.AssignLiteral("read"); break; case DEVICE_STORAGE_REQUEST_WRITE: case DEVICE_STORAGE_REQUEST_APPEND: case DEVICE_STORAGE_REQUEST_DELETE: case DEVICE_STORAGE_REQUEST_FORMAT: case DEVICE_STORAGE_REQUEST_MOUNT: case DEVICE_STORAGE_REQUEST_UNMOUNT: aAccessResult.AssignLiteral("write"); break; case DEVICE_STORAGE_REQUEST_CREATE: case DEVICE_STORAGE_REQUEST_CREATEFD: aAccessResult.AssignLiteral("create"); break; default: aAccessResult.AssignLiteral("undefined"); } return NS_OK; } static bool IsMediaType(const nsAString& aType) { return aType.EqualsLiteral(DEVICESTORAGE_PICTURES) || aType.EqualsLiteral(DEVICESTORAGE_VIDEOS) || aType.EqualsLiteral(DEVICESTORAGE_MUSIC) || aType.EqualsLiteral(DEVICESTORAGE_SDCARD); } //static bool DeviceStorageTypeChecker::IsVolumeBased(const nsAString& aType) { #ifdef MOZ_WIDGET_GONK // The apps and crashes aren't stored in the same place as the media, so // we only ever return a single apps object, and not an array // with one per volume (as is the case for the remaining // storage types). return IsMediaType(aType); #else return false; #endif } //static bool DeviceStorageTypeChecker::IsSharedMediaRoot(const nsAString& aType) { // This function determines if aType shares a root directory with the // other media types (so only applies to music, videos, pictures and sdcard). #ifdef MOZ_WIDGET_GONK return IsMediaType(aType); #else // For desktop, if the directories have been overridden, then they share // a common root. return IsMediaType(aType) && sDirs->overrideRootDir; #endif } NS_IMPL_ISUPPORTS(FileUpdateDispatcher, nsIObserver) mozilla::StaticRefPtr FileUpdateDispatcher::sSingleton; FileUpdateDispatcher* FileUpdateDispatcher::GetSingleton() { if (sSingleton) { return sSingleton; } sSingleton = new FileUpdateDispatcher(); nsCOMPtr obs = mozilla::services::GetObserverService(); obs->AddObserver(sSingleton, kFileWatcherNotify, false); obs->AddObserver(sSingleton, kDownloadWatcherNotify, false); ClearOnShutdown(&sSingleton); return sSingleton; } NS_IMETHODIMP FileUpdateDispatcher::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { nsRefPtr dsf; if (!strcmp(aTopic, kDownloadWatcherNotify)) { // aSubject will be an nsISupportsString with the native path to the file // in question. nsCOMPtr supportsString = do_QueryInterface(aSubject); if (!supportsString) { return NS_OK; } nsString path; nsresult rv = supportsString->GetData(path); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_OK; } // The downloader uses the sdcard storage type. nsString volName; #ifdef MOZ_WIDGET_GONK if (DeviceStorageTypeChecker::IsVolumeBased(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD))) { nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); if (NS_WARN_IF(!vs)) { return NS_OK; } nsCOMPtr vol; rv = vs->GetVolumeByPath(path, getter_AddRefs(vol)); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_OK; } rv = vol->GetName(volName); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_OK; } nsString mountPoint; rv = vol->GetMountPoint(mountPoint); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_OK; } if (!Substring(path, 0, mountPoint.Length()).Equals(mountPoint)) { return NS_OK; } path = Substring(path, mountPoint.Length() + 1); } #endif dsf = new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD), volName, path); } else if (!strcmp(aTopic, kFileWatcherNotify)) { dsf = static_cast(aSubject); } else { NS_WARNING("FileUpdateDispatcher: Unrecognized topic"); return NS_OK; } if (!dsf || !dsf->mFile) { NS_WARNING("FileUpdateDispatcher: Device storage file looks invalid!"); return NS_OK; } if (XRE_GetProcessType() != GeckoProcessType_Default) { // Child process. Forward the notification to the parent. ContentChild::GetSingleton() ->SendFilePathUpdateNotify(dsf->mStorageType, dsf->mStorageName, dsf->mPath, NS_ConvertUTF16toUTF8(aData)); return NS_OK; } // Multiple storage types may match the same files. So walk through each of // the storage types, and if the extension matches, tell them about it. nsCOMPtr obs = mozilla::services::GetObserverService(); if (DeviceStorageTypeChecker::IsSharedMediaRoot(dsf->mStorageType)) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); MOZ_ASSERT(typeChecker); static const nsLiteralString kMediaTypes[] = { NS_LITERAL_STRING(DEVICESTORAGE_SDCARD), NS_LITERAL_STRING(DEVICESTORAGE_PICTURES), NS_LITERAL_STRING(DEVICESTORAGE_VIDEOS), NS_LITERAL_STRING(DEVICESTORAGE_MUSIC), }; for (size_t i = 0; i < MOZ_ARRAY_LENGTH(kMediaTypes); i++) { nsRefPtr dsf2; if (typeChecker->Check(kMediaTypes[i], dsf->mPath)) { if (dsf->mStorageType.Equals(kMediaTypes[i])) { dsf2 = dsf; } else { dsf2 = new DeviceStorageFile(kMediaTypes[i], dsf->mStorageName, dsf->mPath); } obs->NotifyObservers(dsf2, kFileWatcherUpdate, aData); } } } else { obs->NotifyObservers(dsf, kFileWatcherUpdate, aData); } return NS_OK; } class IOEventComplete : public nsRunnable { public: IOEventComplete(DeviceStorageFile *aFile, const char *aType) : mFile(aFile) , mType(aType) { } ~IOEventComplete() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsString data; CopyASCIItoUTF16(mType, data); nsCOMPtr obs = mozilla::services::GetObserverService(); obs->NotifyObservers(mFile, kFileWatcherNotify, data.get()); DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet(); MOZ_ASSERT(usedSpaceCache); usedSpaceCache->Invalidate(mFile->mStorageName); return NS_OK; } private: nsRefPtr mFile; nsCString mType; }; DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, const nsAString& aStorageName, const nsAString& aRootDir, const nsAString& aPath) : mStorageType(aStorageType) , mStorageName(aStorageName) , mRootDir(aRootDir) , mPath(aPath) , mEditable(false) , mLength(UINT64_MAX) , mLastModifiedDate(UINT64_MAX) { Init(); AppendRelativePath(mRootDir); if (!mPath.EqualsLiteral("")) { AppendRelativePath(mPath); } NormalizeFilePath(); } DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, const nsAString& aStorageName, const nsAString& aPath) : mStorageType(aStorageType) , mStorageName(aStorageName) , mPath(aPath) , mEditable(false) , mLength(UINT64_MAX) , mLastModifiedDate(UINT64_MAX) { Init(); AppendRelativePath(aPath); NormalizeFilePath(); } DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType, const nsAString& aStorageName) : mStorageType(aStorageType) , mStorageName(aStorageName) , mEditable(false) , mLength(UINT64_MAX) , mLastModifiedDate(UINT64_MAX) { Init(); } void DeviceStorageFile::Dump(const char* label) { nsString path; if (mFile) { mFile->GetPath(path); } else { path = NS_LITERAL_STRING("(null)"); } const char* ptStr; if (XRE_GetProcessType() == GeckoProcessType_Default) { ptStr = "parent"; } else { ptStr = "child"; } printf_stderr("DSF (%s) %s: mStorageType '%s' mStorageName '%s' " "mRootDir '%s' mPath '%s' mFile->GetPath '%s'\n", ptStr, label, NS_LossyConvertUTF16toASCII(mStorageType).get(), NS_LossyConvertUTF16toASCII(mStorageName).get(), NS_LossyConvertUTF16toASCII(mRootDir).get(), NS_LossyConvertUTF16toASCII(mPath).get(), NS_LossyConvertUTF16toASCII(path).get()); } void DeviceStorageFile::Init() { DeviceStorageFile::GetRootDirectoryForType(mStorageType, mStorageName, getter_AddRefs(mFile)); DebugOnly typeChecker = DeviceStorageTypeChecker::CreateOrGet(); MOZ_ASSERT(typeChecker); } // The OverrideRootDir is needed to facilitate testing of the // device.storage.overrideRootDir preference. The preference is normally // only read once during initialization, but since the test environment has // no convenient way to restart, we use a pref watcher instead. class OverrideRootDir final : public nsIObserver { ~OverrideRootDir(); public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER static OverrideRootDir* GetSingleton(); void Init(); private: static mozilla::StaticRefPtr sSingleton; }; NS_IMPL_ISUPPORTS(OverrideRootDir, nsIObserver) mozilla::StaticRefPtr OverrideRootDir::sSingleton; OverrideRootDir* OverrideRootDir::GetSingleton() { if (sSingleton) { return sSingleton; } // Preference changes are automatically forwarded from parent to child // in ContentParent::Observe, so we'll see the change in both the parent // and the child process. sSingleton = new OverrideRootDir(); Preferences::AddStrongObserver(sSingleton, "device.storage.overrideRootDir"); Preferences::AddStrongObserver(sSingleton, "device.storage.testing"); ClearOnShutdown(&sSingleton); return sSingleton; } OverrideRootDir::~OverrideRootDir() { Preferences::RemoveObserver(this, "device.storage.overrideRootDir"); Preferences::RemoveObserver(this, "device.storage.testing"); } NS_IMETHODIMP OverrideRootDir::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { MOZ_ASSERT(NS_IsMainThread()); if (sSingleton) { sSingleton->Init(); } return NS_OK; } void OverrideRootDir::Init() { MOZ_ASSERT(NS_IsMainThread()); if (!sDirs) { return; } if (mozilla::Preferences::GetBool("device.storage.testing", false)) { nsCOMPtr dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); MOZ_ASSERT(dirService); dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->overrideRootDir)); if (sDirs->overrideRootDir) { sDirs->overrideRootDir->AppendRelativeNativePath( NS_LITERAL_CSTRING("device-storage-testing")); } } else { // For users running on desktop, it's convenient to be able to override // all of the directories to point to a single tree, much like what happens // on a real device. const nsAdoptingString& overrideRootDir = mozilla::Preferences::GetString("device.storage.overrideRootDir"); if (overrideRootDir && overrideRootDir.Length() > 0) { NS_NewLocalFile(overrideRootDir, false, getter_AddRefs(sDirs->overrideRootDir)); } else { sDirs->overrideRootDir = nullptr; } } if (sDirs->overrideRootDir) { if (XRE_GetProcessType() == GeckoProcessType_Default) { // Only the parent process can create directories. In testing, because // the preference is updated after startup, its entirely possible that // the preference updated notification will be received by a child // prior to the parent. nsresult rv = sDirs->overrideRootDir->Create(nsIFile::DIRECTORY_TYPE, 0777); nsString path; sDirs->overrideRootDir->GetPath(path); if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { nsPrintfCString msg("DeviceStorage: Unable to create directory '%s'", NS_LossyConvertUTF16toASCII(path).get()); NS_WARNING(msg.get()); } } sDirs->overrideRootDir->Normalize(); } } // Directories which don't depend on a volume should be calculated once // here. Directories which depend on the root directory of a volume // should be calculated in DeviceStorageFile::GetRootDirectoryForType. static void InitDirs() { if (sDirs) { return; } MOZ_ASSERT(NS_IsMainThread()); sDirs = new GlobalDirs; ClearOnShutdown(&sDirs); nsCOMPtr dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); MOZ_ASSERT(dirService); #if !defined(MOZ_WIDGET_GONK) // Keep MOZ_WIDGET_COCOA above XP_UNIX, // because both are defined in Darwin builds. #if defined (MOZ_WIDGET_COCOA) dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->pictures)); dirService->Get(NS_OSX_MOVIE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->videos)); dirService->Get(NS_OSX_MUSIC_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->music)); // Keep MOZ_WIDGET_ANDROID above XP_UNIX, // because both are defined in Android builds. #elif defined (MOZ_WIDGET_ANDROID) nsAutoString path; if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory( NS_LITERAL_STRING(DEVICESTORAGE_PICTURES), path))) { NS_NewLocalFile(path, /* aFollowLinks */ true, getter_AddRefs(sDirs->pictures)); } if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory( NS_LITERAL_STRING(DEVICESTORAGE_VIDEOS), path))) { NS_NewLocalFile(path, /* aFollowLinks */ true, getter_AddRefs(sDirs->videos)); } if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory( NS_LITERAL_STRING(DEVICESTORAGE_MUSIC), path))) { NS_NewLocalFile(path, /* aFollowLinks */ true, getter_AddRefs(sDirs->music)); } if (NS_SUCCEEDED(mozilla::AndroidBridge::GetExternalPublicDirectory( NS_LITERAL_STRING(DEVICESTORAGE_SDCARD), path))) { NS_NewLocalFile(path, /* aFollowLinks */ true, getter_AddRefs(sDirs->sdcard)); } #elif defined (XP_UNIX) dirService->Get(NS_UNIX_XDG_PICTURES_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->pictures)); dirService->Get(NS_UNIX_XDG_VIDEOS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->videos)); dirService->Get(NS_UNIX_XDG_MUSIC_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->music)); #elif defined (XP_WIN) dirService->Get(NS_WIN_PICTURES_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->pictures)); dirService->Get(NS_WIN_VIDEOS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->videos)); dirService->Get(NS_WIN_MUSIC_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->music)); #endif #ifndef MOZ_WIDGET_ANDROID // Eventually, on desktop, we want to do something smarter -- for example, // detect when an sdcard is inserted, and use that instead of this. dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->sdcard)); if (sDirs->sdcard) { sDirs->sdcard->AppendRelativeNativePath(NS_LITERAL_CSTRING("fake-sdcard")); } #endif // !MOZ_WIDGET_ANDROID #endif // !MOZ_WIDGET_GONK #ifdef MOZ_WIDGET_GONK NS_NewLocalFile(NS_LITERAL_STRING("/data"), false, getter_AddRefs(sDirs->apps)); #else dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(sDirs->apps)); if (sDirs->apps) { sDirs->apps->AppendRelativeNativePath(NS_LITERAL_CSTRING("webapps")); } #endif if (XRE_GetProcessType() == GeckoProcessType_Default) { NS_GetSpecialDirectory("UAppData", getter_AddRefs(sDirs->crashes)); if (sDirs->crashes) { sDirs->crashes->Append(NS_LITERAL_STRING("Crash Reports")); } } else { // NS_GetSpecialDirectory("UAppData") fails in content processes because // gAppData from toolkit/xre/nsAppRunner.cpp is not initialized. #ifdef MOZ_WIDGET_GONK NS_NewLocalFile(NS_LITERAL_STRING("/data/b2g/mozilla/Crash Reports"), false, getter_AddRefs(sDirs->crashes)); #endif } OverrideRootDir::GetSingleton()->Init(); } void DeviceStorageFile::GetFullPath(nsAString &aFullPath) { aFullPath.Truncate(); if (!mStorageName.EqualsLiteral("")) { aFullPath.Append('/'); aFullPath.Append(mStorageName); aFullPath.Append('/'); } if (!mRootDir.EqualsLiteral("")) { aFullPath.Append(mRootDir); aFullPath.Append('/'); } aFullPath.Append(mPath); } // Directories which don't depend on a volume should be calculated once // in InitDirs. Directories which depend on the root directory of a volume // should be calculated in this method. void DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType, const nsAString& aStorageName, nsIFile** aFile) { nsCOMPtr f; *aFile = nullptr; bool allowOverride = true; InitDirs(); #ifdef MOZ_WIDGET_GONK nsresult rv; nsString volMountPoint; if (DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) { nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; rv = vs->GetVolumeByName(aStorageName, getter_AddRefs(vol)); if(NS_FAILED(rv)) { printf_stderr("##### DeviceStorage: GetVolumeByName('%s') failed\n", NS_LossyConvertUTF16toASCII(aStorageName).get()); } NS_ENSURE_SUCCESS_VOID(rv); vol->GetMountPoint(volMountPoint); } #endif // Picture directory if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) { #ifdef MOZ_WIDGET_GONK rv = NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f)); if(NS_FAILED(rv)) { printf_stderr("##### DeviceStorage: NS_NewLocalFile failed StorageType: '%s' path '%s'\n", NS_LossyConvertUTF16toASCII(volMountPoint).get(), NS_LossyConvertUTF16toASCII(aStorageType).get()); } #else f = sDirs->pictures; #endif } // Video directory else if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) { #ifdef MOZ_WIDGET_GONK rv = NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f)); if(NS_FAILED(rv)) { printf_stderr("##### DeviceStorage: NS_NewLocalFile failed StorageType: '%s' path '%s'\n", NS_LossyConvertUTF16toASCII(volMountPoint).get(), NS_LossyConvertUTF16toASCII(aStorageType).get()); } #else f = sDirs->videos; #endif } // Music directory else if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) { #ifdef MOZ_WIDGET_GONK rv = NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f)); if(NS_FAILED(rv)) { printf_stderr("##### DeviceStorage: NS_NewLocalFile failed StorageType: '%s' path '%s'\n", NS_LossyConvertUTF16toASCII(volMountPoint).get(), NS_LossyConvertUTF16toASCII(aStorageType).get()); } #else f = sDirs->music; #endif } // Apps directory else if (aStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) { f = sDirs->apps; allowOverride = false; } // default SDCard else if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) { #ifdef MOZ_WIDGET_GONK rv = NS_NewLocalFile(volMountPoint, false, getter_AddRefs(f)); if(NS_FAILED(rv)) { printf_stderr("##### DeviceStorage: NS_NewLocalFile failed StorageType: '%s' path '%s'\n", NS_LossyConvertUTF16toASCII(volMountPoint).get(), NS_LossyConvertUTF16toASCII(aStorageType).get()); } #else f = sDirs->sdcard; #endif } // crash reports directory. else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) { f = sDirs->crashes; allowOverride = false; } else { // Not a storage type that we recognize. Return null return; } // In testing, we default all device storage types to a temp directory. // sDirs->overrideRootDir will only have been initialized (in InitDirs) // if the preference device.storage.testing was set to true, or if // device.storage.overrideRootDir is set. We can't test the preferences // directly here, since we may not be on the main thread. if (allowOverride && sDirs->overrideRootDir) { f = sDirs->overrideRootDir; } if (f) { f->Clone(aFile); } else { // This should never happen unless something is severely wrong. So // scream a little. printf_stderr("##### GetRootDirectoryForType('%s', '%s') failed #####", NS_LossyConvertUTF16toASCII(aStorageType).get(), NS_LossyConvertUTF16toASCII(aStorageName).get()); } } //static already_AddRefed DeviceStorageFile::CreateUnique(nsAString& aFileName, uint32_t aFileType, uint32_t aFileAttributes) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); MOZ_ASSERT(typeChecker); nsString storageType; typeChecker->GetTypeFromFileName(aFileName, storageType); nsString storageName; nsString storagePath; if (!nsDOMDeviceStorage::ParseFullPath(aFileName, storageName, storagePath)) { return nullptr; } if (storageName.IsEmpty()) { nsDOMDeviceStorage::GetDefaultStorageName(storageType, storageName); } nsRefPtr dsf = new DeviceStorageFile(storageType, storageName, storagePath); if (!dsf->mFile) { return nullptr; } nsresult rv = dsf->mFile->CreateUnique(aFileType, aFileAttributes); NS_ENSURE_SUCCESS(rv, nullptr); // CreateUnique may cause the filename to change. So we need to update mPath // to reflect that. nsString leafName; dsf->mFile->GetLeafName(leafName); int32_t lastSlashIndex = dsf->mPath.RFindChar('/'); if (lastSlashIndex == kNotFound) { dsf->mPath.Assign(leafName); } else { // Include the last '/' dsf->mPath = Substring(dsf->mPath, 0, lastSlashIndex + 1); dsf->mPath.Append(leafName); } return dsf.forget(); } void DeviceStorageFile::SetPath(const nsAString& aPath) { mPath.Assign(aPath); NormalizeFilePath(); } void DeviceStorageFile::SetEditable(bool aEditable) { mEditable = aEditable; } // we want to make sure that the names of file can't reach // outside of the type of storage the user asked for. bool DeviceStorageFile::IsSafePath() { return IsSafePath(mRootDir) && IsSafePath(mPath); } bool DeviceStorageFile::IsSafePath(const nsAString& aPath) { nsAString::const_iterator start, end; aPath.BeginReading(start); aPath.EndReading(end); // if the path is a '~' or starts with '~/', return false. NS_NAMED_LITERAL_STRING(tilde, "~"); NS_NAMED_LITERAL_STRING(tildeSlash, "~/"); if (aPath.Equals(tilde) || StringBeginsWith(aPath, tildeSlash)) { NS_WARNING("Path name starts with tilde!"); return false; } // split on /. if any token is "", ., or .., return false. NS_ConvertUTF16toUTF8 cname(aPath); char* buffer = cname.BeginWriting(); const char* token; while ((token = nsCRT::strtok(buffer, "/", &buffer))) { if (PL_strcmp(token, "") == 0 || PL_strcmp(token, ".") == 0 || PL_strcmp(token, "..") == 0 ) { return false; } } return true; } void DeviceStorageFile::NormalizeFilePath() { FileSystemUtils::LocalPathToNormalizedPath(mPath, mPath); } void DeviceStorageFile::AppendRelativePath(const nsAString& aPath) { if (!mFile) { return; } if (!IsSafePath(aPath)) { // All of the APIs (in the child) do checks to verify that the path is // valid and return PERMISSION_DENIED if a non-safe path is entered. // This check is done in the parent and prevents a compromised // child from bypassing the check. It shouldn't be possible for this // code path to be taken with a non-compromised child. NS_WARNING("Unsafe path detected - ignoring"); NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get()); return; } nsString localPath; FileSystemUtils::NormalizedPathToLocalPath(aPath, localPath); mFile->AppendRelativePath(localPath); } nsresult DeviceStorageFile::CreateFileDescriptor(FileDescriptor& aFileDescriptor) { if (!mFile) { return NS_ERROR_FAILURE; } ScopedPRFileDesc fd; nsresult rv = mFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 0660, &fd.rwget()); NS_ENSURE_SUCCESS(rv, rv); // NOTE: The FileDescriptor::PlatformHandleType constructor returns a dup of // the file descriptor, so we don't need the original fd after this. // Our scoped file descriptor will automatically close fd. aFileDescriptor = FileDescriptor( FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd))); return NS_OK; } nsresult DeviceStorageFile::Write(nsIInputStream* aInputStream) { if (!aInputStream || !mFile) { return NS_ERROR_FAILURE; } nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr iocomplete = new IOEventComplete(this, "created"); rv = NS_DispatchToMainThread(iocomplete); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr outputStream; NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile); if (!outputStream) { return NS_ERROR_FAILURE; } return Append(aInputStream, outputStream); } nsresult DeviceStorageFile::Write(InfallibleTArray& aBits) { if (!mFile) { return NS_ERROR_FAILURE; } nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr iocomplete = new IOEventComplete(this, "created"); rv = NS_DispatchToMainThread(iocomplete); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr outputStream; NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile); if (!outputStream) { return NS_ERROR_FAILURE; } uint32_t wrote; outputStream->Write((char*) aBits.Elements(), aBits.Length(), &wrote); outputStream->Close(); iocomplete = new IOEventComplete(this, "modified"); rv = NS_DispatchToMainThread(iocomplete); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aBits.Length() != wrote) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult DeviceStorageFile::Append(nsIInputStream* aInputStream) { if (!aInputStream || !mFile) { return NS_ERROR_FAILURE; } nsCOMPtr outputStream; NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile, PR_WRONLY | PR_CREATE_FILE | PR_APPEND, -1, 0); if (!outputStream) { return NS_ERROR_FAILURE; } return Append(aInputStream, outputStream); } nsresult DeviceStorageFile::Append(nsIInputStream* aInputStream, nsIOutputStream* aOutputStream) { uint64_t bufSize = 0; aInputStream->Available(&bufSize); nsCOMPtr bufferedOutputStream; nsresult rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), aOutputStream, 4096*4); NS_ENSURE_SUCCESS(rv, rv); while (bufSize) { uint32_t wrote; rv = bufferedOutputStream->WriteFrom( aInputStream, static_cast(std::min(bufSize, UINT32_MAX)), &wrote); if (NS_FAILED(rv)) { break; } bufSize -= wrote; } nsCOMPtr iocomplete = new IOEventComplete(this, "modified"); rv = NS_DispatchToMainThread(iocomplete); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bufferedOutputStream->Close(); aOutputStream->Close(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult DeviceStorageFile::Remove() { MOZ_ASSERT(!NS_IsMainThread()); if (!mFile) { return NS_ERROR_FAILURE; } bool check; nsresult rv = mFile->Exists(&check); if (NS_FAILED(rv)) { return rv; } if (!check) { return NS_OK; } rv = mFile->Remove(true); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr iocomplete = new IOEventComplete(this, "deleted"); return NS_DispatchToMainThread(iocomplete); } nsresult DeviceStorageFile::CalculateMimeType() { MOZ_ASSERT(NS_IsMainThread()); if (!mFile) { return NS_ERROR_FAILURE; } nsAutoCString mimeType; nsCOMPtr mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID); if (mimeService) { nsresult rv = mimeService->GetTypeFromFile(mFile, mimeType); if (NS_FAILED(rv)) { mimeType.Truncate(); return rv; } } mMimeType = NS_ConvertUTF8toUTF16(mimeType); return NS_OK; } nsresult DeviceStorageFile::CalculateSizeAndModifiedDate() { MOZ_ASSERT(!NS_IsMainThread()); if (!mFile) { return NS_ERROR_FAILURE; } int64_t fileSize; nsresult rv = mFile->GetFileSize(&fileSize); NS_ENSURE_SUCCESS(rv, rv); mLength = fileSize; PRTime modDate; rv = mFile->GetLastModifiedTime(&modDate); NS_ENSURE_SUCCESS(rv, rv); mLastModifiedDate = modDate; return NS_OK; } void DeviceStorageFile::CollectFiles(nsTArray > &aFiles, PRTime aSince) { if (!mFile) { return; } nsString fullRootPath; mFile->GetPath(fullRootPath); collectFilesInternal(aFiles, aSince, fullRootPath); } void DeviceStorageFile::collectFilesInternal( nsTArray > &aFiles, PRTime aSince, nsAString& aRootPath) { if (!mFile || !IsAvailable()) { return; } nsCOMPtr e; mFile->GetDirectoryEntries(getter_AddRefs(e)); if (!e) { return; } nsCOMPtr files = do_QueryInterface(e); nsCOMPtr f; while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) { bool isFile; f->IsFile(&isFile); if (isFile) { PRTime msecs; f->GetLastModifiedTime(&msecs); if (msecs < aSince) { continue; } } bool isDir; f->IsDirectory(&isDir); nsString fullpath; nsresult rv = f->GetPath(fullpath); if (NS_FAILED(rv)) { continue; } if (!StringBeginsWith(fullpath, aRootPath)) { NS_ERROR("collectFiles returned a path that does not belong!"); continue; } nsAString::size_type len = aRootPath.Length() + 1; // +1 for the trailing / nsDependentSubstring newPath = Substring(fullpath, len); if (isDir) { DeviceStorageFile dsf(mStorageType, mStorageName, mRootDir, newPath); dsf.collectFilesInternal(aFiles, aSince, aRootPath); } else if (isFile) { nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, mRootDir, newPath); dsf->CalculateSizeAndModifiedDate(); aFiles.AppendElement(dsf); } } } void DeviceStorageFile::AccumDiskUsage(uint64_t* aPicturesSoFar, uint64_t* aVideosSoFar, uint64_t* aMusicSoFar, uint64_t* aTotalSoFar) { if (!IsAvailable()) { return; } uint64_t pictureUsage = 0, videoUsage = 0, musicUsage = 0, totalUsage = 0; if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) { DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet(); MOZ_ASSERT(usedSpaceCache); nsresult rv = usedSpaceCache->AccumUsedSizes(mStorageName, aPicturesSoFar, aVideosSoFar, aMusicSoFar, aTotalSoFar); if (NS_SUCCEEDED(rv)) { return; } AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage, &musicUsage, &totalUsage); usedSpaceCache->SetUsedSizes(mStorageName, pictureUsage, videoUsage, musicUsage, totalUsage); } else { AccumDirectoryUsage(mFile, &pictureUsage, &videoUsage, &musicUsage, &totalUsage); } *aPicturesSoFar += pictureUsage; *aVideosSoFar += videoUsage; *aMusicSoFar += musicUsage; *aTotalSoFar += totalUsage; } void DeviceStorageFile::AccumDirectoryUsage(nsIFile* aFile, uint64_t* aPicturesSoFar, uint64_t* aVideosSoFar, uint64_t* aMusicSoFar, uint64_t* aTotalSoFar) { if (!aFile) { return; } nsresult rv; nsCOMPtr e; rv = aFile->GetDirectoryEntries(getter_AddRefs(e)); if (NS_FAILED(rv) || !e) { return; } nsCOMPtr files = do_QueryInterface(e); MOZ_ASSERT(files); nsCOMPtr f; while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) { bool isDir; rv = f->IsDirectory(&isDir); if (NS_FAILED(rv)) { continue; } bool isFile; rv = f->IsFile(&isFile); if (NS_FAILED(rv)) { continue; } bool isLink; rv = f->IsSymlink(&isLink); if (NS_FAILED(rv)) { continue; } if (isLink) { // for now, lets just totally ignore symlinks. NS_WARNING("DirectoryDiskUsage ignores symlinks"); } else if (isDir) { AccumDirectoryUsage(f, aPicturesSoFar, aVideosSoFar, aMusicSoFar, aTotalSoFar); } else if (isFile) { int64_t size; rv = f->GetFileSize(&size); if (NS_FAILED(rv)) { continue; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); MOZ_ASSERT(typeChecker); nsString type; typeChecker->GetTypeFromFile(f, type); if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) { *aPicturesSoFar += size; } else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) { *aVideosSoFar += size; } else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) { *aMusicSoFar += size; } *aTotalSoFar += size; } } } void DeviceStorageFile::GetDiskFreeSpace(int64_t* aSoFar) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return; } if (!mFile || !IsAvailable()) { return; } int64_t storageAvail = 0; nsresult rv = mFile->GetDiskSpaceAvailable(&storageAvail); if (NS_SUCCEEDED(rv)) { *aSoFar += storageAvail; } } bool DeviceStorageFile::IsAvailable() { nsString status; GetStatus(status); return status.EqualsLiteral("available"); } void DeviceStorageFile::DoFormat(nsAString& aStatus) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker || !mFile) { return; } if (!typeChecker->IsVolumeBased(mStorageType)) { aStatus.AssignLiteral("notVolume"); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); NS_ENSURE_SUCCESS_VOID(rv); if (!vol) { return; } vol->Format(); aStatus.AssignLiteral("formatting"); #endif return; } void DeviceStorageFile::DoMount(nsAString& aStatus) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker || !mFile) { return; } if (!typeChecker->IsVolumeBased(mStorageType)) { aStatus.AssignLiteral("notVolume"); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); NS_ENSURE_SUCCESS_VOID(rv); if (!vol) { return; } vol->Mount(); aStatus.AssignLiteral("mounting"); #endif return; } void DeviceStorageFile::DoUnmount(nsAString& aStatus) { DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker || !mFile) { return; } if (!typeChecker->IsVolumeBased(mStorageType)) { aStatus.AssignLiteral("notVolume"); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); NS_ENSURE_SUCCESS_VOID(rv); if (!vol) { return; } vol->Unmount(); aStatus.AssignLiteral("unmounting"); #endif return; } void DeviceStorageFile::GetStatus(nsAString& aStatus) { aStatus.AssignLiteral("unavailable"); DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker || !mFile) { return; } if (!typeChecker->IsVolumeBased(mStorageType)) { aStatus.AssignLiteral("available"); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); NS_ENSURE_SUCCESS_VOID(rv); if (!vol) { return; } bool isMediaPresent; rv = vol->GetIsMediaPresent(&isMediaPresent); NS_ENSURE_SUCCESS_VOID(rv); if (!isMediaPresent) { return; } bool isSharing; rv = vol->GetIsSharing(&isSharing); NS_ENSURE_SUCCESS_VOID(rv); if (isSharing) { aStatus.AssignLiteral("shared"); return; } bool isFormatting; rv = vol->GetIsFormatting(&isFormatting); NS_ENSURE_SUCCESS_VOID(rv); if (isFormatting) { aStatus.AssignLiteral("unavailable"); return; } bool isUnmounting; rv = vol->GetIsUnmounting(&isUnmounting); NS_ENSURE_SUCCESS_VOID(rv); if (isUnmounting) { aStatus.AssignLiteral("unavailable"); return; } int32_t volState; rv = vol->GetState(&volState); NS_ENSURE_SUCCESS_VOID(rv); if (volState == nsIVolume::STATE_MOUNTED) { aStatus.AssignLiteral("available"); } #endif } void DeviceStorageFile::GetStorageStatus(nsAString& aStatus) { aStatus.AssignLiteral("undefined"); DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker || !mFile) { return; } if (!typeChecker->IsVolumeBased(mStorageType)) { aStatus.AssignLiteral("available"); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); NS_ENSURE_TRUE_VOID(vs); nsCOMPtr vol; nsresult rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); NS_ENSURE_SUCCESS_VOID(rv); if (!vol) { return; } int32_t volState; rv = vol->GetState(&volState); NS_ENSURE_SUCCESS_VOID(rv); aStatus.AssignASCII(mozilla::system::NS_VolumeStateStr(volState)); #endif } NS_IMPL_ISUPPORTS0(DeviceStorageFile) static void RegisterForSDCardChanges(nsIObserver* aObserver) { #ifdef MOZ_WIDGET_GONK nsCOMPtr obs = mozilla::services::GetObserverService(); obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false); #endif } static void UnregisterForSDCardChanges(nsIObserver* aObserver) { #ifdef MOZ_WIDGET_GONK nsCOMPtr obs = mozilla::services::GetObserverService(); obs->RemoveObserver(aObserver, NS_VOLUME_STATE_CHANGED); #endif } void nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aStorageType, const nsAString& aStorageName) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr f; DeviceStorageFile::GetRootDirectoryForType(aStorageType, aStorageName, getter_AddRefs(f)); nsCOMPtr obs = mozilla::services::GetObserverService(); obs->AddObserver(this, kFileWatcherUpdate, false); obs->AddObserver(this, "disk-space-watcher", false); mRootDirectory = f; mStorageType = aStorageType; mStorageName = aStorageName; } JS::Value InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID) { nsCOMPtr sgo = do_QueryInterface(aWindow); if (!sgo) { return JS::NullValue(); } JSObject *unrootedScopeObj = sgo->GetGlobalJSObject(); NS_ENSURE_TRUE(unrootedScopeObj, JS::NullValue()); JSRuntime *runtime = JS_GetObjectRuntime(unrootedScopeObj); JS::Rooted someJsVal(runtime); JS::Rooted scopeObj(runtime, unrootedScopeObj); nsresult rv; { // Protect someJsVal from moving GC in ~JSAutoCompartment AutoJSContext cx; JSAutoCompartment ac(cx, scopeObj); rv = nsContentUtils::WrapNative(cx, aObject, aIID, &someJsVal); } if (NS_FAILED(rv)) { return JS::NullValue(); } return someJsVal; } JS::Value nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aWindow); if (!aFile) { return JSVAL_NULL; } if (aFile->mEditable) { // TODO - needs janv's file handle support. return JSVAL_NULL; } nsString fullPath; aFile->GetFullPath(fullPath); // This check is useful to know if somewhere the DeviceStorageFile // has not been properly set. Mimetype is not checked because it can be // empty. MOZ_ASSERT(aFile->mLength != UINT64_MAX); MOZ_ASSERT(aFile->mLastModifiedDate != UINT64_MAX); nsCOMPtr blob = Blob::Create(aWindow, new BlobImplFile(fullPath, aFile->mMimeType, aFile->mLength, aFile->mFile, aFile->mLastModifiedDate)); return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob)); } bool StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString, JS::MutableHandle result) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aWindow); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(aWindow))) { return false; } JSContext* cx = jsapi.cx(); if (!xpc::StringToJsval(cx, aString, result)) { return false; } return true; } class DeviceStorageCursorRequest final : public nsIContentPermissionRequest { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageCursorRequest, nsIContentPermissionRequest) NS_FORWARD_NSICONTENTPERMISSIONREQUEST(mCursor->); explicit DeviceStorageCursorRequest(nsDOMDeviceStorageCursor* aCursor) : mCursor(aCursor) { } private: ~DeviceStorageCursorRequest() {} nsRefPtr mCursor; }; NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageCursorRequest) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageCursorRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageCursorRequest) NS_IMPL_CYCLE_COLLECTION(DeviceStorageCursorRequest, mCursor) class PostErrorEvent : public nsRunnable { public: PostErrorEvent(already_AddRefed aRequest, const char* aMessage) : mRequest(aRequest) { CopyASCIItoUTF16(aMessage, mError); } PostErrorEvent(DOMRequest* aRequest, const char* aMessage) : mRequest(aRequest) { CopyASCIItoUTF16(aMessage, mError); } ~PostErrorEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); if (!mRequest->GetOwner()) { return NS_OK; } mRequest->FireError(mError); mRequest = nullptr; return NS_OK; } private: nsRefPtr mRequest; nsString mError; }; ContinueCursorEvent::ContinueCursorEvent(already_AddRefed aRequest) : mRequest(aRequest) { } ContinueCursorEvent::ContinueCursorEvent(DOMRequest* aRequest) : mRequest(aRequest) { } already_AddRefed ContinueCursorEvent::GetNextFile() { MOZ_ASSERT(NS_IsMainThread()); nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); nsString cursorStorageType; cursor->GetStorageType(cursorStorageType); DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return nullptr; } while (cursor->mFiles.Length() > 0) { nsRefPtr file = cursor->mFiles[0]; cursor->mFiles.RemoveElementAt(0); if (!typeChecker->Check(cursorStorageType, file->mFile)) { continue; } file->CalculateMimeType(); return file.forget(); } return nullptr; } ContinueCursorEvent::~ContinueCursorEvent() {} void ContinueCursorEvent::Continue() { if (XRE_GetProcessType() == GeckoProcessType_Default) { DebugOnly rv = NS_DispatchToMainThread(this); MOZ_ASSERT(NS_SUCCEEDED(rv)); return; } nsRefPtr file = GetNextFile(); if (!file) { // done with enumeration. DebugOnly rv = NS_DispatchToMainThread(this); MOZ_ASSERT(NS_SUCCEEDED(rv)); return; } nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); nsString cursorStorageType; cursor->GetStorageType(cursorStorageType); DeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, file); child->SetCallback(cursor); DeviceStorageGetParams params(cursorStorageType, file->mStorageName, file->mRootDir, file->mPath); ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params); mRequest = nullptr; } NS_IMETHODIMP ContinueCursorEvent::Run() { nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsRefPtr file = GetNextFile(); nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); AutoJSContext cx; JS::Rooted val(cx, nsIFileToJsval(window, file)); if (file) { cursor->mOkToCallContinue = true; cursor->FireSuccess(val); } else { cursor->FireDone(); } mRequest = nullptr; return NS_OK; } class InitCursorEvent : public nsRunnable { public: InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile) : mFile(aFile) , mRequest(aRequest) { } ~InitCursorEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); if (mFile->mFile) { bool check; mFile->mFile->IsDirectory(&check); if (!check) { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_NOT_ENUMERABLE); return NS_DispatchToMainThread(event); } } nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); mFile->CollectFiles(cursor->mFiles, cursor->mSince); nsRefPtr event = new ContinueCursorEvent(mRequest.forget()); event->Continue(); return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorageCursor) NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) NS_INTERFACE_MAP_END_INHERITING(DOMCursor) NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorageCursor, DOMCursor) NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMCursor) nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsPIDOMWindow* aWindow, nsIPrincipal* aPrincipal, DeviceStorageFile* aFile, PRTime aSince) : DOMCursor(aWindow, nullptr) , mOkToCallContinue(false) , mSince(aSince) , mFile(aFile) , mPrincipal(aPrincipal) , mRequester(new nsContentPermissionRequester(GetOwner())) { } nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor() { } void nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType) { aType = mFile->mStorageType; } NS_IMETHODIMP nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes) { nsCString type; nsresult rv = DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type); NS_ENSURE_SUCCESS(rv, rv); nsTArray emptyOptions; return nsContentPermissionUtils::CreatePermissionArray(type, NS_LITERAL_CSTRING("read"), emptyOptions, aTypes); } NS_IMETHODIMP nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal** aRequestingPrincipal) { NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal); return NS_OK; } NS_IMETHODIMP nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow** aRequestingWindow) { NS_IF_ADDREF(*aRequestingWindow = GetOwner()); return NS_OK; } NS_IMETHODIMP nsDOMDeviceStorageCursor::GetElement(nsIDOMElement** aRequestingElement) { *aRequestingElement = nullptr; return NS_OK; } NS_IMETHODIMP nsDOMDeviceStorageCursor::Cancel() { nsCOMPtr event = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED); return NS_DispatchToMainThread(event); } NS_IMETHODIMP nsDOMDeviceStorageCursor::Allow(JS::HandleValue aChoices) { MOZ_ASSERT(aChoices.isUndefined()); if (!mFile->IsSafePath()) { nsCOMPtr r = new PostErrorEvent(this, POST_ERROR_EVENT_PERMISSION_DENIED); return NS_DispatchToMainThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this, mFile); DeviceStorageEnumerationParams params(mFile->mStorageType, mFile->mStorageName, mFile->mRootDir, mSince); ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); MOZ_ASSERT(target); nsCOMPtr event = new InitCursorEvent(this, mFile); target->Dispatch(event, NS_DISPATCH_NORMAL); return NS_OK; } NS_IMETHODIMP nsDOMDeviceStorageCursor::GetRequester(nsIContentPermissionRequester** aRequester) { NS_ENSURE_ARG_POINTER(aRequester); nsCOMPtr requester = mRequester; requester.forget(aRequester); return NS_OK; } void nsDOMDeviceStorageCursor::Continue(ErrorResult& aRv) { if (!mOkToCallContinue) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } if (!mResult.isUndefined()) { // We call onsuccess multiple times. Clear the last // result. mResult.setUndefined(); mDone = false; } nsRefPtr event = new ContinueCursorEvent(this); event->Continue(); mOkToCallContinue = false; } void nsDOMDeviceStorageCursor::RequestComplete() { MOZ_ASSERT(!mOkToCallContinue); mOkToCallContinue = true; } class PostAvailableResultEvent : public nsRunnable { public: PostAvailableResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostAvailableResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsString state = NS_LITERAL_STRING("unavailable"); if (mFile) { mFile->GetStatus(state); } AutoJSContext cx; JS::Rooted result(cx); StringToJsval(window, state, &result); mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; class PostStatusResultEvent : public nsRunnable { public: PostStatusResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostStatusResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsString state = NS_LITERAL_STRING("undefined"); if (mFile) { mFile->GetStorageStatus(state); } AutoJSContext cx; JS::Rooted result(cx); StringToJsval(window, state, &result); mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; class PostFormatResultEvent : public nsRunnable { public: PostFormatResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostFormatResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsString state = NS_LITERAL_STRING("unavailable"); if (mFile) { mFile->DoFormat(state); } AutoJSContext cx; JS::Rooted result(cx); StringToJsval(window, state, &result); mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; class PostMountResultEvent : public nsRunnable { public: PostMountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostMountResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsString state = NS_LITERAL_STRING("unavailable"); if (mFile) { mFile->DoMount(state); } AutoJSContext cx; JS::Rooted result(cx); StringToJsval(window, state, &result); mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; class PostUnmountResultEvent : public nsRunnable { public: PostUnmountResultEvent(DeviceStorageFile *aFile, DOMRequest* aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostUnmountResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } nsString state = NS_LITERAL_STRING("unavailable"); if (mFile) { mFile->DoUnmount(state); } AutoJSContext cx; JS::Rooted result(cx); StringToJsval(window, state, &result); mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsRefPtr mRequest; }; class PostResultEvent : public nsRunnable { public: PostResultEvent(already_AddRefed aRequest, DeviceStorageFile* aFile) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } PostResultEvent(already_AddRefed aRequest, const nsAString & aPath) : mPath(aPath) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } PostResultEvent(already_AddRefed aRequest, const uint64_t aValue) : mValue(aValue) , mRequest(aRequest) { MOZ_ASSERT(mRequest); } ~PostResultEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = mRequest->GetOwner(); if (!window) { return NS_OK; } AutoJSContext cx; JS::Rooted result(cx, JSVAL_NULL); if (mFile) { result = nsIFileToJsval(window, mFile); } else if (mPath.Length()) { StringToJsval(window, mPath, &result); } else { result = JS_NumberValue(double(mValue)); } mRequest->FireSuccess(result); mRequest = nullptr; return NS_OK; } private: nsRefPtr mFile; nsString mPath; uint64_t mValue; nsRefPtr mRequest; }; class CreateFdEvent : public nsRunnable { public: CreateFdEvent(DeviceStorageFileDescriptor* aDSFileDescriptor, already_AddRefed aRequest) : mDSFileDescriptor(aDSFileDescriptor) , mRequest(aRequest) { MOZ_ASSERT(mDSFileDescriptor); MOZ_ASSERT(mDSFileDescriptor->mDSFile); MOZ_ASSERT(mDSFileDescriptor->mDSFile->mFile); MOZ_ASSERT(mRequest); } NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); DeviceStorageFile* dsFile = mDSFileDescriptor->mDSFile; nsString fullPath; dsFile->GetFullPath(fullPath); MOZ_ASSERT(!fullPath.IsEmpty()); bool check = false; dsFile->mFile->Exists(&check); if (check) { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS); return NS_DispatchToMainThread(event); } nsresult rv = dsFile->CreateFileDescriptor(mDSFileDescriptor->mFileDescriptor); if (NS_FAILED(rv)) { dsFile->mFile->Remove(false); nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN); return NS_DispatchToMainThread(event); } nsCOMPtr event = new PostResultEvent(mRequest.forget(), fullPath); return NS_DispatchToMainThread(event); } private: nsRefPtr mDSFileDescriptor; nsRefPtr mRequest; }; class WriteFileEvent : public nsRunnable { public: WriteFileEvent(BlobImpl* aBlobImpl, DeviceStorageFile *aFile, already_AddRefed aRequest, int32_t aRequestType) : mBlobImpl(aBlobImpl) , mFile(aFile) , mRequest(aRequest) , mRequestType(aRequestType) { MOZ_ASSERT(mFile); MOZ_ASSERT(mFile->mFile); MOZ_ASSERT(mRequest); MOZ_ASSERT(mRequestType); } ~WriteFileEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); ErrorResult rv; nsCOMPtr stream; mBlobImpl->GetInternalStream(getter_AddRefs(stream), rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN); return NS_DispatchToMainThread(event); } bool check = false; mFile->mFile->Exists(&check); if (mRequestType == DEVICE_STORAGE_REQUEST_APPEND) { if (!check) { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_DOES_NOT_EXIST); return NS_DispatchToMainThread(event); } rv = mFile->Append(stream); } else if (mRequestType == DEVICE_STORAGE_REQUEST_CREATE) { if (check) { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_EXISTS); return NS_DispatchToMainThread(event); } rv = mFile->Write(stream); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); mFile->mFile->Remove(false); } } else { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN); return NS_DispatchToMainThread(event); } if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN); return NS_DispatchToMainThread(event); } nsString fullPath; mFile->GetFullPath(fullPath); nsCOMPtr event = new PostResultEvent(mRequest.forget(), fullPath); return NS_DispatchToMainThread(event); } private: nsRefPtr mBlobImpl; nsRefPtr mFile; nsRefPtr mRequest; int32_t mRequestType; }; class ReadFileEvent : public nsRunnable { public: ReadFileEvent(DeviceStorageFile* aFile, already_AddRefed aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); mFile->CalculateMimeType(); } ~ReadFileEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); nsCOMPtr r; if (!mFile->mEditable) { bool check = false; mFile->mFile->Exists(&check); if (!check) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_DOES_NOT_EXIST); } } if (!r) { nsresult rv = mFile->CalculateSizeAndModifiedDate(); if (NS_FAILED(rv)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_UNKNOWN); } } if (!r) { r = new PostResultEvent(mRequest.forget(), mFile); } return NS_DispatchToMainThread(r); } private: nsRefPtr mFile; nsRefPtr mRequest; }; class DeleteFileEvent : public nsRunnable { public: DeleteFileEvent(DeviceStorageFile* aFile, already_AddRefed aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); } ~DeleteFileEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); mFile->Remove(); nsCOMPtr r; bool check = false; mFile->mFile->Exists(&check); if (check) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_FILE_DOES_NOT_EXIST); } else { nsString fullPath; mFile->GetFullPath(fullPath); r = new PostResultEvent(mRequest.forget(), fullPath); } return NS_DispatchToMainThread(r); } private: nsRefPtr mFile; nsRefPtr mRequest; }; class UsedSpaceFileEvent : public nsRunnable { public: UsedSpaceFileEvent(DeviceStorageFile* aFile, already_AddRefed aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); } ~UsedSpaceFileEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); uint64_t picturesUsage = 0, videosUsage = 0, musicUsage = 0, totalUsage = 0; mFile->AccumDiskUsage(&picturesUsage, &videosUsage, &musicUsage, &totalUsage); nsCOMPtr r; if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) { r = new PostResultEvent(mRequest.forget(), picturesUsage); } else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) { r = new PostResultEvent(mRequest.forget(), videosUsage); } else if (mFile->mStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) { r = new PostResultEvent(mRequest.forget(), musicUsage); } else { r = new PostResultEvent(mRequest.forget(), totalUsage); } return NS_DispatchToMainThread(r); } private: nsRefPtr mFile; nsRefPtr mRequest; }; class FreeSpaceFileEvent : public nsRunnable { public: FreeSpaceFileEvent(DeviceStorageFile* aFile, already_AddRefed aRequest) : mFile(aFile) , mRequest(aRequest) { MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); } ~FreeSpaceFileEvent() {} NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); int64_t freeSpace = 0; if (mFile) { mFile->GetDiskFreeSpace(&freeSpace); } nsCOMPtr r; r = new PostResultEvent(mRequest.forget(), static_cast(freeSpace)); return NS_DispatchToMainThread(r); } private: nsRefPtr mFile; nsRefPtr mRequest; }; class DeviceStorageRequest final : public nsIContentPermissionRequest , public nsIRunnable { public: DeviceStorageRequest(const DeviceStorageRequestType aRequestType, nsPIDOMWindow* aWindow, nsIPrincipal* aPrincipal, DeviceStorageFile* aFile, DOMRequest* aRequest, nsDOMDeviceStorage* aDeviceStorage) : mRequestType(aRequestType) , mWindow(aWindow) , mPrincipal(aPrincipal) , mFile(aFile) , mRequest(aRequest) , mDeviceStorage(aDeviceStorage) , mRequester(new nsContentPermissionRequester(mWindow)) { MOZ_ASSERT(mWindow); MOZ_ASSERT(mPrincipal); MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); MOZ_ASSERT(mDeviceStorage); } DeviceStorageRequest(const DeviceStorageRequestType aRequestType, nsPIDOMWindow* aWindow, nsIPrincipal* aPrincipal, DeviceStorageFile* aFile, DOMRequest* aRequest, Blob* aBlob = nullptr) : mRequestType(aRequestType) , mWindow(aWindow) , mPrincipal(aPrincipal) , mFile(aFile) , mRequest(aRequest) , mBlob(aBlob) , mRequester(new nsContentPermissionRequester(mWindow)) { MOZ_ASSERT(mWindow); MOZ_ASSERT(mPrincipal); MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); } DeviceStorageRequest(const DeviceStorageRequestType aRequestType, nsPIDOMWindow* aWindow, nsIPrincipal* aPrincipal, DeviceStorageFile* aFile, DOMRequest* aRequest, DeviceStorageFileDescriptor* aDSFileDescriptor) : mRequestType(aRequestType) , mWindow(aWindow) , mPrincipal(aPrincipal) , mFile(aFile) , mRequest(aRequest) , mDSFileDescriptor(aDSFileDescriptor) , mRequester(new nsContentPermissionRequester(mWindow)) { MOZ_ASSERT(mRequestType == DEVICE_STORAGE_REQUEST_CREATEFD); MOZ_ASSERT(mWindow); MOZ_ASSERT(mPrincipal); MOZ_ASSERT(mFile); MOZ_ASSERT(mRequest); MOZ_ASSERT(mDSFileDescriptor); } NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest, nsIContentPermissionRequest) NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { Allow(JS::UndefinedHandleValue); return NS_OK; } return nsContentPermissionUtils::AskPermission(this, mWindow); } NS_IMETHODIMP GetTypes(nsIArray** aTypes) override { nsCString type; nsresult rv = DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type); if (NS_FAILED(rv)) { return rv; } nsCString access; rv = DeviceStorageTypeChecker::GetAccessForRequest( DeviceStorageRequestType(mRequestType), access); if (NS_FAILED(rv)) { return rv; } nsTArray emptyOptions; return nsContentPermissionUtils::CreatePermissionArray(type, access, emptyOptions, aTypes); } NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal) override { NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal); return NS_OK; } NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow) override { NS_IF_ADDREF(*aRequestingWindow = mWindow); return NS_OK; } NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement) override { *aRequestingElement = nullptr; return NS_OK; } NS_IMETHOD Cancel() override { nsCOMPtr event = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_PERMISSION_DENIED); return NS_DispatchToMainThread(event); } NS_IMETHOD Allow(JS::HandleValue aChoices) override { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aChoices.isUndefined()); if (!mRequest) { return NS_ERROR_FAILURE; } nsCOMPtr r; switch(mRequestType) { case DEVICE_STORAGE_REQUEST_CREATEFD: { if (!mFile->mFile) { return NS_ERROR_FAILURE; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return NS_OK; } if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_ILLEGAL_TYPE); return NS_DispatchToCurrentThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { DeviceStorageCreateFdParams params; params.type() = mFile->mStorageType; params.storageName() = mFile->mStorageName; params.relpath() = mFile->mPath; mFile->Dump("DeviceStorageCreateFdParams"); PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile, mDSFileDescriptor.get()); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } mDSFileDescriptor->mDSFile = mFile; r = new CreateFdEvent(mDSFileDescriptor.get(), mRequest.forget()); break; } case DEVICE_STORAGE_REQUEST_CREATE: { if (!mBlob || !mFile->mFile) { return NS_ERROR_FAILURE; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return NS_OK; } if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) || !typeChecker->Check(mFile->mStorageType, mBlob)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_ILLEGAL_TYPE); return NS_DispatchToCurrentThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { BlobChild* actor = ContentChild::GetSingleton()->GetOrCreateActorForBlob( static_cast(mBlob.get())); if (!actor) { return NS_ERROR_FAILURE; } DeviceStorageAddParams params; params.blobChild() = actor; params.type() = mFile->mStorageType; params.storageName() = mFile->mStorageName; params.relpath() = mFile->mPath; PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } File* blob = static_cast(mBlob.get()); r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(), mRequestType); break; } case DEVICE_STORAGE_REQUEST_APPEND: { if (!mBlob || !mFile->mFile) { return NS_ERROR_FAILURE; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return NS_OK; } if (!typeChecker->Check(mFile->mStorageType, mFile->mFile) || !typeChecker->Check(mFile->mStorageType, mBlob)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_ILLEGAL_TYPE); return NS_DispatchToCurrentThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { BlobChild* actor = ContentChild::GetSingleton()->GetOrCreateActorForBlob( static_cast(mBlob.get())); if (!actor) { return NS_ERROR_FAILURE; } DeviceStorageAppendParams params; params.blobChild() = actor; params.type() = mFile->mStorageType; params.storageName() = mFile->mStorageName; params.relpath() = mFile->mPath; PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } File* blob = static_cast(mBlob.get()); r = new WriteFileEvent(blob->Impl(), mFile, mRequest.forget(), mRequestType); break; } case DEVICE_STORAGE_REQUEST_READ: case DEVICE_STORAGE_REQUEST_WRITE: { if (!mFile->mFile) { return NS_ERROR_FAILURE; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return NS_OK; } if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_ILLEGAL_TYPE); return NS_DispatchToCurrentThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageGetParams params(mFile->mStorageType, mFile->mStorageName, mFile->mRootDir, mFile->mPath); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new ReadFileEvent(mFile, mRequest.forget()); break; } case DEVICE_STORAGE_REQUEST_DELETE: { if (!mFile->mFile) { return NS_ERROR_FAILURE; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { return NS_OK; } if (!typeChecker->Check(mFile->mStorageType, mFile->mFile)) { r = new PostErrorEvent(mRequest.forget(), POST_ERROR_EVENT_ILLEGAL_TYPE); return NS_DispatchToCurrentThread(r); } if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageDeleteParams params(mFile->mStorageType, mFile->mStorageName, mFile->mPath); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new DeleteFileEvent(mFile, mRequest.forget()); break; } case DEVICE_STORAGE_REQUEST_FREE_SPACE: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageFreeSpaceParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new FreeSpaceFileEvent(mFile, mRequest.forget()); break; } case DEVICE_STORAGE_REQUEST_USED_SPACE: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageUsedSpaceParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } // this needs to be dispatched to only one (1) // thread or we will do more work than required. DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet(); MOZ_ASSERT(usedSpaceCache); r = new UsedSpaceFileEvent(mFile, mRequest.forget()); usedSpaceCache->Dispatch(r); return NS_OK; } case DEVICE_STORAGE_REQUEST_AVAILABLE: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageAvailableParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new PostAvailableResultEvent(mFile, mRequest); return NS_DispatchToCurrentThread(r); } case DEVICE_STORAGE_REQUEST_STATUS: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageStatusParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new PostStatusResultEvent(mFile, mRequest); return NS_DispatchToCurrentThread(r); } case DEVICE_STORAGE_REQUEST_WATCH: { mDeviceStorage->mAllowedToWatchFile = true; return NS_OK; } case DEVICE_STORAGE_REQUEST_FORMAT: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageFormatParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new PostFormatResultEvent(mFile, mRequest); return NS_DispatchToCurrentThread(r); } case DEVICE_STORAGE_REQUEST_MOUNT: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageMountParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new PostMountResultEvent(mFile, mRequest); return NS_DispatchToCurrentThread(r); } case DEVICE_STORAGE_REQUEST_UNMOUNT: { if (XRE_GetProcessType() != GeckoProcessType_Default) { PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile); DeviceStorageUnmountParams params(mFile->mStorageType, mFile->mStorageName); ContentChild::GetSingleton() ->SendPDeviceStorageRequestConstructor(child, params); return NS_OK; } r = new PostUnmountResultEvent(mFile, mRequest); return NS_DispatchToCurrentThread(r); } } if (r) { nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); MOZ_ASSERT(target); target->Dispatch(r, NS_DISPATCH_NORMAL); } return NS_OK; } NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override { NS_ENSURE_ARG_POINTER(aRequester); nsCOMPtr requester = mRequester; requester.forget(aRequester); return NS_OK; } private: ~DeviceStorageRequest() {} int32_t mRequestType; nsCOMPtr mWindow; nsCOMPtr mPrincipal; nsRefPtr mFile; nsRefPtr mRequest; nsRefPtr mBlob; nsRefPtr mDeviceStorage; nsRefPtr mDSFileDescriptor; nsCOMPtr mRequester; }; NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest) NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) NS_INTERFACE_MAP_ENTRY(nsIRunnable) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DeviceStorageRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageRequest) NS_IMPL_CYCLE_COLLECTION(DeviceStorageRequest, mRequest, mWindow, mBlob, mDeviceStorage) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorage) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper) int nsDOMDeviceStorage::sInstanceCount = 0; nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindow* aWindow) : DOMEventTargetHelper(aWindow) , mIsShareable(false) , mIsRemovable(false) , mIsWatchingFile(false) , mAllowedToWatchFile(false) { MOZ_ASSERT(NS_IsMainThread()); sInstanceCount++; } /* virtual */ JSObject* nsDOMDeviceStorage::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return DeviceStorageBinding::Wrap(aCx, this, aGivenProto); } nsresult nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType, const nsAString &aVolName) { DebugOnly observer = FileUpdateDispatcher::GetSingleton(); MOZ_ASSERT(observer); MOZ_ASSERT(aWindow); SetRootDirectoryForType(aType, aVolName); if (!mRootDirectory) { return NS_ERROR_NOT_AVAILABLE; } if (!mStorageName.IsEmpty()) { Preferences::AddStrongObserver(this, PREF_STORAGE_WRITABLE_NAME); mIsDefaultLocation = Default(); RegisterForSDCardChanges(this); #ifdef MOZ_WIDGET_GONK if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) { nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); if (NS_WARN_IF(!vs)) { return NS_ERROR_FAILURE; } nsresult rv; nsCOMPtr vol; rv = vs->GetVolumeByName(mStorageName, getter_AddRefs(vol)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool isFake; rv = vol->GetIsFake(&isFake); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mIsShareable = !isFake; bool isRemovable; rv = vol->GetIsHotSwappable(&isRemovable); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mIsRemovable = isRemovable; } #endif } // Grab the principal of the document nsCOMPtr doc = aWindow->GetDoc(); if (!doc) { return NS_ERROR_FAILURE; } mPrincipal = doc->NodePrincipal(); // the 'apps' type is special. We only want this exposed // if the caller has the "webapps-manage" permission. if (aType.EqualsLiteral(DEVICESTORAGE_APPS)) { nsCOMPtr permissionManager = services::GetPermissionManager(); NS_ENSURE_TRUE(permissionManager, NS_ERROR_FAILURE); uint32_t permission; nsresult rv = permissionManager->TestPermissionFromPrincipal(mPrincipal, "webapps-manage", &permission); if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) { return NS_ERROR_NOT_AVAILABLE; } } return NS_OK; } nsDOMDeviceStorage::~nsDOMDeviceStorage() { MOZ_ASSERT(NS_IsMainThread()); sInstanceCount--; } void nsDOMDeviceStorage::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); if (mFileSystem) { mFileSystem->Shutdown(); mFileSystem = nullptr; } if (!mStorageName.IsEmpty()) { Preferences::RemoveObserver(this, PREF_STORAGE_WRITABLE_NAME); UnregisterForSDCardChanges(this); } nsCOMPtr obs = mozilla::services::GetObserverService(); obs->RemoveObserver(this, kFileWatcherUpdate); obs->RemoveObserver(this, "disk-space-watcher"); } StaticAutoPtr> nsDOMDeviceStorage::sVolumeNameCache; // static void nsDOMDeviceStorage::GetOrderedVolumeNames( nsDOMDeviceStorage::VolumeNameArray &aVolumeNames) { if (sVolumeNameCache && sVolumeNameCache->Length() > 0) { aVolumeNames.AppendElements(*sVolumeNameCache); return; } #ifdef MOZ_WIDGET_GONK nsCOMPtr vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID); if (vs) { nsCOMPtr volNames; vs->GetVolumeNames(getter_AddRefs(volNames)); uint32_t length = -1; volNames->GetLength(&length); for (uint32_t i = 0; i < length; i++) { nsCOMPtr str = do_QueryElementAt(volNames, i); if (str) { nsAutoString s; if (NS_SUCCEEDED(str->GetData(s)) && !s.IsEmpty()) { aVolumeNames.AppendElement(s); } } } // If the volume sdcard exists, then we want it to be first. VolumeNameArray::index_type sdcardIndex; sdcardIndex = aVolumeNames.IndexOf(NS_LITERAL_STRING("sdcard")); if (sdcardIndex != VolumeNameArray::NoIndex && sdcardIndex > 0) { aVolumeNames.RemoveElementAt(sdcardIndex); aVolumeNames.InsertElementAt(0, NS_LITERAL_STRING("sdcard")); } } #endif if (aVolumeNames.IsEmpty()) { aVolumeNames.AppendElement(EmptyString()); } sVolumeNameCache = new nsTArray; sVolumeNameCache->AppendElements(aVolumeNames); } // static void nsDOMDeviceStorage::CreateDeviceStorageFor(nsPIDOMWindow* aWin, const nsAString &aType, nsDOMDeviceStorage** aStore) { nsString storageName; if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) { // The storage name will be the empty string storageName.Truncate(); } else { GetDefaultStorageName(aType, storageName); } nsRefPtr ds = new nsDOMDeviceStorage(aWin); if (NS_FAILED(ds->Init(aWin, aType, storageName))) { *aStore = nullptr; return; } ds.forget(aStore); } // static void nsDOMDeviceStorage::CreateDeviceStoragesFor( nsPIDOMWindow* aWin, const nsAString &aType, nsTArray > &aStores) { nsresult rv; if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) { nsRefPtr storage = new nsDOMDeviceStorage(aWin); rv = storage->Init(aWin, aType, EmptyString()); if (NS_SUCCEEDED(rv)) { aStores.AppendElement(storage); } return; } VolumeNameArray volNames; GetOrderedVolumeNames(volNames); VolumeNameArray::size_type numVolumeNames = volNames.Length(); for (VolumeNameArray::index_type i = 0; i < numVolumeNames; i++) { nsRefPtr storage = new nsDOMDeviceStorage(aWin); rv = storage->Init(aWin, aType, volNames[i]); if (NS_FAILED(rv)) { break; } aStores.AppendElement(storage); } } // static void nsDOMDeviceStorage::CreateDeviceStorageByNameAndType( nsPIDOMWindow* aWin, const nsAString& aName, const nsAString& aType, nsDOMDeviceStorage** aStore) { if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) { nsRefPtr storage = new nsDOMDeviceStorage(aWin); if (NS_FAILED(storage->Init(aWin, aType, EmptyString()))) { *aStore = nullptr; return; } NS_ADDREF(*aStore = storage.get()); return; } nsRefPtr storage = GetStorageByNameAndType(aWin, aName, aType); NS_ADDREF(*aStore = storage.get()); } // static bool nsDOMDeviceStorage::ParseFullPath(const nsAString& aFullPath, nsAString& aOutStorageName, nsAString& aOutStoragePath) { aOutStorageName.Truncate(); aOutStoragePath.Truncate(); NS_NAMED_LITERAL_STRING(slash, "/"); nsDependentSubstring storageName; if (StringBeginsWith(aFullPath, slash)) { int32_t slashIndex = aFullPath.FindChar('/', 1); if (slashIndex == kNotFound) { // names of the form /filename are illegal return false; } storageName.Rebind(aFullPath, 1, slashIndex - 1); aOutStoragePath = Substring(aFullPath, slashIndex + 1); } else { aOutStoragePath = aFullPath; } // If no volume name was specified in aFullPath, then aOutStorageName // will wind up being the empty string. It's up to the caller to figure // out which storage name to actually use. aOutStorageName = storageName; return true; } already_AddRefed nsDOMDeviceStorage::GetStorage(const nsAString& aFullPath, nsAString& aOutStoragePath) { nsString storageName; if (!ParseFullPath(aFullPath, storageName, aOutStoragePath)) { return nullptr; } nsRefPtr ds; if (storageName.IsEmpty()) { ds = this; } else { ds = GetStorageByName(storageName); } return ds.forget(); } already_AddRefed nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr ds; if (mStorageName.Equals(aStorageName)) { ds = this; return ds.forget(); } return GetStorageByNameAndType(GetOwner(), aStorageName, mStorageType); } // static already_AddRefed nsDOMDeviceStorage::GetStorageByNameAndType(nsPIDOMWindow* aWin, const nsAString& aStorageName, const nsAString& aType) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr ds; VolumeNameArray volNames; GetOrderedVolumeNames(volNames); VolumeNameArray::size_type numVolumes = volNames.Length(); VolumeNameArray::index_type i; for (i = 0; i < numVolumes; i++) { if (volNames[i].Equals(aStorageName)) { ds = new nsDOMDeviceStorage(aWin); nsresult rv = ds->Init(aWin, aType, aStorageName); if (NS_FAILED(rv)) { return nullptr; } return ds.forget(); } } return nullptr; } // static void nsDOMDeviceStorage::GetDefaultStorageName(const nsAString& aStorageType, nsAString& aStorageName) { // See if the preferred volume is available. nsAdoptingString prefStorageName = mozilla::Preferences::GetString(PREF_STORAGE_WRITABLE_NAME); if (prefStorageName) { nsString status; nsRefPtr dsf = new DeviceStorageFile(aStorageType, prefStorageName); dsf->GetStorageStatus(status); if (!status.EqualsLiteral("NoMedia")) { aStorageName = prefStorageName; return; } } // If there is no preferred storage or preferred storage is not presented, // we'll use the first one (which should be sdcard). VolumeNameArray volNames; GetOrderedVolumeNames(volNames); if (volNames.Length() > 0) { aStorageName = volNames[0]; // overwrite the value of "device.storage.writable.name" mozilla::Preferences::SetString(PREF_STORAGE_WRITABLE_NAME, aStorageName); return; } // No volumes available, return the empty string. This is normal for // b2g-desktop. aStorageName.Truncate(); } bool nsDOMDeviceStorage::IsAvailable() { nsRefPtr dsf(new DeviceStorageFile(mStorageType, mStorageName)); return dsf->IsAvailable(); } already_AddRefed nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv) { if (!aBlob) { return nullptr; } nsCOMPtr mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID); if (!mimeSvc) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } // if mimeType isn't set, we will not get a correct // extension, and AddNamed() will fail. This will post an // onerror to the requestee. nsString mimeType; aBlob->GetType(mimeType); nsCString extension; mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType), EmptyCString(), extension); // if extension is null here, we will ignore it for now. // AddNamed() will check the file path and fail. This // will post an onerror to the requestee. // possible race here w/ unique filename char buffer[32]; NS_MakeRandomString(buffer, ArrayLength(buffer) - 1); nsAutoCString path; path.Assign(nsDependentCString(buffer)); path.Append('.'); path.Append(extension); return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), aRv); } already_AddRefed nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath, ErrorResult& aRv) { return AddOrAppendNamed(aBlob, aPath, DEVICE_STORAGE_REQUEST_CREATE, aRv); } already_AddRefed nsDOMDeviceStorage::AppendNamed(Blob* aBlob, const nsAString& aPath, ErrorResult& aRv) { return AddOrAppendNamed(aBlob, aPath, DEVICE_STORAGE_REQUEST_APPEND, aRv); } already_AddRefed nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath, const int32_t aRequestType, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); // if the blob is null here, bail if (!aBlob) { return nullptr; } nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr r; nsresult rv; if (IsFullPath(aPath)) { nsString storagePath; nsRefPtr ds = GetStorage(aPath, storagePath); if (!ds) { nsRefPtr request = new DOMRequest(win); r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN); rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } return ds->AddOrAppendNamed(aBlob, storagePath, aRequestType, aRv); } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath); if (!dsf->IsSafePath()) { r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED); } else if (!typeChecker->Check(mStorageType, dsf->mFile) || !typeChecker->Check(mStorageType, aBlob)) { r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE); } else if (aRequestType == DEVICE_STORAGE_REQUEST_APPEND || aRequestType == DEVICE_STORAGE_REQUEST_CREATE) { r = new DeviceStorageRequest(DeviceStorageRequestType(aRequestType), win, mPrincipal, dsf, request, aBlob); } else { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); if (IsFullPath(aPath)) { nsString storagePath; nsRefPtr ds = GetStorage(aPath, storagePath); if (!ds) { nsCOMPtr r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } ds->GetInternal(win, storagePath, request, aEditable); return request.forget(); } GetInternal(win, aPath, request, aEditable); return request.forget(); } void nsDOMDeviceStorage::GetInternal(nsPIDOMWindow *aWin, const nsAString& aPath, DOMRequest* aRequest, bool aEditable) { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath); dsf->SetEditable(aEditable); nsCOMPtr r; if (!dsf->IsSafePath()) { r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED); } else { r = new DeviceStorageRequest(aEditable ? DEVICE_STORAGE_REQUEST_WRITE : DEVICE_STORAGE_REQUEST_READ, aWin, mPrincipal, dsf, aRequest); } DebugOnly rv = NS_DispatchToCurrentThread(r); MOZ_ASSERT(NS_SUCCEEDED(rv)); } already_AddRefed nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); if (IsFullPath(aPath)) { nsString storagePath; nsRefPtr ds = GetStorage(aPath, storagePath); if (!ds) { nsCOMPtr r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } ds->DeleteInternal(win, storagePath, request); return request.forget(); } DeleteInternal(win, aPath, request); return request.forget(); } void nsDOMDeviceStorage::DeleteInternal(nsPIDOMWindow *aWin, const nsAString& aPath, DOMRequest* aRequest) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr r; nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath); if (!dsf->IsSafePath()) { r = new PostErrorEvent(aRequest, POST_ERROR_EVENT_PERMISSION_DENIED); } else { r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_DELETE, aWin, mPrincipal, dsf, aRequest); } DebugOnly rv = NS_DispatchToCurrentThread(r); MOZ_ASSERT(NS_SUCCEEDED(rv)); } already_AddRefed nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FREE_SPACE, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } DebugOnly usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet(); MOZ_ASSERT(usedSpaceCache); nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_USED_SPACE, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::Available(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_AVAILABLE, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_STATUS, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::Format(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_FORMAT, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::Mount(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_MOUNT, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::Unmount(ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_UNMOUNT, win, mPrincipal, dsf, request); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_FAILED(rv)) { aRv.Throw(rv); } return request.forget(); } already_AddRefed nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath, DeviceStorageFileDescriptor* aDSFileDescriptor, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aDSFileDescriptor); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } DeviceStorageTypeChecker* typeChecker = DeviceStorageTypeChecker::CreateOrGet(); if (!typeChecker) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsCOMPtr r; if (IsFullPath(aPath)) { nsString storagePath; nsRefPtr ds = GetStorage(aPath, storagePath); if (!ds) { nsRefPtr request = new DOMRequest(win); r = new PostErrorEvent(request, POST_ERROR_EVENT_UNKNOWN); aRv = NS_DispatchToCurrentThread(r); if (aRv.Failed()) { return nullptr; } return request.forget(); } return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv); } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath); if (!dsf->IsSafePath()) { r = new PostErrorEvent(request, POST_ERROR_EVENT_PERMISSION_DENIED); } else if (!typeChecker->Check(mStorageType, dsf->mFile)) { r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_TYPE); } else { r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_CREATEFD, win, mPrincipal, dsf, request, aDSFileDescriptor); } aRv = NS_DispatchToCurrentThread(r); if (aRv.Failed()) { return nullptr; } return request.forget(); } bool nsDOMDeviceStorage::Default() { nsString defaultStorageName; GetDefaultStorageName(mStorageType, defaultStorageName); return mStorageName.Equals(defaultStorageName); } bool nsDOMDeviceStorage::CanBeFormatted() { // Currently, any volume which can be shared can also be formatted. return mIsShareable; } bool nsDOMDeviceStorage::CanBeMounted() { // Currently, any volume which can be shared can also be mounted/unmounted. return mIsShareable; } bool nsDOMDeviceStorage::CanBeShared() { return mIsShareable; } bool nsDOMDeviceStorage::IsRemovable() { return mIsRemovable; } already_AddRefed nsDOMDeviceStorage::GetRoot(ErrorResult& aRv) { if (!mFileSystem) { mFileSystem = new DeviceStorageFileSystem(mStorageType, mStorageName); mFileSystem->Init(this); } return mozilla::dom::Directory::GetRoot(mFileSystem, aRv); } void nsDOMDeviceStorage::GetStorageName(nsAString& aStorageName) { aStorageName = mStorageName; } already_AddRefed nsDOMDeviceStorage::Enumerate(const nsAString& aPath, const EnumerationParameters& aOptions, ErrorResult& aRv) { return EnumerateInternal(aPath, aOptions, false, aRv); } already_AddRefed nsDOMDeviceStorage::EnumerateEditable(const nsAString& aPath, const EnumerationParameters& aOptions, ErrorResult& aRv) { return EnumerateInternal(aPath, aOptions, true, aRv); } already_AddRefed nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath, const EnumerationParameters& aOptions, bool aEditable, ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr win = GetOwner(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } PRTime since = 0; if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) { since = PRTime(aOptions.mSince.Value().TimeStamp()); } nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath, EmptyString()); dsf->SetEditable(aEditable); nsRefPtr cursor = new nsDOMDeviceStorageCursor(win, mPrincipal, dsf, since); nsRefPtr r = new DeviceStorageCursorRequest(cursor); if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { r->Allow(JS::UndefinedHandleValue); return cursor.forget(); } nsContentPermissionUtils::AskPermission(r, win); return cursor.forget(); } void nsDOMDeviceStorage::DispatchDefaultChangeEvent() { nsAdoptingString DefaultLocation; GetDefaultStorageName(mStorageType, DefaultLocation); DeviceStorageChangeEventInit init; init.mBubbles = true; init.mCancelable = false; init.mPath = DefaultLocation; if (mIsDefaultLocation) { init.mReason.AssignLiteral("default-location-changed"); } else { init.mReason.AssignLiteral("became-default-location"); } nsRefPtr event = DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING(STORAGE_CHANGE_EVENT), init); event->SetTrusted(true); bool ignore; DispatchEvent(event, &ignore); mIsDefaultLocation = Default(); } #ifdef MOZ_WIDGET_GONK void nsDOMDeviceStorage::DispatchStatusChangeEvent(nsAString& aStatus) { if (aStatus == mLastStatus) { // We've already sent this status, don't bother sending it again. return; } mLastStatus = aStatus; DeviceStorageChangeEventInit init; init.mBubbles = true; init.mCancelable = false; init.mPath = mStorageName; init.mReason = aStatus; nsRefPtr event = DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING(STORAGE_CHANGE_EVENT), init); event->SetTrusted(true); bool ignore; DispatchEvent(event, &ignore); } void nsDOMDeviceStorage::DispatchStorageStatusChangeEvent(nsAString& aStorageStatus) { if (aStorageStatus == mLastStorageStatus) { // We've already sent this status, don't bother sending it again. return; } mLastStorageStatus = aStorageStatus; DeviceStorageChangeEventInit init; init.mBubbles = true; init.mCancelable = false; init.mPath = mStorageName; init.mReason = aStorageStatus; nsRefPtr event = DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("storage-state-change"), init); event->SetTrusted(true); bool ignore; DispatchEvent(event, &ignore); } #endif NS_IMETHODIMP nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { MOZ_ASSERT(NS_IsMainThread()); if (!strcmp(aTopic, kFileWatcherUpdate)) { DeviceStorageFile* file = static_cast(aSubject); Notify(NS_ConvertUTF16toUTF8(aData).get(), file); return NS_OK; } if (!strcmp(aTopic, "disk-space-watcher")) { // 'disk-space-watcher' notifications are sent when there is a modification // of a file in a specific location while a low device storage situation // exists or after recovery of a low storage situation. For Firefox OS, // these notifications are specific for apps storage. nsRefPtr file = new DeviceStorageFile(mStorageType, mStorageName); if (!NS_strcmp(aData, MOZ_UTF16("full"))) { Notify("low-disk-space", file); } else if (!NS_strcmp(aData, MOZ_UTF16("free"))) { Notify("available-disk-space", file); } return NS_OK; } if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) && aData && nsDependentString(aData).Equals(NS_LITERAL_STRING(PREF_STORAGE_WRITABLE_NAME))) { DispatchDefaultChangeEvent(); return NS_OK; } #ifdef MOZ_WIDGET_GONK else if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) { // We invalidate the used space cache for the volume that actually changed // state. nsCOMPtr vol = do_QueryInterface(aSubject); if (!vol) { return NS_OK; } nsString volName; vol->GetName(volName); DeviceStorageUsedSpaceCache* usedSpaceCache = DeviceStorageUsedSpaceCache::CreateOrGet(); MOZ_ASSERT(usedSpaceCache); usedSpaceCache->Invalidate(volName); if (!volName.Equals(mStorageName)) { // Not our volume - we can ignore. return NS_OK; } nsRefPtr dsf(new DeviceStorageFile(mStorageType, mStorageName)); nsString status, storageStatus; // Get Status (one of "available, unavailable, shared") dsf->GetStatus(status); DispatchStatusChangeEvent(status); // Get real volume status (defined in dom/system/gonk/nsIVolume.idl) dsf->GetStorageStatus(storageStatus); DispatchStorageStatusChangeEvent(storageStatus); return NS_OK; } #endif return NS_OK; } nsresult nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile) { if (!mAllowedToWatchFile) { return NS_OK; } if (!mStorageType.Equals(aFile->mStorageType) || !mStorageName.Equals(aFile->mStorageName)) { // Ignore this return NS_OK; } DeviceStorageChangeEventInit init; init.mBubbles = true; init.mCancelable = false; aFile->GetFullPath(init.mPath); init.mReason.AssignWithConversion(aReason); nsRefPtr event = DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING(STORAGE_CHANGE_EVENT), init); event->SetTrusted(true); bool ignore; DispatchEvent(event, &ignore); return NS_OK; } void nsDOMDeviceStorage::EventListenerWasAdded(const nsAString& aType, ErrorResult& aRv, JSCompartment* aCompartment) { MOZ_ASSERT(NS_IsMainThread()); if (!aType.EqualsLiteral("change")) { return; } nsCOMPtr win = GetOwner(); if (NS_WARN_IF(!win)) { aRv.Throw(NS_ERROR_UNEXPECTED); return; } nsRefPtr request = new DOMRequest(win); nsRefPtr dsf = new DeviceStorageFile(mStorageType, mStorageName); nsCOMPtr r = new DeviceStorageRequest(DEVICE_STORAGE_REQUEST_WATCH, win, mPrincipal, dsf, request, this); nsresult rv = NS_DispatchToCurrentThread(r); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); } }