michael@0: /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/Hal.h" michael@0: #include "mozilla/HalWakeLock.h" michael@0: #include "mozilla/Services.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "mozilla/dom/ContentParent.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "nsIObserverService.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::hal; michael@0: michael@0: namespace { michael@0: michael@0: struct LockCount { michael@0: LockCount() michael@0: : numLocks(0) michael@0: , numHidden(0) michael@0: {} michael@0: uint32_t numLocks; michael@0: uint32_t numHidden; michael@0: nsTArray processes; michael@0: }; michael@0: michael@0: typedef nsDataHashtable ProcessLockTable; michael@0: typedef nsClassHashtable LockTable; michael@0: michael@0: int sActiveListeners = 0; michael@0: StaticAutoPtr sLockTable; michael@0: bool sInitialized = false; michael@0: bool sIsShuttingDown = false; michael@0: michael@0: WakeLockInformation michael@0: WakeLockInfoFromLockCount(const nsAString& aTopic, const LockCount& aLockCount) michael@0: { michael@0: // TODO: Once we abandon b2g18, we can switch this to use the michael@0: // WakeLockInformation constructor, which is better because it doesn't let us michael@0: // forget to assign a param. For now we have to do it this way, because michael@0: // b2g18 doesn't have the nsTArray <--> InfallibleTArray conversion (bug michael@0: // 819791). michael@0: michael@0: WakeLockInformation info; michael@0: info.topic() = aTopic; michael@0: info.numLocks() = aLockCount.numLocks; michael@0: info.numHidden() = aLockCount.numHidden; michael@0: info.lockingProcesses().AppendElements(aLockCount.processes); michael@0: return info; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: CountWakeLocks(const uint64_t& aKey, LockCount aCount, void* aUserArg) michael@0: { michael@0: MOZ_ASSERT(aUserArg); michael@0: michael@0: LockCount* totalCount = static_cast(aUserArg); michael@0: totalCount->numLocks += aCount.numLocks; michael@0: totalCount->numHidden += aCount.numHidden; michael@0: michael@0: // This is linear in the number of processes, but that should be small. michael@0: if (!totalCount->processes.Contains(aKey)) { michael@0: totalCount->processes.AppendElement(aKey); michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator michael@0: RemoveChildFromList(const nsAString& aKey, nsAutoPtr& aTable, michael@0: void* aUserArg) michael@0: { michael@0: MOZ_ASSERT(aUserArg); michael@0: michael@0: PLDHashOperator op = PL_DHASH_NEXT; michael@0: uint64_t childID = *static_cast(aUserArg); michael@0: if (aTable->Get(childID, nullptr)) { michael@0: aTable->Remove(childID); michael@0: michael@0: LockCount totalCount; michael@0: aTable->EnumerateRead(CountWakeLocks, &totalCount); michael@0: if (!totalCount.numLocks) { michael@0: op = PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: if (sActiveListeners) { michael@0: NotifyWakeLockChange(WakeLockInfoFromLockCount(aKey, totalCount)); michael@0: } michael@0: } michael@0: michael@0: return op; michael@0: } michael@0: michael@0: class ClearHashtableOnShutdown MOZ_FINAL : public nsIObserver { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ClearHashtableOnShutdown, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: ClearHashtableOnShutdown::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* data) michael@0: { michael@0: MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); michael@0: michael@0: sIsShuttingDown = true; michael@0: sLockTable = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class CleanupOnContentShutdown MOZ_FINAL : public nsIObserver { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(CleanupOnContentShutdown, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: CleanupOnContentShutdown::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* data) michael@0: { michael@0: MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown")); michael@0: michael@0: if (sIsShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr props = do_QueryInterface(aSubject); michael@0: if (!props) { michael@0: NS_WARNING("ipc:content-shutdown message without property bag as subject"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t childID = 0; michael@0: nsresult rv = props->GetPropertyAsUint64(NS_LITERAL_STRING("childID"), michael@0: &childID); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: sLockTable->Enumerate(RemoveChildFromList, &childID); michael@0: } else { michael@0: NS_WARNING("ipc:content-shutdown message without childID property"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Init() michael@0: { michael@0: sLockTable = new LockTable(); michael@0: sInitialized = true; michael@0: michael@0: nsCOMPtr obs = mozilla::services::GetObserverService(); michael@0: if (obs) { michael@0: obs->AddObserver(new ClearHashtableOnShutdown(), "xpcom-shutdown", false); michael@0: obs->AddObserver(new CleanupOnContentShutdown(), "ipc:content-shutdown", false); michael@0: } michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: michael@0: namespace hal { michael@0: michael@0: WakeLockState michael@0: ComputeWakeLockState(int aNumLocks, int aNumHidden) michael@0: { michael@0: if (aNumLocks == 0) { michael@0: return WAKE_LOCK_STATE_UNLOCKED; michael@0: } else if (aNumLocks == aNumHidden) { michael@0: return WAKE_LOCK_STATE_HIDDEN; michael@0: } else { michael@0: return WAKE_LOCK_STATE_VISIBLE; michael@0: } michael@0: } michael@0: michael@0: } // namespace hal michael@0: michael@0: namespace hal_impl { michael@0: michael@0: void michael@0: EnableWakeLockNotifications() michael@0: { michael@0: sActiveListeners++; michael@0: } michael@0: michael@0: void michael@0: DisableWakeLockNotifications() michael@0: { michael@0: sActiveListeners--; michael@0: } michael@0: michael@0: void michael@0: ModifyWakeLock(const nsAString& aTopic, michael@0: hal::WakeLockControl aLockAdjust, michael@0: hal::WakeLockControl aHiddenAdjust, michael@0: uint64_t aProcessID) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(aProcessID != CONTENT_PROCESS_ID_UNKNOWN); michael@0: michael@0: if (sIsShuttingDown) { michael@0: return; michael@0: } michael@0: if (!sInitialized) { michael@0: Init(); michael@0: } michael@0: michael@0: ProcessLockTable* table = sLockTable->Get(aTopic); michael@0: LockCount processCount; michael@0: LockCount totalCount; michael@0: if (!table) { michael@0: table = new ProcessLockTable(); michael@0: sLockTable->Put(aTopic, table); michael@0: } else { michael@0: table->Get(aProcessID, &processCount); michael@0: table->EnumerateRead(CountWakeLocks, &totalCount); michael@0: } michael@0: michael@0: MOZ_ASSERT(processCount.numLocks >= processCount.numHidden); michael@0: MOZ_ASSERT(aLockAdjust >= 0 || processCount.numLocks > 0); michael@0: MOZ_ASSERT(aHiddenAdjust >= 0 || processCount.numHidden > 0); michael@0: MOZ_ASSERT(totalCount.numLocks >= totalCount.numHidden); michael@0: MOZ_ASSERT(aLockAdjust >= 0 || totalCount.numLocks > 0); michael@0: MOZ_ASSERT(aHiddenAdjust >= 0 || totalCount.numHidden > 0); michael@0: michael@0: WakeLockState oldState = ComputeWakeLockState(totalCount.numLocks, totalCount.numHidden); michael@0: bool processWasLocked = processCount.numLocks > 0; michael@0: michael@0: processCount.numLocks += aLockAdjust; michael@0: processCount.numHidden += aHiddenAdjust; michael@0: michael@0: totalCount.numLocks += aLockAdjust; michael@0: totalCount.numHidden += aHiddenAdjust; michael@0: michael@0: if (processCount.numLocks) { michael@0: table->Put(aProcessID, processCount); michael@0: } else { michael@0: table->Remove(aProcessID); michael@0: } michael@0: if (!totalCount.numLocks) { michael@0: sLockTable->Remove(aTopic); michael@0: } michael@0: michael@0: if (sActiveListeners && michael@0: (oldState != ComputeWakeLockState(totalCount.numLocks, michael@0: totalCount.numHidden) || michael@0: processWasLocked != (processCount.numLocks > 0))) { michael@0: michael@0: WakeLockInformation info; michael@0: hal::GetWakeLockInfo(aTopic, &info); michael@0: NotifyWakeLockChange(info); michael@0: } michael@0: } michael@0: michael@0: void michael@0: GetWakeLockInfo(const nsAString& aTopic, WakeLockInformation* aWakeLockInfo) michael@0: { michael@0: if (sIsShuttingDown) { michael@0: NS_WARNING("You don't want to get wake lock information during xpcom-shutdown!"); michael@0: *aWakeLockInfo = WakeLockInformation(); michael@0: return; michael@0: } michael@0: if (!sInitialized) { michael@0: Init(); michael@0: } michael@0: michael@0: ProcessLockTable* table = sLockTable->Get(aTopic); michael@0: if (!table) { michael@0: *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, LockCount()); michael@0: return; michael@0: } michael@0: LockCount totalCount; michael@0: table->EnumerateRead(CountWakeLocks, &totalCount); michael@0: *aWakeLockInfo = WakeLockInfoFromLockCount(aTopic, totalCount); michael@0: } michael@0: michael@0: } // hal_impl michael@0: } // mozilla