diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index d020fb68f8df..a1d85e0f6cbf 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -164,6 +164,7 @@ @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt +@BINPATH@/components/dom_devicestorage.xpt @BINPATH@/components/dom_events.xpt @BINPATH@/components/dom_geolocation.xpt @BINPATH@/components/dom_network.xpt diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 89d8e46d95fb..60ffff8315b0 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -170,6 +170,7 @@ @BINPATH@/components/dom_contacts.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt +@BINPATH@/components/dom_devicestorage.xpt @BINPATH@/components/dom_events.xpt @BINPATH@/components/dom_geolocation.xpt @BINPATH@/components/dom_network.xpt diff --git a/browser/installer/removed-files.in b/browser/installer/removed-files.in index fac098a8b82f..02480c2cbbf9 100644 --- a/browser/installer/removed-files.in +++ b/browser/installer/removed-files.in @@ -1179,6 +1179,7 @@ xpicleanup@BIN_SUFFIX@ components/dom_css.xpt components/dom_events.xpt components/dom_geolocation.xpt + components/dom_devicestorage.xpt components/dom_html.xpt components/dom_json.xpt components/dom_loadsave.xpt diff --git a/content/base/public/nsDOMFile.h b/content/base/public/nsDOMFile.h index 9aa455271bce..ce7d209e3e3b 100644 --- a/content/base/public/nsDOMFile.h +++ b/content/base/public/nsDOMFile.h @@ -132,7 +132,7 @@ public: // Create as a blob nsDOMFileFile(nsIFile *aFile, const nsAString& aContentType, - nsISupports *aCacheToken = nsnull) + nsISupports *aCacheToken) : nsDOMFileBase(aContentType, UINT64_MAX), mFile(aFile), mWholeFile(true), mStoredFile(false), mCacheToken(aCacheToken) @@ -140,6 +140,17 @@ public: NS_ASSERTION(mFile, "must have file"); } + // Create as a file with custom name + nsDOMFileFile(nsIFile *aFile, const nsAString& aName) + : nsDOMFileBase(EmptyString(), EmptyString(), UINT64_MAX), + mFile(aFile), mWholeFile(true), mStoredFile(false) + { + NS_ASSERTION(mFile, "must have file"); + // Lazily get the content type and size + mContentType.SetIsVoid(true); + mName.Assign(aName); + } + // Create as a stored file nsDOMFileFile(const nsAString& aName, const nsAString& aContentType, PRUint64 aLength, nsIFile* aFile, diff --git a/dom/Makefile.in b/dom/Makefile.in index 59f99ebae7e4..13c87a5b2733 100644 --- a/dom/Makefile.in +++ b/dom/Makefile.in @@ -16,6 +16,7 @@ DIRS = \ interfaces/base \ interfaces/canvas \ interfaces/core \ + interfaces/devicestorage \ interfaces/html \ interfaces/events \ interfaces/contacts \ @@ -48,6 +49,7 @@ DIRS += \ bindings \ battery \ contacts \ + devicestorage \ power \ settings \ sms \ diff --git a/dom/base/DOMRequest.h b/dom/base/DOMRequest.h index 17e1be8cbba5..bc5df35b4454 100644 --- a/dom/base/DOMRequest.h +++ b/dom/base/DOMRequest.h @@ -20,11 +20,6 @@ namespace dom { class DOMRequest : public nsDOMEventTargetHelper, public nsIDOMDOMRequest { - bool mDone; - jsval mResult; - nsCOMPtr mError; - bool mRooted; - NS_DECL_EVENT_HANDLER(success) NS_DECL_EVENT_HANDLER(error) @@ -46,6 +41,11 @@ public: UnrootResultVal(); } + bool mDone; + jsval mResult; + nsCOMPtr mError; + bool mRooted; + private: void FireEvent(const nsAString& aType); diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index 86d133316892..22278c0d868b 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -13,6 +13,7 @@ #include "nsMimeTypeArray.h" #include "nsDesktopNotification.h" #include "nsGeolocation.h" +#include "nsDeviceStorage.h" #include "nsIHttpProtocolHandler.h" #include "nsICachingChannel.h" #include "nsIDocShell.h" @@ -89,6 +90,7 @@ NS_INTERFACE_MAP_BEGIN(Navigator) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNavigator) NS_INTERFACE_MAP_ENTRY(nsIDOMNavigator) NS_INTERFACE_MAP_ENTRY(nsIDOMClientInformation) + NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDeviceStorage) NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorGeolocation) NS_INTERFACE_MAP_ENTRY(nsIDOMMozNavigatorBattery) NS_INTERFACE_MAP_ENTRY(nsIDOMNavigatorDesktopNotification) @@ -839,6 +841,26 @@ Navigator::MozIsLocallyAvailable(const nsAString &aURI, return httpChannel->GetRequestSucceeded(aIsAvailable); } +//***************************************************************************** +// Navigator::nsIDOMNavigatorDeviceStorage +//***************************************************************************** + +NS_IMETHODIMP Navigator::GetDeviceStorage(const nsAString &aType, nsIVariant** _retval) +{ + if (!Preferences::GetBool("device.storage.enabled", false)) { + return NS_OK; + } + + nsCOMPtr win(do_QueryReferent(mWindow)); + + if (!win || !win->GetOuterWindow() || !win->GetDocShell()) { + return NS_ERROR_FAILURE; + } + + nsDOMDeviceStorage::CreateDeviceStoragesFor(win, aType, _retval); + return NS_OK; +} + //***************************************************************************** // Navigator::nsIDOMNavigatorGeolocation //***************************************************************************** diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index b56f98841ff1..794f26b63f01 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -9,6 +9,7 @@ #include "nsIDOMNavigator.h" #include "nsIDOMNavigatorGeolocation.h" +#include "nsIDOMNavigatorDeviceStorage.h" #include "nsIDOMNavigatorDesktopNotification.h" #include "nsIDOMClientInformation.h" #include "nsIDOMNavigatorBattery.h" @@ -60,6 +61,7 @@ class PowerManager; class Navigator : public nsIDOMNavigator , public nsIDOMClientInformation + , public nsIDOMNavigatorDeviceStorage , public nsIDOMNavigatorGeolocation , public nsIDOMNavigatorDesktopNotification , public nsIDOMMozNavigatorBattery @@ -79,6 +81,7 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIDOMNAVIGATOR NS_DECL_NSIDOMCLIENTINFORMATION + NS_DECL_NSIDOMNAVIGATORDEVICESTORAGE NS_DECL_NSIDOMNAVIGATORGEOLOCATION NS_DECL_NSIDOMNAVIGATORDESKTOPNOTIFICATION NS_DECL_NSIDOMMOZNAVIGATORBATTERY diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 72663f3ab5af..4e49985c7662 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -414,6 +414,10 @@ // Storage includes #include "nsDOMStorage.h" +// Device Storage +#include "nsIDOMDeviceStorage.h" +#include "nsIDOMDeviceStorageCursor.h" + // Drag and drop #include "nsIDOMDataTransfer.h" @@ -434,6 +438,7 @@ #include "nsIDOMDesktopNotification.h" #include "nsIDOMNavigatorDesktopNotification.h" +#include "nsIDOMNavigatorDeviceStorage.h" #include "nsIDOMNavigatorGeolocation.h" #include "Navigator.h" @@ -1394,6 +1399,12 @@ static nsDOMClassInfoData sClassInfoData[] = { NS_DEFINE_CLASSINFO_DATA(MessageEvent, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(DeviceStorage, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + + NS_DEFINE_CLASSINFO_DATA(DeviceStorageCursor, nsDOMGenericSH, + DOM_DEFAULT_SCRIPTABLE_FLAGS) + NS_DEFINE_CLASSINFO_DATA(GeoGeolocation, nsDOMGenericSH, DOM_DEFAULT_SCRIPTABLE_FLAGS) @@ -2431,6 +2442,7 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_MAP_BEGIN(Navigator, nsIDOMNavigator) DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigator) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorDeviceStorage) DOM_CLASSINFO_MAP_ENTRY(nsIDOMNavigatorGeolocation) DOM_CLASSINFO_MAP_CONDITIONAL_ENTRY(nsIDOMNavigatorDesktopNotification, Navigator::HasDesktopNotificationSupport()) @@ -4034,6 +4046,16 @@ nsDOMClassInfo::Init() DOM_CLASSINFO_EVENT_MAP_ENTRIES DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(DeviceStorage, nsIDOMDeviceStorage) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorage) + DOM_CLASSINFO_MAP_END + + DOM_CLASSINFO_MAP_BEGIN(DeviceStorageCursor, nsIDOMDeviceStorageCursor) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageCursor) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest) + DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget) + DOM_CLASSINFO_MAP_END + DOM_CLASSINFO_MAP_BEGIN(GeoGeolocation, nsIDOMGeoGeolocation) DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoGeolocation) DOM_CLASSINFO_MAP_END diff --git a/dom/base/nsDOMClassInfoClasses.h b/dom/base/nsDOMClassInfoClasses.h index 8a6002685a33..89c6ad468f04 100644 --- a/dom/base/nsDOMClassInfoClasses.h +++ b/dom/base/nsDOMClassInfoClasses.h @@ -387,6 +387,9 @@ DOMCI_CLASS(DataContainerEvent) // HTML5 DOMCI_CLASS(MessageEvent) +DOMCI_CLASS(DeviceStorage) +DOMCI_CLASS(DeviceStorageCursor) + // Geolocation DOMCI_CLASS(GeoGeolocation) DOMCI_CLASS(GeoPosition) diff --git a/dom/devicestorage/Makefile.in b/dom/devicestorage/Makefile.in new file mode 100644 index 000000000000..be8b0f586607 --- /dev/null +++ b/dom/devicestorage/Makefile.in @@ -0,0 +1,40 @@ +# 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/. + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +LIBRARY_NAME = domdevicestorage_s +XPIDL_MODULE = dom_devicestorage +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/dom/dom-config.mk + +CPPSRCS = \ + nsDeviceStorage.cpp \ + $(NULL) + +EXPORTS = \ + nsDeviceStorage.h \ + $(NULL) + +LOCAL_INCLUDES = \ + -I$(topsrcdir)/dom/base \ + -I$(topsrcdir)/dom/ipc \ + -I$(topsrcdir)/content/base/src \ + -I$(topsrcdir)/content/events/src \ + $(NULL) + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/ipc/chromium/chromium-config.mk +include $(topsrcdir)/config/rules.mk + +DEFINES += -D_IMPL_NS_LAYOUT + diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp new file mode 100644 index 000000000000..4cf885c1e4c0 --- /dev/null +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -0,0 +1,1186 @@ +/* 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 "DOMRequest.h" +#include "nsServiceManagerUtils.h" +#include "nsILocalFile.h" +#include "nsIDirectoryEnumerator.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsIDOMFile.h" +#include "nsDOMBlobBuilder.h" +#include "nsNetUtil.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIContentPermissionPrompt.h" +#include "nsIPrincipal.h" +#include "mozilla/Preferences.h" +#include "nsJSUtils.h" + +using namespace mozilla::dom; + +#include "nsDirectoryServiceDefs.h" + +class DeviceStorageFile : public nsISupports { +public: + DeviceStorageFile(nsIFile* aFile, const nsAString& aPath) + : mFile(aFile) + , mPath(aPath) + { + NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile"); + NormalizeFilePath(); + } + DeviceStorageFile(nsIFile* aFile) + : mFile(aFile) + { + NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile"); + } + nsCOMPtr mFile; + nsString mPath; + + NS_DECL_ISUPPORTS + +private: + void NormalizeFilePath() { +#if defined(XP_WIN) + PRUnichar* cur = mPath.BeginWriting(); + PRUnichar* end = mPath.EndWriting(); + for (; cur < end; ++cur) { + if (PRUnichar('\\') == *cur) + *cur = PRUnichar('/'); + } +#endif + } + +}; + +NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile) + +// we want to make sure that the names of file can't reach +// outside of the type of storage the user asked for. +bool +isSafePath(const nsAString& aPath) +{ + nsAString::const_iterator start, end; + aPath.BeginReading(start); + aPath.EndReading(end); + + // if the path has a ~ or \ in it, return false. + NS_NAMED_LITERAL_STRING(tilde, "~"); + NS_NAMED_LITERAL_STRING(bslash, "\\"); + if (FindInReadable(tilde, start, end) || + FindInReadable(bslash, start, end)) { + 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; +} + +static void AppendRelativePath(nsIFile* file, const nsAString& aPath) { + +#if defined(XP_WIN) + // replace forward slashes with backslashes, + // since nsLocalFileWin chokes on them + nsString temp; + temp.Assign(aPath); + + PRUnichar* cur = temp.BeginWriting(); + PRUnichar* end = temp.EndWriting(); + + for (; cur < end; ++cur) { + if (PRUnichar('/') == *cur) + *cur = PRUnichar('\\'); + } + file->AppendRelativePath(temp); +#else + file->AppendRelativePath(aPath); +#endif +} + +// TODO - eventually, we will want to factor this method +// out into different system specific subclasses (or +// something) +PRInt32 +nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType, const PRInt32 aIndex) +{ + PRInt32 typeResult = DEVICE_STORAGE_TYPE_DEFAULT; + + nsCOMPtr f; + nsCOMPtr dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + NS_ASSERTION(dirService, "Must have directory service"); + + // Picture directory + if (aType.Equals(NS_LITERAL_STRING("pictures"))) { +#ifdef MOZ_WIDGET_GONK + if (aIndex == 0) { + NS_NewLocalFile(NS_LITERAL_STRING("/data/pictures"), false, getter_AddRefs(f)); + } + else if (aIndex == 1) { + NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/DCIM"), false, getter_AddRefs(f)); + typeResult = DEVICE_STORAGE_TYPE_EXTERNAL; + } +#elif defined (MOZ_WIDGET_COCOA) + if (aIndex == 0) { + dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f)); + } +#elif defined (XP_UNIX) + if (aIndex == 0) { + dirService->Get(NS_UNIX_XDG_PICTURES_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f)); + } +#endif + } + + // in testing, we have access to a few more directory locations + if (mozilla::Preferences::GetBool("device.storage.testing", false)) { + + // Temp directory + if (aType.Equals(NS_LITERAL_STRING("temp")) && aIndex == 0) { + dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f)); + } + + // Profile directory + else if (aType.Equals(NS_LITERAL_STRING("profile")) && aIndex == 0) { + dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(f)); + } + } + + mFile = f; + return typeResult; +} + +static jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile, bool aEditable) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWindow, "Null Window"); + + if (aEditable) { + // TODO - needs janv's file handle support. + return JSVAL_NULL; + } + + if (aFile == nsnull) { + return JSVAL_NULL; + } + + nsCOMPtr blob = new nsDOMFileFile(aFile->mFile, aFile->mPath); + + nsCOMPtr sgo = do_QueryInterface(aWindow); + if (!sgo) { + return JSVAL_NULL; + } + + nsIScriptContext *scriptContext = sgo->GetScriptContext(); + if (!scriptContext) { + return JSVAL_NULL; + } + + JSContext *cx = scriptContext->GetNativeContext(); + if (!cx) { + return JSVAL_NULL; + } + + jsval wrappedFile; + nsresult rv = nsContentUtils::WrapNative(cx, + JS_GetGlobalObject(cx), + blob, + &NS_GET_IID(nsIDOMFile), + &wrappedFile); + if (NS_FAILED(rv)) { + return JSVAL_NULL; + } + + return wrappedFile; +} + + +static jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(aWindow, "Null Window"); + + nsCOMPtr sgo = do_QueryInterface(aWindow); + if (!sgo) { + return JSVAL_NULL; + } + + nsIScriptContext *scriptContext = sgo->GetScriptContext(); + if (!scriptContext) { + return JSVAL_NULL; + } + + JSContext *cx = scriptContext->GetNativeContext(); + if (!cx) { + return JSVAL_NULL; + } + + JSAutoRequest ar(cx); + + jsval result = JSVAL_NULL; + if (!xpc::StringToJsval(cx, aString, &result)) { + return JSVAL_NULL; + } + + return result; +} + + +class nsDOMDeviceStorageCursor + : public nsIDOMDeviceStorageCursor + , public DOMRequest + , public nsIContentPermissionRequest +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICONTENTPERMISSIONREQUEST + NS_DECL_NSIDOMDEVICESTORAGECURSOR + + nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow, + nsIURI* aURI, + DeviceStorageFile* aFile, + bool aEditable); + +private: + ~nsDOMDeviceStorageCursor(); + +protected: + nsTArray > mFiles; + + bool mOkToCallContinue; + nsRefPtr mFile; + nsCOMPtr mURI; + bool mEditable; + + // to access mFiles + friend class InitCursorEvent; + friend class ContinueCursorEvent; +}; + +#define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST "File location doesn't exists" +#define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE "File location is not enumerable" +#define POST_ERROR_EVENT_PERMISSION_DENIED "Permission Denied" +#define POST_ERROR_EVENT_ILLEGAL_FILE_NAME "Illegal file name" +#define POST_ERROR_EVENT_UNKNOWN "Unknown" +#define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported" + +class PostErrorEvent : public nsRunnable +{ +public: + PostErrorEvent(nsRefPtr& aRequest, const char* aMessage, DeviceStorageFile* aFile) + { + mRequest.swap(aRequest); + BuildErrorString(aMessage, aFile); + } + + PostErrorEvent(DOMRequest* aRequest, const char* aMessage, DeviceStorageFile* aFile) + : mRequest(aRequest) + { + BuildErrorString(aMessage, aFile); + } + + ~PostErrorEvent() {} + + void BuildErrorString(const char* aMessage, DeviceStorageFile* aFile) + { + nsAutoString fullPath; + + if (aFile && aFile->mFile) { + aFile->mFile->GetPath(fullPath); + } + else { + fullPath.Assign(NS_LITERAL_STRING("null file")); + } + + mError = NS_ConvertASCIItoUTF16(aMessage); + mError.Append(NS_LITERAL_STRING(" file path = ")); + mError.Append(fullPath.get()); + mError.Append(NS_LITERAL_STRING(" path = ")); + + if (aFile) { + mError.Append(aFile->mPath); + } + else { + mError.Append(NS_LITERAL_STRING("null path")); + } + } + + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + mRequest->FireError(mError); + mRequest = nsnull; + return NS_OK; + } + +private: + nsRefPtr mRequest; + nsString mError; +}; + +class ContinueCursorEvent : public nsRunnable +{ +public: + + ContinueCursorEvent(nsRefPtr& aRequest) + { + mRequest.swap(aRequest); + } + + ContinueCursorEvent(DOMRequest* aRequest) + : mRequest(aRequest) + { + } + + ~ContinueCursorEvent() {} + + NS_IMETHOD Run() { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + jsval val; + + nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); + if (cursor->mFiles.Length() == 0) { + val = JSVAL_NULL; + } + else { + nsRefPtr file = cursor->mFiles[0]; + cursor->mFiles.RemoveElementAt(0); + val = nsIFileToJsval(cursor->GetOwner(), file, cursor->mEditable); + cursor->mOkToCallContinue = true; + } + + mRequest->FireSuccess(val); + mRequest = nsnull; + return NS_OK; + } + +private: + nsRefPtr mRequest; +}; + + +class InitCursorEvent : public nsRunnable +{ +public: + InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile) + : mFile(aFile) + , mRequest(aRequest) + { + } + + ~InitCursorEvent() {} + + NS_IMETHOD Run() { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + bool check; + mFile->mFile->IsDirectory(&check); + if (!check) { + nsCOMPtr event = new PostErrorEvent(mRequest, + POST_ERROR_EVENT_FILE_NOT_ENUMERABLE, + mFile); + NS_DispatchToMainThread(event); + return NS_OK; + } + + nsString fullpath; + mFile->mFile->GetPath(fullpath); + collectFiles(fullpath, mFile); + + nsCOMPtr event = new ContinueCursorEvent(mRequest); + NS_DispatchToMainThread(event); + + return NS_OK; + } + + void collectFiles(const nsString& aInitialFullPath, DeviceStorageFile* aFile) + { + // TODO - we may want to do this incrementally. + if (!aFile) + return; + + nsCOMPtr e; + aFile->mFile->GetDirectoryEntries(getter_AddRefs(e)); + + nsCOMPtr files = do_QueryInterface(e); + nsCOMPtr f; + + while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) { + bool isDir; + f->IsDirectory(&isDir); + + bool isFile; + f->IsFile(&isFile); + + nsString fullpath; + f->GetPath(fullpath); + + nsAString::size_type len = aInitialFullPath.Length() + 1; // +1 for the trailing / + nsDependentSubstring newPath = Substring(fullpath, len); + + if (!StringBeginsWith(fullpath, aInitialFullPath)) { + NS_WARNING("collectFiles returned a path that does not belong!"); + continue; + } + + nsRefPtr dsf = new DeviceStorageFile(f, newPath); + if (isDir) { + collectFiles(aInitialFullPath, dsf); + } + else if (isFile) { + nsDOMDeviceStorageCursor* cursor = static_cast(mRequest.get()); + cursor->mFiles.AppendElement(dsf); + } + } + } + +private: + nsRefPtr mFile; + nsRefPtr mRequest; +}; + +DOMCI_DATA(DeviceStorageCursor, nsDOMDeviceStorageCursor) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDeviceStorageCursor) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDeviceStorageCursor) + NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorageCursor) + NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest) + NS_INTERFACE_MAP_ENTRY(nsIDOMDOMRequest) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorageCursor) +NS_INTERFACE_MAP_END_INHERITING(DOMRequest) + +NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorageCursor, DOMRequest) +NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMRequest) + +nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow, + nsIURI* aURI, + DeviceStorageFile* aFile, + bool aEditable) + : DOMRequest(aWindow) + , mOkToCallContinue(false) + , mFile(aFile) + , mURI(aURI) + , mEditable(aEditable) +{ + if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { + Allow(); + return; + } + + nsCOMPtr prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); + if (prompt) { + prompt->Prompt(this); + } +} + +nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor() +{ +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::GetType(nsACString & aType) +{ + aType = "device-storage"; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::GetUri(nsIURI * *aRequestingURI) +{ + mURI.forget(aRequestingURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::GetWindow(nsIDOMWindow * *aRequestingWindow) +{ + NS_IF_ADDREF(*aRequestingWindow = GetOwner()); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::GetElement(nsIDOMElement * *aRequestingElement) +{ + *aRequestingElement = nsnull; + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::Cancel() +{ + nsCOMPtr event = new PostErrorEvent(this, + POST_ERROR_EVENT_PERMISSION_DENIED, + mFile); + NS_DispatchToMainThread(event); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::Allow() +{ + if (!isSafePath(mFile->mPath)) { + nsCOMPtr r = new PostErrorEvent(this, + POST_ERROR_EVENT_ILLEGAL_FILE_NAME, + mFile); + NS_DispatchToMainThread(r); + return NS_OK; + } + + AppendRelativePath(mFile->mFile, mFile->mPath); + + nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ASSERTION(target, "Must have stream transport service"); + + nsCOMPtr event = new InitCursorEvent(this, mFile); + target->Dispatch(event, NS_DISPATCH_NORMAL); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorageCursor::Continue() +{ + if (!mOkToCallContinue) { + return NS_ERROR_UNEXPECTED; + } + + if (mRooted) { + // We call onsuccess multiple times. clear the last + // rooted result. + NS_DROP_JS_OBJECTS(this, nsDOMDeviceStorageCursor); + mResult = JSVAL_VOID; + mDone = false; + mRooted = false; + } + + nsCOMPtr event = new ContinueCursorEvent(this); + NS_DispatchToMainThread(event); + + mOkToCallContinue = false; + return NS_OK; +} + + +class PostResultEvent : public nsRunnable +{ +public: + PostResultEvent(nsRefPtr& aRequest, bool aEditable, DeviceStorageFile* aFile) + : mEditable(aEditable) + , mFile(aFile) + { + mRequest.swap(aRequest); + } + + PostResultEvent(nsRefPtr& aRequest, const nsAString & aPath) + : mPath(aPath) + { + mRequest.swap(aRequest); + } + + ~PostResultEvent() {} + + NS_IMETHOD Run() + { + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + jsval result = JSVAL_NULL; + if (mFile) { + result = nsIFileToJsval(mRequest->GetOwner(), mFile, mEditable); + } else { + result = StringToJsval(mRequest->GetOwner(), mPath); + } + + mRequest->FireSuccess(result); + mRequest = nsnull; + return NS_OK; + } + +private: + bool mEditable; + nsRefPtr mFile; + nsString mPath; + nsRefPtr mRequest; +}; + +class WriteFileEvent : public nsRunnable +{ +public: + WriteFileEvent(nsIDOMBlob *aBlob, + DeviceStorageFile *aFile, + nsRefPtr& aRequest) + : mBlob(aBlob) + , mFile(aFile) + { + mRequest.swap(aRequest); + } + + ~WriteFileEvent() {} + + void CleanupOnFail(const char* error) + { + if (mFile) { + mFile->mFile->Remove(false); + } + + nsCOMPtr event = new PostErrorEvent(mRequest, + error, + mFile); + NS_DispatchToMainThread(event); + } + + NS_IMETHOD Run() + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + //TODO - this might be faster if we check to see if + //these are backed by OS-files, and if so, then just do + //a copy() + + nsCOMPtr f = mFile->mFile; + + // This also creates all ancestors + nsresult rv = f->Create(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) { + CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 1 "); + return NS_OK; + } + + nsCOMPtr stream; + mBlob->GetInternalStream(getter_AddRefs(stream)); + + if (!stream) { + CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 2 "); + return NS_OK; + } + + PRUint32 bufSize; + stream->Available(&bufSize); + + nsCOMPtr outputStream; + NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), f); + + if (!outputStream) { + CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 3 "); + return NS_OK; + } + + nsCOMPtr bufferedOutputStream; + NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), + outputStream, + 4096*4); + + if (!bufferedOutputStream) { + CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 4" ); + return NS_OK; + } + + PRUint32 wrote; + bufferedOutputStream->WriteFrom(stream, bufSize, &wrote); + bufferedOutputStream->Close(); + outputStream->Close(); + + if (bufSize != wrote) { + CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 5 " ); + return NS_OK; + } + + nsCOMPtr event = new PostResultEvent(mRequest, mFile->mPath); + NS_DispatchToMainThread(event); + + return NS_OK; + } + +private: + nsCOMPtr mBlob; + nsRefPtr mFile; + nsRefPtr mRequest; +}; + + +class ReadFileEvent : public nsRunnable +{ +public: + ReadFileEvent(DeviceStorageFile* aFile, + bool aEditable, + nsRefPtr& aRequest) + : mFile(aFile) + , mEditable(aEditable) + { + mRequest.swap(aRequest); + } + + ~ReadFileEvent() {} + + NS_IMETHOD Run() + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsRefPtr r; + + if (!mEditable) { + bool check = false; + mFile->mFile->Exists(&check); + if (!check) { + r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST, mFile); + } + } + + if (!r) { + r = new PostResultEvent(mRequest, mEditable, mFile); + } + NS_DispatchToMainThread(r); + return NS_OK; + } + +private: + nsRefPtr mFile; + bool mEditable; + nsRefPtr mRequest; +}; + +class DeleteFileEvent : public nsRunnable +{ +public: + DeleteFileEvent(DeviceStorageFile* aFile, + nsRefPtr& aRequest) + : mFile(aFile) + { + mRequest.swap(aRequest); + } + + ~DeleteFileEvent() {} + + NS_IMETHOD Run() + { + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + mFile->mFile->Remove(true); + nsCOMPtr event = new PostResultEvent(mRequest, mFile->mPath); + NS_DispatchToMainThread(event); + return NS_OK; + } + +private: + nsRefPtr mFile; + nsRefPtr mRequest; +}; + +class DeviceStorageRequest : public nsIContentPermissionRequest, public nsIRunnable +{ +public: + + enum { + DEVICE_STORAGE_REQUEST_READ, + DEVICE_STORAGE_REQUEST_WRITE, + DEVICE_STORAGE_REQUEST_DELETE + }; + DeviceStorageRequest(const PRInt32 aRequestType, + nsPIDOMWindow *aWindow, + nsIURI *aURI, + DeviceStorageFile *aFile, + DOMRequest* aRequest, + bool aEditable, + nsIDOMBlob *aBlob = nsnull) + : mRequestType(aRequestType) + , mWindow(aWindow) + , mURI(aURI) + , mFile(aFile) + , mRequest(aRequest) + , mEditable(aEditable) + , mBlob(aBlob) {} + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageRequest, nsIContentPermissionRequest) + + NS_IMETHOD Run() { + + if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { + Allow(); + return NS_OK; + } + + nsCOMPtr prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); + if (prompt) { + prompt->Prompt(this); + } + return NS_OK; + } + + NS_IMETHOD GetType(nsACString & aType) + { + aType = "device-storage"; + return NS_OK; + } + + NS_IMETHOD GetUri(nsIURI * *aRequestingURI) + { + NS_ADDREF(*aRequestingURI = mURI); + return NS_OK; + } + + NS_IMETHOD GetWindow(nsIDOMWindow * *aRequestingWindow) + { + NS_IF_ADDREF(*aRequestingWindow = mWindow); + return NS_OK; + } + + NS_IMETHOD GetElement(nsIDOMElement * *aRequestingElement) + { + *aRequestingElement = nsnull; + return NS_OK; + } + + NS_IMETHOD Cancel() + { + nsCOMPtr event = new PostErrorEvent(mRequest, + POST_ERROR_EVENT_PERMISSION_DENIED, + mFile); + NS_DispatchToMainThread(event); + return NS_OK; + } + + NS_IMETHOD Allow() + { + nsCOMPtr r; + + if (!mRequest) { + return NS_ERROR_FAILURE; + } + + switch(mRequestType) { + case DEVICE_STORAGE_REQUEST_WRITE: + { + if (!mBlob) { + return NS_ERROR_FAILURE; + } + + r = new WriteFileEvent(mBlob, mFile, mRequest); + break; + } + case DEVICE_STORAGE_REQUEST_READ: + { + r = new ReadFileEvent(mFile, mEditable, mRequest); + break; + } + case DEVICE_STORAGE_REQUEST_DELETE: + { + r = new DeleteFileEvent(mFile, mRequest); + break; + } + } + + if (r) { + nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ASSERTION(target, "Must have stream transport service"); + target->Dispatch(r, NS_DISPATCH_NORMAL); + } + return NS_OK; + } + +private: + PRInt32 mRequestType; + nsCOMPtr mWindow; + nsCOMPtr mURI; + nsRefPtr mFile; + + nsRefPtr mRequest; + bool mEditable; + nsCOMPtr mBlob; +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest) + 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(DeviceStorageRequest) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DeviceStorageRequest) +NS_IMPL_CYCLE_COLLECTION_CLASS(DeviceStorageRequest) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DeviceStorageRequest) +NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest) +NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWindow) +NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mBlob) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DeviceStorageRequest) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRequest, nsIDOMDOMRequest) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mWindow, nsPIDOMWindow) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mBlob, nsIDOMBlob) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +DOMCI_DATA(DeviceStorage, nsDOMDeviceStorage) + +NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorage) + NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorage) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDeviceStorage) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorage) +NS_INTERFACE_MAP_END + +NS_IMPL_THREADSAFE_ADDREF(nsDOMDeviceStorage) +NS_IMPL_THREADSAFE_RELEASE(nsDOMDeviceStorage) + +nsDOMDeviceStorage::nsDOMDeviceStorage() + : mStorageType(DEVICE_STORAGE_TYPE_DEFAULT) +{ +} + +nsresult +nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType, const PRInt32 aIndex) +{ + NS_ASSERTION(aWindow, "Must have a content dom"); + + mStorageType = SetRootFileForType(aType, aIndex); + if (!mFile) { + return NS_ERROR_NOT_AVAILABLE; + } + + mOwner = do_GetWeakReference(aWindow); + if (!mOwner) { + return NS_ERROR_FAILURE; + } + + // Grab the uri of the document + nsCOMPtr domdoc; + aWindow->GetDocument(getter_AddRefs(domdoc)); + nsCOMPtr doc = do_QueryInterface(domdoc); + if (!doc) { + return NS_ERROR_FAILURE; + } + doc->NodePrincipal()->GetURI(getter_AddRefs(mURI)); + + return NS_OK; +} + +nsDOMDeviceStorage::~nsDOMDeviceStorage() +{ +} + +void +nsDOMDeviceStorage::CreateDeviceStoragesFor(nsPIDOMWindow* aWin, + const nsAString &aType, + nsIVariant** _retval) +{ + nsTArray > stores; + + PRInt32 index = 0; + while (1) { + nsresult rv; + nsRefPtr storage = new nsDOMDeviceStorage(); + rv = storage->Init(aWin, aType, index++); + if (NS_FAILED(rv)) + break; + stores.AppendElement(storage); + } + + nsCOMPtr result = do_CreateInstance("@mozilla.org/variant;1"); + if (!result) { + return; + } + + result->SetAsArray(nsIDataType::VTYPE_INTERFACE, + &NS_GET_IID(nsIDOMDeviceStorage), + stores.Length(), + const_cast(static_cast(stores.Elements()))); + NS_ADDREF(*_retval = result); +} + +NS_IMETHODIMP +nsDOMDeviceStorage::GetType(nsAString & aType) +{ + switch(mStorageType) { + case DEVICE_STORAGE_TYPE_EXTERNAL: + aType.AssignLiteral("external"); + break; + case DEVICE_STORAGE_TYPE_SHARED: + aType.AssignLiteral("shared"); + break; + case DEVICE_STORAGE_TYPE_DEFAULT: + default: + aType.AssignLiteral("default"); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval NS_OUTPARAM) +{ + char buffer[128]; + NS_MakeRandomString(buffer, 128); + + nsString path; + path.AssignWithConversion(nsDependentCString(buffer)); + + return AddNamed(aBlob, path, _retval); +} + +NS_IMETHODIMP +nsDOMDeviceStorage::AddNamed(nsIDOMBlob *aBlob, + const nsAString & aPath, + nsIDOMDOMRequest * *_retval NS_OUTPARAM) +{ + // if the blob is null here, bail + if (aBlob == nsnull) + return NS_OK; + + nsCOMPtr win = do_QueryReferent(mOwner); + if (!win) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr request = new DOMRequest(win); + NS_ADDREF(*_retval = request); + + nsCOMPtr r; + + nsCOMPtr file; + mFile->Clone(getter_AddRefs(file)); + AppendRelativePath(file, aPath); + + nsRefPtr dsf = new DeviceStorageFile(file, aPath); + + if (!isSafePath(aPath)) { + r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf); + } + else { + r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_WRITE, + win, mURI, dsf, request, true, aBlob); + } + NS_DispatchToMainThread(r); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorage::Get(const JS::Value & aPath, + JSContext* aCx, + nsIDOMDOMRequest * *_retval NS_OUTPARAM) +{ + return GetInternal(aPath, aCx, _retval, false); +} + +NS_IMETHODIMP +nsDOMDeviceStorage::GetEditable(const JS::Value & aPath, + JSContext* aCx, + nsIDOMDOMRequest * *_retval NS_OUTPARAM) +{ + return GetInternal(aPath, aCx, _retval, true); +} + +nsresult +nsDOMDeviceStorage::GetInternal(const JS::Value & aPath, + JSContext* aCx, + nsIDOMDOMRequest * *_retval NS_OUTPARAM, + bool aEditable) +{ + nsCOMPtr win = do_QueryReferent(mOwner); + if (!win) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr request = new DOMRequest(win); + NS_ADDREF(*_retval = request); + + nsCOMPtr r; + + JSString* jsstr = JS_ValueToString(aCx, aPath); + nsDependentJSString path; + if (!path.init(aCx, jsstr)) { + nsRefPtr dsf = new DeviceStorageFile(mFile); + r = new PostErrorEvent(request, + POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED, + dsf); + } else if (!isSafePath(path)) { + nsRefPtr dsf = new DeviceStorageFile(mFile, path); + r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf); + } else { + + nsCOMPtr file; + mFile->Clone(getter_AddRefs(file)); + AppendRelativePath(file, path); + + nsRefPtr dsf = new DeviceStorageFile(file, path); + + r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_READ, + win, mURI, dsf, request, aEditable); + } + NS_DispatchToMainThread(r); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorage::Delete(const JS::Value & aPath, JSContext* aCx, nsIDOMDOMRequest * *_retval NS_OUTPARAM) +{ + nsCOMPtr r; + + nsCOMPtr win = do_QueryReferent(mOwner); + if (!win) { + return NS_ERROR_UNEXPECTED; + } + + nsRefPtr request = new DOMRequest(win); + NS_ADDREF(*_retval = request); + + JSString* jsstr = JS_ValueToString(aCx, aPath); + nsDependentJSString path; + if (!path.init(aCx, jsstr)) { + nsRefPtr dsf = new DeviceStorageFile(mFile); + r = new PostErrorEvent(request, POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED, dsf); + } else if (!isSafePath(path)) { + nsRefPtr dsf = new DeviceStorageFile(mFile, path); + r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf); + } + else { + nsCOMPtr file; + mFile->Clone(getter_AddRefs(file)); + AppendRelativePath(file, path); + + nsRefPtr dsf = new DeviceStorageFile(file, path); + r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_DELETE, + win, mURI, dsf, request, true); + } + NS_DispatchToMainThread(r); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMDeviceStorage::Enumerate(const nsAString & aPath, + nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM) +{ + return EnumerateInternal(aPath, _retval, false); +} + +NS_IMETHODIMP +nsDOMDeviceStorage::EnumerateEditable(const nsAString & aPath, + nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM) +{ + return EnumerateInternal(aPath, _retval, true); +} + +nsresult +nsDOMDeviceStorage::EnumerateInternal(const nsAString & aPath, + nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM, + bool aEditable) +{ + nsCOMPtr win = do_QueryReferent(mOwner); + if (!win) + return NS_ERROR_UNEXPECTED; + + nsRefPtr dsf = new DeviceStorageFile(mFile, aPath); + + nsDOMDeviceStorageCursor* cursor = new nsDOMDeviceStorageCursor(win, mURI, dsf, aEditable); + NS_ADDREF(*_retval = cursor); + + return NS_OK; +} diff --git a/dom/devicestorage/nsDeviceStorage.h b/dom/devicestorage/nsDeviceStorage.h new file mode 100644 index 000000000000..d237a5d96b65 --- /dev/null +++ b/dom/devicestorage/nsDeviceStorage.h @@ -0,0 +1,58 @@ +/* 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 "nsIClassInfo.h" +#include "nsIDOMDeviceStorage.h" +#include "nsIDOMDeviceStorageCursor.h" +#include "nsIDOMWindow.h" +#include "nsIURI.h" + +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDOMClassInfoID.h" +#include "nsString.h" +#include "nsWeakPtr.h" +#include "nsInterfaceHashtable.h" + +class nsDOMDeviceStorage : public nsIDOMDeviceStorage +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMDEVICESTORAGE + + nsDOMDeviceStorage(); + + nsresult Init(nsPIDOMWindow* aWindow, const nsAString &aType, const PRInt32 aIndex); + + PRInt32 SetRootFileForType(const nsAString& aType, const PRInt32 aIndex); + + static void CreateDeviceStoragesFor(nsPIDOMWindow* aWin, const nsAString &aType, nsIVariant** _retval); + +private: + ~nsDOMDeviceStorage(); + + + nsresult GetInternal(const JS::Value & aName, JSContext* aCx, nsIDOMDOMRequest * *_retval NS_OUTPARAM, bool aEditable); + nsresult EnumerateInternal(const nsAString & aName, nsIDOMDeviceStorageCursor * *_retval NS_OUTPARAM, bool aEditable); + + PRInt32 mStorageType; + nsCOMPtr mFile; + + nsWeakPtr mOwner; + nsCOMPtr mURI; + + // nsIDOMDeviceStorage.type + enum { + DEVICE_STORAGE_TYPE_DEFAULT = 0, + DEVICE_STORAGE_TYPE_SHARED, + DEVICE_STORAGE_TYPE_EXTERNAL, + }; +}; + +#endif diff --git a/dom/interfaces/devicestorage/Makefile.in b/dom/interfaces/devicestorage/Makefile.in new file mode 100644 index 000000000000..76622a30f875 --- /dev/null +++ b/dom/interfaces/devicestorage/Makefile.in @@ -0,0 +1,26 @@ +# 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/. + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = dom +LIBRARY_NAME = domdevicestorage_s +XPIDL_MODULE = dom_devicestorage +LIBXUL_LIBRARY = 1 +FORCE_STATIC_LIB = 1 + +include $(topsrcdir)/dom/dom-config.mk + +XPIDLSRCS = \ + nsIDOMDeviceStorage.idl \ + nsIDOMDeviceStorageCursor.idl \ + nsIDOMNavigatorDeviceStorage.idl + +include $(topsrcdir)/config/rules.mk + diff --git a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl new file mode 100644 index 000000000000..3f5a98de6699 --- /dev/null +++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl @@ -0,0 +1,35 @@ +/* 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 "domstubs.idl" +interface nsIDOMBlob; +interface nsIDOMDOMRequest; +interface nsIDOMDeviceStorageCursor; + +[scriptable, uuid(05C0D0C8-D698-4CCD-899C-7198A33BD7EC)] +interface nsIDOMDeviceStorage : nsISupports +{ + /* + * Hint as to what kind of storage this object is. + * May be "external", "shared", or "default". + */ + readonly attribute DOMString type; + + nsIDOMDOMRequest add(in nsIDOMBlob aBlob); + nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName); + + [implicit_jscontext] + nsIDOMDOMRequest get(in jsval aName); + + [implicit_jscontext] + nsIDOMDOMRequest getEditable(in jsval aName); + + [implicit_jscontext] + nsIDOMDOMRequest delete(in jsval aName); + + nsIDOMDeviceStorageCursor enumerate([optional] in DOMString directory); + + nsIDOMDeviceStorageCursor enumerateEditable([optional] in DOMString directory); +}; + diff --git a/dom/interfaces/devicestorage/nsIDOMDeviceStorageCursor.idl b/dom/interfaces/devicestorage/nsIDOMDeviceStorageCursor.idl new file mode 100644 index 000000000000..c5472f24e0f9 --- /dev/null +++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorageCursor.idl @@ -0,0 +1,11 @@ +/* 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 "domstubs.idl" + +[scriptable, uuid(995DFF99-ED70-4780-AC9A-4B58CD491186)] +interface nsIDOMDeviceStorageCursor : nsISupports +{ + void continue(); +}; diff --git a/dom/interfaces/devicestorage/nsIDOMNavigatorDeviceStorage.idl b/dom/interfaces/devicestorage/nsIDOMNavigatorDeviceStorage.idl new file mode 100644 index 000000000000..af17d94821e2 --- /dev/null +++ b/dom/interfaces/devicestorage/nsIDOMNavigatorDeviceStorage.idl @@ -0,0 +1,17 @@ +/* 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 "domstubs.idl" +interface nsIVariant; + +/** + * Property that extends the navigator object. + */ +[scriptable, uuid(A4B2831D-6065-472F-8A6D-2C9085C74C15)] +interface nsIDOMNavigatorDeviceStorage : nsISupports +{ + // returns an array of nsIDOMDeviceStorage + nsIVariant getDeviceStorage(in DOMString type); +}; + diff --git a/dom/tests/mochitest/Makefile.in b/dom/tests/mochitest/Makefile.in index c7fb51171a4a..aaeadebbe40d 100644 --- a/dom/tests/mochitest/Makefile.in +++ b/dom/tests/mochitest/Makefile.in @@ -18,6 +18,7 @@ DIRS += \ ajax \ bugs \ chrome \ + devicestorage \ general \ whatwg \ geolocation \ diff --git a/dom/tests/mochitest/devicestorage/Makefile.in b/dom/tests/mochitest/devicestorage/Makefile.in new file mode 100644 index 000000000000..575bbbcc3e6a --- /dev/null +++ b/dom/tests/mochitest/devicestorage/Makefile.in @@ -0,0 +1,27 @@ +# 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/. + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = dom/tests/mochitest/devicestorage + +include $(DEPTH)/config/autoconf.mk + +include $(topsrcdir)/config/rules.mk + +_TEST_FILES = \ + test_sanity.html \ + test_basic.html \ + test_enumerate.html \ + test_enumerateMultipleContinue.html \ + test_overwrite.html \ + test_dotdot.html \ + devicestorage_common.js \ + $(NULL) + +libs:: $(_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir) + diff --git a/dom/tests/mochitest/devicestorage/devicestorage_common.js b/dom/tests/mochitest/devicestorage/devicestorage_common.js new file mode 100644 index 000000000000..8aa986b347e2 --- /dev/null +++ b/dom/tests/mochitest/devicestorage/devicestorage_common.js @@ -0,0 +1,48 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var oldVal = false; + +function devicestorage_setup() { + SimpleTest.waitForExplicitFinish(); + try { + oldVal = SpecialPowers.getBoolPref("device.storage.enabled"); + } catch(e) {} + SpecialPowers.setBoolPref("device.storage.enabled", true); + SpecialPowers.setBoolPref("device.storage.testing", true); + SpecialPowers.setBoolPref("device.storage.prompt.testing", true); +} + +function devicestorage_cleanup() { + SpecialPowers.setBoolPref("device.storage.enabled", oldVal); + SpecialPowers.setBoolPref("device.storage.testing", false); + SpecialPowers.setBoolPref("device.storage.prompt.testing", false); + SimpleTest.finish(); +} + +function getRandomBuffer() { + var size = 1024; + var buffer = new ArrayBuffer(size); + var view = new Uint8Array(buffer); + for (var i = 0; i < size; i++) { + view[i] = parseInt(Math.random() * 255); + } + return buffer; +} + +function createRandomBlob() { + return blob = new Blob([getRandomBuffer()], {type: 'binary/random'}); +} + +function randomFilename(l) { + var set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ"; + var result = ""; + for (var i=0; i + + + + Test for the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_dotdot.html b/dom/tests/mochitest/devicestorage/test_dotdot.html new file mode 100644 index 000000000000..ebdf0d23327d --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_dotdot.html @@ -0,0 +1,71 @@ + + + + + Test for the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_enumerate.html b/dom/tests/mochitest/devicestorage/test_enumerate.html new file mode 100644 index 000000000000..b9af36954e56 --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_enumerate.html @@ -0,0 +1,101 @@ + + + + + Test for the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_enumerateMultipleContinue.html b/dom/tests/mochitest/devicestorage/test_enumerateMultipleContinue.html new file mode 100644 index 000000000000..0d56d62130d4 --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_enumerateMultipleContinue.html @@ -0,0 +1,50 @@ + + + + + Test for the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_enumerateNoParam.html b/dom/tests/mochitest/devicestorage/test_enumerateNoParam.html new file mode 100644 index 000000000000..b84f9c151729 --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_enumerateNoParam.html @@ -0,0 +1,95 @@ + + + + + Test for the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_overwrite.html b/dom/tests/mochitest/devicestorage/test_overwrite.html new file mode 100644 index 000000000000..e2a342e1ee37 --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_overwrite.html @@ -0,0 +1,90 @@ + + + + + + Test for basic sanity of the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/dom/tests/mochitest/devicestorage/test_sanity.html b/dom/tests/mochitest/devicestorage/test_sanity.html new file mode 100644 index 000000000000..097765b1590e --- /dev/null +++ b/dom/tests/mochitest/devicestorage/test_sanity.html @@ -0,0 +1,72 @@ + + + + + + Test for basic sanity of the device storage API + + + + + + +Mozilla Bug 717103 +

