Files
roytam1 69d1f32ff7 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1268085 - Remove unused post barrier callbacks r=terrence (0ab13411c9)
- Bug 1267699 - Move some public types to the right namespace; r=sfink (3d5008e610)
- Bug 1267550 (part 1) - Rename MOZ_MUST_USE as MOZ_MUST_USE_TYPE. r=ehsan. (6f47375796)
- Bug 1259021 - Rename Vector::extractRawBuffer to extractOrCopyRawBuffer r=Waldo (97ca94495b)
- Bug 1259021 - Add Vector::extractRawBuffer method that doesn't copy the buffer r=Waldo (e58deec48f)
- Bug 1265892 - Change Vector to use Impl::new_ consistently. r=Waldo (7a52d21b29)
- Bug 1267912 - Rename nsNetUtil.inl as nsNetUtilInlines.h. r=valentin. (548a41b293)
- Bug 1265690 part 1 - Mark StringBuffer methods WARN_UNUSED_RESULT, fix OOM issues. r=jonco (0d7e6837e3)
- Bug 1265690 part 2 - Fix some more OOM issues in TypedObject code. r=jonco (b60902453e)
- Bug 1263490 - Part 2: Add GetFirstDollarIndex intrinsic and use it inRegExpReplace. r=till (4ba19db8c4)
- Bug 1263490 - Part 3: Inline GetFirstDollarIndex intrinsic. r=h4writer (e7d9b5d1cc)
- Bug 1263490 - Part 4: Fold GetFirstDollarIndex into a integer constant. r=h4writer (3479c7d1af)
- Bug 1267269 - Make MIRType an enum class. r=bbouvier (d580ef372a)
- Bug 1259295 - BaldrMonkey: Postorder (r=luke) (6ef7a77663)
- Bug 1254142: BaldrMonkey: make br_table yield (r=luke) (80e7635e58)
- Bug 1263202 - BaldrMonkey: switch to arities on branches, calls and return (r=bbouvier) (f5a0358634)
- Bug 1236358 - Improper reading of string16 in Pickle::ReadString16. r=jld (8370ba6a0b)
- Bug 1263205 - BaldrMonkey: Update section headers for proposed spec changes (r=luke) (0def2e6bc2)
- Bug 1263205 - BaldrMonkey: Update for proposed new section names (r=luke) (e57f0e3367)
- Bug 1263205 - BaldrMonkey: Add 'form' field to types section (r=bbouvier) (794edc890f)
- Bug 1259021 - Use in-place storage in AutoStableStringChars to avoid allocation for short strings r=jandem r=Waldo (ffb53cbcf4)
- Bug 1267550 (part 2) - Rename MOZ_WARN_UNUSED_RESULT as MOZ_MUST_USE. r=froydnj. (47bc674b86)
- Bug 1268518: Baldr: implement int32/int64 rotations; r=luke (0d5eedccce)
- Bug 1255008: IonMonkey - Add a by default disabled flow sensitive alias analysis pass, r=jandem (521c585d75)
- Bug 1266781: Baldr: implement proper checked truncations to integer types; r=sunfish (46078fb3d3)
- Bug 1266781: Rename MTruncateToInt64 into MWasmTruncateInt64; r=sunfish (c7d7d1ac11)
- Bug 1266781: Add new traps; r=luke (b7ed3d44e6)
- Bug 1268024: Pass the atomic attribute down to EmitHeapAccess; r=luke (6195f7d7a3)
- Bug 1268024: A few cleanups related to loads/stores; r=luke (88141e3a01)
- Bug 1258312 - Make Pickle::Resize infallible r=jld (241ee9b60d)
- Bug 1162772, part 1 - Allow CompartmentCreationOptions to store Secure Context state. r=jorendorff (ff666384cf)
- Bug 1162772, part 2 - Expose whether SEC_FORCE_INHERIT_PRINCIPAL was dropped from an nsILoadInfo. r=bz (ada46f86bf)
- Bug 1162772, part 3 - Add a getChannelResultPrincipalIfNotSandboxed method to nsIScriptSecurityManager. r=bz (5b1d9f6807)
- Bug 1162772, part 4 - Implement nsGlobalWindow::IsSecureContext. r=bz (f392f439c9)
- Bug 1162772, part 5 - Expose Window.isSecureContext to content. r=bz (e7296e2cf1)
- Bug 1267509 - Make nsContentSecurityManager::IsURIPotentiallyTrustworthy act on an nsIPrincipal. r=bz (83de80350a)
- Bug 1219098 - Use UniquePtr in UncompressedSourceCache, for it is good (r=jandem) (b68769c729)
- Bug 1244279 - Part 1: Take a bit in ObjectElements::Flags to indicate whether the object is in the whole cell store buffer. r=terrence (968cf373f9)
- Bug 1244279 - Part 0: Add a GC ubench for large arrays with both elements and properties. r=terrence (ec76b48323)
- Bug 1255925 - Give a name to getters/setters and integer-named methods. r=efaust (f978cc6916)
- Bug 888969 - Make the getPrototypeOf/setPrototypeOf traps scriptable. r=efaust, r=bholley (eb2325a9ea)
- Bug 1267557 part 0 - Move JS poison constants to jsutil.h. r=jonco (65afc690d2)
- Bug 1267557 part 1 - Also poison bytes allocated before the actual jitcode. r=nbp (70f0b327d3)
- Bug 1267557 part 2 - Use different jitcode poison values. r=nbp (08008ab9dc)
- Bug 1267557 part 3 - Define JS_SWEPT_CODE_PATTERN for mips. r=nbp (17e894d59d)
- Bug 1267449 - Do not infinite loop in js_fputs; r=jimb (67f961b6cd)
- Bug 1219098 - Reenable compression on large sources, but revert to uncompressed if decompression happens (r=jandem) (b44ee8d77d)
- Bug 1267551 (part 1) - Use MOZ_MUST_USE more in jsnum.h. r=jonco. (d2476bf8f4)
- Bug 1267551 (part 2) - Use MOZ_MUST_USE more in js/src/ds/. r=jonco. (4ff5d9aa88)
- Bug 1267412 - Use MutableHandleValue instead of pointer-to-AutoValueVector; r=sfink (3f6dd284bb)
- Bug 1266406 - Use EnumSet<AllocKind> to simplify GC sweeping phase information r=terrence (64811500e7)
- Bug 1266457 - Update pointers in GC things in two phases when compacting r=terrence (f6f5bc4e4d)
- Bug 1266457 - Simplify typed object trace hook r=terence (3b06c8d1e5)
- Bug 1268541 - Compact arenas containing base shapes r=terrence (b458b92eea)
- Bug 1268805 - Implement PrivateGCThingValue. (r=terrence) (deec9a83ae)
- Bug 1268415: Initialize members in UpdatePointerTasks; r=jonco (6cb219005a)
- Bug 1268501 - Release the GC lock periodically when releasing arenas on the backgound thread r=terrence (37f0997682)
- Bug 1263572 - Wait for background sweeping to finish before checking base shapes r=terrence (354801a411)
- Bug 1266887 - Store Rooted heads on the Zone; r=sfink (91c0101ee3)
- Bug 1266402 - Add iteration to EnumSet<T> so that it can be used in range-based for loops r=Waldo (e9507a2524)
- Bug 1266404 - Allow construction of an EnumSet<T> using an initializer list r=Waldo (1b6d340e99)
- Bug 1254020 - Always compute theme scaling factor when per-monitor dpi aware, even if only a single display is currently present. r=emk (a00cda21f4)
- Bug 1263525 - Add dedicated function for std_Array self-hosted intrinsic. r=efaust (449d8bb7eb)
- Bug 1255925 - Change JSFunction::name to return a JSAtom. r=efaust (5ab396ce83)
- Bug 888969 - Make our tree's sole implementation of nsIRemoteTagService.getRemoteObjectTag not depend upon the infallibility of [[GetPrototypeOf]] on the object provided to it. r=bz (f388f4bf1f)
- Bug 1264896 - Kill off nsIRemoteTagService and do what it does, in its sole caller, in far-faster C++. r=billm (5ed3fb103d)
- Bug 1268246 - Add a simple Poison class lifetime checker. r=froydnj (7b237bc70e)
- Bug 1249496 - Don't apply dpi-based scaling for window titlebar dimensions when on a secondary display, because windows doesn't scale it. r=emk (64dd706dbc)
- Bug 1164518 - Avoid unnecessary DB updates when caching Safe Browsing results. r=gcp (3cafd9a4df)
- Bug 1264472 - Use nsRunnables in FIDO U2F. r=keeler (3aa9570132)
- Bug 1236060 - Dispatch error should advance queue. r=smaug (74155b75dd)
- Bug 1251697 part 1. Thread an ErrorResult reference through the worker XHR WorkerThreadProxySyncRunnable implementations. r=khuey (77804cbb7c)
- Bug 1251697 part 2. Have WorkerThreadProxySyncRunnable hand the ErrorResult reference it holds to its ResponseRunnable so it can report exceptions on there instead of on a JSContext. r=khuey (355c9ee313)
- Bug 1251697 part 3. Remove the JSContext argument of StopSyncLoopRunnable::MaybeSetException. r=khuey (010f5b1058)
- Bug 1155328. r=smaug (e1f8dac304)
- Bug 1265927: Move nsRunnable to mozilla::Runnable, CancelableRunnable to mozilla::CancelableRunnable. r=froydnj (f83bfcae02)
- Bug 1239946 - Change test to return error on Speak. r=eeejay (1d402beb02)
- Bug 1254378 - Update synth tests and introduce no voiceschanged test. r=smaug (f5823bb70e)
- Bug 1251627. Fix XMLHttpRequest.send() to follow the spec better in terms of the exceptions it throws. r=khuey (cd0e321948)
- Bug 1268868: [MSE] P1. Re-enable gap detection within a media segment. r=gerald (b8b8df4bc2)
- Bug 1268868: [MSE] P2. Reset longest duration after keyframe is seen. r=gerald (2b1401465c)
- Bug 1268868: [MSE] P3. Prevent crash should gap be detected in content. r=gerald (063d9376fc)
- Bug 1254378 - Implement nsISynthVoiceRegistry.notifyVoicesChanged. r=smaug (4b63b1c360)
- Bug 1266804 - Un-inline js::Unbox(); r=jorendorff (0f288b6173)
- Bug 1268863 - Report ScriptSources that are only reachable via AsmJSModule (r=njn) (5ba40acb64)
- bump version to 45.1b1 (1414db0ca8)
- Bug 1262062 - remove old futex names. r=bbouvier (62662bdd2e)
- memory: build fix after renaming MOZ_WARN_UNUSED_RESULT (7254dc8d53)
- import from mozilla:
 - Bug 1268725 - BaldrMonkey: Refactor away the internal storage from ExprIter. r=luke (1931bd636f17)
 - Bug 1268725 - BaldrMonkey: Convert default arguments into explicit arguments. r=luke (c8a11b8b6bbd) (867ec715d6)
