/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et ft=cpp : */
/* 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 "HalImpl.h"
#include "HalLog.h"
#include "HalSandbox.h"
#include "nsIDOMDocument.h"
#include "nsIDOMWindow.h"
#include "nsIDocument.h"
#include "nsIDocShell.h"
#include "nsITabChild.h"
#include "nsIWebNavigation.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "nsPIDOMWindow.h"
#include "nsJSUtils.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Observer.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ScreenOrientation.h"
#include "WindowIdentifier.h"
#ifdef XP_WIN
#include <process.h>
#define getpid _getpid
#endif
using namespace mozilla::services;
using namespace mozilla::dom;
#define PROXY_IF_SANDBOXED(_call) \
do { \
if (InSandbox()) { \
if (!hal_sandbox::HalChildDestroyed()) { \
hal_sandbox::_call; \
} \
} else { \
hal_impl::_call; \
} \
} while (0)
#define RETURN_PROXY_IF_SANDBOXED(_call, defValue) \
do { \
if (InSandbox()) { \
if (hal_sandbox::HalChildDestroyed()) { \
return defValue; \
} \
return hal_sandbox::_call; \
} else { \
return hal_impl::_call; \
} \
} while (0)
namespace mozilla {
namespace hal {
mozilla::LogModule* GetHalLog() {
static mozilla::LazyLogModule sHalLog("hal");
return sHalLog;
}
namespace {
void AssertMainThread() { MOZ_ASSERT(NS_IsMainThread()); }
bool InSandbox() { return GeckoProcessType_Content == XRE_GetProcessType(); }
void AssertMainProcess() {
MOZ_ASSERT(GeckoProcessType_Default == XRE_GetProcessType());
}
bool WindowIsActive(nsPIDOMWindowInner* aWindow) {
nsIDocument* document = aWindow->GetDoc();
NS_ENSURE_TRUE(document, false);
return !document->Hidden();
}
StaticAutoPtr<WindowIdentifier::IDArrayType> gLastIDToVibrate;
void InitLastIDToVibrate() {
gLastIDToVibrate = new WindowIdentifier::IDArrayType();
ClearOnShutdown(&gLastIDToVibrate);
}
} // namespace
void Vibrate(const nsTArray<uint32_t>& pattern, nsPIDOMWindowInner* window) {
Vibrate(pattern, WindowIdentifier(window));
}
void Vibrate(const nsTArray<uint32_t>& pattern, const WindowIdentifier& id) {
AssertMainThread();
// Only active windows may start vibrations. If |id| hasn't gone
// through the IPC layer -- that is, if our caller is the outside
// world, not hal_proxy -- check whether the window is active. If
// |id| has gone through IPC, don't check the window's visibility;
// only the window corresponding to the bottommost process has its
// visibility state set correctly.
if (!id.HasTraveledThroughIPC() && !WindowIsActive(id.GetWindow())) {
HAL_LOG("Vibrate: Window is inactive, dropping vibrate.");
return;
}
if (!InSandbox()) {
if (!gLastIDToVibrate) {
InitLastIDToVibrate();
}
*gLastIDToVibrate = id.AsArray();
}
// Don't forward our ID if we are not in the sandbox, because hal_impl
// doesn't need it, and we don't want it to be tempted to read it. The
// empty identifier will assert if it's used.
PROXY_IF_SANDBOXED(Vibrate(pattern, InSandbox() ? id : WindowIdentifier()));
}
void CancelVibrate(nsPIDOMWindowInner* window) {
CancelVibrate(WindowIdentifier(window));
}
void CancelVibrate(const WindowIdentifier& id) {
AssertMainThread();
// Although only active windows may start vibrations, a window may
// cancel its own vibration even if it's no longer active.
//
// After a window is marked as inactive, it sends a CancelVibrate
// request. We want this request to cancel a playing vibration
// started by that window, so we certainly don't want to reject the
// cancellation request because the window is now inactive.
//
// But it could be the case that, after this window became inactive,
// some other window came along and started a vibration. We don't
// want this window's cancellation request to cancel that window's
// actively-playing vibration!
//
// To solve this problem, we keep track of the id of the last window
// to start a vibration, and only accepts cancellation requests from
// the same window. All other cancellation requests are ignored.
if (InSandbox() || (gLastIDToVibrate && *gLastIDToVibrate == id.AsArray())) {
// Don't forward our ID if we are not in the sandbox, because hal_impl
// doesn't need it, and we don't want it to be tempted to read it. The
// empty identifier will assert if it's used.
PROXY_IF_SANDBOXED(CancelVibrate(InSandbox() ? id : WindowIdentifier()));
}
}
template <class InfoType>
class ObserversManager {
public:
void AddObserver(Observer<InfoType>* aObserver) {
if (!mObservers) {
mObservers = new mozilla::ObserverList<InfoType>();
}
mObservers->AddObserver(aObserver);
if (mObservers->Length() == 1) {
EnableNotifications();
}
}
void RemoveObserver(Observer<InfoType>* aObserver) {
bool removed = mObservers && mObservers->RemoveObserver(aObserver);
if (!removed) {
return;
}
if (mObservers->Length() == 0) {
DisableNotifications();
OnNotificationsDisabled();
delete mObservers;
mObservers = nullptr;
}
}
void BroadcastInformation(const InfoType& aInfo) {
// It is possible for mObservers to be nullptr here on some platforms,
// because a call to BroadcastInformation gets queued up asynchronously
// while RemoveObserver is running (and before the notifications are
// disabled). The queued call can then get run after mObservers has
// been nulled out. See bug 757025.
if (!mObservers) {
return;
}
mObservers->Broadcast(aInfo);
}
protected:
virtual void EnableNotifications() = 0;
virtual void DisableNotifications() = 0;
virtual void OnNotificationsDisabled() {}
private:
mozilla::ObserverList<InfoType>* mObservers;
};
template <class InfoType>
class CachingObserversManager : public ObserversManager<InfoType> {
public:
InfoType GetCurrentInformation() {
if (mHasValidCache) {
return mInfo;
}
GetCurrentInformationInternal(&mInfo);
mHasValidCache = true;
return mInfo;
}
void CacheInformation(const InfoType& aInfo) {
mHasValidCache = true;
mInfo = aInfo;
}
void BroadcastCachedInformation() { this->BroadcastInformation(mInfo); }
protected:
virtual void GetCurrentInformationInternal(InfoType*) = 0;
void OnNotificationsDisabled() override { mHasValidCache = false; }
private:
InfoType mInfo;
bool mHasValidCache;
};
class BatteryObserversManager
: public CachingObserversManager<BatteryInformation> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableBatteryNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableBatteryNotifications());
}
void GetCurrentInformationInternal(BatteryInformation* aInfo) override {
PROXY_IF_SANDBOXED(GetCurrentBatteryInformation(aInfo));
}
};
static BatteryObserversManager& BatteryObservers() {
static BatteryObserversManager sBatteryObservers;
AssertMainThread();
return sBatteryObservers;
}
class NetworkObserversManager
: public CachingObserversManager<NetworkInformation> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableNetworkNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableNetworkNotifications());
}
void GetCurrentInformationInternal(NetworkInformation* aInfo) override {
PROXY_IF_SANDBOXED(GetCurrentNetworkInformation(aInfo));
}
};
static NetworkObserversManager& NetworkObservers() {
static NetworkObserversManager sNetworkObservers;
AssertMainThread();
return sNetworkObservers;
}
class WakeLockObserversManager : public ObserversManager<WakeLockInformation> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableWakeLockNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableWakeLockNotifications());
}
};
static WakeLockObserversManager& WakeLockObservers() {
static WakeLockObserversManager sWakeLockObservers;
AssertMainThread();
return sWakeLockObservers;
}
class ScreenConfigurationObserversManager
: public CachingObserversManager<ScreenConfiguration> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableScreenConfigurationNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableScreenConfigurationNotifications());
}
void GetCurrentInformationInternal(ScreenConfiguration* aInfo) override {
PROXY_IF_SANDBOXED(GetCurrentScreenConfiguration(aInfo));
}
};
static ScreenConfigurationObserversManager& ScreenConfigurationObservers() {
AssertMainThread();
static ScreenConfigurationObserversManager sScreenConfigurationObservers;
return sScreenConfigurationObservers;
}
void RegisterBatteryObserver(BatteryObserver* aObserver) {
AssertMainThread();
BatteryObservers().AddObserver(aObserver);
}
void UnregisterBatteryObserver(BatteryObserver* aObserver) {
AssertMainThread();
BatteryObservers().RemoveObserver(aObserver);
}
void GetCurrentBatteryInformation(BatteryInformation* aInfo) {
AssertMainThread();
*aInfo = BatteryObservers().GetCurrentInformation();
}
void NotifyBatteryChange(const BatteryInformation& aInfo) {
AssertMainThread();
BatteryObservers().CacheInformation(aInfo);
BatteryObservers().BroadcastCachedInformation();
}
class SystemClockChangeObserversManager : public ObserversManager<int64_t> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableSystemClockChangeNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableSystemClockChangeNotifications());
}
};
static SystemClockChangeObserversManager& SystemClockChangeObservers() {
static SystemClockChangeObserversManager sSystemClockChangeObservers;
AssertMainThread();
return sSystemClockChangeObservers;
}
void RegisterSystemClockChangeObserver(SystemClockChangeObserver* aObserver) {
AssertMainThread();
SystemClockChangeObservers().AddObserver(aObserver);
}
void UnregisterSystemClockChangeObserver(SystemClockChangeObserver* aObserver) {
AssertMainThread();
SystemClockChangeObservers().RemoveObserver(aObserver);
}
void NotifySystemClockChange(const int64_t& aClockDeltaMS) {
SystemClockChangeObservers().BroadcastInformation(aClockDeltaMS);
}
class SystemTimezoneChangeObserversManager
: public ObserversManager<SystemTimezoneChangeInformation> {
protected:
void EnableNotifications() override {
PROXY_IF_SANDBOXED(EnableSystemTimezoneChangeNotifications());
}
void DisableNotifications() override {
PROXY_IF_SANDBOXED(DisableSystemTimezoneChangeNotifications());
}
};
static SystemTimezoneChangeObserversManager& SystemTimezoneChangeObservers() {
static SystemTimezoneChangeObserversManager sSystemTimezoneChangeObservers;
return sSystemTimezoneChangeObservers;
}
void RegisterSystemTimezoneChangeObserver(
SystemTimezoneChangeObserver* aObserver) {
AssertMainThread();
SystemTimezoneChangeObservers().AddObserver(aObserver);
}
void UnregisterSystemTimezoneChangeObserver(
SystemTimezoneChangeObserver* aObserver) {
AssertMainThread();
SystemTimezoneChangeObservers().RemoveObserver(aObserver);
}
void NotifySystemTimezoneChange(
const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) {
nsJSUtils::ResetTimeZone();
SystemTimezoneChangeObservers().BroadcastInformation(
aSystemTimezoneChangeInfo);
}
void AdjustSystemClock(int64_t aDeltaMilliseconds) {
AssertMainThread();
PROXY_IF_SANDBOXED(AdjustSystemClock(aDeltaMilliseconds));
}
void EnableSensorNotifications(SensorType aSensor) {
AssertMainThread();
PROXY_IF_SANDBOXED(EnableSensorNotifications(aSensor));
}
void DisableSensorNotifications(SensorType aSensor) {
AssertMainThread();
PROXY_IF_SANDBOXED(DisableSensorNotifications(aSensor));
}
typedef mozilla::ObserverList<SensorData> SensorObserverList;
static SensorObserverList* gSensorObservers = nullptr;
static SensorObserverList& GetSensorObservers(SensorType sensor_type) {
MOZ_ASSERT(sensor_type < NUM_SENSOR_TYPE);
if (!gSensorObservers) {
gSensorObservers = new SensorObserverList[NUM_SENSOR_TYPE];
}
return gSensorObservers[sensor_type];
}
void RegisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) {
SensorObserverList& observers = GetSensorObservers(aSensor);
AssertMainThread();
observers.AddObserver(aObserver);
if (observers.Length() == 1) {
EnableSensorNotifications(aSensor);
}
}
void UnregisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) {
AssertMainThread();
if (!gSensorObservers) {
HAL_ERR("Un-registering a sensor when none have been registered");
return;
}
SensorObserverList& observers = GetSensorObservers(aSensor);
if (!observers.RemoveObserver(aObserver) || observers.Length() > 0) {
return;
}
DisableSensorNotifications(aSensor);
for (int i = 0; i < NUM_SENSOR_TYPE; i++) {
if (gSensorObservers[i].Length() > 0) {
return;
}
}
// We want to destroy gSensorObservers if all observer lists are
// empty, but we have to defer the deallocation via a runnable to
// mainthread (since we may be inside NotifySensorChange()/Broadcast()
// when it calls UnregisterSensorObserver()).
SensorObserverList* sensorlists = gSensorObservers;
gSensorObservers = nullptr;
// Unlike DispatchToMainThread, DispatchToCurrentThread doesn't leak a
// runnable if it fails (and we assert we're on MainThread).
if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"UnregisterSensorObserver",
[sensorlists]() -> void { delete[] sensorlists; })))) {
// Still need to delete sensorlists if the dispatch fails
delete[] sensorlists;
}
}
void NotifySensorChange(const SensorData& aSensorData) {
SensorObserverList& observers = GetSensorObservers(aSensorData.sensor());
AssertMainThread();
observers.Broadcast(aSensorData);
}
void RegisterNetworkObserver(NetworkObserver* aObserver) {
AssertMainThread();
NetworkObservers().AddObserver(aObserver);
}
void UnregisterNetworkObserver(NetworkObserver* aObserver) {
AssertMainThread();
NetworkObservers().RemoveObserver(aObserver);
}
void GetCurrentNetworkInformation(NetworkInformation* aInfo) {
AssertMainThread();
*aInfo = NetworkObservers().GetCurrentInformation();
}
void NotifyNetworkChange(const NetworkInformation& aInfo) {
NetworkObservers().CacheInformation(aInfo);
NetworkObservers().BroadcastCachedInformation();
}
void RegisterWakeLockObserver(WakeLockObserver* aObserver) {
AssertMainThread();
WakeLockObservers().AddObserver(aObserver);
}
void UnregisterWakeLockObserver(WakeLockObserver* aObserver) {
AssertMainThread();
WakeLockObservers().RemoveObserver(aObserver);
}
void ModifyWakeLock(const nsAString& aTopic, WakeLockControl aLockAdjust,
WakeLockControl aHiddenAdjust,
uint64_t aProcessID /* = CONTENT_PROCESS_ID_UNKNOWN */) {
AssertMainThread();
if (aProcessID == CONTENT_PROCESS_ID_UNKNOWN) {
aProcessID = InSandbox() ? ContentChild::GetSingleton()->GetID()
: CONTENT_PROCESS_ID_MAIN;
}
PROXY_IF_SANDBOXED(
ModifyWakeLock(aTopic, aLockAdjust, aHiddenAdjust, aProcessID));
}
void GetWakeLockInfo(const nsAString& aTopic,
WakeLockInformation* aWakeLockInfo) {
AssertMainThread();
PROXY_IF_SANDBOXED(GetWakeLockInfo(aTopic, aWakeLockInfo));
}
void NotifyWakeLockChange(const WakeLockInformation& aInfo) {
AssertMainThread();
WakeLockObservers().BroadcastInformation(aInfo);
}
void RegisterScreenConfigurationObserver(
ScreenConfigurationObserver* aObserver) {
AssertMainThread();
ScreenConfigurationObservers().AddObserver(aObserver);
}
void UnregisterScreenConfigurationObserver(
ScreenConfigurationObserver* aObserver) {
AssertMainThread();
ScreenConfigurationObservers().RemoveObserver(aObserver);
}
void GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) {
AssertMainThread();
*aScreenConfiguration =
ScreenConfigurationObservers().GetCurrentInformation();
}
void NotifyScreenConfigurationChange(
const ScreenConfiguration& aScreenConfiguration) {
ScreenConfigurationObservers().CacheInformation(aScreenConfiguration);
ScreenConfigurationObservers().BroadcastCachedInformation();
}
bool LockScreenOrientation(const dom::ScreenOrientationInternal& aOrientation) {
AssertMainThread();
RETURN_PROXY_IF_SANDBOXED(LockScreenOrientation(aOrientation), false);
}
void UnlockScreenOrientation() {
AssertMainThread();
PROXY_IF_SANDBOXED(UnlockScreenOrientation());
}
bool SetProcessPrioritySupported() {
RETURN_PROXY_IF_SANDBOXED(SetProcessPrioritySupported(), false);
}
void SetProcessPriority(int aPid, ProcessPriority aPriority) {
// n.b. The sandboxed implementation crashes; SetProcessPriority works only
// from the main process.
PROXY_IF_SANDBOXED(SetProcessPriority(aPid, aPriority));
}
void SetCurrentThreadPriority(hal::ThreadPriority aThreadPriority) {
PROXY_IF_SANDBOXED(SetCurrentThreadPriority(aThreadPriority));
}
void SetThreadPriority(PlatformThreadId aThreadId,
hal::ThreadPriority aThreadPriority) {
PROXY_IF_SANDBOXED(SetThreadPriority(aThreadId, aThreadPriority));
}
// From HalTypes.h.
const char* ProcessPriorityToString(ProcessPriority aPriority) {
switch (aPriority) {
case PROCESS_PRIORITY_MASTER:
return "MASTER";
case PROCESS_PRIORITY_PREALLOC:
return "PREALLOC";
case PROCESS_PRIORITY_FOREGROUND_HIGH:
return "FOREGROUND_HIGH";
case PROCESS_PRIORITY_FOREGROUND:
return "FOREGROUND";
case PROCESS_PRIORITY_FOREGROUND_KEYBOARD:
return "FOREGROUND_KEYBOARD";
case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE:
return "BACKGROUND_PERCEIVABLE";
case PROCESS_PRIORITY_BACKGROUND:
return "BACKGROUND";
case PROCESS_PRIORITY_UNKNOWN:
return "UNKNOWN";
default:
MOZ_ASSERT(false);
return "???";
}
}
const char* ThreadPriorityToString(ThreadPriority aPriority) {
switch (aPriority) {
case THREAD_PRIORITY_COMPOSITOR:
return "COMPOSITOR";
default:
MOZ_ASSERT(false);
return "???";
}
}
void StartDiskSpaceWatcher() {
AssertMainProcess();
AssertMainThread();
PROXY_IF_SANDBOXED(StartDiskSpaceWatcher());
}
void StopDiskSpaceWatcher() {
AssertMainProcess();
AssertMainThread();
PROXY_IF_SANDBOXED(StopDiskSpaceWatcher());
}
} // namespace hal
} // namespace mozilla