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