Bumping manifests a=b2g-bump
[gecko.git] / dom / quota / QuotaManager.cpp
blob74813d55d1c04181c3a234a5b06c2d71591eb63f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "QuotaManager.h"
9 #include "mozIApplicationClearPrivateDataParams.h"
10 #include "nsIBinaryInputStream.h"
11 #include "nsIBinaryOutputStream.h"
12 #include "nsIFile.h"
13 #include "nsIObserverService.h"
14 #include "nsIOfflineStorage.h"
15 #include "nsIPrincipal.h"
16 #include "nsIQuotaRequest.h"
17 #include "nsIRunnable.h"
18 #include "nsISimpleEnumerator.h"
19 #include "nsIScriptObjectPrincipal.h"
20 #include "nsIScriptSecurityManager.h"
21 #include "nsITimer.h"
22 #include "nsIURI.h"
23 #include "nsIUsageCallback.h"
24 #include "nsPIDOMWindow.h"
26 #include <algorithm>
27 #include "GeckoProfiler.h"
28 #include "mozilla/Atomics.h"
29 #include "mozilla/CondVar.h"
30 #include "mozilla/dom/asmjscache/AsmJSCache.h"
31 #include "mozilla/dom/FileService.h"
32 #include "mozilla/dom/indexedDB/Client.h"
33 #include "mozilla/Mutex.h"
34 #include "mozilla/LazyIdleThread.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/Services.h"
37 #include "nsAppDirectoryServiceDefs.h"
38 #include "nsComponentManagerUtils.h"
39 #include "nsAboutProtocolUtils.h"
40 #include "nsContentUtils.h"
41 #include "nsCRTGlue.h"
42 #include "nsDirectoryServiceUtils.h"
43 #include "nsNetUtil.h"
44 #include "nsScriptSecurityManager.h"
45 #include "nsThreadUtils.h"
46 #include "nsXULAppAPI.h"
47 #include "xpcpublic.h"
49 #include "AcquireListener.h"
50 #include "CheckQuotaHelper.h"
51 #include "OriginCollection.h"
52 #include "OriginOrPatternString.h"
53 #include "QuotaObject.h"
54 #include "StorageMatcher.h"
55 #include "UsageInfo.h"
56 #include "Utilities.h"
58 // The amount of time, in milliseconds, that our IO thread will stay alive
59 // after the last event it processes.
60 #define DEFAULT_THREAD_TIMEOUT_MS 30000
62 // The amount of time, in milliseconds, that we will wait for active storage
63 // transactions on shutdown before aborting them.
64 #define DEFAULT_SHUTDOWN_TIMER_MS 30000
66 // Preference that users can set to override DEFAULT_QUOTA_MB
67 #define PREF_STORAGE_QUOTA "dom.indexedDB.warningQuota"
69 // Preference that users can set to override temporary storage smart limit
70 // calculation.
71 #define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit"
72 #define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize"
74 // Preference that is used to enable testing features
75 #define PREF_TESTING_FEATURES "dom.quotaManager.testing"
77 // profile-before-change, when we need to shut down quota manager
78 #define PROFILE_BEFORE_CHANGE_OBSERVER_ID "profile-before-change"
80 // The name of the file that we use to load/save the last access time of an
81 // origin.
82 #define METADATA_FILE_NAME ".metadata"
84 #define PERMISSION_DEFAUT_PERSISTENT_STORAGE "default-persistent-storage"
86 #define KB * 1024ULL
87 #define MB * 1024ULL KB
88 #define GB * 1024ULL MB
90 USING_QUOTA_NAMESPACE
91 using namespace mozilla::dom;
92 using mozilla::dom::FileService;
94 static_assert(
95 static_cast<uint32_t>(StorageType::Persistent) ==
96 static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
97 "Enum values should match.");
99 static_assert(
100 static_cast<uint32_t>(StorageType::Temporary) ==
101 static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
102 "Enum values should match.");
104 BEGIN_QUOTA_NAMESPACE
106 // A struct that contains the information corresponding to a pending or
107 // running operation that requires synchronization (e.g. opening a db,
108 // clearing dbs for an origin, etc).
109 struct SynchronizedOp
111 SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
112 Nullable<PersistenceType> aPersistenceType,
113 const nsACString& aId);
115 ~SynchronizedOp();
117 // Test whether this SynchronizedOp needs to wait for the given op.
118 bool
119 MustWaitFor(const SynchronizedOp& aOp);
121 void
122 DelayRunnable(nsIRunnable* aRunnable);
124 void
125 DispatchDelayedRunnables();
127 const OriginOrPatternString mOriginOrPattern;
128 Nullable<PersistenceType> mPersistenceType;
129 nsCString mId;
130 nsRefPtr<AcquireListener> mListener;
131 nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
132 ArrayCluster<nsIOfflineStorage*> mStorages;
135 class CollectOriginsHelper MOZ_FINAL : public nsRunnable
137 public:
138 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
140 NS_IMETHOD
141 Run();
143 // Blocks the current thread until origins are collected on the main thread.
144 // The returned value contains an aggregate size of those origins.
145 int64_t
146 BlockAndReturnOriginsForEviction(nsTArray<OriginInfo*>& aOriginInfos);
148 private:
149 ~CollectOriginsHelper()
152 uint64_t mMinSizeToBeFreed;
154 mozilla::Mutex& mMutex;
155 mozilla::CondVar mCondVar;
157 // The members below are protected by mMutex.
158 nsTArray<OriginInfo*> mOriginInfos;
159 uint64_t mSizeToBeFreed;
160 bool mWaiting;
163 // Responsible for clearing the storage files for a particular origin on the
164 // IO thread. Created when nsIQuotaManager::ClearStoragesForURI is called.
165 // Runs three times, first on the main thread, next on the IO thread, and then
166 // finally again on the main thread. While on the IO thread the runnable will
167 // actually remove the origin's storage files and the directory that contains
168 // them before dispatching itself back to the main thread. When back on the main
169 // thread the runnable will notify the QuotaManager that the job has been
170 // completed.
171 class OriginClearRunnable MOZ_FINAL : public nsRunnable,
172 public AcquireListener
174 enum CallbackState {
175 // Not yet run.
176 Pending = 0,
178 // Running on the main thread in the callback for OpenAllowed.
179 OpenAllowed,
181 // Running on the IO thread.
184 // Running on the main thread after all work is done.
185 Complete
188 public:
189 NS_DECL_ISUPPORTS_INHERITED
191 OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern,
192 Nullable<PersistenceType> aPersistenceType)
193 : mOriginOrPattern(aOriginOrPattern),
194 mPersistenceType(aPersistenceType),
195 mCallbackState(Pending)
198 NS_IMETHOD
199 Run();
201 // AcquireListener override
202 virtual nsresult
203 OnExclusiveAccessAcquired() MOZ_OVERRIDE;
205 void
206 AdvanceState()
208 switch (mCallbackState) {
209 case Pending:
210 mCallbackState = OpenAllowed;
211 return;
212 case OpenAllowed:
213 mCallbackState = IO;
214 return;
215 case IO:
216 mCallbackState = Complete;
217 return;
218 default:
219 NS_NOTREACHED("Can't advance past Complete!");
223 static void
224 InvalidateOpenedStorages(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
225 void* aClosure);
227 void
228 DeleteFiles(QuotaManager* aQuotaManager,
229 PersistenceType aPersistenceType);
231 private:
232 ~OriginClearRunnable() {}
234 OriginOrPatternString mOriginOrPattern;
235 Nullable<PersistenceType> mPersistenceType;
236 CallbackState mCallbackState;
239 // Responsible for calculating the amount of space taken up by storages of a
240 // certain origin. Created when nsIQuotaManager::GetUsageForURI is called.
241 // May be canceled with nsIQuotaRequest::Cancel. Runs three times, first
242 // on the main thread, next on the IO thread, and then finally again on the main
243 // thread. While on the IO thread the runnable will calculate the size of all
244 // files in the origin's directory before dispatching itself back to the main
245 // thread. When on the main thread the runnable will call the callback and then
246 // notify the QuotaManager that the job has been completed.
247 class AsyncUsageRunnable MOZ_FINAL : public UsageInfo,
248 public nsRunnable,
249 public nsIQuotaRequest
251 enum CallbackState {
252 // Not yet run.
253 Pending = 0,
255 // Running on the main thread in the callback for OpenAllowed.
256 OpenAllowed,
258 // Running on the IO thread.
261 // Running on the main thread after all work is done.
262 Complete,
264 // Running on the main thread after skipping the work
265 Shortcut
268 public:
269 NS_DECL_ISUPPORTS_INHERITED
270 NS_DECL_NSIQUOTAREQUEST
272 AsyncUsageRunnable(uint32_t aAppId,
273 bool aInMozBrowserOnly,
274 const nsACString& aGroup,
275 const OriginOrPatternString& aOrigin,
276 nsIURI* aURI,
277 nsIUsageCallback* aCallback);
279 NS_IMETHOD
280 Run();
282 void
283 AdvanceState()
285 switch (mCallbackState) {
286 case Pending:
287 mCallbackState = OpenAllowed;
288 return;
289 case OpenAllowed:
290 mCallbackState = IO;
291 return;
292 case IO:
293 mCallbackState = Complete;
294 return;
295 default:
296 NS_NOTREACHED("Can't advance past Complete!");
300 nsresult
301 TakeShortcut();
303 private:
304 ~AsyncUsageRunnable() {}
306 // Run calls the RunInternal method and makes sure that we always dispatch
307 // to the main thread in case of an error.
308 inline nsresult
309 RunInternal();
311 nsresult
312 AddToUsage(QuotaManager* aQuotaManager,
313 PersistenceType aPersistenceType);
315 nsCOMPtr<nsIURI> mURI;
316 nsCOMPtr<nsIUsageCallback> mCallback;
317 uint32_t mAppId;
318 nsCString mGroup;
319 OriginOrPatternString mOrigin;
320 CallbackState mCallbackState;
321 bool mInMozBrowserOnly;
324 class ResetOrClearRunnable MOZ_FINAL : public nsRunnable,
325 public AcquireListener
327 enum CallbackState {
328 // Not yet run.
329 Pending = 0,
331 // Running on the main thread in the callback for OpenAllowed.
332 OpenAllowed,
334 // Running on the IO thread.
337 // Running on the main thread after all work is done.
338 Complete
341 public:
342 NS_DECL_ISUPPORTS_INHERITED
344 ResetOrClearRunnable(bool aClear)
345 : mCallbackState(Pending),
346 mClear(aClear)
349 NS_IMETHOD
350 Run();
352 // AcquireListener override
353 virtual nsresult
354 OnExclusiveAccessAcquired() MOZ_OVERRIDE;
356 void
357 AdvanceState()
359 switch (mCallbackState) {
360 case Pending:
361 mCallbackState = OpenAllowed;
362 return;
363 case OpenAllowed:
364 mCallbackState = IO;
365 return;
366 case IO:
367 mCallbackState = Complete;
368 return;
369 default:
370 NS_NOTREACHED("Can't advance past Complete!");
374 static void
375 InvalidateOpenedStorages(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
376 void* aClosure);
378 void
379 DeleteFiles(QuotaManager* aQuotaManager,
380 PersistenceType aPersistenceType);
382 private:
383 ~ResetOrClearRunnable() {}
385 CallbackState mCallbackState;
386 bool mClear;
389 // Responsible for finalizing eviction of certian origins (storage files have
390 // been already cleared, we just need to release IO thread only objects and
391 // allow next synchronized ops for evicted origins). Created when
392 // QuotaManager::FinalizeOriginEviction is called. Runs three times, first
393 // on the main thread, next on the IO thread, and then finally again on the main
394 // thread. While on the IO thread the runnable will release IO thread only
395 // objects before dispatching itself back to the main thread. When back on the
396 // main thread the runnable will call QuotaManager::AllowNextSynchronizedOp.
397 // The runnable can also run in a shortened mode (runs only twice).
398 class FinalizeOriginEvictionRunnable MOZ_FINAL : public nsRunnable
400 enum CallbackState {
401 // Not yet run.
402 Pending = 0,
404 // Running on the main thread in the callback for OpenAllowed.
405 OpenAllowed,
407 // Running on the IO thread.
410 // Running on the main thread after IO work is done.
411 Complete
414 public:
415 FinalizeOriginEvictionRunnable(nsTArray<nsCString>& aOrigins)
416 : mCallbackState(Pending)
418 mOrigins.SwapElements(aOrigins);
421 NS_IMETHOD
422 Run();
424 void
425 AdvanceState()
427 switch (mCallbackState) {
428 case Pending:
429 mCallbackState = OpenAllowed;
430 return;
431 case OpenAllowed:
432 mCallbackState = IO;
433 return;
434 case IO:
435 mCallbackState = Complete;
436 return;
437 default:
438 MOZ_ASSERT_UNREACHABLE("Can't advance past Complete!");
442 nsresult
443 Dispatch();
445 nsresult
446 RunImmediately();
448 private:
449 CallbackState mCallbackState;
450 nsTArray<nsCString> mOrigins;
453 bool
454 IsOnIOThread()
456 QuotaManager* quotaManager = QuotaManager::Get();
457 NS_ASSERTION(quotaManager, "Must have a manager here!");
459 bool currentThread;
460 return NS_SUCCEEDED(quotaManager->IOThread()->
461 IsOnCurrentThread(&currentThread)) && currentThread;
464 void
465 AssertIsOnIOThread()
467 NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
470 void
471 AssertCurrentThreadOwnsQuotaMutex()
473 #ifdef DEBUG
474 QuotaManager* quotaManager = QuotaManager::Get();
475 NS_ASSERTION(quotaManager, "Must have a manager here!");
477 quotaManager->AssertCurrentThreadOwnsQuotaMutex();
478 #endif
481 END_QUOTA_NAMESPACE
483 namespace {
485 // Amount of space that storages may use by default in megabytes.
486 static const int32_t kDefaultQuotaMB = 50;
489 QuotaManager* gInstance = nullptr;
490 mozilla::Atomic<bool> gShutdown(false);
492 int32_t gStorageQuotaMB = kDefaultQuotaMB;
494 // Constants for temporary storage limit computing.
495 static const int32_t kDefaultFixedLimitKB = -1;
496 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
497 int32_t gFixedLimitKB = kDefaultFixedLimitKB;
498 uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
500 bool gTestingEnabled = false;
502 // A callback runnable used by the TransactionPool when it's safe to proceed
503 // with a SetVersion/DeleteDatabase/etc.
504 class WaitForTransactionsToFinishRunnable MOZ_FINAL : public nsRunnable
506 public:
507 WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
508 : mOp(aOp), mCountdown(1)
510 NS_ASSERTION(mOp, "Why don't we have a runnable?");
511 NS_ASSERTION(mOp->mStorages.IsEmpty(), "We're here too early!");
512 NS_ASSERTION(mOp->mListener,
513 "What are we supposed to do when we're done?");
514 NS_ASSERTION(mCountdown, "Wrong countdown!");
517 NS_IMETHOD
518 Run();
520 void
521 AddRun()
523 mCountdown++;
526 private:
527 // The QuotaManager holds this alive.
528 SynchronizedOp* mOp;
529 uint32_t mCountdown;
532 class WaitForFileHandlesToFinishRunnable MOZ_FINAL : public nsRunnable
534 public:
535 WaitForFileHandlesToFinishRunnable()
536 : mBusy(true)
539 NS_IMETHOD
540 Run();
542 bool
543 IsBusy() const
545 return mBusy;
548 private:
549 bool mBusy;
552 class SaveOriginAccessTimeRunnable MOZ_FINAL : public nsRunnable
554 public:
555 SaveOriginAccessTimeRunnable(const nsACString& aOrigin, int64_t aTimestamp)
556 : mOrigin(aOrigin), mTimestamp(aTimestamp)
559 NS_IMETHOD
560 Run();
562 private:
563 nsCString mOrigin;
564 int64_t mTimestamp;
567 struct MOZ_STACK_CLASS RemoveQuotaInfo
569 RemoveQuotaInfo(PersistenceType aPersistenceType, const nsACString& aPattern)
570 : persistenceType(aPersistenceType), pattern(aPattern)
573 PersistenceType persistenceType;
574 nsCString pattern;
577 struct MOZ_STACK_CLASS InactiveOriginsInfo
579 InactiveOriginsInfo(OriginCollection& aCollection,
580 nsTArray<OriginInfo*>& aOrigins)
581 : collection(aCollection), origins(aOrigins)
584 OriginCollection& collection;
585 nsTArray<OriginInfo*>& origins;
588 bool
589 IsMainProcess()
591 return XRE_GetProcessType() == GeckoProcessType_Default;
594 void
595 SanitizeOriginString(nsCString& aOrigin)
597 // We want profiles to be platform-independent so we always need to replace
598 // the same characters on every platform. Windows has the most extensive set
599 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
600 // FILE_PATH_SEPARATOR.
601 static const char kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
603 #ifdef XP_WIN
604 NS_ASSERTION(!strcmp(kReplaceChars,
605 FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
606 "Illegal file characters have changed!");
607 #endif
609 aOrigin.ReplaceChar(kReplaceChars, '+');
612 nsresult
613 EnsureDirectory(nsIFile* aDirectory, bool* aCreated)
615 AssertIsOnIOThread();
617 nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
618 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
619 bool isDirectory;
620 rv = aDirectory->IsDirectory(&isDirectory);
621 NS_ENSURE_SUCCESS(rv, rv);
622 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
624 *aCreated = false;
626 else {
627 NS_ENSURE_SUCCESS(rv, rv);
629 *aCreated = true;
632 return NS_OK;
635 nsresult
636 CreateDirectoryUpgradeStamp(nsIFile* aDirectory)
638 AssertIsOnIOThread();
640 nsCOMPtr<nsIFile> metadataFile;
641 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
642 NS_ENSURE_SUCCESS(rv, rv);
644 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
645 NS_ENSURE_SUCCESS(rv, rv);
647 rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
648 NS_ENSURE_SUCCESS(rv, rv);
650 return NS_OK;
653 nsresult
654 GetDirectoryMetadataStream(nsIFile* aDirectory, bool aUpdate,
655 nsIBinaryOutputStream** aStream)
657 AssertIsOnIOThread();
659 nsCOMPtr<nsIFile> metadataFile;
660 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
661 NS_ENSURE_SUCCESS(rv, rv);
663 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
664 NS_ENSURE_SUCCESS(rv, rv);
666 nsCOMPtr<nsIOutputStream> outputStream;
667 if (aUpdate) {
668 bool exists;
669 rv = metadataFile->Exists(&exists);
670 NS_ENSURE_SUCCESS(rv, rv);
672 if (!exists) {
673 *aStream = nullptr;
674 return NS_OK;
677 nsCOMPtr<nsIFileStream> stream;
678 rv = NS_NewLocalFileStream(getter_AddRefs(stream), metadataFile);
679 NS_ENSURE_SUCCESS(rv, rv);
681 outputStream = do_QueryInterface(stream);
682 NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE);
684 else {
685 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
686 metadataFile);
687 NS_ENSURE_SUCCESS(rv, rv);
690 nsCOMPtr<nsIBinaryOutputStream> binaryStream =
691 do_CreateInstance("@mozilla.org/binaryoutputstream;1");
692 NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE);
694 rv = binaryStream->SetOutputStream(outputStream);
695 NS_ENSURE_SUCCESS(rv, rv);
697 binaryStream.forget(aStream);
698 return NS_OK;
701 nsresult
702 CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
703 const nsACString& aGroup, const nsACString& aOrigin)
705 AssertIsOnIOThread();
707 nsCOMPtr<nsIBinaryOutputStream> stream;
708 nsresult rv =
709 GetDirectoryMetadataStream(aDirectory, false, getter_AddRefs(stream));
710 NS_ENSURE_SUCCESS(rv, rv);
712 NS_ASSERTION(stream, "This shouldn't be null!");
714 rv = stream->Write64(aTimestamp);
715 NS_ENSURE_SUCCESS(rv, rv);
717 rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get());
718 NS_ENSURE_SUCCESS(rv, rv);
720 rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get());
721 NS_ENSURE_SUCCESS(rv, rv);
723 return NS_OK;
726 nsresult
727 GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp,
728 nsACString& aGroup, nsACString& aOrigin)
730 AssertIsOnIOThread();
732 nsCOMPtr<nsIFile> metadataFile;
733 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
734 NS_ENSURE_SUCCESS(rv, rv);
736 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
737 NS_ENSURE_SUCCESS(rv, rv);
739 nsCOMPtr<nsIInputStream> stream;
740 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metadataFile);
741 NS_ENSURE_SUCCESS(rv, rv);
743 nsCOMPtr<nsIInputStream> bufferedStream;
744 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512);
745 NS_ENSURE_SUCCESS(rv, rv);
747 nsCOMPtr<nsIBinaryInputStream> binaryStream =
748 do_CreateInstance("@mozilla.org/binaryinputstream;1");
749 NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE);
751 rv = binaryStream->SetInputStream(bufferedStream);
752 NS_ENSURE_SUCCESS(rv, rv);
754 uint64_t timestamp;
755 rv = binaryStream->Read64(&timestamp);
756 NS_ENSURE_SUCCESS(rv, rv);
758 nsCString group;
759 rv = binaryStream->ReadCString(group);
760 NS_ENSURE_SUCCESS(rv, rv);
762 nsCString origin;
763 rv = binaryStream->ReadCString(origin);
764 NS_ENSURE_SUCCESS(rv, rv);
766 *aTimestamp = timestamp;
767 aGroup = group;
768 aOrigin = origin;
769 return NS_OK;
772 nsresult
773 MaybeUpgradeOriginDirectory(nsIFile* aDirectory)
775 AssertIsOnIOThread();
776 NS_ASSERTION(aDirectory, "Null pointer!");
778 nsCOMPtr<nsIFile> metadataFile;
779 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
780 NS_ENSURE_SUCCESS(rv, rv);
782 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
783 NS_ENSURE_SUCCESS(rv, rv);
785 bool exists;
786 rv = metadataFile->Exists(&exists);
787 NS_ENSURE_SUCCESS(rv, rv);
789 if (!exists) {
790 // Directory structure upgrade needed.
791 // Move all files to IDB specific directory.
793 nsString idbDirectoryName;
794 rv = Client::TypeToText(Client::IDB, idbDirectoryName);
795 NS_ENSURE_SUCCESS(rv, rv);
797 nsCOMPtr<nsIFile> idbDirectory;
798 rv = aDirectory->Clone(getter_AddRefs(idbDirectory));
799 NS_ENSURE_SUCCESS(rv, rv);
801 rv = idbDirectory->Append(idbDirectoryName);
802 NS_ENSURE_SUCCESS(rv, rv);
804 rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
805 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
806 NS_WARNING("IDB directory already exists!");
808 bool isDirectory;
809 rv = idbDirectory->IsDirectory(&isDirectory);
810 NS_ENSURE_SUCCESS(rv, rv);
811 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
813 else {
814 NS_ENSURE_SUCCESS(rv, rv);
817 nsCOMPtr<nsISimpleEnumerator> entries;
818 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
819 NS_ENSURE_SUCCESS(rv, rv);
821 bool hasMore;
822 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
823 nsCOMPtr<nsISupports> entry;
824 rv = entries->GetNext(getter_AddRefs(entry));
825 NS_ENSURE_SUCCESS(rv, rv);
827 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
828 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
830 nsString leafName;
831 rv = file->GetLeafName(leafName);
832 NS_ENSURE_SUCCESS(rv, rv);
834 if (!leafName.Equals(idbDirectoryName)) {
835 rv = file->MoveTo(idbDirectory, EmptyString());
836 NS_ENSURE_SUCCESS(rv, rv);
840 rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
841 NS_ENSURE_SUCCESS(rv, rv);
844 return NS_OK;
847 // This method computes and returns our best guess for the temporary storage
848 // limit (in bytes), based on the amount of space users have free on their hard
849 // drive and on given temporary storage usage (also in bytes).
850 nsresult
851 GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage,
852 uint64_t* aLimit)
854 // Check for free space on device where temporary storage directory lives.
855 int64_t bytesAvailable;
856 nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable);
857 NS_ENSURE_SUCCESS(rv, rv);
859 NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!");
861 uint64_t availableKB =
862 static_cast<uint64_t>((bytesAvailable + aCurrentUsage) / 1024);
864 // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case
865 // we don't shrink temporary storage and evict origin data every time we
866 // initialize.
867 availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB;
869 // Allow temporary storage to consume up to half the available space.
870 uint64_t resultKB = availableKB * .50;
872 *aLimit = resultKB * 1024;
873 return NS_OK;
876 } // anonymous namespace
878 QuotaManager::QuotaManager()
879 : mCurrentWindowIndex(BAD_TLS_INDEX),
880 mQuotaMutex("QuotaManager.mQuotaMutex"),
881 mTemporaryStorageLimit(0),
882 mTemporaryStorageUsage(0),
883 mTemporaryStorageInitialized(false),
884 mStorageAreaInitialized(false)
886 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
887 NS_ASSERTION(!gInstance, "More than one instance!");
890 QuotaManager::~QuotaManager()
892 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
893 NS_ASSERTION(!gInstance || gInstance == this, "Different instances!");
894 gInstance = nullptr;
897 // static
898 QuotaManager*
899 QuotaManager::GetOrCreate()
901 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
903 if (IsShuttingDown()) {
904 NS_ERROR("Calling GetOrCreate() after shutdown!");
905 return nullptr;
908 if (!gInstance) {
909 nsRefPtr<QuotaManager> instance(new QuotaManager());
911 nsresult rv = instance->Init();
912 NS_ENSURE_SUCCESS(rv, nullptr);
914 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
915 NS_ENSURE_TRUE(obs, nullptr);
917 // We need this callback to know when to shut down all our threads.
918 rv = obs->AddObserver(instance, PROFILE_BEFORE_CHANGE_OBSERVER_ID, false);
919 NS_ENSURE_SUCCESS(rv, nullptr);
921 // The observer service will hold our last reference, don't AddRef here.
922 gInstance = instance;
925 return gInstance;
928 // static
929 QuotaManager*
930 QuotaManager::Get()
932 // Does not return an owning reference.
933 return gInstance;
936 // static
937 QuotaManager*
938 QuotaManager::FactoryCreate()
940 // Returns a raw pointer that carries an owning reference! Lame, but the
941 // singleton factory macros force this.
942 QuotaManager* quotaManager = GetOrCreate();
943 NS_IF_ADDREF(quotaManager);
944 return quotaManager;
947 // static
948 bool
949 QuotaManager::IsShuttingDown()
951 return gShutdown;
954 nsresult
955 QuotaManager::Init()
957 // We need a thread-local to hold the current window.
958 NS_ASSERTION(mCurrentWindowIndex == BAD_TLS_INDEX, "Huh?");
960 if (PR_NewThreadPrivateIndex(&mCurrentWindowIndex, nullptr) != PR_SUCCESS) {
961 NS_ERROR("PR_NewThreadPrivateIndex failed, QuotaManager disabled");
962 mCurrentWindowIndex = BAD_TLS_INDEX;
963 return NS_ERROR_FAILURE;
966 nsresult rv;
967 if (IsMainProcess()) {
968 nsCOMPtr<nsIFile> baseDir;
969 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
970 getter_AddRefs(baseDir));
971 if (NS_FAILED(rv)) {
972 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
973 getter_AddRefs(baseDir));
975 NS_ENSURE_SUCCESS(rv, rv);
977 nsCOMPtr<nsIFile> indexedDBDir;
978 rv = baseDir->Clone(getter_AddRefs(indexedDBDir));
979 NS_ENSURE_SUCCESS(rv, rv);
981 rv = indexedDBDir->Append(NS_LITERAL_STRING("indexedDB"));
982 NS_ENSURE_SUCCESS(rv, rv);
984 rv = indexedDBDir->GetPath(mIndexedDBPath);
985 NS_ENSURE_SUCCESS(rv, rv);
987 rv = baseDir->Append(NS_LITERAL_STRING("storage"));
988 NS_ENSURE_SUCCESS(rv, rv);
990 nsCOMPtr<nsIFile> persistentStorageDir;
991 rv = baseDir->Clone(getter_AddRefs(persistentStorageDir));
992 NS_ENSURE_SUCCESS(rv, rv);
994 rv = persistentStorageDir->Append(NS_LITERAL_STRING("persistent"));
995 NS_ENSURE_SUCCESS(rv, rv);
997 rv = persistentStorageDir->GetPath(mPersistentStoragePath);
998 NS_ENSURE_SUCCESS(rv, rv);
1000 nsCOMPtr<nsIFile> temporaryStorageDir;
1001 rv = baseDir->Clone(getter_AddRefs(temporaryStorageDir));
1002 NS_ENSURE_SUCCESS(rv, rv);
1004 rv = temporaryStorageDir->Append(NS_LITERAL_STRING("temporary"));
1005 NS_ENSURE_SUCCESS(rv, rv);
1007 rv = temporaryStorageDir->GetPath(mTemporaryStoragePath);
1008 NS_ENSURE_SUCCESS(rv, rv);
1010 // Make a lazy thread for any IO we need (like clearing or enumerating the
1011 // contents of storage directories).
1012 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
1013 NS_LITERAL_CSTRING("Storage I/O"),
1014 LazyIdleThread::ManualShutdown);
1016 // Make a timer here to avoid potential failures later. We don't actually
1017 // initialize the timer until shutdown.
1018 mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
1019 NS_ENSURE_TRUE(mShutdownTimer, NS_ERROR_FAILURE);
1022 if (NS_FAILED(Preferences::AddIntVarCache(&gStorageQuotaMB,
1023 PREF_STORAGE_QUOTA,
1024 kDefaultQuotaMB))) {
1025 NS_WARNING("Unable to respond to quota pref changes!");
1028 if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT,
1029 kDefaultFixedLimitKB)) ||
1030 NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB,
1031 PREF_CHUNK_SIZE,
1032 kDefaultChunkSizeKB))) {
1033 NS_WARNING("Unable to respond to temp storage pref changes!");
1036 if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled,
1037 PREF_TESTING_FEATURES, false))) {
1038 NS_WARNING("Unable to respond to testing pref changes!");
1041 static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::TYPE_MAX == 2,
1042 "Fix the registration!");
1044 NS_ASSERTION(mClients.Capacity() == Client::TYPE_MAX,
1045 "Should be using an auto array with correct capacity!");
1047 // Register IndexedDB
1048 mClients.AppendElement(new indexedDB::Client());
1049 mClients.AppendElement(asmjscache::CreateClient());
1051 return NS_OK;
1054 void
1055 QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType,
1056 const nsACString& aGroup,
1057 const nsACString& aOrigin,
1058 uint64_t aLimitBytes,
1059 uint64_t aUsageBytes,
1060 int64_t aAccessTime)
1062 AssertIsOnIOThread();
1063 MOZ_ASSERT(aLimitBytes > 0 ||
1064 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
1065 MOZ_ASSERT(aUsageBytes <= aLimitBytes ||
1066 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
1068 MutexAutoLock lock(mQuotaMutex);
1070 GroupInfoPair* pair;
1071 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1072 pair = new GroupInfoPair();
1073 mGroupInfoPairs.Put(aGroup, pair);
1074 // The hashtable is now responsible to delete the GroupInfoPair.
1077 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1078 if (!groupInfo) {
1079 groupInfo = new GroupInfo(aPersistenceType, aGroup);
1080 pair->LockedSetGroupInfo(groupInfo);
1083 nsRefPtr<OriginInfo> originInfo =
1084 new OriginInfo(groupInfo, aOrigin, aLimitBytes, aUsageBytes, aAccessTime);
1085 groupInfo->LockedAddOriginInfo(originInfo);
1088 void
1089 QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
1090 const nsACString& aGroup,
1091 const nsACString& aOrigin,
1092 int64_t aSize)
1094 AssertIsOnIOThread();
1096 MutexAutoLock lock(mQuotaMutex);
1098 GroupInfoPair* pair;
1099 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1100 return;
1103 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1104 if (!groupInfo) {
1105 return;
1108 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1109 if (originInfo) {
1110 originInfo->LockedDecreaseUsage(aSize);
1114 void
1115 QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType,
1116 const nsACString& aGroup,
1117 const nsACString& aOrigin)
1119 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1121 MutexAutoLock lock(mQuotaMutex);
1123 GroupInfoPair* pair;
1124 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1125 return;
1128 nsRefPtr<GroupInfo> groupInfo =
1129 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
1130 if (!groupInfo) {
1131 return;
1134 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1135 if (originInfo) {
1136 int64_t timestamp = PR_Now();
1137 originInfo->LockedUpdateAccessTime(timestamp);
1139 if (!groupInfo->IsForTemporaryStorage()) {
1140 return;
1143 MutexAutoUnlock autoUnlock(mQuotaMutex);
1145 SaveOriginAccessTime(aOrigin, timestamp);
1149 // static
1150 PLDHashOperator
1151 QuotaManager::RemoveQuotaCallback(const nsACString& aKey,
1152 nsAutoPtr<GroupInfoPair>& aValue,
1153 void* aUserArg)
1155 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1156 NS_ASSERTION(aValue, "Null pointer!");
1158 nsRefPtr<GroupInfo> groupInfo =
1159 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
1160 if (groupInfo) {
1161 groupInfo->LockedRemoveOriginInfos();
1164 return PL_DHASH_REMOVE;
1167 void
1168 QuotaManager::RemoveQuota()
1170 MutexAutoLock lock(mQuotaMutex);
1172 mGroupInfoPairs.Enumerate(RemoveQuotaCallback, nullptr);
1174 NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
1177 // static
1178 PLDHashOperator
1179 QuotaManager::RemoveQuotaForPersistenceTypeCallback(
1180 const nsACString& aKey,
1181 nsAutoPtr<GroupInfoPair>& aValue,
1182 void* aUserArg)
1184 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1185 NS_ASSERTION(aValue, "Null pointer!");
1186 NS_ASSERTION(aUserArg, "Null pointer!");
1188 PersistenceType& persistenceType = *static_cast<PersistenceType*>(aUserArg);
1190 if (persistenceType == PERSISTENCE_TYPE_TEMPORARY) {
1191 nsRefPtr<GroupInfo> groupInfo =
1192 aValue->LockedGetGroupInfo(persistenceType);
1193 if (groupInfo) {
1194 groupInfo->LockedRemoveOriginInfos();
1198 aValue->LockedClearGroupInfo(persistenceType);
1200 return aValue->LockedHasGroupInfos() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
1203 void
1204 QuotaManager::RemoveQuotaForPersistenceType(PersistenceType aPersistenceType)
1206 MutexAutoLock lock(mQuotaMutex);
1208 mGroupInfoPairs.Enumerate(RemoveQuotaForPersistenceTypeCallback,
1209 &aPersistenceType);
1211 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
1212 mTemporaryStorageUsage == 0, "Should be zero!");
1215 // static
1216 PLDHashOperator
1217 QuotaManager::RemoveQuotaForPatternCallback(const nsACString& aKey,
1218 nsAutoPtr<GroupInfoPair>& aValue,
1219 void* aUserArg)
1221 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1222 NS_ASSERTION(aValue, "Null pointer!");
1223 NS_ASSERTION(aUserArg, "Null pointer!");
1225 RemoveQuotaInfo* info = static_cast<RemoveQuotaInfo*>(aUserArg);
1227 nsRefPtr<GroupInfo> groupInfo =
1228 aValue->LockedGetGroupInfo(info->persistenceType);
1229 if (groupInfo) {
1230 groupInfo->LockedRemoveOriginInfosForPattern(info->pattern);
1232 if (!groupInfo->LockedHasOriginInfos()) {
1233 aValue->LockedClearGroupInfo(info->persistenceType);
1235 if (!aValue->LockedHasGroupInfos()) {
1236 return PL_DHASH_REMOVE;
1241 return PL_DHASH_NEXT;
1244 void
1245 QuotaManager::RemoveQuotaForPattern(PersistenceType aPersistenceType,
1246 const nsACString& aPattern)
1248 NS_ASSERTION(!aPattern.IsEmpty(), "Empty pattern!");
1250 RemoveQuotaInfo info(aPersistenceType, aPattern);
1252 MutexAutoLock lock(mQuotaMutex);
1254 mGroupInfoPairs.Enumerate(RemoveQuotaForPatternCallback, &info);
1257 already_AddRefed<QuotaObject>
1258 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
1259 const nsACString& aGroup,
1260 const nsACString& aOrigin,
1261 nsIFile* aFile)
1263 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
1265 nsString path;
1266 nsresult rv = aFile->GetPath(path);
1267 NS_ENSURE_SUCCESS(rv, nullptr);
1269 int64_t fileSize;
1271 bool exists;
1272 rv = aFile->Exists(&exists);
1273 NS_ENSURE_SUCCESS(rv, nullptr);
1275 if (exists) {
1276 rv = aFile->GetFileSize(&fileSize);
1277 NS_ENSURE_SUCCESS(rv, nullptr);
1279 else {
1280 fileSize = 0;
1283 nsRefPtr<QuotaObject> result;
1285 MutexAutoLock lock(mQuotaMutex);
1287 GroupInfoPair* pair;
1288 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1289 return nullptr;
1292 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1294 if (!groupInfo) {
1295 return nullptr;
1298 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1300 if (!originInfo) {
1301 return nullptr;
1304 // We need this extra raw pointer because we can't assign to the smart
1305 // pointer directly since QuotaObject::AddRef would try to acquire the same
1306 // mutex.
1307 QuotaObject* quotaObject;
1308 if (!originInfo->mQuotaObjects.Get(path, &quotaObject)) {
1309 // Create a new QuotaObject.
1310 quotaObject = new QuotaObject(originInfo, path, fileSize);
1312 // Put it to the hashtable. The hashtable is not responsible to delete
1313 // the QuotaObject.
1314 originInfo->mQuotaObjects.Put(path, quotaObject);
1317 // Addref the QuotaObject and move the ownership to the result. This must
1318 // happen before we unlock!
1319 result = quotaObject->LockedAddRef();
1322 // The caller becomes the owner of the QuotaObject, that is, the caller is
1323 // is responsible to delete it when the last reference is removed.
1324 return result.forget();
1327 already_AddRefed<QuotaObject>
1328 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
1329 const nsACString& aGroup,
1330 const nsACString& aOrigin,
1331 const nsAString& aPath)
1333 nsresult rv;
1334 nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1335 NS_ENSURE_SUCCESS(rv, nullptr);
1337 rv = file->InitWithPath(aPath);
1338 NS_ENSURE_SUCCESS(rv, nullptr);
1340 return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file);
1343 bool
1344 QuotaManager::RegisterStorage(nsIOfflineStorage* aStorage)
1346 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1347 NS_ASSERTION(aStorage, "Null pointer!");
1349 // Don't allow any new storages to be created after shutdown.
1350 if (IsShuttingDown()) {
1351 return false;
1354 // Add this storage to its origin info if it exists, create it otherwise.
1355 const nsACString& origin = aStorage->Origin();
1356 ArrayCluster<nsIOfflineStorage*>* cluster;
1357 if (!mLiveStorages.Get(origin, &cluster)) {
1358 cluster = new ArrayCluster<nsIOfflineStorage*>();
1359 mLiveStorages.Put(origin, cluster);
1361 UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
1363 (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage);
1365 return true;
1368 void
1369 QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage)
1371 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1372 NS_ASSERTION(aStorage, "Null pointer!");
1374 // Remove this storage from its origin array, maybe remove the array if it
1375 // is then empty.
1376 const nsACString& origin = aStorage->Origin();
1377 ArrayCluster<nsIOfflineStorage*>* cluster;
1378 if (mLiveStorages.Get(origin, &cluster) &&
1379 (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage)) {
1380 if (cluster->IsEmpty()) {
1381 mLiveStorages.Remove(origin);
1383 UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
1385 return;
1387 NS_ERROR("Didn't know anything about this storage!");
1390 void
1391 QuotaManager::OnStorageClosed(nsIOfflineStorage* aStorage)
1393 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1394 NS_ASSERTION(aStorage, "Null pointer!");
1396 // Check through the list of SynchronizedOps to see if any are waiting for
1397 // this storage to close before proceeding.
1398 SynchronizedOp* op =
1399 FindSynchronizedOp(aStorage->Origin(),
1400 Nullable<PersistenceType>(aStorage->Type()),
1401 aStorage->Id());
1402 if (op) {
1403 Client::Type clientType = aStorage->GetClient()->GetType();
1405 // This storage is in the scope of this SynchronizedOp. Remove it
1406 // from the list if necessary.
1407 if (op->mStorages[clientType].RemoveElement(aStorage)) {
1408 // Now set up the helper if there are no more live storages.
1409 NS_ASSERTION(op->mListener,
1410 "How did we get rid of the listener before removing the "
1411 "last storage?");
1412 if (op->mStorages[clientType].IsEmpty()) {
1413 // At this point, all storages are closed, so no new transactions
1414 // can be started. There may, however, still be outstanding
1415 // transactions that have not completed. We need to wait for those
1416 // before we dispatch the helper.
1417 if (NS_FAILED(RunSynchronizedOp(aStorage, op))) {
1418 NS_WARNING("Failed to run synchronized op!");
1425 void
1426 QuotaManager::AbortCloseStoragesForWindow(nsPIDOMWindow* aWindow)
1428 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1429 NS_ASSERTION(aWindow, "Null pointer!");
1431 FileService* service = FileService::Get();
1433 StorageMatcher<ArrayCluster<nsIOfflineStorage*> > liveStorages;
1434 liveStorages.Find(mLiveStorages);
1436 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
1437 nsRefPtr<Client>& client = mClients[i];
1438 bool utilized = service && client->IsFileServiceUtilized();
1439 bool activated = client->IsTransactionServiceActivated();
1441 nsTArray<nsIOfflineStorage*>& array = liveStorages[i];
1442 for (uint32_t j = 0; j < array.Length(); j++) {
1443 nsIOfflineStorage*& storage = array[j];
1445 if (storage->IsOwned(aWindow)) {
1446 if (NS_FAILED(storage->Close())) {
1447 NS_WARNING("Failed to close storage for dying window!");
1450 if (utilized) {
1451 service->AbortFileHandlesForStorage(storage);
1454 if (activated) {
1455 client->AbortTransactionsForStorage(storage);
1462 bool
1463 QuotaManager::HasOpenTransactions(nsPIDOMWindow* aWindow)
1465 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1466 NS_ASSERTION(aWindow, "Null pointer!");
1468 FileService* service = FileService::Get();
1470 nsAutoPtr<StorageMatcher<ArrayCluster<nsIOfflineStorage*> > > liveStorages;
1472 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
1473 nsRefPtr<Client>& client = mClients[i];
1474 bool utilized = service && client->IsFileServiceUtilized();
1475 bool activated = client->IsTransactionServiceActivated();
1477 if (utilized || activated) {
1478 if (!liveStorages) {
1479 liveStorages = new StorageMatcher<ArrayCluster<nsIOfflineStorage*> >();
1480 liveStorages->Find(mLiveStorages);
1483 nsTArray<nsIOfflineStorage*>& storages = liveStorages->ArrayAt(i);
1484 for (uint32_t j = 0; j < storages.Length(); j++) {
1485 nsIOfflineStorage*& storage = storages[j];
1487 if (storage->IsOwned(aWindow) &&
1488 ((utilized && service->HasFileHandlesForStorage(storage)) ||
1489 (activated && client->HasTransactionsForStorage(storage)))) {
1490 return true;
1496 return false;
1499 nsresult
1500 QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
1501 Nullable<PersistenceType> aPersistenceType,
1502 const nsACString& aId, nsIRunnable* aRunnable)
1504 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1505 NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
1506 "Empty pattern!");
1507 NS_ASSERTION(aRunnable, "Null pointer!");
1509 nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
1510 aPersistenceType, aId));
1512 // See if this runnable needs to wait.
1513 bool delayed = false;
1514 for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
1515 nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
1516 if (op->MustWaitFor(*existingOp)) {
1517 existingOp->DelayRunnable(aRunnable);
1518 delayed = true;
1519 break;
1523 // Otherwise, dispatch it immediately.
1524 if (!delayed) {
1525 nsresult rv = NS_DispatchToCurrentThread(aRunnable);
1526 NS_ENSURE_SUCCESS(rv, rv);
1529 // Adding this to the synchronized ops list will block any additional
1530 // ops from proceeding until this one is done.
1531 mSynchronizedOps.AppendElement(op.forget());
1533 return NS_OK;
1536 void
1537 QuotaManager::AddSynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
1538 Nullable<PersistenceType> aPersistenceType)
1540 nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
1541 aPersistenceType,
1542 EmptyCString()));
1544 #ifdef DEBUG
1545 for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
1546 nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
1547 NS_ASSERTION(!op->MustWaitFor(*existingOp), "What?");
1549 #endif
1551 mSynchronizedOps.AppendElement(op.forget());
1554 void
1555 QuotaManager::AllowNextSynchronizedOp(
1556 const OriginOrPatternString& aOriginOrPattern,
1557 Nullable<PersistenceType> aPersistenceType,
1558 const nsACString& aId)
1560 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1561 NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
1562 "Empty origin/pattern!");
1564 uint32_t count = mSynchronizedOps.Length();
1565 for (uint32_t index = 0; index < count; index++) {
1566 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
1567 if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() &&
1568 op->mOriginOrPattern == aOriginOrPattern &&
1569 op->mPersistenceType == aPersistenceType) {
1570 if (op->mId == aId) {
1571 NS_ASSERTION(op->mStorages.IsEmpty(), "How did this happen?");
1573 op->DispatchDelayedRunnables();
1575 mSynchronizedOps.RemoveElementAt(index);
1576 return;
1579 // If one or the other is for an origin clear, we should have matched
1580 // solely on origin.
1581 NS_ASSERTION(!op->mId.IsEmpty() && !aId.IsEmpty(),
1582 "Why didn't we match earlier?");
1586 NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
1589 nsresult
1590 QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType,
1591 const nsACString& aASCIIOrigin,
1592 nsIFile** aDirectory) const
1594 nsresult rv;
1595 nsCOMPtr<nsIFile> directory =
1596 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1597 NS_ENSURE_SUCCESS(rv, rv);
1599 rv = directory->InitWithPath(GetStoragePath(aPersistenceType));
1600 NS_ENSURE_SUCCESS(rv, rv);
1602 nsAutoCString originSanitized(aASCIIOrigin);
1603 SanitizeOriginString(originSanitized);
1605 rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized));
1606 NS_ENSURE_SUCCESS(rv, rv);
1608 directory.forget(aDirectory);
1609 return NS_OK;
1612 nsresult
1613 QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
1614 const nsACString& aGroup,
1615 const nsACString& aOrigin,
1616 bool aTrackQuota,
1617 int64_t aAccessTime,
1618 nsIFile* aDirectory)
1620 AssertIsOnIOThread();
1622 nsresult rv;
1624 bool temporaryStorage = aPersistenceType == PERSISTENCE_TYPE_TEMPORARY;
1625 if (!temporaryStorage) {
1626 rv = MaybeUpgradeOriginDirectory(aDirectory);
1627 NS_ENSURE_SUCCESS(rv, rv);
1630 // We need to initialize directories of all clients if they exists and also
1631 // get the total usage to initialize the quota.
1632 nsAutoPtr<UsageInfo> usageInfo;
1633 if (aTrackQuota) {
1634 usageInfo = new UsageInfo();
1637 nsCOMPtr<nsISimpleEnumerator> entries;
1638 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1639 NS_ENSURE_SUCCESS(rv, rv);
1641 bool hasMore;
1642 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
1643 nsCOMPtr<nsISupports> entry;
1644 rv = entries->GetNext(getter_AddRefs(entry));
1645 NS_ENSURE_SUCCESS(rv, rv);
1647 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
1648 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
1650 nsString leafName;
1651 rv = file->GetLeafName(leafName);
1652 NS_ENSURE_SUCCESS(rv, rv);
1654 if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
1655 leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
1656 continue;
1659 bool isDirectory;
1660 rv = file->IsDirectory(&isDirectory);
1661 NS_ENSURE_SUCCESS(rv, rv);
1663 if (!isDirectory) {
1664 NS_WARNING("Unknown file found!");
1665 return NS_ERROR_UNEXPECTED;
1668 Client::Type clientType;
1669 rv = Client::TypeFromText(leafName, clientType);
1670 if (NS_FAILED(rv)) {
1671 NS_WARNING("Unknown directory found!");
1672 return NS_ERROR_UNEXPECTED;
1675 rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin,
1676 usageInfo);
1677 NS_ENSURE_SUCCESS(rv, rv);
1680 if (aTrackQuota) {
1681 uint64_t quotaMaxBytes;
1682 uint64_t totalUsageBytes = usageInfo->TotalUsage();
1684 if (temporaryStorage) {
1685 // Temporary storage has no limit for origin usage (there's a group and
1686 // the global limit though).
1687 quotaMaxBytes = 0;
1689 else {
1690 quotaMaxBytes = GetStorageQuotaMB() * 1024 * 1024;
1691 if (totalUsageBytes > quotaMaxBytes) {
1692 NS_WARNING("Origin is already using more storage than allowed!");
1693 return NS_ERROR_UNEXPECTED;
1697 InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, quotaMaxBytes,
1698 totalUsageBytes, aAccessTime);
1701 return NS_OK;
1704 nsresult
1705 QuotaManager::MaybeUpgradeIndexedDBDirectory()
1707 AssertIsOnIOThread();
1709 if (mStorageAreaInitialized) {
1710 return NS_OK;
1713 nsresult rv;
1715 nsCOMPtr<nsIFile> indexedDBDir =
1716 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1717 NS_ENSURE_SUCCESS(rv, rv);
1719 rv = indexedDBDir->InitWithPath(mIndexedDBPath);
1720 NS_ENSURE_SUCCESS(rv, rv);
1722 bool exists;
1723 rv = indexedDBDir->Exists(&exists);
1724 NS_ENSURE_SUCCESS(rv, rv);
1726 if (!exists) {
1727 // Nothing to upgrade.
1728 mStorageAreaInitialized = true;
1730 return NS_OK;
1733 bool isDirectory;
1734 rv = indexedDBDir->IsDirectory(&isDirectory);
1735 NS_ENSURE_SUCCESS(rv, rv);
1737 if (!isDirectory) {
1738 NS_WARNING("indexedDB entry is not a directory!");
1739 return NS_OK;
1742 nsCOMPtr<nsIFile> persistentStorageDir =
1743 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1744 NS_ENSURE_SUCCESS(rv, rv);
1746 rv = persistentStorageDir->InitWithPath(mPersistentStoragePath);
1747 NS_ENSURE_SUCCESS(rv, rv);
1749 rv = persistentStorageDir->Exists(&exists);
1750 NS_ENSURE_SUCCESS(rv, rv);
1752 if (exists) {
1753 NS_WARNING("indexedDB directory shouldn't exist after the upgrade!");
1754 return NS_OK;
1757 nsCOMPtr<nsIFile> storageDir;
1758 rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir));
1759 NS_ENSURE_SUCCESS(rv, rv);
1761 nsString persistentStorageName;
1762 rv = persistentStorageDir->GetLeafName(persistentStorageName);
1763 NS_ENSURE_SUCCESS(rv, rv);
1765 // MoveTo() is atomic if the move happens on the same volume which should
1766 // be our case, so even if we crash in the middle of the operation nothing
1767 // breaks next time we try to initialize.
1768 // However there's a theoretical possibility that the indexedDB directory
1769 // is on different volume, but it should be rare enough that we don't have
1770 // to worry about it.
1771 rv = indexedDBDir->MoveTo(storageDir, persistentStorageName);
1772 NS_ENSURE_SUCCESS(rv, rv);
1774 mStorageAreaInitialized = true;
1776 return NS_OK;
1779 nsresult
1780 QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType,
1781 const nsACString& aGroup,
1782 const nsACString& aOrigin,
1783 bool aTrackQuota,
1784 nsIFile** aDirectory)
1786 AssertIsOnIOThread();
1788 nsresult rv = MaybeUpgradeIndexedDBDirectory();
1789 NS_ENSURE_SUCCESS(rv, rv);
1791 // Get directory for this origin and persistence type.
1792 nsCOMPtr<nsIFile> directory;
1793 rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
1794 getter_AddRefs(directory));
1795 NS_ENSURE_SUCCESS(rv, rv);
1797 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1798 if (mInitializedOrigins.Contains(aOrigin)) {
1799 NS_ADDREF(*aDirectory = directory);
1800 return NS_OK;
1803 bool created;
1804 rv = EnsureDirectory(directory, &created);
1805 NS_ENSURE_SUCCESS(rv, rv);
1807 if (created) {
1808 rv = CreateDirectoryUpgradeStamp(directory);
1809 NS_ENSURE_SUCCESS(rv, rv);
1812 rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota, 0,
1813 directory);
1814 NS_ENSURE_SUCCESS(rv, rv);
1816 mInitializedOrigins.AppendElement(aOrigin);
1818 directory.forget(aDirectory);
1819 return NS_OK;
1822 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
1823 NS_ASSERTION(aTrackQuota, "Huh?");
1825 if (!mTemporaryStorageInitialized) {
1826 nsCOMPtr<nsIFile> parentDirectory;
1827 rv = directory->GetParent(getter_AddRefs(parentDirectory));
1828 NS_ENSURE_SUCCESS(rv, rv);
1830 bool created;
1831 rv = EnsureDirectory(parentDirectory, &created);
1832 NS_ENSURE_SUCCESS(rv, rv);
1834 nsCOMPtr<nsISimpleEnumerator> entries;
1835 rv = parentDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1836 NS_ENSURE_SUCCESS(rv, rv);
1838 bool hasMore;
1839 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
1840 nsCOMPtr<nsISupports> entry;
1841 rv = entries->GetNext(getter_AddRefs(entry));
1842 NS_ENSURE_SUCCESS(rv, rv);
1844 nsCOMPtr<nsIFile> childDirectory = do_QueryInterface(entry);
1845 NS_ENSURE_TRUE(childDirectory, NS_NOINTERFACE);
1847 bool isDirectory;
1848 rv = childDirectory->IsDirectory(&isDirectory);
1849 NS_ENSURE_SUCCESS(rv, rv);
1850 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
1852 int64_t timestamp;
1853 nsCString group;
1854 nsCString origin;
1855 rv = GetDirectoryMetadata(childDirectory, &timestamp, group, origin);
1856 NS_ENSURE_SUCCESS(rv, rv);
1858 rv = InitializeOrigin(aPersistenceType, group, origin, aTrackQuota,
1859 timestamp, childDirectory);
1860 if (NS_FAILED(rv)) {
1861 NS_WARNING("Failed to initialize origin!");
1863 // We have to cleanup partially initialized quota for temporary storage.
1864 RemoveQuotaForPersistenceType(aPersistenceType);
1866 return rv;
1870 if (gFixedLimitKB >= 0) {
1871 mTemporaryStorageLimit = gFixedLimitKB * 1024;
1873 else {
1874 rv = GetTemporaryStorageLimit(parentDirectory, mTemporaryStorageUsage,
1875 &mTemporaryStorageLimit);
1876 NS_ENSURE_SUCCESS(rv, rv);
1879 mTemporaryStorageInitialized = true;
1881 CheckTemporaryStorageLimits();
1884 bool created;
1885 rv = EnsureDirectory(directory, &created);
1886 NS_ENSURE_SUCCESS(rv, rv);
1888 if (created) {
1889 int64_t timestamp = PR_Now();
1891 rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin);
1892 NS_ENSURE_SUCCESS(rv, rv);
1894 rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota,
1895 timestamp, directory);
1896 NS_ENSURE_SUCCESS(rv, rv);
1899 directory.forget(aDirectory);
1900 return NS_OK;
1903 void
1904 QuotaManager::OriginClearCompleted(
1905 PersistenceType aPersistenceType,
1906 const OriginOrPatternString& aOriginOrPattern)
1908 AssertIsOnIOThread();
1910 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1911 if (aOriginOrPattern.IsOrigin()) {
1912 mInitializedOrigins.RemoveElement(aOriginOrPattern);
1914 else {
1915 for (uint32_t index = mInitializedOrigins.Length(); index > 0; index--) {
1916 if (PatternMatchesOrigin(aOriginOrPattern,
1917 mInitializedOrigins[index - 1])) {
1918 mInitializedOrigins.RemoveElementAt(index - 1);
1924 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
1925 mClients[index]->OnOriginClearCompleted(aPersistenceType, aOriginOrPattern);
1929 void
1930 QuotaManager::ResetOrClearCompleted()
1932 AssertIsOnIOThread();
1934 mInitializedOrigins.Clear();
1935 mTemporaryStorageInitialized = false;
1937 ReleaseIOThreadObjects();
1940 already_AddRefed<mozilla::dom::quota::Client>
1941 QuotaManager::GetClient(Client::Type aClientType)
1943 nsRefPtr<Client> client = mClients.SafeElementAt(aClientType);
1944 return client.forget();
1947 uint64_t
1948 QuotaManager::GetGroupLimit() const
1950 MOZ_ASSERT(mTemporaryStorageInitialized);
1952 // To avoid one group evicting all the rest, limit the amount any one group
1953 // can use to 20%. To prevent individual sites from using exorbitant amounts
1954 // of storage where there is a lot of free space, cap the group limit to 2GB.
1955 uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit * .20, 2 GB);
1957 // In low-storage situations, make an exception (while not exceeding the total
1958 // storage limit).
1959 return std::min<uint64_t>(mTemporaryStorageLimit,
1960 std::max<uint64_t>(x, 10 MB));
1963 // static
1964 uint32_t
1965 QuotaManager::GetStorageQuotaMB()
1967 return uint32_t(std::max(gStorageQuotaMB, 0));
1970 // static
1971 void
1972 QuotaManager::GetStorageId(PersistenceType aPersistenceType,
1973 const nsACString& aOrigin,
1974 Client::Type aClientType,
1975 const nsAString& aName,
1976 nsACString& aDatabaseId)
1978 nsAutoCString str;
1979 str.AppendInt(aPersistenceType);
1980 str.Append('*');
1981 str.Append(aOrigin);
1982 str.Append('*');
1983 str.AppendInt(aClientType);
1984 str.Append('*');
1985 str.Append(NS_ConvertUTF16toUTF8(aName));
1987 aDatabaseId = str;
1990 // static
1991 nsresult
1992 QuotaManager::GetInfoFromURI(nsIURI* aURI,
1993 uint32_t aAppId,
1994 bool aInMozBrowser,
1995 nsACString* aGroup,
1996 nsACString* aASCIIOrigin,
1997 StoragePrivilege* aPrivilege,
1998 PersistenceType* aDefaultPersistenceType)
2000 NS_ASSERTION(aURI, "Null uri!");
2002 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
2003 NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
2005 nsCOMPtr<nsIPrincipal> principal;
2006 nsresult rv = secMan->GetAppCodebasePrincipal(aURI, aAppId, aInMozBrowser,
2007 getter_AddRefs(principal));
2008 NS_ENSURE_SUCCESS(rv, rv);
2010 rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin, aPrivilege,
2011 aDefaultPersistenceType);
2012 NS_ENSURE_SUCCESS(rv, rv);
2014 return NS_OK;
2017 static nsresult
2018 TryGetInfoForAboutURI(nsIPrincipal* aPrincipal,
2019 nsACString& aGroup,
2020 nsACString& aASCIIOrigin,
2021 StoragePrivilege* aPrivilege,
2022 PersistenceType* aDefaultPersistenceType)
2024 NS_ASSERTION(aPrincipal, "Don't hand me a null principal!");
2026 nsCOMPtr<nsIURI> uri;
2027 nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
2028 NS_ENSURE_SUCCESS(rv, rv);
2029 if (!uri) {
2030 return NS_ERROR_NOT_AVAILABLE;
2033 bool isAbout;
2034 rv = uri->SchemeIs("about", &isAbout);
2035 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && isAbout, NS_ERROR_FAILURE);
2037 nsCOMPtr<nsIAboutModule> module;
2038 rv = NS_GetAboutModule(uri, getter_AddRefs(module));
2039 NS_ENSURE_SUCCESS(rv, rv);
2041 nsCOMPtr<nsIURI> inner = NS_GetInnermostURI(uri);
2042 NS_ENSURE_TRUE(inner, NS_ERROR_FAILURE);
2044 nsAutoString postfix;
2045 rv = module->GetIndexedDBOriginPostfix(uri, postfix);
2046 NS_ENSURE_SUCCESS(rv, rv);
2048 nsCString origin;
2049 if (DOMStringIsNull(postfix)) {
2050 rv = inner->GetSpec(origin);
2051 NS_ENSURE_SUCCESS(rv, rv);
2052 } else {
2053 nsAutoCString scheme;
2054 rv = inner->GetScheme(scheme);
2055 NS_ENSURE_SUCCESS(rv, rv);
2057 origin = scheme + NS_LITERAL_CSTRING(":") + NS_ConvertUTF16toUTF8(postfix);
2060 ToLowerCase(origin);
2061 aGroup.Assign(origin);
2062 aASCIIOrigin.Assign(origin);
2064 if (aPrivilege) {
2065 *aPrivilege = Content;
2068 if (aDefaultPersistenceType) {
2069 *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
2072 return NS_OK;
2075 // static
2076 nsresult
2077 QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
2078 nsACString* aGroup,
2079 nsACString* aASCIIOrigin,
2080 StoragePrivilege* aPrivilege,
2081 PersistenceType* aDefaultPersistenceType)
2083 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2084 NS_ASSERTION(aPrincipal, "Don't hand me a null principal!");
2086 if (aGroup && aASCIIOrigin) {
2087 nsresult rv = TryGetInfoForAboutURI(aPrincipal, *aGroup, *aASCIIOrigin,
2088 aPrivilege, aDefaultPersistenceType);
2089 if (NS_SUCCEEDED(rv)) {
2090 return NS_OK;
2094 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
2095 GetInfoForChrome(aGroup, aASCIIOrigin, aPrivilege, aDefaultPersistenceType);
2096 return NS_OK;
2099 bool isNullPrincipal;
2100 nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal);
2101 NS_ENSURE_SUCCESS(rv, rv);
2103 if (isNullPrincipal) {
2104 NS_WARNING("IndexedDB not supported from this principal!");
2105 return NS_ERROR_FAILURE;
2108 nsCString origin;
2109 rv = aPrincipal->GetOrigin(getter_Copies(origin));
2110 NS_ENSURE_SUCCESS(rv, rv);
2112 if (origin.EqualsLiteral("chrome")) {
2113 NS_WARNING("Non-chrome principal can't use chrome origin!");
2114 return NS_ERROR_FAILURE;
2117 nsCString jarPrefix;
2118 if (aGroup || aASCIIOrigin) {
2119 rv = aPrincipal->GetJarPrefix(jarPrefix);
2120 NS_ENSURE_SUCCESS(rv, rv);
2123 if (aGroup) {
2124 nsCString baseDomain;
2125 rv = aPrincipal->GetBaseDomain(baseDomain);
2126 if (NS_FAILED(rv)) {
2127 // A hack for JetPack.
2129 nsCOMPtr<nsIURI> uri;
2130 rv = aPrincipal->GetURI(getter_AddRefs(uri));
2131 NS_ENSURE_SUCCESS(rv, rv);
2133 bool isIndexedDBURI = false;
2134 rv = uri->SchemeIs("indexedDB", &isIndexedDBURI);
2135 NS_ENSURE_SUCCESS(rv, rv);
2137 if (isIndexedDBURI) {
2138 rv = NS_OK;
2141 NS_ENSURE_SUCCESS(rv, rv);
2143 if (baseDomain.IsEmpty()) {
2144 aGroup->Assign(jarPrefix + origin);
2146 else {
2147 aGroup->Assign(jarPrefix + baseDomain);
2151 if (aASCIIOrigin) {
2152 aASCIIOrigin->Assign(jarPrefix + origin);
2155 if (aPrivilege) {
2156 *aPrivilege = Content;
2159 if (aDefaultPersistenceType) {
2160 *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
2163 return NS_OK;
2166 // static
2167 nsresult
2168 QuotaManager::GetInfoFromWindow(nsPIDOMWindow* aWindow,
2169 nsACString* aGroup,
2170 nsACString* aASCIIOrigin,
2171 StoragePrivilege* aPrivilege,
2172 PersistenceType* aDefaultPersistenceType)
2174 NS_ASSERTION(NS_IsMainThread(),
2175 "We're about to touch a window off the main thread!");
2176 NS_ASSERTION(aWindow, "Don't hand me a null window!");
2178 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
2179 NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE);
2181 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
2182 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
2184 nsresult rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin,
2185 aPrivilege, aDefaultPersistenceType);
2186 NS_ENSURE_SUCCESS(rv, rv);
2188 return NS_OK;
2191 // static
2192 void
2193 QuotaManager::GetInfoForChrome(nsACString* aGroup,
2194 nsACString* aASCIIOrigin,
2195 StoragePrivilege* aPrivilege,
2196 PersistenceType* aDefaultPersistenceType)
2198 NS_ASSERTION(nsContentUtils::IsCallerChrome(), "Only for chrome!");
2200 static const char kChromeOrigin[] = "chrome";
2202 if (aGroup) {
2203 aGroup->AssignLiteral(kChromeOrigin);
2205 if (aASCIIOrigin) {
2206 aASCIIOrigin->AssignLiteral(kChromeOrigin);
2208 if (aPrivilege) {
2209 *aPrivilege = Chrome;
2211 if (aDefaultPersistenceType) {
2212 *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
2216 NS_IMPL_ISUPPORTS(QuotaManager, nsIQuotaManager, nsIObserver)
2218 NS_IMETHODIMP
2219 QuotaManager::GetUsageForURI(nsIURI* aURI,
2220 nsIUsageCallback* aCallback,
2221 uint32_t aAppId,
2222 bool aInMozBrowserOnly,
2223 uint8_t aOptionalArgCount,
2224 nsIQuotaRequest** _retval)
2226 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2228 NS_ENSURE_ARG_POINTER(aURI);
2229 NS_ENSURE_ARG_POINTER(aCallback);
2231 // This only works from the main process.
2232 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2234 if (!aOptionalArgCount) {
2235 aAppId = nsIScriptSecurityManager::NO_APP_ID;
2238 // Figure out which origin we're dealing with.
2239 nsCString group;
2240 nsCString origin;
2241 nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, &group, &origin,
2242 nullptr, nullptr);
2243 NS_ENSURE_SUCCESS(rv, rv);
2245 OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin);
2247 nsRefPtr<AsyncUsageRunnable> runnable =
2248 new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, group, oops, aURI,
2249 aCallback);
2251 // Put the computation runnable in the queue.
2252 rv = WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2253 runnable);
2254 NS_ENSURE_SUCCESS(rv, rv);
2256 runnable->AdvanceState();
2258 runnable.forget(_retval);
2259 return NS_OK;
2262 NS_IMETHODIMP
2263 QuotaManager::Clear()
2265 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2267 if (!gTestingEnabled) {
2268 NS_WARNING("Testing features are not enabled!");
2269 return NS_OK;
2272 OriginOrPatternString oops = OriginOrPatternString::FromNull();
2274 nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(true);
2276 // Put the clear runnable in the queue.
2277 nsresult rv =
2278 WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2279 runnable);
2280 NS_ENSURE_SUCCESS(rv, rv);
2282 runnable->AdvanceState();
2284 // Give the runnable some help by invalidating any storages in the way.
2285 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2286 matches.Find(mLiveStorages);
2288 for (uint32_t index = 0; index < matches.Length(); index++) {
2289 // We need to grab references to any live storages here to prevent them
2290 // from dying while we invalidate them.
2291 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2292 storage->Invalidate();
2295 // After everything has been invalidated the helper should be dispatched to
2296 // the end of the event queue.
2297 return NS_OK;
2300 NS_IMETHODIMP
2301 QuotaManager::ClearStoragesForURI(nsIURI* aURI,
2302 uint32_t aAppId,
2303 bool aInMozBrowserOnly,
2304 const nsACString& aPersistenceType,
2305 uint8_t aOptionalArgCount)
2307 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2309 NS_ENSURE_ARG_POINTER(aURI);
2311 Nullable<PersistenceType> persistenceType;
2312 nsresult rv =
2313 NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
2314 if (NS_WARN_IF(NS_FAILED(rv))) {
2315 return NS_ERROR_INVALID_ARG;
2318 // This only works from the main process.
2319 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2321 if (!aOptionalArgCount) {
2322 aAppId = nsIScriptSecurityManager::NO_APP_ID;
2325 // Figure out which origin we're dealing with.
2326 nsCString origin;
2327 rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, nullptr, &origin,
2328 nullptr, nullptr);
2329 NS_ENSURE_SUCCESS(rv, rv);
2331 nsAutoCString pattern;
2332 GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern);
2334 // If there is a pending or running clear operation for this origin, return
2335 // immediately.
2336 if (IsClearOriginPending(pattern, persistenceType)) {
2337 return NS_OK;
2340 OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
2342 // Queue up the origin clear runnable.
2343 nsRefPtr<OriginClearRunnable> runnable =
2344 new OriginClearRunnable(oops, persistenceType);
2346 rv = WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
2347 NS_ENSURE_SUCCESS(rv, rv);
2349 runnable->AdvanceState();
2351 // Give the runnable some help by invalidating any storages in the way.
2352 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2353 matches.Find(mLiveStorages, pattern);
2355 for (uint32_t index = 0; index < matches.Length(); index++) {
2356 if (persistenceType.IsNull() ||
2357 matches[index]->Type() == persistenceType.Value()) {
2358 // We need to grab references to any live storages here to prevent them
2359 // from dying while we invalidate them.
2360 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2361 storage->Invalidate();
2365 // After everything has been invalidated the helper should be dispatched to
2366 // the end of the event queue.
2367 return NS_OK;
2370 NS_IMETHODIMP
2371 QuotaManager::Reset()
2373 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2375 if (!gTestingEnabled) {
2376 NS_WARNING("Testing features are not enabled!");
2377 return NS_OK;
2380 OriginOrPatternString oops = OriginOrPatternString::FromNull();
2382 nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(false);
2384 // Put the reset runnable in the queue.
2385 nsresult rv =
2386 WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2387 runnable);
2388 NS_ENSURE_SUCCESS(rv, rv);
2390 runnable->AdvanceState();
2392 // Give the runnable some help by invalidating any storages in the way.
2393 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2394 matches.Find(mLiveStorages);
2396 for (uint32_t index = 0; index < matches.Length(); index++) {
2397 // We need to grab references to any live storages here to prevent them
2398 // from dying while we invalidate them.
2399 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2400 storage->Invalidate();
2403 // After everything has been invalidated the helper should be dispatched to
2404 // the end of the event queue.
2405 return NS_OK;
2408 NS_IMETHODIMP
2409 QuotaManager::Observe(nsISupports* aSubject,
2410 const char* aTopic,
2411 const char16_t* aData)
2413 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2415 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) {
2416 // Setting this flag prevents the service from being recreated and prevents
2417 // further storagess from being created.
2418 if (gShutdown.exchange(true)) {
2419 NS_ERROR("Shutdown more than once?!");
2422 if (IsMainProcess()) {
2423 FileService* service = FileService::Get();
2424 if (service) {
2425 // This should only wait for storages registered in this manager
2426 // to complete. Other storages may still have running file handles.
2427 // If the necko service (thread pool) gets the shutdown notification
2428 // first then the sync loop won't be processed at all, otherwise it will
2429 // lock the main thread until all storages registered in this manager
2430 // are finished.
2432 nsTArray<uint32_t> indexes;
2433 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
2434 if (mClients[index]->IsFileServiceUtilized()) {
2435 indexes.AppendElement(index);
2439 StorageMatcher<nsTArray<nsCOMPtr<nsIOfflineStorage>>> liveStorages;
2440 liveStorages.Find(mLiveStorages, &indexes);
2442 if (!liveStorages.IsEmpty()) {
2443 nsRefPtr<WaitForFileHandlesToFinishRunnable> runnable =
2444 new WaitForFileHandlesToFinishRunnable();
2446 service->WaitForStoragesToComplete(liveStorages, runnable);
2448 nsIThread* thread = NS_GetCurrentThread();
2449 while (runnable->IsBusy()) {
2450 if (!NS_ProcessNextEvent(thread)) {
2451 NS_ERROR("Failed to process next event!");
2452 break;
2458 // Kick off the shutdown timer.
2459 if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
2460 nsITimer::TYPE_ONE_SHOT))) {
2461 NS_WARNING("Failed to initialize shutdown timer!");
2464 // Each client will spin the event loop while we wait on all the threads
2465 // to close. Our timer may fire during that loop.
2466 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
2467 mClients[index]->ShutdownTransactionService();
2470 // Cancel the timer regardless of whether it actually fired.
2471 if (NS_FAILED(mShutdownTimer->Cancel())) {
2472 NS_WARNING("Failed to cancel shutdown timer!");
2475 // Give clients a chance to cleanup IO thread only objects.
2476 nsCOMPtr<nsIRunnable> runnable =
2477 NS_NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects);
2478 if (!runnable) {
2479 NS_WARNING("Failed to create runnable!");
2482 if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
2483 NS_WARNING("Failed to dispatch runnable!");
2486 // Make sure to join with our IO thread.
2487 if (NS_FAILED(mIOThread->Shutdown())) {
2488 NS_WARNING("Failed to shutdown IO thread!");
2492 return NS_OK;
2495 if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
2496 NS_ASSERTION(IsMainProcess(), "Should only happen in the main process!");
2498 NS_WARNING("Some storage operations are taking longer than expected "
2499 "during shutdown and will be aborted!");
2501 // Grab all live storages, for all origins.
2502 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 50> > liveStorages;
2503 liveStorages.Find(mLiveStorages);
2505 // Invalidate them all.
2506 if (!liveStorages.IsEmpty()) {
2507 uint32_t count = liveStorages.Length();
2508 for (uint32_t index = 0; index < count; index++) {
2509 liveStorages[index]->Invalidate();
2513 return NS_OK;
2516 if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) {
2517 nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
2518 do_QueryInterface(aSubject);
2519 NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED);
2521 uint32_t appId;
2522 nsresult rv = params->GetAppId(&appId);
2523 NS_ENSURE_SUCCESS(rv, rv);
2525 bool browserOnly;
2526 rv = params->GetBrowserOnly(&browserOnly);
2527 NS_ENSURE_SUCCESS(rv, rv);
2529 rv = ClearStoragesForApp(appId, browserOnly);
2530 NS_ENSURE_SUCCESS(rv, rv);
2532 return NS_OK;
2535 NS_NOTREACHED("Unknown topic!");
2536 return NS_ERROR_UNEXPECTED;
2539 void
2540 QuotaManager::SetCurrentWindowInternal(nsPIDOMWindow* aWindow)
2542 NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX,
2543 "Should have a valid TLS storage index!");
2545 if (aWindow) {
2546 NS_ASSERTION(!PR_GetThreadPrivate(mCurrentWindowIndex),
2547 "Somebody forgot to clear the current window!");
2548 PR_SetThreadPrivate(mCurrentWindowIndex, aWindow);
2550 else {
2551 // We cannot assert PR_GetThreadPrivate(mCurrentWindowIndex) here because
2552 // there are some cases where we did not already have a window.
2553 PR_SetThreadPrivate(mCurrentWindowIndex, nullptr);
2557 void
2558 QuotaManager::CancelPromptsForWindowInternal(nsPIDOMWindow* aWindow)
2560 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2562 nsRefPtr<CheckQuotaHelper> helper;
2564 MutexAutoLock autoLock(mQuotaMutex);
2566 if (mCheckQuotaHelpers.Get(aWindow, getter_AddRefs(helper))) {
2567 helper->Cancel();
2571 bool
2572 QuotaManager::LockedQuotaIsLifted()
2574 mQuotaMutex.AssertCurrentThreadOwns();
2576 NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX,
2577 "Should have a valid TLS storage index!");
2579 nsPIDOMWindow* window =
2580 static_cast<nsPIDOMWindow*>(PR_GetThreadPrivate(mCurrentWindowIndex));
2582 // Quota is not enforced in chrome contexts (e.g. for components and JSMs)
2583 // so we must have a window here.
2584 NS_ASSERTION(window, "Why don't we have a Window here?");
2586 bool createdHelper = false;
2588 nsRefPtr<CheckQuotaHelper> helper;
2589 if (!mCheckQuotaHelpers.Get(window, getter_AddRefs(helper))) {
2590 helper = new CheckQuotaHelper(window, mQuotaMutex);
2591 createdHelper = true;
2593 mCheckQuotaHelpers.Put(window, helper);
2595 // Unlock while calling out to XPCOM (code behind the dispatch method needs
2596 // to acquire its own lock which can potentially lead to a deadlock and it
2597 // also calls an observer that can do various stuff like IO, so it's better
2598 // to not hold our mutex while that happens).
2600 MutexAutoUnlock autoUnlock(mQuotaMutex);
2602 nsresult rv = NS_DispatchToMainThread(helper);
2603 NS_ENSURE_SUCCESS(rv, false);
2606 // Relocked. If any other threads hit the quota limit on the same Window,
2607 // they are using the helper we created here and are now blocking in
2608 // PromptAndReturnQuotaDisabled.
2611 bool result = helper->PromptAndReturnQuotaIsDisabled();
2613 // If this thread created the helper and added it to the hash, this thread
2614 // must remove it.
2615 if (createdHelper) {
2616 mCheckQuotaHelpers.Remove(window);
2619 return result;
2622 uint64_t
2623 QuotaManager::LockedCollectOriginsForEviction(
2624 uint64_t aMinSizeToBeFreed,
2625 nsTArray<OriginInfo*>& aOriginInfos)
2627 mQuotaMutex.AssertCurrentThreadOwns();
2629 nsRefPtr<CollectOriginsHelper> helper =
2630 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
2632 // Unlock while calling out to XPCOM (see the detailed comment in
2633 // LockedQuotaIsLifted)
2635 MutexAutoUnlock autoUnlock(mQuotaMutex);
2637 if (NS_FAILED(NS_DispatchToMainThread(helper))) {
2638 NS_WARNING("Failed to dispatch to the main thread!");
2642 return helper->BlockAndReturnOriginsForEviction(aOriginInfos);
2645 void
2646 QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
2647 const nsACString& aGroup,
2648 const nsACString& aOrigin)
2650 mQuotaMutex.AssertCurrentThreadOwns();
2652 GroupInfoPair* pair;
2653 mGroupInfoPairs.Get(aGroup, &pair);
2655 if (!pair) {
2656 return;
2659 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2660 if (groupInfo) {
2661 groupInfo->LockedRemoveOriginInfo(aOrigin);
2663 if (!groupInfo->LockedHasOriginInfos()) {
2664 pair->LockedClearGroupInfo(aPersistenceType);
2666 if (!pair->LockedHasGroupInfos()) {
2667 mGroupInfoPairs.Remove(aGroup);
2673 nsresult
2674 QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern,
2675 Nullable<PersistenceType> aPersistenceType,
2676 nsIOfflineStorage* aStorage,
2677 AcquireListener* aListener,
2678 WaitingOnStoragesCallback aCallback,
2679 void* aClosure)
2681 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2682 NS_ASSERTION(aListener, "Need a listener!");
2684 // Find the right SynchronizedOp.
2685 SynchronizedOp* op =
2686 FindSynchronizedOp(aPattern, aPersistenceType,
2687 aStorage ? aStorage->Id() : EmptyCString());
2689 NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
2690 NS_ASSERTION(!op->mListener, "SynchronizedOp already has a listener?!?");
2692 nsTArray<nsCOMPtr<nsIOfflineStorage> > liveStorages;
2694 if (aStorage) {
2695 // We need to wait for the storages to go away.
2696 // Hold on to all storage objects that represent the same storage file
2697 // (except the one that is requesting this version change).
2699 Client::Type clientType = aStorage->GetClient()->GetType();
2701 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2702 matches.Find(mLiveStorages, aPattern, clientType);
2704 if (!matches.IsEmpty()) {
2705 // Grab all storages that are not yet closed but whose storage id match
2706 // the one we're looking for.
2707 for (uint32_t index = 0; index < matches.Length(); index++) {
2708 nsIOfflineStorage*& storage = matches[index];
2709 if (!storage->IsClosed() &&
2710 storage != aStorage &&
2711 storage->Id() == aStorage->Id() &&
2712 (aPersistenceType.IsNull() ||
2713 aPersistenceType.Value() == storage->Type())) {
2714 liveStorages.AppendElement(storage);
2719 if (!liveStorages.IsEmpty()) {
2720 NS_ASSERTION(op->mStorages[clientType].IsEmpty(),
2721 "How do we already have storages here?");
2722 op->mStorages[clientType].AppendElements(liveStorages);
2725 else {
2726 StorageMatcher<ArrayCluster<nsIOfflineStorage*> > matches;
2727 if (aPattern.IsVoid()) {
2728 matches.Find(mLiveStorages);
2730 else {
2731 matches.Find(mLiveStorages, aPattern);
2734 NS_ASSERTION(op->mStorages.IsEmpty(),
2735 "How do we already have storages here?");
2737 // We want *all* storages that match the given persistence type, even those
2738 // that are closed, when we're going to clear the origin.
2739 if (!matches.IsEmpty()) {
2740 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
2741 nsTArray<nsIOfflineStorage*>& storages = matches.ArrayAt(i);
2742 for (uint32_t j = 0; j < storages.Length(); j++) {
2743 nsIOfflineStorage* storage = storages[j];
2744 if (aPersistenceType.IsNull() ||
2745 aPersistenceType.Value() == storage->Type()) {
2746 liveStorages.AppendElement(storage);
2747 op->mStorages[i].AppendElement(storage);
2754 op->mListener = aListener;
2756 if (!liveStorages.IsEmpty()) {
2757 // Give our callback the storages so it can decide what to do with them.
2758 aCallback(liveStorages, aClosure);
2760 NS_ASSERTION(liveStorages.IsEmpty(),
2761 "Should have done something with the array!");
2763 if (aStorage) {
2764 // Wait for those storages to close.
2765 return NS_OK;
2769 // If we're trying to open a storage and nothing blocks it, or if we're
2770 // clearing an origin, then go ahead and schedule the op.
2771 nsresult rv = RunSynchronizedOp(aStorage, op);
2772 NS_ENSURE_SUCCESS(rv, rv);
2774 return NS_OK;
2777 nsresult
2778 QuotaManager::RunSynchronizedOp(nsIOfflineStorage* aStorage,
2779 SynchronizedOp* aOp)
2781 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2782 NS_ASSERTION(aOp, "Null pointer!");
2783 NS_ASSERTION(aOp->mListener, "No listener on this op!");
2784 NS_ASSERTION(!aStorage ||
2785 aOp->mStorages[aStorage->GetClient()->GetType()].IsEmpty(),
2786 "This op isn't ready to run!");
2788 ArrayCluster<nsIOfflineStorage*> storages;
2790 uint32_t startIndex;
2791 uint32_t endIndex;
2793 if (aStorage) {
2794 Client::Type clientType = aStorage->GetClient()->GetType();
2796 storages[clientType].AppendElement(aStorage);
2798 startIndex = clientType;
2799 endIndex = clientType + 1;
2801 else {
2802 aOp->mStorages.SwapElements(storages);
2804 startIndex = 0;
2805 endIndex = Client::TYPE_MAX;
2808 nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
2809 new WaitForTransactionsToFinishRunnable(aOp);
2811 // Ask the file service to call us back when it's done with this storage.
2812 FileService* service = FileService::Get();
2814 if (service) {
2815 // Have to copy here in case a transaction service needs a list too.
2816 nsTArray<nsCOMPtr<nsIOfflineStorage>> array;
2818 for (uint32_t index = startIndex; index < endIndex; index++) {
2819 if (!storages[index].IsEmpty() &&
2820 mClients[index]->IsFileServiceUtilized()) {
2821 array.AppendElements(storages[index]);
2825 if (!array.IsEmpty()) {
2826 runnable->AddRun();
2828 service->WaitForStoragesToComplete(array, runnable);
2832 // Ask each transaction service to call us back when they're done with this
2833 // storage.
2834 for (uint32_t index = startIndex; index < endIndex; index++) {
2835 nsRefPtr<Client>& client = mClients[index];
2836 if (!storages[index].IsEmpty() && client->IsTransactionServiceActivated()) {
2837 runnable->AddRun();
2839 client->WaitForStoragesToComplete(storages[index], runnable);
2843 nsresult rv = runnable->Run();
2844 NS_ENSURE_SUCCESS(rv, rv);
2846 return NS_OK;
2849 SynchronizedOp*
2850 QuotaManager::FindSynchronizedOp(const nsACString& aPattern,
2851 Nullable<PersistenceType> aPersistenceType,
2852 const nsACString& aId)
2854 for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) {
2855 const nsAutoPtr<SynchronizedOp>& currentOp = mSynchronizedOps[index];
2856 if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) &&
2857 (currentOp->mPersistenceType.IsNull() ||
2858 currentOp->mPersistenceType == aPersistenceType) &&
2859 (currentOp->mId.IsEmpty() || currentOp->mId == aId)) {
2860 return currentOp;
2864 return nullptr;
2867 nsresult
2868 QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly)
2870 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2871 NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
2872 "Bad appId!");
2874 // This only works from the main process.
2875 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2877 nsAutoCString pattern;
2878 GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern);
2880 // Clear both temporary and persistent storages.
2881 Nullable<PersistenceType> persistenceType;
2883 // If there is a pending or running clear operation for this app, return
2884 // immediately.
2885 if (IsClearOriginPending(pattern, persistenceType)) {
2886 return NS_OK;
2889 OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
2891 // Queue up the origin clear runnable.
2892 nsRefPtr<OriginClearRunnable> runnable =
2893 new OriginClearRunnable(oops, persistenceType);
2895 nsresult rv =
2896 WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
2897 NS_ENSURE_SUCCESS(rv, rv);
2899 runnable->AdvanceState();
2901 // Give the runnable some help by invalidating any storages in the way.
2902 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2903 matches.Find(mLiveStorages, pattern);
2905 for (uint32_t index = 0; index < matches.Length(); index++) {
2906 // We need to grab references here to prevent the storage from dying while
2907 // we invalidate it.
2908 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2909 storage->Invalidate();
2912 return NS_OK;
2915 // static
2916 PLDHashOperator
2917 QuotaManager::GetOriginsExceedingGroupLimit(const nsACString& aKey,
2918 GroupInfoPair* aValue,
2919 void* aUserArg)
2921 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
2922 NS_ASSERTION(aValue, "Null pointer!");
2924 nsRefPtr<GroupInfo> groupInfo =
2925 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2926 if (groupInfo) {
2927 QuotaManager* quotaManager = QuotaManager::Get();
2928 NS_ASSERTION(quotaManager, "Shouldn't be null!");
2930 if (groupInfo->mUsage > quotaManager->GetGroupLimit()) {
2931 nsTArray<OriginInfo*>* doomedOriginInfos =
2932 static_cast<nsTArray<OriginInfo*>*>(aUserArg);
2934 nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
2935 originInfos.Sort(OriginInfoLRUComparator());
2937 uint64_t usage = groupInfo->mUsage;
2938 for (uint32_t i = 0; i < originInfos.Length(); i++) {
2939 OriginInfo* originInfo = originInfos[i];
2941 doomedOriginInfos->AppendElement(originInfo);
2942 usage -= originInfo->mUsage;
2944 if (usage <= quotaManager->GetGroupLimit()) {
2945 break;
2951 return PL_DHASH_NEXT;
2954 // static
2955 PLDHashOperator
2956 QuotaManager::GetAllTemporaryStorageOrigins(const nsACString& aKey,
2957 GroupInfoPair* aValue,
2958 void* aUserArg)
2960 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
2961 NS_ASSERTION(aValue, "Null pointer!");
2963 nsRefPtr<GroupInfo> groupInfo =
2964 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2965 if (groupInfo) {
2966 nsTArray<OriginInfo*>* originInfos =
2967 static_cast<nsTArray<OriginInfo*>*>(aUserArg);
2969 originInfos->AppendElements(groupInfo->mOriginInfos);
2972 return PL_DHASH_NEXT;
2975 void
2976 QuotaManager::CheckTemporaryStorageLimits()
2978 AssertIsOnIOThread();
2980 nsTArray<OriginInfo*> doomedOriginInfos;
2982 MutexAutoLock lock(mQuotaMutex);
2984 mGroupInfoPairs.EnumerateRead(GetOriginsExceedingGroupLimit,
2985 &doomedOriginInfos);
2987 uint64_t usage = 0;
2988 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
2989 usage += doomedOriginInfos[index]->mUsage;
2992 if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) {
2993 nsTArray<OriginInfo*> originInfos;
2995 mGroupInfoPairs.EnumerateRead(GetAllTemporaryStorageOrigins,
2996 &originInfos);
2998 for (uint32_t index = originInfos.Length(); index > 0; index--) {
2999 if (doomedOriginInfos.Contains(originInfos[index - 1])) {
3000 originInfos.RemoveElementAt(index - 1);
3004 originInfos.Sort(OriginInfoLRUComparator());
3006 for (uint32_t i = 0; i < originInfos.Length(); i++) {
3007 if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) {
3008 originInfos.TruncateLength(i);
3009 break;
3012 usage += originInfos[i]->mUsage;
3015 doomedOriginInfos.AppendElements(originInfos);
3019 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
3020 DeleteTemporaryFilesForOrigin(doomedOriginInfos[index]->mOrigin);
3023 nsTArray<nsCString> doomedOrigins;
3025 MutexAutoLock lock(mQuotaMutex);
3027 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
3028 OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
3030 nsCString group = doomedOriginInfo->mGroupInfo->mGroup;
3031 nsCString origin = doomedOriginInfo->mOrigin;
3032 LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY, group, origin);
3034 #ifdef DEBUG
3035 doomedOriginInfos[index] = nullptr;
3036 #endif
3038 doomedOrigins.AppendElement(origin);
3042 for (uint32_t index = 0; index < doomedOrigins.Length(); index++) {
3043 OriginClearCompleted(
3044 PERSISTENCE_TYPE_TEMPORARY,
3045 OriginOrPatternString::FromOrigin(doomedOrigins[index]));
3049 // static
3050 PLDHashOperator
3051 QuotaManager::AddTemporaryStorageOrigins(
3052 const nsACString& aKey,
3053 ArrayCluster<nsIOfflineStorage*>* aValue,
3054 void* aUserArg)
3056 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3057 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
3058 NS_ASSERTION(aValue, "Null pointer!");
3059 NS_ASSERTION(aUserArg, "Null pointer!");
3061 OriginCollection& collection = *static_cast<OriginCollection*>(aUserArg);
3063 if (collection.ContainsOrigin(aKey)) {
3064 return PL_DHASH_NEXT;
3067 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
3068 nsTArray<nsIOfflineStorage*>& array = (*aValue)[i];
3069 for (uint32_t j = 0; j < array.Length(); j++) {
3070 nsIOfflineStorage*& storage = array[j];
3071 if (storage->Type() == PERSISTENCE_TYPE_TEMPORARY) {
3072 collection.AddOrigin(aKey);
3073 return PL_DHASH_NEXT;
3078 return PL_DHASH_NEXT;
3081 // static
3082 PLDHashOperator
3083 QuotaManager::GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
3084 GroupInfoPair* aValue,
3085 void* aUserArg)
3087 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
3088 NS_ASSERTION(aValue, "Null pointer!");
3089 NS_ASSERTION(aUserArg, "Null pointer!");
3091 nsRefPtr<GroupInfo> groupInfo =
3092 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3093 if (groupInfo) {
3094 InactiveOriginsInfo* info = static_cast<InactiveOriginsInfo*>(aUserArg);
3096 nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
3098 for (uint32_t i = 0; i < originInfos.Length(); i++) {
3099 OriginInfo* originInfo = originInfos[i];
3101 if (!info->collection.ContainsOrigin(originInfo->mOrigin)) {
3102 NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
3103 "Inactive origin shouldn't have open files!");
3104 info->origins.AppendElement(originInfo);
3109 return PL_DHASH_NEXT;
3112 uint64_t
3113 QuotaManager::CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
3114 nsTArray<OriginInfo*>& aOriginInfos)
3116 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3118 // Collect active origins first.
3119 OriginCollection originCollection;
3121 // Add patterns and origins that have running or pending synchronized ops.
3122 // (add patterns first to reduce redundancy in the origin collection).
3123 uint32_t index;
3124 for (index = 0; index < mSynchronizedOps.Length(); index++) {
3125 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
3126 if (op->mPersistenceType.IsNull() ||
3127 op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3128 if (op->mOriginOrPattern.IsPattern() &&
3129 !originCollection.ContainsPattern(op->mOriginOrPattern)) {
3130 originCollection.AddPattern(op->mOriginOrPattern);
3135 for (index = 0; index < mSynchronizedOps.Length(); index++) {
3136 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
3137 if (op->mPersistenceType.IsNull() ||
3138 op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3139 if (op->mOriginOrPattern.IsOrigin() &&
3140 !originCollection.ContainsOrigin(op->mOriginOrPattern)) {
3141 originCollection.AddOrigin(op->mOriginOrPattern);
3146 // Add origins that have live temporary storages.
3147 mLiveStorages.EnumerateRead(AddTemporaryStorageOrigins, &originCollection);
3149 // Enumerate inactive origins. This must be protected by the mutex.
3150 nsTArray<OriginInfo*> inactiveOrigins;
3152 InactiveOriginsInfo info(originCollection, inactiveOrigins);
3153 MutexAutoLock lock(mQuotaMutex);
3154 mGroupInfoPairs.EnumerateRead(GetInactiveTemporaryStorageOrigins, &info);
3157 // We now have a list of all inactive origins. So it's safe to sort the list
3158 // and calculate available size without holding the lock.
3160 // Sort by the origin access time.
3161 inactiveOrigins.Sort(OriginInfoLRUComparator());
3163 // Create a list of inactive and the least recently used origins
3164 // whose aggregate size is greater or equals the minimal size to be freed.
3165 uint64_t sizeToBeFreed = 0;
3166 for(index = 0; index < inactiveOrigins.Length(); index++) {
3167 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3168 inactiveOrigins.TruncateLength(index);
3169 break;
3172 sizeToBeFreed += inactiveOrigins[index]->mUsage;
3175 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3176 // Success, add synchronized ops for these origins, so any other
3177 // operations for them will be delayed (until origin eviction is finalized).
3179 for(index = 0; index < inactiveOrigins.Length(); index++) {
3180 OriginOrPatternString oops =
3181 OriginOrPatternString::FromOrigin(inactiveOrigins[index]->mOrigin);
3183 AddSynchronizedOp(oops,
3184 Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY));
3187 inactiveOrigins.SwapElements(aOriginInfos);
3188 return sizeToBeFreed;
3191 return 0;
3194 void
3195 QuotaManager::DeleteTemporaryFilesForOrigin(const nsACString& aOrigin)
3197 nsCOMPtr<nsIFile> directory;
3198 nsresult rv = GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, aOrigin,
3199 getter_AddRefs(directory));
3200 NS_ENSURE_SUCCESS_VOID(rv);
3202 rv = directory->Remove(true);
3203 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
3204 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
3205 // This should never fail if we've closed all storage connections
3206 // correctly...
3207 NS_ERROR("Failed to remove directory!");
3211 void
3212 QuotaManager::FinalizeOriginEviction(nsTArray<nsCString>& aOrigins)
3214 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3216 nsRefPtr<FinalizeOriginEvictionRunnable> runnable =
3217 new FinalizeOriginEvictionRunnable(aOrigins);
3219 nsresult rv = IsOnIOThread() ? runnable->RunImmediately()
3220 : runnable->Dispatch();
3221 NS_ENSURE_SUCCESS_VOID(rv);
3224 void
3225 QuotaManager::SaveOriginAccessTime(const nsACString& aOrigin,
3226 int64_t aTimestamp)
3228 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3230 if (QuotaManager::IsShuttingDown()) {
3231 return;
3234 nsRefPtr<SaveOriginAccessTimeRunnable> runnable =
3235 new SaveOriginAccessTimeRunnable(aOrigin, aTimestamp);
3237 if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
3238 NS_WARNING("Failed to dispatch runnable!");
3242 void
3243 QuotaManager::GetOriginPatternString(uint32_t aAppId,
3244 MozBrowserPatternFlag aBrowserFlag,
3245 const nsACString& aOrigin,
3246 nsAutoCString& _retval)
3248 NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
3249 "Bad appId!");
3250 NS_ASSERTION(aOrigin.IsEmpty() || aBrowserFlag != IgnoreMozBrowser,
3251 "Bad args!");
3253 if (aOrigin.IsEmpty()) {
3254 _retval.Truncate();
3256 _retval.AppendInt(aAppId);
3257 _retval.Append('+');
3259 if (aBrowserFlag != IgnoreMozBrowser) {
3260 if (aBrowserFlag == MozBrowser) {
3261 _retval.Append('t');
3263 else {
3264 _retval.Append('f');
3266 _retval.Append('+');
3269 return;
3272 #ifdef DEBUG
3273 if (aAppId != nsIScriptSecurityManager::NO_APP_ID ||
3274 aBrowserFlag == MozBrowser) {
3275 nsAutoCString pattern;
3276 GetOriginPatternString(aAppId, aBrowserFlag, EmptyCString(), pattern);
3277 NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin),
3278 "Origin doesn't match parameters!");
3280 #endif
3282 _retval = aOrigin;
3285 SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
3286 Nullable<PersistenceType> aPersistenceType,
3287 const nsACString& aId)
3288 : mOriginOrPattern(aOriginOrPattern), mPersistenceType(aPersistenceType),
3289 mId(aId)
3291 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3292 MOZ_COUNT_CTOR(SynchronizedOp);
3295 SynchronizedOp::~SynchronizedOp()
3297 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3298 MOZ_COUNT_DTOR(SynchronizedOp);
3301 bool
3302 SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp)
3304 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3306 if (aExistingOp.mOriginOrPattern.IsNull() || mOriginOrPattern.IsNull()) {
3307 return true;
3310 bool match;
3312 if (aExistingOp.mOriginOrPattern.IsOrigin()) {
3313 if (mOriginOrPattern.IsOrigin()) {
3314 match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern);
3316 else {
3317 match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern);
3320 else if (mOriginOrPattern.IsOrigin()) {
3321 match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
3323 else {
3324 match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) ||
3325 PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
3328 // If the origins don't match, the second can proceed.
3329 if (!match) {
3330 return false;
3333 // If the origins match but the persistence types are different, the second
3334 // can proceed.
3335 if (!aExistingOp.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
3336 aExistingOp.mPersistenceType.Value() != mPersistenceType.Value()) {
3337 return false;
3340 // If the origins and the ids match, the second must wait.
3341 if (aExistingOp.mId == mId) {
3342 return true;
3345 // Waiting is required if either one corresponds to an origin clearing
3346 // (an empty Id).
3347 if (aExistingOp.mId.IsEmpty() || mId.IsEmpty()) {
3348 return true;
3351 // Otherwise, things for the same origin but different storages can proceed
3352 // independently.
3353 return false;
3356 void
3357 SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
3359 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3360 NS_ASSERTION(mDelayedRunnables.IsEmpty() || mId.IsEmpty(),
3361 "Only ClearOrigin operations can delay multiple runnables!");
3363 mDelayedRunnables.AppendElement(aRunnable);
3366 void
3367 SynchronizedOp::DispatchDelayedRunnables()
3369 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3370 NS_ASSERTION(!mListener, "Any listener should be gone by now!");
3372 uint32_t count = mDelayedRunnables.Length();
3373 for (uint32_t index = 0; index < count; index++) {
3374 NS_DispatchToCurrentThread(mDelayedRunnables[index]);
3377 mDelayedRunnables.Clear();
3380 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
3381 uint64_t aMinSizeToBeFreed)
3382 : mMinSizeToBeFreed(aMinSizeToBeFreed),
3383 mMutex(aMutex),
3384 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
3385 mSizeToBeFreed(0),
3386 mWaiting(true)
3388 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
3389 mMutex.AssertCurrentThreadOwns();
3392 int64_t
3393 CollectOriginsHelper::BlockAndReturnOriginsForEviction(
3394 nsTArray<OriginInfo*>& aOriginInfos)
3396 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
3397 mMutex.AssertCurrentThreadOwns();
3399 while (mWaiting) {
3400 mCondVar.Wait();
3403 mOriginInfos.SwapElements(aOriginInfos);
3404 return mSizeToBeFreed;
3407 NS_IMETHODIMP
3408 CollectOriginsHelper::Run()
3410 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
3412 QuotaManager* quotaManager = QuotaManager::Get();
3413 NS_ASSERTION(quotaManager, "Shouldn't be null!");
3415 // We use extra stack vars here to avoid race detector warnings (the same
3416 // memory accessed with and without the lock held).
3417 nsTArray<OriginInfo*> originInfos;
3418 uint64_t sizeToBeFreed =
3419 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, originInfos);
3421 MutexAutoLock lock(mMutex);
3423 NS_ASSERTION(mWaiting, "Huh?!");
3425 mOriginInfos.SwapElements(originInfos);
3426 mSizeToBeFreed = sizeToBeFreed;
3427 mWaiting = false;
3428 mCondVar.Notify();
3430 return NS_OK;
3433 nsresult
3434 OriginClearRunnable::OnExclusiveAccessAcquired()
3436 QuotaManager* quotaManager = QuotaManager::Get();
3437 NS_ASSERTION(quotaManager, "This should never fail!");
3439 nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3440 NS_ENSURE_SUCCESS(rv, rv);
3442 return NS_OK;
3445 // static
3446 void
3447 OriginClearRunnable::InvalidateOpenedStorages(
3448 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
3449 void* aClosure)
3451 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3453 nsTArray<nsCOMPtr<nsIOfflineStorage> > storages;
3454 storages.SwapElements(aStorages);
3456 for (uint32_t index = 0; index < storages.Length(); index++) {
3457 storages[index]->Invalidate();
3461 void
3462 OriginClearRunnable::DeleteFiles(QuotaManager* aQuotaManager,
3463 PersistenceType aPersistenceType)
3465 AssertIsOnIOThread();
3466 NS_ASSERTION(aQuotaManager, "Don't pass me null!");
3468 nsresult rv;
3470 nsCOMPtr<nsIFile> directory =
3471 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
3472 NS_ENSURE_SUCCESS_VOID(rv);
3474 rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
3475 NS_ENSURE_SUCCESS_VOID(rv);
3477 nsCOMPtr<nsISimpleEnumerator> entries;
3478 if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) ||
3479 !entries) {
3480 return;
3483 nsCString originSanitized(mOriginOrPattern);
3484 SanitizeOriginString(originSanitized);
3486 bool hasMore;
3487 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
3488 nsCOMPtr<nsISupports> entry;
3489 rv = entries->GetNext(getter_AddRefs(entry));
3490 NS_ENSURE_SUCCESS_VOID(rv);
3492 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
3493 NS_ASSERTION(file, "Don't know what this is!");
3495 bool isDirectory;
3496 rv = file->IsDirectory(&isDirectory);
3497 NS_ENSURE_SUCCESS_VOID(rv);
3499 if (!isDirectory) {
3500 NS_WARNING("Something in the IndexedDB directory that doesn't belong!");
3501 continue;
3504 nsString leafName;
3505 rv = file->GetLeafName(leafName);
3506 NS_ENSURE_SUCCESS_VOID(rv);
3508 // Skip storages for other apps.
3509 if (!PatternMatchesOrigin(originSanitized,
3510 NS_ConvertUTF16toUTF8(leafName))) {
3511 continue;
3514 if (NS_FAILED(file->Remove(true))) {
3515 // This should never fail if we've closed all storage connections
3516 // correctly...
3517 NS_ERROR("Failed to remove directory!");
3521 aQuotaManager->RemoveQuotaForPattern(aPersistenceType, mOriginOrPattern);
3523 aQuotaManager->OriginClearCompleted(aPersistenceType, mOriginOrPattern);
3526 NS_IMPL_ISUPPORTS_INHERITED0(OriginClearRunnable, nsRunnable)
3528 NS_IMETHODIMP
3529 OriginClearRunnable::Run()
3531 PROFILER_LABEL("OriginClearRunnable", "Run",
3532 js::ProfileEntry::Category::OTHER);
3534 QuotaManager* quotaManager = QuotaManager::Get();
3535 NS_ASSERTION(quotaManager, "This should never fail!");
3537 switch (mCallbackState) {
3538 case Pending: {
3539 NS_NOTREACHED("Should never get here without being dispatched!");
3540 return NS_ERROR_UNEXPECTED;
3543 case OpenAllowed: {
3544 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3546 AdvanceState();
3548 // Now we have to wait until the thread pool is done with all of the
3549 // storages we care about.
3550 nsresult rv =
3551 quotaManager->AcquireExclusiveAccess(mOriginOrPattern, mPersistenceType,
3552 this, InvalidateOpenedStorages,
3553 nullptr);
3554 NS_ENSURE_SUCCESS(rv, rv);
3556 return NS_OK;
3559 case IO: {
3560 AssertIsOnIOThread();
3562 AdvanceState();
3564 if (mPersistenceType.IsNull()) {
3565 DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3566 DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3567 } else {
3568 DeleteFiles(quotaManager, mPersistenceType.Value());
3571 // Now dispatch back to the main thread.
3572 if (NS_FAILED(NS_DispatchToMainThread(this))) {
3573 NS_WARNING("Failed to dispatch to main thread!");
3574 return NS_ERROR_FAILURE;
3577 return NS_OK;
3580 case Complete: {
3581 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3583 // Tell the QuotaManager that we're done.
3584 quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, mPersistenceType,
3585 EmptyCString());
3587 return NS_OK;
3590 default:
3591 NS_ERROR("Unknown state value!");
3592 return NS_ERROR_UNEXPECTED;
3595 NS_NOTREACHED("Should never get here!");
3596 return NS_ERROR_UNEXPECTED;
3599 AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId,
3600 bool aInMozBrowserOnly,
3601 const nsACString& aGroup,
3602 const OriginOrPatternString& aOrigin,
3603 nsIURI* aURI,
3604 nsIUsageCallback* aCallback)
3605 : mURI(aURI),
3606 mCallback(aCallback),
3607 mAppId(aAppId),
3608 mGroup(aGroup),
3609 mOrigin(aOrigin),
3610 mCallbackState(Pending),
3611 mInMozBrowserOnly(aInMozBrowserOnly)
3613 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3614 NS_ASSERTION(aURI, "Null pointer!");
3615 NS_ASSERTION(!aGroup.IsEmpty(), "Empty group!");
3616 NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!");
3617 NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
3618 NS_ASSERTION(aCallback, "Null pointer!");
3621 nsresult
3622 AsyncUsageRunnable::TakeShortcut()
3624 NS_ASSERTION(mCallbackState == Pending, "Huh?");
3626 nsresult rv = NS_DispatchToCurrentThread(this);
3627 NS_ENSURE_SUCCESS(rv, rv);
3629 mCallbackState = Shortcut;
3630 return NS_OK;
3633 nsresult
3634 AsyncUsageRunnable::RunInternal()
3636 QuotaManager* quotaManager = QuotaManager::Get();
3637 NS_ASSERTION(quotaManager, "This should never fail!");
3639 nsresult rv;
3641 switch (mCallbackState) {
3642 case Pending: {
3643 NS_NOTREACHED("Should never get here without being dispatched!");
3644 return NS_ERROR_UNEXPECTED;
3647 case OpenAllowed: {
3648 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3650 AdvanceState();
3652 rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3653 if (NS_FAILED(rv)) {
3654 NS_WARNING("Failed to dispatch to the IO thread!");
3657 return NS_OK;
3660 case IO: {
3661 AssertIsOnIOThread();
3663 AdvanceState();
3665 // Add all the persistent storage files we care about.
3666 rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3667 NS_ENSURE_SUCCESS(rv, rv);
3669 // Add all the temporary storage files we care about.
3670 rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3671 NS_ENSURE_SUCCESS(rv, rv);
3673 // Run dispatches us back to the main thread.
3674 return NS_OK;
3677 case Complete: // Fall through
3678 case Shortcut: {
3679 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3681 // Call the callback unless we were canceled.
3682 if (!mCanceled) {
3683 mCallback->OnUsageResult(mURI, TotalUsage(), FileUsage(), mAppId,
3684 mInMozBrowserOnly);
3687 // Clean up.
3688 mURI = nullptr;
3689 mCallback = nullptr;
3691 // And tell the QuotaManager that we're done.
3692 if (mCallbackState == Complete) {
3693 quotaManager->AllowNextSynchronizedOp(mOrigin,
3694 Nullable<PersistenceType>(),
3695 EmptyCString());
3698 return NS_OK;
3701 default:
3702 NS_ERROR("Unknown state value!");
3703 return NS_ERROR_UNEXPECTED;
3706 NS_NOTREACHED("Should never get here!");
3707 return NS_ERROR_UNEXPECTED;
3710 nsresult
3711 AsyncUsageRunnable::AddToUsage(QuotaManager* aQuotaManager,
3712 PersistenceType aPersistenceType)
3714 AssertIsOnIOThread();
3716 nsCOMPtr<nsIFile> directory;
3717 nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, mOrigin,
3718 getter_AddRefs(directory));
3719 NS_ENSURE_SUCCESS(rv, rv);
3721 bool exists;
3722 rv = directory->Exists(&exists);
3723 NS_ENSURE_SUCCESS(rv, rv);
3725 // If the directory exists then enumerate all the files inside, adding up
3726 // the sizes to get the final usage statistic.
3727 if (exists && !mCanceled) {
3728 bool initialized;
3730 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3731 initialized = aQuotaManager->mInitializedOrigins.Contains(mOrigin);
3733 if (!initialized) {
3734 rv = MaybeUpgradeOriginDirectory(directory);
3735 NS_ENSURE_SUCCESS(rv, rv);
3738 else {
3739 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
3740 initialized = aQuotaManager->mTemporaryStorageInitialized;
3743 nsCOMPtr<nsISimpleEnumerator> entries;
3744 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
3745 NS_ENSURE_SUCCESS(rv, rv);
3747 bool hasMore;
3748 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
3749 hasMore && !mCanceled) {
3750 nsCOMPtr<nsISupports> entry;
3751 rv = entries->GetNext(getter_AddRefs(entry));
3752 NS_ENSURE_SUCCESS(rv, rv);
3754 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
3755 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
3757 nsString leafName;
3758 rv = file->GetLeafName(leafName);
3759 NS_ENSURE_SUCCESS(rv, rv);
3761 if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
3762 leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
3763 continue;
3766 if (!initialized) {
3767 bool isDirectory;
3768 rv = file->IsDirectory(&isDirectory);
3769 NS_ENSURE_SUCCESS(rv, rv);
3771 if (!isDirectory) {
3772 NS_WARNING("Unknown file found!");
3773 return NS_ERROR_UNEXPECTED;
3777 Client::Type clientType;
3778 rv = Client::TypeFromText(leafName, clientType);
3779 if (NS_FAILED(rv)) {
3780 NS_WARNING("Unknown directory found!");
3781 if (!initialized) {
3782 return NS_ERROR_UNEXPECTED;
3784 continue;
3787 nsRefPtr<Client>& client = aQuotaManager->mClients[clientType];
3789 if (initialized) {
3790 rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOrigin, this);
3792 else {
3793 rv = client->InitOrigin(aPersistenceType, mGroup, mOrigin, this);
3795 NS_ENSURE_SUCCESS(rv, rv);
3799 return NS_OK;
3802 NS_IMPL_ISUPPORTS_INHERITED(AsyncUsageRunnable, nsRunnable, nsIQuotaRequest)
3804 NS_IMETHODIMP
3805 AsyncUsageRunnable::Run()
3807 PROFILER_LABEL("Quota", "AsyncUsageRunnable::Run",
3808 js::ProfileEntry::Category::OTHER);
3810 nsresult rv = RunInternal();
3812 if (!NS_IsMainThread()) {
3813 if (NS_FAILED(rv)) {
3814 ResetUsage();
3817 if (NS_FAILED(NS_DispatchToMainThread(this))) {
3818 NS_WARNING("Failed to dispatch to main thread!");
3822 return NS_OK;
3825 NS_IMETHODIMP
3826 AsyncUsageRunnable::Cancel()
3828 if (mCanceled.exchange(true)) {
3829 NS_WARNING("Canceled more than once?!");
3830 return NS_ERROR_UNEXPECTED;
3833 return NS_OK;
3836 nsresult
3837 ResetOrClearRunnable::OnExclusiveAccessAcquired()
3839 QuotaManager* quotaManager = QuotaManager::Get();
3840 NS_ASSERTION(quotaManager, "This should never fail!");
3842 nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3843 NS_ENSURE_SUCCESS(rv, rv);
3845 return NS_OK;
3848 // static
3849 void
3850 ResetOrClearRunnable::InvalidateOpenedStorages(
3851 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
3852 void* aClosure)
3854 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3856 nsTArray<nsCOMPtr<nsIOfflineStorage> > storages;
3857 storages.SwapElements(aStorages);
3859 for (uint32_t index = 0; index < storages.Length(); index++) {
3860 storages[index]->Invalidate();
3864 void
3865 ResetOrClearRunnable::DeleteFiles(QuotaManager* aQuotaManager,
3866 PersistenceType aPersistenceType)
3868 AssertIsOnIOThread();
3869 NS_ASSERTION(aQuotaManager, "Don't pass me null!");
3871 nsresult rv;
3873 nsCOMPtr<nsIFile> directory =
3874 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
3875 NS_ENSURE_SUCCESS_VOID(rv);
3877 rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
3878 NS_ENSURE_SUCCESS_VOID(rv);
3880 rv = directory->Remove(true);
3881 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
3882 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
3883 // This should never fail if we've closed all storage connections
3884 // correctly...
3885 NS_ERROR("Failed to remove directory!");
3889 NS_IMPL_ISUPPORTS_INHERITED0(ResetOrClearRunnable, nsRunnable)
3891 NS_IMETHODIMP
3892 ResetOrClearRunnable::Run()
3894 QuotaManager* quotaManager = QuotaManager::Get();
3895 NS_ASSERTION(quotaManager, "This should never fail!");
3897 switch (mCallbackState) {
3898 case Pending: {
3899 NS_NOTREACHED("Should never get here without being dispatched!");
3900 return NS_ERROR_UNEXPECTED;
3903 case OpenAllowed: {
3904 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3906 AdvanceState();
3908 // Now we have to wait until the thread pool is done with all of the
3909 // storages we care about.
3910 nsresult rv =
3911 quotaManager->AcquireExclusiveAccess(NullCString(),
3912 Nullable<PersistenceType>(), this,
3913 InvalidateOpenedStorages, nullptr);
3914 NS_ENSURE_SUCCESS(rv, rv);
3916 return NS_OK;
3919 case IO: {
3920 AssertIsOnIOThread();
3922 AdvanceState();
3924 if (mClear) {
3925 DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3926 DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3929 quotaManager->RemoveQuota();
3930 quotaManager->ResetOrClearCompleted();
3932 // Now dispatch back to the main thread.
3933 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
3934 NS_WARNING("Failed to dispatch to main thread!");
3935 return NS_ERROR_FAILURE;
3938 return NS_OK;
3941 case Complete: {
3942 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3944 // Tell the QuotaManager that we're done.
3945 quotaManager->AllowNextSynchronizedOp(OriginOrPatternString::FromNull(),
3946 Nullable<PersistenceType>(),
3947 EmptyCString());
3949 return NS_OK;
3952 default:
3953 NS_ERROR("Unknown state value!");
3954 return NS_ERROR_UNEXPECTED;
3957 NS_NOTREACHED("Should never get here!");
3958 return NS_ERROR_UNEXPECTED;
3961 NS_IMETHODIMP
3962 FinalizeOriginEvictionRunnable::Run()
3964 QuotaManager* quotaManager = QuotaManager::Get();
3965 NS_ASSERTION(quotaManager, "This should never fail!");
3967 nsresult rv;
3969 switch (mCallbackState) {
3970 case Pending: {
3971 NS_NOTREACHED("Should never get here without being dispatched!");
3972 return NS_ERROR_UNEXPECTED;
3975 case OpenAllowed: {
3976 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3978 AdvanceState();
3980 rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3981 if (NS_FAILED(rv)) {
3982 NS_WARNING("Failed to dispatch to the IO thread!");
3985 return NS_OK;
3988 case IO: {
3989 AssertIsOnIOThread();
3991 AdvanceState();
3993 for (uint32_t index = 0; index < mOrigins.Length(); index++) {
3994 quotaManager->OriginClearCompleted(
3995 PERSISTENCE_TYPE_TEMPORARY,
3996 OriginOrPatternString::FromOrigin(mOrigins[index]));
3999 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
4000 NS_WARNING("Failed to dispatch to main thread!");
4001 return NS_ERROR_FAILURE;
4004 return NS_OK;
4007 case Complete: {
4008 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
4010 for (uint32_t index = 0; index < mOrigins.Length(); index++) {
4011 quotaManager->AllowNextSynchronizedOp(
4012 OriginOrPatternString::FromOrigin(mOrigins[index]),
4013 Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY),
4014 EmptyCString());
4017 return NS_OK;
4020 default:
4021 NS_ERROR("Unknown state value!");
4022 return NS_ERROR_UNEXPECTED;
4025 NS_NOTREACHED("Should never get here!");
4026 return NS_ERROR_UNEXPECTED;
4029 nsresult
4030 FinalizeOriginEvictionRunnable::Dispatch()
4032 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
4033 NS_ASSERTION(mCallbackState == Pending, "Huh?");
4035 mCallbackState = OpenAllowed;
4036 return NS_DispatchToMainThread(this);
4039 nsresult
4040 FinalizeOriginEvictionRunnable::RunImmediately()
4042 AssertIsOnIOThread();
4043 NS_ASSERTION(mCallbackState == Pending, "Huh?");
4045 mCallbackState = IO;
4046 return this->Run();
4049 NS_IMETHODIMP
4050 WaitForTransactionsToFinishRunnable::Run()
4052 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
4053 NS_ASSERTION(mOp, "Null op!");
4054 NS_ASSERTION(mOp->mListener, "Nothing to run!");
4055 NS_ASSERTION(mCountdown, "Wrong countdown!");
4057 if (--mCountdown) {
4058 return NS_OK;
4061 // Don't hold the listener alive longer than necessary.
4062 nsRefPtr<AcquireListener> listener;
4063 listener.swap(mOp->mListener);
4065 mOp = nullptr;
4067 nsresult rv = listener->OnExclusiveAccessAcquired();
4068 NS_ENSURE_SUCCESS(rv, rv);
4070 // The listener is responsible for calling
4071 // QuotaManager::AllowNextSynchronizedOp.
4072 return NS_OK;
4075 NS_IMETHODIMP
4076 WaitForFileHandlesToFinishRunnable::Run()
4078 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
4080 mBusy = false;
4082 return NS_OK;
4085 NS_IMETHODIMP
4086 SaveOriginAccessTimeRunnable::Run()
4088 AssertIsOnIOThread();
4090 QuotaManager* quotaManager = QuotaManager::Get();
4091 NS_ASSERTION(quotaManager, "This should never fail!");
4093 nsCOMPtr<nsIFile> directory;
4094 nsresult rv =
4095 quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, mOrigin,
4096 getter_AddRefs(directory));
4097 NS_ENSURE_SUCCESS(rv, rv);
4099 nsCOMPtr<nsIBinaryOutputStream> stream;
4100 rv = GetDirectoryMetadataStream(directory, true, getter_AddRefs(stream));
4101 NS_ENSURE_SUCCESS(rv, rv);
4103 // The origin directory may not exist anymore.
4104 if (stream) {
4105 rv = stream->Write64(mTimestamp);
4106 NS_ENSURE_SUCCESS(rv, rv);
4109 return NS_OK;