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
#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 "nsBaseDragService.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) {
nsCOMPtr<nsIWidget> widget;
switch (mWidgetType) {
case WidgetType::Native: {
if (aInitData.mWindowType == WindowType::Popup) {
widget = AllocateChildPopupWidget();
} else {
widget = nsIWidget::CreateChildWindow();
}
break;
}
case WidgetType::Headless:
widget = nsIWidget::CreateHeadlessWidget();
break;
case WidgetType::Puppet: {
// This really only should happen in crashtests that have menupopups.
MOZ_ASSERT(aInitData.mWindowType == WindowType::Popup,
"Creating non-popup puppet widget?");
widget = nsIWidget::CreatePuppetWidget(nullptr);
break;
}
}
if (!widget) {
return nullptr;
}
if (mNeedFastSnaphot) {
widget->SetNeedFastSnaphot();
}
if (NS_FAILED(widget->Create(this, aRect, &aInitData))) {
return nullptr;
}
return widget.forget();
}
// 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
// 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
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
// 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
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(