dom/storage/StorageActivityService.cpp
author Jared Wein <jwein@mozilla.com>
Mon, 24 Dec 2018 16:19:20 +0000
changeset 511855 28805dd1b97ab5b421d9e886fd5d96460d5d26ab
parent 508163 6f3709b3878117466168c40affa7bca0b60cf75b
child 522767 f41cee9bf14931b453838d1bbcbda528e3b064e8
permissions -rw-r--r--
Bug 1507595 - Convert about:support to Fluent. r=Gijs,flod Initial patch by Collin Wing (masterkrombi@gmail.com). Differential Revision: https://phabricator.services.mozilla.com/D14905

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "StorageActivityService.h"

#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/StaticPtr.h"
#include "nsIMutableArray.h"
#include "nsSupportsPrimitives.h"
#include "nsXPCOM.h"

// This const is used to know when origin activities should be purged because
// too old. This value should be in sync with what the UI needs.
#define TIME_MAX_SECS 86400 /* 24 hours */

namespace mozilla {
namespace dom {

static StaticRefPtr<StorageActivityService> gStorageActivityService;
static bool gStorageActivityShutdown = false;

/* static */ void StorageActivityService::SendActivity(
    nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(NS_IsMainThread());

  if (!aPrincipal || BasePrincipal::Cast(aPrincipal)->Kind() !=
                         BasePrincipal::eCodebasePrincipal) {
    // Only codebase principals.
    return;
  }

  RefPtr<StorageActivityService> service = GetOrCreate();
  if (NS_WARN_IF(!service)) {
    return;
  }

  service->SendActivityInternal(aPrincipal);
}

/* static */ void StorageActivityService::SendActivity(
    const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
  if (aPrincipalInfo.type() !=
      mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
    // only content principal.
    return;
  }

  RefPtr<Runnable> r = NS_NewRunnableFunction(
      "StorageActivityService::SendActivity", [aPrincipalInfo]() {
        MOZ_ASSERT(NS_IsMainThread());

        nsCOMPtr<nsIPrincipal> principal =
            mozilla::ipc::PrincipalInfoToPrincipal(aPrincipalInfo);

        StorageActivityService::SendActivity(principal);
      });

  SystemGroup::Dispatch(TaskCategory::Other, r.forget());
}

/* static */ void StorageActivityService::SendActivity(
    const nsACString& aOrigin) {
  MOZ_ASSERT(XRE_IsParentProcess());

  nsCString origin;
  origin.Assign(aOrigin);

  RefPtr<Runnable> r = NS_NewRunnableFunction(
      "StorageActivityService::SendActivity", [origin]() {
        MOZ_ASSERT(NS_IsMainThread());

        RefPtr<StorageActivityService> service = GetOrCreate();
        if (NS_WARN_IF(!service)) {
          return;
        }

        service->SendActivityInternal(origin);
      });

  if (NS_IsMainThread()) {
    Unused << r->Run();
  } else {
    SystemGroup::Dispatch(TaskCategory::Other, r.forget());
  }
}

/* static */ already_AddRefed<StorageActivityService>
StorageActivityService::GetOrCreate() {
  MOZ_ASSERT(NS_IsMainThread());

  if (!gStorageActivityService && !gStorageActivityShutdown) {
    RefPtr<StorageActivityService> service = new StorageActivityService();

    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    if (NS_WARN_IF(!obs)) {
      return nullptr;
    }

    nsresult rv =
        obs->AddObserver(service, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return nullptr;
    }

    gStorageActivityService = service;
  }

  RefPtr<StorageActivityService> service = gStorageActivityService;
  return service.forget();
}

StorageActivityService::StorageActivityService() {
  MOZ_ASSERT(NS_IsMainThread());
}

StorageActivityService::~StorageActivityService() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!mTimer);
}

