hal/Hal.cpp
author Andrew McCreight <continuation@gmail.com>
Mon, 22 Apr 2019 16:34:51 +0000
changeset 470376 3073770e06f157040f4c64951b7e8425e1ad7bbe
parent 454354 5f4630838d46dd81dadb13220a4af0da9e23a619
child 470773 9998ccf0c05f87940376b736fb587355acf03c2c
permissions -rw-r--r--
Bug 1535403 - Take indirection into account for the CC optimizations for the outer window wrapper. r=peterv Most wrapper cached C++ objects are held alive by their wrapper. The cycle collector takes advantage of this in many classes and ignores the C++ object if the wrapper is marked black. However, this is not true for the outer window's wrapper. Instead, the outer window's wrapper keeps the inner window alive. The inner window usually keeps its outer window alive, but not after it has been unlinked. For reasons I do not yet understand, the outer window's wrapper can be kept alive after the inner window it is a proxy for is unlinked. This patch fixes the cycle collector optimization for the outer window by only applying it if the outer window still has a weak reference to the inner window, which it will until the inner no longer holds the outer alive. This in turn fixes, or at least helps fix, window leaks seen intermittently when the lifetime of outer windows and docshells are tied together. The code comment is based on a review comment by peterv. Differential Revision: https://phabricator.services.mozilla.com/D27981

