tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HalWakeLock.cpp (7880B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=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/. */
      6 
      7 #include "Hal.h"
      8 #include "base/process_util.h"
      9 #include "mozilla/FOGIPC.h"
     10 #include "mozilla/HalWakeLock.h"
     11 #include "mozilla/Services.h"
     12 #include "mozilla/StaticPtr.h"
     13 #include "nsClassHashtable.h"
     14 #include "nsTHashMap.h"
     15 #include "nsHashKeys.h"
     16 #include "nsIPropertyBag2.h"
     17 #include "nsIObserver.h"
     18 #include "nsIObserverService.h"
     19 
     20 using namespace mozilla;
     21 using namespace mozilla::hal;
     22 
     23 namespace {
     24 
     25 struct LockCount {
     26  LockCount() : numLocks(0), numHidden(0) {}
     27  uint32_t numLocks;
     28  uint32_t numHidden;
     29  CopyableTArray<uint64_t> processes;
     30 };
     31 
     32 typedef nsTHashMap<nsUint64HashKey, LockCount> ProcessLockTable;
     33 typedef nsClassHashtable<nsStringHashKey, ProcessLockTable> LockTable;
     34 
     35 int sActiveListeners = 0;
     36 StaticAutoPtr<LockTable> sLockTable;
     37 bool sIsShuttingDown = false;
     38 
     39 WakeLockInformation WakeLockInfoFromLockCount(const nsAString& aTopic,
     40                                              const LockCount& aLockCount) {
     41  nsString topic(aTopic);
     42  WakeLockInformation info(topic, aLockCount.numLocks, aLockCount.numHidden,
     43                           aLockCount.processes);
     44 
     45  return info;
     46 }
     47 
     48 static void CountWakeLocks(ProcessLockTable* aTable, LockCount* aTotalCount) {
     49  for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) {
     50    const uint64_t& key = iter.Key();
     51    LockCount count = iter.UserData();
     52 
     53    aTotalCount->numLocks += count.numLocks;
     54    aTotalCount->numHidden += count.numHidden;
     55 
     56    // This is linear in the number of processes, but that should be small.
     57    if (!aTotalCount->processes.Contains(key)) {
     58      aTotalCount->processes.AppendElement(key);
     59    }
     60  }
     61 }
     62 
     63 class ClearHashtableOnShutdown final : public nsIObserver {
     64  ~ClearHashtableOnShutdown() {}
     65 
     66 public:
     67  NS_DECL_ISUPPORTS
     68  NS_DECL_NSIOBSERVER
     69 };
     70 
     71 NS_IMPL_ISUPPORTS(ClearHashtableOnShutdown, nsIObserver)
     72 
     73 NS_IMETHODIMP
     74 ClearHashtableOnShutdown::Observe(nsISupports* aSubject, const char* aTopic,
     75                                  const char16_t* data) {
     76  MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown"));
     77 
     78  sIsShuttingDown = true;
     79  sLockTable = nullptr;
     80 
     81  return NS_OK;
     82 }
     83 
     84 class CleanupOnContentShutdown final : public nsIObserver {
     85  ~CleanupOnContentShutdown() {}
     86 
     87 public:
     88  NS_DECL_ISUPPORTS
     89  NS_DECL_NSIOBSERVER
     90 };
     91 
     92 NS_IMPL_ISUPPORTS(CleanupOnContentShutdown, nsIObserver)
     93 
     94 NS_IMETHODIMP
     95 CleanupOnContentShutdown::Observe(nsISupports* aSubject, const char* aTopic,
     96                                  const char16_t* data) {
     97  MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown"));
     98 
     99  if (sIsShuttingDown) {
    100    return NS_OK;
    101  }
    102 
    103  nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject);
    104  if (!props) {
    105    NS_WARNING("ipc:content-shutdown message without property bag as subject");
    106    return NS_OK;
    107  }
    108 
    109  uint64_t childID = 0;
    110  nsresult rv = props->GetPropertyAsUint64(u"childID"_ns, &childID);
    111  if (NS_SUCCEEDED(rv)) {
    112    for (auto iter = sLockTable->Iter(); !iter.Done(); iter.Next()) {
    113      auto table = iter.UserData();
    114 
    115      if (table->Get(childID, nullptr)) {
    116        table->Remove(childID);
    117 
    118        LockCount totalCount;
    119        CountWakeLocks(table, &totalCount);
    120 
    121        if (sActiveListeners) {
    122          NotifyWakeLockChange(
    123              WakeLockInfoFromLockCount(iter.Key(), totalCount));
    124        }
    125 
    126        if (totalCount.numLocks == 0) {
    127          iter.Remove();
    128        }
    129      }
    130    }
    131  } else {
    132    NS_WARNING("ipc:content-shutdown message without childID property");
    133  }
    134  return NS_OK;
    135 }
    136 
    137 }  // namespace
    138 
    139 namespace mozilla {
    140 
    141 namespace hal {
    142 
    143 void WakeLockInit() {
    144  sLockTable = new LockTable();
    145 
    146  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
    147  if (obs) {
    148    obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false);
    149    obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown",
    150                     false);
    151  }
    152 }
    153 
    154 WakeLockState ComputeWakeLockState(int aNumLocks, int aNumHidden) {
    155  if (aNumLocks == 0) {
    156    return WAKE_LOCK_STATE_UNLOCKED;
    157  } else if (aNumLocks == aNumHidden) {
    158    return WAKE_LOCK_STATE_HIDDEN;
    159  } else {
    160    return WAKE_LOCK_STATE_VISIBLE;
    161  }
    162 }
    163 
    164 }  // namespace hal
    165 
    166 namespace hal_impl {
    167 
    168 void EnableWakeLockNotifications() { sActiveListeners++; }
    169 
    170 void DisableWakeLockNotifications() { sActiveListeners--; }
    171 
    172 void ModifyWakeLockWithChildID(const nsAString& aTopic,
    173                               hal::WakeLockControl aLockAdjust,
    174                               hal::WakeLockControl aHiddenAdjust,
    175                               uint64_t aChildID) {
    176  MOZ_ASSERT(NS_IsMainThread());
    177  MOZ_ASSERT(aChildID != CONTENT_PROCESS_ID_UNKNOWN);
    178 
    179  if (sIsShuttingDown) {
    180    return;
    181  }
    182 
    183  LockCount processCount;
    184  LockCount totalCount;
    185  ProcessLockTable* const table =
    186      sLockTable->WithEntryHandle(aTopic, [&](auto&& entry) {
    187        if (!entry) {
    188          entry.Insert(MakeUnique<ProcessLockTable>());
    189        } else {
    190          (void)entry.Data()->Get(aChildID, &processCount);
    191          CountWakeLocks(entry->get(), &totalCount);
    192        }
    193        return entry->get();
    194      });
    195 
    196  MOZ_ASSERT(processCount.numLocks >= processCount.numHidden);
    197  MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0);
    198  MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0);
    199  MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden);
    200  MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0);
    201  MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0);
    202 
    203  WakeLockState oldState =
    204      ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden);
    205 
    206  if (ComputeWakeLockState(totalCount.numLocks + aLockAdjust,
    207                           totalCount.numHidden + aHiddenAdjust) != oldState &&
    208      (aTopic.Equals(u"video-playing"_ns) ||
    209       aTopic.Equals(u"audio-playing"_ns))) {
    210    glean::RecordPowerMetrics();
    211  }
    212 
    213  bool processWasLocked = processCount.numLocks > 0;
    214 
    215  processCount.numLocks += aLockAdjust;
    216  processCount.numHidden += aHiddenAdjust;
    217 
    218  totalCount.numLocks += aLockAdjust;
    219  totalCount.numHidden += aHiddenAdjust;
    220 
    221  if (processCount.numLocks) {
    222    table->InsertOrUpdate(aChildID, processCount);
    223  } else {
    224    table->Remove(aChildID);
    225  }
    226  if (!totalCount.numLocks) {
    227    sLockTable->Remove(aTopic);
    228  }
    229 
    230  if (sActiveListeners &&
    231      (oldState !=
    232           ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden) ||
    233       processWasLocked != (processCount.numLocks > 0))) {
    234    WakeLockInformation info;
    235    hal::GetWakeLockInfo(aTopic, &info);
    236    NotifyWakeLockChange(info);
    237  }
    238 }
    239 
    240 void ModifyWakeLock(const nsAString& aTopic, hal::WakeLockControl aLockAdjust,
    241                    hal::WakeLockControl aHiddenAdjust) {
    242  ModifyWakeLockWithChildID(aTopic, aLockAdjust, aHiddenAdjust,
    243                            CONTENT_PROCESS_ID_MAIN);
    244 }
    245 
    246 void GetWakeLockInfo(const nsAString& aTopic,
    247                     WakeLockInformation* aWakeLockInfo) {
    248  if (sIsShuttingDown) {
    249    NS_WARNING(
    250        "You don't want to get wake lock information during xpcom-shutdown!");
    251    *aWakeLockInfo = WakeLockInformation();
    252    return;
    253  }
    254 
    255  if (!sLockTable) {
    256    // This can happen during some gtests.
    257    NS_WARNING("Attempting to get wake lock information before initialization");
    258    *aWakeLockInfo = WakeLockInformation();
    259    return;
    260  }
    261 
    262  ProcessLockTable* table = sLockTable->Get(aTopic);
    263  if (!table) {
    264    *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount());
    265    return;
    266  }
    267  LockCount totalCount;
    268  CountWakeLocks(table, &totalCount);
    269  *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount);
    270 }
    271 
    272 }  // namespace hal_impl
    273 }  // namespace mozilla