Files
palemoon27/dom/devicestorage/nsDeviceStorage.cpp
T
roytam1 e4c3e62beb import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1216751 part 1. Restrict value iterators to interfaces that have indexed properties and pair iterators to interfaces that do not have indexed properties. r=qdot (6519f3f8c5)
- Bug 1216751 part 2. For value iterators, "entries", "keys", and "values" must just come from Array.prototype. r=qdot (c0859f945c)
- Bug 1216751 part 3. For pair iterators, @@iterator should be an alias for "entries". Similarly for maplikes and "entries" and setlikes and "values". r=qdot (bbe7c04782)
- Bug 1216751 part 4. Implement forEach for iterable interfaces. r=qdot (8fdba677a4)
- Bug 1216751 part 5. Remove the now-unnecessary value iterator infrastructure, since it's entirely handled via the %ArrayPrototype% methods now. r=qdot (88d3911694)
- Bug 1231333 - part 1, JS engine: only allow futexWait in workers. r=luke (28e16fd2f9)
- Bug 1231333 - part 2, DOM: only allow futexWait in workers. r=khuey (6c4dc98037)
- Bug 1148990 - Don't ship bagheeraclient.js or tokenserverclient.js on Android. r=gps (aa9b22699a)
- Bug 1216749 - Land the Firefox Kinto.js client (r=rnewman) (ea8c74e2ea)
- Bug 1230221 - Convert JS callsites to use asyncOpen2 within services/ (r=sicking) (07ac8751f1)
- Bug 1242965 - Make services/common eslintable. r=rnewman (0c84562750)
- Bug 1055616 - Skip addons addons without a sourceURI or from a non-secure domain rather than treating them as errors. r=rnewman (7b8b738be0)
- Bug 1229986 - get Sync tps tests starting again. r=whimboo (8cd0bf4f7f)
- Bug 1003204: Removed CommonUtils.exceptionStr() in services/sync r=makh r=gfritzsche (830c106a29)
- Bug 1003204: Removed CommonUtils.exceptionStr() in services/common/ r=gfritzsche (2c7bd4f8b5)
- Bug 1234734 - Replace CommonUtils.stackTrace() with Log.stackTrace(). r=markh (3f0e88f192)
- Bug 1241715 - get Sync TPS tests working locally by tweaking observers listened for and the authentication setup. r=whimboo (529b2f3d44)
- Bug 1203736 - Convert H264::DecodeSPS assert to error return. r=jya (41c8c34c42)
- Bug 1186716: Error if SPS NAL parsing failed. r=rillian (6c158be51e)
- Bug 1187076 - Warn at end of SPS buffers. r=jya (2a49671261)
- fix broken files (a090aad200)
- Bug 1218217: avoid buffersize overflow even if codec is unbounded in dimensions r=pkerr (356140c947)
- Bug 1218217: bustage fix for static assert r=bustage (e86dc5bf3a)
- Bug 1041882 - Remove Froyo-specific support from libcubeb. r=snorp, r=padenot (e1f2d5283f)
- Bug 1073319 - Enable AVX2 for libvpx on linux (update.py). r=rillian (934fd0a896)
- Bug 1245027 - Move LOCAL_INCLUDES to moz.build in media/libvpx. r=mshal (7e56797d0e)
- parts of Bug 1151175 - Update libvpx update.py for 1.4.0. (0e3f4a470f)
- bits of 1178215 (bab7592703)
- Bug 1218124 - Add vpx_once patch to update script. r=gerald (7b72a43382)
- Bug 1225221 - vpx: Allow 8k video in update.sh. r=kinetik (9ec59f7737)
- Bug 1224363 - Upstream update patch - r=rillian (4772921a5f)
- Bug 1224361 - Upstream update patch - r=rillian (36ad6f1de4)
- Bug 1233983 - Make libvpx build with clang-cl; r=rillian (5d98a8d888)
- Bug 1224371 - Upstream update patch. r=jya (25164ba856)
- Bug 1237848 - Updated update.py patch - r=rillian (69646eb6dc)
- Bug 1184226 - Suppressing received packets when disabled, r=ekr (c8dfdb1a56)
- Bug 1184226 - Disabling write on shutdown, r=ekr (d5a810dbe5)
- Bug 1184226 - Updating transportlayerdtls logging levels, r=ekr (f3bc4a9889)
- Bug 1137932: Unwind the stack before starting the DTLS handshake. r=mt (69dce8243a)
- Bug 1214269 - read multiple DTLS packets from NSS if present. r=mt rjesup (e57b1628f5)
- Bug 1235235 - Fix -Wimplicit-fallthrough warning in media/mtransport/. r=ekr (d56c9d1244)
- Bug 1115483 - Accept a match on any a=fingerprint value. r=ekr (4a58378c09)
- Bug 1167274 - Do the right thing when accessing the proxyinfo fails for some reason. r=mt (3ea23173ea)
- Bug 1125292 - Sending ALPN header field for WebRTC calls, r=bwc (16fda60c39)
- Bug 1167443 - Fix verification of end-of-candidates in mochitests. r=mt (8d74546e68)
- Bug 1192813 - update the default candidate as new candidates arrive.  r=bwc (490ac80af2)
- Bug 1206981 - prevent ICE TCP from being turned off under e10s. r=jesup (a38afd56b8)
- Bug 1234578 - Assert if PCM is destroyed improperly. r=rjesup (f1aa0d7cbc)
- Bug 1164564 - WorkerDebugger.initialize should not return failure when called more than once;r=khuey (c316c83af7)
- Bug 1211903 - WorkerDebugger should live on the main thread;r=khuey (5586888e77)
- Bug 1164581 - Adding an overload for NS_ProxyRelease that accepts already_AddRefed, and removing all the others. r=bobbyholley (bc70230689)
- Bug 1186750 part 1 - Inlinize trivial constructors and destructors of events in DeviceStorageRequestParent. r=dhylands (0fc6b594b1)
- Bug 1186750 part 2 - Remove some unused member fields in events in DeviceStorageRequestParent. r=dhylands (d4be7e7031)
- Bug 1186750 part 3 - Abstract CancelableFileEvent in DeviceStorageReqeustParent and use already_AddRefed&& for passing DeviceStorageFile parameter. r=dhylands (cea4df4465)
- Bug 1186750 part 4 - Clear runnable list in DeviceStorageRequestParent when being destroyed. r=dhylands (a4d6018ce6)
- Bug 1196315 - Ensure MIME service is only accessed on the main thread. r=dhylands (20c07f4baf)
- Bug 1186750 part 5 - Convert nsDOMDeviceStorage::CheckPermission to take already_AddRefed&&. r=dhylands (7b2d0b415e)
- Bug 1186750 part 6 - Remove unused and unimplemented method nsDOMDeviceStorage::StorePermission. r=dhylands (e6772e7b51)
- Bug 1186750 part 7 - Convert DispatchToOwningThread and DispatchOrAbandon to take already_AddRefed&&. r=dhylands (5925568a22)
- Bug 1186750 part 8 - Convert DeviceStorageUsedSpaceCache::Dispatch to use already_AddRef&&. r=dhylands (660b44eec7)
- Bug 1186750 part 9 - Use already_AddRefed&& to initialize mFile of device storage requests. r=dhylands (c94464f412)
- Bug 1186750 part 10 - Simplify code in DeviceStorageRequestParent::Dispatch. r=dhylands (debcc219ca)
- Bug 1186750 part 11 - Convert all usage of Dispatch/NS_DispatchToMainThread in dom/devicestorage to pass in either already_AddRefed or raw pointer. r=dhylands (753694d0b5)
- Bug 1059469: Part 1 - Add a log module for dump() calls. r=bent (d94c677e49)
- Bug 1059469: Part 2 - When rescheduling the interval timer, cancel it first, and refactor things so that actually does something. r=bent (1edc485b0f)
- Bug 1243881 - patch 1 - unship performance.translateTime, r=bz (5a4afeea67)
- Bug 1243881 - patch 2 - unship performance.translateTime, r=bz (5bf9557cd4)
- Bug 1165722 - Replace JS_GetPropertyDescriptor usage in Xray code. r=bholley (e277cbcc78)
- Bug 1243824. Add support for static functions and attributes on JSXrays. r=bholley (498d6c6034)
- Bug 1228456 - SharedWorker should close the MessagePort in case the connecting runnable is not dispatched, r=smaug (c14a3e212f)
- Bug 779707 - Add crashtest. (e86caca48e)
- Bug 1228456 - add 'override' to the Cancel() method of a nsICancelableRunnable, rs=me (48db3b97e9)
- Bug 1131323 - Enable SharedWorker loads to be intercepted through service workers; r=nsm (b2d972c5e3)
- Bug 1173002 - Set worker system principal flag correctly when created from chrome, r=bz, a=kwierso. (ac9fc2980d)
- bits of 1113429 backout (a862f16bb7)
- bug 1206312 - add IndexedDatabaseManager include to IDBKeyRange. r=bz (bd6663f976)
- Bug 1247117: De-namespace much of IndexedDB. r=baku (a996e3b443)
- Bug 1196841: Update getAll/getAllKeys to match the spec and expose them. r=baku (7365769e04)
- Bug 1196840: Make IDBTransaction::ObjectStoreNames const. r=baku (e7af2b0510)
- Bug 1176165 - Fix the exception codes returned from functions that modify the IndexedDB schema, r=janv. (efa4e818d0)
- Bug 935753 - Firefox displays the "This is a secure Firefox page" indicator on pages served by addons. r=MattN (77dced27ad)
- Bug 925681 - Show identity block and reload icon in awesomebar in Australis' customization mode. ui-r=shorlander, r=Gijs (ffd1b2f6a4)
- Bug 970382 - Add about:accounts to the list of chrome UIs with a special identity mode r=gavin (6d2817d087)
- Bug 1051847 - Add trusted identity block to about:license and about:rights. r=dao (aa8dfe4d1d)
- Bug 1094947 - The trusted identity block is not displayed for the about:downloads page. r=jaws (1c51faa077)
- Bug 686281 - Implement CSS mask style; r=dbaron. (2f823c4a49)
- Bug 686281 - Mask CSS parsing and Mask DOM API. r=dbaron (f9cc291131)
- Bug 686281 - Mask CSS rendering; r=mstange (b26ba7ba7e)
- Bug 686281 - Mask CSS animation; r=dbaron. (4ce1ba671e)
- Bug 686281 - Mask CSS webkit-alias; r=dbaron. (c27f4023d6)
- Bug 686281 - Mask mochitest; r=dbaron. (010fcdfd04)
- Bug 686281 - Expands will-change of a shorthand prop to longhand ones; r=dbaron. (f8e4a6dcfd)
- Bug 686281 - A static assertion to keep value correctness of NS_RULE_NODE_IS_ANIMATION_RULE; r=dbaron. (5ae87b576b)
- Bug 686281 - Remove nsStyleSVGReset::mMask; r=dbaron (1e7a0dfb45)
- Bug 686281 - mask-composite reftests; r=dbaron (7f769e196a)
- Bug 686281 - Rename nsStyleSVGReset::mLayers to nsStyleSVGReset::mMask; Rename nsStyleBackground::mLayers to nsStyleBackground::mImage. r=dbaron (3bd4fc6e3b)
- Bug 1241275 - Change the way -moz-window-dragging works. r=heycam,roc (5691f2dbf5)
- Bug 1246892 - pass aCTF as a reference instead of value. r=roc (98b0e45063)
- Bug 1234800 - Reinstate code that adjusts dirty rects for fixed-position frames in display ports. r=tn (44e55ebacb)
- Bug 1234800 - Move this line to the right place. r=tn (1a86a7fc72)
- Bug 1216832 - Handle preserve-3d visible regions during display list building by always transforming from the preserve-3d root each time. r=roc (1887af1172)
- Bug 1231243 - In nsDisplayBackgroundImage::GetBoundsInternal(), take the union of the image bounds and the viewport bounds if APZ is enabled. r=mstange (87a1fa0ab4)
- Bug 1246622 - Handle nested preserve-3d contexts when hit testing. r=roc (6eed51c734)
- Bug 1235945 - Fix assertion error in some cases when running szip when debug flags are enabled for host tools. r=froydnj (3a0aa4f728)
- Bug 1224798: Do not produce a clip mask if our context is entirely clipped out anyway. r=jrmuizel (3926a4ef7d)
- Bug 1223604 - Disentangle nsSVGClipPathFrame::ApplyClipOrPaintClipMask and make the code easier to understand. r=Bas (c8c19a1b0d)
- Bug 1204405: Don't access prefs off main thread in testing ProcessLink::Open(). r=khuey (301aa7259d)
- Bug 1248896 - don't conditional compile on config ENABLE_TESTS in Nuwa. r=khuey (4f2fd275fd)
- Bug 1232458 - use UniquePtr<T[]> instead of nsAutoArrayPtr<T> in WindowsDllBlocklist.cpp; r=aklotz (292071bdb5)
- Bug 1247741 - Additional checks for pointer validity in LdrLoadDLL detour. r=aklotz (8ee48e8cf3)
- Bug 1113930 - Move __libc_stack_end related code block from StackWalk.cpp in a non-OSX section. r=froydnj (4f0f9e2e66)
- Bug 1113930 - Use the actual stack end address on x86 OSX and Android for the stack walker. r=froydnj (7371d9a508)
- missing bit of Bug 1216681 (fdf69e362f)
- Bug 1193593 - Test fingerprinting resistance for media queries in picture elements. r=heycam (6155b73c26)
- Bug 1232829 - Detach obsolete DocumentTimeline from refresh driver when the document is reset; r=smaug (564680e2a0)
- Bug 1075457, part 1 - Implement rendering for |clip-path:polygon()|. r=mstange, r=jwatt (76056caacd)
- Bug 1075457, part 2 - Implement circle() and ellipse() for the |clip-path| property. r=mstange, r=jwatt (4b8b39c682)
- Bug 1094571 - add unicode-range load tests. r=heycam (3358555411)
- Bug 1216695 - Remove the Request.context specific bits from fetch-request-resources.https.html; r=bkelly (2315e50b97)
- Bug 1193133 - Disable broken service worker wpt tests. r=bkelly (8f0205d5e7)
- Bug 1199831: Fix a bunch of mixed-content violations in imported ServiceWorker WPTs. r=jdm (33f261ce91)
- bit of Bug 603201 (325170577f)
- Bug 1184798 - same origin, cors and no-cors load tests. r=bkelly (f8549dd0bb)
- Bug 1210581: Test controlled worker loads (XHR, fetch, importScripts). r=ehsan (41a436df47)
- Bug 1215196 - Fix web-platform-tests iframe scripts to avoid pulling in testharness.js in them; r=bkelly (a2edb0784c)
- Bug 1242798 - Don't OSR into Ion on debuggee frames. (r=jandem) (21e17bdd9d)
- Bug 1238658 - Allow setElem-accessor optimizations only for native baseHolder objects; r=efaust (12c9766a53)
- Bug 1144630 - Follup: Fix review nit. (rs=evilpie) (67b5cc2c7f)
- Bug 1182866 - Fix Baseline GETNAME stubs to check for uninitialized lexicals. (r=jandem) (dd47d2025a)
- Bug 1189536 - Make fetch-request-xhr.https.html pass; r=bkelly (ce177226bf)
- Bug 1188822 - Make service-workers/service-worker/fetch-request-resources.https.html pass. r=bkelly (3a5f3a6660)
2023-11-14 15:08:43 +08:00