2024-08-21 10:45:07 +08:00

4204 lines
110 KiB
C++

/* -*- 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/Scoped.h"
#include "mozilla/Services.h"
#include "mozilla/ipc/BackgroundUtils.h" // for PrincipalInfoToPrincipal
#include "nsArrayUtils.h"
#include "nsAutoPtr.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsGlobalWindow.h"
#include "nsServiceManagerUtils.h"
#include "nsIFile.h"
#include "nsIDirectoryEnumerator.h"
#include "nsNetUtil.h"
#include "nsIOutputStream.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIPrincipal.h"
#include "nsJSUtils.h"
#include "nsContentUtils.h"
#include "nsXULAppAPI.h"
#include "DeviceStorageFileDescriptor.h"
#include "DeviceStorageRequestChild.h"
#include "DeviceStorageStatics.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 <algorithm>
#include "private/pprio.h"
#include "nsContentPermissionHelper.h"
#include "mozilla/dom/DeviceStorageBinding.h"
// Microsoft's API Name hackery sucks
#undef CreateEvent
#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 STORAGE_CHANGE_EVENT "change"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::devicestorage;
using namespace mozilla::ipc;
namespace mozilla
{
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
} // namespace mozilla
namespace {
void
NormalizeFilePath(nsAString& aPath)
{
#if defined(XP_WIN)
char16_t* cur = aPath.BeginWriting();
char16_t* end = aPath.EndWriting();
for (; cur < end; ++cur) {
if (char16_t('\\') == *cur) {
*cur = FILESYSTEM_DOM_PATH_SEPARATOR_CHAR;
}
}
#endif
}
bool
TokenizerIgnoreNothing(char16_t /* aChar */)
{
return false;
}
} // anonymous namespace
StaticAutoPtr<DeviceStorageUsedSpaceCache>
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::CacheEntry>
DeviceStorageUsedSpaceCache::GetCacheEntry(const nsAString& aStorageName)
{
nsTArray<RefPtr<CacheEntry>>::size_type numEntries = mCacheEntries.Length();
nsTArray<RefPtr<CacheEntry>>::index_type i;
for (i = 0; i < numEntries; i++) {
RefPtr<CacheEntry>& cacheEntry = mCacheEntries[i];
if (cacheEntry->mStorageName.Equals(aStorageName)) {
RefPtr<CacheEntry> 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.
RefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_PICTURES),
aStorageName));
int64_t freeBytes = 0;
dsf->GetStorageFreeSpace(&freeBytes);
return freeBytes;
}
nsresult
DeviceStorageUsedSpaceCache::AccumUsedSizes(const nsAString& aStorageName,
uint64_t* aPicturesSoFar,
uint64_t* aVideosSoFar,
uint64_t* aMusicSoFar,
uint64_t* aTotalSoFar)
{
RefPtr<CacheEntry> 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)
{
RefPtr<CacheEntry> 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;
}
StaticAutoPtr<DeviceStorageTypeChecker>
DeviceStorageTypeChecker::sDeviceStorageTypeChecker;
DeviceStorageTypeChecker::DeviceStorageTypeChecker()
{
}
DeviceStorageTypeChecker::~DeviceStorageTypeChecker()
{
}
DeviceStorageTypeChecker*
DeviceStorageTypeChecker::CreateOrGet()
{
if (sDeviceStorageTypeChecker) {
return sDeviceStorageTypeChecker;
}
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIStringBundleService> stringService
= mozilla::services::GetStringBundleService();
if (!stringService) {
return nullptr;
}
nsCOMPtr<nsIStringBundle> 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, BlobImpl* 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;
}
size_t
DeviceStorageTypeChecker::GetAccessIndexForRequest(
const DeviceStorageRequestType aRequestType)
{
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:
case DEVICE_STORAGE_REQUEST_CURSOR:
return DEVICE_STORAGE_ACCESS_READ;
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:
return DEVICE_STORAGE_ACCESS_WRITE;
case DEVICE_STORAGE_REQUEST_CREATE:
case DEVICE_STORAGE_REQUEST_CREATEFD:
return DEVICE_STORAGE_ACCESS_CREATE;
default:
return DEVICE_STORAGE_ACCESS_UNDEFINED;
}
}
nsresult
DeviceStorageTypeChecker::GetAccessForRequest(
const DeviceStorageRequestType aRequestType, nsACString& aAccessResult)
{
size_t access = GetAccessIndexForRequest(aRequestType);
return GetAccessForIndex(access, aAccessResult);
}
nsresult
DeviceStorageTypeChecker::GetAccessForIndex(
size_t aAccessIndex, nsACString& aAccessResult)
{
static const char *names[] = { "read", "write", "create", "undefined" };
MOZ_ASSERT(aAccessIndex < MOZ_ARRAY_LENGTH(names));
aAccessResult.AssignASCII(names[aAccessIndex]);
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) && DeviceStorageStatics::HasOverrideRootDir();
#endif
}
class IOEventComplete : public Runnable
{
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<nsIObserverService> obs = mozilla::services::GetObserverService();
obs->NotifyObservers(mFile, "file-watcher-notify", data.get());
DeviceStorageUsedSpaceCache* usedSpaceCache
= DeviceStorageUsedSpaceCache::CreateOrGet();
MOZ_ASSERT(usedSpaceCache);
usedSpaceCache->Invalidate(mFile->mStorageName);
return NS_OK;
}
private:
RefPtr<DeviceStorageFile> 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(mPath);
}
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(mPath);
}
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_IsParentProcess()) {
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<DeviceStorageTypeChecker*> typeChecker
= DeviceStorageTypeChecker::CreateOrGet();
MOZ_ASSERT(typeChecker);
DS_LOG_INFO("type '%s' name '%s' root '%s' path '%s'",
NS_LossyConvertUTF16toASCII(mStorageType).get(),
NS_LossyConvertUTF16toASCII(mStorageName).get(),
NS_LossyConvertUTF16toASCII(mRootDir).get(),
NS_LossyConvertUTF16toASCII(mPath).get());
}
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 DeviceStorageStatics::Initialize. 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<nsIFile> f;
*aFile = nullptr;
DeviceStorageStatics::InitializeDirs();
#ifdef MOZ_WIDGET_GONK
nsresult rv;
nsString volMountPoint;
if (DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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
if (aStorageType.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
f = DeviceStorageStatics::GetPicturesDir();
} else if (aStorageType.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
f = DeviceStorageStatics::GetVideosDir();
} else if (aStorageType.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
f = DeviceStorageStatics::GetMusicDir();
} else if (aStorageType.EqualsLiteral(DEVICESTORAGE_APPS)) {
f = DeviceStorageStatics::GetAppsDir();
} else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
f = DeviceStorageStatics::GetCrashesDir();
} else if (aStorageType.EqualsLiteral(DEVICESTORAGE_SDCARD)) {
f = DeviceStorageStatics::GetSdcardDir();
} else {
printf_stderr("##### DeviceStorage: Unrecognized StorageType: '%s'\n",
NS_LossyConvertUTF16toASCII(aStorageType).get());
return;
}
#ifdef MOZ_WIDGET_GONK
/* For volume based storage types, we will only have a file already
if the override root directory option is in effect. */
if (!f && !volMountPoint.IsEmpty()) {
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());
}
}
#endif
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>
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);
}
return CreateUnique(storageType, storageName, storagePath,
aFileType, aFileAttributes);
}
//static
already_AddRefed<DeviceStorageFile>
DeviceStorageFile::CreateUnique(const nsAString& aStorageType,
const nsAString& aStorageName,
nsAString& aFileName,
uint32_t aFileType,
uint32_t aFileAttributes)
{
RefPtr<DeviceStorageFile> dsf =
new DeviceStorageFile(aStorageType, aStorageName, aFileName);
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(mPath);
}
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() const
{
return ValidateAndSplitPath(mRootDir) && ValidateAndSplitPath(mPath);
}
bool
DeviceStorageFile::ValidateAndSplitPath(const nsAString& aPath,
nsTArray<nsString>* aParts) const
{
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;
}
NS_NAMED_LITERAL_STRING(kCurrentDir, ".");
NS_NAMED_LITERAL_STRING(kParentDir, "..");
// Split path and check each path component.
nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
tokenizer(aPath, FILESYSTEM_DOM_PATH_SEPARATOR_CHAR);
while (tokenizer.hasMoreTokens()) {
nsDependentSubstring pathComponent = tokenizer.nextToken();
// The path containing empty components, such as "foo//bar", is invalid.
// We don't allow paths, such as "../foo", "foo/./bar" and "foo/../bar",
// to walk up the directory.
if (pathComponent.IsEmpty() ||
pathComponent.Equals(kCurrentDir) ||
pathComponent.Equals(kParentDir)) {
return false;
}
if (aParts) {
aParts->AppendElement(pathComponent);
}
}
return true;
}
void
DeviceStorageFile::AppendRelativePath(const nsAString& aPath)
{
if (!mFile) {
return;
}
nsTArray<nsString> parts;
if (!ValidateAndSplitPath(aPath, &parts)) {
// 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;
}
for (uint32_t i = 0; i < parts.Length(); ++i) {
nsresult rv = mFile->AppendRelativePath(parts[i]);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
}
}
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;
}
rv = NS_DispatchToMainThread(new IOEventComplete(this, "created"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIOutputStream> outputStream;
NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
if (!outputStream) {
return NS_ERROR_FAILURE;
}
return Append(aInputStream, outputStream);
}
nsresult
DeviceStorageFile::Write(InfallibleTArray<uint8_t>& 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;
}
rv = NS_DispatchToMainThread(new IOEventComplete(this, "created"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIOutputStream> 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();
rv = NS_DispatchToMainThread(new IOEventComplete(this, "modified"));
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<nsIOutputStream> 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<nsIOutputStream> 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<uint32_t>(std::min<uint64_t>(bufSize, UINT32_MAX)),
&wrote);
if (NS_FAILED(rv)) {
break;
}
bufSize -= wrote;
}
rv = NS_DispatchToMainThread(new IOEventComplete(this, "modified"));
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;
}
return NS_DispatchToMainThread(new IOEventComplete(this, "deleted"));
}
nsresult
DeviceStorageFile::CalculateMimeType()
{
MOZ_ASSERT(NS_IsMainThread());
if (!mFile) {
return NS_ERROR_FAILURE;
}
nsAutoCString mimeType;
nsCOMPtr<nsIMIMEService> 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<RefPtr<DeviceStorageFile> > &aFiles,
PRTime aSince)
{
if (!mFile) {
return;
}
nsString fullRootPath;
mFile->GetPath(fullRootPath);
collectFilesInternal(aFiles, aSince, fullRootPath);
}
void
DeviceStorageFile::collectFilesInternal(
nsTArray<RefPtr<DeviceStorageFile> > &aFiles,
PRTime aSince,
nsAString& aRootPath)
{
if (!mFile || !IsAvailable()) {
return;
}
nsCOMPtr<nsISimpleEnumerator> e;
mFile->GetDirectoryEntries(getter_AddRefs(e));
if (!e) {
return;
}
DeviceStorageTypeChecker* typeChecker
= DeviceStorageTypeChecker::CreateOrGet();
MOZ_ASSERT(typeChecker);
nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
nsCOMPtr<nsIFile> 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 (isFile && !typeChecker->Check(mStorageType, fullpath)) {
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) {
RefPtr<DeviceStorageFile> 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<nsISimpleEnumerator> e;
rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
if (NS_FAILED(rv) || !e) {
return;
}
nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
MOZ_ASSERT(files);
nsCOMPtr<nsIFile> 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::GetStorageFreeSpace(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<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(vs);
nsCOMPtr<nsIVolume> 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)
void
nsDOMDeviceStorage::SetRootDirectoryForType(const nsAString& aStorageType,
const nsAString& aStorageName)
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIFile> f;
DeviceStorageFile::GetRootDirectoryForType(aStorageType,
aStorageName,
getter_AddRefs(f));
mRootDirectory = f;
mStorageType = aStorageType;
mStorageName = aStorageName;
}
nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIGlobalObject* aGlobal,
DeviceStorageCursorRequest* aRequest)
: DOMCursor(aGlobal, nullptr)
, mOkToCallContinue(false)
, mRequest(aRequest)
{
}
nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
{
}
void
nsDOMDeviceStorageCursor::FireSuccess(JS::Handle<JS::Value> aResult)
{
mOkToCallContinue = true;
DOMCursor::FireSuccess(aResult);
}
void
nsDOMDeviceStorageCursor::FireDone()
{
mRequest = nullptr;
DOMCursor::FireDone();
}
void
nsDOMDeviceStorageCursor::FireError(const nsString& aReason)
{
mOkToCallContinue = false;
mRequest = nullptr;
if (!mResult.isUndefined()) {
// If we previously succeeded, we cannot fail without
// clearing the last result.
mResult.setUndefined();
mDone = false;
}
DOMCursor::FireError(aReason);
}
void
nsDOMDeviceStorageCursor::Continue(ErrorResult& aRv)
{
if (!mOkToCallContinue || !mRequest) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (!mResult.isUndefined()) {
// We call onsuccess multiple times. Clear the last
// result.
mResult.setUndefined();
mDone = false;
}
mOkToCallContinue = false;
aRv = mRequest->Continue();
}
DeviceStorageRequest::DeviceStorageRequest()
: mId(DeviceStorageRequestManager::INVALID_ID)
, mAccess(DEVICE_STORAGE_ACCESS_UNDEFINED)
, mSendToParent(true)
, mUseMainThread(false)
, mUseStreamTransport(false)
, mCheckFile(false)
, mCheckBlob(false)
, mMultipleResolve(false)
, mPermissionCached(true)
{
DS_LOG_DEBUG("%p", this);
}
DeviceStorageRequest::~DeviceStorageRequest()
{
DS_LOG_DEBUG("%p", this);
if (mId != DeviceStorageRequestManager::INVALID_ID) {
/* Cursors may be freed without completing if the caller does not
call continue until there is no data left. */
MOZ_ASSERT(mMultipleResolve, "Still has valid ID but request being freed!");
Reject(POST_ERROR_EVENT_UNKNOWN);
}
}
void
DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aId)
{
DS_LOG_DEBUG("%p manages %p", aManager, this);
mManager = aManager;
mFile = aFile;
mId = aId;
MOZ_ASSERT(mManager);
MOZ_ASSERT(mFile);
MOZ_ASSERT(mId != DeviceStorageRequestManager::INVALID_ID);
}
void
DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
BlobImpl* aBlob)
{
Initialize(aManager, Move(aFile), aRequest);
mBlob = aBlob;
mCheckBlob = true;
mCheckFile = true;
MOZ_ASSERT(mBlob);
}
void
DeviceStorageRequest::Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
DeviceStorageFileDescriptor* aDSFileDescriptor)
{
Initialize(aManager, Move(aFile), aRequest);
mDSFileDescriptor = aDSFileDescriptor;
MOZ_ASSERT(mDSFileDescriptor);
}
DeviceStorageAccessType
DeviceStorageRequest::GetAccess() const
{
return mAccess;
}
void
DeviceStorageRequest::GetStorageType(nsAString& aType) const
{
aType = mFile->mStorageType;
}
nsresult
DeviceStorageRequest::Cancel()
{
return Reject(POST_ERROR_EVENT_PERMISSION_DENIED);
}
nsresult
DeviceStorageRequest::Allow()
{
if (mUseMainThread && !NS_IsMainThread()) {
RefPtr<DeviceStorageRequest> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
{
self->Allow();
});
return NS_DispatchToMainThread(r.forget());
}
nsresult rv = AllowInternal();
if (NS_WARN_IF(NS_FAILED(rv))) {
const char *reason;
switch (rv) {
case NS_ERROR_ILLEGAL_VALUE:
reason = POST_ERROR_EVENT_ILLEGAL_TYPE;
break;
case NS_ERROR_DOM_SECURITY_ERR:
reason = POST_ERROR_EVENT_PERMISSION_DENIED;
break;
default:
reason = POST_ERROR_EVENT_UNKNOWN;
break;
}
return Reject(reason);
}
return rv;
}
DeviceStorageFile*
DeviceStorageRequest::GetFile() const
{
MOZ_ASSERT(mFile);
return mFile;
}
DeviceStorageFileDescriptor*
DeviceStorageRequest::GetFileDescriptor() const
{
MOZ_ASSERT(mDSFileDescriptor);
return mDSFileDescriptor;
}
DeviceStorageRequestManager*
DeviceStorageRequest::GetManager() const
{
return mManager;
}
nsresult
DeviceStorageRequest::Prepare()
{
return NS_OK;
}
nsresult
DeviceStorageRequest::CreateSendParams(DeviceStorageParams& aParams)
{
MOZ_ASSERT_UNREACHABLE("Cannot send to parent, missing param creator");
return NS_ERROR_UNEXPECTED;
}
nsresult
DeviceStorageRequest::AllowInternal()
{
MOZ_ASSERT(mManager->IsOwningThread() || NS_IsMainThread());
nsresult rv = Prepare();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
DeviceStorageTypeChecker* typeChecker
= DeviceStorageTypeChecker::CreateOrGet();
if (!typeChecker) {
return NS_ERROR_UNEXPECTED;
}
if (mCheckBlob && (!mBlob ||
!typeChecker->Check(mFile->mStorageType, mBlob))) {
return NS_ERROR_ILLEGAL_VALUE;
}
if (mCheckFile && (!mFile->mFile ||
!typeChecker->Check(mFile->mStorageType, mFile->mFile))) {
return NS_ERROR_ILLEGAL_VALUE;
}
mSendToParent = mSendToParent && !XRE_IsParentProcess();
if (mSendToParent) {
return SendToParentProcess();
}
if (mUseStreamTransport) {
DS_LOG_INFO("run stream transport %u", mId);
nsCOMPtr<nsIEventTarget> target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
MOZ_ASSERT(target);
nsCOMPtr<nsIRunnable> self = this;
return target->Dispatch(self.forget(), NS_DISPATCH_NORMAL);
}
DS_LOG_INFO("run %u", mId);
return Run();
}
nsresult
DeviceStorageRequest::SendToParentProcess()
{
// PContent can only be used on the main thread
if (!NS_IsMainThread()) {
RefPtr<DeviceStorageRequest> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
{
nsresult rv = self->SendToParentProcess();
if (NS_WARN_IF(NS_FAILED(rv))) {
self->Reject(POST_ERROR_EVENT_UNKNOWN);
}
});
return NS_DispatchToMainThread(r.forget());
}
MOZ_ASSERT(NS_IsMainThread());
DS_LOG_INFO("request parent %u", mId);
DeviceStorageParams params;
nsresult rv = CreateSendParams(params);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_UNEXPECTED;
}
PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this);
ContentChild::GetSingleton()
->SendPDeviceStorageRequestConstructor(child, params);
return NS_OK;
}
DeviceStorageCursorRequest::DeviceStorageCursorRequest()
: mIndex(0)
, mSince(0)
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
mUseStreamTransport = true;
mMultipleResolve = true;
DS_LOG_INFO("");
}
void
DeviceStorageCursorRequest::Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
PRTime aSince)
{
Initialize(aManager, Move(aFile), aRequest);
mStorageType = mFile->mStorageType;
mSince = aSince;
}
void
DeviceStorageCursorRequest::AddFiles(size_t aSize)
{
mFiles.SetCapacity(mFiles.Length() + aSize);
}
void
DeviceStorageCursorRequest::AddFile(already_AddRefed<DeviceStorageFile> aFile)
{
mFiles.AppendElement(aFile);
}
nsresult
DeviceStorageCursorRequest::SendContinueToParentProcess()
{
if (!NS_IsMainThread()) {
RefPtr<DeviceStorageCursorRequest> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
{
self->SendContinueToParentProcess();
});
return NS_DispatchToMainThread(r.forget());
}
MOZ_ASSERT(NS_IsMainThread());
DS_LOG_INFO("request parent %u", mId);
DeviceStorageRequestChild* child
= new DeviceStorageRequestChild(this);
DeviceStorageGetParams params(mStorageType,
mFile->mStorageName,
mFile->mRootDir,
mFile->mPath);
ContentChild::GetSingleton()
->SendPDeviceStorageRequestConstructor(child, params);
return NS_OK;
}
nsresult
DeviceStorageCursorRequest::Continue()
{
if (!NS_IsMainThread()) {
/* The MIME service can only be accessed from the main thread */
RefPtr<DeviceStorageCursorRequest> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
{
self->Continue();
});
nsresult rv = NS_DispatchToMainThread(r.forget());
if (NS_WARN_IF(NS_FAILED(rv))) {
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
return rv;
}
DS_LOG_INFO("%u", mId);
RefPtr<DeviceStorageFile> file;
while (!file && mIndex < mFiles.Length()) {
file = mFiles[mIndex].forget();
++mIndex;
}
if (!file) {
// No more files remaining, complete cursor
return Resolve();
}
file->CalculateMimeType();
if (XRE_IsParentProcess()) {
return Resolve(file);
}
mFile = file;
nsresult rv = SendContinueToParentProcess();
if (NS_FAILED(rv)) {
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
return rv;
}
NS_IMETHODIMP
DeviceStorageCursorRequest::Run()
{
if (mFile->mFile) {
bool check;
mFile->mFile->IsDirectory(&check);
if (!check) {
return Reject(POST_ERROR_EVENT_FILE_NOT_ENUMERABLE);
}
}
mFile->CollectFiles(mFiles, mSince);
return Continue();
}
nsresult
DeviceStorageCursorRequest::CreateSendParams(DeviceStorageParams& aParams)
{
DeviceStorageEnumerationParams params(mFile->mStorageType,
mFile->mStorageName,
mFile->mRootDir,
mSince);
aParams = params;
return NS_OK;
}
class DeviceStorageCreateFdRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageCreateFdRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_CREATE;
mUseStreamTransport = true;
mCheckFile = true;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString fullPath;
mFile->GetFullPath(fullPath);
MOZ_ASSERT(!fullPath.IsEmpty());
bool check = false;
mFile->mFile->Exists(&check);
if (check) {
return Reject(POST_ERROR_EVENT_FILE_EXISTS);
}
nsresult rv = mFile->CreateFileDescriptor(
mDSFileDescriptor->mFileDescriptor);
if (NS_FAILED(rv)) {
mFile->mFile->Remove(false);
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
return Resolve(fullPath);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageCreateFdParams params;
params.type() = mFile->mStorageType;
params.storageName() = mFile->mStorageName;
params.relpath() = mFile->mPath;
aParams = params;
mFile->Dump("DeviceStorageCreateFdParams");
return NS_OK;
}
};
class DeviceStorageCreateRequest final
: public DeviceStorageRequest
{
public:
using DeviceStorageRequest::Initialize;
DeviceStorageCreateRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_CREATE;
mUseStreamTransport = true;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
ErrorResult rv;
nsCOMPtr<nsIInputStream> stream;
mBlob->GetInternalStream(getter_AddRefs(stream), rv);
if (NS_WARN_IF(rv.Failed())) {
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
bool check = false;
mFile->mFile->Exists(&check);
if (check) {
return Reject(POST_ERROR_EVENT_FILE_EXISTS);
}
rv = mFile->Write(stream);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
mFile->mFile->Remove(false);
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
nsString fullPath;
mFile->GetFullPath(fullPath);
return Resolve(fullPath);
}
void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest) override
{
DeviceStorageRequest::Initialize(aManager, Move(aFile), aRequest);
mUseMainThread = mFile->mPath.IsEmpty();
}
protected:
nsresult Prepare() override
{
if (!mFile->mPath.IsEmpty()) {
// Checks have already been performed when request was created
return NS_OK;
}
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
if (!mimeSvc) {
return NS_ERROR_FAILURE;
}
// if mimeType or extension are null, the request will be rejected
// in DeviceStorageRequest::AllowInternal when the type checker
// verifies the file path
nsString mimeType;
mBlob->GetType(mimeType);
nsCString extension;
mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
EmptyCString(), extension);
char buffer[32];
NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);
nsAutoString path;
path.AssignLiteral(buffer);
path.Append('.');
path.AppendASCII(extension.get());
RefPtr<DeviceStorageFile> file
= DeviceStorageFile::CreateUnique(mFile->mStorageType,
mFile->mStorageName, path,
nsIFile::NORMAL_FILE_TYPE, 00600);
if (!file) {
return NS_ERROR_FAILURE;
}
if (!file->IsSafePath()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
mFile = file.forget();
return NS_OK;
}
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
BlobChild* actor
= ContentChild::GetSingleton()->GetOrCreateActorForBlobImpl(mBlob);
if (!actor) {
return NS_ERROR_FAILURE;
}
DeviceStorageAddParams params;
params.blobChild() = actor;
params.type() = mFile->mStorageType;
params.storageName() = mFile->mStorageName;
params.relpath() = mFile->mPath;
aParams = params;
return NS_OK;
}
};
class DeviceStorageAppendRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageAppendRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_WRITE;
mUseStreamTransport = true;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
ErrorResult rv;
nsCOMPtr<nsIInputStream> stream;
mBlob->GetInternalStream(getter_AddRefs(stream), rv);
if (NS_WARN_IF(rv.Failed())) {
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
bool check = false;
mFile->mFile->Exists(&check);
if (!check) {
return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
}
rv = mFile->Append(stream);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
nsString fullPath;
mFile->GetFullPath(fullPath);
return Resolve(fullPath);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
BlobChild* actor
= ContentChild::GetSingleton()->GetOrCreateActorForBlobImpl(mBlob);
if (!actor) {
return NS_ERROR_FAILURE;
}
DeviceStorageAppendParams params;
params.blobChild() = actor;
params.type() = mFile->mStorageType;
params.storageName() = mFile->mStorageName;
params.relpath() = mFile->mPath;
aParams = params;
return NS_OK;
}
};
class DeviceStorageOpenRequest final
: public DeviceStorageRequest
{
public:
using DeviceStorageRequest::Initialize;
DeviceStorageOpenRequest()
{
mUseMainThread = true;
mUseStreamTransport = true;
mCheckFile = true;
DS_LOG_INFO("");
}
void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest) override
{
DeviceStorageRequest::Initialize(aManager, Move(aFile), aRequest);
mAccess = mFile->mEditable ? DEVICE_STORAGE_ACCESS_WRITE
: DEVICE_STORAGE_ACCESS_READ;
}
NS_IMETHOD Run() override
{
if (!mFile->mEditable) {
bool check = false;
mFile->mFile->Exists(&check);
if (!check) {
return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
}
}
nsresult rv = mFile->CalculateSizeAndModifiedDate();
if (NS_FAILED(rv)) {
return Reject(POST_ERROR_EVENT_UNKNOWN);
}
return Resolve(mFile);
}
protected:
nsresult Prepare() override
{
MOZ_ASSERT(NS_IsMainThread());
mFile->CalculateMimeType();
return NS_OK;
}
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageGetParams params(mFile->mStorageType,
mFile->mStorageName,
mFile->mRootDir,
mFile->mPath);
aParams = params;
return NS_OK;
}
};
class DeviceStorageDeleteRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageDeleteRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_WRITE;
mUseStreamTransport = true;
mCheckFile = true;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
mFile->Remove();
bool check = false;
mFile->mFile->Exists(&check);
if (check) {
return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
}
nsString fullPath;
mFile->GetFullPath(fullPath);
return Resolve(fullPath);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageDeleteParams params(mFile->mStorageType,
mFile->mStorageName,
mFile->mPath);
aParams = params;
return NS_OK;
}
};
class DeviceStorageFreeSpaceRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageFreeSpaceRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
mUseStreamTransport = true;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
int64_t freeSpace = 0;
if (mFile) {
mFile->GetStorageFreeSpace(&freeSpace);
}
return Resolve(static_cast<uint64_t>(freeSpace));
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageFreeSpaceParams params(mFile->mStorageType,
mFile->mStorageName);
aParams = params;
return NS_OK;
}
};
class DeviceStorageUsedSpaceRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageUsedSpaceRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
if (mManager->IsOwningThread()) {
// 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);
nsCOMPtr<nsIRunnable> self = this;
usedSpaceCache->Dispatch(self.forget());
return NS_OK;
}
uint64_t picturesUsage = 0, videosUsage = 0,
musicUsage = 0, totalUsage = 0;
mFile->AccumDiskUsage(&picturesUsage, &videosUsage,
&musicUsage, &totalUsage);
const nsString& type = mFile->mStorageType;
if (type.EqualsLiteral(DEVICESTORAGE_PICTURES)) {
totalUsage = picturesUsage;
} else if (type.EqualsLiteral(DEVICESTORAGE_VIDEOS)) {
totalUsage = videosUsage;
} else if (type.EqualsLiteral(DEVICESTORAGE_MUSIC)) {
totalUsage = musicUsage;
}
return Resolve(totalUsage);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageUsedSpaceParams params(mFile->mStorageType,
mFile->mStorageName);
aParams = params;
return NS_OK;
}
};
class DeviceStorageAvailableRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageAvailableRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
mSendToParent = false;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->GetStatus(state);
}
return Resolve(state);
}
};
class DeviceStorageStatusRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageStatusRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
mSendToParent = false;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString state = NS_LITERAL_STRING("undefined");
if (mFile) {
mFile->GetStorageStatus(state);
}
return Resolve(state);
}
};
class DeviceStorageWatchRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageWatchRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_READ;
mSendToParent = false;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
return Resolve();
}
};
class DeviceStorageFormatRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageFormatRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_WRITE;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->DoFormat(state);
}
return Resolve(state);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageFormatParams params(mFile->mStorageType,
mFile->mStorageName);
aParams = params;
return NS_OK;
}
};
class DeviceStorageMountRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageMountRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_WRITE;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->DoMount(state);
}
return Resolve(state);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageMountParams params(mFile->mStorageType,
mFile->mStorageName);
aParams = params;
return NS_OK;
}
};
class DeviceStorageUnmountRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageUnmountRequest()
{
mAccess = DEVICE_STORAGE_ACCESS_WRITE;
DS_LOG_INFO("");
}
NS_IMETHOD Run() override
{
nsString state = NS_LITERAL_STRING("unavailable");
if (mFile) {
mFile->DoUnmount(state);
}
return Resolve(state);
}
protected:
nsresult CreateSendParams(DeviceStorageParams& aParams) override
{
DeviceStorageUnmountParams params(mFile->mStorageType,
mFile->mStorageName);
aParams = params;
return NS_OK;
}
};
class DeviceStoragePermissionCheck final
: public nsIContentPermissionRequest
, public nsIRunnable
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStoragePermissionCheck,
nsIContentPermissionRequest)
DeviceStoragePermissionCheck(
already_AddRefed<DeviceStorageRequest>&& aRequest,
uint64_t aWindowID, const PrincipalInfo &aPrincipalInfo)
: mRequest(Move(aRequest))
, mWindowID(aWindowID)
, mPrincipalInfo(aPrincipalInfo)
{
MOZ_ASSERT(mRequest);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
if (DeviceStorageStatics::IsPromptTesting()) {
return Allow(JS::UndefinedHandleValue);
}
mWindow = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
if (NS_WARN_IF(!mWindow)) {
return Cancel();
}
nsresult rv;
mPrincipal = PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return Cancel();
}
mRequester = new nsContentPermissionRequester(mWindow);
return nsContentPermissionUtils::AskPermission(this, mWindow);
}
NS_IMETHOD Cancel() override
{
return Resolve(false);
}
NS_IMETHOD Allow(JS::HandleValue aChoices) override
{
MOZ_ASSERT(aChoices.isUndefined());
return Resolve(true);
}
NS_IMETHODIMP GetTypes(nsIArray** aTypes) override
{
nsString storageType;
mRequest->GetStorageType(storageType);
nsCString type;
nsresult rv =
DeviceStorageTypeChecker::GetPermissionForType(storageType, type);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCString access;
rv = DeviceStorageTypeChecker::GetAccessForIndex(mRequest->GetAccess(),
access);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsTArray<nsString> emptyOptions;
return nsContentPermissionUtils::CreatePermissionArray(type, access, emptyOptions, aTypes);
}
NS_IMETHOD GetRequester(nsIContentPermissionRequester** aRequester) override
{
NS_ENSURE_ARG_POINTER(aRequester);
nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
requester.forget(aRequester);
return NS_OK;
}
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;
}
private:
nsresult Resolve(bool aResolve)
{
mRequest->GetManager()->StorePermission(mRequest->GetAccess(), aResolve);
mRequest->PermissionCacheMissed();
if (aResolve) {
return mRequest->Allow();
}
return mRequest->Cancel();
}
virtual ~DeviceStoragePermissionCheck()
{ }
RefPtr<DeviceStorageRequest> mRequest;
uint64_t mWindowID;
PrincipalInfo mPrincipalInfo;
nsCOMPtr<nsPIDOMWindow> mWindow;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsIContentPermissionRequester> mRequester;
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStoragePermissionCheck)
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(DeviceStoragePermissionCheck)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStoragePermissionCheck)
NS_IMPL_CYCLE_COLLECTION(DeviceStoragePermissionCheck,
mWindow)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorage)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
/* nsISupports is an ambiguous base of nsDOMDeviceStorage
so we have to work around that. */
if ( aIID.Equals(NS_GET_IID(nsDOMDeviceStorage)) )
foundInterface = static_cast<nsISupports*>(static_cast<void*>(this));
else
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)
, mInnerWindowID(0)
, mOwningThread(NS_GetCurrentThread())
{
MOZ_ASSERT(NS_IsMainThread()); // worker support incomplete
sInstanceCount++;
DS_LOG_DEBUG("%p (%d)", this, sInstanceCount);
}
nsresult
nsDOMDeviceStorage::CheckPermission(
already_AddRefed<DeviceStorageRequest>&& aRequest)
{
MOZ_ASSERT(mManager);
RefPtr<DeviceStorageRequest> request(aRequest);
uint32_t cache = mManager->CheckPermission(request->GetAccess());
switch (cache) {
case nsIPermissionManager::ALLOW_ACTION:
return request->Allow();
case nsIPermissionManager::DENY_ACTION:
return request->Cancel();
case nsIPermissionManager::PROMPT_ACTION:
default:
{
nsCOMPtr<nsIThread> mainThread;
nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
return request->Reject(POST_ERROR_EVENT_UNKNOWN);
}
return mainThread->Dispatch(
MakeAndAddRef<DeviceStoragePermissionCheck>(request.forget(),
mInnerWindowID,
*mPrincipalInfo),
NS_DISPATCH_NORMAL);
}
}
}
bool
nsDOMDeviceStorage::IsOwningThread()
{
bool owner = false;
mOwningThread->IsOnCurrentThread(&owner);
return owner;
}
nsresult
nsDOMDeviceStorage::DispatchToOwningThread(
already_AddRefed<nsIRunnable>&& aRunnable)
{
return mOwningThread->Dispatch(Move(aRunnable), NS_DISPATCH_NORMAL);
}
/* virtual */ JSObject*
nsDOMDeviceStorage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return DeviceStorageBinding::Wrap(aCx, this, aGivenProto);
}
nsresult
nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType,
const nsAString &aVolName)
{
MOZ_ASSERT(aWindow);
mInnerWindowID = aWindow->WindowID();
SetRootDirectoryForType(aType, aVolName);
if (!mRootDirectory) {
return NS_ERROR_NOT_AVAILABLE;
}
nsresult rv;
DeviceStorageStatics::AddListener(this);
if (!mStorageName.IsEmpty()) {
mIsDefaultLocation = Default();
#ifdef MOZ_WIDGET_GONK
if (DeviceStorageTypeChecker::IsVolumeBased(mStorageType)) {
nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
if (NS_WARN_IF(!vs)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIVolume> 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
}
nsCOMPtr<nsIPrincipal> principal;
rv = CheckPrincipal(aWindow, aType.EqualsLiteral(DEVICESTORAGE_APPS), getter_AddRefs(principal));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mPrincipalInfo = new PrincipalInfo();
rv = PrincipalToPrincipalInfo(principal, mPrincipalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mManager = new DeviceStorageRequestManager();
DS_LOG_DEBUG("%p owns %p", this, mManager.get());
return NS_OK;
}
nsDOMDeviceStorage::~nsDOMDeviceStorage()
{
DS_LOG_DEBUG("%p (%d)", this, sInstanceCount);
MOZ_ASSERT(IsOwningThread());
sInstanceCount--;
DeviceStorageStatics::RemoveListener(this);
}
// static
nsresult
nsDOMDeviceStorage::CheckPrincipal(nsPIDOMWindow* aWindow, bool aIsAppsStorage, nsIPrincipal** aPrincipal)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aWindow);
// Grab the principal of the document
nsCOMPtr<nsIDocument> doc = aWindow->GetDoc();
if (!doc) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
// the 'apps' type is special. We only want this exposed
// if the caller has the "webapps-manage" permission.
if (aIsAppsStorage) {
nsCOMPtr<nsIPermissionManager> permissionManager
= services::GetPermissionManager();
NS_ENSURE_TRUE(permissionManager, NS_ERROR_FAILURE);
uint32_t permission;
nsresult rv
= permissionManager->TestPermissionFromPrincipal(principal,
"webapps-manage",
&permission);
if (NS_FAILED(rv) || permission != nsIPermissionManager::ALLOW_ACTION) {
return NS_ERROR_NOT_AVAILABLE;
}
}
principal.forget(aPrincipal);
return NS_OK;
}
void
nsDOMDeviceStorage::Shutdown()
{
MOZ_ASSERT(IsOwningThread());
if (mManager) {
mManager->Shutdown();
mManager = nullptr;
}
if (mFileSystem) {
mFileSystem->Shutdown();
mFileSystem = nullptr;
}
DeviceStorageStatics::RemoveListener(this);
}
StaticAutoPtr<nsTArray<nsString>> nsDOMDeviceStorage::sVolumeNameCache;
// static
void nsDOMDeviceStorage::InvalidateVolumeCaches()
{
MOZ_ASSERT(NS_IsMainThread());
// Currently there is only the one volume cache. DeviceStorageAreaListener
// calls this function any time it detects a volume was added or removed.
sVolumeNameCache = nullptr;
}
// static
void
nsDOMDeviceStorage::GetOrderedVolumeNames(
const nsAString& aType,
nsDOMDeviceStorage::VolumeNameArray& aVolumeNames)
{
if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
aVolumeNames.Clear();
return;
}
GetOrderedVolumeNames(aVolumeNames);
}
// static
void
nsDOMDeviceStorage::GetOrderedVolumeNames(
nsDOMDeviceStorage::VolumeNameArray& aVolumeNames)
{
MOZ_ASSERT(NS_IsMainThread());
if (sVolumeNameCache && sVolumeNameCache->Length() > 0) {
aVolumeNames.AppendElements(*sVolumeNameCache);
return;
}
#ifdef MOZ_WIDGET_GONK
nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
if (vs) {
nsCOMPtr<nsIArray> volNames;
vs->GetVolumeNames(getter_AddRefs(volNames));
uint32_t length = -1;
volNames->GetLength(&length);
for (uint32_t i = 0; i < length; i++) {
nsCOMPtr<nsISupportsString> 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<nsString>;
sVolumeNameCache->AppendElements(aVolumeNames);
}
// static
void
nsDOMDeviceStorage::CreateDeviceStorageFor(nsPIDOMWindow* aWin,
const nsAString &aType,
nsDOMDeviceStorage** aStore)
{
nsString storageName;
GetDefaultStorageName(aType, storageName);
RefPtr<nsDOMDeviceStorage> ds = new nsDOMDeviceStorage(aWin);
if (NS_FAILED(ds->Init(aWin, aType, storageName))) {
*aStore = nullptr;
return;
}
ds.forget(aStore);
}
// static
void
nsDOMDeviceStorage::CreateDeviceStorageByNameAndType(
nsPIDOMWindow* aWin,
const nsAString& aName,
const nsAString& aType,
nsDOMDeviceStorage** aStore)
{
if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
RefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage(aWin);
if (NS_FAILED(storage->Init(aWin, aType, EmptyString()))) {
*aStore = nullptr;
return;
}
NS_ADDREF(*aStore = storage.get());
return;
}
RefPtr<nsDOMDeviceStorage> storage = GetStorageByNameAndType(aWin,
aName,
aType);
if (!storage) {
*aStore = nullptr;
return;
}
NS_ADDREF(*aStore = storage.get());
}
bool
nsDOMDeviceStorage::Equals(nsPIDOMWindow* aWin,
const nsAString& aName,
const nsAString& aType)
{
MOZ_ASSERT(aWin);
return aWin && aWin->WindowID() == mInnerWindowID &&
mStorageName.Equals(aName) &&
mStorageType.Equals(aType);
}
// 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>
nsDOMDeviceStorage::GetStorage(const nsAString& aFullPath,
nsAString& aOutStoragePath)
{
nsString storageName;
if (!ParseFullPath(aFullPath, storageName, aOutStoragePath)) {
return nullptr;
}
RefPtr<nsDOMDeviceStorage> ds;
if (storageName.IsEmpty()) {
ds = this;
} else {
ds = GetStorageByName(storageName);
}
return ds.forget();
}
already_AddRefed<nsDOMDeviceStorage>
nsDOMDeviceStorage::GetStorageByName(const nsAString& aStorageName)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<nsDOMDeviceStorage> ds;
if (mStorageName.Equals(aStorageName)) {
ds = this;
return ds.forget();
}
return GetStorageByNameAndType(GetOwner(), aStorageName, mStorageType);
}
// static
already_AddRefed<nsDOMDeviceStorage>
nsDOMDeviceStorage::GetStorageByNameAndType(nsPIDOMWindow* aWin,
const nsAString& aStorageName,
const nsAString& aType)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<nsDOMDeviceStorage> 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)
{
if (!DeviceStorageTypeChecker::IsVolumeBased(aStorageType)) {
// The storage name will be the empty string
aStorageName.Truncate();
return;
}
// See if the preferred volume is available.
nsString prefStorageName;
DeviceStorageStatics::GetWritableName(prefStorageName);
if (!prefStorageName.IsEmpty()) {
nsString status;
RefPtr<DeviceStorageFile> 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"
DeviceStorageStatics::SetWritableName(aStorageName);
return;
}
// No volumes available, return the empty string. This is normal for
// b2g-desktop.
aStorageName.Truncate();
}
bool
nsDOMDeviceStorage::IsAvailable()
{
RefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
return dsf->IsAvailable();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv)
{
nsString path;
return AddOrAppendNamed(aBlob, path, true, aRv);
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath,
ErrorResult& aRv)
{
if (aPath.IsEmpty()) {
aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
return nullptr;
}
return AddOrAppendNamed(aBlob, aPath, true, aRv);
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AppendNamed(Blob* aBlob, const nsAString& aPath,
ErrorResult& aRv)
{
if (aPath.IsEmpty()) {
aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
return nullptr;
}
return AddOrAppendNamed(aBlob, aPath, false, aRv);
}
uint32_t
nsDOMDeviceStorage::CreateDOMRequest(DOMRequest** aRequest, ErrorResult& aRv)
{
if (!mManager) {
DS_LOG_WARN("shutdown");
aRv.Throw(NS_ERROR_UNEXPECTED);
return DeviceStorageRequestManager::INVALID_ID;
}
return mManager->Create(this, aRequest);
}
uint32_t
nsDOMDeviceStorage::CreateDOMCursor(DeviceStorageCursorRequest* aRequest, nsDOMDeviceStorageCursor** aCursor, ErrorResult& aRv)
{
if (!mManager) {
DS_LOG_WARN("shutdown");
aRv.Throw(NS_ERROR_UNEXPECTED);
return DeviceStorageRequestManager::INVALID_ID;
}
return mManager->Create(this, aRequest, aCursor);
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::CreateAndRejectDOMRequest(const char *aReason, ErrorResult& aRv)
{
RefPtr<DOMRequest> request;
uint32_t id = CreateDOMRequest(getter_AddRefs(request), aRv);
if (aRv.Failed()) {
return nullptr;
}
aRv = mManager->Reject(id, aReason);
return request.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
bool aCreate, ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(aCreate || !aPath.IsEmpty());
// if the blob is null here, bail
if (!aBlob) {
return nullptr;
}
nsCOMPtr<nsIRunnable> r;
if (IsFullPath(aPath)) {
nsString storagePath;
RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
if (!ds) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
}
return ds->AddOrAppendNamed(aBlob, storagePath, aCreate, aRv);
}
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageFile> dsf;
if (aPath.IsEmpty()) {
dsf = new DeviceStorageFile(mStorageType, mStorageName);
} else {
dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath);
if (!dsf->IsSafePath()) {
aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
return domRequest.forget();
}
}
RefPtr<DeviceStorageRequest> request;
if (aCreate) {
request = new DeviceStorageCreateRequest();
} else {
request = new DeviceStorageAppendRequest();
}
request->Initialize(mManager, dsf.forget(), id, aBlob->Impl());
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
if (IsFullPath(aPath)) {
nsString storagePath;
RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
if (!ds) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
}
return ds->GetInternal(storagePath, aEditable, aRv);
}
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName,
aPath);
dsf->SetEditable(aEditable);
if (!dsf->IsSafePath()) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
}
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageOpenRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Delete(const nsAString& aPath, ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
if (IsFullPath(aPath)) {
nsString storagePath;
RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
if (!ds) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
}
return ds->Delete(storagePath, aRv);
}
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName,
aPath);
if (!dsf->IsSafePath()) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
}
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageDeleteRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::FreeSpace(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageFreeSpaceRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::UsedSpace(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
DebugOnly<DeviceStorageUsedSpaceCache*> usedSpaceCache
= DeviceStorageUsedSpaceCache::CreateOrGet();
MOZ_ASSERT(usedSpaceCache);
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageUsedSpaceRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Available(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageAvailableRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::StorageStatus(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageStatusRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Format(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageFormatRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Mount(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageMountRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::Unmount(ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageUnmountRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
return domRequest.forget();
}
already_AddRefed<DOMRequest>
nsDOMDeviceStorage::CreateFileDescriptor(const nsAString& aPath,
DeviceStorageFileDescriptor* aDSFileDescriptor,
ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(aDSFileDescriptor);
DeviceStorageTypeChecker* typeChecker
= DeviceStorageTypeChecker::CreateOrGet();
if (!typeChecker) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (IsFullPath(aPath)) {
nsString storagePath;
RefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
if (!ds) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
}
return ds->CreateFileDescriptor(storagePath, aDSFileDescriptor, aRv);
}
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName,
aPath);
if (!dsf->IsSafePath()) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_PERMISSION_DENIED, aRv);
}
if (!typeChecker->Check(mStorageType, dsf->mFile)) {
return CreateAndRejectDOMRequest(POST_ERROR_EVENT_ILLEGAL_TYPE, aRv);
}
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return nullptr;
}
RefPtr<DeviceStorageRequest> request = new DeviceStorageCreateFdRequest();
request->Initialize(mManager, dsf.forget(), id, aDSFileDescriptor);
aRv = CheckPermission(request.forget());
return domRequest.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;
}
bool
nsDOMDeviceStorage::LowDiskSpace()
{
return DeviceStorageStatics::LowDiskSpace();
}
already_AddRefed<Promise>
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<DOMCursor>
nsDOMDeviceStorage::Enumerate(const nsAString& aPath,
const EnumerationParameters& aOptions,
ErrorResult& aRv)
{
return EnumerateInternal(aPath, aOptions, false, aRv);
}
already_AddRefed<DOMCursor>
nsDOMDeviceStorage::EnumerateEditable(const nsAString& aPath,
const EnumerationParameters& aOptions,
ErrorResult& aRv)
{
return EnumerateInternal(aPath, aOptions, true, aRv);
}
already_AddRefed<DOMCursor>
nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath,
const EnumerationParameters& aOptions,
bool aEditable, ErrorResult& aRv)
{
MOZ_ASSERT(IsOwningThread());
PRTime since = 0;
if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) {
since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble());
}
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName,
aPath,
EmptyString());
dsf->SetEditable(aEditable);
RefPtr<DeviceStorageCursorRequest> request = new DeviceStorageCursorRequest();
RefPtr<nsDOMDeviceStorageCursor> cursor;
uint32_t id = CreateDOMCursor(request, getter_AddRefs(cursor), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (!dsf->IsSafePath()) {
aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
} else {
request->Initialize(mManager, dsf.forget(), id, since);
aRv = CheckPermission(request.forget());
}
return cursor.forget();
}
void
nsDOMDeviceStorage::OnWritableNameChanged()
{
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");
}
RefPtr<DeviceStorageChangeEvent> 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;
RefPtr<DeviceStorageChangeEvent> 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;
RefPtr<DeviceStorageChangeEvent> event =
DeviceStorageChangeEvent::Constructor(this, NS_LITERAL_STRING("storage-state-change"),
init);
event->SetTrusted(true);
bool ignore;
DispatchEvent(event, &ignore);
}
#endif
void
nsDOMDeviceStorage::OnFileWatcherUpdate(const nsCString& aData, DeviceStorageFile* aFile)
{
MOZ_ASSERT(IsOwningThread());
Notify(aData.get(), aFile);
}
void
nsDOMDeviceStorage::OnDiskSpaceWatcher(bool aLowDiskSpace)
{
MOZ_ASSERT(IsOwningThread());
RefPtr<DeviceStorageFile> file =
new DeviceStorageFile(mStorageType, mStorageName);
if (aLowDiskSpace) {
Notify("low-disk-space", file);
} else {
Notify("available-disk-space", file);
}
}
#ifdef MOZ_WIDGET_GONK
void
nsDOMDeviceStorage::OnVolumeStateChanged(nsIVolume* aVolume) {
MOZ_ASSERT(IsOwningThread());
// We invalidate the used space cache for the volume that actually changed
// state.
nsString volName;
aVolume->GetName(volName);
DeviceStorageUsedSpaceCache* usedSpaceCache
= DeviceStorageUsedSpaceCache::CreateOrGet();
MOZ_ASSERT(usedSpaceCache);
usedSpaceCache->Invalidate(volName);
if (!volName.Equals(mStorageName)) {
// Not our volume - we can ignore.
return;
}
RefPtr<DeviceStorageFile> 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);
}
#endif
nsresult
nsDOMDeviceStorage::Notify(const char* aReason, DeviceStorageFile* aFile)
{
if (!mManager) {
return NS_OK;
}
if (mManager->CheckPermission(DEVICE_STORAGE_ACCESS_READ) !=
nsIPermissionManager::ALLOW_ACTION) {
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);
RefPtr<DeviceStorageChangeEvent> 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(IsOwningThread());
if (!mManager) {
return;
}
if (mManager->CheckPermission(DEVICE_STORAGE_ACCESS_READ) !=
nsIPermissionManager::PROMPT_ACTION) {
return;
}
if (!aType.EqualsLiteral(STORAGE_CHANGE_EVENT)) {
return;
}
RefPtr<DOMRequest> domRequest;
uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
if (aRv.Failed()) {
return;
}
RefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
mStorageName);
RefPtr<DeviceStorageRequest> request = new DeviceStorageWatchRequest();
request->Initialize(mManager, dsf.forget(), id);
aRv = CheckPermission(request.forget());
}
Atomic<uint32_t> DeviceStorageRequestManager::sLastRequestId(0);
DeviceStorageRequestManager::DeviceStorageRequestManager()
: mOwningThread(NS_GetCurrentThread())
, mMutex("DeviceStorageRequestManager::mMutex")
, mShutdown(false)
{
DS_LOG_INFO("%p", this);
for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mPermissionCache); ++i) {
mPermissionCache[i] = nsIPermissionManager::PROMPT_ACTION;
}
}
DeviceStorageRequestManager::~DeviceStorageRequestManager()
{
DS_LOG_INFO("%p pending %zu", this, mPending.Length());
if (!mPending.IsEmpty()) {
MOZ_ASSERT_UNREACHABLE("Should not destroy, still has pending requests");
ListIndex i = mPending.Length();
while (i > 0) {
--i;
DS_LOG_ERROR("terminate %u", mPending[i].mId);
NS_ProxyRelease(mOwningThread, mPending[i].mRequest.forget());
}
}
}
void
DeviceStorageRequestManager::StorePermission(size_t aAccess, bool aAllow)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aAccess < MOZ_ARRAY_LENGTH(mPermissionCache));
MutexAutoLock lock(mMutex);
mPermissionCache[aAccess] = aAllow ? nsIPermissionManager::ALLOW_ACTION
: nsIPermissionManager::DENY_ACTION;
DS_LOG_INFO("access %zu cache %u", aAccess, mPermissionCache[aAccess]);
}
uint32_t
DeviceStorageRequestManager::CheckPermission(size_t aAccess)
{
MOZ_ASSERT(IsOwningThread() || NS_IsMainThread());
MOZ_ASSERT(aAccess < MOZ_ARRAY_LENGTH(mPermissionCache));
MutexAutoLock lock(mMutex);
DS_LOG_INFO("access %zu cache %u", aAccess, mPermissionCache[aAccess]);
return mPermissionCache[aAccess];
}
bool
DeviceStorageRequestManager::IsOwningThread()
{
bool owner = false;
mOwningThread->IsOnCurrentThread(&owner);
return owner;
}
nsresult
DeviceStorageRequestManager::DispatchToOwningThread(
already_AddRefed<nsIRunnable>&& aRunnable)
{
return mOwningThread->Dispatch(Move(aRunnable), NS_DISPATCH_NORMAL);
}
nsresult
DeviceStorageRequestManager::DispatchOrAbandon(
uint32_t aId, already_AddRefed<nsIRunnable>&& aRunnable)
{
MutexAutoLock lock(mMutex);
if (mShutdown) {
/* Dispatching in this situation may result in the runnable being
silently discarded but not freed. The runnables themselves are
safe to be freed off the owner thread but the dispatch method
does not know that. */
DS_LOG_DEBUG("shutdown %u", aId);
nsCOMPtr<nsIRunnable> runnable(aRunnable);
return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
}
nsresult rv = DispatchToOwningThread(Move(aRunnable));
if (NS_WARN_IF(NS_FAILED(rv))) {
DS_LOG_ERROR("abandon %u", aId);
}
return rv;
}
uint32_t
DeviceStorageRequestManager::Create(nsDOMDeviceStorage* aDeviceStorage,
DeviceStorageCursorRequest* aRequest,
nsDOMDeviceStorageCursor** aCursor)
{
MOZ_ASSERT(aDeviceStorage);
MOZ_ASSERT(aRequest);
MOZ_ASSERT(aCursor);
RefPtr<nsDOMDeviceStorageCursor> request
= new nsDOMDeviceStorageCursor(aDeviceStorage->GetOwnerGlobal(), aRequest);
uint32_t id = CreateInternal(request, true);
DS_LOG_INFO("%u", id);
request.forget(aCursor);
return id;
}
uint32_t
DeviceStorageRequestManager::Create(nsDOMDeviceStorage* aDeviceStorage,
DOMRequest** aRequest)
{
MOZ_ASSERT(aDeviceStorage);
MOZ_ASSERT(aRequest);
RefPtr<DOMRequest> request
= new DOMRequest(aDeviceStorage->GetOwnerGlobal());
uint32_t id = CreateInternal(request, false);
DS_LOG_INFO("%u", id);
request.forget(aRequest);
return id;
}
uint32_t
DeviceStorageRequestManager::CreateInternal(DOMRequest* aRequest, bool aCursor)
{
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(!mShutdown);
uint32_t id;
do {
id = ++sLastRequestId;
} while (id == INVALID_ID || Find(id) != mPending.Length());
ListEntry* entry = mPending.AppendElement();
entry->mId = id;
entry->mRequest = aRequest;
entry->mCursor = aCursor;
return entry->mId;
}
nsresult
DeviceStorageRequestManager::Resolve(uint32_t aId, bool aForceDispatch)
{
if (aForceDispatch || !IsOwningThread()) {
DS_LOG_DEBUG("recv %u", aId);
RefPtr<DeviceStorageRequestManager> self = this;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self, aId] () -> void
{
self->Resolve(aId, false);
});
return DispatchOrAbandon(aId, r.forget());
}
DS_LOG_INFO("posted %u", aId);
if (NS_WARN_IF(aId == INVALID_ID)) {
DS_LOG_ERROR("invalid");
MOZ_ASSERT_UNREACHABLE("resolve invalid request");
return NS_OK;
}
ListIndex i = Find(aId);
if (NS_WARN_IF(i == mPending.Length())) {
return NS_OK;
}
return ResolveInternal(i, JS::UndefinedHandleValue);
}
nsresult
DeviceStorageRequestManager::Resolve(uint32_t aId, const nsString& aResult,
bool aForceDispatch)
{
if (aForceDispatch || !IsOwningThread()) {
DS_LOG_DEBUG("recv %u", aId);
RefPtr<DeviceStorageRequestManager> self = this;
nsString result = aResult;
nsCOMPtr<nsIRunnable> r
= NS_NewRunnableFunction([self, aId, result] () -> void
{
self->Resolve(aId, result, false);
});
return DispatchOrAbandon(aId, r.forget());
}
DS_LOG_INFO("posted %u w/ %s", aId,
NS_LossyConvertUTF16toASCII(aResult).get());
if (NS_WARN_IF(aId == INVALID_ID)) {
DS_LOG_WARN("invalid");
MOZ_ASSERT_UNREACHABLE("resolve invalid request");
return NS_OK;
}
ListIndex i = Find(aId);
if (NS_WARN_IF(i == mPending.Length())) {
return NS_OK;
}
nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal();
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(global))) {
return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
}
JS::RootedValue rvalue(jsapi.cx());
JS::MutableHandleValue mvalue(&rvalue);
if (NS_WARN_IF(!xpc::StringToJsval(jsapi.cx(), aResult, mvalue))) {
return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
}
return ResolveInternal(i, rvalue);
}
nsresult
DeviceStorageRequestManager::Resolve(uint32_t aId, uint64_t aValue,
bool aForceDispatch)
{
if (aForceDispatch || !IsOwningThread()) {
DS_LOG_DEBUG("recv %u w/ %" PRIu64, aId, aValue);
RefPtr<DeviceStorageRequestManager> self = this;
nsCOMPtr<nsIRunnable> r
= NS_NewRunnableFunction([self, aId, aValue] () -> void
{
self->Resolve(aId, aValue, false);
});
return DispatchOrAbandon(aId, r.forget());
}
DS_LOG_INFO("posted %u w/ %" PRIu64, aId, aValue);
if (NS_WARN_IF(aId == INVALID_ID)) {
DS_LOG_ERROR("invalid");
MOZ_ASSERT_UNREACHABLE("resolve invalid request");
return NS_OK;
}
ListIndex i = Find(aId);
if (NS_WARN_IF(i == mPending.Length())) {
return NS_OK;
}
JS::RootedValue value(nsContentUtils::RootingCxForThread(),
JS_NumberValue((double)aValue));
return ResolveInternal(i, value);
}
nsresult
DeviceStorageRequestManager::Resolve(uint32_t aId, DeviceStorageFile* aFile,
bool aForceDispatch)
{
MOZ_ASSERT(aFile);
DS_LOG_DEBUG("recv %u w/ %p", aId, aFile);
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);
RefPtr<BlobImpl> blobImpl = new BlobImplFile(fullPath, aFile->mMimeType,
aFile->mLength, aFile->mFile,
aFile->mLastModifiedDate);
/* File should start out as mutable by default but we should turn
that off if it wasn't requested. */
bool editable;
nsresult rv = blobImpl->GetMutable(&editable);
if (NS_WARN_IF(NS_FAILED(rv))) {
DS_LOG_WARN("%u cannot query mutable", aId);
return Reject(aId, POST_ERROR_EVENT_UNKNOWN);
}
if (editable != aFile->mEditable) {
rv = blobImpl->SetMutable(aFile->mEditable);
if (NS_WARN_IF(NS_FAILED(rv))) {
DS_LOG_WARN("%u cannot set mutable %d", aId, aFile->mEditable);
return Reject(aId, POST_ERROR_EVENT_UNKNOWN);
}
}
return Resolve(aId, blobImpl, aForceDispatch);
}
nsresult
DeviceStorageRequestManager::Resolve(uint32_t aId, BlobImpl* aBlobImpl,
bool aForceDispatch)
{
if (aForceDispatch || !IsOwningThread()) {
DS_LOG_DEBUG("recv %u w/ %p", aId, aBlobImpl);
RefPtr<DeviceStorageRequestManager> self = this;
RefPtr<BlobImpl> blobImpl = aBlobImpl;
nsCOMPtr<nsIRunnable> r
= NS_NewRunnableFunction([self, aId, blobImpl] () -> void
{
self->Resolve(aId, blobImpl, false);
});
return DispatchOrAbandon(aId, r.forget());
}
DS_LOG_INFO("posted %u w/ %p", aId, aBlobImpl);
if (NS_WARN_IF(aId == INVALID_ID)) {
DS_LOG_ERROR("invalid");
MOZ_ASSERT_UNREACHABLE("resolve invalid request");
return NS_OK;
}
ListIndex i = Find(aId);
if (NS_WARN_IF(i == mPending.Length())) {
return NS_OK;
}
if (!aBlobImpl) {
return ResolveInternal(i, JS::NullHandleValue);
}
nsIGlobalObject* global = mPending[i].mRequest->GetOwnerGlobal();
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(global))) {
return RejectInternal(i, NS_LITERAL_STRING(POST_ERROR_EVENT_UNKNOWN));
}
RefPtr<Blob> blob = Blob::Create(global, aBlobImpl);
JS::Rooted<JSObject*> obj(jsapi.cx(),
blob->WrapObject(jsapi.cx(), nullptr));
MOZ_ASSERT(obj);
JS::RootedValue value(jsapi.cx(), JS::ObjectValue(*obj));
return ResolveInternal(i, value);
}
nsresult
DeviceStorageRequestManager::ResolveInternal(ListIndex aIndex,
JS::HandleValue aResult)
{
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(!mShutdown);
/* Note that we must seize the DOMRequest reference and destroy the entry
before calling FireError because it may go straight to the JS code
which in term may call back into our code (as observed in Shutdown).
The safest thing to do is to ensure the very last thing we do is the
DOM call so that there is no inconsistent state. */
RefPtr<DOMRequest> request;
bool isCursor = mPending[aIndex].mCursor;
if (!isCursor || aResult.isUndefined()) {
request = mPending[aIndex].mRequest.forget();
mPending.RemoveElementAt(aIndex);
} else {
request = mPending[aIndex].mRequest;
}
if (isCursor) {
auto cursor = static_cast<nsDOMDeviceStorageCursor*>(request.get());
/* Must call it with the right pointer type since the base class does
not define FireDone and FireSuccess as virtual. */
if (aResult.isUndefined()) {
DS_LOG_INFO("cursor complete");
cursor->FireDone();
} else {
DS_LOG_INFO("cursor continue");
cursor->FireSuccess(aResult);
}
} else {
DS_LOG_INFO("request complete");
request->FireSuccess(aResult);
}
return NS_OK;
}
nsresult
DeviceStorageRequestManager::RejectInternal(DeviceStorageRequestManager::ListIndex aIndex,
const nsString& aReason)
{
MOZ_ASSERT(IsOwningThread());
MOZ_ASSERT(!mShutdown);
/* Note that we must seize the DOMRequest reference and destroy the entry
before calling FireError because it may go straight to the JS code
which in term may call back into our code (as observed in Shutdown).
The safest thing to do is to ensure the very last thing we do is the
DOM call so that there is no inconsistent state. */
RefPtr<DOMRequest> request = mPending[aIndex].mRequest.forget();
bool isCursor = mPending[aIndex].mCursor;
mPending.RemoveElementAt(aIndex);
if (isCursor) {
/* Must call it with the right pointer type since the base class does
not define FireError as virtual. */
auto cursor = static_cast<nsDOMDeviceStorageCursor*>(request.get());
cursor->FireError(aReason);
} else {
request->FireError(aReason);
}
return NS_OK;
}
nsresult
DeviceStorageRequestManager::Reject(uint32_t aId, const nsString& aReason)
{
DS_LOG_DEBUG("recv %u", aId);
if (NS_WARN_IF(aId == INVALID_ID)) {
DS_LOG_ERROR("invalid");
MOZ_ASSERT_UNREACHABLE("reject invalid request");
return NS_OK;
}
RefPtr<DeviceStorageRequestManager> self = this;
nsString reason = aReason;
nsCOMPtr<nsIRunnable> r
= NS_NewRunnableFunction([self, aId, reason] () -> void
{
DS_LOG_INFO("posted %u w/ %s", aId,
NS_LossyConvertUTF16toASCII(reason).get());
ListIndex i = self->Find(aId);
if (NS_WARN_IF(i == self->mPending.Length())) {
return;
}
self->RejectInternal(i, reason);
});
return DispatchOrAbandon(aId, r.forget());
}
nsresult
DeviceStorageRequestManager::Reject(uint32_t aId, const char* aReason)
{
nsString reason;
reason.AssignASCII(aReason);
return Reject(aId, reason);
}
void
DeviceStorageRequestManager::Shutdown()
{
MOZ_ASSERT(IsOwningThread());
MutexAutoLock lock(mMutex);
mShutdown = true;
ListIndex i = mPending.Length();
DS_LOG_INFO("pending %zu", i);
while (i > 0) {
--i;
DS_LOG_INFO("terminate %u (%u)", mPending[i].mId, mPending[i].mCursor);
}
mPending.Clear();
}
DeviceStorageRequestManager::ListIndex
DeviceStorageRequestManager::Find(uint32_t aId)
{
MOZ_ASSERT(IsOwningThread());
ListIndex i = mPending.Length();
while (i > 0) {
--i;
if (mPending[i].mId == aId) {
return i;
}
}
DS_LOG_DEBUG("no id %u", aId);
return mPending.Length();
}