widget/ScreenManager.cpp
author Jim Chen <nchen@mozilla.com>
Wed, 20 Jun 2018 16:46:20 -0400
changeset 423113 2806729c61eaac199ad4670788c4008079a99f34
parent 420862 b54db66223586b4e04f5cb926fccdacf8a176b91
child 448947 6f3709b3878117466168c40affa7bca0b60cf75b
permissions -rw-r--r--
Bug 1469683 - 1. Fix crash tests; r=esawin Specify individual sessions in crash tests (i.e. "mainSession.waitUntilCalled" instead of "sessionRule.waitUntilCalled"), so that the tests assert behavior on the correct session, and not inadvertently on the cached session. Also, under x86 debug builds, Gecko installs an "ah_crap_handler" for SIGSEGV that waits for a long time, which causes our crash tests to time out. Therefore, ignore crash tests under x86 debug. MozReview-Commit-ID: DdtmRBLmPGp

/* -*- 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 "ScreenManager.h"

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/Logging.h"
#include "mozilla/StaticPtr.h"

static mozilla::LazyLogModule sScreenLog("WidgetScreen");

namespace mozilla {
namespace widget {

NS_IMPL_ISUPPORTS(ScreenManager, nsIScreenManager)

ScreenManager::ScreenManager()
{
}

ScreenManager::~ScreenManager()
{
}

static StaticRefPtr<ScreenManager> sSingleton;

ScreenManager&
ScreenManager::GetSingleton()
{
  if (!sSingleton) {
    sSingleton = new ScreenManager();
    ClearOnShutdown(&sSingleton);
  }
  return *sSingleton;
}

already_AddRefed<ScreenManager>
ScreenManager::GetAddRefedSingleton()
{
  RefPtr<ScreenManager> sm = &GetSingleton();
  return sm.forget();
}

void
ScreenManager::SetHelper(UniquePtr<Helper> aHelper)
{
  mHelper = std::move(aHelper);
}

void
ScreenManager::Refresh(nsTArray<RefPtr<Screen>>&& aScreens)
{
  MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens"));

  mScreenList = std::move(aScreens);

  CopyScreensToAllRemotesIfIsParent();
}

void
ScreenManager::Refresh(nsTArray<mozilla::dom::ScreenDetails>&& aScreens)
{
  MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refresh screens from IPC"));

  mScreenList.Clear();
  for (auto& screen : aScreens) {
    mScreenList.AppendElement(new Screen(screen));
  }

  CopyScreensToAllRemotesIfIsParent();
}

template<class Range>
void
ScreenManager::CopyScreensToRemoteRange(Range aRemoteRange)
{
  AutoTArray<dom::ScreenDetails, 4> screens;
  for (auto& screen : mScreenList) {
    screens.AppendElement(screen->ToScreenDetails());
  }
  for (auto cp : aRemoteRange) {
    MOZ_LOG(sScreenLog, LogLevel::Debug, ("Send screens to [Pid %d]", cp->Pid()));
    if (!cp->SendRefreshScreens(screens)) {
      MOZ_LOG(sScreenLog, LogLevel::Error,
              ("SendRefreshScreens to [Pid %d] failed", cp->Pid()));
    }
  }
}

void
ScreenManager::CopyScreensToRemote(dom::ContentParent* aContentParent)
{
  MOZ_ASSERT(aContentParent);
  MOZ_ASSERT(XRE_IsParentProcess());

  auto range = { aContentParent };
  CopyScreensToRemoteRange(range);
}

void
ScreenManager::CopyScreensToAllRemotesIfIsParent()
{
  if (XRE_IsContentProcess()) {
    return;
  }

  MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing all ContentParents"));

  CopyScreensToRemoteRange(dom::ContentParent::AllProcesses(dom::ContentParent::eLive));
}

// Returns the screen that contains the rectangle. If the rect overlaps
// multiple screens, it picks the screen with the greatest area of intersection.
//
// The coordinates are in desktop pixels.
//
NS_IMETHODIMP
ScreenManager::ScreenForRect(int32_t aX, int32_t aY,
                             int32_t aWidth, int32_t aHeight,
                             nsIScreen** aOutScreen)
{
  if (mScreenList.IsEmpty()) {
    MOZ_LOG(sScreenLog, LogLevel::Warning,
            ("No screen available. This can happen in xpcshell."));
    RefPtr<Screen> ret = new Screen(LayoutDeviceIntRect(), LayoutDeviceIntRect(),
                                    0, 0,
                                    DesktopToLayoutDeviceScale(),
                                    CSSToLayoutDeviceScale(),
                                    96 /* dpi */);
    ret.forget(aOutScreen);
    return NS_OK;
  }

  // Optimize for the common case. If the number of screens is only
  // one then just return the primary screen.
  if (mScreenList.Length() == 1) {
    return GetPrimaryScreen(aOutScreen);
  }

  // which screen should we return?
  Screen* which = mScreenList[0].get();

  // walk the list of screens and find the one that has the most
  // surface area.
  uint32_t area = 0;
  DesktopIntRect windowRect(aX, aY, aWidth, aHeight);
  for (auto& screen : mScreenList) {
    int32_t  x, y, width, height;
    x = y = width = height = 0;
    screen->GetRectDisplayPix(&x, &y, &width, &height);
    // calculate the surface area
    DesktopIntRect screenRect(x, y, width, height);
    screenRect.IntersectRect(screenRect, windowRect);
    uint32_t tempArea = screenRect.Area();
    if (tempArea > area) {
      which = screen.get();
      area = tempArea;
    }
  }

  // If the rect intersects one or more screen,
  // return the screen that has the largest intersection.
  if (area > 0) {
    RefPtr<Screen> ret = which;
    ret.forget(aOutScreen);
    return NS_OK;
  }

  // If the rect does not intersect a screen, find
  // a screen that is nearest to the rect.
  uint32_t distance = UINT32_MAX;
  for (auto& screen : mScreenList) {
    int32_t  x, y, width, height;
    x = y = width = height = 0;
    screen->GetRectDisplayPix(&x, &y, &width, &height);

    uint32_t distanceX = 0;
    if (aX > (x + width)) {
      distanceX = aX - (x + width);
    } else if ((aX + aWidth) < x) {
      distanceX = x - (aX + aWidth);
    }

    uint32_t distanceY = 0;
    if (aY > (y + height)) {
      distanceY = aY - (y + height);
    } else if ((aY + aHeight) < y) {
      distanceY = y - (aY + aHeight);
    }

    uint32_t tempDistance = distanceX * distanceX + distanceY * distanceY;
    if (tempDistance < distance) {
      which = screen.get();
      distance = tempDistance;
      if (distance == 0) {
        break;
      }
    }
  }

  RefPtr<Screen> ret = which;
  ret.forget(aOutScreen);
  return NS_OK;
}

// The screen with the menubar/taskbar. This shouldn't be needed very
// often.
//
NS_IMETHODIMP
ScreenManager::GetPrimaryScreen(nsIScreen** aPrimaryScreen)
{
  if (mScreenList.IsEmpty()) {
    MOZ_LOG(sScreenLog, LogLevel::Warning,
            ("No screen available. This can happen in xpcshell."));
    RefPtr<Screen> ret = new Screen(LayoutDeviceIntRect(), LayoutDeviceIntRect(),
                                    0, 0,
                                    DesktopToLayoutDeviceScale(),
                                    CSSToLayoutDeviceScale(),
                                    96 /* dpi */);
    ret.forget(aPrimaryScreen);
    return NS_OK;
  }

  RefPtr<Screen> ret = mScreenList[0];
  ret.forget(aPrimaryScreen);
  return NS_OK;
}

} // namespace widget
} // namespace mozilla