Files
palemoon27/dom/devicestorage/nsDeviceStorage.cpp
T
roytam1 cf2d7d1ae9 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1212114 - Stop using dom::Promise::MaybeRejectBrokenly() in various FileSystemTaskBase subclasses. r=baku (9f6ea5db27)
- Bug 1253534 - Suspicious code with probably reversed parms in call to IsSingleLineTextControl, r=mounir (1a5ee1fd1e)
- Bug 769117 - Mochitests for youtube flash -> html5 rewriting; r=bz r=hsivonen (6324471dd9)
- Bug 1250148 - FormData should treat empty input type=file as empty string in FormData and as unnamed Blob in HTML submission, r=smaug (7043113247)
- Bug 1173320 - patch 1/8 - Implement Directory object as string and not as BlobImpl, r=smaug (eca4bec6ea)
- Bug 1173320 - patch 2/8 - Proper naming for the FileSystem path serialization, r=smaug (e1604ff2b1)
- Bug 1173320 - patch 3/8 - Improve the Windows path management, r=smaug (f8da8541b5)
- Bug 1252347 - Provide missing implementations of nsIBaseWindow::SetPositionDesktopPix. r=emk (97f9b92465)
- Bug 1235066 - SVG elements should not display title attributes as tooltips. r=enndeakin (a77e809688)
- Bug 1251809 - Add input[type=file] tooltip support for e10s. r=ehsan (9f72cf3cd2)
- Bug 1173320 - patch 4/8 - Directory in FileList, r=smaug (c09d445043)
- Bug 1246244 - Regression test. r=jaws,Margaret (8de084f9db)
- Bug 1224105 - Allow windowless chrome docshells containing content docshells (r=smaug) (3343a2a966)
- Bug 1230267 - Inconsistent display of SVG title as tooltip when multi-process is enabled. r=jst (b3fc298b7f)
- Bug 1173320 - patch 5/8 - Cleanup manual string path management, r=smaug (8f6f0c4e44)
- Bug 1250403 - Part 1. Define ARCH_CPU_ARM64 instead of ARCH_CPU_AARCH64. r=billm (d93a0b54da)
- Bug 1250403 - Part 2. Import crbug #354405 for aarch64. r=billm (9dca949bcc)
- Bug 1246501 - Add ppc specific atomic operations to ipc/chromium. r=Waldo (8054b612be)
- Bug 1257305 - Avoid VS2015 about casting int to void*. r=dvander (6234acf6fc)
- Bug 1207401 - Send B2G sandbox logging to both stderr and logcat. r=kang (ae404aa5ca)
- Bug 1173320 - patch 6/8 - Make FileList clonable to workers if it doesn't contain Directories, r=smaug (ea6ba42f31)
- Bug 1173320 - patch 7/8 - Tests for FileList and Directories, r=smaug (3f11503300)
- Bug 1222522, part 1 - Make most dom/devicestorage/ tests work with e10s. r=dhylands (3cc7e339a5)
- Bug 1173320 - patch 8/8 - Fix e10s tests for DeviceStorage API, r=smaug (949454bae7)
- Bug 1222522, part 2 - Inline devicestorage_cleanup() in dom/devicestorage/ tests. r=dhylands (bcdee11385)
- Bug 1258137 - OSFileSystem should not be kept alive by more than 1 Directory, r=smaug (225775f48d)
- Bug 1255867. Remove some unnecessary AutoJSAPI uses. r=bholley (618cf018e8)
- Bug 1237173 - Part2: Change type of duration to Maybe<StickyTimeDuration>. r=birtles (6b83473e05)
- Bug 1238469 - Part 1: Refactor b2g emulator tests to remove the usage of custom mozharness configs; r=ahal (a128c8a8bb)
- Bug 1238469 - Part 2: Refactor b2g mulet tests to remove the usage of custom mozharness configs; r=ahal (6c7ced3419)
- Bug 1238469 - Part 3: Use b2g_emulator_unittest.py for b2g marionette tests; r=ahal (5fdb88b0e1)
- Bug 1237173 - Part3: Throw TypeError if duration is NaN, negative value or not 'auto' string. r=birtles, r=smaug (d60b0318d2)
- Bug 1136567 - Marionette test for selection carets' positions after changing orientation of device. r=Automatedtester (e7b25b1e4f)
- Bug 1138839 - Part1 - Marionette test for selecting text inside an iframe. r=automatedtester (45db397a94)
- Bug 1138839 - Part2 - Fix naming issue in marionette test for selection carets. r=automatedtester (c54850d9d3)
- Bug 1198542: Update Marionette element IDs to be valid UUID. r=ato This allows WebDriver compatibility to create valid URI. (1b3de245de)
- Bug 1204496: When searching by link text start from the startNode and not the rootNode; r=ato (0dfa3b8830)
- Bug 1157725 - Rewrite test_mouse_action.py to be more robust. r=ato (40273abca2)
- Bug 1141519: added test that puts marionette into a position that can cause hangs when in content scope; r=jgriffin (c99d8cf325)
- Bug 1246407 - Rename parent directories for Marionette client and test harness; r=automatedtester (79d4e521fd)
- Bug 1253989 Part 3 - Use @parameterized to rewrite selection mode tests. r=mtseng (baf189f1d5)
- Bug 1253989 Part 4 - Rename AccessibleCaret test files. r=mtseng (d562edc8f4)
- Bug 1251519 Part 3 - Add regression tests for caret dragging. r=mats (048dd5f103)
- Bug 1253989 Part 5 - Remove touch caret and selection carets naming. r=mtseng (9929425b6d)
- Bug 1253989 Part 6 - Refactor open_test_html(). r=mtseng (25a2424ca1)
- Bug 1251519 Part 4 - Add tests for dragging caret to content boundary. r=mats (489b47269b)
- Bug 1251519 Part 5 - Use union rect of child frames for clamping. r=mats (5520416749)
- Bug 1216924 - Don't align to tiles (either real or virtual) if displayport suppression is enabled. r=BenWa (d470d188e4)
- Bug 1254273 - Align the displayport to a max of 256 pixels even if the layer is larger. r=BenWa (0cf8cc0b6c)
- Bug 1257938 part 3: Remove support for the "layout.css.sticky.enabled" pref (so we'll unconditionally support "position: sticky"). r=corey (fe12efd18f)
- Bug 1257938 part 1: Adjust automated tests to assume position:sticky is unconditionally supported. r=corey (e749ac579e)
- Bug 1257938 part 2: Remove separation between test_position_sticky.html & its helper-file, now that it doesn't need to tweak a pref. r=corey (631edab31a)
- Bug 1257491 - Ensure that if the peek-messages code modifies the displayport, we schedule a repaint. r=BenWa (d6fb6ff96c)
- Bug 1255006 - Ensure the displayport rect takes priority over a suppressed-margins displayport. r=kats (aa6cbc0250)
- Bug 1259235 - Add IsScrollFrameWithSnapping to speed up event regions. r=mstange (2a744c311e)
- Bug 1185140 - [css-grid][flexbox] Make grid/flex item blockification happen before creating table pseudos, per the latest specs. r=dholbert (dddb8b17ef)
- Bug 1224424 - Replace mask-mode:auto keyword by mask-mode:match-source; r=dbaron (5a51b3b301)
- Bug 1252039 - corrected MOZ_ASSERT expression in SeparatorRequiredBetweenTokens. r=dbaron (08790aa514)
- Bug 1243734 - Part 1. Use MOZ_ENABLE_MASK_AS_SHORTHAND to define the type of mask property; r=dbaron (d8cd3a1c4a)
- Bug 1243734 - Part 2. Set up gCSSProperties depends on mask-as-shorth and; r=dbaron (aa6b0259d8)
- Bug 1243734 - Part 3. Set mask-mode reftest as failure before enable mask-as-shorthand; r=dbaron (6a326fbaf2)
- Bug 1243734 - Part 5. Add MOZ_ENABLE_MASK_AS_SHORTHAND compile flag; r=ted r=dbaron (fcc1344ac8)
- Bug 1142531: Check more bits in nsStyleContext::MoveTo assertion. r=heycam (8b62b139df)
- Bug 1258147 - Pierce through display:contents style context ancestors when looking for CB context to compare our writing-mode to. r=jfkthame (956d8c25e5)
- Bug 823483 patch 1 - Check for percentage max-width in addition to percentage width when deciding to ignore intrinsic min-width of replaced elements. r=dholbert (f88cb5f6a8)
- Bug 823483 patch 3 - Limit effect of percentage width and max-width on intrinsic size to elements with replaced element sizing. r=dholbert (2573c3cfff)
- Bug 823483 patch 4 - Make a percentage max-width override a fixed width for replaced element intrinsic size computation. r=dholbert (dda859f06c)
- Bug 823483 patch 5 - Make (again) percentage width on text inputs make intrinsic minimum width be 0. r=dholbert (d46ada73ef)
- Bug 1247929 patch 2 - Hard-code the Web-compatible set of form controls whose intrinsic minimum inline-size shrinks to 0 when inline-size (width) is specified as a percentage. r=dholbert (e4f0c80fcb)
- Bug 1254968 - Add support for running JS builtins' constructors over Xray wrappers without unwrapping the newTarget. r=bholley,f=bz (56213ae395)
- Bug 1249123 - Add telemetry for __defineGetter__/__defineSetter__ |this| values. data-review=bsmedberg r=till (52c5fd3488)
- Bug 1232639 - Implement Object.{values,entries} in C++ to avoid native call overhead in tight loop. r=jorendorff (7262497283)
- Bug 1254966 - Disambiguate JS Telemetry macro names. r=evilpie (781d0916c4)
- Bug 1254384: Use generic shell switch syntax in js/src/jit-test tests. r=nbp (6f5975cc55)
- Bug 1253016 - Remove legacy __defineGetter__/__defineSetter__ this behavior. r=till (cf1b7ad28c)
- Bug 1253016 - Implement and test the new spec for legacy functions. r=till (1ff7762e3e)
2024-02-28 12:10:08 +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 nsRunnable
{
public:
IOEventComplete(DeviceStorageFile *aFile, const char *aType)
: mFile(aFile)
, mType(aType)
{
}
~IOEventComplete() {}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
nsString data;
CopyASCIItoUTF16(mType, data);
nsCOMPtr<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();
}