/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=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 "DataStoreService.h" #include "DataStoreCallbacks.h" #include "DataStoreDB.h" #include "DataStoreRevision.h" #include "mozilla/dom/DataStore.h" #include "nsIDataStore.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/DOMError.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/indexedDB/IDBCursor.h" #include "mozilla/dom/indexedDB/IDBObjectStore.h" #include "mozilla/dom/DataStoreBinding.h" #include "mozilla/dom/DataStoreImplBinding.h" #include "mozIApplication.h" #include "mozIApplicationClearPrivateDataParams.h" #include "nsIAppsService.h" #include "nsIDOMEvent.h" #include "nsIDocument.h" #include "nsIDOMGlobalPropertyInitializer.h" #include "nsIIOService.h" #include "nsIObserverService.h" #include "nsIPermissionManager.h" #include "nsIScriptSecurityManager.h" #include "nsIUUIDGenerator.h" #include "nsPIDOMWindow.h" #include "nsIURI.h" #include "nsContentUtils.h" #include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" #define ASSERT_PARENT_PROCESS() \ AssertIsInMainProcess(); \ if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) { \ return NS_ERROR_FAILURE; \ } namespace mozilla { namespace dom { using namespace indexedDB; // This class contains all the information about a DataStore. class DataStoreInfo { public: DataStoreInfo(const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly, bool aEnabled) : mName(aName) , mOriginURL(aOriginURL) , mManifestURL(aManifestURL) , mReadOnly(aReadOnly) , mEnabled(aEnabled) {} void Update(const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly) { mName = aName; mOriginURL = aOriginURL; mManifestURL = aManifestURL; mReadOnly = aReadOnly; } void Enable() { mEnabled = true; } nsString mName; nsString mOriginURL; nsString mManifestURL; bool mReadOnly; // A DataStore is enabled when it has its first revision. bool mEnabled; }; namespace { // Singleton for DataStoreService. StaticRefPtr gDataStoreService; static uint64_t gCounterID = 0; typedef nsClassHashtable HashApp; bool IsMainProcess() { static const bool isMainProcess = XRE_GetProcessType() == GeckoProcessType_Default; return isMainProcess; } void AssertIsInMainProcess() { MOZ_ASSERT(IsMainProcess()); } void RejectPromise(nsPIDOMWindow* aWindow, Promise* aPromise, nsresult aRv) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_FAILED(aRv)); nsRefPtr error; if (aRv == NS_ERROR_DOM_SECURITY_ERR) { error = new DOMError(aWindow, NS_LITERAL_STRING("SecurityError"), NS_LITERAL_STRING("Access denied")); } else { error = new DOMError(aWindow, NS_LITERAL_STRING("InternalError"), NS_LITERAL_STRING("An error occurred")); } aPromise->MaybeReject(error); } void DeleteDatabase(const nsAString& aName, const nsAString& aManifestURL) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr db = new DataStoreDB(aManifestURL, aName); db->Delete(); } PLDHashOperator DeleteDataStoresAppEnumerator( const uint32_t& aAppId, nsAutoPtr& aInfo, void* aUserData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); auto* appId = static_cast(aUserData); if (*appId != aAppId) { return PL_DHASH_NEXT; } DeleteDatabase(aInfo->mName, aInfo->mManifestURL); return PL_DHASH_REMOVE; } PLDHashOperator DeleteDataStoresEnumerator(const nsAString& aName, nsAutoPtr& aApps, void* aUserData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); aApps->Enumerate(DeleteDataStoresAppEnumerator, aUserData); return aApps->Count() ? PL_DHASH_NEXT : PL_DHASH_REMOVE; } void GeneratePermissionName(nsAString& aPermission, const nsAString& aName, const nsAString& aManifestURL) { aPermission.AssignASCII("indexedDB-chrome-"); aPermission.Append(aName); aPermission.AppendASCII("|"); aPermission.Append(aManifestURL); } nsresult ResetPermission(uint32_t aAppId, const nsAString& aOriginURL, const nsAString& aManifestURL, const nsAString& aPermission, bool aReadOnly) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsresult rv; nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr uri; rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aOriginURL), nullptr, nullptr, getter_AddRefs(uri)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); if (!ssm) { return NS_ERROR_FAILURE; } nsCOMPtr principal; rv = ssm->GetAppCodebasePrincipal(uri, aAppId, false, getter_AddRefs(principal)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr pm = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); if (!pm) { return NS_ERROR_FAILURE; } nsCString basePermission; basePermission.Append(NS_ConvertUTF16toUTF8(aPermission)); // Write permission { nsCString permission; permission.Append(basePermission); permission.AppendASCII("-write"); uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; rv = pm->TestExactPermissionFromPrincipal(principal, permission.get(), &perm); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (aReadOnly && perm == nsIPermissionManager::ALLOW_ACTION) { rv = pm->RemoveFromPrincipal(principal, permission.get()); } else if (!aReadOnly && perm != nsIPermissionManager::ALLOW_ACTION) { rv = pm->AddFromPrincipal(principal, permission.get(), nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_NEVER, 0); } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // Read permission { nsCString permission; permission.Append(basePermission); permission.AppendASCII("-read"); uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; rv = pm->TestExactPermissionFromPrincipal(principal, permission.get(), &perm); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (perm != nsIPermissionManager::ALLOW_ACTION) { rv = pm->AddFromPrincipal(principal, permission.get(), nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_NEVER, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } } // Generic permission uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; rv = pm->TestExactPermissionFromPrincipal(principal, basePermission.get(), &perm); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (perm != nsIPermissionManager::ALLOW_ACTION) { rv = pm->AddFromPrincipal(principal, basePermission.get(), nsIPermissionManager::ALLOW_ACTION, nsIPermissionManager::EXPIRE_NEVER, 0); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return NS_OK; } class MOZ_STACK_CLASS GetDataStoreInfosData { public: GetDataStoreInfosData(nsClassHashtable& aAccessStores, const nsAString& aName, uint32_t aAppId, nsTArray& aStores) : mAccessStores(aAccessStores) , mName(aName) , mAppId(aAppId) , mStores(aStores) {} nsClassHashtable& mAccessStores; nsString mName; uint32_t mAppId; nsTArray& mStores; }; PLDHashOperator GetDataStoreInfosEnumerator(const uint32_t& aAppId, DataStoreInfo* aInfo, void* aUserData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); auto* data = static_cast(aUserData); if (aAppId == data->mAppId) { return PL_DHASH_NEXT; } HashApp* apps; if (!data->mAccessStores.Get(data->mName, &apps)) { return PL_DHASH_NEXT; } DataStoreInfo* accessInfo = nullptr; if (!apps->Get(data->mAppId, &accessInfo)) { return PL_DHASH_NEXT; } bool readOnly = aInfo->mReadOnly || accessInfo->mReadOnly; DataStoreInfo accessStore(aInfo->mName, aInfo->mOriginURL, aInfo->mManifestURL, readOnly, aInfo->mEnabled); data->mStores.AppendElement(accessStore); return PL_DHASH_NEXT; } // This class is useful to enumerate the add permissions for each app. class MOZ_STACK_CLASS AddPermissionsData { public: AddPermissionsData(const nsAString& aPermission, bool aReadOnly) : mPermission(aPermission) , mReadOnly(aReadOnly) , mResult(NS_OK) {} nsString mPermission; bool mReadOnly; nsresult mResult; }; PLDHashOperator AddPermissionsEnumerator(const uint32_t& aAppId, DataStoreInfo* aInfo, void* userData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); auto* data = static_cast(userData); // ReadOnly is decided by the owner first. bool readOnly = data->mReadOnly || aInfo->mReadOnly; data->mResult = ResetPermission(aAppId, aInfo->mOriginURL, aInfo->mManifestURL, data->mPermission, readOnly); return NS_FAILED(data->mResult) ? PL_DHASH_STOP : PL_DHASH_NEXT; } // This class is useful to enumerate the add permissions for each app. class MOZ_STACK_CLASS AddAccessPermissionsData { public: AddAccessPermissionsData(const nsAString& aName, bool aReadOnly) : mName(aName) , mReadOnly(aReadOnly) , mResult(NS_OK) {} nsString mName; bool mReadOnly; nsresult mResult; }; PLDHashOperator AddAccessPermissionsEnumerator(const uint32_t& aAppId, DataStoreInfo* aInfo, void* userData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); auto* data = static_cast(userData); nsString permission; GeneratePermissionName(permission, data->mName, aInfo->mManifestURL); // ReadOnly is decided by the owner first. bool readOnly = aInfo->mReadOnly || data->mReadOnly; data->mResult = ResetPermission(aAppId, aInfo->mOriginURL, aInfo->mManifestURL, permission, readOnly); return NS_FAILED(data->mResult) ? PL_DHASH_STOP : PL_DHASH_NEXT; } } /* anonymous namespace */ // A PendingRequest is created when a content code wants a list of DataStores // but some of them are not enabled yet. class PendingRequest { public: PendingRequest(nsPIDOMWindow* aWindow, Promise* aPromise, const nsTArray& aStores, const nsTArray& aPendingDataStores) : mWindow(aWindow) , mPromise(aPromise) , mStores(aStores) , mPendingDataStores(aPendingDataStores) { } nsCOMPtr mWindow; nsRefPtr mPromise; nsTArray mStores; // This array contains the list of manifestURLs of the DataStores that are // not enabled yet. nsTArray mPendingDataStores; }; // This callback is used to enable a DataStore when its first revisionID is // created. class RevisionAddedEnableStoreCallback MOZ_FINAL : public DataStoreRevisionCallback { public: NS_INLINE_DECL_REFCOUNTING(RevisionAddedEnableStoreCallback); RevisionAddedEnableStoreCallback(uint32_t aAppId, const nsAString& aName, const nsAString& aManifestURL) : mAppId(aAppId) , mName(aName) , mManifestURL(aManifestURL) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); } void Run(const nsAString& aRevisionId) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr service = DataStoreService::Get(); MOZ_ASSERT(service); service->EnableDataStore(mAppId, mName, mManifestURL); } private: uint32_t mAppId; nsString mName; nsString mManifestURL; }; // This DataStoreDBCallback is called when DataStoreDB opens the DataStore DB. // Then the first revision will be created if it doesn't exist yet. class FirstRevisionIdCallback MOZ_FINAL : public DataStoreDBCallback , public nsIDOMEventListener { public: NS_DECL_ISUPPORTS FirstRevisionIdCallback(uint32_t aAppId, const nsAString& aName, const nsAString& aManifestURL) : mAppId(aAppId) , mName(aName) , mManifestURL(aManifestURL) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); } void Run(DataStoreDB* aDb, bool aSuccess) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aDb); if (!aSuccess) { NS_WARNING("Failed to create the first revision."); return; } mTxn = aDb->Transaction(); ErrorResult rv; nsRefPtr store = mTxn->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION), rv); if (NS_WARN_IF(rv.Failed())) { return; } // a Null JSContext is ok because OpenCursor ignores it if the range is // undefined. mRequest = store->OpenCursor(nullptr, JS::UndefinedHandleValue, IDBCursorDirection::Prev, rv); if (NS_WARN_IF(rv.Failed())) { return; } nsresult res; res = mRequest->EventTarget::AddEventListener(NS_LITERAL_STRING("success"), this, false); if (NS_WARN_IF(NS_FAILED(res))) { return; } } // nsIDOMEventListener NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsString type; nsresult rv = aEvent->GetType(type); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!type.EqualsASCII("success")) { return NS_ERROR_FAILURE; } mRequest->RemoveEventListener(NS_LITERAL_STRING("success"), this, false); // Note: this cx is only used for rooting and AddRevision, neither of which // actually care which compartment we're in. AutoSafeJSContext cx; ErrorResult error; JS::Rooted result(cx, mRequest->GetResult(error)); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } // This means that the content is a IDBCursor, so the first revision already // exists. if (result.isObject()) { nsRefPtr service = DataStoreService::Get(); MOZ_ASSERT(service); return service->EnableDataStore(mAppId, mName, mManifestURL); } MOZ_ASSERT(mTxn); nsRefPtr store = mTxn->ObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION), error); if (NS_WARN_IF(error.Failed())) { return error.ErrorCode(); } MOZ_ASSERT(store); nsRefPtr callback = new RevisionAddedEnableStoreCallback(mAppId, mName, mManifestURL); // If the revision doesn't exist, let's create it. nsRefPtr mRevision = new DataStoreRevision(); return mRevision->AddRevision(cx, store, 0, DataStoreRevision::RevisionVoid, callback); } private: nsRefPtr mRequest; nsRefPtr mTxn; nsRefPtr mRevision; uint32_t mAppId; nsString mName; nsString mManifestURL; }; NS_IMPL_ISUPPORTS(FirstRevisionIdCallback, nsIDOMEventListener) // This class calls the 'retrieveRevisionId' method of the DataStore object for // any DataStore in the 'mResults' array. When all of them are called, the // promise is resolved with 'mResults'. // The reson why this has to be done is because DataStore are object that can be // created in any thread and in any process. The first revision has been // created, but they don't know its value yet. class RetrieveRevisionsCounter { public: NS_INLINE_DECL_REFCOUNTING(RetrieveRevisionsCounter); RetrieveRevisionsCounter(uint32_t aId, Promise* aPromise, uint32_t aCount) : mPromise(aPromise) , mId(aId) , mCount(aCount) { MOZ_ASSERT(NS_IsMainThread()); } void AppendDataStore(JSContext* aCx, DataStore* aDataStore, nsIDataStore* aDataStoreIf) { MOZ_ASSERT(NS_IsMainThread()); mResults.AppendElement(aDataStore); // DataStore will run this callback when the revisionID is retrieved. JSFunction* func = js::NewFunctionWithReserved(aCx, JSCallback, 0 /* nargs */, 0 /* flags */, nullptr, nullptr); if (!func) { return; } JS::Rooted obj(aCx, JS_GetFunctionObject(func)); if (!obj) { return; } // We use the ID to know which counter is this. The service keeps all of // these counters alive with their own IDs in an hashtable. js::SetFunctionNativeReserved(obj, 0, JS::Int32Value(mId)); JS::Rooted value(aCx, JS::ObjectValue(*obj)); nsresult rv = aDataStoreIf->RetrieveRevisionId(value); if (NS_WARN_IF(NS_FAILED(rv))) { return; } } private: static bool JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp) { MOZ_ASSERT(NS_IsMainThread()); JS::CallArgs args = CallArgsFromVp(aArgc, aVp); JS::Rooted value(aCx, js::GetFunctionNativeReserved(&args.callee(), 0)); uint32_t id = value.toInt32(); nsRefPtr service = DataStoreService::Get(); MOZ_ASSERT(service); nsRefPtr counter = service->GetCounter(id); MOZ_ASSERT(counter); // When all the callbacks are called, we can resolve the promise and remove // the counter from the service. --counter->mCount; if (!counter->mCount) { service->RemoveCounter(id); counter->mPromise->MaybeResolve(counter->mResults); } return true; } nsRefPtr mPromise; nsTArray> mResults; uint32_t mId; uint32_t mCount; }; /* static */ already_AddRefed DataStoreService::GetOrCreate() { MOZ_ASSERT(NS_IsMainThread()); if (!gDataStoreService) { nsRefPtr service = new DataStoreService(); if (NS_WARN_IF(NS_FAILED(service->Init()))) { return nullptr; } gDataStoreService = service; } nsRefPtr service = gDataStoreService.get(); return service.forget(); } /* static */ already_AddRefed DataStoreService::Get() { MOZ_ASSERT(NS_IsMainThread()); nsRefPtr service = gDataStoreService.get(); return service.forget(); } /* static */ void DataStoreService::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); if (gDataStoreService) { if (IsMainProcess()) { nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(gDataStoreService, "webapps-clear-data"); } } gDataStoreService = nullptr; } } NS_INTERFACE_MAP_BEGIN(DataStoreService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDataStoreService) NS_INTERFACE_MAP_ENTRY(nsIDataStoreService) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END NS_IMPL_ADDREF(DataStoreService) NS_IMPL_RELEASE(DataStoreService) DataStoreService::DataStoreService() { MOZ_ASSERT(NS_IsMainThread()); } DataStoreService::~DataStoreService() { MOZ_ASSERT(NS_IsMainThread()); } nsresult DataStoreService::Init() { if (!IsMainProcess()) { return NS_OK; } nsCOMPtr obs = mozilla::services::GetObserverService(); if (!obs) { return NS_ERROR_FAILURE; } nsresult rv = obs->AddObserver(this, "webapps-clear-data", false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } NS_IMETHODIMP DataStoreService::InstallDataStore(uint32_t aAppId, const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly) { ASSERT_PARENT_PROCESS() MOZ_ASSERT(NS_IsMainThread()); HashApp* apps = nullptr; if (!mStores.Get(aName, &apps)) { apps = new HashApp(); mStores.Put(aName, apps); } DataStoreInfo* info = nullptr; if (!apps->Get(aAppId, &info)) { info = new DataStoreInfo(aName, aOriginURL, aManifestURL, aReadOnly, false); apps->Put(aAppId, info); } else { info->Update(aName, aOriginURL, aManifestURL, aReadOnly); } nsresult rv = AddPermissions(aAppId, aName, aOriginURL, aManifestURL, aReadOnly); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Immediately create the first revision. return CreateFirstRevisionId(aAppId, aName, aManifestURL); } NS_IMETHODIMP DataStoreService::InstallAccessDataStore(uint32_t aAppId, const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly) { ASSERT_PARENT_PROCESS() MOZ_ASSERT(NS_IsMainThread()); HashApp* apps = nullptr; if (!mAccessStores.Get(aName, &apps)) { apps = new HashApp(); mAccessStores.Put(aName, apps); } DataStoreInfo* info = nullptr; if (!apps->Get(aAppId, &info)) { info = new DataStoreInfo(aName, aOriginURL, aManifestURL, aReadOnly, false); apps->Put(aAppId, info); } else { info->Update(aName, aOriginURL, aManifestURL, aReadOnly); } return AddAccessPermissions(aAppId, aName, aOriginURL, aManifestURL, aReadOnly); } NS_IMETHODIMP DataStoreService::GetDataStores(nsIDOMWindow* aWindow, const nsAString& aName, nsISupports** aDataStores) { // FIXME This will be a thread-safe method. MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr window = do_QueryInterface(aWindow); if (!window) { return NS_ERROR_FAILURE; } nsCOMPtr global = do_QueryInterface(window); nsRefPtr promise = new Promise(global); // If this request comes from the main process, we have access to the // window, so we can skip the ipc communication. if (IsMainProcess()) { nsCOMPtr document = window->GetDoc(); MOZ_ASSERT(document); nsCOMPtr principal = document->NodePrincipal(); MOZ_ASSERT(principal); uint32_t appId; nsresult rv = principal->GetAppId(&appId); if (NS_FAILED(rv)) { RejectPromise(window, promise, rv); promise.forget(aDataStores); return NS_OK; } nsTArray stores; rv = GetDataStoreInfos(aName, appId, stores); if (NS_FAILED(rv)) { RejectPromise(window, promise, rv); promise.forget(aDataStores); return NS_OK; } GetDataStoresCreate(window, promise, stores); promise.forget(aDataStores); return NS_OK; } else { // This method can be called in the child so we need to send a request // to the parent and create DataStore object here. // TODO } promise.forget(aDataStores); return NS_OK; } void DataStoreService::GetDataStoresCreate(nsPIDOMWindow* aWindow, Promise* aPromise, const nsTArray& aStores) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); if (!aStores.Length()) { GetDataStoresResolve(aWindow, aPromise, aStores); return; } nsTArray pendingDataStores; for (uint32_t i = 0; i < aStores.Length(); ++i) { if (!aStores[i].mEnabled) { pendingDataStores.AppendElement(aStores[i].mManifestURL); } } if (!pendingDataStores.Length()) { GetDataStoresResolve(aWindow, aPromise, aStores); return; } PendingRequests* requests; if (!mPendingRequests.Get(aStores[0].mName, &requests)) { requests = new PendingRequests(); mPendingRequests.Put(aStores[0].mName, requests); } PendingRequest request(aWindow, aPromise, aStores, pendingDataStores); requests->AppendElement(request); } void DataStoreService::GetDataStoresResolve(nsPIDOMWindow* aWindow, Promise* aPromise, const nsTArray& aStores) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); if (!aStores.Length()) { nsTArray> results; aPromise->MaybeResolve(results); return; } AutoSafeJSContext cx; // The counter will finish this task once all the DataStores will know their // first revision Ids. nsRefPtr counter = new RetrieveRevisionsCounter(++gCounterID, aPromise, aStores.Length()); mPendingCounters.Put(gCounterID, counter); for (uint32_t i = 0; i < aStores.Length(); ++i) { nsCOMPtr dataStore = do_CreateInstance("@mozilla.org/dom/datastore;1"); if (NS_WARN_IF(!dataStore)) { return; } nsresult rv = dataStore->Init(aWindow, aStores[i].mName, aStores[i].mManifestURL, aStores[i].mReadOnly); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsCOMPtr xpcwrappedjs = do_QueryInterface(dataStore); if (NS_WARN_IF(!xpcwrappedjs)) { return; } JS::Rooted dataStoreJS(cx, xpcwrappedjs->GetJSObject()); if (NS_WARN_IF(!dataStoreJS)) { return; } JSAutoCompartment ac(cx, dataStoreJS); nsRefPtr dataStoreObj = new DataStoreImpl(dataStoreJS, aWindow); nsRefPtr exposedStore = new DataStore(aWindow); ErrorResult error; exposedStore->SetDataStoreImpl(*dataStoreObj, error); if (error.Failed()) { return; } JS::Rooted obj(cx, exposedStore->WrapObject(cx)); MOZ_ASSERT(obj); JS::Rooted exposedObject(cx, JS::ObjectValue(*obj)); dataStore->SetExposedObject(exposedObject); counter->AppendDataStore(cx, exposedStore, dataStore); } } // Thie method populates 'aStores' with the list of DataStores with 'aName' as // name and available for this 'aAppId'. nsresult DataStoreService::GetDataStoreInfos(const nsAString& aName, uint32_t aAppId, nsTArray& aStores) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr appsService = do_GetService("@mozilla.org/AppsService;1"); if (NS_WARN_IF(!appsService)) { return NS_ERROR_FAILURE; } nsCOMPtr app; nsresult rv = appsService->GetAppByLocalId(aAppId, getter_AddRefs(app)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!app) { return NS_ERROR_DOM_SECURITY_ERR; } uint16_t status; rv = app->GetAppStatus(&status); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (status != nsIPrincipal::APP_STATUS_CERTIFIED && !Preferences::GetBool("dom.testing.datastore_enabled_for_hosted_apps", false)) { return NS_ERROR_DOM_SECURITY_ERR; } aStores.Clear(); HashApp* apps = nullptr; if (!mStores.Get(aName, &apps)) { return NS_OK; } DataStoreInfo* info = nullptr; if (apps->Get(aAppId, &info)) { DataStoreInfo owned(info->mName, info->mOriginURL, info->mManifestURL, false, info->mEnabled); aStores.AppendElement(owned); } GetDataStoreInfosData data(mAccessStores, aName, aAppId, aStores); apps->EnumerateRead(GetDataStoreInfosEnumerator, &data); return NS_OK; } // This method is called when an app with DataStores is deleted. void DataStoreService::DeleteDataStores(uint32_t aAppId) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); mStores.Enumerate(DeleteDataStoresEnumerator, &aAppId); mAccessStores.Enumerate(DeleteDataStoresEnumerator, &aAppId); } NS_IMETHODIMP DataStoreService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); if (strcmp(aTopic, "webapps-clear-data")) { return NS_OK; } nsCOMPtr params = do_QueryInterface(aSubject); MOZ_ASSERT(params); // DataStore is explosed to apps, not browser content. bool browserOnly; nsresult rv = params->GetBrowserOnly(&browserOnly); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (browserOnly) { return NS_OK; } uint32_t appId; rv = params->GetAppId(&appId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } DeleteDataStores(appId); return NS_OK; } nsresult DataStoreService::AddPermissions(uint32_t aAppId, const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); // This is the permission name. nsString permission; GeneratePermissionName(permission, aName, aManifestURL); // When a new DataStore is installed, the permissions must be set for the // owner app. nsresult rv = ResetPermission(aAppId, aOriginURL, aManifestURL, permission, aReadOnly); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // For any app that wants to have access to this DataStore we add the // permissions. HashApp* apps; if (!mAccessStores.Get(aName, &apps)) { return NS_OK; } AddPermissionsData data(permission, aReadOnly); apps->EnumerateRead(AddPermissionsEnumerator, &data); return data.mResult; } nsresult DataStoreService::AddAccessPermissions(uint32_t aAppId, const nsAString& aName, const nsAString& aOriginURL, const nsAString& aManifestURL, bool aReadOnly) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); // When an app wants to have access to a DataStore, the permissions must be // set. HashApp* apps = nullptr; if (!mStores.Get(aName, &apps)) { return NS_OK; } AddAccessPermissionsData data(aName, aReadOnly); apps->EnumerateRead(AddAccessPermissionsEnumerator, &data); return data.mResult; } // This method starts the operation to create the first revision for a DataStore // if needed. nsresult DataStoreService::CreateFirstRevisionId(uint32_t aAppId, const nsAString& aName, const nsAString& aManifestURL) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr db = new DataStoreDB(aManifestURL, aName); nsRefPtr callback = new FirstRevisionIdCallback(aAppId, aName, aManifestURL); Sequence dbs; dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION)); return db->Open(IDBTransactionMode::Readwrite, dbs, callback); } nsresult DataStoreService::EnableDataStore(uint32_t aAppId, const nsAString& aName, const nsAString& aManifestURL) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); { HashApp* apps = nullptr; DataStoreInfo* info = nullptr; if (mStores.Get(aName, &apps) && apps->Get(aAppId, &info)) { info->Enable(); } } // Maybe we have some pending request waiting for this DataStore. PendingRequests* requests; if (!mPendingRequests.Get(aName, &requests)) { return NS_OK; } for (uint32_t i = 0; i < requests->Length();) { PendingRequest& request = requests->ElementAt(i); nsTArray::index_type pos = request.mPendingDataStores.IndexOf(aManifestURL); if (pos != request.mPendingDataStores.NoIndex) { request.mPendingDataStores.RemoveElementAt(pos); // No other pending dataStores. if (request.mPendingDataStores.IsEmpty()) { GetDataStoresResolve(request.mWindow, request.mPromise, request.mStores); requests->RemoveElementAt(i); continue; } } ++i; } // No other pending requests for this name. if (requests->IsEmpty()) { mPendingRequests.Remove(aName); } return NS_OK; } already_AddRefed DataStoreService::GetCounter(uint32_t aId) const { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); nsRefPtr counter; return mPendingCounters.Get(aId, getter_AddRefs(counter)) ? counter.forget() : nullptr; } void DataStoreService::RemoveCounter(uint32_t aId) { AssertIsInMainProcess(); MOZ_ASSERT(NS_IsMainThread()); mPendingCounters.Remove(aId); } nsresult DataStoreService::GenerateUUID(nsAString& aID) { nsresult rv; if (!mUUIDGenerator) { mUUIDGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } nsID id; rv = mUUIDGenerator->GenerateUUIDInPlace(&id); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } char chars[NSID_LENGTH]; id.ToProvidedString(chars); CopyASCIItoUTF16(chars, aID); return NS_OK; } } // namespace dom } // namespace mozilla