void StorageActivityService::SendActivityInternal(nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aPrincipal);
  MOZ_ASSERT(BasePrincipal::Cast(aPrincipal)->Kind() ==
             BasePrincipal::eCodebasePrincipal);

  if (!XRE_IsParentProcess()) {
    SendActivityToParent(aPrincipal);
    return;
  }

  nsAutoCString origin;
  nsresult rv = aPrincipal->GetOrigin(origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  SendActivityInternal(origin);
}

void StorageActivityService::SendActivityInternal(const nsACString& aOrigin) {
  MOZ_ASSERT(XRE_IsParentProcess());

  mActivities.Put(aOrigin, PR_Now());
  MaybeStartTimer();
}

void StorageActivityService::SendActivityToParent(nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!XRE_IsParentProcess());

  PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
  if (NS_WARN_IF(!actor)) {
    return;
  }

  mozilla::ipc::PrincipalInfo principalInfo;
  nsresult rv =
      mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  actor->SendStorageActivity(principalInfo);
}

NS_IMETHODIMP
StorageActivityService::Observe(nsISupports* aSubject, const char* aTopic,
                                const char16_t* aData) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));

  MaybeStopTimer();

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
  }

  gStorageActivityShutdown = true;
  gStorageActivityService = nullptr;
  return NS_OK;
}

void StorageActivityService::MaybeStartTimer() {
  MOZ_ASSERT(NS_IsMainThread());

  if (!mTimer) {
    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
    mTimer->InitWithCallback(this, 1000 * 5 * 60 /* any 5 minutes */,
                             nsITimer::TYPE_REPEATING_SLACK);
  }
}

void StorageActivityService::MaybeStopTimer() {
  MOZ_ASSERT(NS_IsMainThread());

  if (mTimer) {
    mTimer->Cancel();
    mTimer = nullptr;
  }
}

NS_IMETHODIMP
StorageActivityService::Notify(nsITimer* aTimer) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mTimer == aTimer);

  uint64_t now = PR_Now();

  for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
    if ((now - iter.UserData()) / PR_USEC_PER_SEC > TIME_MAX_SECS) {
      iter.Remove();
    }
  }

  // If no activities, let's stop the timer.
  if (mActivities.Count() == 0) {
    MaybeStopTimer();
  }

  return NS_OK;
}

NS_IMETHODIMP
StorageActivityService::GetActiveOrigins(PRTime aFrom, PRTime aTo,
                                         nsIArray** aRetval) {
  uint64_t now = PR_Now();
  if (((now - aFrom) / PR_USEC_PER_SEC) > TIME_MAX_SECS || aFrom >= aTo) {
    return NS_ERROR_RANGE_ERR;
  }

  nsresult rv = NS_OK;
  nsCOMPtr<nsIMutableArray> devices =
      do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
    if (iter.UserData() >= aFrom && iter.UserData() <= aTo) {
      RefPtr<BasePrincipal> principal =
          BasePrincipal::CreateCodebasePrincipal(iter.Key());
      MOZ_ASSERT(principal);

      rv = devices->AppendElement(principal);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  devices.forget(aRetval);
  return NS_OK;
}

NS_IMETHODIMP
StorageActivityService::MoveOriginInTime(nsIPrincipal* aPrincipal,
                                         PRTime aWhen) {
  if (!XRE_IsParentProcess()) {
    return NS_ERROR_FAILURE;
  }

  nsAutoCString origin;
  nsresult rv = aPrincipal->GetOrigin(origin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  mActivities.Put(origin, aWhen / PR_USEC_PER_SEC);
  return NS_OK;
}

NS_IMETHODIMP
StorageActivityService::TestOnlyReset() {
  mActivities.Clear();
  return NS_OK;
}

NS_INTERFACE_MAP_BEGIN(StorageActivityService)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStorageActivityService)
  NS_INTERFACE_MAP_ENTRY(nsIStorageActivityService)
  NS_INTERFACE_MAP_ENTRY(nsIObserver)
  NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_END

NS_IMPL_ADDREF(StorageActivityService)
NS_IMPL_RELEASE(StorageActivityService)

}  // namespace dom
}  // namespace mozilla