michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef nsDOMFile_h__ michael@0: #define nsDOMFile_h__ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "nsICharsetDetectionObserver.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIDOMFileList.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIJSNativeInitializer.h" michael@0: #include "nsIMutable.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsIXMLHttpRequest.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsFileStreams.h" michael@0: #include "nsTemporaryFileInputStream.h" michael@0: michael@0: #include "mozilla/GuardObjects.h" michael@0: #include "mozilla/LinkedList.h" michael@0: #include michael@0: #include "mozilla/StaticMutex.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/dom/DOMError.h" michael@0: #include "mozilla/dom/indexedDB/FileInfo.h" michael@0: #include "mozilla/dom/indexedDB/FileManager.h" michael@0: #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" michael@0: #include "nsWrapperCache.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: michael@0: class nsIFile; michael@0: class nsIInputStream; michael@0: class nsIClassInfo; michael@0: michael@0: class nsDOMFileBase : public nsIDOMFile, michael@0: public nsIXHRSendable, michael@0: public nsIMutable michael@0: { michael@0: public: michael@0: typedef mozilla::dom::indexedDB::FileInfo FileInfo; michael@0: michael@0: virtual already_AddRefed michael@0: CreateSlice(uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) = 0; michael@0: michael@0: virtual const nsTArray >* michael@0: GetSubBlobs() const { return nullptr; } michael@0: michael@0: NS_DECL_NSIDOMBLOB michael@0: NS_DECL_NSIDOMFILE michael@0: NS_DECL_NSIXHRSENDABLE michael@0: NS_DECL_NSIMUTABLE michael@0: michael@0: void michael@0: SetLazyData(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, uint64_t aLastModifiedDate) michael@0: { michael@0: NS_ASSERTION(aLength, "must have length"); michael@0: michael@0: mName = aName; michael@0: mContentType = aContentType; michael@0: mLength = aLength; michael@0: mLastModificationDate = aLastModifiedDate; michael@0: mIsFile = !aName.IsVoid(); michael@0: } michael@0: michael@0: bool IsSizeUnknown() const michael@0: { michael@0: return mLength == UINT64_MAX; michael@0: } michael@0: michael@0: bool IsDateUnknown() const michael@0: { michael@0: return mIsFile && mLastModificationDate == UINT64_MAX; michael@0: } michael@0: michael@0: protected: michael@0: nsDOMFileBase(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, uint64_t aLastModifiedDate) michael@0: : mIsFile(true), mImmutable(false), mContentType(aContentType), michael@0: mName(aName), mStart(0), mLength(aLength), mLastModificationDate(aLastModifiedDate) michael@0: { michael@0: // Ensure non-null mContentType by default michael@0: mContentType.SetIsVoid(false); michael@0: } michael@0: michael@0: nsDOMFileBase(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength) michael@0: : mIsFile(true), mImmutable(false), mContentType(aContentType), michael@0: mName(aName), mStart(0), mLength(aLength), mLastModificationDate(UINT64_MAX) michael@0: { michael@0: // Ensure non-null mContentType by default michael@0: mContentType.SetIsVoid(false); michael@0: } michael@0: michael@0: nsDOMFileBase(const nsAString& aContentType, uint64_t aLength) michael@0: : mIsFile(false), mImmutable(false), mContentType(aContentType), michael@0: mStart(0), mLength(aLength), mLastModificationDate(UINT64_MAX) michael@0: { michael@0: // Ensure non-null mContentType by default michael@0: mContentType.SetIsVoid(false); michael@0: } michael@0: michael@0: nsDOMFileBase(const nsAString& aContentType, uint64_t aStart, michael@0: uint64_t aLength) michael@0: : mIsFile(false), mImmutable(false), mContentType(aContentType), michael@0: mStart(aStart), mLength(aLength), mLastModificationDate(UINT64_MAX) michael@0: { michael@0: NS_ASSERTION(aLength != UINT64_MAX, michael@0: "Must know length when creating slice"); michael@0: // Ensure non-null mContentType by default michael@0: mContentType.SetIsVoid(false); michael@0: } michael@0: michael@0: virtual ~nsDOMFileBase() {} michael@0: michael@0: virtual bool IsStoredFile() const michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: virtual bool IsWholeFile() const michael@0: { michael@0: NS_NOTREACHED("Should only be called on dom blobs backed by files!"); michael@0: return false; michael@0: } michael@0: michael@0: virtual bool IsSnapshot() const michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: FileInfo* GetFileInfo() const michael@0: { michael@0: NS_ASSERTION(IsStoredFile(), "Should only be called on stored files!"); michael@0: NS_ASSERTION(!mFileInfos.IsEmpty(), "Must have at least one file info!"); michael@0: michael@0: return mFileInfos.ElementAt(0); michael@0: } michael@0: michael@0: bool mIsFile; michael@0: bool mImmutable; michael@0: michael@0: nsString mContentType; michael@0: nsString mName; michael@0: nsString mPath; // The path relative to a directory chosen by the user michael@0: michael@0: uint64_t mStart; michael@0: uint64_t mLength; michael@0: michael@0: uint64_t mLastModificationDate; michael@0: michael@0: // Protected by IndexedDatabaseManager::FileMutex() michael@0: nsTArray > mFileInfos; michael@0: }; michael@0: michael@0: class nsDOMFile : public nsDOMFileBase michael@0: { michael@0: public: michael@0: nsDOMFile(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, uint64_t aLastModifiedDate) michael@0: : nsDOMFileBase(aName, aContentType, aLength, aLastModifiedDate) michael@0: { } michael@0: michael@0: nsDOMFile(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength) michael@0: : nsDOMFileBase(aName, aContentType, aLength) michael@0: { } michael@0: michael@0: nsDOMFile(const nsAString& aContentType, uint64_t aLength) michael@0: : nsDOMFileBase(aContentType, aLength) michael@0: { } michael@0: michael@0: nsDOMFile(const nsAString& aContentType, uint64_t aStart, uint64_t aLength) michael@0: : nsDOMFileBase(aContentType, aStart, aLength) michael@0: { } michael@0: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: }; michael@0: michael@0: class nsDOMFileCC : public nsDOMFileBase michael@0: { michael@0: public: michael@0: nsDOMFileCC(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength) michael@0: : nsDOMFileBase(aName, aContentType, aLength) michael@0: { } michael@0: michael@0: nsDOMFileCC(const nsAString& aContentType, uint64_t aLength) michael@0: : nsDOMFileBase(aContentType, aLength) michael@0: { } michael@0: michael@0: nsDOMFileCC(const nsAString& aContentType, uint64_t aStart, uint64_t aLength) michael@0: : nsDOMFileBase(aContentType, aStart, aLength) michael@0: { } michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: michael@0: NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsDOMFileCC, nsIDOMFile) michael@0: }; michael@0: michael@0: class nsDOMFileFile : public nsDOMFile michael@0: { michael@0: public: michael@0: // Create as a file michael@0: nsDOMFileFile(nsIFile *aFile) michael@0: : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(false) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: // Lazily get the content type and size michael@0: mContentType.SetIsVoid(true); michael@0: mFile->GetLeafName(mName); michael@0: } michael@0: michael@0: nsDOMFileFile(nsIFile *aFile, FileInfo *aFileInfo) michael@0: : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(true) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: NS_ASSERTION(aFileInfo, "must have file info"); michael@0: // Lazily get the content type and size michael@0: mContentType.SetIsVoid(true); michael@0: mFile->GetLeafName(mName); michael@0: michael@0: mFileInfos.AppendElement(aFileInfo); michael@0: } michael@0: michael@0: // Create as a file michael@0: nsDOMFileFile(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, nsIFile *aFile) michael@0: : nsDOMFile(aName, aContentType, aLength, UINT64_MAX), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(false) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: } michael@0: michael@0: nsDOMFileFile(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, nsIFile *aFile, uint64_t aLastModificationDate) michael@0: : nsDOMFile(aName, aContentType, aLength, aLastModificationDate), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(false) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: } michael@0: michael@0: // Create as a file with custom name michael@0: nsDOMFileFile(nsIFile *aFile, const nsAString& aName, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aName, aContentType, UINT64_MAX, UINT64_MAX), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(false) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: if (aContentType.IsEmpty()) { michael@0: // Lazily get the content type and size michael@0: mContentType.SetIsVoid(true); michael@0: } michael@0: } michael@0: michael@0: // Create as a stored file michael@0: nsDOMFileFile(const nsAString& aName, const nsAString& aContentType, michael@0: uint64_t aLength, nsIFile* aFile, michael@0: FileInfo* aFileInfo) michael@0: : nsDOMFile(aName, aContentType, aLength, UINT64_MAX), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(true) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: mFileInfos.AppendElement(aFileInfo); michael@0: } michael@0: michael@0: // Create as a stored blob michael@0: nsDOMFileFile(const nsAString& aContentType, uint64_t aLength, michael@0: nsIFile* aFile, FileInfo* aFileInfo) michael@0: : nsDOMFile(aContentType, aLength), michael@0: mFile(aFile), mWholeFile(true), mStoredFile(true) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: mFileInfos.AppendElement(aFileInfo); michael@0: } michael@0: michael@0: // Create as a file to be later initialized michael@0: nsDOMFileFile() michael@0: : nsDOMFile(EmptyString(), EmptyString(), UINT64_MAX, UINT64_MAX), michael@0: mWholeFile(true), mStoredFile(false) michael@0: { michael@0: // Lazily get the content type and size michael@0: mContentType.SetIsVoid(true); michael@0: mName.SetIsVoid(true); michael@0: } michael@0: michael@0: // Overrides michael@0: NS_IMETHOD GetSize(uint64_t* aSize) MOZ_OVERRIDE; michael@0: NS_IMETHOD GetType(nsAString& aType) MOZ_OVERRIDE; michael@0: NS_IMETHOD GetLastModifiedDate(JSContext* cx, JS::MutableHandle aLastModifiedDate) MOZ_OVERRIDE; michael@0: NS_IMETHOD GetMozLastModifiedDate(uint64_t* aLastModifiedDate) MOZ_OVERRIDE; michael@0: NS_IMETHOD GetMozFullPathInternal(nsAString& aFullPath) MOZ_OVERRIDE; michael@0: NS_IMETHOD GetInternalStream(nsIInputStream**) MOZ_OVERRIDE; michael@0: michael@0: void SetPath(const nsAString& aFullPath); michael@0: michael@0: protected: michael@0: // Create slice michael@0: nsDOMFileFile(const nsDOMFileFile* aOther, uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aContentType, aOther->mStart + aStart, aLength), michael@0: mFile(aOther->mFile), mWholeFile(false), michael@0: mStoredFile(aOther->mStoredFile) michael@0: { michael@0: NS_ASSERTION(mFile, "must have file"); michael@0: mImmutable = aOther->mImmutable; michael@0: michael@0: if (mStoredFile) { michael@0: FileInfo* fileInfo; michael@0: michael@0: using mozilla::dom::indexedDB::IndexedDatabaseManager; michael@0: michael@0: if (IndexedDatabaseManager::IsClosed()) { michael@0: fileInfo = aOther->GetFileInfo(); michael@0: } michael@0: else { michael@0: mozilla::MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); michael@0: fileInfo = aOther->GetFileInfo(); michael@0: } michael@0: michael@0: mFileInfos.AppendElement(fileInfo); michael@0: } michael@0: } michael@0: michael@0: virtual already_AddRefed michael@0: CreateSlice(uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) MOZ_OVERRIDE; michael@0: michael@0: virtual bool IsStoredFile() const MOZ_OVERRIDE michael@0: { michael@0: return mStoredFile; michael@0: } michael@0: michael@0: virtual bool IsWholeFile() const MOZ_OVERRIDE michael@0: { michael@0: return mWholeFile; michael@0: } michael@0: michael@0: nsCOMPtr mFile; michael@0: bool mWholeFile; michael@0: bool mStoredFile; michael@0: }; michael@0: michael@0: /** michael@0: * This class may be used off the main thread, and in particular, its michael@0: * constructor and destructor may not run on the same thread. Be careful! michael@0: */ michael@0: class nsDOMMemoryFile : public nsDOMFile michael@0: { michael@0: public: michael@0: // Create as file michael@0: nsDOMMemoryFile(void *aMemoryBuffer, michael@0: uint64_t aLength, michael@0: const nsAString& aName, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aName, aContentType, aLength, UINT64_MAX), michael@0: mDataOwner(new DataOwner(aMemoryBuffer, aLength)) michael@0: { michael@0: NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data"); michael@0: } michael@0: michael@0: // Create as blob michael@0: nsDOMMemoryFile(void *aMemoryBuffer, michael@0: uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aContentType, aLength), michael@0: mDataOwner(new DataOwner(aMemoryBuffer, aLength)) michael@0: { michael@0: NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data"); michael@0: } michael@0: michael@0: NS_IMETHOD GetInternalStream(nsIInputStream**) MOZ_OVERRIDE; michael@0: michael@0: NS_IMETHOD_(bool) IsMemoryFile(void) MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: // Create slice michael@0: nsDOMMemoryFile(const nsDOMMemoryFile* aOther, uint64_t aStart, michael@0: uint64_t aLength, const nsAString& aContentType) michael@0: : nsDOMFile(aContentType, aOther->mStart + aStart, aLength), michael@0: mDataOwner(aOther->mDataOwner) michael@0: { michael@0: NS_ASSERTION(mDataOwner && mDataOwner->mData, "must have data"); michael@0: mImmutable = aOther->mImmutable; michael@0: } michael@0: virtual already_AddRefed michael@0: CreateSlice(uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) MOZ_OVERRIDE; michael@0: michael@0: // These classes need to see DataOwner. michael@0: friend class DataOwnerAdapter; michael@0: friend class nsDOMMemoryFileDataOwnerMemoryReporter; michael@0: michael@0: class DataOwner MOZ_FINAL : public mozilla::LinkedListElement { michael@0: public: michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataOwner) michael@0: DataOwner(void* aMemoryBuffer, uint64_t aLength) michael@0: : mData(aMemoryBuffer) michael@0: , mLength(aLength) michael@0: { michael@0: mozilla::StaticMutexAutoLock lock(sDataOwnerMutex); michael@0: michael@0: if (!sDataOwners) { michael@0: sDataOwners = new mozilla::LinkedList(); michael@0: EnsureMemoryReporterRegistered(); michael@0: } michael@0: sDataOwners->insertBack(this); michael@0: } michael@0: michael@0: private: michael@0: // Private destructor, to discourage deletion outside of Release(): michael@0: ~DataOwner() { michael@0: mozilla::StaticMutexAutoLock lock(sDataOwnerMutex); michael@0: michael@0: remove(); michael@0: if (sDataOwners->isEmpty()) { michael@0: // Free the linked list if it's empty. michael@0: sDataOwners = nullptr; michael@0: } michael@0: michael@0: moz_free(mData); michael@0: } michael@0: michael@0: public: michael@0: static void EnsureMemoryReporterRegistered(); michael@0: michael@0: // sDataOwners and sMemoryReporterRegistered may only be accessed while michael@0: // holding sDataOwnerMutex! You also must hold the mutex while touching michael@0: // elements of the linked list that DataOwner inherits from. michael@0: static mozilla::StaticMutex sDataOwnerMutex; michael@0: static mozilla::StaticAutoPtr > sDataOwners; michael@0: static bool sMemoryReporterRegistered; michael@0: michael@0: void* mData; michael@0: uint64_t mLength; michael@0: }; michael@0: michael@0: // Used when backed by a memory store michael@0: nsRefPtr mDataOwner; michael@0: }; michael@0: michael@0: class nsDOMFileList MOZ_FINAL : public nsIDOMFileList, michael@0: public nsWrapperCache michael@0: { michael@0: public: michael@0: nsDOMFileList(nsISupports *aParent) : mParent(aParent) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMFileList) michael@0: michael@0: NS_DECL_NSIDOMFILELIST michael@0: michael@0: virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE; michael@0: michael@0: nsISupports* GetParentObject() michael@0: { michael@0: return mParent; michael@0: } michael@0: michael@0: void Disconnect() michael@0: { michael@0: mParent = nullptr; michael@0: } michael@0: michael@0: bool Append(nsIDOMFile *aFile) { return mFiles.AppendObject(aFile); } michael@0: michael@0: bool Remove(uint32_t aIndex) { return mFiles.RemoveObjectAt(aIndex); } michael@0: void Clear() { return mFiles.Clear(); } michael@0: michael@0: static nsDOMFileList* FromSupports(nsISupports* aSupports) michael@0: { michael@0: #ifdef DEBUG michael@0: { michael@0: nsCOMPtr list_qi = do_QueryInterface(aSupports); michael@0: michael@0: // If this assertion fires the QI implementation for the object in michael@0: // question doesn't use the nsIDOMFileList pointer as the nsISupports michael@0: // pointer. That must be fixed, or we'll crash... michael@0: NS_ASSERTION(list_qi == static_cast(aSupports), michael@0: "Uh, fix QI!"); michael@0: } michael@0: #endif michael@0: michael@0: return static_cast(aSupports); michael@0: } michael@0: michael@0: nsIDOMFile* Item(uint32_t aIndex) michael@0: { michael@0: return mFiles.SafeObjectAt(aIndex); michael@0: } michael@0: nsIDOMFile* IndexedGetter(uint32_t aIndex, bool& aFound) michael@0: { michael@0: aFound = aIndex < static_cast(mFiles.Count()); michael@0: return aFound ? mFiles.ObjectAt(aIndex) : nullptr; michael@0: } michael@0: uint32_t Length() michael@0: { michael@0: return mFiles.Count(); michael@0: } michael@0: michael@0: private: michael@0: nsCOMArray mFiles; michael@0: nsISupports *mParent; michael@0: }; michael@0: michael@0: class MOZ_STACK_CLASS nsDOMFileInternalUrlHolder { michael@0: public: michael@0: nsDOMFileInternalUrlHolder(nsIDOMBlob* aFile, nsIPrincipal* aPrincipal michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM); michael@0: ~nsDOMFileInternalUrlHolder(); michael@0: nsAutoString mUrl; michael@0: private: michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: }; michael@0: // This class would take the ownership of aFD and the caller must not close it. michael@0: class nsDOMTemporaryFileBlob : public nsDOMFile michael@0: { michael@0: public: michael@0: nsDOMTemporaryFileBlob(PRFileDesc* aFD, uint64_t aStartPos, uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aContentType, aLength), michael@0: mLength(aLength), michael@0: mStartPos(aStartPos), michael@0: mContentType(aContentType) michael@0: { michael@0: mFileDescOwner = new nsTemporaryFileInputStream::FileDescOwner(aFD); michael@0: } michael@0: michael@0: ~nsDOMTemporaryFileBlob() { } michael@0: NS_IMETHOD GetInternalStream(nsIInputStream**) MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: nsDOMTemporaryFileBlob(const nsDOMTemporaryFileBlob* aOther, uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) michael@0: : nsDOMFile(aContentType, aLength), michael@0: mLength(aLength), michael@0: mStartPos(aStart), michael@0: mFileDescOwner(aOther->mFileDescOwner), michael@0: mContentType(aContentType) { } michael@0: michael@0: virtual already_AddRefed michael@0: CreateSlice(uint64_t aStart, uint64_t aLength, michael@0: const nsAString& aContentType) MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: uint64_t mLength; michael@0: uint64_t mStartPos; michael@0: nsRefPtr mFileDescOwner; michael@0: nsString mContentType; michael@0: }; michael@0: michael@0: #endif