Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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"
25 #include <algorithm>
26 #include "GeckoProfiler.h"
27 #include "mozilla/Atomics.h"
28 #include "mozilla/CondVar.h"
29 #include "mozilla/dom/asmjscache/AsmJSCache.h"
30 #include "mozilla/dom/file/FileService.h"
31 #include "mozilla/dom/indexedDB/Client.h"
32 #include "mozilla/Mutex.h"
33 #include "mozilla/LazyIdleThread.h"
34 #include "mozilla/Preferences.h"
35 #include "mozilla/Services.h"
36 #include "nsAppDirectoryServiceDefs.h"
37 #include "nsComponentManagerUtils.h"
38 #include "nsContentUtils.h"
39 #include "nsCRTGlue.h"
40 #include "nsDirectoryServiceUtils.h"
41 #include "nsNetUtil.h"
42 #include "nsScriptSecurityManager.h"
43 #include "nsThreadUtils.h"
44 #include "nsXULAppAPI.h"
45 #include "xpcpublic.h"
47 #include "AcquireListener.h"
48 #include "CheckQuotaHelper.h"
49 #include "OriginCollection.h"
50 #include "OriginOrPatternString.h"
51 #include "QuotaObject.h"
52 #include "StorageMatcher.h"
53 #include "UsageInfo.h"
54 #include "Utilities.h"
56 // The amount of time, in milliseconds, that our IO thread will stay alive
57 // after the last event it processes.
58 #define DEFAULT_THREAD_TIMEOUT_MS 30000
60 // The amount of time, in milliseconds, that we will wait for active storage
61 // transactions on shutdown before aborting them.
62 #define DEFAULT_SHUTDOWN_TIMER_MS 30000
64 // Preference that users can set to override DEFAULT_QUOTA_MB
65 #define PREF_STORAGE_QUOTA "dom.indexedDB.warningQuota"
67 // Preference that users can set to override temporary storage smart limit
68 // calculation.
69 #define PREF_FIXED_LIMIT "dom.quotaManager.temporaryStorage.fixedLimit"
70 #define PREF_CHUNK_SIZE "dom.quotaManager.temporaryStorage.chunkSize"
72 // Preference that is used to enable testing features
73 #define PREF_TESTING_FEATURES "dom.quotaManager.testing"
75 // profile-before-change, when we need to shut down quota manager
76 #define PROFILE_BEFORE_CHANGE_OBSERVER_ID "profile-before-change"
78 // The name of the file that we use to load/save the last access time of an
79 // origin.
80 #define METADATA_FILE_NAME ".metadata"
82 #define PERMISSION_DEFAUT_PERSISTENT_STORAGE "default-persistent-storage"
84 #define KB * 1024ULL
85 #define MB * 1024ULL KB
86 #define GB * 1024ULL MB
88 USING_QUOTA_NAMESPACE
89 using namespace mozilla::dom;
90 using mozilla::dom::file::FileService;
92 static_assert(
93 static_cast<uint32_t>(StorageType::Persistent) ==
94 static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
95 "Enum values should match.");
97 static_assert(
98 static_cast<uint32_t>(StorageType::Temporary) ==
99 static_cast<uint32_t>(PERSISTENCE_TYPE_TEMPORARY),
100 "Enum values should match.");
102 BEGIN_QUOTA_NAMESPACE
104 // A struct that contains the information corresponding to a pending or
105 // running operation that requires synchronization (e.g. opening a db,
106 // clearing dbs for an origin, etc).
107 struct SynchronizedOp
108 {
109 SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
110 Nullable<PersistenceType> aPersistenceType,
111 const nsACString& aId);
113 ~SynchronizedOp();
115 // Test whether this SynchronizedOp needs to wait for the given op.
116 bool
117 MustWaitFor(const SynchronizedOp& aOp);
119 void
120 DelayRunnable(nsIRunnable* aRunnable);
122 void
123 DispatchDelayedRunnables();
125 const OriginOrPatternString mOriginOrPattern;
126 Nullable<PersistenceType> mPersistenceType;
127 nsCString mId;
128 nsRefPtr<AcquireListener> mListener;
129 nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
130 ArrayCluster<nsIOfflineStorage*> mStorages;
131 };
133 class CollectOriginsHelper MOZ_FINAL : public nsRunnable
134 {
135 public:
136 CollectOriginsHelper(mozilla::Mutex& aMutex, uint64_t aMinSizeToBeFreed);
138 NS_IMETHOD
139 Run();
141 // Blocks the current thread until origins are collected on the main thread.
142 // The returned value contains an aggregate size of those origins.
143 int64_t
144 BlockAndReturnOriginsForEviction(nsTArray<OriginInfo*>& aOriginInfos);
146 private:
147 ~CollectOriginsHelper()
148 { }
150 uint64_t mMinSizeToBeFreed;
152 mozilla::Mutex& mMutex;
153 mozilla::CondVar mCondVar;
155 // The members below are protected by mMutex.
156 nsTArray<OriginInfo*> mOriginInfos;
157 uint64_t mSizeToBeFreed;
158 bool mWaiting;
159 };
161 // Responsible for clearing the storage files for a particular origin on the
162 // IO thread. Created when nsIQuotaManager::ClearStoragesForURI is called.
163 // Runs three times, first on the main thread, next on the IO thread, and then
164 // finally again on the main thread. While on the IO thread the runnable will
165 // actually remove the origin's storage files and the directory that contains
166 // them before dispatching itself back to the main thread. When back on the main
167 // thread the runnable will notify the QuotaManager that the job has been
168 // completed.
169 class OriginClearRunnable MOZ_FINAL : public nsRunnable,
170 public AcquireListener
171 {
172 enum CallbackState {
173 // Not yet run.
174 Pending = 0,
176 // Running on the main thread in the callback for OpenAllowed.
177 OpenAllowed,
179 // Running on the IO thread.
180 IO,
182 // Running on the main thread after all work is done.
183 Complete
184 };
186 public:
187 NS_DECL_ISUPPORTS_INHERITED
189 OriginClearRunnable(const OriginOrPatternString& aOriginOrPattern,
190 Nullable<PersistenceType> aPersistenceType)
191 : mOriginOrPattern(aOriginOrPattern),
192 mPersistenceType(aPersistenceType),
193 mCallbackState(Pending)
194 { }
196 NS_IMETHOD
197 Run();
199 // AcquireListener override
200 virtual nsresult
201 OnExclusiveAccessAcquired() MOZ_OVERRIDE;
203 void
204 AdvanceState()
205 {
206 switch (mCallbackState) {
207 case Pending:
208 mCallbackState = OpenAllowed;
209 return;
210 case OpenAllowed:
211 mCallbackState = IO;
212 return;
213 case IO:
214 mCallbackState = Complete;
215 return;
216 default:
217 NS_NOTREACHED("Can't advance past Complete!");
218 }
219 }
221 static void
222 InvalidateOpenedStorages(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
223 void* aClosure);
225 void
226 DeleteFiles(QuotaManager* aQuotaManager,
227 PersistenceType aPersistenceType);
229 private:
230 OriginOrPatternString mOriginOrPattern;
231 Nullable<PersistenceType> mPersistenceType;
232 CallbackState mCallbackState;
233 };
235 // Responsible for calculating the amount of space taken up by storages of a
236 // certain origin. Created when nsIQuotaManager::GetUsageForURI is called.
237 // May be canceled with nsIQuotaRequest::Cancel. Runs three times, first
238 // on the main thread, next on the IO thread, and then finally again on the main
239 // thread. While on the IO thread the runnable will calculate the size of all
240 // files in the origin's directory before dispatching itself back to the main
241 // thread. When on the main thread the runnable will call the callback and then
242 // notify the QuotaManager that the job has been completed.
243 class AsyncUsageRunnable MOZ_FINAL : public UsageInfo,
244 public nsRunnable,
245 public nsIQuotaRequest
246 {
247 enum CallbackState {
248 // Not yet run.
249 Pending = 0,
251 // Running on the main thread in the callback for OpenAllowed.
252 OpenAllowed,
254 // Running on the IO thread.
255 IO,
257 // Running on the main thread after all work is done.
258 Complete,
260 // Running on the main thread after skipping the work
261 Shortcut
262 };
264 public:
265 NS_DECL_ISUPPORTS_INHERITED
266 NS_DECL_NSIQUOTAREQUEST
268 AsyncUsageRunnable(uint32_t aAppId,
269 bool aInMozBrowserOnly,
270 const nsACString& aGroup,
271 const OriginOrPatternString& aOrigin,
272 nsIURI* aURI,
273 nsIUsageCallback* aCallback);
275 NS_IMETHOD
276 Run();
278 void
279 AdvanceState()
280 {
281 switch (mCallbackState) {
282 case Pending:
283 mCallbackState = OpenAllowed;
284 return;
285 case OpenAllowed:
286 mCallbackState = IO;
287 return;
288 case IO:
289 mCallbackState = Complete;
290 return;
291 default:
292 NS_NOTREACHED("Can't advance past Complete!");
293 }
294 }
296 nsresult
297 TakeShortcut();
299 private:
300 // Run calls the RunInternal method and makes sure that we always dispatch
301 // to the main thread in case of an error.
302 inline nsresult
303 RunInternal();
305 nsresult
306 AddToUsage(QuotaManager* aQuotaManager,
307 PersistenceType aPersistenceType);
309 nsCOMPtr<nsIURI> mURI;
310 nsCOMPtr<nsIUsageCallback> mCallback;
311 uint32_t mAppId;
312 nsCString mGroup;
313 OriginOrPatternString mOrigin;
314 CallbackState mCallbackState;
315 bool mInMozBrowserOnly;
316 };
318 class ResetOrClearRunnable MOZ_FINAL : public nsRunnable,
319 public AcquireListener
320 {
321 enum CallbackState {
322 // Not yet run.
323 Pending = 0,
325 // Running on the main thread in the callback for OpenAllowed.
326 OpenAllowed,
328 // Running on the IO thread.
329 IO,
331 // Running on the main thread after all work is done.
332 Complete
333 };
335 public:
336 NS_DECL_ISUPPORTS_INHERITED
338 ResetOrClearRunnable(bool aClear)
339 : mCallbackState(Pending),
340 mClear(aClear)
341 { }
343 NS_IMETHOD
344 Run();
346 // AcquireListener override
347 virtual nsresult
348 OnExclusiveAccessAcquired() MOZ_OVERRIDE;
350 void
351 AdvanceState()
352 {
353 switch (mCallbackState) {
354 case Pending:
355 mCallbackState = OpenAllowed;
356 return;
357 case OpenAllowed:
358 mCallbackState = IO;
359 return;
360 case IO:
361 mCallbackState = Complete;
362 return;
363 default:
364 NS_NOTREACHED("Can't advance past Complete!");
365 }
366 }
368 static void
369 InvalidateOpenedStorages(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
370 void* aClosure);
372 void
373 DeleteFiles(QuotaManager* aQuotaManager,
374 PersistenceType aPersistenceType);
376 private:
377 CallbackState mCallbackState;
378 bool mClear;
379 };
381 // Responsible for finalizing eviction of certian origins (storage files have
382 // been already cleared, we just need to release IO thread only objects and
383 // allow next synchronized ops for evicted origins). Created when
384 // QuotaManager::FinalizeOriginEviction is called. Runs three times, first
385 // on the main thread, next on the IO thread, and then finally again on the main
386 // thread. While on the IO thread the runnable will release IO thread only
387 // objects before dispatching itself back to the main thread. When back on the
388 // main thread the runnable will call QuotaManager::AllowNextSynchronizedOp.
389 // The runnable can also run in a shortened mode (runs only twice).
390 class FinalizeOriginEvictionRunnable MOZ_FINAL : public nsRunnable
391 {
392 enum CallbackState {
393 // Not yet run.
394 Pending = 0,
396 // Running on the main thread in the callback for OpenAllowed.
397 OpenAllowed,
399 // Running on the IO thread.
400 IO,
402 // Running on the main thread after IO work is done.
403 Complete
404 };
406 public:
407 FinalizeOriginEvictionRunnable(nsTArray<nsCString>& aOrigins)
408 : mCallbackState(Pending)
409 {
410 mOrigins.SwapElements(aOrigins);
411 }
413 NS_IMETHOD
414 Run();
416 void
417 AdvanceState()
418 {
419 switch (mCallbackState) {
420 case Pending:
421 mCallbackState = OpenAllowed;
422 return;
423 case OpenAllowed:
424 mCallbackState = IO;
425 return;
426 case IO:
427 mCallbackState = Complete;
428 return;
429 default:
430 MOZ_ASSUME_UNREACHABLE("Can't advance past Complete!");
431 }
432 }
434 nsresult
435 Dispatch();
437 nsresult
438 RunImmediately();
440 private:
441 CallbackState mCallbackState;
442 nsTArray<nsCString> mOrigins;
443 };
445 bool
446 IsOnIOThread()
447 {
448 QuotaManager* quotaManager = QuotaManager::Get();
449 NS_ASSERTION(quotaManager, "Must have a manager here!");
451 bool currentThread;
452 return NS_SUCCEEDED(quotaManager->IOThread()->
453 IsOnCurrentThread(¤tThread)) && currentThread;
454 }
456 void
457 AssertIsOnIOThread()
458 {
459 NS_ASSERTION(IsOnIOThread(), "Running on the wrong thread!");
460 }
462 void
463 AssertCurrentThreadOwnsQuotaMutex()
464 {
465 #ifdef DEBUG
466 QuotaManager* quotaManager = QuotaManager::Get();
467 NS_ASSERTION(quotaManager, "Must have a manager here!");
469 quotaManager->AssertCurrentThreadOwnsQuotaMutex();
470 #endif
471 }
473 END_QUOTA_NAMESPACE
475 namespace {
477 // Amount of space that storages may use by default in megabytes.
478 static const int32_t kDefaultQuotaMB = 50;
481 QuotaManager* gInstance = nullptr;
482 mozilla::Atomic<bool> gShutdown(false);
484 int32_t gStorageQuotaMB = kDefaultQuotaMB;
486 // Constants for temporary storage limit computing.
487 static const int32_t kDefaultFixedLimitKB = -1;
488 static const uint32_t kDefaultChunkSizeKB = 10 * 1024;
489 int32_t gFixedLimitKB = kDefaultFixedLimitKB;
490 uint32_t gChunkSizeKB = kDefaultChunkSizeKB;
492 bool gTestingEnabled = false;
494 // A callback runnable used by the TransactionPool when it's safe to proceed
495 // with a SetVersion/DeleteDatabase/etc.
496 class WaitForTransactionsToFinishRunnable MOZ_FINAL : public nsRunnable
497 {
498 public:
499 WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
500 : mOp(aOp), mCountdown(1)
501 {
502 NS_ASSERTION(mOp, "Why don't we have a runnable?");
503 NS_ASSERTION(mOp->mStorages.IsEmpty(), "We're here too early!");
504 NS_ASSERTION(mOp->mListener,
505 "What are we supposed to do when we're done?");
506 NS_ASSERTION(mCountdown, "Wrong countdown!");
507 }
509 NS_IMETHOD
510 Run();
512 void
513 AddRun()
514 {
515 mCountdown++;
516 }
518 private:
519 // The QuotaManager holds this alive.
520 SynchronizedOp* mOp;
521 uint32_t mCountdown;
522 };
524 class WaitForLockedFilesToFinishRunnable MOZ_FINAL : public nsRunnable
525 {
526 public:
527 WaitForLockedFilesToFinishRunnable()
528 : mBusy(true)
529 { }
531 NS_IMETHOD
532 Run();
534 bool
535 IsBusy() const
536 {
537 return mBusy;
538 }
540 private:
541 bool mBusy;
542 };
544 class SaveOriginAccessTimeRunnable MOZ_FINAL : public nsRunnable
545 {
546 public:
547 SaveOriginAccessTimeRunnable(const nsACString& aOrigin, int64_t aTimestamp)
548 : mOrigin(aOrigin), mTimestamp(aTimestamp)
549 { }
551 NS_IMETHOD
552 Run();
554 private:
555 nsCString mOrigin;
556 int64_t mTimestamp;
557 };
559 struct MOZ_STACK_CLASS RemoveQuotaInfo
560 {
561 RemoveQuotaInfo(PersistenceType aPersistenceType, const nsACString& aPattern)
562 : persistenceType(aPersistenceType), pattern(aPattern)
563 { }
565 PersistenceType persistenceType;
566 nsCString pattern;
567 };
569 struct MOZ_STACK_CLASS InactiveOriginsInfo
570 {
571 InactiveOriginsInfo(OriginCollection& aCollection,
572 nsTArray<OriginInfo*>& aOrigins)
573 : collection(aCollection), origins(aOrigins)
574 { }
576 OriginCollection& collection;
577 nsTArray<OriginInfo*>& origins;
578 };
580 bool
581 IsMainProcess()
582 {
583 return XRE_GetProcessType() == GeckoProcessType_Default;
584 }
586 void
587 SanitizeOriginString(nsCString& aOrigin)
588 {
589 // We want profiles to be platform-independent so we always need to replace
590 // the same characters on every platform. Windows has the most extensive set
591 // of illegal characters so we use its FILE_ILLEGAL_CHARACTERS and
592 // FILE_PATH_SEPARATOR.
593 static const char kReplaceChars[] = CONTROL_CHARACTERS "/:*?\"<>|\\";
595 #ifdef XP_WIN
596 NS_ASSERTION(!strcmp(kReplaceChars,
597 FILE_ILLEGAL_CHARACTERS FILE_PATH_SEPARATOR),
598 "Illegal file characters have changed!");
599 #endif
601 aOrigin.ReplaceChar(kReplaceChars, '+');
602 }
604 nsresult
605 EnsureDirectory(nsIFile* aDirectory, bool* aCreated)
606 {
607 AssertIsOnIOThread();
609 nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
610 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
611 bool isDirectory;
612 rv = aDirectory->IsDirectory(&isDirectory);
613 NS_ENSURE_SUCCESS(rv, rv);
614 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
616 *aCreated = false;
617 }
618 else {
619 NS_ENSURE_SUCCESS(rv, rv);
621 *aCreated = true;
622 }
624 return NS_OK;
625 }
627 nsresult
628 CreateDirectoryUpgradeStamp(nsIFile* aDirectory)
629 {
630 AssertIsOnIOThread();
632 nsCOMPtr<nsIFile> metadataFile;
633 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
634 NS_ENSURE_SUCCESS(rv, rv);
636 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
637 NS_ENSURE_SUCCESS(rv, rv);
639 rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
640 NS_ENSURE_SUCCESS(rv, rv);
642 return NS_OK;
643 }
645 nsresult
646 GetDirectoryMetadataStream(nsIFile* aDirectory, bool aUpdate,
647 nsIBinaryOutputStream** aStream)
648 {
649 AssertIsOnIOThread();
651 nsCOMPtr<nsIFile> metadataFile;
652 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
653 NS_ENSURE_SUCCESS(rv, rv);
655 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
656 NS_ENSURE_SUCCESS(rv, rv);
658 nsCOMPtr<nsIOutputStream> outputStream;
659 if (aUpdate) {
660 bool exists;
661 rv = metadataFile->Exists(&exists);
662 NS_ENSURE_SUCCESS(rv, rv);
664 if (!exists) {
665 *aStream = nullptr;
666 return NS_OK;
667 }
669 nsCOMPtr<nsIFileStream> stream;
670 rv = NS_NewLocalFileStream(getter_AddRefs(stream), metadataFile);
671 NS_ENSURE_SUCCESS(rv, rv);
673 outputStream = do_QueryInterface(stream);
674 NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE);
675 }
676 else {
677 rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
678 metadataFile);
679 NS_ENSURE_SUCCESS(rv, rv);
680 }
682 nsCOMPtr<nsIBinaryOutputStream> binaryStream =
683 do_CreateInstance("@mozilla.org/binaryoutputstream;1");
684 NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE);
686 rv = binaryStream->SetOutputStream(outputStream);
687 NS_ENSURE_SUCCESS(rv, rv);
689 binaryStream.forget(aStream);
690 return NS_OK;
691 }
693 nsresult
694 CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
695 const nsACString& aGroup, const nsACString& aOrigin)
696 {
697 AssertIsOnIOThread();
699 nsCOMPtr<nsIBinaryOutputStream> stream;
700 nsresult rv =
701 GetDirectoryMetadataStream(aDirectory, false, getter_AddRefs(stream));
702 NS_ENSURE_SUCCESS(rv, rv);
704 NS_ASSERTION(stream, "This shouldn't be null!");
706 rv = stream->Write64(aTimestamp);
707 NS_ENSURE_SUCCESS(rv, rv);
709 rv = stream->WriteStringZ(PromiseFlatCString(aGroup).get());
710 NS_ENSURE_SUCCESS(rv, rv);
712 rv = stream->WriteStringZ(PromiseFlatCString(aOrigin).get());
713 NS_ENSURE_SUCCESS(rv, rv);
715 return NS_OK;
716 }
718 nsresult
719 GetDirectoryMetadata(nsIFile* aDirectory, int64_t* aTimestamp,
720 nsACString& aGroup, nsACString& aOrigin)
721 {
722 AssertIsOnIOThread();
724 nsCOMPtr<nsIFile> metadataFile;
725 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
726 NS_ENSURE_SUCCESS(rv, rv);
728 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
729 NS_ENSURE_SUCCESS(rv, rv);
731 nsCOMPtr<nsIInputStream> stream;
732 rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metadataFile);
733 NS_ENSURE_SUCCESS(rv, rv);
735 nsCOMPtr<nsIInputStream> bufferedStream;
736 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512);
737 NS_ENSURE_SUCCESS(rv, rv);
739 nsCOMPtr<nsIBinaryInputStream> binaryStream =
740 do_CreateInstance("@mozilla.org/binaryinputstream;1");
741 NS_ENSURE_TRUE(binaryStream, NS_ERROR_FAILURE);
743 rv = binaryStream->SetInputStream(bufferedStream);
744 NS_ENSURE_SUCCESS(rv, rv);
746 uint64_t timestamp;
747 rv = binaryStream->Read64(×tamp);
748 NS_ENSURE_SUCCESS(rv, rv);
750 nsCString group;
751 rv = binaryStream->ReadCString(group);
752 NS_ENSURE_SUCCESS(rv, rv);
754 nsCString origin;
755 rv = binaryStream->ReadCString(origin);
756 NS_ENSURE_SUCCESS(rv, rv);
758 *aTimestamp = timestamp;
759 aGroup = group;
760 aOrigin = origin;
761 return NS_OK;
762 }
764 nsresult
765 MaybeUpgradeOriginDirectory(nsIFile* aDirectory)
766 {
767 AssertIsOnIOThread();
768 NS_ASSERTION(aDirectory, "Null pointer!");
770 nsCOMPtr<nsIFile> metadataFile;
771 nsresult rv = aDirectory->Clone(getter_AddRefs(metadataFile));
772 NS_ENSURE_SUCCESS(rv, rv);
774 rv = metadataFile->Append(NS_LITERAL_STRING(METADATA_FILE_NAME));
775 NS_ENSURE_SUCCESS(rv, rv);
777 bool exists;
778 rv = metadataFile->Exists(&exists);
779 NS_ENSURE_SUCCESS(rv, rv);
781 if (!exists) {
782 // Directory structure upgrade needed.
783 // Move all files to IDB specific directory.
785 nsString idbDirectoryName;
786 rv = Client::TypeToText(Client::IDB, idbDirectoryName);
787 NS_ENSURE_SUCCESS(rv, rv);
789 nsCOMPtr<nsIFile> idbDirectory;
790 rv = aDirectory->Clone(getter_AddRefs(idbDirectory));
791 NS_ENSURE_SUCCESS(rv, rv);
793 rv = idbDirectory->Append(idbDirectoryName);
794 NS_ENSURE_SUCCESS(rv, rv);
796 rv = idbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
797 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
798 NS_WARNING("IDB directory already exists!");
800 bool isDirectory;
801 rv = idbDirectory->IsDirectory(&isDirectory);
802 NS_ENSURE_SUCCESS(rv, rv);
803 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
804 }
805 else {
806 NS_ENSURE_SUCCESS(rv, rv);
807 }
809 nsCOMPtr<nsISimpleEnumerator> entries;
810 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
811 NS_ENSURE_SUCCESS(rv, rv);
813 bool hasMore;
814 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
815 nsCOMPtr<nsISupports> entry;
816 rv = entries->GetNext(getter_AddRefs(entry));
817 NS_ENSURE_SUCCESS(rv, rv);
819 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
820 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
822 nsString leafName;
823 rv = file->GetLeafName(leafName);
824 NS_ENSURE_SUCCESS(rv, rv);
826 if (!leafName.Equals(idbDirectoryName)) {
827 rv = file->MoveTo(idbDirectory, EmptyString());
828 NS_ENSURE_SUCCESS(rv, rv);
829 }
830 }
832 rv = metadataFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
833 NS_ENSURE_SUCCESS(rv, rv);
834 }
836 return NS_OK;
837 }
839 // This method computes and returns our best guess for the temporary storage
840 // limit (in bytes), based on the amount of space users have free on their hard
841 // drive and on given temporary storage usage (also in bytes).
842 nsresult
843 GetTemporaryStorageLimit(nsIFile* aDirectory, uint64_t aCurrentUsage,
844 uint64_t* aLimit)
845 {
846 // Check for free space on device where temporary storage directory lives.
847 int64_t bytesAvailable;
848 nsresult rv = aDirectory->GetDiskSpaceAvailable(&bytesAvailable);
849 NS_ENSURE_SUCCESS(rv, rv);
851 NS_ASSERTION(bytesAvailable >= 0, "Negative bytes available?!");
853 uint64_t availableKB =
854 static_cast<uint64_t>((bytesAvailable + aCurrentUsage) / 1024);
856 // Grow/shrink in gChunkSizeKB units, deliberately, so that in the common case
857 // we don't shrink temporary storage and evict origin data every time we
858 // initialize.
859 availableKB = (availableKB / gChunkSizeKB) * gChunkSizeKB;
861 // Allow temporary storage to consume up to half the available space.
862 uint64_t resultKB = availableKB * .50;
864 *aLimit = resultKB * 1024;
865 return NS_OK;
866 }
868 } // anonymous namespace
870 QuotaManager::QuotaManager()
871 : mCurrentWindowIndex(BAD_TLS_INDEX),
872 mQuotaMutex("QuotaManager.mQuotaMutex"),
873 mTemporaryStorageLimit(0),
874 mTemporaryStorageUsage(0),
875 mTemporaryStorageInitialized(false),
876 mStorageAreaInitialized(false)
877 {
878 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
879 NS_ASSERTION(!gInstance, "More than one instance!");
880 }
882 QuotaManager::~QuotaManager()
883 {
884 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
885 NS_ASSERTION(!gInstance || gInstance == this, "Different instances!");
886 gInstance = nullptr;
887 }
889 // static
890 QuotaManager*
891 QuotaManager::GetOrCreate()
892 {
893 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
895 if (IsShuttingDown()) {
896 NS_ERROR("Calling GetOrCreate() after shutdown!");
897 return nullptr;
898 }
900 if (!gInstance) {
901 nsRefPtr<QuotaManager> instance(new QuotaManager());
903 nsresult rv = instance->Init();
904 NS_ENSURE_SUCCESS(rv, nullptr);
906 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
907 NS_ENSURE_TRUE(obs, nullptr);
909 // We need this callback to know when to shut down all our threads.
910 rv = obs->AddObserver(instance, PROFILE_BEFORE_CHANGE_OBSERVER_ID, false);
911 NS_ENSURE_SUCCESS(rv, nullptr);
913 // The observer service will hold our last reference, don't AddRef here.
914 gInstance = instance;
915 }
917 return gInstance;
918 }
920 // static
921 QuotaManager*
922 QuotaManager::Get()
923 {
924 // Does not return an owning reference.
925 return gInstance;
926 }
928 // static
929 QuotaManager*
930 QuotaManager::FactoryCreate()
931 {
932 // Returns a raw pointer that carries an owning reference! Lame, but the
933 // singleton factory macros force this.
934 QuotaManager* quotaManager = GetOrCreate();
935 NS_IF_ADDREF(quotaManager);
936 return quotaManager;
937 }
939 // static
940 bool
941 QuotaManager::IsShuttingDown()
942 {
943 return gShutdown;
944 }
946 nsresult
947 QuotaManager::Init()
948 {
949 // We need a thread-local to hold the current window.
950 NS_ASSERTION(mCurrentWindowIndex == BAD_TLS_INDEX, "Huh?");
952 if (PR_NewThreadPrivateIndex(&mCurrentWindowIndex, nullptr) != PR_SUCCESS) {
953 NS_ERROR("PR_NewThreadPrivateIndex failed, QuotaManager disabled");
954 mCurrentWindowIndex = BAD_TLS_INDEX;
955 return NS_ERROR_FAILURE;
956 }
958 nsresult rv;
959 if (IsMainProcess()) {
960 nsCOMPtr<nsIFile> baseDir;
961 rv = NS_GetSpecialDirectory(NS_APP_INDEXEDDB_PARENT_DIR,
962 getter_AddRefs(baseDir));
963 if (NS_FAILED(rv)) {
964 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
965 getter_AddRefs(baseDir));
966 }
967 NS_ENSURE_SUCCESS(rv, rv);
969 nsCOMPtr<nsIFile> indexedDBDir;
970 rv = baseDir->Clone(getter_AddRefs(indexedDBDir));
971 NS_ENSURE_SUCCESS(rv, rv);
973 rv = indexedDBDir->Append(NS_LITERAL_STRING("indexedDB"));
974 NS_ENSURE_SUCCESS(rv, rv);
976 rv = indexedDBDir->GetPath(mIndexedDBPath);
977 NS_ENSURE_SUCCESS(rv, rv);
979 rv = baseDir->Append(NS_LITERAL_STRING("storage"));
980 NS_ENSURE_SUCCESS(rv, rv);
982 nsCOMPtr<nsIFile> persistentStorageDir;
983 rv = baseDir->Clone(getter_AddRefs(persistentStorageDir));
984 NS_ENSURE_SUCCESS(rv, rv);
986 rv = persistentStorageDir->Append(NS_LITERAL_STRING("persistent"));
987 NS_ENSURE_SUCCESS(rv, rv);
989 rv = persistentStorageDir->GetPath(mPersistentStoragePath);
990 NS_ENSURE_SUCCESS(rv, rv);
992 nsCOMPtr<nsIFile> temporaryStorageDir;
993 rv = baseDir->Clone(getter_AddRefs(temporaryStorageDir));
994 NS_ENSURE_SUCCESS(rv, rv);
996 rv = temporaryStorageDir->Append(NS_LITERAL_STRING("temporary"));
997 NS_ENSURE_SUCCESS(rv, rv);
999 rv = temporaryStorageDir->GetPath(mTemporaryStoragePath);
1000 NS_ENSURE_SUCCESS(rv, rv);
1002 // Make a lazy thread for any IO we need (like clearing or enumerating the
1003 // contents of storage directories).
1004 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
1005 NS_LITERAL_CSTRING("Storage I/O"),
1006 LazyIdleThread::ManualShutdown);
1008 // Make a timer here to avoid potential failures later. We don't actually
1009 // initialize the timer until shutdown.
1010 mShutdownTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
1011 NS_ENSURE_TRUE(mShutdownTimer, NS_ERROR_FAILURE);
1012 }
1014 if (NS_FAILED(Preferences::AddIntVarCache(&gStorageQuotaMB,
1015 PREF_STORAGE_QUOTA,
1016 kDefaultQuotaMB))) {
1017 NS_WARNING("Unable to respond to quota pref changes!");
1018 }
1020 if (NS_FAILED(Preferences::AddIntVarCache(&gFixedLimitKB, PREF_FIXED_LIMIT,
1021 kDefaultFixedLimitKB)) ||
1022 NS_FAILED(Preferences::AddUintVarCache(&gChunkSizeKB,
1023 PREF_CHUNK_SIZE,
1024 kDefaultChunkSizeKB))) {
1025 NS_WARNING("Unable to respond to temp storage pref changes!");
1026 }
1028 if (NS_FAILED(Preferences::AddBoolVarCache(&gTestingEnabled,
1029 PREF_TESTING_FEATURES, false))) {
1030 NS_WARNING("Unable to respond to testing pref changes!");
1031 }
1033 static_assert(Client::IDB == 0 && Client::ASMJS == 1 && Client::TYPE_MAX == 2,
1034 "Fix the registration!");
1036 NS_ASSERTION(mClients.Capacity() == Client::TYPE_MAX,
1037 "Should be using an auto array with correct capacity!");
1039 // Register IndexedDB
1040 mClients.AppendElement(new indexedDB::Client());
1041 mClients.AppendElement(asmjscache::CreateClient());
1043 return NS_OK;
1044 }
1046 void
1047 QuotaManager::InitQuotaForOrigin(PersistenceType aPersistenceType,
1048 const nsACString& aGroup,
1049 const nsACString& aOrigin,
1050 uint64_t aLimitBytes,
1051 uint64_t aUsageBytes,
1052 int64_t aAccessTime)
1053 {
1054 AssertIsOnIOThread();
1055 MOZ_ASSERT(aLimitBytes > 0 ||
1056 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
1057 MOZ_ASSERT(aUsageBytes <= aLimitBytes ||
1058 aPersistenceType == PERSISTENCE_TYPE_TEMPORARY);
1060 MutexAutoLock lock(mQuotaMutex);
1062 GroupInfoPair* pair;
1063 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1064 pair = new GroupInfoPair();
1065 mGroupInfoPairs.Put(aGroup, pair);
1066 // The hashtable is now responsible to delete the GroupInfoPair.
1067 }
1069 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1070 if (!groupInfo) {
1071 groupInfo = new GroupInfo(aPersistenceType, aGroup);
1072 pair->LockedSetGroupInfo(groupInfo);
1073 }
1075 nsRefPtr<OriginInfo> originInfo =
1076 new OriginInfo(groupInfo, aOrigin, aLimitBytes, aUsageBytes, aAccessTime);
1077 groupInfo->LockedAddOriginInfo(originInfo);
1078 }
1080 void
1081 QuotaManager::DecreaseUsageForOrigin(PersistenceType aPersistenceType,
1082 const nsACString& aGroup,
1083 const nsACString& aOrigin,
1084 int64_t aSize)
1085 {
1086 AssertIsOnIOThread();
1088 MutexAutoLock lock(mQuotaMutex);
1090 GroupInfoPair* pair;
1091 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1092 return;
1093 }
1095 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1096 if (!groupInfo) {
1097 return;
1098 }
1100 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1101 if (originInfo) {
1102 originInfo->LockedDecreaseUsage(aSize);
1103 }
1104 }
1106 void
1107 QuotaManager::UpdateOriginAccessTime(PersistenceType aPersistenceType,
1108 const nsACString& aGroup,
1109 const nsACString& aOrigin)
1110 {
1111 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1113 MutexAutoLock lock(mQuotaMutex);
1115 GroupInfoPair* pair;
1116 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1117 return;
1118 }
1120 nsRefPtr<GroupInfo> groupInfo =
1121 pair->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
1122 if (!groupInfo) {
1123 return;
1124 }
1126 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1127 if (originInfo) {
1128 int64_t timestamp = PR_Now();
1129 originInfo->LockedUpdateAccessTime(timestamp);
1131 if (!groupInfo->IsForTemporaryStorage()) {
1132 return;
1133 }
1135 MutexAutoUnlock autoUnlock(mQuotaMutex);
1137 SaveOriginAccessTime(aOrigin, timestamp);
1138 }
1139 }
1141 // static
1142 PLDHashOperator
1143 QuotaManager::RemoveQuotaCallback(const nsACString& aKey,
1144 nsAutoPtr<GroupInfoPair>& aValue,
1145 void* aUserArg)
1146 {
1147 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1148 NS_ASSERTION(aValue, "Null pointer!");
1150 nsRefPtr<GroupInfo> groupInfo =
1151 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
1152 if (groupInfo) {
1153 groupInfo->LockedRemoveOriginInfos();
1154 }
1156 return PL_DHASH_REMOVE;
1157 }
1159 void
1160 QuotaManager::RemoveQuota()
1161 {
1162 MutexAutoLock lock(mQuotaMutex);
1164 mGroupInfoPairs.Enumerate(RemoveQuotaCallback, nullptr);
1166 NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!");
1167 }
1169 // static
1170 PLDHashOperator
1171 QuotaManager::RemoveQuotaForPersistenceTypeCallback(
1172 const nsACString& aKey,
1173 nsAutoPtr<GroupInfoPair>& aValue,
1174 void* aUserArg)
1175 {
1176 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1177 NS_ASSERTION(aValue, "Null pointer!");
1178 NS_ASSERTION(aUserArg, "Null pointer!");
1180 PersistenceType& persistenceType = *static_cast<PersistenceType*>(aUserArg);
1182 if (persistenceType == PERSISTENCE_TYPE_TEMPORARY) {
1183 nsRefPtr<GroupInfo> groupInfo =
1184 aValue->LockedGetGroupInfo(persistenceType);
1185 if (groupInfo) {
1186 groupInfo->LockedRemoveOriginInfos();
1187 }
1188 }
1190 aValue->LockedClearGroupInfo(persistenceType);
1192 return aValue->LockedHasGroupInfos() ? PL_DHASH_NEXT : PL_DHASH_REMOVE;
1193 }
1195 void
1196 QuotaManager::RemoveQuotaForPersistenceType(PersistenceType aPersistenceType)
1197 {
1198 MutexAutoLock lock(mQuotaMutex);
1200 mGroupInfoPairs.Enumerate(RemoveQuotaForPersistenceTypeCallback,
1201 &aPersistenceType);
1203 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_PERSISTENT ||
1204 mTemporaryStorageUsage == 0, "Should be zero!");
1205 }
1207 // static
1208 PLDHashOperator
1209 QuotaManager::RemoveQuotaForPatternCallback(const nsACString& aKey,
1210 nsAutoPtr<GroupInfoPair>& aValue,
1211 void* aUserArg)
1212 {
1213 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
1214 NS_ASSERTION(aValue, "Null pointer!");
1215 NS_ASSERTION(aUserArg, "Null pointer!");
1217 RemoveQuotaInfo* info = static_cast<RemoveQuotaInfo*>(aUserArg);
1219 nsRefPtr<GroupInfo> groupInfo =
1220 aValue->LockedGetGroupInfo(info->persistenceType);
1221 if (groupInfo) {
1222 groupInfo->LockedRemoveOriginInfosForPattern(info->pattern);
1224 if (!groupInfo->LockedHasOriginInfos()) {
1225 aValue->LockedClearGroupInfo(info->persistenceType);
1227 if (!aValue->LockedHasGroupInfos()) {
1228 return PL_DHASH_REMOVE;
1229 }
1230 }
1231 }
1233 return PL_DHASH_NEXT;
1234 }
1236 void
1237 QuotaManager::RemoveQuotaForPattern(PersistenceType aPersistenceType,
1238 const nsACString& aPattern)
1239 {
1240 NS_ASSERTION(!aPattern.IsEmpty(), "Empty pattern!");
1242 RemoveQuotaInfo info(aPersistenceType, aPattern);
1244 MutexAutoLock lock(mQuotaMutex);
1246 mGroupInfoPairs.Enumerate(RemoveQuotaForPatternCallback, &info);
1247 }
1249 already_AddRefed<QuotaObject>
1250 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
1251 const nsACString& aGroup,
1252 const nsACString& aOrigin,
1253 nsIFile* aFile)
1254 {
1255 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
1257 nsString path;
1258 nsresult rv = aFile->GetPath(path);
1259 NS_ENSURE_SUCCESS(rv, nullptr);
1261 int64_t fileSize;
1263 bool exists;
1264 rv = aFile->Exists(&exists);
1265 NS_ENSURE_SUCCESS(rv, nullptr);
1267 if (exists) {
1268 rv = aFile->GetFileSize(&fileSize);
1269 NS_ENSURE_SUCCESS(rv, nullptr);
1270 }
1271 else {
1272 fileSize = 0;
1273 }
1275 nsRefPtr<QuotaObject> result;
1276 {
1277 MutexAutoLock lock(mQuotaMutex);
1279 GroupInfoPair* pair;
1280 if (!mGroupInfoPairs.Get(aGroup, &pair)) {
1281 return nullptr;
1282 }
1284 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
1286 if (!groupInfo) {
1287 return nullptr;
1288 }
1290 nsRefPtr<OriginInfo> originInfo = groupInfo->LockedGetOriginInfo(aOrigin);
1292 if (!originInfo) {
1293 return nullptr;
1294 }
1296 // We need this extra raw pointer because we can't assign to the smart
1297 // pointer directly since QuotaObject::AddRef would try to acquire the same
1298 // mutex.
1299 QuotaObject* quotaObject;
1300 if (!originInfo->mQuotaObjects.Get(path, "aObject)) {
1301 // Create a new QuotaObject.
1302 quotaObject = new QuotaObject(originInfo, path, fileSize);
1304 // Put it to the hashtable. The hashtable is not responsible to delete
1305 // the QuotaObject.
1306 originInfo->mQuotaObjects.Put(path, quotaObject);
1307 }
1309 // Addref the QuotaObject and move the ownership to the result. This must
1310 // happen before we unlock!
1311 result = quotaObject->LockedAddRef();
1312 }
1314 // The caller becomes the owner of the QuotaObject, that is, the caller is
1315 // is responsible to delete it when the last reference is removed.
1316 return result.forget();
1317 }
1319 already_AddRefed<QuotaObject>
1320 QuotaManager::GetQuotaObject(PersistenceType aPersistenceType,
1321 const nsACString& aGroup,
1322 const nsACString& aOrigin,
1323 const nsAString& aPath)
1324 {
1325 nsresult rv;
1326 nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1327 NS_ENSURE_SUCCESS(rv, nullptr);
1329 rv = file->InitWithPath(aPath);
1330 NS_ENSURE_SUCCESS(rv, nullptr);
1332 return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file);
1333 }
1335 bool
1336 QuotaManager::RegisterStorage(nsIOfflineStorage* aStorage)
1337 {
1338 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1339 NS_ASSERTION(aStorage, "Null pointer!");
1341 // Don't allow any new storages to be created after shutdown.
1342 if (IsShuttingDown()) {
1343 return false;
1344 }
1346 // Add this storage to its origin info if it exists, create it otherwise.
1347 const nsACString& origin = aStorage->Origin();
1348 ArrayCluster<nsIOfflineStorage*>* cluster;
1349 if (!mLiveStorages.Get(origin, &cluster)) {
1350 cluster = new ArrayCluster<nsIOfflineStorage*>();
1351 mLiveStorages.Put(origin, cluster);
1353 UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
1354 }
1355 (*cluster)[aStorage->GetClient()->GetType()].AppendElement(aStorage);
1357 return true;
1358 }
1360 void
1361 QuotaManager::UnregisterStorage(nsIOfflineStorage* aStorage)
1362 {
1363 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1364 NS_ASSERTION(aStorage, "Null pointer!");
1366 // Remove this storage from its origin array, maybe remove the array if it
1367 // is then empty.
1368 const nsACString& origin = aStorage->Origin();
1369 ArrayCluster<nsIOfflineStorage*>* cluster;
1370 if (mLiveStorages.Get(origin, &cluster) &&
1371 (*cluster)[aStorage->GetClient()->GetType()].RemoveElement(aStorage)) {
1372 if (cluster->IsEmpty()) {
1373 mLiveStorages.Remove(origin);
1375 UpdateOriginAccessTime(aStorage->Type(), aStorage->Group(), origin);
1376 }
1377 return;
1378 }
1379 NS_ERROR("Didn't know anything about this storage!");
1380 }
1382 void
1383 QuotaManager::OnStorageClosed(nsIOfflineStorage* aStorage)
1384 {
1385 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1386 NS_ASSERTION(aStorage, "Null pointer!");
1388 // Check through the list of SynchronizedOps to see if any are waiting for
1389 // this storage to close before proceeding.
1390 SynchronizedOp* op =
1391 FindSynchronizedOp(aStorage->Origin(),
1392 Nullable<PersistenceType>(aStorage->Type()),
1393 aStorage->Id());
1394 if (op) {
1395 Client::Type clientType = aStorage->GetClient()->GetType();
1397 // This storage is in the scope of this SynchronizedOp. Remove it
1398 // from the list if necessary.
1399 if (op->mStorages[clientType].RemoveElement(aStorage)) {
1400 // Now set up the helper if there are no more live storages.
1401 NS_ASSERTION(op->mListener,
1402 "How did we get rid of the listener before removing the "
1403 "last storage?");
1404 if (op->mStorages[clientType].IsEmpty()) {
1405 // At this point, all storages are closed, so no new transactions
1406 // can be started. There may, however, still be outstanding
1407 // transactions that have not completed. We need to wait for those
1408 // before we dispatch the helper.
1409 if (NS_FAILED(RunSynchronizedOp(aStorage, op))) {
1410 NS_WARNING("Failed to run synchronized op!");
1411 }
1412 }
1413 }
1414 }
1415 }
1417 void
1418 QuotaManager::AbortCloseStoragesForWindow(nsPIDOMWindow* aWindow)
1419 {
1420 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1421 NS_ASSERTION(aWindow, "Null pointer!");
1423 FileService* service = FileService::Get();
1425 StorageMatcher<ArrayCluster<nsIOfflineStorage*> > liveStorages;
1426 liveStorages.Find(mLiveStorages);
1428 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
1429 nsRefPtr<Client>& client = mClients[i];
1430 bool utilized = service && client->IsFileServiceUtilized();
1431 bool activated = client->IsTransactionServiceActivated();
1433 nsTArray<nsIOfflineStorage*>& array = liveStorages[i];
1434 for (uint32_t j = 0; j < array.Length(); j++) {
1435 nsIOfflineStorage*& storage = array[j];
1437 if (storage->IsOwned(aWindow)) {
1438 if (NS_FAILED(storage->Close())) {
1439 NS_WARNING("Failed to close storage for dying window!");
1440 }
1442 if (utilized) {
1443 service->AbortLockedFilesForStorage(storage);
1444 }
1446 if (activated) {
1447 client->AbortTransactionsForStorage(storage);
1448 }
1449 }
1450 }
1451 }
1452 }
1454 bool
1455 QuotaManager::HasOpenTransactions(nsPIDOMWindow* aWindow)
1456 {
1457 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1458 NS_ASSERTION(aWindow, "Null pointer!");
1460 FileService* service = FileService::Get();
1462 nsAutoPtr<StorageMatcher<ArrayCluster<nsIOfflineStorage*> > > liveStorages;
1464 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
1465 nsRefPtr<Client>& client = mClients[i];
1466 bool utilized = service && client->IsFileServiceUtilized();
1467 bool activated = client->IsTransactionServiceActivated();
1469 if (utilized || activated) {
1470 if (!liveStorages) {
1471 liveStorages = new StorageMatcher<ArrayCluster<nsIOfflineStorage*> >();
1472 liveStorages->Find(mLiveStorages);
1473 }
1475 nsTArray<nsIOfflineStorage*>& storages = liveStorages->ArrayAt(i);
1476 for (uint32_t j = 0; j < storages.Length(); j++) {
1477 nsIOfflineStorage*& storage = storages[j];
1479 if (storage->IsOwned(aWindow) &&
1480 ((utilized && service->HasLockedFilesForStorage(storage)) ||
1481 (activated && client->HasTransactionsForStorage(storage)))) {
1482 return true;
1483 }
1484 }
1485 }
1486 }
1488 return false;
1489 }
1491 nsresult
1492 QuotaManager::WaitForOpenAllowed(const OriginOrPatternString& aOriginOrPattern,
1493 Nullable<PersistenceType> aPersistenceType,
1494 const nsACString& aId, nsIRunnable* aRunnable)
1495 {
1496 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1497 NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
1498 "Empty pattern!");
1499 NS_ASSERTION(aRunnable, "Null pointer!");
1501 nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
1502 aPersistenceType, aId));
1504 // See if this runnable needs to wait.
1505 bool delayed = false;
1506 for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
1507 nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
1508 if (op->MustWaitFor(*existingOp)) {
1509 existingOp->DelayRunnable(aRunnable);
1510 delayed = true;
1511 break;
1512 }
1513 }
1515 // Otherwise, dispatch it immediately.
1516 if (!delayed) {
1517 nsresult rv = NS_DispatchToCurrentThread(aRunnable);
1518 NS_ENSURE_SUCCESS(rv, rv);
1519 }
1521 // Adding this to the synchronized ops list will block any additional
1522 // ops from proceeding until this one is done.
1523 mSynchronizedOps.AppendElement(op.forget());
1525 return NS_OK;
1526 }
1528 void
1529 QuotaManager::AddSynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
1530 Nullable<PersistenceType> aPersistenceType)
1531 {
1532 nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOriginOrPattern,
1533 aPersistenceType,
1534 EmptyCString()));
1536 #ifdef DEBUG
1537 for (uint32_t index = mSynchronizedOps.Length(); index > 0; index--) {
1538 nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
1539 NS_ASSERTION(!op->MustWaitFor(*existingOp), "What?");
1540 }
1541 #endif
1543 mSynchronizedOps.AppendElement(op.forget());
1544 }
1546 void
1547 QuotaManager::AllowNextSynchronizedOp(
1548 const OriginOrPatternString& aOriginOrPattern,
1549 Nullable<PersistenceType> aPersistenceType,
1550 const nsACString& aId)
1551 {
1552 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1553 NS_ASSERTION(!aOriginOrPattern.IsEmpty() || aOriginOrPattern.IsNull(),
1554 "Empty origin/pattern!");
1556 uint32_t count = mSynchronizedOps.Length();
1557 for (uint32_t index = 0; index < count; index++) {
1558 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
1559 if (op->mOriginOrPattern.IsOrigin() == aOriginOrPattern.IsOrigin() &&
1560 op->mOriginOrPattern == aOriginOrPattern &&
1561 op->mPersistenceType == aPersistenceType) {
1562 if (op->mId == aId) {
1563 NS_ASSERTION(op->mStorages.IsEmpty(), "How did this happen?");
1565 op->DispatchDelayedRunnables();
1567 mSynchronizedOps.RemoveElementAt(index);
1568 return;
1569 }
1571 // If one or the other is for an origin clear, we should have matched
1572 // solely on origin.
1573 NS_ASSERTION(!op->mId.IsEmpty() && !aId.IsEmpty(),
1574 "Why didn't we match earlier?");
1575 }
1576 }
1578 NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
1579 }
1581 nsresult
1582 QuotaManager::GetDirectoryForOrigin(PersistenceType aPersistenceType,
1583 const nsACString& aASCIIOrigin,
1584 nsIFile** aDirectory) const
1585 {
1586 nsresult rv;
1587 nsCOMPtr<nsIFile> directory =
1588 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1589 NS_ENSURE_SUCCESS(rv, rv);
1591 rv = directory->InitWithPath(GetStoragePath(aPersistenceType));
1592 NS_ENSURE_SUCCESS(rv, rv);
1594 nsAutoCString originSanitized(aASCIIOrigin);
1595 SanitizeOriginString(originSanitized);
1597 rv = directory->Append(NS_ConvertASCIItoUTF16(originSanitized));
1598 NS_ENSURE_SUCCESS(rv, rv);
1600 directory.forget(aDirectory);
1601 return NS_OK;
1602 }
1604 nsresult
1605 QuotaManager::InitializeOrigin(PersistenceType aPersistenceType,
1606 const nsACString& aGroup,
1607 const nsACString& aOrigin,
1608 bool aTrackQuota,
1609 int64_t aAccessTime,
1610 nsIFile* aDirectory)
1611 {
1612 AssertIsOnIOThread();
1614 nsresult rv;
1616 bool temporaryStorage = aPersistenceType == PERSISTENCE_TYPE_TEMPORARY;
1617 if (!temporaryStorage) {
1618 rv = MaybeUpgradeOriginDirectory(aDirectory);
1619 NS_ENSURE_SUCCESS(rv, rv);
1620 }
1622 // We need to initialize directories of all clients if they exists and also
1623 // get the total usage to initialize the quota.
1624 nsAutoPtr<UsageInfo> usageInfo;
1625 if (aTrackQuota) {
1626 usageInfo = new UsageInfo();
1627 }
1629 nsCOMPtr<nsISimpleEnumerator> entries;
1630 rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1631 NS_ENSURE_SUCCESS(rv, rv);
1633 bool hasMore;
1634 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
1635 nsCOMPtr<nsISupports> entry;
1636 rv = entries->GetNext(getter_AddRefs(entry));
1637 NS_ENSURE_SUCCESS(rv, rv);
1639 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
1640 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
1642 nsString leafName;
1643 rv = file->GetLeafName(leafName);
1644 NS_ENSURE_SUCCESS(rv, rv);
1646 if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
1647 leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
1648 continue;
1649 }
1651 bool isDirectory;
1652 rv = file->IsDirectory(&isDirectory);
1653 NS_ENSURE_SUCCESS(rv, rv);
1655 if (!isDirectory) {
1656 NS_WARNING("Unknown file found!");
1657 return NS_ERROR_UNEXPECTED;
1658 }
1660 Client::Type clientType;
1661 rv = Client::TypeFromText(leafName, clientType);
1662 if (NS_FAILED(rv)) {
1663 NS_WARNING("Unknown directory found!");
1664 return NS_ERROR_UNEXPECTED;
1665 }
1667 rv = mClients[clientType]->InitOrigin(aPersistenceType, aGroup, aOrigin,
1668 usageInfo);
1669 NS_ENSURE_SUCCESS(rv, rv);
1670 }
1672 if (aTrackQuota) {
1673 uint64_t quotaMaxBytes;
1674 uint64_t totalUsageBytes = usageInfo->TotalUsage();
1676 if (temporaryStorage) {
1677 // Temporary storage has no limit for origin usage (there's a group and
1678 // the global limit though).
1679 quotaMaxBytes = 0;
1680 }
1681 else {
1682 quotaMaxBytes = GetStorageQuotaMB() * 1024 * 1024;
1683 if (totalUsageBytes > quotaMaxBytes) {
1684 NS_WARNING("Origin is already using more storage than allowed!");
1685 return NS_ERROR_UNEXPECTED;
1686 }
1687 }
1689 InitQuotaForOrigin(aPersistenceType, aGroup, aOrigin, quotaMaxBytes,
1690 totalUsageBytes, aAccessTime);
1691 }
1693 return NS_OK;
1694 }
1696 nsresult
1697 QuotaManager::MaybeUpgradeIndexedDBDirectory()
1698 {
1699 AssertIsOnIOThread();
1701 if (mStorageAreaInitialized) {
1702 return NS_OK;
1703 }
1705 nsresult rv;
1707 nsCOMPtr<nsIFile> indexedDBDir =
1708 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1709 NS_ENSURE_SUCCESS(rv, rv);
1711 rv = indexedDBDir->InitWithPath(mIndexedDBPath);
1712 NS_ENSURE_SUCCESS(rv, rv);
1714 bool exists;
1715 rv = indexedDBDir->Exists(&exists);
1716 NS_ENSURE_SUCCESS(rv, rv);
1718 if (!exists) {
1719 // Nothing to upgrade.
1720 mStorageAreaInitialized = true;
1722 return NS_OK;
1723 }
1725 bool isDirectory;
1726 rv = indexedDBDir->IsDirectory(&isDirectory);
1727 NS_ENSURE_SUCCESS(rv, rv);
1729 if (!isDirectory) {
1730 NS_WARNING("indexedDB entry is not a directory!");
1731 return NS_OK;
1732 }
1734 nsCOMPtr<nsIFile> persistentStorageDir =
1735 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
1736 NS_ENSURE_SUCCESS(rv, rv);
1738 rv = persistentStorageDir->InitWithPath(mPersistentStoragePath);
1739 NS_ENSURE_SUCCESS(rv, rv);
1741 rv = persistentStorageDir->Exists(&exists);
1742 NS_ENSURE_SUCCESS(rv, rv);
1744 if (exists) {
1745 NS_WARNING("indexedDB directory shouldn't exist after the upgrade!");
1746 return NS_OK;
1747 }
1749 nsCOMPtr<nsIFile> storageDir;
1750 rv = persistentStorageDir->GetParent(getter_AddRefs(storageDir));
1751 NS_ENSURE_SUCCESS(rv, rv);
1753 nsString persistentStorageName;
1754 rv = persistentStorageDir->GetLeafName(persistentStorageName);
1755 NS_ENSURE_SUCCESS(rv, rv);
1757 // MoveTo() is atomic if the move happens on the same volume which should
1758 // be our case, so even if we crash in the middle of the operation nothing
1759 // breaks next time we try to initialize.
1760 // However there's a theoretical possibility that the indexedDB directory
1761 // is on different volume, but it should be rare enough that we don't have
1762 // to worry about it.
1763 rv = indexedDBDir->MoveTo(storageDir, persistentStorageName);
1764 NS_ENSURE_SUCCESS(rv, rv);
1766 mStorageAreaInitialized = true;
1768 return NS_OK;
1769 }
1771 nsresult
1772 QuotaManager::EnsureOriginIsInitialized(PersistenceType aPersistenceType,
1773 const nsACString& aGroup,
1774 const nsACString& aOrigin,
1775 bool aTrackQuota,
1776 nsIFile** aDirectory)
1777 {
1778 AssertIsOnIOThread();
1780 nsresult rv = MaybeUpgradeIndexedDBDirectory();
1781 NS_ENSURE_SUCCESS(rv, rv);
1783 // Get directory for this origin and persistence type.
1784 nsCOMPtr<nsIFile> directory;
1785 rv = GetDirectoryForOrigin(aPersistenceType, aOrigin,
1786 getter_AddRefs(directory));
1787 NS_ENSURE_SUCCESS(rv, rv);
1789 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1790 if (mInitializedOrigins.Contains(aOrigin)) {
1791 NS_ADDREF(*aDirectory = directory);
1792 return NS_OK;
1793 }
1795 bool created;
1796 rv = EnsureDirectory(directory, &created);
1797 NS_ENSURE_SUCCESS(rv, rv);
1799 if (created) {
1800 rv = CreateDirectoryUpgradeStamp(directory);
1801 NS_ENSURE_SUCCESS(rv, rv);
1802 }
1804 rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota, 0,
1805 directory);
1806 NS_ENSURE_SUCCESS(rv, rv);
1808 mInitializedOrigins.AppendElement(aOrigin);
1810 directory.forget(aDirectory);
1811 return NS_OK;
1812 }
1814 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
1815 NS_ASSERTION(aTrackQuota, "Huh?");
1817 if (!mTemporaryStorageInitialized) {
1818 nsCOMPtr<nsIFile> parentDirectory;
1819 rv = directory->GetParent(getter_AddRefs(parentDirectory));
1820 NS_ENSURE_SUCCESS(rv, rv);
1822 bool created;
1823 rv = EnsureDirectory(parentDirectory, &created);
1824 NS_ENSURE_SUCCESS(rv, rv);
1826 nsCOMPtr<nsISimpleEnumerator> entries;
1827 rv = parentDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1828 NS_ENSURE_SUCCESS(rv, rv);
1830 bool hasMore;
1831 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
1832 nsCOMPtr<nsISupports> entry;
1833 rv = entries->GetNext(getter_AddRefs(entry));
1834 NS_ENSURE_SUCCESS(rv, rv);
1836 nsCOMPtr<nsIFile> childDirectory = do_QueryInterface(entry);
1837 NS_ENSURE_TRUE(childDirectory, NS_NOINTERFACE);
1839 bool isDirectory;
1840 rv = childDirectory->IsDirectory(&isDirectory);
1841 NS_ENSURE_SUCCESS(rv, rv);
1842 NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED);
1844 int64_t timestamp;
1845 nsCString group;
1846 nsCString origin;
1847 rv = GetDirectoryMetadata(childDirectory, ×tamp, group, origin);
1848 NS_ENSURE_SUCCESS(rv, rv);
1850 rv = InitializeOrigin(aPersistenceType, group, origin, aTrackQuota,
1851 timestamp, childDirectory);
1852 if (NS_FAILED(rv)) {
1853 NS_WARNING("Failed to initialize origin!");
1855 // We have to cleanup partially initialized quota for temporary storage.
1856 RemoveQuotaForPersistenceType(aPersistenceType);
1858 return rv;
1859 }
1860 }
1862 if (gFixedLimitKB >= 0) {
1863 mTemporaryStorageLimit = gFixedLimitKB * 1024;
1864 }
1865 else {
1866 rv = GetTemporaryStorageLimit(parentDirectory, mTemporaryStorageUsage,
1867 &mTemporaryStorageLimit);
1868 NS_ENSURE_SUCCESS(rv, rv);
1869 }
1871 mTemporaryStorageInitialized = true;
1873 CheckTemporaryStorageLimits();
1874 }
1876 bool created;
1877 rv = EnsureDirectory(directory, &created);
1878 NS_ENSURE_SUCCESS(rv, rv);
1880 if (created) {
1881 int64_t timestamp = PR_Now();
1883 rv = CreateDirectoryMetadata(directory, timestamp, aGroup, aOrigin);
1884 NS_ENSURE_SUCCESS(rv, rv);
1886 rv = InitializeOrigin(aPersistenceType, aGroup, aOrigin, aTrackQuota,
1887 timestamp, directory);
1888 NS_ENSURE_SUCCESS(rv, rv);
1889 }
1891 directory.forget(aDirectory);
1892 return NS_OK;
1893 }
1895 void
1896 QuotaManager::OriginClearCompleted(
1897 PersistenceType aPersistenceType,
1898 const OriginOrPatternString& aOriginOrPattern)
1899 {
1900 AssertIsOnIOThread();
1902 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
1903 if (aOriginOrPattern.IsOrigin()) {
1904 mInitializedOrigins.RemoveElement(aOriginOrPattern);
1905 }
1906 else {
1907 for (uint32_t index = mInitializedOrigins.Length(); index > 0; index--) {
1908 if (PatternMatchesOrigin(aOriginOrPattern,
1909 mInitializedOrigins[index - 1])) {
1910 mInitializedOrigins.RemoveElementAt(index - 1);
1911 }
1912 }
1913 }
1914 }
1916 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
1917 mClients[index]->OnOriginClearCompleted(aPersistenceType, aOriginOrPattern);
1918 }
1919 }
1921 void
1922 QuotaManager::ResetOrClearCompleted()
1923 {
1924 AssertIsOnIOThread();
1926 mInitializedOrigins.Clear();
1927 mTemporaryStorageInitialized = false;
1929 ReleaseIOThreadObjects();
1930 }
1932 already_AddRefed<mozilla::dom::quota::Client>
1933 QuotaManager::GetClient(Client::Type aClientType)
1934 {
1935 nsRefPtr<Client> client = mClients.SafeElementAt(aClientType);
1936 return client.forget();
1937 }
1939 uint64_t
1940 QuotaManager::GetGroupLimit() const
1941 {
1942 MOZ_ASSERT(mTemporaryStorageInitialized);
1944 // To avoid one group evicting all the rest, limit the amount any one group
1945 // can use to 20%. To prevent individual sites from using exorbitant amounts
1946 // of storage where there is a lot of free space, cap the group limit to 2GB.
1947 uint64_t x = std::min<uint64_t>(mTemporaryStorageLimit * .20, 2 GB);
1949 // In low-storage situations, make an exception (while not exceeding the total
1950 // storage limit).
1951 return std::min<uint64_t>(mTemporaryStorageLimit,
1952 std::max<uint64_t>(x, 10 MB));
1953 }
1955 // static
1956 uint32_t
1957 QuotaManager::GetStorageQuotaMB()
1958 {
1959 return uint32_t(std::max(gStorageQuotaMB, 0));
1960 }
1962 // static
1963 void
1964 QuotaManager::GetStorageId(PersistenceType aPersistenceType,
1965 const nsACString& aOrigin,
1966 Client::Type aClientType,
1967 const nsAString& aName,
1968 nsACString& aDatabaseId)
1969 {
1970 nsAutoCString str;
1971 str.AppendInt(aPersistenceType);
1972 str.Append('*');
1973 str.Append(aOrigin);
1974 str.Append('*');
1975 str.AppendInt(aClientType);
1976 str.Append('*');
1977 str.Append(NS_ConvertUTF16toUTF8(aName));
1979 aDatabaseId = str;
1980 }
1982 // static
1983 nsresult
1984 QuotaManager::GetInfoFromURI(nsIURI* aURI,
1985 uint32_t aAppId,
1986 bool aInMozBrowser,
1987 nsACString* aGroup,
1988 nsACString* aASCIIOrigin,
1989 StoragePrivilege* aPrivilege,
1990 PersistenceType* aDefaultPersistenceType)
1991 {
1992 NS_ASSERTION(aURI, "Null uri!");
1994 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
1995 NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
1997 nsCOMPtr<nsIPrincipal> principal;
1998 nsresult rv = secMan->GetAppCodebasePrincipal(aURI, aAppId, aInMozBrowser,
1999 getter_AddRefs(principal));
2000 NS_ENSURE_SUCCESS(rv, rv);
2002 rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin, aPrivilege,
2003 aDefaultPersistenceType);
2004 NS_ENSURE_SUCCESS(rv, rv);
2006 return NS_OK;
2007 }
2009 // static
2010 nsresult
2011 QuotaManager::GetInfoFromPrincipal(nsIPrincipal* aPrincipal,
2012 nsACString* aGroup,
2013 nsACString* aASCIIOrigin,
2014 StoragePrivilege* aPrivilege,
2015 PersistenceType* aDefaultPersistenceType)
2016 {
2017 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2018 NS_ASSERTION(aPrincipal, "Don't hand me a null principal!");
2020 if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
2021 GetInfoForChrome(aGroup, aASCIIOrigin, aPrivilege, aDefaultPersistenceType);
2022 return NS_OK;
2023 }
2025 bool isNullPrincipal;
2026 nsresult rv = aPrincipal->GetIsNullPrincipal(&isNullPrincipal);
2027 NS_ENSURE_SUCCESS(rv, rv);
2029 if (isNullPrincipal) {
2030 NS_WARNING("IndexedDB not supported from this principal!");
2031 return NS_ERROR_FAILURE;
2032 }
2034 nsCString origin;
2035 rv = aPrincipal->GetOrigin(getter_Copies(origin));
2036 NS_ENSURE_SUCCESS(rv, rv);
2038 if (origin.EqualsLiteral("chrome")) {
2039 NS_WARNING("Non-chrome principal can't use chrome origin!");
2040 return NS_ERROR_FAILURE;
2041 }
2043 nsCString jarPrefix;
2044 if (aGroup || aASCIIOrigin) {
2045 rv = aPrincipal->GetJarPrefix(jarPrefix);
2046 NS_ENSURE_SUCCESS(rv, rv);
2047 }
2049 if (aGroup) {
2050 nsCString baseDomain;
2051 rv = aPrincipal->GetBaseDomain(baseDomain);
2052 if (NS_FAILED(rv)) {
2053 // A hack for JetPack.
2055 nsCOMPtr<nsIURI> uri;
2056 rv = aPrincipal->GetURI(getter_AddRefs(uri));
2057 NS_ENSURE_SUCCESS(rv, rv);
2059 bool isIndexedDBURI = false;
2060 rv = uri->SchemeIs("indexedDB", &isIndexedDBURI);
2061 NS_ENSURE_SUCCESS(rv, rv);
2063 if (isIndexedDBURI) {
2064 rv = NS_OK;
2065 }
2066 }
2067 NS_ENSURE_SUCCESS(rv, rv);
2069 if (baseDomain.IsEmpty()) {
2070 aGroup->Assign(jarPrefix + origin);
2071 }
2072 else {
2073 aGroup->Assign(jarPrefix + baseDomain);
2074 }
2075 }
2077 if (aASCIIOrigin) {
2078 aASCIIOrigin->Assign(jarPrefix + origin);
2079 }
2081 if (aPrivilege) {
2082 *aPrivilege = Content;
2083 }
2085 if (aDefaultPersistenceType) {
2086 *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
2087 }
2089 return NS_OK;
2090 }
2092 // static
2093 nsresult
2094 QuotaManager::GetInfoFromWindow(nsPIDOMWindow* aWindow,
2095 nsACString* aGroup,
2096 nsACString* aASCIIOrigin,
2097 StoragePrivilege* aPrivilege,
2098 PersistenceType* aDefaultPersistenceType)
2099 {
2100 NS_ASSERTION(NS_IsMainThread(),
2101 "We're about to touch a window off the main thread!");
2102 NS_ASSERTION(aWindow, "Don't hand me a null window!");
2104 nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow);
2105 NS_ENSURE_TRUE(sop, NS_ERROR_FAILURE);
2107 nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal();
2108 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
2110 nsresult rv = GetInfoFromPrincipal(principal, aGroup, aASCIIOrigin,
2111 aPrivilege, aDefaultPersistenceType);
2112 NS_ENSURE_SUCCESS(rv, rv);
2114 return NS_OK;
2115 }
2117 // static
2118 void
2119 QuotaManager::GetInfoForChrome(nsACString* aGroup,
2120 nsACString* aASCIIOrigin,
2121 StoragePrivilege* aPrivilege,
2122 PersistenceType* aDefaultPersistenceType)
2123 {
2124 NS_ASSERTION(nsContentUtils::IsCallerChrome(), "Only for chrome!");
2126 static const char kChromeOrigin[] = "chrome";
2128 if (aGroup) {
2129 aGroup->AssignLiteral(kChromeOrigin);
2130 }
2131 if (aASCIIOrigin) {
2132 aASCIIOrigin->AssignLiteral(kChromeOrigin);
2133 }
2134 if (aPrivilege) {
2135 *aPrivilege = Chrome;
2136 }
2137 if (aDefaultPersistenceType) {
2138 *aDefaultPersistenceType = PERSISTENCE_TYPE_PERSISTENT;
2139 }
2140 }
2142 NS_IMPL_ISUPPORTS(QuotaManager, nsIQuotaManager, nsIObserver)
2144 NS_IMETHODIMP
2145 QuotaManager::GetUsageForURI(nsIURI* aURI,
2146 nsIUsageCallback* aCallback,
2147 uint32_t aAppId,
2148 bool aInMozBrowserOnly,
2149 uint8_t aOptionalArgCount,
2150 nsIQuotaRequest** _retval)
2151 {
2152 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2154 NS_ENSURE_ARG_POINTER(aURI);
2155 NS_ENSURE_ARG_POINTER(aCallback);
2157 // This only works from the main process.
2158 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2160 if (!aOptionalArgCount) {
2161 aAppId = nsIScriptSecurityManager::NO_APP_ID;
2162 }
2164 // Figure out which origin we're dealing with.
2165 nsCString group;
2166 nsCString origin;
2167 nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, &group, &origin,
2168 nullptr, nullptr);
2169 NS_ENSURE_SUCCESS(rv, rv);
2171 OriginOrPatternString oops = OriginOrPatternString::FromOrigin(origin);
2173 nsRefPtr<AsyncUsageRunnable> runnable =
2174 new AsyncUsageRunnable(aAppId, aInMozBrowserOnly, group, oops, aURI,
2175 aCallback);
2177 // Put the computation runnable in the queue.
2178 rv = WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2179 runnable);
2180 NS_ENSURE_SUCCESS(rv, rv);
2182 runnable->AdvanceState();
2184 runnable.forget(_retval);
2185 return NS_OK;
2186 }
2188 NS_IMETHODIMP
2189 QuotaManager::Clear()
2190 {
2191 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2193 if (!gTestingEnabled) {
2194 NS_WARNING("Testing features are not enabled!");
2195 return NS_OK;
2196 }
2198 OriginOrPatternString oops = OriginOrPatternString::FromNull();
2200 nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(true);
2202 // Put the clear runnable in the queue.
2203 nsresult rv =
2204 WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2205 runnable);
2206 NS_ENSURE_SUCCESS(rv, rv);
2208 runnable->AdvanceState();
2210 // Give the runnable some help by invalidating any storages in the way.
2211 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2212 matches.Find(mLiveStorages);
2214 for (uint32_t index = 0; index < matches.Length(); index++) {
2215 // We need to grab references to any live storages here to prevent them
2216 // from dying while we invalidate them.
2217 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2218 storage->Invalidate();
2219 }
2221 // After everything has been invalidated the helper should be dispatched to
2222 // the end of the event queue.
2223 return NS_OK;
2224 }
2226 NS_IMETHODIMP
2227 QuotaManager::ClearStoragesForURI(nsIURI* aURI,
2228 uint32_t aAppId,
2229 bool aInMozBrowserOnly,
2230 const nsACString& aPersistenceType,
2231 uint8_t aOptionalArgCount)
2232 {
2233 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2235 NS_ENSURE_ARG_POINTER(aURI);
2237 // This only works from the main process.
2238 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2240 if (!aOptionalArgCount) {
2241 aAppId = nsIScriptSecurityManager::NO_APP_ID;
2242 }
2244 // Figure out which origin we're dealing with.
2245 nsCString origin;
2246 nsresult rv = GetInfoFromURI(aURI, aAppId, aInMozBrowserOnly, nullptr, &origin,
2247 nullptr, nullptr);
2248 NS_ENSURE_SUCCESS(rv, rv);
2250 nsAutoCString pattern;
2251 GetOriginPatternString(aAppId, aInMozBrowserOnly, origin, pattern);
2253 Nullable<PersistenceType> persistenceType;
2254 rv = NullablePersistenceTypeFromText(aPersistenceType, &persistenceType);
2255 NS_ENSURE_SUCCESS(rv, rv);
2257 // If there is a pending or running clear operation for this origin, return
2258 // immediately.
2259 if (IsClearOriginPending(pattern, persistenceType)) {
2260 return NS_OK;
2261 }
2263 OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
2265 // Queue up the origin clear runnable.
2266 nsRefPtr<OriginClearRunnable> runnable =
2267 new OriginClearRunnable(oops, persistenceType);
2269 rv = WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
2270 NS_ENSURE_SUCCESS(rv, rv);
2272 runnable->AdvanceState();
2274 // Give the runnable some help by invalidating any storages in the way.
2275 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2276 matches.Find(mLiveStorages, pattern);
2278 for (uint32_t index = 0; index < matches.Length(); index++) {
2279 if (persistenceType.IsNull() ||
2280 matches[index]->Type() == persistenceType.Value()) {
2281 // We need to grab references to any live storages here to prevent them
2282 // from dying while we invalidate them.
2283 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2284 storage->Invalidate();
2285 }
2286 }
2288 // After everything has been invalidated the helper should be dispatched to
2289 // the end of the event queue.
2290 return NS_OK;
2291 }
2293 NS_IMETHODIMP
2294 QuotaManager::Reset()
2295 {
2296 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2298 if (!gTestingEnabled) {
2299 NS_WARNING("Testing features are not enabled!");
2300 return NS_OK;
2301 }
2303 OriginOrPatternString oops = OriginOrPatternString::FromNull();
2305 nsRefPtr<ResetOrClearRunnable> runnable = new ResetOrClearRunnable(false);
2307 // Put the reset runnable in the queue.
2308 nsresult rv =
2309 WaitForOpenAllowed(oops, Nullable<PersistenceType>(), EmptyCString(),
2310 runnable);
2311 NS_ENSURE_SUCCESS(rv, rv);
2313 runnable->AdvanceState();
2315 // Give the runnable some help by invalidating any storages in the way.
2316 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2317 matches.Find(mLiveStorages);
2319 for (uint32_t index = 0; index < matches.Length(); index++) {
2320 // We need to grab references to any live storages here to prevent them
2321 // from dying while we invalidate them.
2322 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2323 storage->Invalidate();
2324 }
2326 // After everything has been invalidated the helper should be dispatched to
2327 // the end of the event queue.
2328 return NS_OK;
2329 }
2331 NS_IMETHODIMP
2332 QuotaManager::Observe(nsISupports* aSubject,
2333 const char* aTopic,
2334 const char16_t* aData)
2335 {
2336 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2338 if (!strcmp(aTopic, PROFILE_BEFORE_CHANGE_OBSERVER_ID)) {
2339 // Setting this flag prevents the service from being recreated and prevents
2340 // further storagess from being created.
2341 if (gShutdown.exchange(true)) {
2342 NS_ERROR("Shutdown more than once?!");
2343 }
2345 if (IsMainProcess()) {
2346 FileService* service = FileService::Get();
2347 if (service) {
2348 // This should only wait for storages registered in this manager
2349 // to complete. Other storages may still have running locked files.
2350 // If the necko service (thread pool) gets the shutdown notification
2351 // first then the sync loop won't be processed at all, otherwise it will
2352 // lock the main thread until all storages registered in this manager
2353 // are finished.
2355 nsTArray<uint32_t> indexes;
2356 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
2357 if (mClients[index]->IsFileServiceUtilized()) {
2358 indexes.AppendElement(index);
2359 }
2360 }
2362 StorageMatcher<nsTArray<nsCOMPtr<nsIFileStorage> > > liveStorages;
2363 liveStorages.Find(mLiveStorages, &indexes);
2365 if (!liveStorages.IsEmpty()) {
2366 nsRefPtr<WaitForLockedFilesToFinishRunnable> runnable =
2367 new WaitForLockedFilesToFinishRunnable();
2369 service->WaitForStoragesToComplete(liveStorages, runnable);
2371 nsIThread* thread = NS_GetCurrentThread();
2372 while (runnable->IsBusy()) {
2373 if (!NS_ProcessNextEvent(thread)) {
2374 NS_ERROR("Failed to process next event!");
2375 break;
2376 }
2377 }
2378 }
2379 }
2381 // Kick off the shutdown timer.
2382 if (NS_FAILED(mShutdownTimer->Init(this, DEFAULT_SHUTDOWN_TIMER_MS,
2383 nsITimer::TYPE_ONE_SHOT))) {
2384 NS_WARNING("Failed to initialize shutdown timer!");
2385 }
2387 // Each client will spin the event loop while we wait on all the threads
2388 // to close. Our timer may fire during that loop.
2389 for (uint32_t index = 0; index < Client::TYPE_MAX; index++) {
2390 mClients[index]->ShutdownTransactionService();
2391 }
2393 // Cancel the timer regardless of whether it actually fired.
2394 if (NS_FAILED(mShutdownTimer->Cancel())) {
2395 NS_WARNING("Failed to cancel shutdown timer!");
2396 }
2398 // Give clients a chance to cleanup IO thread only objects.
2399 nsCOMPtr<nsIRunnable> runnable =
2400 NS_NewRunnableMethod(this, &QuotaManager::ReleaseIOThreadObjects);
2401 if (!runnable) {
2402 NS_WARNING("Failed to create runnable!");
2403 }
2405 if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
2406 NS_WARNING("Failed to dispatch runnable!");
2407 }
2409 // Make sure to join with our IO thread.
2410 if (NS_FAILED(mIOThread->Shutdown())) {
2411 NS_WARNING("Failed to shutdown IO thread!");
2412 }
2413 }
2415 return NS_OK;
2416 }
2418 if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
2419 NS_ASSERTION(IsMainProcess(), "Should only happen in the main process!");
2421 NS_WARNING("Some storage operations are taking longer than expected "
2422 "during shutdown and will be aborted!");
2424 // Grab all live storages, for all origins.
2425 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 50> > liveStorages;
2426 liveStorages.Find(mLiveStorages);
2428 // Invalidate them all.
2429 if (!liveStorages.IsEmpty()) {
2430 uint32_t count = liveStorages.Length();
2431 for (uint32_t index = 0; index < count; index++) {
2432 liveStorages[index]->Invalidate();
2433 }
2434 }
2436 return NS_OK;
2437 }
2439 if (!strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)) {
2440 nsCOMPtr<mozIApplicationClearPrivateDataParams> params =
2441 do_QueryInterface(aSubject);
2442 NS_ENSURE_TRUE(params, NS_ERROR_UNEXPECTED);
2444 uint32_t appId;
2445 nsresult rv = params->GetAppId(&appId);
2446 NS_ENSURE_SUCCESS(rv, rv);
2448 bool browserOnly;
2449 rv = params->GetBrowserOnly(&browserOnly);
2450 NS_ENSURE_SUCCESS(rv, rv);
2452 rv = ClearStoragesForApp(appId, browserOnly);
2453 NS_ENSURE_SUCCESS(rv, rv);
2455 return NS_OK;
2456 }
2458 NS_NOTREACHED("Unknown topic!");
2459 return NS_ERROR_UNEXPECTED;
2460 }
2462 void
2463 QuotaManager::SetCurrentWindowInternal(nsPIDOMWindow* aWindow)
2464 {
2465 NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX,
2466 "Should have a valid TLS storage index!");
2468 if (aWindow) {
2469 NS_ASSERTION(!PR_GetThreadPrivate(mCurrentWindowIndex),
2470 "Somebody forgot to clear the current window!");
2471 PR_SetThreadPrivate(mCurrentWindowIndex, aWindow);
2472 }
2473 else {
2474 // We cannot assert PR_GetThreadPrivate(mCurrentWindowIndex) here because
2475 // there are some cases where we did not already have a window.
2476 PR_SetThreadPrivate(mCurrentWindowIndex, nullptr);
2477 }
2478 }
2480 void
2481 QuotaManager::CancelPromptsForWindowInternal(nsPIDOMWindow* aWindow)
2482 {
2483 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2485 nsRefPtr<CheckQuotaHelper> helper;
2487 MutexAutoLock autoLock(mQuotaMutex);
2489 if (mCheckQuotaHelpers.Get(aWindow, getter_AddRefs(helper))) {
2490 helper->Cancel();
2491 }
2492 }
2494 bool
2495 QuotaManager::LockedQuotaIsLifted()
2496 {
2497 mQuotaMutex.AssertCurrentThreadOwns();
2499 NS_ASSERTION(mCurrentWindowIndex != BAD_TLS_INDEX,
2500 "Should have a valid TLS storage index!");
2502 nsPIDOMWindow* window =
2503 static_cast<nsPIDOMWindow*>(PR_GetThreadPrivate(mCurrentWindowIndex));
2505 // Quota is not enforced in chrome contexts (e.g. for components and JSMs)
2506 // so we must have a window here.
2507 NS_ASSERTION(window, "Why don't we have a Window here?");
2509 bool createdHelper = false;
2511 nsRefPtr<CheckQuotaHelper> helper;
2512 if (!mCheckQuotaHelpers.Get(window, getter_AddRefs(helper))) {
2513 helper = new CheckQuotaHelper(window, mQuotaMutex);
2514 createdHelper = true;
2516 mCheckQuotaHelpers.Put(window, helper);
2518 // Unlock while calling out to XPCOM (code behind the dispatch method needs
2519 // to acquire its own lock which can potentially lead to a deadlock and it
2520 // also calls an observer that can do various stuff like IO, so it's better
2521 // to not hold our mutex while that happens).
2522 {
2523 MutexAutoUnlock autoUnlock(mQuotaMutex);
2525 nsresult rv = NS_DispatchToMainThread(helper);
2526 NS_ENSURE_SUCCESS(rv, false);
2527 }
2529 // Relocked. If any other threads hit the quota limit on the same Window,
2530 // they are using the helper we created here and are now blocking in
2531 // PromptAndReturnQuotaDisabled.
2532 }
2534 bool result = helper->PromptAndReturnQuotaIsDisabled();
2536 // If this thread created the helper and added it to the hash, this thread
2537 // must remove it.
2538 if (createdHelper) {
2539 mCheckQuotaHelpers.Remove(window);
2540 }
2542 return result;
2543 }
2545 uint64_t
2546 QuotaManager::LockedCollectOriginsForEviction(
2547 uint64_t aMinSizeToBeFreed,
2548 nsTArray<OriginInfo*>& aOriginInfos)
2549 {
2550 mQuotaMutex.AssertCurrentThreadOwns();
2552 nsRefPtr<CollectOriginsHelper> helper =
2553 new CollectOriginsHelper(mQuotaMutex, aMinSizeToBeFreed);
2555 // Unlock while calling out to XPCOM (see the detailed comment in
2556 // LockedQuotaIsLifted)
2557 {
2558 MutexAutoUnlock autoUnlock(mQuotaMutex);
2560 if (NS_FAILED(NS_DispatchToMainThread(helper))) {
2561 NS_WARNING("Failed to dispatch to the main thread!");
2562 }
2563 }
2565 return helper->BlockAndReturnOriginsForEviction(aOriginInfos);
2566 }
2568 void
2569 QuotaManager::LockedRemoveQuotaForOrigin(PersistenceType aPersistenceType,
2570 const nsACString& aGroup,
2571 const nsACString& aOrigin)
2572 {
2573 mQuotaMutex.AssertCurrentThreadOwns();
2575 GroupInfoPair* pair;
2576 mGroupInfoPairs.Get(aGroup, &pair);
2578 if (!pair) {
2579 return;
2580 }
2582 nsRefPtr<GroupInfo> groupInfo = pair->LockedGetGroupInfo(aPersistenceType);
2583 if (groupInfo) {
2584 groupInfo->LockedRemoveOriginInfo(aOrigin);
2586 if (!groupInfo->LockedHasOriginInfos()) {
2587 pair->LockedClearGroupInfo(aPersistenceType);
2589 if (!pair->LockedHasGroupInfos()) {
2590 mGroupInfoPairs.Remove(aGroup);
2591 }
2592 }
2593 }
2594 }
2596 nsresult
2597 QuotaManager::AcquireExclusiveAccess(const nsACString& aPattern,
2598 Nullable<PersistenceType> aPersistenceType,
2599 nsIOfflineStorage* aStorage,
2600 AcquireListener* aListener,
2601 WaitingOnStoragesCallback aCallback,
2602 void* aClosure)
2603 {
2604 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2605 NS_ASSERTION(aListener, "Need a listener!");
2607 // Find the right SynchronizedOp.
2608 SynchronizedOp* op =
2609 FindSynchronizedOp(aPattern, aPersistenceType,
2610 aStorage ? aStorage->Id() : EmptyCString());
2612 NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
2613 NS_ASSERTION(!op->mListener, "SynchronizedOp already has a listener?!?");
2615 nsTArray<nsCOMPtr<nsIOfflineStorage> > liveStorages;
2617 if (aStorage) {
2618 // We need to wait for the storages to go away.
2619 // Hold on to all storage objects that represent the same storage file
2620 // (except the one that is requesting this version change).
2622 Client::Type clientType = aStorage->GetClient()->GetType();
2624 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2625 matches.Find(mLiveStorages, aPattern, clientType);
2627 if (!matches.IsEmpty()) {
2628 // Grab all storages that are not yet closed but whose storage id match
2629 // the one we're looking for.
2630 for (uint32_t index = 0; index < matches.Length(); index++) {
2631 nsIOfflineStorage*& storage = matches[index];
2632 if (!storage->IsClosed() &&
2633 storage != aStorage &&
2634 storage->Id() == aStorage->Id()) {
2635 liveStorages.AppendElement(storage);
2636 }
2637 }
2638 }
2640 if (!liveStorages.IsEmpty()) {
2641 NS_ASSERTION(op->mStorages[clientType].IsEmpty(),
2642 "How do we already have storages here?");
2643 op->mStorages[clientType].AppendElements(liveStorages);
2644 }
2645 }
2646 else {
2647 StorageMatcher<ArrayCluster<nsIOfflineStorage*> > matches;
2648 if (aPattern.IsVoid()) {
2649 matches.Find(mLiveStorages);
2650 }
2651 else {
2652 matches.Find(mLiveStorages, aPattern);
2653 }
2655 if (!matches.IsEmpty()) {
2656 // We want *all* storages, even those that are closed, when we're going to
2657 // clear the origin.
2658 matches.AppendElementsTo(liveStorages);
2660 NS_ASSERTION(op->mStorages.IsEmpty(),
2661 "How do we already have storages here?");
2662 matches.SwapElements(op->mStorages);
2663 }
2664 }
2666 op->mListener = aListener;
2668 if (!liveStorages.IsEmpty()) {
2669 // Give our callback the storages so it can decide what to do with them.
2670 aCallback(liveStorages, aClosure);
2672 NS_ASSERTION(liveStorages.IsEmpty(),
2673 "Should have done something with the array!");
2675 if (aStorage) {
2676 // Wait for those storages to close.
2677 return NS_OK;
2678 }
2679 }
2681 // If we're trying to open a storage and nothing blocks it, or if we're
2682 // clearing an origin, then go ahead and schedule the op.
2683 nsresult rv = RunSynchronizedOp(aStorage, op);
2684 NS_ENSURE_SUCCESS(rv, rv);
2686 return NS_OK;
2687 }
2689 nsresult
2690 QuotaManager::RunSynchronizedOp(nsIOfflineStorage* aStorage,
2691 SynchronizedOp* aOp)
2692 {
2693 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2694 NS_ASSERTION(aOp, "Null pointer!");
2695 NS_ASSERTION(aOp->mListener, "No listener on this op!");
2696 NS_ASSERTION(!aStorage ||
2697 aOp->mStorages[aStorage->GetClient()->GetType()].IsEmpty(),
2698 "This op isn't ready to run!");
2700 ArrayCluster<nsIOfflineStorage*> storages;
2702 uint32_t startIndex;
2703 uint32_t endIndex;
2705 if (aStorage) {
2706 Client::Type clientType = aStorage->GetClient()->GetType();
2708 storages[clientType].AppendElement(aStorage);
2710 startIndex = clientType;
2711 endIndex = clientType + 1;
2712 }
2713 else {
2714 aOp->mStorages.SwapElements(storages);
2716 startIndex = 0;
2717 endIndex = Client::TYPE_MAX;
2718 }
2720 nsRefPtr<WaitForTransactionsToFinishRunnable> runnable =
2721 new WaitForTransactionsToFinishRunnable(aOp);
2723 // Ask the file service to call us back when it's done with this storage.
2724 FileService* service = FileService::Get();
2726 if (service) {
2727 // Have to copy here in case a transaction service needs a list too.
2728 nsTArray<nsCOMPtr<nsIFileStorage> > array;
2730 for (uint32_t index = startIndex; index < endIndex; index++) {
2731 if (!storages[index].IsEmpty() &&
2732 mClients[index]->IsFileServiceUtilized()) {
2733 array.AppendElements(storages[index]);
2734 }
2735 }
2737 if (!array.IsEmpty()) {
2738 runnable->AddRun();
2740 service->WaitForStoragesToComplete(array, runnable);
2741 }
2742 }
2744 // Ask each transaction service to call us back when they're done with this
2745 // storage.
2746 for (uint32_t index = startIndex; index < endIndex; index++) {
2747 nsRefPtr<Client>& client = mClients[index];
2748 if (!storages[index].IsEmpty() && client->IsTransactionServiceActivated()) {
2749 runnable->AddRun();
2751 client->WaitForStoragesToComplete(storages[index], runnable);
2752 }
2753 }
2755 nsresult rv = runnable->Run();
2756 NS_ENSURE_SUCCESS(rv, rv);
2758 return NS_OK;
2759 }
2761 SynchronizedOp*
2762 QuotaManager::FindSynchronizedOp(const nsACString& aPattern,
2763 Nullable<PersistenceType> aPersistenceType,
2764 const nsACString& aId)
2765 {
2766 for (uint32_t index = 0; index < mSynchronizedOps.Length(); index++) {
2767 const nsAutoPtr<SynchronizedOp>& currentOp = mSynchronizedOps[index];
2768 if (PatternMatchesOrigin(aPattern, currentOp->mOriginOrPattern) &&
2769 (currentOp->mPersistenceType.IsNull() ||
2770 currentOp->mPersistenceType == aPersistenceType) &&
2771 (currentOp->mId.IsEmpty() || currentOp->mId == aId)) {
2772 return currentOp;
2773 }
2774 }
2776 return nullptr;
2777 }
2779 nsresult
2780 QuotaManager::ClearStoragesForApp(uint32_t aAppId, bool aBrowserOnly)
2781 {
2782 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2783 NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
2784 "Bad appId!");
2786 // This only works from the main process.
2787 NS_ENSURE_TRUE(IsMainProcess(), NS_ERROR_NOT_AVAILABLE);
2789 nsAutoCString pattern;
2790 GetOriginPatternStringMaybeIgnoreBrowser(aAppId, aBrowserOnly, pattern);
2792 // Clear both temporary and persistent storages.
2793 Nullable<PersistenceType> persistenceType;
2795 // If there is a pending or running clear operation for this app, return
2796 // immediately.
2797 if (IsClearOriginPending(pattern, persistenceType)) {
2798 return NS_OK;
2799 }
2801 OriginOrPatternString oops = OriginOrPatternString::FromPattern(pattern);
2803 // Queue up the origin clear runnable.
2804 nsRefPtr<OriginClearRunnable> runnable =
2805 new OriginClearRunnable(oops, persistenceType);
2807 nsresult rv =
2808 WaitForOpenAllowed(oops, persistenceType, EmptyCString(), runnable);
2809 NS_ENSURE_SUCCESS(rv, rv);
2811 runnable->AdvanceState();
2813 // Give the runnable some help by invalidating any storages in the way.
2814 StorageMatcher<nsAutoTArray<nsIOfflineStorage*, 20> > matches;
2815 matches.Find(mLiveStorages, pattern);
2817 for (uint32_t index = 0; index < matches.Length(); index++) {
2818 // We need to grab references here to prevent the storage from dying while
2819 // we invalidate it.
2820 nsCOMPtr<nsIOfflineStorage> storage = matches[index];
2821 storage->Invalidate();
2822 }
2824 return NS_OK;
2825 }
2827 // static
2828 PLDHashOperator
2829 QuotaManager::GetOriginsExceedingGroupLimit(const nsACString& aKey,
2830 GroupInfoPair* aValue,
2831 void* aUserArg)
2832 {
2833 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
2834 NS_ASSERTION(aValue, "Null pointer!");
2836 nsRefPtr<GroupInfo> groupInfo =
2837 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2838 if (groupInfo) {
2839 QuotaManager* quotaManager = QuotaManager::Get();
2840 NS_ASSERTION(quotaManager, "Shouldn't be null!");
2842 if (groupInfo->mUsage > quotaManager->GetGroupLimit()) {
2843 nsTArray<OriginInfo*>* doomedOriginInfos =
2844 static_cast<nsTArray<OriginInfo*>*>(aUserArg);
2846 nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
2847 originInfos.Sort(OriginInfoLRUComparator());
2849 uint64_t usage = groupInfo->mUsage;
2850 for (uint32_t i = 0; i < originInfos.Length(); i++) {
2851 OriginInfo* originInfo = originInfos[i];
2853 doomedOriginInfos->AppendElement(originInfo);
2854 usage -= originInfo->mUsage;
2856 if (usage <= quotaManager->GetGroupLimit()) {
2857 break;
2858 }
2859 }
2860 }
2861 }
2863 return PL_DHASH_NEXT;
2864 }
2866 // static
2867 PLDHashOperator
2868 QuotaManager::GetAllTemporaryStorageOrigins(const nsACString& aKey,
2869 GroupInfoPair* aValue,
2870 void* aUserArg)
2871 {
2872 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
2873 NS_ASSERTION(aValue, "Null pointer!");
2875 nsRefPtr<GroupInfo> groupInfo =
2876 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
2877 if (groupInfo) {
2878 nsTArray<OriginInfo*>* originInfos =
2879 static_cast<nsTArray<OriginInfo*>*>(aUserArg);
2881 originInfos->AppendElements(groupInfo->mOriginInfos);
2882 }
2884 return PL_DHASH_NEXT;
2885 }
2887 void
2888 QuotaManager::CheckTemporaryStorageLimits()
2889 {
2890 AssertIsOnIOThread();
2892 nsTArray<OriginInfo*> doomedOriginInfos;
2893 {
2894 MutexAutoLock lock(mQuotaMutex);
2896 mGroupInfoPairs.EnumerateRead(GetOriginsExceedingGroupLimit,
2897 &doomedOriginInfos);
2899 uint64_t usage = 0;
2900 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
2901 usage += doomedOriginInfos[index]->mUsage;
2902 }
2904 if (mTemporaryStorageUsage - usage > mTemporaryStorageLimit) {
2905 nsTArray<OriginInfo*> originInfos;
2907 mGroupInfoPairs.EnumerateRead(GetAllTemporaryStorageOrigins,
2908 &originInfos);
2910 for (uint32_t index = originInfos.Length(); index > 0; index--) {
2911 if (doomedOriginInfos.Contains(originInfos[index - 1])) {
2912 originInfos.RemoveElementAt(index - 1);
2913 }
2914 }
2916 originInfos.Sort(OriginInfoLRUComparator());
2918 for (uint32_t i = 0; i < originInfos.Length(); i++) {
2919 if (mTemporaryStorageUsage - usage <= mTemporaryStorageLimit) {
2920 originInfos.TruncateLength(i);
2921 break;
2922 }
2924 usage += originInfos[i]->mUsage;
2925 }
2927 doomedOriginInfos.AppendElements(originInfos);
2928 }
2929 }
2931 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
2932 DeleteTemporaryFilesForOrigin(doomedOriginInfos[index]->mOrigin);
2933 }
2935 nsTArray<nsCString> doomedOrigins;
2936 {
2937 MutexAutoLock lock(mQuotaMutex);
2939 for (uint32_t index = 0; index < doomedOriginInfos.Length(); index++) {
2940 OriginInfo* doomedOriginInfo = doomedOriginInfos[index];
2942 nsCString group = doomedOriginInfo->mGroupInfo->mGroup;
2943 nsCString origin = doomedOriginInfo->mOrigin;
2944 LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY, group, origin);
2946 #ifdef DEBUG
2947 doomedOriginInfos[index] = nullptr;
2948 #endif
2950 doomedOrigins.AppendElement(origin);
2951 }
2952 }
2954 for (uint32_t index = 0; index < doomedOrigins.Length(); index++) {
2955 OriginClearCompleted(
2956 PERSISTENCE_TYPE_TEMPORARY,
2957 OriginOrPatternString::FromOrigin(doomedOrigins[index]));
2958 }
2959 }
2961 // static
2962 PLDHashOperator
2963 QuotaManager::AddTemporaryStorageOrigins(
2964 const nsACString& aKey,
2965 ArrayCluster<nsIOfflineStorage*>* aValue,
2966 void* aUserArg)
2967 {
2968 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
2969 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
2970 NS_ASSERTION(aValue, "Null pointer!");
2971 NS_ASSERTION(aUserArg, "Null pointer!");
2973 OriginCollection& collection = *static_cast<OriginCollection*>(aUserArg);
2975 if (collection.ContainsOrigin(aKey)) {
2976 return PL_DHASH_NEXT;
2977 }
2979 for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
2980 nsTArray<nsIOfflineStorage*>& array = (*aValue)[i];
2981 for (uint32_t j = 0; j < array.Length(); j++) {
2982 nsIOfflineStorage*& storage = array[j];
2983 if (storage->Type() == PERSISTENCE_TYPE_TEMPORARY) {
2984 collection.AddOrigin(aKey);
2985 return PL_DHASH_NEXT;
2986 }
2987 }
2988 }
2990 return PL_DHASH_NEXT;
2991 }
2993 // static
2994 PLDHashOperator
2995 QuotaManager::GetInactiveTemporaryStorageOrigins(const nsACString& aKey,
2996 GroupInfoPair* aValue,
2997 void* aUserArg)
2998 {
2999 NS_ASSERTION(!aKey.IsEmpty(), "Empty key!");
3000 NS_ASSERTION(aValue, "Null pointer!");
3001 NS_ASSERTION(aUserArg, "Null pointer!");
3003 nsRefPtr<GroupInfo> groupInfo =
3004 aValue->LockedGetGroupInfo(PERSISTENCE_TYPE_TEMPORARY);
3005 if (groupInfo) {
3006 InactiveOriginsInfo* info = static_cast<InactiveOriginsInfo*>(aUserArg);
3008 nsTArray<nsRefPtr<OriginInfo> >& originInfos = groupInfo->mOriginInfos;
3010 for (uint32_t i = 0; i < originInfos.Length(); i++) {
3011 OriginInfo* originInfo = originInfos[i];
3013 if (!info->collection.ContainsOrigin(originInfo->mOrigin)) {
3014 NS_ASSERTION(!originInfo->mQuotaObjects.Count(),
3015 "Inactive origin shouldn't have open files!");
3016 info->origins.AppendElement(originInfo);
3017 }
3018 }
3019 }
3021 return PL_DHASH_NEXT;
3022 }
3024 uint64_t
3025 QuotaManager::CollectOriginsForEviction(uint64_t aMinSizeToBeFreed,
3026 nsTArray<OriginInfo*>& aOriginInfos)
3027 {
3028 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3030 // Collect active origins first.
3031 OriginCollection originCollection;
3033 // Add patterns and origins that have running or pending synchronized ops.
3034 // (add patterns first to reduce redundancy in the origin collection).
3035 uint32_t index;
3036 for (index = 0; index < mSynchronizedOps.Length(); index++) {
3037 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
3038 if (op->mPersistenceType.IsNull() ||
3039 op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3040 if (op->mOriginOrPattern.IsPattern() &&
3041 !originCollection.ContainsPattern(op->mOriginOrPattern)) {
3042 originCollection.AddPattern(op->mOriginOrPattern);
3043 }
3044 }
3045 }
3047 for (index = 0; index < mSynchronizedOps.Length(); index++) {
3048 nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
3049 if (op->mPersistenceType.IsNull() ||
3050 op->mPersistenceType.Value() == PERSISTENCE_TYPE_TEMPORARY) {
3051 if (op->mOriginOrPattern.IsOrigin() &&
3052 !originCollection.ContainsOrigin(op->mOriginOrPattern)) {
3053 originCollection.AddOrigin(op->mOriginOrPattern);
3054 }
3055 }
3056 }
3058 // Add origins that have live temporary storages.
3059 mLiveStorages.EnumerateRead(AddTemporaryStorageOrigins, &originCollection);
3061 // Enumerate inactive origins. This must be protected by the mutex.
3062 nsTArray<OriginInfo*> inactiveOrigins;
3063 {
3064 InactiveOriginsInfo info(originCollection, inactiveOrigins);
3065 MutexAutoLock lock(mQuotaMutex);
3066 mGroupInfoPairs.EnumerateRead(GetInactiveTemporaryStorageOrigins, &info);
3067 }
3069 // We now have a list of all inactive origins. So it's safe to sort the list
3070 // and calculate available size without holding the lock.
3072 // Sort by the origin access time.
3073 inactiveOrigins.Sort(OriginInfoLRUComparator());
3075 // Create a list of inactive and the least recently used origins
3076 // whose aggregate size is greater or equals the minimal size to be freed.
3077 uint64_t sizeToBeFreed = 0;
3078 for(index = 0; index < inactiveOrigins.Length(); index++) {
3079 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3080 inactiveOrigins.TruncateLength(index);
3081 break;
3082 }
3084 sizeToBeFreed += inactiveOrigins[index]->mUsage;
3085 }
3087 if (sizeToBeFreed >= aMinSizeToBeFreed) {
3088 // Success, add synchronized ops for these origins, so any other
3089 // operations for them will be delayed (until origin eviction is finalized).
3091 for(index = 0; index < inactiveOrigins.Length(); index++) {
3092 OriginOrPatternString oops =
3093 OriginOrPatternString::FromOrigin(inactiveOrigins[index]->mOrigin);
3095 AddSynchronizedOp(oops,
3096 Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY));
3097 }
3099 inactiveOrigins.SwapElements(aOriginInfos);
3100 return sizeToBeFreed;
3101 }
3103 return 0;
3104 }
3106 void
3107 QuotaManager::DeleteTemporaryFilesForOrigin(const nsACString& aOrigin)
3108 {
3109 nsCOMPtr<nsIFile> directory;
3110 nsresult rv = GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, aOrigin,
3111 getter_AddRefs(directory));
3112 NS_ENSURE_SUCCESS_VOID(rv);
3114 rv = directory->Remove(true);
3115 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
3116 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
3117 // This should never fail if we've closed all storage connections
3118 // correctly...
3119 NS_ERROR("Failed to remove directory!");
3120 }
3121 }
3123 void
3124 QuotaManager::FinalizeOriginEviction(nsTArray<nsCString>& aOrigins)
3125 {
3126 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3128 nsRefPtr<FinalizeOriginEvictionRunnable> runnable =
3129 new FinalizeOriginEvictionRunnable(aOrigins);
3131 nsresult rv = IsOnIOThread() ? runnable->RunImmediately()
3132 : runnable->Dispatch();
3133 NS_ENSURE_SUCCESS_VOID(rv);
3134 }
3136 void
3137 QuotaManager::SaveOriginAccessTime(const nsACString& aOrigin,
3138 int64_t aTimestamp)
3139 {
3140 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3142 if (QuotaManager::IsShuttingDown()) {
3143 return;
3144 }
3146 nsRefPtr<SaveOriginAccessTimeRunnable> runnable =
3147 new SaveOriginAccessTimeRunnable(aOrigin, aTimestamp);
3149 if (NS_FAILED(mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
3150 NS_WARNING("Failed to dispatch runnable!");
3151 }
3152 }
3154 void
3155 QuotaManager::GetOriginPatternString(uint32_t aAppId,
3156 MozBrowserPatternFlag aBrowserFlag,
3157 const nsACString& aOrigin,
3158 nsAutoCString& _retval)
3159 {
3160 NS_ASSERTION(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID,
3161 "Bad appId!");
3162 NS_ASSERTION(aOrigin.IsEmpty() || aBrowserFlag != IgnoreMozBrowser,
3163 "Bad args!");
3165 if (aOrigin.IsEmpty()) {
3166 _retval.Truncate();
3168 _retval.AppendInt(aAppId);
3169 _retval.Append('+');
3171 if (aBrowserFlag != IgnoreMozBrowser) {
3172 if (aBrowserFlag == MozBrowser) {
3173 _retval.Append('t');
3174 }
3175 else {
3176 _retval.Append('f');
3177 }
3178 _retval.Append('+');
3179 }
3181 return;
3182 }
3184 #ifdef DEBUG
3185 if (aAppId != nsIScriptSecurityManager::NO_APP_ID ||
3186 aBrowserFlag == MozBrowser) {
3187 nsAutoCString pattern;
3188 GetOriginPatternString(aAppId, aBrowserFlag, EmptyCString(), pattern);
3189 NS_ASSERTION(PatternMatchesOrigin(pattern, aOrigin),
3190 "Origin doesn't match parameters!");
3191 }
3192 #endif
3194 _retval = aOrigin;
3195 }
3197 SynchronizedOp::SynchronizedOp(const OriginOrPatternString& aOriginOrPattern,
3198 Nullable<PersistenceType> aPersistenceType,
3199 const nsACString& aId)
3200 : mOriginOrPattern(aOriginOrPattern), mPersistenceType(aPersistenceType),
3201 mId(aId)
3202 {
3203 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3204 MOZ_COUNT_CTOR(SynchronizedOp);
3205 }
3207 SynchronizedOp::~SynchronizedOp()
3208 {
3209 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3210 MOZ_COUNT_DTOR(SynchronizedOp);
3211 }
3213 bool
3214 SynchronizedOp::MustWaitFor(const SynchronizedOp& aExistingOp)
3215 {
3216 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3218 if (aExistingOp.mOriginOrPattern.IsNull() || mOriginOrPattern.IsNull()) {
3219 return true;
3220 }
3222 bool match;
3224 if (aExistingOp.mOriginOrPattern.IsOrigin()) {
3225 if (mOriginOrPattern.IsOrigin()) {
3226 match = aExistingOp.mOriginOrPattern.Equals(mOriginOrPattern);
3227 }
3228 else {
3229 match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern);
3230 }
3231 }
3232 else if (mOriginOrPattern.IsOrigin()) {
3233 match = PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
3234 }
3235 else {
3236 match = PatternMatchesOrigin(mOriginOrPattern, aExistingOp.mOriginOrPattern) ||
3237 PatternMatchesOrigin(aExistingOp.mOriginOrPattern, mOriginOrPattern);
3238 }
3240 // If the origins don't match, the second can proceed.
3241 if (!match) {
3242 return false;
3243 }
3245 // If the origins match but the persistence types are different, the second
3246 // can proceed.
3247 if (!aExistingOp.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
3248 aExistingOp.mPersistenceType.Value() != mPersistenceType.Value()) {
3249 return false;
3250 }
3252 // If the origins and the ids match, the second must wait.
3253 if (aExistingOp.mId == mId) {
3254 return true;
3255 }
3257 // Waiting is required if either one corresponds to an origin clearing
3258 // (an empty Id).
3259 if (aExistingOp.mId.IsEmpty() || mId.IsEmpty()) {
3260 return true;
3261 }
3263 // Otherwise, things for the same origin but different storages can proceed
3264 // independently.
3265 return false;
3266 }
3268 void
3269 SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
3270 {
3271 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3272 NS_ASSERTION(mDelayedRunnables.IsEmpty() || mId.IsEmpty(),
3273 "Only ClearOrigin operations can delay multiple runnables!");
3275 mDelayedRunnables.AppendElement(aRunnable);
3276 }
3278 void
3279 SynchronizedOp::DispatchDelayedRunnables()
3280 {
3281 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3282 NS_ASSERTION(!mListener, "Any listener should be gone by now!");
3284 uint32_t count = mDelayedRunnables.Length();
3285 for (uint32_t index = 0; index < count; index++) {
3286 NS_DispatchToCurrentThread(mDelayedRunnables[index]);
3287 }
3289 mDelayedRunnables.Clear();
3290 }
3292 CollectOriginsHelper::CollectOriginsHelper(mozilla::Mutex& aMutex,
3293 uint64_t aMinSizeToBeFreed)
3294 : mMinSizeToBeFreed(aMinSizeToBeFreed),
3295 mMutex(aMutex),
3296 mCondVar(aMutex, "CollectOriginsHelper::mCondVar"),
3297 mSizeToBeFreed(0),
3298 mWaiting(true)
3299 {
3300 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
3301 mMutex.AssertCurrentThreadOwns();
3302 }
3304 int64_t
3305 CollectOriginsHelper::BlockAndReturnOriginsForEviction(
3306 nsTArray<OriginInfo*>& aOriginInfos)
3307 {
3308 MOZ_ASSERT(!NS_IsMainThread(), "Wrong thread!");
3309 mMutex.AssertCurrentThreadOwns();
3311 while (mWaiting) {
3312 mCondVar.Wait();
3313 }
3315 mOriginInfos.SwapElements(aOriginInfos);
3316 return mSizeToBeFreed;
3317 }
3319 NS_IMETHODIMP
3320 CollectOriginsHelper::Run()
3321 {
3322 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
3324 QuotaManager* quotaManager = QuotaManager::Get();
3325 NS_ASSERTION(quotaManager, "Shouldn't be null!");
3327 // We use extra stack vars here to avoid race detector warnings (the same
3328 // memory accessed with and without the lock held).
3329 nsTArray<OriginInfo*> originInfos;
3330 uint64_t sizeToBeFreed =
3331 quotaManager->CollectOriginsForEviction(mMinSizeToBeFreed, originInfos);
3333 MutexAutoLock lock(mMutex);
3335 NS_ASSERTION(mWaiting, "Huh?!");
3337 mOriginInfos.SwapElements(originInfos);
3338 mSizeToBeFreed = sizeToBeFreed;
3339 mWaiting = false;
3340 mCondVar.Notify();
3342 return NS_OK;
3343 }
3345 nsresult
3346 OriginClearRunnable::OnExclusiveAccessAcquired()
3347 {
3348 QuotaManager* quotaManager = QuotaManager::Get();
3349 NS_ASSERTION(quotaManager, "This should never fail!");
3351 nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3352 NS_ENSURE_SUCCESS(rv, rv);
3354 return NS_OK;
3355 }
3357 // static
3358 void
3359 OriginClearRunnable::InvalidateOpenedStorages(
3360 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
3361 void* aClosure)
3362 {
3363 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3365 nsTArray<nsCOMPtr<nsIOfflineStorage> > storages;
3366 storages.SwapElements(aStorages);
3368 for (uint32_t index = 0; index < storages.Length(); index++) {
3369 storages[index]->Invalidate();
3370 }
3371 }
3373 void
3374 OriginClearRunnable::DeleteFiles(QuotaManager* aQuotaManager,
3375 PersistenceType aPersistenceType)
3376 {
3377 AssertIsOnIOThread();
3378 NS_ASSERTION(aQuotaManager, "Don't pass me null!");
3380 nsresult rv;
3382 nsCOMPtr<nsIFile> directory =
3383 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
3384 NS_ENSURE_SUCCESS_VOID(rv);
3386 rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
3387 NS_ENSURE_SUCCESS_VOID(rv);
3389 nsCOMPtr<nsISimpleEnumerator> entries;
3390 if (NS_FAILED(directory->GetDirectoryEntries(getter_AddRefs(entries))) ||
3391 !entries) {
3392 return;
3393 }
3395 nsCString originSanitized(mOriginOrPattern);
3396 SanitizeOriginString(originSanitized);
3398 bool hasMore;
3399 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
3400 nsCOMPtr<nsISupports> entry;
3401 rv = entries->GetNext(getter_AddRefs(entry));
3402 NS_ENSURE_SUCCESS_VOID(rv);
3404 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
3405 NS_ASSERTION(file, "Don't know what this is!");
3407 bool isDirectory;
3408 rv = file->IsDirectory(&isDirectory);
3409 NS_ENSURE_SUCCESS_VOID(rv);
3411 if (!isDirectory) {
3412 NS_WARNING("Something in the IndexedDB directory that doesn't belong!");
3413 continue;
3414 }
3416 nsString leafName;
3417 rv = file->GetLeafName(leafName);
3418 NS_ENSURE_SUCCESS_VOID(rv);
3420 // Skip storages for other apps.
3421 if (!PatternMatchesOrigin(originSanitized,
3422 NS_ConvertUTF16toUTF8(leafName))) {
3423 continue;
3424 }
3426 if (NS_FAILED(file->Remove(true))) {
3427 // This should never fail if we've closed all storage connections
3428 // correctly...
3429 NS_ERROR("Failed to remove directory!");
3430 }
3431 }
3433 aQuotaManager->RemoveQuotaForPattern(aPersistenceType, mOriginOrPattern);
3435 aQuotaManager->OriginClearCompleted(aPersistenceType, mOriginOrPattern);
3436 }
3438 NS_IMPL_ISUPPORTS_INHERITED0(OriginClearRunnable, nsRunnable)
3440 NS_IMETHODIMP
3441 OriginClearRunnable::Run()
3442 {
3443 PROFILER_LABEL("Quota", "OriginClearRunnable::Run");
3445 QuotaManager* quotaManager = QuotaManager::Get();
3446 NS_ASSERTION(quotaManager, "This should never fail!");
3448 switch (mCallbackState) {
3449 case Pending: {
3450 NS_NOTREACHED("Should never get here without being dispatched!");
3451 return NS_ERROR_UNEXPECTED;
3452 }
3454 case OpenAllowed: {
3455 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3457 AdvanceState();
3459 // Now we have to wait until the thread pool is done with all of the
3460 // storages we care about.
3461 nsresult rv =
3462 quotaManager->AcquireExclusiveAccess(mOriginOrPattern, mPersistenceType,
3463 this, InvalidateOpenedStorages,
3464 nullptr);
3465 NS_ENSURE_SUCCESS(rv, rv);
3467 return NS_OK;
3468 }
3470 case IO: {
3471 AssertIsOnIOThread();
3473 AdvanceState();
3475 if (mPersistenceType.IsNull()) {
3476 DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3477 DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3478 } else {
3479 DeleteFiles(quotaManager, mPersistenceType.Value());
3480 }
3482 // Now dispatch back to the main thread.
3483 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
3484 NS_WARNING("Failed to dispatch to main thread!");
3485 return NS_ERROR_FAILURE;
3486 }
3488 return NS_OK;
3489 }
3491 case Complete: {
3492 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3494 // Tell the QuotaManager that we're done.
3495 quotaManager->AllowNextSynchronizedOp(mOriginOrPattern, mPersistenceType,
3496 EmptyCString());
3498 return NS_OK;
3499 }
3501 default:
3502 NS_ERROR("Unknown state value!");
3503 return NS_ERROR_UNEXPECTED;
3504 }
3506 NS_NOTREACHED("Should never get here!");
3507 return NS_ERROR_UNEXPECTED;
3508 }
3510 AsyncUsageRunnable::AsyncUsageRunnable(uint32_t aAppId,
3511 bool aInMozBrowserOnly,
3512 const nsACString& aGroup,
3513 const OriginOrPatternString& aOrigin,
3514 nsIURI* aURI,
3515 nsIUsageCallback* aCallback)
3516 : mURI(aURI),
3517 mCallback(aCallback),
3518 mAppId(aAppId),
3519 mGroup(aGroup),
3520 mOrigin(aOrigin),
3521 mCallbackState(Pending),
3522 mInMozBrowserOnly(aInMozBrowserOnly)
3523 {
3524 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3525 NS_ASSERTION(aURI, "Null pointer!");
3526 NS_ASSERTION(!aGroup.IsEmpty(), "Empty group!");
3527 NS_ASSERTION(aOrigin.IsOrigin(), "Expect origin only here!");
3528 NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
3529 NS_ASSERTION(aCallback, "Null pointer!");
3530 }
3532 nsresult
3533 AsyncUsageRunnable::TakeShortcut()
3534 {
3535 NS_ASSERTION(mCallbackState == Pending, "Huh?");
3537 nsresult rv = NS_DispatchToCurrentThread(this);
3538 NS_ENSURE_SUCCESS(rv, rv);
3540 mCallbackState = Shortcut;
3541 return NS_OK;
3542 }
3544 nsresult
3545 AsyncUsageRunnable::RunInternal()
3546 {
3547 QuotaManager* quotaManager = QuotaManager::Get();
3548 NS_ASSERTION(quotaManager, "This should never fail!");
3550 nsresult rv;
3552 switch (mCallbackState) {
3553 case Pending: {
3554 NS_NOTREACHED("Should never get here without being dispatched!");
3555 return NS_ERROR_UNEXPECTED;
3556 }
3558 case OpenAllowed: {
3559 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3561 AdvanceState();
3563 rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3564 if (NS_FAILED(rv)) {
3565 NS_WARNING("Failed to dispatch to the IO thread!");
3566 }
3568 return NS_OK;
3569 }
3571 case IO: {
3572 AssertIsOnIOThread();
3574 AdvanceState();
3576 // Add all the persistent storage files we care about.
3577 rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3578 NS_ENSURE_SUCCESS(rv, rv);
3580 // Add all the temporary storage files we care about.
3581 rv = AddToUsage(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3582 NS_ENSURE_SUCCESS(rv, rv);
3584 // Run dispatches us back to the main thread.
3585 return NS_OK;
3586 }
3588 case Complete: // Fall through
3589 case Shortcut: {
3590 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3592 // Call the callback unless we were canceled.
3593 if (!mCanceled) {
3594 mCallback->OnUsageResult(mURI, TotalUsage(), FileUsage(), mAppId,
3595 mInMozBrowserOnly);
3596 }
3598 // Clean up.
3599 mURI = nullptr;
3600 mCallback = nullptr;
3602 // And tell the QuotaManager that we're done.
3603 if (mCallbackState == Complete) {
3604 quotaManager->AllowNextSynchronizedOp(mOrigin,
3605 Nullable<PersistenceType>(),
3606 EmptyCString());
3607 }
3609 return NS_OK;
3610 }
3612 default:
3613 NS_ERROR("Unknown state value!");
3614 return NS_ERROR_UNEXPECTED;
3615 }
3617 NS_NOTREACHED("Should never get here!");
3618 return NS_ERROR_UNEXPECTED;
3619 }
3621 nsresult
3622 AsyncUsageRunnable::AddToUsage(QuotaManager* aQuotaManager,
3623 PersistenceType aPersistenceType)
3624 {
3625 AssertIsOnIOThread();
3627 nsCOMPtr<nsIFile> directory;
3628 nsresult rv = aQuotaManager->GetDirectoryForOrigin(aPersistenceType, mOrigin,
3629 getter_AddRefs(directory));
3630 NS_ENSURE_SUCCESS(rv, rv);
3632 bool exists;
3633 rv = directory->Exists(&exists);
3634 NS_ENSURE_SUCCESS(rv, rv);
3636 // If the directory exists then enumerate all the files inside, adding up
3637 // the sizes to get the final usage statistic.
3638 if (exists && !mCanceled) {
3639 bool initialized;
3641 if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
3642 initialized = aQuotaManager->mInitializedOrigins.Contains(mOrigin);
3644 if (!initialized) {
3645 rv = MaybeUpgradeOriginDirectory(directory);
3646 NS_ENSURE_SUCCESS(rv, rv);
3647 }
3648 }
3649 else {
3650 NS_ASSERTION(aPersistenceType == PERSISTENCE_TYPE_TEMPORARY, "Huh?");
3651 initialized = aQuotaManager->mTemporaryStorageInitialized;
3652 }
3654 nsCOMPtr<nsISimpleEnumerator> entries;
3655 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
3656 NS_ENSURE_SUCCESS(rv, rv);
3658 bool hasMore;
3659 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
3660 hasMore && !mCanceled) {
3661 nsCOMPtr<nsISupports> entry;
3662 rv = entries->GetNext(getter_AddRefs(entry));
3663 NS_ENSURE_SUCCESS(rv, rv);
3665 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
3666 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
3668 nsString leafName;
3669 rv = file->GetLeafName(leafName);
3670 NS_ENSURE_SUCCESS(rv, rv);
3672 if (leafName.EqualsLiteral(METADATA_FILE_NAME) ||
3673 leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
3674 continue;
3675 }
3677 if (!initialized) {
3678 bool isDirectory;
3679 rv = file->IsDirectory(&isDirectory);
3680 NS_ENSURE_SUCCESS(rv, rv);
3682 if (!isDirectory) {
3683 NS_WARNING("Unknown file found!");
3684 return NS_ERROR_UNEXPECTED;
3685 }
3686 }
3688 Client::Type clientType;
3689 rv = Client::TypeFromText(leafName, clientType);
3690 if (NS_FAILED(rv)) {
3691 NS_WARNING("Unknown directory found!");
3692 if (!initialized) {
3693 return NS_ERROR_UNEXPECTED;
3694 }
3695 continue;
3696 }
3698 nsRefPtr<Client>& client = aQuotaManager->mClients[clientType];
3700 if (initialized) {
3701 rv = client->GetUsageForOrigin(aPersistenceType, mGroup, mOrigin, this);
3702 }
3703 else {
3704 rv = client->InitOrigin(aPersistenceType, mGroup, mOrigin, this);
3705 }
3706 NS_ENSURE_SUCCESS(rv, rv);
3707 }
3708 }
3710 return NS_OK;
3711 }
3713 NS_IMPL_ISUPPORTS_INHERITED(AsyncUsageRunnable, nsRunnable, nsIQuotaRequest)
3715 NS_IMETHODIMP
3716 AsyncUsageRunnable::Run()
3717 {
3718 PROFILER_LABEL("Quota", "AsyncUsageRunnable::Run");
3720 nsresult rv = RunInternal();
3722 if (!NS_IsMainThread()) {
3723 if (NS_FAILED(rv)) {
3724 ResetUsage();
3725 }
3727 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
3728 NS_WARNING("Failed to dispatch to main thread!");
3729 }
3730 }
3732 return NS_OK;
3733 }
3735 NS_IMETHODIMP
3736 AsyncUsageRunnable::Cancel()
3737 {
3738 if (mCanceled.exchange(true)) {
3739 NS_WARNING("Canceled more than once?!");
3740 return NS_ERROR_UNEXPECTED;
3741 }
3743 return NS_OK;
3744 }
3746 nsresult
3747 ResetOrClearRunnable::OnExclusiveAccessAcquired()
3748 {
3749 QuotaManager* quotaManager = QuotaManager::Get();
3750 NS_ASSERTION(quotaManager, "This should never fail!");
3752 nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3753 NS_ENSURE_SUCCESS(rv, rv);
3755 return NS_OK;
3756 }
3758 // static
3759 void
3760 ResetOrClearRunnable::InvalidateOpenedStorages(
3761 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
3762 void* aClosure)
3763 {
3764 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3766 nsTArray<nsCOMPtr<nsIOfflineStorage> > storages;
3767 storages.SwapElements(aStorages);
3769 for (uint32_t index = 0; index < storages.Length(); index++) {
3770 storages[index]->Invalidate();
3771 }
3772 }
3774 void
3775 ResetOrClearRunnable::DeleteFiles(QuotaManager* aQuotaManager,
3776 PersistenceType aPersistenceType)
3777 {
3778 AssertIsOnIOThread();
3779 NS_ASSERTION(aQuotaManager, "Don't pass me null!");
3781 nsresult rv;
3783 nsCOMPtr<nsIFile> directory =
3784 do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
3785 NS_ENSURE_SUCCESS_VOID(rv);
3787 rv = directory->InitWithPath(aQuotaManager->GetStoragePath(aPersistenceType));
3788 NS_ENSURE_SUCCESS_VOID(rv);
3790 rv = directory->Remove(true);
3791 if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
3792 rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
3793 // This should never fail if we've closed all storage connections
3794 // correctly...
3795 NS_ERROR("Failed to remove directory!");
3796 }
3797 }
3799 NS_IMPL_ISUPPORTS_INHERITED0(ResetOrClearRunnable, nsRunnable)
3801 NS_IMETHODIMP
3802 ResetOrClearRunnable::Run()
3803 {
3804 QuotaManager* quotaManager = QuotaManager::Get();
3805 NS_ASSERTION(quotaManager, "This should never fail!");
3807 switch (mCallbackState) {
3808 case Pending: {
3809 NS_NOTREACHED("Should never get here without being dispatched!");
3810 return NS_ERROR_UNEXPECTED;
3811 }
3813 case OpenAllowed: {
3814 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3816 AdvanceState();
3818 // Now we have to wait until the thread pool is done with all of the
3819 // storages we care about.
3820 nsresult rv =
3821 quotaManager->AcquireExclusiveAccess(NullCString(),
3822 Nullable<PersistenceType>(), this,
3823 InvalidateOpenedStorages, nullptr);
3824 NS_ENSURE_SUCCESS(rv, rv);
3826 return NS_OK;
3827 }
3829 case IO: {
3830 AssertIsOnIOThread();
3832 AdvanceState();
3834 if (mClear) {
3835 DeleteFiles(quotaManager, PERSISTENCE_TYPE_PERSISTENT);
3836 DeleteFiles(quotaManager, PERSISTENCE_TYPE_TEMPORARY);
3837 }
3839 quotaManager->RemoveQuota();
3840 quotaManager->ResetOrClearCompleted();
3842 // Now dispatch back to the main thread.
3843 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
3844 NS_WARNING("Failed to dispatch to main thread!");
3845 return NS_ERROR_FAILURE;
3846 }
3848 return NS_OK;
3849 }
3851 case Complete: {
3852 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3854 // Tell the QuotaManager that we're done.
3855 quotaManager->AllowNextSynchronizedOp(OriginOrPatternString::FromNull(),
3856 Nullable<PersistenceType>(),
3857 EmptyCString());
3859 return NS_OK;
3860 }
3862 default:
3863 NS_ERROR("Unknown state value!");
3864 return NS_ERROR_UNEXPECTED;
3865 }
3867 NS_NOTREACHED("Should never get here!");
3868 return NS_ERROR_UNEXPECTED;
3869 }
3871 NS_IMETHODIMP
3872 FinalizeOriginEvictionRunnable::Run()
3873 {
3874 QuotaManager* quotaManager = QuotaManager::Get();
3875 NS_ASSERTION(quotaManager, "This should never fail!");
3877 nsresult rv;
3879 switch (mCallbackState) {
3880 case Pending: {
3881 NS_NOTREACHED("Should never get here without being dispatched!");
3882 return NS_ERROR_UNEXPECTED;
3883 }
3885 case OpenAllowed: {
3886 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3888 AdvanceState();
3890 rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
3891 if (NS_FAILED(rv)) {
3892 NS_WARNING("Failed to dispatch to the IO thread!");
3893 }
3895 return NS_OK;
3896 }
3898 case IO: {
3899 AssertIsOnIOThread();
3901 AdvanceState();
3903 for (uint32_t index = 0; index < mOrigins.Length(); index++) {
3904 quotaManager->OriginClearCompleted(
3905 PERSISTENCE_TYPE_TEMPORARY,
3906 OriginOrPatternString::FromOrigin(mOrigins[index]));
3907 }
3909 if (NS_FAILED(NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL))) {
3910 NS_WARNING("Failed to dispatch to main thread!");
3911 return NS_ERROR_FAILURE;
3912 }
3914 return NS_OK;
3915 }
3917 case Complete: {
3918 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3920 for (uint32_t index = 0; index < mOrigins.Length(); index++) {
3921 quotaManager->AllowNextSynchronizedOp(
3922 OriginOrPatternString::FromOrigin(mOrigins[index]),
3923 Nullable<PersistenceType>(PERSISTENCE_TYPE_TEMPORARY),
3924 EmptyCString());
3925 }
3927 return NS_OK;
3928 }
3930 default:
3931 NS_ERROR("Unknown state value!");
3932 return NS_ERROR_UNEXPECTED;
3933 }
3935 NS_NOTREACHED("Should never get here!");
3936 return NS_ERROR_UNEXPECTED;
3937 }
3939 nsresult
3940 FinalizeOriginEvictionRunnable::Dispatch()
3941 {
3942 NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
3943 NS_ASSERTION(mCallbackState == Pending, "Huh?");
3945 mCallbackState = OpenAllowed;
3946 return NS_DispatchToMainThread(this);
3947 }
3949 nsresult
3950 FinalizeOriginEvictionRunnable::RunImmediately()
3951 {
3952 AssertIsOnIOThread();
3953 NS_ASSERTION(mCallbackState == Pending, "Huh?");
3955 mCallbackState = IO;
3956 return this->Run();
3957 }
3959 NS_IMETHODIMP
3960 WaitForTransactionsToFinishRunnable::Run()
3961 {
3962 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3963 NS_ASSERTION(mOp, "Null op!");
3964 NS_ASSERTION(mOp->mListener, "Nothing to run!");
3965 NS_ASSERTION(mCountdown, "Wrong countdown!");
3967 if (--mCountdown) {
3968 return NS_OK;
3969 }
3971 // Don't hold the listener alive longer than necessary.
3972 nsRefPtr<AcquireListener> listener;
3973 listener.swap(mOp->mListener);
3975 mOp = nullptr;
3977 nsresult rv = listener->OnExclusiveAccessAcquired();
3978 NS_ENSURE_SUCCESS(rv, rv);
3980 // The listener is responsible for calling
3981 // QuotaManager::AllowNextSynchronizedOp.
3982 return NS_OK;
3983 }
3985 NS_IMETHODIMP
3986 WaitForLockedFilesToFinishRunnable::Run()
3987 {
3988 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
3990 mBusy = false;
3992 return NS_OK;
3993 }
3995 NS_IMETHODIMP
3996 SaveOriginAccessTimeRunnable::Run()
3997 {
3998 AssertIsOnIOThread();
4000 QuotaManager* quotaManager = QuotaManager::Get();
4001 NS_ASSERTION(quotaManager, "This should never fail!");
4003 nsCOMPtr<nsIFile> directory;
4004 nsresult rv =
4005 quotaManager->GetDirectoryForOrigin(PERSISTENCE_TYPE_TEMPORARY, mOrigin,
4006 getter_AddRefs(directory));
4007 NS_ENSURE_SUCCESS(rv, rv);
4009 nsCOMPtr<nsIBinaryOutputStream> stream;
4010 rv = GetDirectoryMetadataStream(directory, true, getter_AddRefs(stream));
4011 NS_ENSURE_SUCCESS(rv, rv);
4013 // The origin directory may not exist anymore.
4014 if (stream) {
4015 rv = stream->Write64(mTimestamp);
4016 NS_ENSURE_SUCCESS(rv, rv);
4017 }
4019 return NS_OK;
4020 }