michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ 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: #include "CheckQuotaHelper.h" michael@0: michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIScriptObjectPrincipal.h" michael@0: #include "nsIURI.h" michael@0: #include "nsPIDOMWindow.h" michael@0: michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: #define TOPIC_QUOTA_PROMPT "indexedDB-quota-prompt" michael@0: #define TOPIC_QUOTA_RESPONSE "indexedDB-quota-response" michael@0: #define TOPIC_QUOTA_CANCEL "indexedDB-quota-cancel" michael@0: michael@0: USING_QUOTA_NAMESPACE michael@0: using namespace mozilla::services; michael@0: using mozilla::MutexAutoLock; michael@0: michael@0: namespace { michael@0: michael@0: inline michael@0: uint32_t michael@0: GetQuotaPermissionFromWindow(nsIDOMWindow* aWindow) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsCOMPtr sop(do_QueryInterface(aWindow)); michael@0: NS_ENSURE_TRUE(sop, nsIPermissionManager::DENY_ACTION); michael@0: michael@0: return CheckQuotaHelper::GetQuotaPermission(sop->GetPrincipal()); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: CheckQuotaHelper::CheckQuotaHelper(nsPIDOMWindow* aWindow, michael@0: mozilla::Mutex& aMutex) michael@0: : mWindow(aWindow), michael@0: mMutex(aMutex), michael@0: mCondVar(mMutex, "CheckQuotaHelper::mCondVar"), michael@0: mPromptResult(0), michael@0: mWaiting(true), michael@0: mHasPrompted(false) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: mMutex.AssertCurrentThreadOwns(); michael@0: } michael@0: michael@0: bool michael@0: CheckQuotaHelper::PromptAndReturnQuotaIsDisabled() michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: mMutex.AssertCurrentThreadOwns(); michael@0: michael@0: while (mWaiting) { michael@0: mCondVar.Wait(); michael@0: } michael@0: michael@0: NS_ASSERTION(!mWindow, "This should always be null here!"); michael@0: michael@0: NS_ASSERTION(mPromptResult == nsIPermissionManager::ALLOW_ACTION || michael@0: mPromptResult == nsIPermissionManager::DENY_ACTION || michael@0: mPromptResult == nsIPermissionManager::UNKNOWN_ACTION, michael@0: "Unknown permission!"); michael@0: michael@0: return mPromptResult == nsIPermissionManager::ALLOW_ACTION; michael@0: } michael@0: michael@0: void michael@0: CheckQuotaHelper::Cancel() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: mMutex.AssertCurrentThreadOwns(); michael@0: michael@0: if (mWaiting && !mHasPrompted) { michael@0: MutexAutoUnlock unlock(mMutex); michael@0: michael@0: // First close any prompts that are open for this window. michael@0: nsCOMPtr obs = GetObserverService(); michael@0: NS_WARN_IF_FALSE(obs, "Failed to get observer service!"); michael@0: if (obs && NS_FAILED(obs->NotifyObservers(static_cast(this), michael@0: TOPIC_QUOTA_CANCEL, nullptr))) { michael@0: NS_WARNING("Failed to notify observers!"); michael@0: } michael@0: michael@0: // If that didn't trigger an Observe callback (maybe the window had already michael@0: // died?) then go ahead and do it manually. michael@0: if (!mHasPrompted) { michael@0: nsAutoString response; michael@0: response.AppendInt(nsIPermissionManager::UNKNOWN_ACTION); michael@0: michael@0: if (NS_SUCCEEDED(Observe(nullptr, TOPIC_QUOTA_RESPONSE, response.get()))) { michael@0: NS_ASSERTION(mHasPrompted, "Should have set this in Observe!"); michael@0: } michael@0: else { michael@0: NS_WARNING("Failed to notify!"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // static michael@0: uint32_t michael@0: CheckQuotaHelper::GetQuotaPermission(nsIPrincipal* aPrincipal) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aPrincipal, "Null principal!"); michael@0: michael@0: NS_ASSERTION(!nsContentUtils::IsSystemPrincipal(aPrincipal), michael@0: "Chrome windows shouldn't track quota!"); michael@0: michael@0: nsCOMPtr pm = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: NS_ENSURE_TRUE(pm, nsIPermissionManager::DENY_ACTION); michael@0: michael@0: uint32_t permission; michael@0: nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal, michael@0: PERMISSION_STORAGE_UNLIMITED, michael@0: &permission); michael@0: NS_ENSURE_SUCCESS(rv, nsIPermissionManager::DENY_ACTION); michael@0: michael@0: return permission; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CheckQuotaHelper, nsIRunnable, michael@0: nsIInterfaceRequestor, michael@0: nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: CheckQuotaHelper::Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (!mHasPrompted) { michael@0: mPromptResult = GetQuotaPermissionFromWindow(mWindow); michael@0: } michael@0: michael@0: if (mHasPrompted) { michael@0: // Add permissions to the database, but only if we are in the parent michael@0: // process (if we are in the child process, we have already michael@0: // set the permission when the prompt was shown in the parent, as michael@0: // we cannot set the permission from the child). michael@0: if (mPromptResult != nsIPermissionManager::UNKNOWN_ACTION && michael@0: XRE_GetProcessType() == GeckoProcessType_Default) { michael@0: nsCOMPtr sop = do_QueryInterface(mWindow); michael@0: NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr permissionManager = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: NS_ENSURE_STATE(permissionManager); michael@0: michael@0: rv = permissionManager->AddFromPrincipal(sop->GetPrincipal(), michael@0: PERMISSION_STORAGE_UNLIMITED, michael@0: mPromptResult, michael@0: nsIPermissionManager::EXPIRE_NEVER, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: else if (mPromptResult == nsIPermissionManager::UNKNOWN_ACTION) { michael@0: uint32_t quota = QuotaManager::GetStorageQuotaMB(); michael@0: michael@0: nsString quotaString; michael@0: quotaString.AppendInt(quota); michael@0: michael@0: nsCOMPtr obs = GetObserverService(); michael@0: NS_ENSURE_STATE(obs); michael@0: michael@0: // We have to watch to make sure that the window doesn't go away without michael@0: // responding to us. Otherwise our database threads will hang. michael@0: rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = obs->NotifyObservers(static_cast(this), michael@0: TOPIC_QUOTA_PROMPT, quotaString.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: MutexAutoLock lock(mMutex); michael@0: michael@0: NS_ASSERTION(mWaiting, "Huh?!"); michael@0: michael@0: // This should never be used again. michael@0: mWindow = nullptr; michael@0: michael@0: mWaiting = false; michael@0: mCondVar.NotifyAll(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CheckQuotaHelper::GetInterface(const nsIID& aIID, michael@0: void** aResult) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (aIID.Equals(NS_GET_IID(nsIObserver))) { michael@0: return QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: if (aIID.Equals(NS_GET_IID(nsIDOMWindow))) { michael@0: return mWindow->QueryInterface(aIID, aResult); michael@0: } michael@0: michael@0: *aResult = nullptr; michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CheckQuotaHelper::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!strcmp(aTopic, TOPIC_QUOTA_RESPONSE)) { michael@0: if (!mHasPrompted) { michael@0: mHasPrompted = true; michael@0: michael@0: mPromptResult = nsDependentString(aData).ToInteger(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = NS_DispatchToCurrentThread(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We no longer care about the window here. michael@0: nsCOMPtr obs = GetObserverService(); michael@0: NS_ENSURE_STATE(obs); michael@0: michael@0: rv = obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { michael@0: NS_ASSERTION(!mHasPrompted, "Should have removed observer before now!"); michael@0: NS_ASSERTION(mWindow, "This should never be null!"); michael@0: michael@0: nsCOMPtr window(do_QueryInterface(aSubject)); michael@0: NS_ENSURE_STATE(window); michael@0: michael@0: if (mWindow->GetSerial() == window->GetSerial()) { michael@0: // This is our window, dying, without responding to our prompt! Fake one. michael@0: mHasPrompted = true; michael@0: mPromptResult = nsIPermissionManager::UNKNOWN_ACTION; michael@0: michael@0: rv = NS_DispatchToCurrentThread(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We no longer care about the window here. michael@0: nsCOMPtr obs = GetObserverService(); michael@0: NS_ENSURE_STATE(obs); michael@0: michael@0: rv = obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_NOTREACHED("Unexpected topic!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: }