Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsBaseWidget.h"
#include <utility>
#include "GLConsts.h"
#include "InputData.h"
#include "LiveResizeListener.h"
#include "SwipeTracker.h"
#include "TouchEvents.h"
#include "X11UndefineNone.h"
#include "base/thread.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/GlobalKeyListener.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/Logging.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/NativeKeyBindingsType.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/VsyncDispatcher.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/SimpleGestureEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/layers/APZCCallbackHelper.h"
#include "mozilla/layers/TouchActionHelper.h"
#include "mozilla/layers/APZEventState.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/ChromeProcessController.h"
#include "mozilla/layers/Compositor.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorOptions.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/webrender/WebRenderTypes.h"
#include "mozilla/widget/ScreenManager.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsDeviceContext.h"
#include "nsGfxCIID.h"
#include "nsIAppWindow.h"
#include "nsIBaseWindow.h"
#include "nsIContent.h"
#include "nsIScreenManager.h"
#include "nsISimpleEnumerator.h"
#include "nsIWidgetListener.h"
#include "nsRefPtrHashtable.h"
#include "nsServiceManagerUtils.h"
#include "nsWidgetsCID.h"
#include "nsXULPopupManager.h"
#include "prdtoa.h"
#include "prenv.h"
#ifdef ACCESSIBILITY
# include "nsAccessibilityService.h"
#endif
#include "gfxConfig.h"
#include "gfxUtils.h" // for ToDeviceColor
#include "mozilla/layers/CompositorSession.h"
#include "VRManagerChild.h"
#include "gfxConfig.h"
#include "nsView.h"
#include "nsViewManager.h"
static mozilla::LazyLogModule sBaseWidgetLog("BaseWidget");
#ifdef DEBUG
# include "nsIObserver.h"
static void debug_RegisterPrefCallbacks();
#endif
#ifdef NOISY_WIDGET_LEAKS
static int32_t gNumWidgets;
#endif
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::ipc;
using namespace mozilla::widget;
using namespace mozilla;
// Async pump timer during injected long touch taps
#define TOUCH_INJECT_PUMP_TIMER_MSEC 50
#define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500
int32_t nsIWidget::sPointerIdCounter = 0;
// Some statics from nsIWidget.h
/*static*/
uint64_t AutoObserverNotifier::sObserverId = 0;
/*static*/ nsTHashMap<uint64_t, nsCOMPtr<nsIObserver>>
AutoObserverNotifier::sSavedObservers;
// The maximum amount of time to let the EnableDragDrop runnable wait in the
// idle queue before timing out and moving it to the regular queue. Value is in
// milliseconds.
const uint32_t kAsyncDragDropTimeout = 1000;
NS_IMPL_ISUPPORTS(nsBaseWidget, nsIWidget, nsISupportsWeakReference)
//-------------------------------------------------------------------------
//
// nsBaseWidget constructor
//
//-------------------------------------------------------------------------
nsBaseWidget::nsBaseWidget() : nsBaseWidget(BorderStyle::None) {}
nsBaseWidget::nsBaseWidget(BorderStyle aBorderStyle)
: mWidgetListener(nullptr),
mAttachedWidgetListener(nullptr),
mPreviouslyAttachedWidgetListener(nullptr),
mCompositorVsyncDispatcher(nullptr),
mBorderStyle(aBorderStyle),
mBounds(0, 0, 0, 0),
mIsTiled(false),
mPopupLevel(PopupLevel::Top),
mPopupType(PopupType::Any),
mHasRemoteContent(false),
mUpdateCursor(true),
mUseAttachedEvents(false),
mIMEHasFocus(false),
mIMEHasQuit(false),
mIsFullyOccluded(false),
mNeedFastSnaphot(false),
mCurrentPanGestureBelongsToSwipe(false),
mIsPIPWindow(false) {
#ifdef NOISY_WIDGET_LEAKS
gNumWidgets++;
printf("WIDGETS+ = %d\n", gNumWidgets);
#endif
#ifdef DEBUG
debug_RegisterPrefCallbacks();
#endif
mShutdownObserver = new WidgetShutdownObserver(this);
}
NS_IMPL_ISUPPORTS(WidgetShutdownObserver, nsIObserver)
WidgetShutdownObserver::WidgetShutdownObserver(nsBaseWidget* aWidget)
: mWidget(aWidget), mRegistered(false) {
Register();
}
WidgetShutdownObserver::~WidgetShutdownObserver() {
// No need to call Unregister(), we can't be destroyed until nsBaseWidget
// gets torn down. The observer service and nsBaseWidget have a ref on us
// so nsBaseWidget has to call Unregister and then clear its ref.
}
NS_IMETHODIMP
WidgetShutdownObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!mWidget) {
return NS_OK;
}
if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
RefPtr<nsBaseWidget> widget(mWidget);
widget->Shutdown();
} else if (!strcmp(aTopic, "quit-application")) {
RefPtr<nsBaseWidget> widget(mWidget);
widget->QuitIME();
}
return NS_OK;
}
void WidgetShutdownObserver::Register() {
if (!mRegistered) {
mRegistered = true;
nsContentUtils::RegisterShutdownObserver(this);
#ifndef MOZ_WIDGET_ANDROID
// The primary purpose of observing quit-application is
// to avoid leaking a widget on Windows when nothing else
// breaks the circular reference between the widget and
// TSFTextStore. However, our Android IME code crashes if
// doing this on Android, so let's not do this on Android.
// Doing this on Gtk and Mac just in case.
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->AddObserver(this, "quit-application", false);
}
#endif
}
}
void WidgetShutdownObserver::Unregister() {
if (mRegistered) {
mWidget = nullptr;
#ifndef MOZ_WIDGET_ANDROID
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->RemoveObserver(this, "quit-application");
}
#endif
nsContentUtils::UnregisterShutdownObserver(this);
mRegistered = false;
}
}
#define INTL_APP_LOCALES_CHANGED "intl:app-locales-changed"
NS_IMPL_ISUPPORTS(LocalesChangedObserver, nsIObserver)
LocalesChangedObserver::LocalesChangedObserver(nsBaseWidget* aWidget)
: mWidget(aWidget), mRegistered(false) {
Register();
}
LocalesChangedObserver::~LocalesChangedObserver() {
// No need to call Unregister(), we can't be destroyed until nsBaseWidget
// gets torn down. The observer service and nsBaseWidget have a ref on us
// so nsBaseWidget has to call Unregister and then clear its ref.
}
NS_IMETHODIMP
LocalesChangedObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
if (!mWidget) {
return NS_OK;
}
if (!strcmp(aTopic, INTL_APP_LOCALES_CHANGED)) {
RefPtr<nsBaseWidget> widget(mWidget);
widget->LocalesChanged();
}
return NS_OK;
}
void LocalesChangedObserver::Register() {
if (mRegistered) {
return;
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, INTL_APP_LOCALES_CHANGED, true);
}
// Locale might be update before registering
RefPtr<nsBaseWidget> widget(mWidget);
widget->LocalesChanged();
mRegistered = true;
}
void LocalesChangedObserver::Unregister() {
if (!mRegistered) {
return;
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, INTL_APP_LOCALES_CHANGED);
}
mWidget = nullptr;
mRegistered = false;
}
void nsBaseWidget::Shutdown() {
NotifyLiveResizeStopped();
DestroyCompositor();
FreeLocalesChangedObserver();
FreeShutdownObserver();
}
void nsBaseWidget::QuitIME() {
IMEStateManager::WidgetOnQuit(this);
this->mIMEHasQuit = true;
}
void nsBaseWidget::DestroyCompositor() {
RevokeTransactionIdAllocator();
// We release this before releasing the compositor, since it may hold the
// last reference to our ClientLayerManager. ClientLayerManager's dtor can
// trigger a paint, creating a new compositor, and we don't want to re-use
// the old vsync dispatcher.
if (mCompositorVsyncDispatcher) {
MOZ_ASSERT(mCompositorVsyncDispatcherLock.get());
MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
mCompositorVsyncDispatcher->Shutdown();
mCompositorVsyncDispatcher = nullptr;
}
// The compositor shutdown sequence looks like this:
// 1. CompositorSession calls CompositorBridgeChild::Destroy.
// 2. CompositorBridgeChild synchronously sends WillClose.
// 3. CompositorBridgeParent releases some resources (such as the layer
// manager, compositor, and widget).
// 4. CompositorBridgeChild::Destroy returns.
// 5. Asynchronously, CompositorBridgeParent::ActorDestroy will fire on the
// compositor thread when the I/O thread closes the IPC channel.
// 6. Step 5 will schedule DeferredDestroy on the compositor thread, which
// releases the reference CompositorBridgeParent holds to itself.
//
// When CompositorSession::Shutdown returns, we assume the compositor is gone
// or will be gone very soon.
if (mCompositorSession) {
ReleaseContentController();
mAPZC = nullptr;
SetCompositorWidgetDelegate(nullptr);
mCompositorBridgeChild = nullptr;
mCompositorSession->Shutdown();
mCompositorSession = nullptr;
}
}
// This prevents the layer manager from starting a new transaction during
// shutdown.
void nsBaseWidget::RevokeTransactionIdAllocator() {
if (!mWindowRenderer || !mWindowRenderer->AsWebRender()) {
return;
}
mWindowRenderer->AsWebRender()->SetTransactionIdAllocator(nullptr);
}
void nsBaseWidget::ReleaseContentController() {
if (mRootContentController) {
mRootContentController->Destroy();
mRootContentController = nullptr;
}
}
void nsBaseWidget::DestroyLayerManager() {
if (mWindowRenderer) {
mWindowRenderer->Destroy();
mWindowRenderer = nullptr;
}
DestroyCompositor();
}
void nsBaseWidget::OnRenderingDeviceReset() { DestroyLayerManager(); }
void nsBaseWidget::FreeShutdownObserver() {
if (mShutdownObserver) {
mShutdownObserver->Unregister();
}
mShutdownObserver = nullptr;
}
void nsBaseWidget::FreeLocalesChangedObserver() {
if (mLocalesChangedObserver) {
mLocalesChangedObserver->Unregister();
}
mLocalesChangedObserver = nullptr;
}
//-------------------------------------------------------------------------
//
// nsBaseWidget destructor
//
//-------------------------------------------------------------------------
nsBaseWidget::~nsBaseWidget() {
if (mSwipeTracker) {
mSwipeTracker->Destroy();
mSwipeTracker = nullptr;
}
IMEStateManager::WidgetDestroyed(this);
FreeLocalesChangedObserver();
FreeShutdownObserver();
DestroyLayerManager();
#ifdef NOISY_WIDGET_LEAKS
gNumWidgets--;
printf("WIDGETS- = %d\n", gNumWidgets);
#endif
}
//-------------------------------------------------------------------------
//
// Basic create.
//
//-------------------------------------------------------------------------
void nsBaseWidget::BaseCreate(nsIWidget* aParent, widget::InitData* aInitData) {
if (aInitData) {
mWindowType = aInitData->mWindowType;
mBorderStyle = aInitData->mBorderStyle;
mPopupLevel = aInitData->mPopupLevel;
mPopupType = aInitData->mPopupHint;
mHasRemoteContent = aInitData->mHasRemoteContent;
mIsPIPWindow = aInitData->mPIPWindow;
}
if (aParent) {
aParent->AddChild(this);
}
}
//-------------------------------------------------------------------------
//
// Accessor functions to get/set the client data
//
//-------------------------------------------------------------------------
nsIWidgetListener* nsBaseWidget::GetWidgetListener() const {
return mWidgetListener;
}
void nsBaseWidget::SetWidgetListener(nsIWidgetListener* aWidgetListener) {
mWidgetListener = aWidgetListener;
}
already_AddRefed<nsIWidget> nsBaseWidget::CreateChild(
const LayoutDeviceIntRect& aRect, widget::InitData* aInitData,
bool aForceUseIWidgetParent) {
nsIWidget* parent = this;
nsNativeWidget nativeParent = nullptr;
if (!aForceUseIWidgetParent) {
// Use only either parent or nativeParent, not both, to match
// existing code. Eventually Create() should be divested of its
// nativeWidget parameter.
nativeParent = parent ? parent->GetNativeData(NS_NATIVE_WIDGET) : nullptr;
parent = nativeParent ? nullptr : parent;
MOZ_ASSERT(!parent || !nativeParent, "messed up logic");
}
nsCOMPtr<nsIWidget> widget;
if (aInitData && aInitData->mWindowType == WindowType::Popup) {
widget = AllocateChildPopupWidget();
} else {
widget = nsIWidget::CreateChildWindow();
}
if (widget && mNeedFastSnaphot) {
widget->SetNeedFastSnaphot();
}
if (widget &&
NS_SUCCEEDED(widget->Create(parent, nativeParent, aRect, aInitData))) {
return widget.forget();
}
return nullptr;
}
// Attach a view to our widget which we'll send events to.
void nsBaseWidget::AttachViewToTopLevel(bool aUseAttachedEvents) {
NS_ASSERTION((mWindowType == WindowType::TopLevel ||
mWindowType == WindowType::Dialog ||
mWindowType == WindowType::Invisible ||
mWindowType == WindowType::Child),
"Can't attach to window of that type");
mUseAttachedEvents = aUseAttachedEvents;
}
nsIWidgetListener* nsBaseWidget::GetAttachedWidgetListener() const {
return mAttachedWidgetListener;
}
nsIWidgetListener* nsBaseWidget::GetPreviouslyAttachedWidgetListener() {
return mPreviouslyAttachedWidgetListener;
}
void nsBaseWidget::SetPreviouslyAttachedWidgetListener(
nsIWidgetListener* aListener) {
mPreviouslyAttachedWidgetListener = aListener;
}
void nsBaseWidget::SetAttachedWidgetListener(nsIWidgetListener* aListener) {
mAttachedWidgetListener = aListener;
}
//-------------------------------------------------------------------------
//
// Close this nsBaseWidget
//
//-------------------------------------------------------------------------
void nsBaseWidget::Destroy() {
DestroyCompositor();
// Just in case our parent is the only ref to us
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
// disconnect from the parent
nsIWidget* parent = GetParent();
if (parent) {
parent->RemoveChild(this);
}
}
//-------------------------------------------------------------------------
//
// Get this nsBaseWidget parent
//
//-------------------------------------------------------------------------
nsIWidget* nsBaseWidget::GetParent(void) { return nullptr; }
//-------------------------------------------------------------------------
//
// Get this nsBaseWidget top level widget
//
//-------------------------------------------------------------------------
nsIWidget* nsBaseWidget::GetTopLevelWidget() {
nsIWidget *topLevelWidget = nullptr, *widget = this;
while (widget) {
topLevelWidget = widget;
widget = widget->GetParent();
}
return topLevelWidget;
}
//-------------------------------------------------------------------------
//
// Get this nsBaseWidget's top (non-sheet) parent (if it's a sheet)
//
//-------------------------------------------------------------------------
nsIWidget* nsBaseWidget::GetSheetWindowParent(void) { return nullptr; }
float nsBaseWidget::GetDPI() { return 96.0f; }
void nsBaseWidget::NotifyAPZOfDPIChange() {
if (mAPZC) {
mAPZC->SetDPI(GetDPI());
}
}
CSSToLayoutDeviceScale nsIWidget::GetDefaultScale() {
double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx();
if (devPixelsPerCSSPixel <= 0.0) {
devPixelsPerCSSPixel = GetDefaultScaleInternal();
}
return CSSToLayoutDeviceScale(devPixelsPerCSSPixel);
}
nsIntSize nsIWidget::CustomCursorSize(const Cursor& aCursor) {
MOZ_ASSERT(aCursor.IsCustom());
int32_t width = 0;
int32_t height = 0;
aCursor.mContainer->GetWidth(&width);
aCursor.mContainer->GetHeight(&height);
aCursor.mResolution.ApplyTo(width, height);
return {width, height};
}
LayoutDeviceIntSize nsIWidget::ClientToWindowSizeDifference() {
auto margin = ClientToWindowMargin();
MOZ_ASSERT(margin.top >= 0, "Window should be bigger than client area");
MOZ_ASSERT(margin.left >= 0, "Window should be bigger than client area");
MOZ_ASSERT(margin.right >= 0, "Window should be bigger than client area");
MOZ_ASSERT(margin.bottom >= 0, "Window should be bigger than client area");
return {margin.LeftRight(), margin.TopBottom()};
}
RefPtr<mozilla::VsyncDispatcher> nsIWidget::GetVsyncDispatcher() {
return nullptr;
}
//-------------------------------------------------------------------------
//
// Add a child to the list of children
//
//-------------------------------------------------------------------------
void nsBaseWidget::AddChild(nsIWidget* aChild) {
MOZ_ASSERT(!aChild->GetNextSibling() && !aChild->GetPrevSibling(),
"aChild not properly removed from its old child list");
if (!mFirstChild) {
mFirstChild = mLastChild = aChild;
} else {
// append to the list
MOZ_ASSERT(mLastChild);
MOZ_ASSERT(!mLastChild->GetNextSibling());
mLastChild->SetNextSibling(aChild);
aChild->SetPrevSibling(mLastChild);
mLastChild = aChild;
}
}
//-------------------------------------------------------------------------
//
// Remove a child from the list of children
//
//-------------------------------------------------------------------------
void nsBaseWidget::RemoveChild(nsIWidget* aChild) {
#ifdef DEBUG
# ifdef XP_MACOSX
// nsCocoaWindow doesn't implement GetParent, so in that case parent will be
// null and we'll just have to do without this assertion.
nsIWidget* parent = aChild->GetParent();
NS_ASSERTION(!parent || parent == this, "Not one of our kids!");
# else
MOZ_RELEASE_ASSERT(aChild->GetParent() == this, "Not one of our kids!");
# endif
#endif
if (mLastChild == aChild) {
mLastChild = mLastChild->GetPrevSibling();
}
if (mFirstChild == aChild) {
mFirstChild = mFirstChild->GetNextSibling();
}
// Now remove from the list. Make sure that we pass ownership of the tail
// of the list correctly before we have aChild let go of it.
nsIWidget* prev = aChild->GetPrevSibling();
nsIWidget* next = aChild->GetNextSibling();
if (prev) {
prev->SetNextSibling(next);
}
if (next) {
next->SetPrevSibling(prev);
}
aChild->SetNextSibling(nullptr);
aChild->SetPrevSibling(nullptr);
}
void nsBaseWidget::GetWorkspaceID(nsAString& workspaceID) {
workspaceID.Truncate();
}
void nsBaseWidget::MoveToWorkspace(const nsAString& workspaceID) {
// Noop.
}
//-------------------------------------------------------------------------
//
// Get this component cursor
//
//-------------------------------------------------------------------------
void nsBaseWidget::SetCursor(const Cursor& aCursor) { mCursor = aCursor; }
void nsBaseWidget::SetCustomCursorAllowed(bool aIsAllowed) {
if (aIsAllowed != mCustomCursorAllowed) {
mCustomCursorAllowed = aIsAllowed;
mUpdateCursor = true;
SetCursor(mCursor);
}
}
//-------------------------------------------------------------------------
//
// Window transparency methods
//
//-------------------------------------------------------------------------
void nsBaseWidget::SetTransparencyMode(TransparencyMode aMode) {}
TransparencyMode nsBaseWidget::GetTransparencyMode() {
return TransparencyMode::Opaque;
}
/* virtual */
void nsBaseWidget::PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback) {
MOZ_ASSERT_UNREACHABLE(
"Should never call PerformFullscreenTransition on nsBaseWidget");
}
//-------------------------------------------------------------------------
//
// Put the window into full-screen mode
//
//-------------------------------------------------------------------------
void nsBaseWidget::InfallibleMakeFullScreen(bool aFullScreen) {
#define MOZ_FORMAT_RECT(fmtstr) "[" fmtstr "," fmtstr " " fmtstr "x" fmtstr "]"
#define MOZ_SPLAT_RECT(rect) \
(rect).X(), (rect).Y(), (rect).Width(), (rect).Height()
// Windows which can be made fullscreen are exactly those which are located on
// the desktop, rather than being a child of some other window.
MOZ_DIAGNOSTIC_ASSERT(BoundsUseDesktopPixels(),
"non-desktop windows cannot be made fullscreen");
// Ensure that the OS chrome is hidden/shown before we resize and/or exit the
// function.
//
// HideWindowChrome() may (depending on platform, implementation details, and
// OS-level user preferences) alter the reported size of the window. The
// obvious and principled solution is socks-and-shoes:
// - On entering fullscreen mode: hide window chrome, then perform resize.
// - On leaving fullscreen mode: unperform resize, then show window chrome.
//
// ... unfortunately, HideWindowChrome() requires Resize() to be called
// afterwards (see bug 498835), which prevents this from being done in a
// straightforward way.
//
// Instead, we always call HideWindowChrome() just before we call Resize().
// This at least ensures that our measurements are consistently taken in a
// pre-transition state.
//
// ... unfortunately again, coupling HideWindowChrome() to Resize() means that
// we have to worry about the possibility of control flows that don't call
// Resize() at all. (That shouldn't happen, but it's not trivial to rule out.)
// We therefore set up a fallback to fix up the OS chrome if it hasn't been
// done at exit time.
bool hasAdjustedOSChrome = false;
const auto adjustOSChrome = [&]() {
if (hasAdjustedOSChrome) {
MOZ_ASSERT_UNREACHABLE("window chrome should only be adjusted once");
return;
}
HideWindowChrome(aFullScreen);
hasAdjustedOSChrome = true;
};
const auto adjustChromeOnScopeExit = MakeScopeExit([&]() {
if (hasAdjustedOSChrome) {
return;
}
MOZ_LOG(sBaseWidgetLog, LogLevel::Warning,
("window was not resized within InfallibleMakeFullScreen()"));
// Hide chrome and "resize" the window to its current size.
auto rect = GetBounds();
adjustOSChrome();
Resize(rect.X(), rect.Y(), rect.Width(), rect.Height(), true);
});
// Attempt to resize to `rect`.
//
// Returns the actual rectangle resized to. (This may differ from `rect`, if
// the OS is unhappy with it. See bug 1786226.)
const auto doReposition = [&](auto rect) -> void {
static_assert(std::is_base_of_v<DesktopPixel,
std::remove_reference_t<decltype(rect)>>,
"doReposition requires a rectangle using desktop pixels");
if (MOZ_LOG_TEST(sBaseWidgetLog, LogLevel::Debug)) {
const DesktopRect previousSize =
GetScreenBounds() / GetDesktopToDeviceScale();
MOZ_LOG(sBaseWidgetLog, LogLevel::Debug,
("before resize: " MOZ_FORMAT_RECT("%f"),
MOZ_SPLAT_RECT(previousSize)));
}
adjustOSChrome();
Resize(rect.X(), rect.Y(), rect.Width(), rect.Height(), true);
if (MOZ_LOG_TEST(sBaseWidgetLog, LogLevel::Warning)) {
// `rect` may have any underlying data type; coerce to float to
// simplify printf-style logging
const gfx::RectTyped<DesktopPixel, float> rectAsFloat{rect};
// The OS may have objected to the target position. That's not necessarily
// a problem -- it'll happen regularly on Macs with camera notches in the
// monitor, for instance (see bug 1786226) -- but it probably deserves to
// be called out.
//
// Since there's floating-point math involved, the actual values may be
// off by a few ulps -- as an upper bound, perhaps 8 * FLT_EPSILON *
// max(MOZ_SPLAT_RECT(rect)) -- but 0.01 should be several orders of
// magnitude bigger than that.
const auto postResizeRectRaw = GetScreenBounds();
const auto postResizeRect = postResizeRectRaw / GetDesktopToDeviceScale();
const bool succeeded = postResizeRect.WithinEpsilonOf(rectAsFloat, 0.01);
if (succeeded) {
MOZ_LOG(sBaseWidgetLog, LogLevel::Debug,
("resized to: " MOZ_FORMAT_RECT("%f"),
MOZ_SPLAT_RECT(rectAsFloat)));
} else {
MOZ_LOG(sBaseWidgetLog, LogLevel::Warning,
("attempted to resize to: " MOZ_FORMAT_RECT("%f"),
MOZ_SPLAT_RECT(rectAsFloat)));
MOZ_LOG(sBaseWidgetLog, LogLevel::Warning,
("... but ended up at: " MOZ_FORMAT_RECT("%f"),
MOZ_SPLAT_RECT(postResizeRect)));
}
MOZ_LOG(
sBaseWidgetLog, LogLevel::Verbose,
("(... which, before DPI adjustment, is:" MOZ_FORMAT_RECT("%d") ")",
MOZ_SPLAT_RECT(postResizeRectRaw)));
}
};
if (aFullScreen) {
if (!mSavedBounds) {
mSavedBounds = Some(FullscreenSavedState());
}
// save current position
mSavedBounds->windowRect = GetScreenBounds() / GetDesktopToDeviceScale();
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (!screen) {
return;
}
// Move to fill the screen.
doReposition(screen->GetRectDisplayPix());
// Save off the new position. (This may differ from GetRectDisplayPix(), if
// the OS was unhappy with it. See bug 1786226.)
mSavedBounds->screenRect = GetScreenBounds() / GetDesktopToDeviceScale();
} else {
if (!mSavedBounds) {
// This should never happen, at present, since we don't make windows
// fullscreen at their creation time; but it's not logically impossible.
MOZ_ASSERT(false, "fullscreen window did not have saved position");
return;
}
// Figure out where to go from here.
//
// Fortunately, since we're currently fullscreen (and other code should be
// handling _keeping_ us fullscreen even after display-layout changes),
// there's an obvious choice for which display we should attach to; all we
// need to determine is where on that display we should go.
const DesktopRect currentWinRect =
GetScreenBounds() / GetDesktopToDeviceScale();
// Optimization: if where we are is where we were, then where we originally
// came from is where we're going to go.
if (currentWinRect == DesktopRect(mSavedBounds->screenRect)) {
MOZ_LOG(sBaseWidgetLog, LogLevel::Debug,
("no location change detected; returning to saved location"));
doReposition(mSavedBounds->windowRect);
return;
}
/*
General case: figure out where we're going to go by dividing where we are
by where we were, and then multiplying by where we originally came from.
Less abstrusely: resize so that we occupy the same proportional position
on our current display after leaving fullscreen as we occupied on our
previous display before entering fullscreen.
(N.B.: We do not clamp. If we were only partially on the old display,
we'll be only partially on the new one, too.)
*/
MOZ_LOG(sBaseWidgetLog, LogLevel::Debug,
("location change detected; computing new destination"));
// splat: convert an arbitrary Rect into a tuple, for syntactic convenience.
const auto splat = [](auto rect) {
return std::tuple(rect.X(), rect.Y(), rect.Width(), rect.Height());
};
// remap: find the unique affine mapping which transforms `src` to `dst`,
// and apply it to `val`.
using Range = std::pair<float, float>;
const auto remap = [](Range dst, Range src, float val) {
// linear interpolation and its inverse: lerp(a, b, invlerp(a, b, t)) == t
const auto lerp = [](float lo, float hi, float t) {
return lo + t * (hi - lo);
};
const auto invlerp = [](float lo, float hi, float mid) {
return (mid - lo) / (hi - lo);
};
const auto [dst_a, dst_b] = dst;
const auto [src_a, src_b] = src;
return lerp(dst_a, dst_b, invlerp(src_a, src_b, val));
};
// original position
const auto [px, py, pw, ph] = splat(mSavedBounds->windowRect);
// source desktop rect
const auto [sx, sy, sw, sh] = splat(mSavedBounds->screenRect);
// target desktop rect
const auto [tx, ty, tw, th] = splat(currentWinRect);
const float nx = remap({tx, tx + tw}, {sx, sx + sw}, px);
const float ny = remap({ty, ty + th}, {sy, sy + sh}, py);
const float nw = remap({0, tw}, {0, sw}, pw);
const float nh = remap({0, th}, {0, sh}, ph);
doReposition(DesktopRect{nx, ny, nw, nh});
}
#undef MOZ_SPLAT_RECT
#undef MOZ_FORMAT_RECT
}
nsresult nsBaseWidget::MakeFullScreen(bool aFullScreen) {
InfallibleMakeFullScreen(aFullScreen);
return NS_OK;
}
nsBaseWidget::AutoLayerManagerSetup::AutoLayerManagerSetup(
nsBaseWidget* aWidget, gfxContext* aTarget, BufferMode aDoubleBuffering)
: mWidget(aWidget) {
WindowRenderer* renderer = mWidget->GetWindowRenderer();
if (renderer->AsFallback()) {
mRenderer = renderer->AsFallback();
mRenderer->SetTarget(aTarget, aDoubleBuffering);
}
}
nsBaseWidget::AutoLayerManagerSetup::~AutoLayerManagerSetup() {
if (mRenderer) {
mRenderer->SetTarget(nullptr, mozilla::layers::BufferMode::BUFFER_NONE);
}
}
bool nsBaseWidget::IsSmallPopup() const {
return mWindowType == WindowType::Popup && mPopupType != PopupType::Panel;
}
bool nsBaseWidget::ComputeShouldAccelerate() {
return gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING) &&
(WidgetTypeSupportsAcceleration() ||
StaticPrefs::gfx_webrender_unaccelerated_widget_force());
}
bool nsBaseWidget::UseAPZ() {
return (gfxPlatform::AsyncPanZoomEnabled() &&
(mWindowType == WindowType::TopLevel ||
mWindowType == WindowType::Child ||
((mWindowType == WindowType::Popup ||
mWindowType == WindowType::Dialog) &&
HasRemoteContent() && StaticPrefs::apz_popups_enabled())));
}
void nsBaseWidget::CreateCompositor() {
LayoutDeviceIntRect rect = GetBounds();
CreateCompositor(rect.Width(), rect.Height());
}
void nsIWidget::PauseOrResumeCompositor(bool aPause) {
auto* renderer = GetRemoteRenderer();
if (!renderer) {
return;
}
if (aPause) {
renderer->SendPause();
} else {
renderer->SendResume();
}
}
already_AddRefed<GeckoContentController>
nsBaseWidget::CreateRootContentController() {
RefPtr<GeckoContentController> controller =
new ChromeProcessController(this, mAPZEventState, mAPZC);
return controller.forget();
}
void nsBaseWidget::ConfigureAPZCTreeManager() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mAPZC);
mAPZC->SetDPI(GetDPI());
if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
KeyboardMap map = RootWindowGlobalKeyListener::CollectKeyboardShortcuts();
mAPZC->SetKeyboardMap(map);
}
ContentReceivedInputBlockCallback callback(
[treeManager = RefPtr{mAPZC.get()}](uint64_t aInputBlockId,
bool aPreventDefault) {
MOZ_ASSERT(NS_IsMainThread());
treeManager->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
});
mAPZEventState = new APZEventState(this, std::move(callback));
mRootContentController = CreateRootContentController();
if (mRootContentController) {
mCompositorSession->SetContentController(mRootContentController);
}
// When APZ is enabled, we can actually enable raw touch events because we
// have code that can deal with them properly. If APZ is not enabled, this
// function doesn't get called.
if (StaticPrefs::dom_w3c_touch_events_enabled()) {
RegisterTouchWindow();
}
}
void nsBaseWidget::ConfigureAPZControllerThread() {
// By default the controller thread is the main thread.
APZThreadUtils::SetControllerThread(NS_GetCurrentThread());
}
void nsBaseWidget::SetConfirmedTargetAPZC(
uint64_t aInputBlockId,
const nsTArray<ScrollableLayerGuid>& aTargets) const {
mAPZC->SetTargetAPZC(aInputBlockId, aTargets);
}
void nsBaseWidget::UpdateZoomConstraints(
const uint32_t& aPresShellId, const ScrollableLayerGuid::ViewID& aViewId,
const Maybe<ZoomConstraints>& aConstraints) {
if (!mCompositorSession || !mAPZC) {
MOZ_ASSERT_IF(mInitialZoomConstraints,
mInitialZoomConstraints->mViewID == aViewId);
if (aConstraints) {
// We have some constraints, but the compositor and APZC aren't
// created yet. Save these so we can use them later.
mInitialZoomConstraints = Some(
InitialZoomConstraints(aPresShellId, aViewId, aConstraints.ref()));
} else {
mInitialZoomConstraints.reset();
}
return;
}
LayersId layersId = mCompositorSession->RootLayerTreeId();
mAPZC->UpdateZoomConstraints(
ScrollableLayerGuid(layersId, aPresShellId, aViewId), aConstraints);
}
bool nsBaseWidget::AsyncPanZoomEnabled() const { return !!mAPZC; }
nsEventStatus nsBaseWidget::ProcessUntransformedAPZEvent(
WidgetInputEvent* aEvent, const APZEventResult& aApzResult) {
MOZ_ASSERT(NS_IsMainThread());
ScrollableLayerGuid targetGuid = aApzResult.mTargetGuid;
uint64_t inputBlockId = aApzResult.mInputBlockId;
InputAPZContext context(aApzResult.mTargetGuid, inputBlockId,
aApzResult.GetStatus());
// Make a copy of the original event for the APZCCallbackHelper helpers that
// we call later, because the event passed to DispatchEvent can get mutated in
// ways that we don't want (i.e. touch points can get stripped out).
nsEventStatus status;
UniquePtr<WidgetEvent> original(aEvent->Duplicate());
DispatchEvent(aEvent, status);
if (mAPZC && !InputAPZContext::WasRoutedToChildProcess() &&
!InputAPZContext::WasDropped() && inputBlockId) {
// EventStateManager did not route the event into the child process and
// the event was dispatched in the parent process.
// It's safe to communicate to APZ that the event has been processed.
// Note that here aGuid.mLayersId might be different from
// mCompositorSession->RootLayerTreeId() because the event might have gotten
// hit-tested by APZ to be targeted at a child process, but a parent process
// event listener called preventDefault on it. In that case aGuid.mLayersId
// would still be the layers id for the child process, but the event would
// not have actually gotten routed to the child process. The main-thread
// hit-test result therefore needs to use the parent process layers id.
LayersId rootLayersId = mCompositorSession->RootLayerTreeId();
RefPtr<DisplayportSetListener> postLayerization;
if (WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent()) {
nsTArray<TouchBehaviorFlags> allowedTouchBehaviors;
if (touchEvent->mMessage == eTouchStart) {
auto& originalEvent = *original->AsTouchEvent();
MOZ_ASSERT(NS_IsMainThread());
allowedTouchBehaviors = TouchActionHelper::GetAllowedTouchBehavior(
this, GetDocument(), originalEvent);
if (!allowedTouchBehaviors.IsEmpty()) {
mAPZC->SetAllowedTouchBehavior(inputBlockId, allowedTouchBehaviors);
}
postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
this, GetDocument(), originalEvent, rootLayersId, inputBlockId);
}
mAPZEventState->ProcessTouchEvent(*touchEvent, targetGuid, inputBlockId,
aApzResult.GetStatus(), status,
std::move(allowedTouchBehaviors));
} else if (WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent()) {
MOZ_ASSERT(wheelEvent->mFlags.mHandledByAPZ);
postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
this, GetDocument(), *original->AsWheelEvent(), rootLayersId,
inputBlockId);
if (wheelEvent->mCanTriggerSwipe) {
ReportSwipeStarted(inputBlockId, wheelEvent->TriggersSwipe());
}
mAPZEventState->ProcessWheelEvent(*wheelEvent, inputBlockId);
} else if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
MOZ_ASSERT(mouseEvent->mFlags.mHandledByAPZ);
postLayerization = APZCCallbackHelper::SendSetTargetAPZCNotification(
this, GetDocument(), *original->AsMouseEvent(), rootLayersId,
inputBlockId);
mAPZEventState->ProcessMouseEvent(*mouseEvent, inputBlockId);
}
if (postLayerization) {
postLayerization->Register();
}
}
return status;
}
template <class InputType, class EventType>
class DispatchEventOnMainThread : public Runnable {
public:
DispatchEventOnMainThread(const InputType& aInput, nsBaseWidget* aWidget,
const APZEventResult& aAPZResult)
: mozilla::Runnable("DispatchEventOnMainThread"),
mInput(aInput),
mWidget(aWidget),
mAPZResult(aAPZResult) {}
NS_IMETHOD Run() override {
EventType event = mInput.ToWidgetEvent(mWidget);
mWidget->ProcessUntransformedAPZEvent(&event, mAPZResult);
return NS_OK;
}
private:
InputType mInput;
nsBaseWidget* mWidget;
APZEventResult mAPZResult;
};
template <>
NS_IMETHODIMP DispatchEventOnMainThread<MouseInput, WidgetMouseEvent>::Run() {
MOZ_ASSERT(
!mInput.IsPointerEventType(),
"Please use DispatchEventOnMainThread<MouseInput, WidgetPointerEvent>");
WidgetMouseEvent event = mInput.ToWidgetEvent<WidgetMouseEvent>(mWidget);
mWidget->ProcessUntransformedAPZEvent(&event, mAPZResult);
return NS_OK;
}
template <>
NS_IMETHODIMP DispatchEventOnMainThread<MouseInput, WidgetPointerEvent>::Run() {
MOZ_ASSERT(
mInput.IsPointerEventType(),
"Please use DispatchEventOnMainThread<MouseInput, WidgetMouseEvent>");
WidgetPointerEvent event = mInput.ToWidgetEvent<WidgetPointerEvent>(mWidget);
mWidget->ProcessUntransformedAPZEvent(&event, mAPZResult);
return NS_OK;
}
template <class InputType, class EventType>
class DispatchInputOnControllerThread : public Runnable {
public:
enum class APZOnly { Yes, No };
DispatchInputOnControllerThread(const EventType& aEvent,
IAPZCTreeManager* aAPZC,
nsBaseWidget* aWidget,
APZOnly aAPZOnly = APZOnly::No)
: mozilla::Runnable("DispatchInputOnControllerThread"),
mMainMessageLoop(MessageLoop::current()),
mInput(aEvent),
mAPZC(aAPZC),
mWidget(aWidget),
mAPZOnly(aAPZOnly) {}
NS_IMETHOD Run() override {
APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(mInput);
if (mAPZOnly == APZOnly::Yes ||
result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return NS_OK;
}
RefPtr<Runnable> r = new DispatchEventOnMainThread<InputType, EventType>(
mInput, mWidget, result);
mMainMessageLoop->PostTask(r.forget());
return NS_OK;
}
private:
MessageLoop* mMainMessageLoop;
InputType mInput;
RefPtr<IAPZCTreeManager> mAPZC;
nsBaseWidget* mWidget;
const APZOnly mAPZOnly;
};
void nsBaseWidget::DispatchTouchInput(MultiTouchInput& aInput,
uint16_t aInputSource) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aInputSource ==
mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH ||
aInputSource == mozilla::dom::MouseEvent_Binding::MOZ_SOURCE_PEN);
if (mAPZC) {
MOZ_ASSERT(APZThreadUtils::IsControllerThread());
APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return;
}
WidgetTouchEvent event = aInput.ToWidgetEvent(this, aInputSource);
ProcessUntransformedAPZEvent(&event, result);
} else {
WidgetTouchEvent event = aInput.ToWidgetEvent(this, aInputSource);
nsEventStatus status;
DispatchEvent(&event, status);
}
}
void nsBaseWidget::DispatchPanGestureInput(PanGestureInput& aInput) {
MOZ_ASSERT(NS_IsMainThread());
if (mAPZC) {
MOZ_ASSERT(APZThreadUtils::IsControllerThread());
APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return;
}
WidgetWheelEvent event = aInput.ToWidgetEvent(this);
ProcessUntransformedAPZEvent(&event, result);
} else {
WidgetWheelEvent event = aInput.ToWidgetEvent(this);
nsEventStatus status;
DispatchEvent(&event, status);
}
}
void nsBaseWidget::DispatchPinchGestureInput(PinchGestureInput& aInput) {
MOZ_ASSERT(NS_IsMainThread());
if (mAPZC) {
MOZ_ASSERT(APZThreadUtils::IsControllerThread());
APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(aInput);
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return;
}
WidgetWheelEvent event = aInput.ToWidgetEvent(this);
ProcessUntransformedAPZEvent(&event, result);
} else {
WidgetWheelEvent event = aInput.ToWidgetEvent(this);
nsEventStatus status;
DispatchEvent(&event, status);
}
}
nsIWidget::ContentAndAPZEventStatus nsBaseWidget::DispatchInputEvent(
WidgetInputEvent* aEvent) {
nsIWidget::ContentAndAPZEventStatus status;
MOZ_ASSERT(NS_IsMainThread());
if (mAPZC) {
if (APZThreadUtils::IsControllerThread()) {
APZEventResult result = mAPZC->InputBridge()->ReceiveInputEvent(*aEvent);
status.mApzStatus = result.GetStatus();
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return status;
}
status.mContentStatus = ProcessUntransformedAPZEvent(aEvent, result);
return status;
}
// Most drag events aren't able to converted to MouseEvent except to
// eDragStart and eDragEnd.
const bool canDispatchToApzc =
!aEvent->AsDragEvent() ||
aEvent->AsDragEvent()->CanConvertToInputData();
if (canDispatchToApzc) {
if (WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent()) {
RefPtr<Runnable> r =
new DispatchInputOnControllerThread<ScrollWheelInput,
WidgetWheelEvent>(*wheelEvent,
mAPZC, this);
APZThreadUtils::RunOnControllerThread(std::move(r));
status.mContentStatus = nsEventStatus_eConsumeDoDefault;
return status;
}
if (WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent()) {
MOZ_ASSERT(aEvent->mMessage == eContextMenu);
RefPtr<Runnable> r =
new DispatchInputOnControllerThread<MouseInput, WidgetPointerEvent>(
*pointerEvent, mAPZC, this);
APZThreadUtils::RunOnControllerThread(std::move(r));
status.mContentStatus = nsEventStatus_eConsumeDoDefault;
return status;
}
if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
RefPtr<Runnable> r =
new DispatchInputOnControllerThread<MouseInput, WidgetMouseEvent>(
*mouseEvent, mAPZC, this);
APZThreadUtils::RunOnControllerThread(std::move(r));
status.mContentStatus = nsEventStatus_eConsumeDoDefault;
return status;
}
if (WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent()) {
RefPtr<Runnable> r =
new DispatchInputOnControllerThread<MultiTouchInput,
WidgetTouchEvent>(*touchEvent,
mAPZC, this);
APZThreadUtils::RunOnControllerThread(std::move(r));
status.mContentStatus = nsEventStatus_eConsumeDoDefault;
return status;
}
// Allow dispatching keyboard/drag events on Gecko thread
// without sending them to APZ
// FIXME: APZ can handle keyboard events now, we should
// be sending them to APZ here
MOZ_ASSERT(aEvent->AsKeyboardEvent() || aEvent->AsDragEvent());
}
}
DispatchEvent(aEvent, status.mContentStatus);
return status;
}
void nsBaseWidget::DispatchEventToAPZOnly(mozilla::WidgetInputEvent* aEvent) {
MOZ_ASSERT(NS_IsMainThread());
if (mAPZC) {
if (APZThreadUtils::IsControllerThread()) {
mAPZC->InputBridge()->ReceiveInputEvent(*aEvent);
return;
}
if (WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
RefPtr<Runnable> r =
new DispatchInputOnControllerThread<MouseInput, WidgetMouseEvent>(
*mouseEvent, mAPZC, this,
DispatchInputOnControllerThread<MouseInput,
WidgetMouseEvent>::APZOnly::Yes);
APZThreadUtils::RunOnControllerThread(std::move(r));
return;
}
MOZ_ASSERT_UNREACHABLE("Not implemented yet");
}
}
bool nsBaseWidget::DispatchWindowEvent(WidgetGUIEvent& event) {
nsEventStatus status;
DispatchEvent(&event, status);
return ConvertStatus(status);
}
Document* nsBaseWidget::GetDocument() const {
if (mWidgetListener) {
if (PresShell* presShell = mWidgetListener->GetPresShell()) {
return presShell->GetDocument();
}
}
return nullptr;
}
void nsBaseWidget::CreateCompositorVsyncDispatcher() {
// Parent directly listens to the vsync source whereas
// child process communicate via IPC
// Should be called AFTER gfxPlatform is initialized
if (XRE_IsParentProcess()) {
if (!mCompositorVsyncDispatcherLock) {
mCompositorVsyncDispatcherLock =
MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
}
MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
if (!mCompositorVsyncDispatcher) {
RefPtr<VsyncDispatcher> vsyncDispatcher =
gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
mCompositorVsyncDispatcher =
new CompositorVsyncDispatcher(std::move(vsyncDispatcher));
}
}
}
already_AddRefed<CompositorVsyncDispatcher>
nsBaseWidget::GetCompositorVsyncDispatcher() {
MOZ_ASSERT(mCompositorVsyncDispatcherLock.get());
MutexAutoLock lock(*mCompositorVsyncDispatcherLock.get());
RefPtr<CompositorVsyncDispatcher> dispatcher = mCompositorVsyncDispatcher;
return dispatcher.forget();
}
already_AddRefed<WebRenderLayerManager> nsBaseWidget::CreateCompositorSession(
int aWidth, int aHeight, CompositorOptions* aOptionsOut) {
MOZ_ASSERT(aOptionsOut);
do {
CreateCompositorVsyncDispatcher();
gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get();
// Make sure GPU process is ready for use.
// If it failed to connect to GPU process, GPU process usage is disabled in
// EnsureGPUReady(). It could update gfxVars and gfxConfigs.
nsresult rv = gpu->EnsureGPUReady();
if (NS_WARN_IF(rv == NS_ERROR_ILLEGAL_DURING_SHUTDOWN)) {
return nullptr;
}
// If widget type does not supports acceleration, we may be allowed to use
// software WebRender instead.
bool supportsAcceleration = WidgetTypeSupportsAcceleration();
bool enableSWWR = true;
if (supportsAcceleration ||
StaticPrefs::gfx_webrender_unaccelerated_widget_force()) {
enableSWWR = gfx::gfxVars::UseSoftwareWebRender();
}
bool enableAPZ = UseAPZ();
CompositorOptions options(enableAPZ, enableSWWR);
#ifdef XP_WIN
if (supportsAcceleration) {
options.SetAllowSoftwareWebRenderD3D11(
gfx::gfxVars::AllowSoftwareWebRenderD3D11());
}
if (mNeedFastSnaphot) {
options.SetNeedFastSnaphot(true);
}
#elif defined(MOZ_WIDGET_ANDROID)
MOZ_ASSERT(supportsAcceleration);
options.SetAllowSoftwareWebRenderOGL(
gfx::gfxVars::AllowSoftwareWebRenderOGL());
#elif defined(MOZ_WIDGET_GTK)
if (supportsAcceleration) {
options.SetAllowSoftwareWebRenderOGL(
gfx::gfxVars::AllowSoftwareWebRenderOGL());
}
#endif
#ifdef MOZ_WIDGET_ANDROID
// Unconditionally set the compositor as initially paused, as we have not
// yet had a chance to send the compositor surface to the GPU process. We
// will do so shortly once we have returned to nsWindow::CreateLayerManager,
// where we will also resume the compositor if required.
options.SetInitiallyPaused(true);
#else
options.SetInitiallyPaused(CompositorInitiallyPaused());
#endif
RefPtr<WebRenderLayerManager> lm = new WebRenderLayerManager(this);
uint64_t innerWindowId = 0;
if (Document* doc = GetDocument()) {
innerWindowId = doc->InnerWindowID();
}
bool retry = false;
mCompositorSession = gpu->CreateTopLevelCompositor(
this, lm, GetDefaultScale(), options, UseExternalCompositingSurface(),
gfx::IntSize(aWidth, aHeight), innerWindowId, &retry);
if (mCompositorSession) {
TextureFactoryIdentifier textureFactoryIdentifier;
nsCString error;
lm->Initialize(mCompositorSession->GetCompositorBridgeChild(),
wr::AsPipelineId(mCompositorSession->RootLayerTreeId()),
&textureFactoryIdentifier, error);
if (textureFactoryIdentifier.mParentBackend != LayersBackend::LAYERS_WR) {
retry = true;
DestroyCompositor();
// gfxVars::UseDoubleBufferingWithCompositor() is also disabled.
gfx::GPUProcessManager::Get()->DisableWebRender(
wr::WebRenderError::INITIALIZE, error);
}
}
// We need to retry in a loop because the act of failing to create the
// compositor can change our state (e.g. disable WebRender).
if (mCompositorSession || !retry) {
*aOptionsOut = options;
return lm.forget();
}
} while (true);
}
void nsBaseWidget::CreateCompositor(int aWidth, int aHeight) {
// This makes sure that gfxPlatforms gets initialized if it hasn't by now.
gfxPlatform::GetPlatform();
MOZ_ASSERT(gfxPlatform::UsesOffMainThreadCompositing(),
"This function assumes OMTC");
MOZ_ASSERT(!mCompositorSession && !mCompositorBridgeChild,
"Should have properly cleaned up the previous PCompositor pair "
"beforehand");
if (mCompositorBridgeChild) {
mCompositorBridgeChild->Destroy();
}
// Recreating this is tricky, as we may still have an old and we need
// to make sure it's properly destroyed by calling DestroyCompositor!
// If we've already received a shutdown notification, don't try
// create a new compositor.
if (!mShutdownObserver) {
return;
}
// The controller thread must be configured before the compositor
// session is created, so that the input bridge runs on the right
// thread.
ConfigureAPZControllerThread();
CompositorOptions options;
RefPtr<WebRenderLayerManager> lm =
CreateCompositorSession(aWidth, aHeight, &options);
if (!lm) {
return;
}
MOZ_ASSERT(mCompositorSession);
mCompositorBridgeChild = mCompositorSession->GetCompositorBridgeChild();
SetCompositorWidgetDelegate(
mCompositorSession->GetCompositorWidgetDelegate());
if (options.UseAPZ()) {
mAPZC = mCompositorSession->GetAPZCTreeManager();
ConfigureAPZCTreeManager();
} else {
mAPZC = nullptr;
}
if (mInitialZoomConstraints) {
UpdateZoomConstraints(mInitialZoomConstraints->mPresShellID,
mInitialZoomConstraints->mViewID,
Some(mInitialZoomConstraints->mConstraints));
mInitialZoomConstraints.reset();
}
TextureFactoryIdentifier textureFactoryIdentifier =
lm->GetTextureFactoryIdentifier();
MOZ_ASSERT(textureFactoryIdentifier.mParentBackend ==
LayersBackend::LAYERS_WR);
ImageBridgeChild::IdentifyCompositorTextureHost(textureFactoryIdentifier);
gfx::VRManagerChild::IdentifyTextureHost(textureFactoryIdentifier);
WindowUsesOMTC();
mWindowRenderer = std::move(lm);
// Only track compositors for top-level windows, since other window types
// may use the basic compositor. Except on the OS X - see bug 1306383
#if defined(XP_MACOSX)
bool getCompositorFromThisWindow = true;
#else
bool getCompositorFromThisWindow = mWindowType == WindowType::TopLevel;
#endif
if (getCompositorFromThisWindow) {
gfxPlatform::GetPlatform()->NotifyCompositorCreated(
mWindowRenderer->GetCompositorBackendType());
}
}
void nsBaseWidget::NotifyCompositorSessionLost(CompositorSession* aSession) {
MOZ_ASSERT(aSession == mCompositorSession);
DestroyLayerManager();
}
bool nsBaseWidget::ShouldUseOffMainThreadCompositing() {
return gfxPlatform::UsesOffMainThreadCompositing();
}
WindowRenderer* nsBaseWidget::GetWindowRenderer() {
if (!mWindowRenderer) {
if (!mShutdownObserver) {
// We are shutting down, do not try to re-create a LayerManager
return nullptr;
}
// Try to use an async compositor first, if possible
if (ShouldUseOffMainThreadCompositing()) {
CreateCompositor();
}
if (!mWindowRenderer) {
mWindowRenderer = CreateFallbackRenderer();
}
}
return mWindowRenderer;
}
WindowRenderer* nsBaseWidget::CreateFallbackRenderer() {
return new FallbackRenderer;
}
CompositorBridgeChild* nsBaseWidget::GetRemoteRenderer() {
return mCompositorBridgeChild;
}
void nsBaseWidget::ClearCachedWebrenderResources() {
if (!mWindowRenderer || !mWindowRenderer->AsWebRender()) {
return;
}
mWindowRenderer->AsWebRender()->ClearCachedResources();
}
void nsBaseWidget::ClearWebrenderAnimationResources() {
if (!mWindowRenderer || !mWindowRenderer->AsWebRender()) {
return;
}
mWindowRenderer->AsWebRender()->ClearAnimationResources();
}
bool nsBaseWidget::SetNeedFastSnaphot() {
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!mCompositorSession);
if (!XRE_IsParentProcess() || mCompositorSession) {
return false;
}
mNeedFastSnaphot = true;
return true;
}
already_AddRefed<gfx::DrawTarget> nsBaseWidget::StartRemoteDrawing() {
return nullptr;
}
uint32_t nsBaseWidget::GetGLFrameBufferFormat() { return LOCAL_GL_RGBA; }
//-------------------------------------------------------------------------
//
// Destroy the window
//
//-------------------------------------------------------------------------
void nsBaseWidget::OnDestroy() {
if (mTextEventDispatcher) {
mTextEventDispatcher->OnDestroyWidget();
// Don't release it until this widget actually released because after this
// is called, TextEventDispatcher() may create it again.
}
// If this widget is being destroyed, let the APZ code know to drop references
// to this widget. Callers of this function all should be holding a deathgrip
// on this widget already.
ReleaseContentController();
}
void nsBaseWidget::MoveClient(const DesktopPoint& aOffset) {
LayoutDeviceIntPoint clientOffset(GetClientOffset());
// GetClientOffset returns device pixels; scale back to desktop pixels
// if that's what this widget uses for the Move/Resize APIs
if (BoundsUseDesktopPixels()) {
DesktopPoint desktopOffset = clientOffset / GetDesktopToDeviceScale();
Move(aOffset.x - desktopOffset.x, aOffset.y - desktopOffset.y);
} else {
LayoutDevicePoint layoutOffset = aOffset * GetDesktopToDeviceScale();
Move(layoutOffset.x - LayoutDeviceCoord(clientOffset.x),
layoutOffset.y - LayoutDeviceCoord(clientOffset.y));
}
}
void nsBaseWidget::ResizeClient(const DesktopSize& aSize, bool aRepaint) {
NS_ASSERTION((aSize.width >= 0), "Negative width passed to ResizeClient");
NS_ASSERTION((aSize.height >= 0), "Negative height passed to ResizeClient");
LayoutDeviceIntRect clientBounds = GetClientBounds();
// GetClientBounds and mBounds are device pixels; scale back to desktop pixels
// if that's what this widget uses for the Move/Resize APIs
if (BoundsUseDesktopPixels()) {
DesktopSize desktopDelta =
(LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()) -
clientBounds.Size()) /
GetDesktopToDeviceScale();
Resize(aSize.width + desktopDelta.width, aSize.height + desktopDelta.height,
aRepaint);
} else {
LayoutDeviceSize layoutSize = aSize * GetDesktopToDeviceScale();
Resize(mBounds.Width() + (layoutSize.width - clientBounds.Width()),
mBounds.Height() + (layoutSize.height - clientBounds.Height()),
aRepaint);
}
}
void nsBaseWidget::ResizeClient(const DesktopRect& aRect, bool aRepaint) {
NS_ASSERTION((aRect.Width() >= 0), "Negative width passed to ResizeClient");
NS_ASSERTION((aRect.Height() >= 0), "Negative height passed to ResizeClient");
LayoutDeviceIntRect clientBounds = GetClientBounds();
LayoutDeviceIntPoint clientOffset = GetClientOffset();
DesktopToLayoutDeviceScale scale = GetDesktopToDeviceScale();
if (BoundsUseDesktopPixels()) {
DesktopPoint desktopOffset = clientOffset / scale;
DesktopSize desktopDelta =
(LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()) -
clientBounds.Size()) /
scale;
Resize(aRect.X() - desktopOffset.x, aRect.Y() - desktopOffset.y,
aRect.Width() + desktopDelta.width,
aRect.Height() + desktopDelta.height, aRepaint);
} else {
LayoutDeviceRect layoutRect = aRect * scale;
Resize(layoutRect.X() - clientOffset.x, layoutRect.Y() - clientOffset.y,
layoutRect.Width() + mBounds.Width() - clientBounds.Width(),
layoutRect.Height() + mBounds.Height() - clientBounds.Height(),
aRepaint);
}
}
//-------------------------------------------------------------------------
//
// Bounds
//
//-------------------------------------------------------------------------
/**
* If the implementation of nsWindow supports borders this method MUST be
* overridden
*
**/
LayoutDeviceIntRect nsBaseWidget::GetClientBounds() { return GetBounds(); }
/**
* If the implementation of nsWindow supports borders this method MUST be
* overridden
*
**/
LayoutDeviceIntRect nsBaseWidget::GetBounds() { return mBounds; }
/**