1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/quota/CheckQuotaHelper.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,283 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "CheckQuotaHelper.h" 1.11 + 1.12 +#include "nsIDOMWindow.h" 1.13 +#include "nsIObserverService.h" 1.14 +#include "nsIPermissionManager.h" 1.15 +#include "nsIPrincipal.h" 1.16 +#include "nsIScriptObjectPrincipal.h" 1.17 +#include "nsIURI.h" 1.18 +#include "nsPIDOMWindow.h" 1.19 + 1.20 +#include "mozilla/dom/quota/QuotaManager.h" 1.21 +#include "mozilla/Services.h" 1.22 +#include "nsContentUtils.h" 1.23 +#include "nsNetUtil.h" 1.24 +#include "nsThreadUtils.h" 1.25 +#include "nsXULAppAPI.h" 1.26 + 1.27 +#define TOPIC_QUOTA_PROMPT "indexedDB-quota-prompt" 1.28 +#define TOPIC_QUOTA_RESPONSE "indexedDB-quota-response" 1.29 +#define TOPIC_QUOTA_CANCEL "indexedDB-quota-cancel" 1.30 + 1.31 +USING_QUOTA_NAMESPACE 1.32 +using namespace mozilla::services; 1.33 +using mozilla::MutexAutoLock; 1.34 + 1.35 +namespace { 1.36 + 1.37 +inline 1.38 +uint32_t 1.39 +GetQuotaPermissionFromWindow(nsIDOMWindow* aWindow) 1.40 +{ 1.41 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.42 + 1.43 + nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(aWindow)); 1.44 + NS_ENSURE_TRUE(sop, nsIPermissionManager::DENY_ACTION); 1.45 + 1.46 + return CheckQuotaHelper::GetQuotaPermission(sop->GetPrincipal()); 1.47 +} 1.48 + 1.49 +} // anonymous namespace 1.50 + 1.51 +CheckQuotaHelper::CheckQuotaHelper(nsPIDOMWindow* aWindow, 1.52 + mozilla::Mutex& aMutex) 1.53 +: mWindow(aWindow), 1.54 + mMutex(aMutex), 1.55 + mCondVar(mMutex, "CheckQuotaHelper::mCondVar"), 1.56 + mPromptResult(0), 1.57 + mWaiting(true), 1.58 + mHasPrompted(false) 1.59 +{ 1.60 + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 1.61 + mMutex.AssertCurrentThreadOwns(); 1.62 +} 1.63 + 1.64 +bool 1.65 +CheckQuotaHelper::PromptAndReturnQuotaIsDisabled() 1.66 +{ 1.67 + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); 1.68 + mMutex.AssertCurrentThreadOwns(); 1.69 + 1.70 + while (mWaiting) { 1.71 + mCondVar.Wait(); 1.72 + } 1.73 + 1.74 + NS_ASSERTION(!mWindow, "This should always be null here!"); 1.75 + 1.76 + NS_ASSERTION(mPromptResult == nsIPermissionManager::ALLOW_ACTION || 1.77 + mPromptResult == nsIPermissionManager::DENY_ACTION || 1.78 + mPromptResult == nsIPermissionManager::UNKNOWN_ACTION, 1.79 + "Unknown permission!"); 1.80 + 1.81 + return mPromptResult == nsIPermissionManager::ALLOW_ACTION; 1.82 +} 1.83 + 1.84 +void 1.85 +CheckQuotaHelper::Cancel() 1.86 +{ 1.87 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.88 + mMutex.AssertCurrentThreadOwns(); 1.89 + 1.90 + if (mWaiting && !mHasPrompted) { 1.91 + MutexAutoUnlock unlock(mMutex); 1.92 + 1.93 + // First close any prompts that are open for this window. 1.94 + nsCOMPtr<nsIObserverService> obs = GetObserverService(); 1.95 + NS_WARN_IF_FALSE(obs, "Failed to get observer service!"); 1.96 + if (obs && NS_FAILED(obs->NotifyObservers(static_cast<nsIRunnable*>(this), 1.97 + TOPIC_QUOTA_CANCEL, nullptr))) { 1.98 + NS_WARNING("Failed to notify observers!"); 1.99 + } 1.100 + 1.101 + // If that didn't trigger an Observe callback (maybe the window had already 1.102 + // died?) then go ahead and do it manually. 1.103 + if (!mHasPrompted) { 1.104 + nsAutoString response; 1.105 + response.AppendInt(nsIPermissionManager::UNKNOWN_ACTION); 1.106 + 1.107 + if (NS_SUCCEEDED(Observe(nullptr, TOPIC_QUOTA_RESPONSE, response.get()))) { 1.108 + NS_ASSERTION(mHasPrompted, "Should have set this in Observe!"); 1.109 + } 1.110 + else { 1.111 + NS_WARNING("Failed to notify!"); 1.112 + } 1.113 + } 1.114 + } 1.115 +} 1.116 + 1.117 +// static 1.118 +uint32_t 1.119 +CheckQuotaHelper::GetQuotaPermission(nsIPrincipal* aPrincipal) 1.120 +{ 1.121 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.122 + NS_ASSERTION(aPrincipal, "Null principal!"); 1.123 + 1.124 + NS_ASSERTION(!nsContentUtils::IsSystemPrincipal(aPrincipal), 1.125 + "Chrome windows shouldn't track quota!"); 1.126 + 1.127 + nsCOMPtr<nsIPermissionManager> pm = 1.128 + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); 1.129 + NS_ENSURE_TRUE(pm, nsIPermissionManager::DENY_ACTION); 1.130 + 1.131 + uint32_t permission; 1.132 + nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal, 1.133 + PERMISSION_STORAGE_UNLIMITED, 1.134 + &permission); 1.135 + NS_ENSURE_SUCCESS(rv, nsIPermissionManager::DENY_ACTION); 1.136 + 1.137 + return permission; 1.138 +} 1.139 + 1.140 +NS_IMPL_ISUPPORTS(CheckQuotaHelper, nsIRunnable, 1.141 + nsIInterfaceRequestor, 1.142 + nsIObserver) 1.143 + 1.144 +NS_IMETHODIMP 1.145 +CheckQuotaHelper::Run() 1.146 +{ 1.147 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.148 + 1.149 + nsresult rv = NS_OK; 1.150 + 1.151 + if (NS_SUCCEEDED(rv)) { 1.152 + if (!mHasPrompted) { 1.153 + mPromptResult = GetQuotaPermissionFromWindow(mWindow); 1.154 + } 1.155 + 1.156 + if (mHasPrompted) { 1.157 + // Add permissions to the database, but only if we are in the parent 1.158 + // process (if we are in the child process, we have already 1.159 + // set the permission when the prompt was shown in the parent, as 1.160 + // we cannot set the permission from the child). 1.161 + if (mPromptResult != nsIPermissionManager::UNKNOWN_ACTION && 1.162 + XRE_GetProcessType() == GeckoProcessType_Default) { 1.163 + nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mWindow); 1.164 + NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE); 1.165 + 1.166 + nsCOMPtr<nsIPermissionManager> permissionManager = 1.167 + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); 1.168 + NS_ENSURE_STATE(permissionManager); 1.169 + 1.170 + rv = permissionManager->AddFromPrincipal(sop->GetPrincipal(), 1.171 + PERMISSION_STORAGE_UNLIMITED, 1.172 + mPromptResult, 1.173 + nsIPermissionManager::EXPIRE_NEVER, 0); 1.174 + NS_ENSURE_SUCCESS(rv, rv); 1.175 + } 1.176 + } 1.177 + else if (mPromptResult == nsIPermissionManager::UNKNOWN_ACTION) { 1.178 + uint32_t quota = QuotaManager::GetStorageQuotaMB(); 1.179 + 1.180 + nsString quotaString; 1.181 + quotaString.AppendInt(quota); 1.182 + 1.183 + nsCOMPtr<nsIObserverService> obs = GetObserverService(); 1.184 + NS_ENSURE_STATE(obs); 1.185 + 1.186 + // We have to watch to make sure that the window doesn't go away without 1.187 + // responding to us. Otherwise our database threads will hang. 1.188 + rv = obs->AddObserver(this, DOM_WINDOW_DESTROYED_TOPIC, false); 1.189 + NS_ENSURE_SUCCESS(rv, rv); 1.190 + 1.191 + rv = obs->NotifyObservers(static_cast<nsIRunnable*>(this), 1.192 + TOPIC_QUOTA_PROMPT, quotaString.get()); 1.193 + NS_ENSURE_SUCCESS(rv, rv); 1.194 + 1.195 + return NS_OK; 1.196 + } 1.197 + } 1.198 + 1.199 + MutexAutoLock lock(mMutex); 1.200 + 1.201 + NS_ASSERTION(mWaiting, "Huh?!"); 1.202 + 1.203 + // This should never be used again. 1.204 + mWindow = nullptr; 1.205 + 1.206 + mWaiting = false; 1.207 + mCondVar.NotifyAll(); 1.208 + 1.209 + return NS_OK; 1.210 +} 1.211 + 1.212 +NS_IMETHODIMP 1.213 +CheckQuotaHelper::GetInterface(const nsIID& aIID, 1.214 + void** aResult) 1.215 +{ 1.216 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.217 + 1.218 + if (aIID.Equals(NS_GET_IID(nsIObserver))) { 1.219 + return QueryInterface(aIID, aResult); 1.220 + } 1.221 + 1.222 + if (aIID.Equals(NS_GET_IID(nsIDOMWindow))) { 1.223 + return mWindow->QueryInterface(aIID, aResult); 1.224 + } 1.225 + 1.226 + *aResult = nullptr; 1.227 + return NS_ERROR_NOT_AVAILABLE; 1.228 +} 1.229 + 1.230 +NS_IMETHODIMP 1.231 +CheckQuotaHelper::Observe(nsISupports* aSubject, 1.232 + const char* aTopic, 1.233 + const char16_t* aData) 1.234 +{ 1.235 + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1.236 + 1.237 + nsresult rv; 1.238 + 1.239 + if (!strcmp(aTopic, TOPIC_QUOTA_RESPONSE)) { 1.240 + if (!mHasPrompted) { 1.241 + mHasPrompted = true; 1.242 + 1.243 + mPromptResult = nsDependentString(aData).ToInteger(&rv); 1.244 + NS_ENSURE_SUCCESS(rv, rv); 1.245 + 1.246 + rv = NS_DispatchToCurrentThread(this); 1.247 + NS_ENSURE_SUCCESS(rv, rv); 1.248 + 1.249 + // We no longer care about the window here. 1.250 + nsCOMPtr<nsIObserverService> obs = GetObserverService(); 1.251 + NS_ENSURE_STATE(obs); 1.252 + 1.253 + rv = obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); 1.254 + NS_ENSURE_SUCCESS(rv, rv); 1.255 + } 1.256 + return NS_OK; 1.257 + } 1.258 + 1.259 + if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) { 1.260 + NS_ASSERTION(!mHasPrompted, "Should have removed observer before now!"); 1.261 + NS_ASSERTION(mWindow, "This should never be null!"); 1.262 + 1.263 + nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(aSubject)); 1.264 + NS_ENSURE_STATE(window); 1.265 + 1.266 + if (mWindow->GetSerial() == window->GetSerial()) { 1.267 + // This is our window, dying, without responding to our prompt! Fake one. 1.268 + mHasPrompted = true; 1.269 + mPromptResult = nsIPermissionManager::UNKNOWN_ACTION; 1.270 + 1.271 + rv = NS_DispatchToCurrentThread(this); 1.272 + NS_ENSURE_SUCCESS(rv, rv); 1.273 + 1.274 + // We no longer care about the window here. 1.275 + nsCOMPtr<nsIObserverService> obs = GetObserverService(); 1.276 + NS_ENSURE_STATE(obs); 1.277 + 1.278 + rv = obs->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); 1.279 + NS_ENSURE_SUCCESS(rv, rv); 1.280 + } 1.281 + return NS_OK; 1.282 + } 1.283 + 1.284 + NS_NOTREACHED("Unexpected topic!"); 1.285 + return NS_ERROR_UNEXPECTED; 1.286 +}