+ +
+
+
+ + + diff --git a/layout/build/Makefile.in b/layout/build/Makefile.in index 1125f87cf511..78fa9aa263a5 100644 --- a/layout/build/Makefile.in +++ b/layout/build/Makefile.in @@ -62,6 +62,7 @@ SHARED_LIBRARY_LIBS = \ $(DEPTH)/dom/base/$(LIB_PREFIX)jsdombase_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/battery/$(LIB_PREFIX)dom_battery_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/contacts/$(LIB_PREFIX)jsdomcontacts_s.$(LIB_SUFFIX) \ + $(DEPTH)/dom/devicestorage/$(LIB_PREFIX)domdevicestorage_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/power/$(LIB_PREFIX)dom_power_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/settings/$(LIB_PREFIX)jsdomsettings_s.$(LIB_SUFFIX) \ $(DEPTH)/dom/network/src/$(LIB_PREFIX)dom_network_s.$(LIB_SUFFIX) \ @@ -214,7 +215,7 @@ LOCAL_INCLUDES += -I$(srcdir)/../base \ -I$(srcdir)/../xul/base/src \ -I$(srcdir)/../mathml \ -I$(topsrcdir)/content/base/src \ - -I$(topsrcdir)/content/canvas/src \ + -I$(topsrcdir)/content/canvas/src \ -I$(topsrcdir)/content/html/content/src \ -I$(topsrcdir)/content/html/document/src \ -I$(topsrcdir)/content/xslt/src/base \ diff --git a/mobile/xul/installer/package-manifest.in b/mobile/xul/installer/package-manifest.in index 3bad6db98521..6f677122244a 100644 --- a/mobile/xul/installer/package-manifest.in +++ b/mobile/xul/installer/package-manifest.in @@ -168,6 +168,7 @@ @BINPATH@/components/dom_canvas.xpt @BINPATH@/components/dom_core.xpt @BINPATH@/components/dom_css.xpt +@BINPATH@/components/dom_devicestorage.xpt @BINPATH@/components/dom_events.xpt @BINPATH@/components/dom_geolocation.xpt @BINPATH@/components/dom_network.xpt diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index df92466c517f..cf006837d026 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -3469,6 +3469,9 @@ pref("geo.enabled", true); // Enable/Disable the orientation API for content pref("device.motion.enabled", true); +// Enable/Disable the device storage API for content +pref("device.storage.enabled", false); + // Toggle which thread the HTML5 parser uses for stream parsing pref("html5.offmainthread", true); // Time in milliseconds between the time a network buffer is seen and the diff --git a/toolkit/toolkit-makefiles.sh b/toolkit/toolkit-makefiles.sh index b9c44012aa36..a925de00df54 100644 --- a/toolkit/toolkit-makefiles.sh +++ b/toolkit/toolkit-makefiles.sh @@ -18,6 +18,7 @@ MAKEFILES_dom=" dom/interfaces/canvas/Makefile dom/interfaces/core/Makefile dom/interfaces/css/Makefile + dom/interfaces/devicestorage/Makefile dom/interfaces/events/Makefile dom/interfaces/geolocation/Makefile dom/interfaces/html/Makefile