4168 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 "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
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();
}
DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
const nsAString& aStorageName,
const nsAString& aPath)
: mStorageType(aStorageType)
, mStorageName(aStorageName)
, mPath(aPath)
, mEditable(false)
, mLength(UINT64_MAX)
, mLastModifiedDate(UINT64_MAX)
{
Init();
AppendRelativePath(aPath);
NormalizeFilePath();
}
DeviceStorageFile::DeviceStorageFile(const nsAString& aStorageType,
const nsAString& aStorageName)
: mStorageType(aStorageType)
, mStorageName(aStorageName)
, mEditable(false)
, mLength(UINT64_MAX)
, mLastModifiedDate(UINT64_MAX)
{
Init();
}
void
DeviceStorageFile::Dump(const char* label)
{
nsString path;
if (mFile) {
mFile->GetPath(path);
} else {
path = NS_LITERAL_STRING("(null)");
}
const char* ptStr;
if (XRE_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();
}
void
DeviceStorageFile::SetEditable(bool aEditable) {
mEditable = aEditable;
}
// we want to make sure that the names of file can't reach
// outside of the type of storage the user asked for.
bool
DeviceStorageFile::IsSafePath()
{
return IsSafePath(mRootDir) && IsSafePath(mPath);
}
bool
DeviceStorageFile::IsSafePath(const nsAString& aPath)
{
nsAString::const_iterator start, end;
aPath.BeginReading(start);
aPath.EndReading(end);
// if the path is a '~' or starts with '~/', return false.
NS_NAMED_LITERAL_STRING(tilde, "~");
NS_NAMED_LITERAL_STRING(tildeSlash, "~/");
if (aPath.Equals(tilde) ||
StringBeginsWith(aPath, tildeSlash)) {
NS_WARNING("Path name starts with tilde!");
return false;
}
// split on /. if any token is "", ., or .., return false.
NS_ConvertUTF16toUTF8 cname(aPath);
char* buffer = cname.BeginWriting();
const char* token;
while ((token = nsCRT::strtok(buffer, "/", &buffer))) {
if (PL_strcmp(token, "") == 0 ||
PL_strcmp(token, ".") == 0 ||
PL_strcmp(token, "..") == 0 ) {
return false;
}
}
return true;
}
void
DeviceStorageFile::NormalizeFilePath() {
FileSystemUtils::LocalPathToNormalizedPath(mPath, mPath);
}
void
DeviceStorageFile::AppendRelativePath(const nsAString& aPath) {
if (!mFile) {
return;
}
if (!IsSafePath(aPath)) {
// All of the APIs (in the child) do checks to verify that the path is
// valid and return PERMISSION_DENIED if a non-safe path is entered.
// This check is done in the parent and prevents a compromised
// child from bypassing the check. It shouldn't be possible for this
// code path to be taken with a non-compromised child.
NS_WARNING("Unsafe path detected - ignoring");
NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get());
return;
}
nsString localPath;
FileSystemUtils::NormalizedPathToLocalPath(aPath, localPath);
mFile->AppendRelativePath(localPath);
}
nsresult
DeviceStorageFile::CreateFileDescriptor(FileDescriptor& aFileDescriptor)
{
if (!mFile) {
return NS_ERROR_FAILURE;
}
ScopedPRFileDesc fd;
nsresult rv = mFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE,
0660, &fd.rwget());
NS_ENSURE_SUCCESS(rv, rv);
// NOTE: The FileDescriptor::PlatformHandleType constructor returns a dup of
// the file descriptor, so we don't need the original fd after this.
// Our scoped file descriptor will automatically close fd.
aFileDescriptor = FileDescriptor(
FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd)));
return NS_OK;
}
nsresult
DeviceStorageFile::Write(nsIInputStream* aInputStream)
{
if (!aInputStream || !mFile) {
return NS_ERROR_FAILURE;
}
nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
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;
}
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 value(jsapi.cx(), 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();
}