hal/HalWakeLock.cpp
author Jonas Finnemann Jensen <jopsen@gmail.com>
Thu, 15 Dec 2016 17:16:14 -0800
changeset 326546 4bd7d95379f640887531f12ecacec9c137c39ac0
parent 301406 232a786cf87fb0f68f9bda6e9316515524d2ac8a
child 434930 1616abd4c12f3d5ca793c0d332b1738e93803ddd
permissions -rw-r--r--
Bug 1324413 - Lock image content hash for decision task; r=dustin This will make harder to falsify a decision task. Notably our validation code only needs to verify that the definition of the decision task as given here matches what is used in the task definition in the Chain-Of-Trust artifact, in order to prove that the decision task is a result of what ran in the tree. MozReview-Commit-ID: 4SRO7G1nyyL

/* -*- 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 "Hal.h"
#include "mozilla/HalWakeLock.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/ContentParent.h"
#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsHashKeys.h"
#include "nsIPropertyBag2.h"
#include "nsIObserverService.h"

using namespace mozilla;
using namespace mozilla::hal;

namespace {

struct LockCount {
  LockCount()
    : numLocks(0)
    , numHidden(0)
  {}
  uint32_t numLocks;
  uint32_t numHidden;
  nsTArray<uint64_t> processes;
};

typedef nsDataHashtable<nsUint64HashKey, LockCount> ProcessLockTable;
typedef nsClassHashtable<nsStringHashKey, ProcessLockTable> LockTable;

int sActiveListeners = 0;
StaticAutoPtr<LockTable> sLockTable;
bool sInitialized = false;
bool sIsShuttingDown = false;

WakeLockInformation
WakeLockInfoFromLockCount(const nsAString& aTopic, const LockCount& aLockCount)
{
  // TODO: Once we abandon b2g18, we can switch this to use the
  // WakeLockInformation constructor, which is better because it doesn't let us
  // forget to assign a param.  For now we have to do it this way, because
  // b2g18 doesn't have the nsTArray <--> InfallibleTArray conversion (bug
  // 819791).

  WakeLockInformation info;
  info.topic() = aTopic;
  info.numLocks() = aLockCount.numLocks;
  info.numHidden() = aLockCount.numHidden;
  info.lockingProcesses().AppendElements(aLockCount.processes);
  return info;
}

static void
CountWakeLocks(ProcessLockTable* aTable, LockCount* aTotalCount)
{
  for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
    const uint64_t& key = iter.Key();
    LockCount count = iter.UserData();

    aTotalCount->numLocks += count.numLocks;
    aTotalCount->numHidden += count.numHidden;

    // This is linear in the number of processes, but that should be small.
    if (!aTotalCount->processes.Contains(key)) {
      aTotalCount->processes.AppendElement(key);
    }
  }
}

class ClearHashtableOnShutdown final : public nsIObserver {
  ~ClearHashtableOnShutdown() {}
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
};

NS_IMPL_ISUPPORTS(ClearHashtableOnShutdown, nsIObserver)

NS_IMETHODIMP
ClearHashtableOnShutdown::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* data)
{
  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));

  sIsShuttingDown = true;
  sLockTable = nullptr;

  return NS_OK;
}

class CleanupOnContentShutdown final : public nsIObserver {
  ~CleanupOnContentShutdown() {}
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIOBSERVER
};

NS_IMPL_ISUPPORTS(CleanupOnContentShutdown, nsIObserver)

NS_IMETHODIMP
CleanupOnContentShutdown::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* data)
{
  MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));

  if (sIsShuttingDown) {
    return NS_OK;
  }

  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
  if (!props) {
    NS_WARNING("ipc:content-shutdown message without property bag as subject");
    return NS_OK;
  }

  uint64_t childID = 0;
  nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"),
                                           &childID);
  if (NS_SUCCEEDED(rv)) {
    for (auto iter = sLockTable->Iter(); !iter.Done(); iter.Next()) {
      nsAutoPtr<ProcessLockTable>& table = iter.Data();

      if (table->Get(childID, nullptr)) {
        table->Remove(childID);

        LockCount totalCount;
        CountWakeLocks(table, &totalCount);

        if (sActiveListeners) {
          NotifyWakeLockChange(WakeLockInfoFromLockCount(iter.Key(),
                                                         totalCount));
        }

        if (totalCount.numLocks == 0) {
          iter.Remove();
        }
      }
    }
  } else {
    NS_WARNING("ipc:content-shutdown message without childID property");
  }
  return NS_OK;
}

void
Init()
{
  sLockTable = new LockTable();
  sInitialized = true;

  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
  if (obs) {
    obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false);
    obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown", false);
  }
}

} // namespace

namespace mozilla {

namespace hal {

WakeLockState
ComputeWakeLockState(int aNumLocks, int aNumHidden)
{
  if (aNumLocks == 0) {
    return WAKE_LOCK_STATE_UNLOCKED;
  } else if (aNumLocks == aNumHidden) {
    return WAKE_LOCK_STATE_HIDDEN;
  } else {
    return WAKE_LOCK_STATE_VISIBLE;
  }
}

} // namespace hal

namespace hal_impl {

void
EnableWakeLockNotifications()
{
  sActiveListeners++;
}

void
DisableWakeLockNotifications()
{
  sActiveListeners--;
}

void
ModifyWakeLock(const nsAString& aTopic,
               hal::WakeLockControl aLockAdjust,
               hal::WakeLockControl aHiddenAdjust,
               uint64_t aProcessID)
{
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aProcessID != CONTENT_PROCESS_ID_UNKNOWN);

  if (sIsShuttingDown) {
    return;
  }
  if (!sInitialized) {
    Init();
  }

  ProcessLockTable* table = sLockTable->Get(aTopic);
  LockCount processCount;
  LockCount totalCount;
  if (!table) {
    table = new ProcessLockTable();
    sLockTable->Put(aTopic, table);
  } else {
    table->Get(aProcessID, &processCount);
    CountWakeLocks(table, &totalCount);
  }

  MOZ_ASSERT(processCount.numLocks >= processCount.numHidden);
  MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0);
  MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0);
  MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden);
  MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0);
  MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0);

  WakeLockState oldState = ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden);
  bool processWasLocked = processCount.numLocks > 0;

  processCount.numLocks += aLockAdjust;
  processCount.numHidden += aHiddenAdjust;

  totalCount.numLocks += aLockAdjust;
  totalCount.numHidden += aHiddenAdjust;

  if (processCount.numLocks) {
    table->Put(aProcessID, processCount);
  } else {
    table->Remove(aProcessID);
  }
  if (!totalCount.numLocks) {
    sLockTable->Remove(aTopic);
  }

  if (sActiveListeners &&
      (oldState != ComputeWakeLockState(totalCount.numLocks,
                                        totalCount.numHidden) ||
       processWasLocked != (processCount.numLocks > 0))) {

    WakeLockInformation info;
    hal::GetWakeLockInfo(aTopic, &info);
    NotifyWakeLockChange(info);
  }
}

void
GetWakeLockInfo(const nsAString& aTopic, WakeLockInformation* aWakeLockInfo)
{
  if (sIsShuttingDown) {
    NS_WARNING("You don't want to get wake lock information during xpcom-shutdown!");
    *aWakeLockInfo = WakeLockInformation();
    return;
  }
  if (!sInitialized) {
    Init();
  }

  ProcessLockTable* table = sLockTable->Get(aTopic);
  if (!table) {
    *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount());
    return;
  }
  LockCount totalCount;
  CountWakeLocks(table, &totalCount);
  *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount);
}

} // namespace hal_impl
} // namespace mozilla