diff options
Diffstat (limited to 'hal/HalWakeLock.cpp')
-rw-r--r-- | hal/HalWakeLock.cpp | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/hal/HalWakeLock.cpp b/hal/HalWakeLock.cpp new file mode 100644 index 000000000..af4dc454e --- /dev/null +++ b/hal/HalWakeLock.cpp @@ -0,0 +1,282 @@ +/* -*- 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 |