/* -*- 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 "HalWakeLockInternal.h"
#include "nsIDOMWindow.h"
#include "mozilla/dom/Document.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 "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 {

static bool sInitialized = false;

mozilla::LogModule* GetHalLog() {
  static mozilla::LazyLogModule sHalLog("hal");
  return sHalLog;
}

namespace {

void AssertMainThread() { MOZ_ASSERT(NS_IsMainThread()); }

bool InSandbox() { return GeckoProcessType_Content == XRE_GetProcessType(); }

bool WindowIsActive(nsPIDOMWindowInner* aWindow) {
  dom::Document* document = aWindow->GetDoc();
  NS_ENSURE_TRUE(document, false);
  return !document->Hidden();
}

StaticAutoPtr<WindowIdentifier::IDArrayType> gLastIDToVibrate;

static void RecordLastIDToVibrate(const WindowIdentifier& aId) {
  if (!InSandbox()) {
    *gLastIDToVibrate = aId.AsArray();
  }
}

static bool MayCancelVibration(const WindowIdentifier& aId) {
  // 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.

  return InSandbox() || (*gLastIDToVibrate == aId.AsArray());
}

}  // 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;
  }

  RecordLastIDToVibrate(id);

  // 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();

  if (MayCancelVibration(id)) {
    // 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) {
    mObservers.AddObserver(aObserver);

    if (mObservers.Length() == 1) {
      EnableNotifications();
    }
  }

  void RemoveObserver(Observer<InfoType>* aObserver) {
    bool removed = mObservers.RemoveObserver(aObserver);
    if (!removed) {
      return;
    }

    if (mObservers.Length() == 0) {
      DisableNotifications();
      OnNotificationsDisabled();
    }
  }

  void BroadcastInformation(const InfoType& aInfo) {
    mObservers.Broadcast(aInfo);
  }

 protected:
  ~ObserversManager() { MOZ_ASSERT(mObservers.Length() == 0); }

  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 final
    : 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));
  }
};

class NetworkObserversManager final
    : 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));
  }
};

class WakeLockObserversManager final
    : public ObserversManager<WakeLockInformation> {
 protected:
  void EnableNotifications() override {
    PROXY_IF_SANDBOXED(EnableWakeLockNotifications());
  }

  void DisableNotifications() override {
    PROXY_IF_SANDBOXED(DisableWakeLockNotifications());
  }
};

class ScreenConfigurationObserversManager final
    : 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));
  }
};

typedef mozilla::ObserverList<SensorData> SensorObserverList;
StaticAutoPtr<SensorObserverList> sSensorObservers[NUM_SENSOR_TYPE];

static SensorObserverList* GetSensorObservers(SensorType sensor_type) {
  AssertMainThread();
  MOZ_ASSERT(sensor_type < NUM_SENSOR_TYPE);

  if (!sSensorObservers[sensor_type]) {
    sSensorObservers[sensor_type] = new SensorObserverList();
  }

  return sSensorObservers[sensor_type];
}

#define MOZ_IMPL_HAL_OBSERVER(name_)                             \
  StaticAutoPtr<name_##ObserversManager> s##name_##Observers;    \
                                                                 \
  static name_##ObserversManager* name_##Observers() {           \
    AssertMainThread();                                          \
                                                                 \
    if (!s##name_##Observers) {                                  \
      MOZ_ASSERT(sInitialized);                                  \
      s##name_##Observers = new name_##ObserversManager();       \
    }                                                            \
                                                                 \
    return s##name_##Observers;                                  \
  }                                                              \
                                                                 \
  void Register##name_##Observer(name_##Observer* aObserver) {   \
    AssertMainThread();                                          \
    name_##Observers()->AddObserver(aObserver);                  \
  }                                                              \
                                                                 \
  void Unregister##name_##Observer(name_##Observer* aObserver) { \
    AssertMainThread();                                          \
    name_##Observers()->RemoveObserver(aObserver);               \
  }

MOZ_IMPL_HAL_OBSERVER(Battery)

void GetCurrentBatteryInformation(BatteryInformation* aInfo) {
  *aInfo = BatteryObservers()->GetCurrentInformation();
}

void NotifyBatteryChange(const BatteryInformation& aInfo) {
  BatteryObservers()->CacheInformation(aInfo);
  BatteryObservers()->BroadcastCachedInformation();
}

void EnableSensorNotifications(SensorType aSensor) {
  AssertMainThread();
  PROXY_IF_SANDBOXED(EnableSensorNotifications(aSensor));
}

void DisableSensorNotifications(SensorType aSensor) {
  AssertMainThread();
  PROXY_IF_SANDBOXED(DisableSensorNotifications(aSensor));
}

void RegisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) {
  SensorObserverList* observers = GetSensorObservers(aSensor);

  observers->AddObserver(aObserver);
  if (observers->Length() == 1) {
    EnableSensorNotifications(aSensor);
  }
}

void UnregisterSensorObserver(SensorType aSensor, ISensorObserver* aObserver) {
  SensorObserverList* observers = GetSensorObservers(aSensor);
  if (!observers->RemoveObserver(aObserver) || observers->Length() > 0) {
    return;
  }
  DisableSensorNotifications(aSensor);
}

void NotifySensorChange(const SensorData& aSensorData) {
  SensorObserverList* observers = GetSensorObservers(aSensorData.sensor());

  observers->Broadcast(aSensorData);
}

MOZ_IMPL_HAL_OBSERVER(Network)

void GetCurrentNetworkInformation(NetworkInformation* aInfo) {
  *aInfo = NetworkObservers()->GetCurrentInformation();
}

void NotifyNetworkChange(const NetworkInformation& aInfo) {
  NetworkObservers()->CacheInformation(aInfo);
  NetworkObservers()->BroadcastCachedInformation();
}

MOZ_IMPL_HAL_OBSERVER(WakeLock)

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);
}

MOZ_IMPL_HAL_OBSERVER(ScreenConfiguration)

void GetCurrentScreenConfiguration(ScreenConfiguration* aScreenConfiguration) {
  *aScreenConfiguration =
      ScreenConfigurationObservers()->GetCurrentInformation();
}

void NotifyScreenConfigurationChange(
    const ScreenConfiguration& aScreenConfiguration) {
  ScreenConfigurationObservers()->CacheInformation(aScreenConfiguration);
  ScreenConfigurationObservers()->BroadcastCachedInformation();
}

bool LockScreenOrientation(const ScreenOrientation& 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));
}

// 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 "???";
  }
}

void Init() {
  MOZ_ASSERT(!sInitialized);

  if (!InSandbox()) {
    gLastIDToVibrate = new WindowIdentifier::IDArrayType();
  }

  WakeLockInit();

  sInitialized = true;
}

void Shutdown() {
  MOZ_ASSERT(sInitialized);

  gLastIDToVibrate = nullptr;

  sBatteryObservers = nullptr;
  sNetworkObservers = nullptr;
  sWakeLockObservers = nullptr;
  sScreenConfigurationObservers = nullptr;

  for (auto& sensorObserver : sSensorObservers) {
    sensorObserver = nullptr;
  }

  sInitialized = false;
}

}  // namespace hal
}  // namespace mozilla