gecko-dev/dom/devicestorage/nsDeviceStorage.h

438 lines
13 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/. */
#ifndef nsDeviceStorage_h
#define nsDeviceStorage_h
class nsPIDOMWindow;
#include "mozilla/Atomics.h"
#include "mozilla/Attributes.h"
#include "mozilla/Logging.h"
#include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
#include "DOMRequest.h"
#include "DOMCursor.h"
#include "nsAutoPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDOMClassInfoID.h"
#include "nsIClassInfo.h"
#include "nsIDOMWindow.h"
#include "nsIURI.h"
#include "nsIPrincipal.h"
#include "nsString.h"
#include "nsWeakPtr.h"
#include "nsIDOMEventListener.h"
#include "nsIObserver.h"
#include "nsIStringBundle.h"
#include "mozilla/Mutex.h"
#include "prtime.h"
#include "DeviceStorage.h"
#include "mozilla/StaticPtr.h"
namespace mozilla {
class ErrorResult;
namespace dom {
class BlobImpl;
class DeviceStorageParams;
} // namespace dom
} // namespace mozilla
class nsDOMDeviceStorage;
class DeviceStorageCursorRequest;
//#define DS_LOGGING 1
#ifdef DS_LOGGING
// FIXME -- use MOZ_LOG and set to warn by default
#define DS_LOG_DEBUG(msg, ...) printf_stderr("[%s:%d] " msg "\n", __func__, __LINE__, ##__VA_ARGS__)
#define DS_LOG_INFO DS_LOG_DEBUG
#define DS_LOG_WARN DS_LOG_DEBUG
#define DS_LOG_ERROR DS_LOG_DEBUG
#else
#define DS_LOG_DEBUG(msg, ...)
#define DS_LOG_INFO(msg, ...)
#define DS_LOG_WARN(msg, ...)
#define DS_LOG_ERROR(msg, ...)
#endif
#define POST_ERROR_EVENT_FILE_EXISTS "NoModificationAllowedError"
#define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST "NotFoundError"
#define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE "TypeMismatchError"
#define POST_ERROR_EVENT_PERMISSION_DENIED "SecurityError"
#define POST_ERROR_EVENT_ILLEGAL_TYPE "TypeMismatchError"
#define POST_ERROR_EVENT_UNKNOWN "Unknown"
enum DeviceStorageRequestType {
DEVICE_STORAGE_REQUEST_READ,
DEVICE_STORAGE_REQUEST_WRITE,
DEVICE_STORAGE_REQUEST_APPEND,
DEVICE_STORAGE_REQUEST_CREATE,
DEVICE_STORAGE_REQUEST_DELETE,
DEVICE_STORAGE_REQUEST_WATCH,
DEVICE_STORAGE_REQUEST_FREE_SPACE,
DEVICE_STORAGE_REQUEST_USED_SPACE,
DEVICE_STORAGE_REQUEST_AVAILABLE,
DEVICE_STORAGE_REQUEST_STATUS,
DEVICE_STORAGE_REQUEST_FORMAT,
DEVICE_STORAGE_REQUEST_MOUNT,
DEVICE_STORAGE_REQUEST_UNMOUNT,
DEVICE_STORAGE_REQUEST_CREATEFD,
DEVICE_STORAGE_REQUEST_CURSOR
};
enum DeviceStorageAccessType {
DEVICE_STORAGE_ACCESS_READ,
DEVICE_STORAGE_ACCESS_WRITE,
DEVICE_STORAGE_ACCESS_CREATE,
DEVICE_STORAGE_ACCESS_UNDEFINED,
DEVICE_STORAGE_ACCESS_COUNT
};
class DeviceStorageUsedSpaceCache final
{
public:
static DeviceStorageUsedSpaceCache* CreateOrGet();
DeviceStorageUsedSpaceCache();
~DeviceStorageUsedSpaceCache();
class InvalidateRunnable final : public nsRunnable
{
public:
InvalidateRunnable(DeviceStorageUsedSpaceCache* aCache,
const nsAString& aStorageName)
: mCache(aCache)
, mStorageName(aStorageName) {}
~InvalidateRunnable() {}
NS_IMETHOD Run() override
{
RefPtr<DeviceStorageUsedSpaceCache::CacheEntry> cacheEntry;
cacheEntry = mCache->GetCacheEntry(mStorageName);
if (cacheEntry) {
cacheEntry->mDirty = true;
}
return NS_OK;
}
private:
DeviceStorageUsedSpaceCache* mCache;
nsString mStorageName;
};
void Invalidate(const nsAString& aStorageName)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mIOThread);
mIOThread->Dispatch(new InvalidateRunnable(this, aStorageName),
NS_DISPATCH_NORMAL);
}
void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mIOThread);
mIOThread->Dispatch(mozilla::Move(aRunnable), NS_DISPATCH_NORMAL);
}
nsresult AccumUsedSizes(const nsAString& aStorageName,
uint64_t* aPictureSize, uint64_t* aVideosSize,
uint64_t* aMusicSize, uint64_t* aTotalSize);
void SetUsedSizes(const nsAString& aStorageName,
uint64_t aPictureSize, uint64_t aVideosSize,
uint64_t aMusicSize, uint64_t aTotalSize);
private:
friend class InvalidateRunnable;
struct CacheEntry
{
// Technically, this doesn't need to be threadsafe, but the implementation
// of the non-thread safe one causes ASSERTS due to the underlying thread
// associated with a LazyIdleThread changing from time to time.
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheEntry)
bool mDirty;
nsString mStorageName;
int64_t mFreeBytes;
uint64_t mPicturesUsedSize;
uint64_t mVideosUsedSize;
uint64_t mMusicUsedSize;
uint64_t mTotalUsedSize;
private:
~CacheEntry() {}
};
already_AddRefed<CacheEntry> GetCacheEntry(const nsAString& aStorageName);
nsTArray<RefPtr<CacheEntry>> mCacheEntries;
nsCOMPtr<nsIThread> mIOThread;
static mozilla::StaticAutoPtr<DeviceStorageUsedSpaceCache> sDeviceStorageUsedSpaceCache;
};
class DeviceStorageTypeChecker final
{
public:
static DeviceStorageTypeChecker* CreateOrGet();
DeviceStorageTypeChecker();
~DeviceStorageTypeChecker();
void InitFromBundle(nsIStringBundle* aBundle);
bool Check(const nsAString& aType, mozilla::dom::BlobImpl* aBlob);
bool Check(const nsAString& aType, nsIFile* aFile);
bool Check(const nsAString& aType, const nsString& aPath);
void GetTypeFromFile(nsIFile* aFile, nsAString& aType);
void GetTypeFromFileName(const nsAString& aFileName, nsAString& aType);
static nsresult GetPermissionForType(const nsAString& aType,
nsACString& aPermissionResult);
static nsresult GetAccessForRequest(const DeviceStorageRequestType aRequestType,
nsACString& aAccessResult);
static nsresult GetAccessForIndex(size_t aAccessIndex, nsACString& aAccessResult);
static size_t GetAccessIndexForRequest(const DeviceStorageRequestType aRequestType);
static bool IsVolumeBased(const nsAString& aType);
static bool IsSharedMediaRoot(const nsAString& aType);
private:
nsString mPicturesExtensions;
nsString mVideosExtensions;
nsString mMusicExtensions;
static mozilla::StaticAutoPtr<DeviceStorageTypeChecker> sDeviceStorageTypeChecker;
};
class nsDOMDeviceStorageCursor final
: public mozilla::dom::DOMCursor
{
public:
NS_FORWARD_NSIDOMDOMCURSOR(mozilla::dom::DOMCursor::)
// DOMCursor
virtual void Continue(mozilla::ErrorResult& aRv) override;
nsDOMDeviceStorageCursor(nsIGlobalObject* aGlobal,
DeviceStorageCursorRequest* aRequest);
void FireSuccess(JS::Handle<JS::Value> aResult);
void FireError(const nsString& aReason);
void FireDone();
private:
virtual ~nsDOMDeviceStorageCursor();
bool mOkToCallContinue;
RefPtr<DeviceStorageCursorRequest> mRequest;
};
class DeviceStorageRequestManager final
{
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DeviceStorageRequestManager)
static const uint32_t INVALID_ID = 0;
DeviceStorageRequestManager();
bool IsOwningThread();
nsresult DispatchToOwningThread(already_AddRefed<nsIRunnable>&& aRunnable);
void StorePermission(size_t aAccess, bool aAllow);
uint32_t CheckPermission(size_t aAccess);
/* These must be called on the owning thread context of the device
storage object. It will hold onto a device storage reference until
all of the pending requests are completed or shutdown is called. */
uint32_t Create(nsDOMDeviceStorage* aDeviceStorage,
mozilla::dom::DOMRequest** aRequest);
uint32_t Create(nsDOMDeviceStorage* aDeviceStorage,
DeviceStorageCursorRequest* aRequest,
nsDOMDeviceStorageCursor** aCursor);
/* These may be called from any thread context and post a request
to the owning thread to resolve the underlying DOMRequest or
DOMCursor. In order to trigger FireDone for a DOMCursor, one
should call Resolve with only the request ID. */
nsresult Resolve(uint32_t aId, bool aForceDispatch);
nsresult Resolve(uint32_t aId, const nsString& aValue, bool aForceDispatch);
nsresult Resolve(uint32_t aId, uint64_t aValue, bool aForceDispatch);
nsresult Resolve(uint32_t aId, DeviceStorageFile* aValue, bool aForceDispatch);
nsresult Resolve(uint32_t aId, mozilla::dom::BlobImpl* aValue, bool aForceDispatch);
nsresult Reject(uint32_t aId, const nsString& aReason);
nsresult Reject(uint32_t aId, const char* aReason);
void Shutdown();
private:
DeviceStorageRequestManager(const DeviceStorageRequestManager&) = delete;
DeviceStorageRequestManager& operator=(const DeviceStorageRequestManager&) = delete;
struct ListEntry {
RefPtr<mozilla::dom::DOMRequest> mRequest;
uint32_t mId;
bool mCursor;
};
typedef nsTArray<ListEntry> ListType;
typedef ListType::index_type ListIndex;
virtual ~DeviceStorageRequestManager();
uint32_t CreateInternal(mozilla::dom::DOMRequest* aRequest, bool aCursor);
nsresult ResolveInternal(ListIndex aIndex, JS::HandleValue aResult);
nsresult RejectInternal(ListIndex aIndex, const nsString& aReason);
nsresult DispatchOrAbandon(uint32_t aId,
already_AddRefed<nsIRunnable>&& aRunnable);
ListType::index_type Find(uint32_t aId);
nsCOMPtr<nsIThread> mOwningThread;
ListType mPending; // owning thread or destructor only
mozilla::Mutex mMutex;
uint32_t mPermissionCache[DEVICE_STORAGE_ACCESS_COUNT];
bool mShutdown;
static mozilla::Atomic<uint32_t> sLastRequestId;
};
class DeviceStorageRequest
: public nsRunnable
{
protected:
DeviceStorageRequest();
public:
virtual void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest);
virtual void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
mozilla::dom::BlobImpl* aBlob);
virtual void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
DeviceStorageFileDescriptor* aDSFileDescriptor);
DeviceStorageAccessType GetAccess() const;
void GetStorageType(nsAString& aType) const;
DeviceStorageFile* GetFile() const;
DeviceStorageFileDescriptor* GetFileDescriptor() const;
DeviceStorageRequestManager* GetManager() const;
uint32_t GetId() const
{
return mId;
}
void PermissionCacheMissed()
{
mPermissionCached = false;
}
nsresult Cancel();
nsresult Allow();
nsresult Resolve()
{
/* Always dispatch an empty resolve because that signals a cursor end
and should not be executed directly from the caller's context due
to the object potentially getting freed before we return. */
uint32_t id = mId;
mId = DeviceStorageRequestManager::INVALID_ID;
return mManager->Resolve(id, true);
}
template<class T>
nsresult Resolve(T aValue)
{
uint32_t id = mId;
if (!mMultipleResolve) {
mId = DeviceStorageRequestManager::INVALID_ID;
}
return mManager->Resolve(id, aValue, ForceDispatch());
}
template<class T>
nsresult Reject(T aReason)
{
uint32_t id = mId;
mId = DeviceStorageRequestManager::INVALID_ID;
return mManager->Reject(id, aReason);
}
protected:
bool ForceDispatch() const
{
return !mSendToParent && mPermissionCached;
}
virtual ~DeviceStorageRequest();
virtual nsresult Prepare();
virtual nsresult CreateSendParams(mozilla::dom::DeviceStorageParams& aParams);
nsresult AllowInternal();
nsresult SendToParentProcess();
RefPtr<DeviceStorageRequestManager> mManager;
RefPtr<DeviceStorageFile> mFile;
uint32_t mId;
RefPtr<mozilla::dom::BlobImpl> mBlob;
RefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
DeviceStorageAccessType mAccess;
bool mSendToParent;
bool mUseMainThread;
bool mUseStreamTransport;
bool mCheckFile;
bool mCheckBlob;
bool mMultipleResolve;
bool mPermissionCached;
private:
DeviceStorageRequest(const DeviceStorageRequest&) = delete;
DeviceStorageRequest& operator=(const DeviceStorageRequest&) = delete;
};
class DeviceStorageCursorRequest final
: public DeviceStorageRequest
{
public:
DeviceStorageCursorRequest();
using DeviceStorageRequest::Initialize;
virtual void Initialize(DeviceStorageRequestManager* aManager,
already_AddRefed<DeviceStorageFile>&& aFile,
uint32_t aRequest,
PRTime aSince);
void AddFiles(size_t aSize);
void AddFile(already_AddRefed<DeviceStorageFile> aFile);
nsresult Continue();
NS_IMETHOD Run() override;
protected:
virtual ~DeviceStorageCursorRequest()
{ };
nsresult SendContinueToParentProcess();
nsresult CreateSendParams(mozilla::dom::DeviceStorageParams& aParams) override;
size_t mIndex;
PRTime mSince;
nsString mStorageType;
nsTArray<RefPtr<DeviceStorageFile> > mFiles;
};
#endif