Blame security/manager/ssl/DataStorage.cpp

Packit f0b94e
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
Packit f0b94e
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
Packit f0b94e
/* This Source Code Form is subject to the terms of the Mozilla Public
Packit f0b94e
 * License, v. 2.0. If a copy of the MPL was not distributed with this
Packit f0b94e
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Packit f0b94e
Packit f0b94e
#include "DataStorage.h"
Packit f0b94e
Packit f0b94e
#include "mozilla/Assertions.h"
Packit f0b94e
#include "mozilla/ClearOnShutdown.h"
Packit f0b94e
#include "mozilla/dom/PContent.h"
Packit f0b94e
#include "mozilla/dom/ContentChild.h"
Packit f0b94e
#include "mozilla/dom/ContentParent.h"
Packit f0b94e
#include "mozilla/Preferences.h"
Packit f0b94e
#include "mozilla/Services.h"
Packit f0b94e
#include "mozilla/Telemetry.h"
Packit f0b94e
#include "mozilla/Unused.h"
Packit f0b94e
#include "nsAppDirectoryServiceDefs.h"
Packit f0b94e
#include "nsDirectoryServiceUtils.h"
Packit f0b94e
#include "nsIMemoryReporter.h"
Packit f0b94e
#include "nsIObserverService.h"
Packit f0b94e
#include "nsITimer.h"
Packit f0b94e
#include "nsNetUtil.h"
Packit f0b94e
#include "nsPrintfCString.h"
Packit f0b94e
#include "nsStreamUtils.h"
Packit f0b94e
#include "nsThreadUtils.h"
Packit f0b94e
Packit f0b94e
// NB: Read DataStorage.h first.
Packit f0b94e
Packit f0b94e
// The default time between data changing and a write, in milliseconds.
Packit f0b94e
static const uint32_t sDataStorageDefaultTimerDelay = 5u * 60u * 1000u;
Packit f0b94e
// The maximum score an entry can have (prevents overflow)
Packit f0b94e
static const uint32_t sMaxScore = UINT32_MAX;
Packit f0b94e
// The maximum number of entries per type of data (limits resource use)
Packit f0b94e
static const uint32_t sMaxDataEntries = 1024;
Packit f0b94e
static const int64_t sOneDayInMicroseconds =
Packit f0b94e
    int64_t(24 * 60 * 60) * PR_USEC_PER_SEC;
Packit f0b94e
Packit f0b94e
namespace mozilla {
Packit f0b94e
Packit f0b94e
class DataStorageMemoryReporter final : public nsIMemoryReporter {
Packit f0b94e
  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
Packit f0b94e
  ~DataStorageMemoryReporter() = default;
Packit f0b94e
Packit f0b94e
 public:
Packit f0b94e
  NS_DECL_ISUPPORTS
Packit f0b94e
Packit f0b94e
  NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
Packit f0b94e
                            nsISupports* aData, bool aAnonymize) final {
Packit f0b94e
    nsTArray<nsString> fileNames;
Packit f0b94e
    DataStorage::GetAllFileNames(fileNames);
Packit f0b94e
    for (const auto& file : fileNames) {
Packit f0b94e
      RefPtr<DataStorage> ds = DataStorage::GetFromRawFileName(file);
Packit f0b94e
      size_t amount = ds->SizeOfIncludingThis(MallocSizeOf);
Packit f0b94e
      nsPrintfCString path("explicit/data-storage/%s",
Packit f0b94e
                           NS_ConvertUTF16toUTF8(file).get());
Packit f0b94e
      Unused << aHandleReport->Callback(
Packit f0b94e
          EmptyCString(), path, KIND_HEAP, UNITS_BYTES, amount,
Packit f0b94e
          NS_LITERAL_CSTRING("Memory used by PSM data storage cache."), aData);
Packit f0b94e
    }
Packit f0b94e
    return NS_OK;
Packit f0b94e
  }
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
NS_IMPL_ISUPPORTS(DataStorageMemoryReporter, nsIMemoryReporter)
Packit f0b94e
Packit f0b94e
NS_IMPL_ISUPPORTS(DataStorage, nsIObserver)
Packit f0b94e
Packit f0b94e
StaticAutoPtr<DataStorage::DataStorages> DataStorage::sDataStorages;
Packit f0b94e
Packit f0b94e
DataStorage::DataStorage(const nsString& aFilename)
Packit f0b94e
    : mMutex("DataStorage::mMutex"),
Packit f0b94e
      mTimerDelay(sDataStorageDefaultTimerDelay),
Packit f0b94e
      mPendingWrite(false),
Packit f0b94e
      mShuttingDown(false),
Packit f0b94e
      mInitCalled(false),
Packit f0b94e
      mReadyMonitor("DataStorage::mReadyMonitor"),
Packit f0b94e
      mReady(false),
Packit f0b94e
      mFilename(aFilename) {}
Packit f0b94e
Packit f0b94e
DataStorage::~DataStorage() {}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
already_AddRefed<DataStorage> DataStorage::Get(DataStorageClass aFilename) {
Packit f0b94e
  switch (aFilename) {
Packit f0b94e
#define DATA_STORAGE(_)     \
Packit f0b94e
  case DataStorageClass::_: \
Packit f0b94e
    return GetFromRawFileName(NS_LITERAL_STRING(#_ ".txt"));
Packit f0b94e
#include "mozilla/DataStorageList.h"
Packit f0b94e
#undef DATA_STORAGE
Packit f0b94e
    default:
Packit f0b94e
      MOZ_ASSERT_UNREACHABLE("Invalid DataStorage type passed?");
Packit f0b94e
      return nullptr;
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
already_AddRefed<DataStorage> DataStorage::GetFromRawFileName(
Packit f0b94e
    const nsString& aFilename) {
Packit f0b94e
  MOZ_ASSERT(NS_IsMainThread());
Packit f0b94e
  if (!sDataStorages) {
Packit f0b94e
    sDataStorages = new DataStorages();
Packit f0b94e
    ClearOnShutdown(&sDataStorages);
Packit f0b94e
  }
Packit f0b94e
  RefPtr<DataStorage> storage;
Packit f0b94e
  if (!sDataStorages->Get(aFilename, getter_AddRefs(storage))) {
Packit f0b94e
    storage = new DataStorage(aFilename);
Packit f0b94e
    sDataStorages->Put(aFilename, storage);
Packit f0b94e
  }
Packit f0b94e
  return storage.forget();
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
already_AddRefed<DataStorage> DataStorage::GetIfExists(
Packit f0b94e
    DataStorageClass aFilename) {
Packit f0b94e
  MOZ_ASSERT(NS_IsMainThread());
Packit f0b94e
  if (!sDataStorages) {
Packit f0b94e
    sDataStorages = new DataStorages();
Packit f0b94e
  }
Packit f0b94e
  nsString name;
Packit f0b94e
  switch (aFilename) {
Packit f0b94e
#define DATA_STORAGE(_)            \
Packit f0b94e
  case DataStorageClass::_:        \
Packit f0b94e
    name.AssignLiteral(#_ ".txt"); \
Packit f0b94e
    break;
Packit f0b94e
#include "mozilla/DataStorageList.h"
Packit f0b94e
#undef DATA_STORAGE
Packit f0b94e
    default:
Packit f0b94e
      MOZ_ASSERT_UNREACHABLE("Invalid DataStorages type passed?");
Packit f0b94e
  }
Packit f0b94e
  RefPtr<DataStorage> storage;
Packit f0b94e
  if (!name.IsEmpty()) {
Packit f0b94e
    sDataStorages->Get(name, getter_AddRefs(storage));
Packit f0b94e
  }
Packit f0b94e
  return storage.forget();
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
void DataStorage::GetAllFileNames(nsTArray<nsString>& aItems) {
Packit f0b94e
  MOZ_ASSERT(NS_IsMainThread());
Packit f0b94e
  if (!sDataStorages) {
Packit f0b94e
    return;
Packit f0b94e
  }
Packit f0b94e
#define DATA_STORAGE(_) aItems.AppendElement(NS_LITERAL_STRING(#_ ".txt"));
Packit f0b94e
#include "mozilla/DataStorageList.h"
Packit f0b94e
#undef DATA_STORAGE
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
void DataStorage::GetAllChildProcessData(
Packit f0b94e
    nsTArray<mozilla::dom::DataStorageEntry>& aEntries) {
Packit f0b94e
  nsTArray<nsString> storageFiles;
Packit f0b94e
  GetAllFileNames(storageFiles);
Packit f0b94e
  for (auto& file : storageFiles) {
Packit f0b94e
    dom::DataStorageEntry entry;
Packit f0b94e
    entry.filename() = file;
Packit f0b94e
    RefPtr<DataStorage> storage = DataStorage::GetFromRawFileName(file);
Packit f0b94e
    if (!storage->mInitCalled) {
Packit f0b94e
      // Perhaps no consumer has initialized the DataStorage object yet,
Packit f0b94e
      // so do that now!
Packit f0b94e
      bool dataWillPersist = false;
Packit f0b94e
      nsresult rv = storage->Init(dataWillPersist);
Packit f0b94e
      if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
        return;
Packit f0b94e
      }
Packit f0b94e
    }
Packit f0b94e
    storage->GetAll(&entry.items());
Packit f0b94e
    aEntries.AppendElement(Move(entry));
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// static
Packit f0b94e
void DataStorage::SetCachedStorageEntries(
Packit f0b94e
    const InfallibleTArray<mozilla::dom::DataStorageEntry>& aEntries) {
Packit f0b94e
  MOZ_ASSERT(XRE_IsContentProcess());
Packit f0b94e
Packit f0b94e
  // Make sure to initialize all DataStorage classes.
Packit f0b94e
  // For each one, we look through the list of our entries and if we find
Packit f0b94e
  // a matching DataStorage object, we initialize it.
Packit f0b94e
  //
Packit f0b94e
  // Note that this is an O(n^2) operation, but the n here is very small
Packit f0b94e
  // (currently 3).  There is a comment in the DataStorageList.h header
Packit f0b94e
  // about updating the algorithm here to something more fancy if the list
Packit f0b94e
  // of DataStorage items grows some day.
Packit f0b94e
  nsTArray<dom::DataStorageEntry> entries;
Packit f0b94e
#define DATA_STORAGE(_)                              \
Packit f0b94e
  {                                                  \
Packit f0b94e
    dom::DataStorageEntry entry;                     \
Packit f0b94e
    entry.filename() = NS_LITERAL_STRING(#_ ".txt"); \
Packit f0b94e
    for (auto& e : aEntries) {                       \
Packit f0b94e
      if (entry.filename().Equals(e.filename())) {   \
Packit f0b94e
        entry.items() = Move(e.items());             \
Packit f0b94e
        break;                                       \
Packit f0b94e
      }                                              \
Packit f0b94e
    }                                                \
Packit f0b94e
    entries.AppendElement(Move(entry));              \
Packit f0b94e
  }
Packit f0b94e
#include "mozilla/DataStorageList.h"
Packit f0b94e
#undef DATA_STORAGE
Packit f0b94e
Packit f0b94e
  for (auto& entry : entries) {
Packit f0b94e
    RefPtr<DataStorage> storage =
Packit f0b94e
        DataStorage::GetFromRawFileName(entry.filename());
Packit f0b94e
    bool dataWillPersist = false;
Packit f0b94e
    storage->Init(dataWillPersist, &entry.items());
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
size_t DataStorage::SizeOfIncludingThis(
Packit f0b94e
    mozilla::MallocSizeOf aMallocSizeOf) const {
Packit f0b94e
  size_t sizeOfExcludingThis =
Packit f0b94e
      mPersistentDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
Packit f0b94e
      mTemporaryDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
Packit f0b94e
      mPrivateDataTable.ShallowSizeOfExcludingThis(aMallocSizeOf) +
Packit f0b94e
      mFilename.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
Packit f0b94e
  return aMallocSizeOf(this) + sizeOfExcludingThis;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::Init(
Packit f0b94e
    bool& aDataWillPersist,
Packit f0b94e
    const InfallibleTArray<mozilla::dom::DataStorageItem>* aItems) {
Packit f0b94e
  // Don't access the observer service or preferences off the main thread.
Packit f0b94e
  if (!NS_IsMainThread()) {
Packit f0b94e
    MOZ_ASSERT_UNREACHABLE("DataStorage::Init called off main thread");
Packit f0b94e
    return NS_ERROR_NOT_SAME_THREAD;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  // Ignore attempts to initialize several times.
Packit f0b94e
  if (mInitCalled) {
Packit f0b94e
    return NS_OK;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  mInitCalled = true;
Packit f0b94e
Packit f0b94e
  static bool memoryReporterRegistered = false;
Packit f0b94e
  if (!memoryReporterRegistered) {
Packit f0b94e
    nsresult rv = RegisterStrongMemoryReporter(new DataStorageMemoryReporter());
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
    memoryReporterRegistered = true;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsresult rv;
Packit f0b94e
  if (XRE_IsParentProcess()) {
Packit f0b94e
    MOZ_ASSERT(!aItems);
Packit f0b94e
Packit f0b94e
    rv = NS_NewNamedThread("DataStorage", getter_AddRefs(mWorkerThread));
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    rv = AsyncReadData(aDataWillPersist, lock);
Packit f0b94e
    if (NS_FAILED(rv)) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
  } else {
Packit f0b94e
    // In the child process, we use the data passed to us by the parent process
Packit f0b94e
    // to initialize.
Packit f0b94e
    MOZ_ASSERT(XRE_IsContentProcess());
Packit f0b94e
    MOZ_ASSERT(aItems);
Packit f0b94e
Packit f0b94e
    aDataWillPersist = false;
Packit f0b94e
    for (auto& item : *aItems) {
Packit f0b94e
      Entry entry;
Packit f0b94e
      entry.mValue = item.value();
Packit f0b94e
      rv = PutInternal(item.key(), entry, item.type(), lock);
Packit f0b94e
      if (NS_FAILED(rv)) {
Packit f0b94e
        return rv;
Packit f0b94e
      }
Packit f0b94e
    }
Packit f0b94e
    mReady = true;
Packit f0b94e
    NotifyObservers("data-storage-ready");
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
Packit f0b94e
  if (NS_WARN_IF(!os)) {
Packit f0b94e
    return NS_ERROR_FAILURE;
Packit f0b94e
  }
Packit f0b94e
  // Clear private data as appropriate.
Packit f0b94e
  os->AddObserver(this, "last-pb-context-exited", false);
Packit f0b94e
  // Observe shutdown; save data and prevent any further writes.
Packit f0b94e
  // In the parent process, we need to write to the profile directory, so
Packit f0b94e
  // we should listen for profile-before-change so that we can safely
Packit f0b94e
  // write to the profile.  In the content process however we don't have
Packit f0b94e
  // access to the profile directory and profile notifications are not
Packit f0b94e
  // dispatched, so we need to clean up on xpcom-shutdown.
Packit f0b94e
  if (XRE_IsParentProcess()) {
Packit f0b94e
    os->AddObserver(this, "profile-before-change", false);
Packit f0b94e
  }
Packit f0b94e
  // In the Parent process, this is a backstop for xpcshell and other cases
Packit f0b94e
  // where profile-before-change might not get sent.
Packit f0b94e
  os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
Packit f0b94e
Packit f0b94e
  // For test purposes, we can set the write timer to be very fast.
Packit f0b94e
  mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
Packit f0b94e
                                    sDataStorageDefaultTimerDelay);
Packit f0b94e
  rv = Preferences::AddStrongObserver(this, "test.datastorage.write_timer_ms");
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
class DataStorage::Reader : public Runnable {
Packit f0b94e
 public:
Packit f0b94e
  explicit Reader(DataStorage* aDataStorage)
Packit f0b94e
      : Runnable("DataStorage::Reader"), mDataStorage(aDataStorage) {}
Packit f0b94e
  ~Reader();
Packit f0b94e
Packit f0b94e
 private:
Packit f0b94e
  NS_DECL_NSIRUNNABLE
Packit f0b94e
Packit f0b94e
  static nsresult ParseLine(nsDependentCSubstring& aLine, nsCString& aKeyOut,
Packit f0b94e
                            Entry& aEntryOut);
Packit f0b94e
Packit f0b94e
  RefPtr<DataStorage> mDataStorage;
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
DataStorage::Reader::~Reader() {
Packit f0b94e
  // Notify that calls to Get can proceed.
Packit f0b94e
  {
Packit f0b94e
    MonitorAutoLock readyLock(mDataStorage->mReadyMonitor);
Packit f0b94e
    mDataStorage->mReady = true;
Packit f0b94e
    nsresult rv = mDataStorage->mReadyMonitor.NotifyAll();
Packit f0b94e
    Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // This is for tests.
Packit f0b94e
  nsCOMPtr<nsIRunnable> job = NewRunnableMethod<const char*>(
Packit f0b94e
      "DataStorage::NotifyObservers", mDataStorage,
Packit f0b94e
      &DataStorage::NotifyObservers, "data-storage-ready");
Packit f0b94e
  nsresult rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
NS_IMETHODIMP
Packit f0b94e
DataStorage::Reader::Run() {
Packit f0b94e
  nsresult rv;
Packit f0b94e
  // Concurrent operations on nsIFile objects are not guaranteed to be safe,
Packit f0b94e
  // so we clone the file while holding the lock and then release the lock.
Packit f0b94e
  // At that point, we can safely operate on the clone.
Packit f0b94e
  nsCOMPtr<nsIFile> file;
Packit f0b94e
  {
Packit f0b94e
    MutexAutoLock lock(mDataStorage->mMutex);
Packit f0b94e
    // If we don't have a profile, bail.
Packit f0b94e
    if (!mDataStorage->mBackingFile) {
Packit f0b94e
      return NS_OK;
Packit f0b94e
    }
Packit f0b94e
    rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
  nsCOMPtr<nsIInputStream> fileInputStream;
Packit f0b94e
  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
Packit f0b94e
  // If we failed for some reason other than the file doesn't exist, bail.
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv) &&
Packit f0b94e
                 rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&  // on Unix
Packit f0b94e
                 rv != NS_ERROR_FILE_NOT_FOUND)) {             // on Windows
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // If there is a file with data in it, read it. If there isn't,
Packit f0b94e
  // we'll essentially fall through to notifying that we're good to go.
Packit f0b94e
  nsCString data;
Packit f0b94e
  if (fileInputStream) {
Packit f0b94e
    // Limit to 2MB of data, but only store sMaxDataEntries entries.
Packit f0b94e
    rv = NS_ConsumeStream(fileInputStream, 1u << 21, data);
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // Atomically parse the data and insert the entries read.
Packit f0b94e
  // Don't clear existing entries - they may have been inserted between when
Packit f0b94e
  // this read was kicked-off and when it was run.
Packit f0b94e
  {
Packit f0b94e
    MutexAutoLock lock(mDataStorage->mMutex);
Packit f0b94e
    // The backing file consists of a list of
Packit f0b94e
    //   <key>\t<score>\t<last accessed time>\t<value>\n
Packit f0b94e
    // The final \n is not optional; if it is not present the line is assumed
Packit f0b94e
    // to be corrupt.
Packit f0b94e
    int32_t currentIndex = 0;
Packit f0b94e
    int32_t newlineIndex = 0;
Packit f0b94e
    do {
Packit f0b94e
      newlineIndex = data.FindChar('\n', currentIndex);
Packit f0b94e
      // If there are no more newlines or the data table has too many
Packit f0b94e
      // entries, we are done.
Packit f0b94e
      if (newlineIndex < 0 ||
Packit f0b94e
          mDataStorage->mPersistentDataTable.Count() >= sMaxDataEntries) {
Packit f0b94e
        break;
Packit f0b94e
      }
Packit f0b94e
Packit f0b94e
      nsDependentCSubstring line(data, currentIndex,
Packit f0b94e
                                 newlineIndex - currentIndex);
Packit f0b94e
      currentIndex = newlineIndex + 1;
Packit f0b94e
      nsCString key;
Packit f0b94e
      Entry entry;
Packit f0b94e
      nsresult parseRV = ParseLine(line, key, entry);
Packit f0b94e
      if (NS_SUCCEEDED(parseRV)) {
Packit f0b94e
        // It could be the case that a newer entry was added before
Packit f0b94e
        // we got around to reading the file. Don't overwrite new entries.
Packit f0b94e
        Entry newerEntry;
Packit f0b94e
        bool present = mDataStorage->mPersistentDataTable.Get(key, &newerEntry);
Packit f0b94e
        if (!present) {
Packit f0b94e
          mDataStorage->mPersistentDataTable.Put(key, entry);
Packit f0b94e
        }
Packit f0b94e
      }
Packit f0b94e
    } while (true);
Packit f0b94e
Packit f0b94e
    Telemetry::Accumulate(Telemetry::DATA_STORAGE_ENTRIES,
Packit f0b94e
                          mDataStorage->mPersistentDataTable.Count());
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// The key must be a non-empty string containing no instances of '\t' or '\n',
Packit f0b94e
// and must have a length no more than 256.
Packit f0b94e
// The value must not contain '\n' and must have a length no more than 1024.
Packit f0b94e
// The length limits are to prevent unbounded memory and disk usage.
Packit f0b94e
/* static */
Packit f0b94e
nsresult DataStorage::ValidateKeyAndValue(const nsCString& aKey,
Packit f0b94e
                                          const nsCString& aValue) {
Packit f0b94e
  if (aKey.IsEmpty()) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
  if (aKey.Length() > 256) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
  int32_t delimiterIndex = aKey.FindChar('\t', 0);
Packit f0b94e
  if (delimiterIndex >= 0) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
  delimiterIndex = aKey.FindChar('\n', 0);
Packit f0b94e
  if (delimiterIndex >= 0) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
  delimiterIndex = aValue.FindChar('\n', 0);
Packit f0b94e
  if (delimiterIndex >= 0) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
  if (aValue.Length() > 1024) {
Packit f0b94e
    return NS_ERROR_INVALID_ARG;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// Each line is: <key>\t<score>\t<last accessed time>\t<value>
Packit f0b94e
// Where <score> is a uint32_t as a string, <last accessed time> is a
Packit f0b94e
// int32_t as a string, and the rest are strings.
Packit f0b94e
// <value> can contain anything but a newline.
Packit f0b94e
// Returns a successful status if the line can be decoded into a key and entry.
Packit f0b94e
// Otherwise, an error status is returned and the values assigned to the
Packit f0b94e
// output parameters are in an undefined state.
Packit f0b94e
/* static */
Packit f0b94e
nsresult DataStorage::Reader::ParseLine(nsDependentCSubstring& aLine,
Packit f0b94e
                                        nsCString& aKeyOut, Entry& aEntryOut) {
Packit f0b94e
  // First find the indices to each part of the line.
Packit f0b94e
  int32_t scoreIndex;
Packit f0b94e
  scoreIndex = aLine.FindChar('\t', 0) + 1;
Packit f0b94e
  if (scoreIndex <= 0) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
  int32_t accessedIndex = aLine.FindChar('\t', scoreIndex) + 1;
Packit f0b94e
  if (accessedIndex <= 0) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
  int32_t valueIndex = aLine.FindChar('\t', accessedIndex) + 1;
Packit f0b94e
  if (valueIndex <= 0) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // Now make substrings based on where each part is.
Packit f0b94e
  nsDependentCSubstring keyPart(aLine, 0, scoreIndex - 1);
Packit f0b94e
  nsDependentCSubstring scorePart(aLine, scoreIndex,
Packit f0b94e
                                  accessedIndex - scoreIndex - 1);
Packit f0b94e
  nsDependentCSubstring accessedPart(aLine, accessedIndex,
Packit f0b94e
                                     valueIndex - accessedIndex - 1);
Packit f0b94e
  nsDependentCSubstring valuePart(aLine, valueIndex);
Packit f0b94e
Packit f0b94e
  nsresult rv;
Packit f0b94e
  rv = DataStorage::ValidateKeyAndValue(nsCString(keyPart),
Packit f0b94e
                                        nsCString(valuePart));
Packit f0b94e
  if (NS_FAILED(rv)) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // Now attempt to decode the score part as a uint32_t.
Packit f0b94e
  // XXX nsDependentCSubstring doesn't support ToInteger
Packit f0b94e
  int32_t integer = nsCString(scorePart).ToInteger(&rv;;
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
  if (integer < 0) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
  aEntryOut.mScore = (uint32_t)integer;
Packit f0b94e
Packit f0b94e
  integer = nsCString(accessedPart).ToInteger(&rv;;
Packit f0b94e
  if (NS_FAILED(rv)) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
  if (integer < 0) {
Packit f0b94e
    return NS_ERROR_UNEXPECTED;
Packit f0b94e
  }
Packit f0b94e
  aEntryOut.mLastAccessed = integer;
Packit f0b94e
Packit f0b94e
  // Now set the key and value.
Packit f0b94e
  aKeyOut.Assign(keyPart);
Packit f0b94e
  aEntryOut.mValue.Assign(valuePart);
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::AsyncReadData(bool& aHaveProfileDir,
Packit f0b94e
                                    const MutexAutoLock& /*aProofOfLock*/) {
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
  aHaveProfileDir = false;
Packit f0b94e
  // Allocate a Reader so that even if it isn't dispatched,
Packit f0b94e
  // the data-storage-ready notification will be fired and Get
Packit f0b94e
  // will be able to proceed (this happens in its destructor).
Packit f0b94e
  RefPtr<Reader> job(new Reader(this));
Packit f0b94e
  nsresult rv;
Packit f0b94e
  // If we don't have a profile directory, this will fail.
Packit f0b94e
  // That's okay - it just means there is no persistent state.
Packit f0b94e
  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
Packit f0b94e
                              getter_AddRefs(mBackingFile));
Packit f0b94e
  if (NS_FAILED(rv)) {
Packit f0b94e
    mBackingFile = nullptr;
Packit f0b94e
    return NS_OK;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  rv = mBackingFile->Append(mFilename);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  aHaveProfileDir = true;
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::WaitForReady() {
Packit f0b94e
  MOZ_DIAGNOSTIC_ASSERT(mInitCalled, "Waiting before Init() has been called?");
Packit f0b94e
Packit f0b94e
  MonitorAutoLock readyLock(mReadyMonitor);
Packit f0b94e
  while (!mReady) {
Packit f0b94e
    nsresult rv = readyLock.Wait();
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      break;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
  MOZ_ASSERT(mReady);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsCString DataStorage::Get(const nsCString& aKey, DataStorageType aType) {
Packit f0b94e
  WaitForReady();
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  Entry entry;
Packit f0b94e
  bool foundValue = GetInternal(aKey, &entry, aType, lock);
Packit f0b94e
  if (!foundValue) {
Packit f0b94e
    return EmptyCString();
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // If we're here, we found a value. Maybe update its score.
Packit f0b94e
  if (entry.UpdateScore()) {
Packit f0b94e
    PutInternal(aKey, entry, aType, lock);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return entry.mValue;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
bool DataStorage::GetInternal(const nsCString& aKey, Entry* aEntry,
Packit f0b94e
                              DataStorageType aType,
Packit f0b94e
                              const MutexAutoLock& aProofOfLock) {
Packit f0b94e
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
Packit f0b94e
  bool foundValue = table.Get(aKey, aEntry);
Packit f0b94e
  return foundValue;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
DataStorage::DataStorageTable& DataStorage::GetTableForType(
Packit f0b94e
    DataStorageType aType, const MutexAutoLock& /*aProofOfLock*/) {
Packit f0b94e
  switch (aType) {
Packit f0b94e
    case DataStorage_Persistent:
Packit f0b94e
      return mPersistentDataTable;
Packit f0b94e
    case DataStorage_Temporary:
Packit f0b94e
      return mTemporaryDataTable;
Packit f0b94e
    case DataStorage_Private:
Packit f0b94e
      return mPrivateDataTable;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  MOZ_CRASH("given bad DataStorage storage type");
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::ReadAllFromTable(
Packit f0b94e
    DataStorageType aType, InfallibleTArray<dom::DataStorageItem>* aItems,
Packit f0b94e
    const MutexAutoLock& aProofOfLock) {
Packit f0b94e
  for (auto iter = GetTableForType(aType, aProofOfLock).Iter(); !iter.Done();
Packit f0b94e
       iter.Next()) {
Packit f0b94e
    DataStorageItem* item = aItems->AppendElement();
Packit f0b94e
    item->key() = iter.Key();
Packit f0b94e
    item->value() = iter.Data().mValue;
Packit f0b94e
    item->type() = aType;
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::GetAll(InfallibleTArray<dom::DataStorageItem>* aItems) {
Packit f0b94e
  WaitForReady();
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  aItems->SetCapacity(mPersistentDataTable.Count() +
Packit f0b94e
                      mTemporaryDataTable.Count() + mPrivateDataTable.Count());
Packit f0b94e
  ReadAllFromTable(DataStorage_Persistent, aItems, lock);
Packit f0b94e
  ReadAllFromTable(DataStorage_Temporary, aItems, lock);
Packit f0b94e
  ReadAllFromTable(DataStorage_Private, aItems, lock);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// Limit the number of entries per table. This is to prevent unbounded
Packit f0b94e
// resource use. The eviction strategy is as follows:
Packit f0b94e
// - An entry's score is incremented once for every day it is accessed.
Packit f0b94e
// - Evict an entry with score no more than any other entry in the table
Packit f0b94e
//   (this is the same as saying evict the entry with the lowest score,
Packit f0b94e
//    except for when there are multiple entries with the lowest score,
Packit f0b94e
//    in which case one of them is evicted - which one is not specified).
Packit f0b94e
void DataStorage::MaybeEvictOneEntry(DataStorageType aType,
Packit f0b94e
                                     const MutexAutoLock& aProofOfLock) {
Packit f0b94e
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
Packit f0b94e
  if (table.Count() >= sMaxDataEntries) {
Packit f0b94e
    KeyAndEntry toEvict;
Packit f0b94e
    // If all entries have score sMaxScore, this won't actually remove
Packit f0b94e
    // anything. This will never happen, however, because having that high
Packit f0b94e
    // a score either means someone tampered with the backing file or every
Packit f0b94e
    // entry has been accessed once a day for ~4 billion days.
Packit f0b94e
    // The worst that will happen is there will be 1025 entries in the
Packit f0b94e
    // persistent data table, with the 1025th entry being replaced every time
Packit f0b94e
    // data with a new key is inserted into the table. This is bad but
Packit f0b94e
    // ultimately not that concerning, considering that if an attacker can
Packit f0b94e
    // modify data in the profile, they can cause much worse harm.
Packit f0b94e
    toEvict.mEntry.mScore = sMaxScore;
Packit f0b94e
Packit f0b94e
    for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
Packit f0b94e
      Entry entry = iter.UserData();
Packit f0b94e
      if (entry.mScore < toEvict.mEntry.mScore) {
Packit f0b94e
        toEvict.mKey = iter.Key();
Packit f0b94e
        toEvict.mEntry = entry;
Packit f0b94e
      }
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    table.Remove(toEvict.mKey);
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
template <class Functor>
Packit f0b94e
static void RunOnAllContentParents(Functor func) {
Packit f0b94e
  if (!XRE_IsParentProcess()) {
Packit f0b94e
    return;
Packit f0b94e
  }
Packit f0b94e
  using dom::ContentParent;
Packit f0b94e
  nsTArray<ContentParent*> parents;
Packit f0b94e
  ContentParent::GetAll(parents);
Packit f0b94e
  for (auto& parent : parents) {
Packit f0b94e
    func(parent);
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::Put(const nsCString& aKey, const nsCString& aValue,
Packit f0b94e
                          DataStorageType aType) {
Packit f0b94e
  WaitForReady();
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  nsresult rv;
Packit f0b94e
  rv = ValidateKeyAndValue(aKey, aValue);
Packit f0b94e
  if (NS_FAILED(rv)) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  Entry entry;
Packit f0b94e
  bool exists = GetInternal(aKey, &entry, aType, lock);
Packit f0b94e
  if (exists) {
Packit f0b94e
    entry.UpdateScore();
Packit f0b94e
  } else {
Packit f0b94e
    MaybeEvictOneEntry(aType, lock);
Packit f0b94e
  }
Packit f0b94e
  entry.mValue = aValue;
Packit f0b94e
  rv = PutInternal(aKey, entry, aType, lock);
Packit f0b94e
  if (NS_FAILED(rv)) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  RunOnAllContentParents([&](dom::ContentParent* aParent) {
Packit f0b94e
    DataStorageItem item;
Packit f0b94e
    item.key() = aKey;
Packit f0b94e
    item.value() = aValue;
Packit f0b94e
    item.type() = aType;
Packit f0b94e
    Unused << aParent->SendDataStoragePut(mFilename, item);
Packit f0b94e
  });
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::PutInternal(const nsCString& aKey, Entry& aEntry,
Packit f0b94e
                                  DataStorageType aType,
Packit f0b94e
                                  const MutexAutoLock& aProofOfLock) {
Packit f0b94e
  DataStorageTable& table = GetTableForType(aType, aProofOfLock);
Packit f0b94e
  table.Put(aKey, aEntry);
Packit f0b94e
Packit f0b94e
  if (aType == DataStorage_Persistent && !mPendingWrite) {
Packit f0b94e
    return AsyncSetTimer(aProofOfLock);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::Remove(const nsCString& aKey, DataStorageType aType) {
Packit f0b94e
  WaitForReady();
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  DataStorageTable& table = GetTableForType(aType, lock);
Packit f0b94e
  table.Remove(aKey);
Packit f0b94e
Packit f0b94e
  if (aType == DataStorage_Persistent && !mPendingWrite) {
Packit f0b94e
    Unused << AsyncSetTimer(lock);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  RunOnAllContentParents([&](dom::ContentParent* aParent) {
Packit f0b94e
    Unused << aParent->SendDataStorageRemove(mFilename, aKey, aType);
Packit f0b94e
  });
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
class DataStorage::Writer : public Runnable {
Packit f0b94e
 public:
Packit f0b94e
  Writer(nsCString& aData, DataStorage* aDataStorage)
Packit f0b94e
      : Runnable("DataStorage::Writer"),
Packit f0b94e
        mData(aData),
Packit f0b94e
        mDataStorage(aDataStorage) {}
Packit f0b94e
Packit f0b94e
 private:
Packit f0b94e
  NS_DECL_NSIRUNNABLE
Packit f0b94e
Packit f0b94e
  nsCString mData;
Packit f0b94e
  RefPtr<DataStorage> mDataStorage;
Packit f0b94e
};
Packit f0b94e
Packit f0b94e
NS_IMETHODIMP
Packit f0b94e
DataStorage::Writer::Run() {
Packit f0b94e
  nsresult rv;
Packit f0b94e
  // Concurrent operations on nsIFile objects are not guaranteed to be safe,
Packit f0b94e
  // so we clone the file while holding the lock and then release the lock.
Packit f0b94e
  // At that point, we can safely operate on the clone.
Packit f0b94e
  nsCOMPtr<nsIFile> file;
Packit f0b94e
  {
Packit f0b94e
    MutexAutoLock lock(mDataStorage->mMutex);
Packit f0b94e
    // If we don't have a profile, bail.
Packit f0b94e
    if (!mDataStorage->mBackingFile) {
Packit f0b94e
      return NS_OK;
Packit f0b94e
    }
Packit f0b94e
    rv = mDataStorage->mBackingFile->Clone(getter_AddRefs(file));
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsCOMPtr<nsIOutputStream> outputStream;
Packit f0b94e
  rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
Packit f0b94e
                                   PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  const char* ptr = mData.get();
Packit f0b94e
  int32_t remaining = mData.Length();
Packit f0b94e
  uint32_t written = 0;
Packit f0b94e
  while (remaining > 0) {
Packit f0b94e
    rv = outputStream->Write(ptr, remaining, &written);
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
    remaining -= written;
Packit f0b94e
    ptr += written;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // Observed by tests.
Packit f0b94e
  nsCOMPtr<nsIRunnable> job = NewRunnableMethod<const char*>(
Packit f0b94e
      "DataStorage::NotifyObservers", mDataStorage,
Packit f0b94e
      &DataStorage::NotifyObservers, "data-storage-written");
Packit f0b94e
  rv = NS_DispatchToMainThread(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::AsyncWriteData(const MutexAutoLock& /*aProofOfLock*/) {
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
Packit f0b94e
  if (mShuttingDown || !mBackingFile) {
Packit f0b94e
    return NS_OK;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsCString output;
Packit f0b94e
  for (auto iter = mPersistentDataTable.Iter(); !iter.Done(); iter.Next()) {
Packit f0b94e
    Entry entry = iter.UserData();
Packit f0b94e
    output.Append(iter.Key());
Packit f0b94e
    output.Append('\t');
Packit f0b94e
    output.AppendInt(entry.mScore);
Packit f0b94e
    output.Append('\t');
Packit f0b94e
    output.AppendInt(entry.mLastAccessed);
Packit f0b94e
    output.Append('\t');
Packit f0b94e
    output.Append(entry.mValue);
Packit f0b94e
    output.Append('\n');
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  RefPtr<Writer> job(new Writer(output, this));
Packit f0b94e
  nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  mPendingWrite = false;
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::Clear() {
Packit f0b94e
  WaitForReady();
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
  mPersistentDataTable.Clear();
Packit f0b94e
  mTemporaryDataTable.Clear();
Packit f0b94e
  mPrivateDataTable.Clear();
Packit f0b94e
Packit f0b94e
  if (XRE_IsParentProcess()) {
Packit f0b94e
    // Asynchronously clear the file. This is similar to the permission manager
Packit f0b94e
    // in that it doesn't wait to synchronously remove the data from its backing
Packit f0b94e
    // storage either.
Packit f0b94e
    nsresult rv = AsyncWriteData(lock);
Packit f0b94e
    if (NS_FAILED(rv)) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  RunOnAllContentParents([&](dom::ContentParent* aParent) {
Packit f0b94e
    Unused << aParent->SendDataStorageClear(mFilename);
Packit f0b94e
  });
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
/* static */
Packit f0b94e
void DataStorage::TimerCallback(nsITimer* aTimer, void* aClosure) {
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
Packit f0b94e
  RefPtr<DataStorage> aDataStorage = (DataStorage*)aClosure;
Packit f0b94e
  MutexAutoLock lock(aDataStorage->mMutex);
Packit f0b94e
  Unused << aDataStorage->AsyncWriteData(lock);
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
// We only initialize the timer on the worker thread because it's not safe
Packit f0b94e
// to mix what threads are operating on the timer.
Packit f0b94e
nsresult DataStorage::AsyncSetTimer(const MutexAutoLock& /*aProofOfLock*/) {
Packit f0b94e
  if (mShuttingDown || !XRE_IsParentProcess()) {
Packit f0b94e
    return NS_OK;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  mPendingWrite = true;
Packit f0b94e
  nsCOMPtr<nsIRunnable> job =
Packit f0b94e
      NewRunnableMethod("DataStorage::SetTimer", this, &DataStorage::SetTimer);
Packit f0b94e
  nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::SetTimer() {
Packit f0b94e
  MOZ_ASSERT(!NS_IsMainThread());
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
Packit f0b94e
  nsresult rv;
Packit f0b94e
  if (!mTimer) {
Packit f0b94e
    mTimer = NS_NewTimer();
Packit f0b94e
    if (NS_WARN_IF(!mTimer)) {
Packit f0b94e
      return;
Packit f0b94e
    }
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  rv = mTimer->InitWithNamedFuncCallback(TimerCallback, this, mTimerDelay,
Packit f0b94e
                                         nsITimer::TYPE_ONE_SHOT,
Packit f0b94e
                                         "DataStorage::SetTimer");
Packit f0b94e
  Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::NotifyObservers(const char* aTopic) {
Packit f0b94e
  // Don't access the observer service off the main thread.
Packit f0b94e
  if (!NS_IsMainThread()) {
Packit f0b94e
    MOZ_ASSERT_UNREACHABLE(
Packit f0b94e
        "DataStorage::NotifyObservers called off main thread");
Packit f0b94e
    return;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
Packit f0b94e
  if (os) {
Packit f0b94e
    os->NotifyObservers(nullptr, aTopic, mFilename.get());
Packit f0b94e
  }
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
nsresult DataStorage::DispatchShutdownTimer(
Packit f0b94e
    const MutexAutoLock& /*aProofOfLock*/) {
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
Packit f0b94e
  nsCOMPtr<nsIRunnable> job = NewRunnableMethod(
Packit f0b94e
      "DataStorage::ShutdownTimer", this, &DataStorage::ShutdownTimer);
Packit f0b94e
  nsresult rv = mWorkerThread->Dispatch(job, NS_DISPATCH_NORMAL);
Packit f0b94e
  if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
    return rv;
Packit f0b94e
  }
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
void DataStorage::ShutdownTimer() {
Packit f0b94e
  MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
  MOZ_ASSERT(!NS_IsMainThread());
Packit f0b94e
  MutexAutoLock lock(mMutex);
Packit f0b94e
  nsresult rv = mTimer->Cancel();
Packit f0b94e
  Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
  mTimer = nullptr;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
//------------------------------------------------------------
Packit f0b94e
// DataStorage::nsIObserver
Packit f0b94e
//------------------------------------------------------------
Packit f0b94e
Packit f0b94e
NS_IMETHODIMP
Packit f0b94e
DataStorage::Observe(nsISupports* /*aSubject*/, const char* aTopic,
Packit f0b94e
                     const char16_t* /*aData*/) {
Packit f0b94e
  // Don't access preferences off the main thread.
Packit f0b94e
  if (!NS_IsMainThread()) {
Packit f0b94e
    MOZ_ASSERT_UNREACHABLE("DataStorage::Observe called off main thread");
Packit f0b94e
    return NS_ERROR_NOT_SAME_THREAD;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  nsresult rv;
Packit f0b94e
  if (strcmp(aTopic, "last-pb-context-exited") == 0) {
Packit f0b94e
    MutexAutoLock lock(mMutex);
Packit f0b94e
    mPrivateDataTable.Clear();
Packit f0b94e
  } else if (strcmp(aTopic, "profile-before-change") == 0 ||
Packit f0b94e
             (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0 &&
Packit f0b94e
              XRE_IsParentProcess())) {
Packit f0b94e
    MOZ_ASSERT(XRE_IsParentProcess());
Packit f0b94e
    // per bug 1271402, this should be safe to run multiple times
Packit f0b94e
    {
Packit f0b94e
      MutexAutoLock lock(mMutex);
Packit f0b94e
      rv = AsyncWriteData(lock);
Packit f0b94e
      mShuttingDown = true;
Packit f0b94e
      Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
      if (mTimer) {
Packit f0b94e
        rv = DispatchShutdownTimer(lock);
Packit f0b94e
        Unused << NS_WARN_IF(NS_FAILED(rv));
Packit f0b94e
      }
Packit f0b94e
    }
Packit f0b94e
    // Run the thread to completion and prevent any further events
Packit f0b94e
    // being scheduled to it. The thread may need the lock, so we can't
Packit f0b94e
    // hold it here.
Packit f0b94e
    rv = mWorkerThread->Shutdown();
Packit f0b94e
    if (NS_WARN_IF(NS_FAILED(rv))) {
Packit f0b94e
      return rv;
Packit f0b94e
    }
Packit f0b94e
Packit f0b94e
    sDataStorages->Clear();
Packit f0b94e
  } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
Packit f0b94e
    MOZ_ASSERT(!XRE_IsParentProcess());
Packit f0b94e
    sDataStorages->Clear();
Packit f0b94e
  } else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
Packit f0b94e
    MutexAutoLock lock(mMutex);
Packit f0b94e
    mTimerDelay = Preferences::GetInt("test.datastorage.write_timer_ms",
Packit f0b94e
                                      sDataStorageDefaultTimerDelay);
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  return NS_OK;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
DataStorage::Entry::Entry()
Packit f0b94e
    : mScore(0), mLastAccessed((int32_t)(PR_Now() / sOneDayInMicroseconds)) {}
Packit f0b94e
Packit f0b94e
// Updates this entry's score. Returns true if the score has actually changed.
Packit f0b94e
// If it's been less than a day since this entry has been accessed, the score
Packit f0b94e
// does not change. Otherwise, the score increases by 1.
Packit f0b94e
// The default score is 0. The maximum score is the maximum value that can
Packit f0b94e
// be represented by an unsigned 32 bit integer.
Packit f0b94e
// This is to handle evictions from our tables, which in turn is to prevent
Packit f0b94e
// unbounded resource use.
Packit f0b94e
bool DataStorage::Entry::UpdateScore() {
Packit f0b94e
  int32_t nowInDays = (int32_t)(PR_Now() / sOneDayInMicroseconds);
Packit f0b94e
  int32_t daysSinceAccessed = (nowInDays - mLastAccessed);
Packit f0b94e
Packit f0b94e
  // Update the last accessed time.
Packit f0b94e
  mLastAccessed = nowInDays;
Packit f0b94e
Packit f0b94e
  // If it's been less than a day since we've been accessed,
Packit f0b94e
  // the score isn't updated.
Packit f0b94e
  if (daysSinceAccessed < 1) {
Packit f0b94e
    return false;
Packit f0b94e
  }
Packit f0b94e
Packit f0b94e
  // Otherwise, increment the score (but don't overflow).
Packit f0b94e
  if (mScore < sMaxScore) {
Packit f0b94e
    mScore++;
Packit f0b94e
  }
Packit f0b94e
  return true;
Packit f0b94e
}
Packit f0b94e
Packit f0b94e
}  // namespace mozilla