Backed out changeset 0a133d5fd155 (bug 1864534) for causing screenshot related failur...
[gecko.git] / widget / headless / HeadlessWidget.cpp
blob083d026d3c019cb76fff2b8f605f3d6ef8dd578f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "HeadlessWidget.h"
6 #include "ErrorList.h"
7 #include "HeadlessCompositorWidget.h"
8 #include "BasicEvents.h"
9 #include "MouseEvents.h"
10 #include "mozilla/gfx/gfxVars.h"
11 #include "mozilla/ClearOnShutdown.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/NativeKeyBindingsType.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/TextEventDispatcher.h"
16 #include "mozilla/TextEvents.h"
17 #include "mozilla/WritingModes.h"
18 #include "mozilla/widget/HeadlessWidgetTypes.h"
19 #include "mozilla/widget/PlatformWidgetTypes.h"
20 #include "mozilla/widget/Screen.h"
21 #include "nsIScreen.h"
22 #include "HeadlessKeyBindings.h"
24 using namespace mozilla;
25 using namespace mozilla::gfx;
26 using namespace mozilla::layers;
28 using mozilla::LogLevel;
30 #ifdef MOZ_LOGGING
32 # include "mozilla/Logging.h"
33 static mozilla::LazyLogModule sWidgetLog("Widget");
34 static mozilla::LazyLogModule sWidgetFocusLog("WidgetFocus");
35 # define LOG(args) MOZ_LOG(sWidgetLog, mozilla::LogLevel::Debug, args)
36 # define LOGFOCUS(args) \
37 MOZ_LOG(sWidgetFocusLog, mozilla::LogLevel::Debug, args)
39 #else
41 # define LOG(args)
42 # define LOGFOCUS(args)
44 #endif /* MOZ_LOGGING */
46 /*static*/
47 already_AddRefed<nsIWidget> nsIWidget::CreateHeadlessWidget() {
48 nsCOMPtr<nsIWidget> widget = new mozilla::widget::HeadlessWidget();
49 return widget.forget();
52 namespace mozilla {
53 namespace widget {
55 StaticAutoPtr<nsTArray<HeadlessWidget*>> HeadlessWidget::sActiveWindows;
57 already_AddRefed<HeadlessWidget> HeadlessWidget::GetActiveWindow() {
58 if (!sActiveWindows) {
59 return nullptr;
61 auto length = sActiveWindows->Length();
62 if (length == 0) {
63 return nullptr;
65 RefPtr<HeadlessWidget> widget = sActiveWindows->ElementAt(length - 1);
66 return widget.forget();
69 HeadlessWidget::HeadlessWidget()
70 : mEnabled(true),
71 mVisible(false),
72 mDestroyed(false),
73 mAlwaysOnTop(false),
74 mTopLevel(nullptr),
75 mCompositorWidget(nullptr),
76 mSizeMode(nsSizeMode_Normal),
77 mLastSizeMode(nsSizeMode_Normal),
78 mEffectiveSizeMode(nsSizeMode_Normal),
79 mRestoreBounds(0, 0, 0, 0) {
80 if (!sActiveWindows) {
81 sActiveWindows = new nsTArray<HeadlessWidget*>();
82 ClearOnShutdown(&sActiveWindows);
86 HeadlessWidget::~HeadlessWidget() {
87 LOG(("HeadlessWidget::~HeadlessWidget() [%p]\n", (void*)this));
89 Destroy();
92 void HeadlessWidget::Destroy() {
93 if (mDestroyed) {
94 return;
96 LOG(("HeadlessWidget::Destroy [%p]\n", (void*)this));
97 mDestroyed = true;
99 if (sActiveWindows) {
100 int32_t index = sActiveWindows->IndexOf(this);
101 if (index != -1) {
102 RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
103 sActiveWindows->RemoveElementAt(index);
104 // If this is the currently active widget and there's a previously active
105 // widget, activate the previous widget.
106 RefPtr<HeadlessWidget> previousActiveWindow = GetActiveWindow();
107 if (this == activeWindow && previousActiveWindow &&
108 previousActiveWindow->mWidgetListener) {
109 previousActiveWindow->mWidgetListener->WindowActivated();
114 nsBaseWidget::OnDestroy();
116 nsBaseWidget::Destroy();
119 nsresult HeadlessWidget::Create(nsIWidget* aParent,
120 nsNativeWidget aNativeParent,
121 const LayoutDeviceIntRect& aRect,
122 widget::InitData* aInitData) {
123 MOZ_ASSERT(!aNativeParent, "No native parents for headless widgets.");
125 BaseCreate(nullptr, aInitData);
127 mBounds = aRect;
128 mRestoreBounds = aRect;
130 mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
132 if (aParent) {
133 mTopLevel = aParent->GetTopLevelWidget();
134 } else {
135 mTopLevel = this;
138 return NS_OK;
141 already_AddRefed<nsIWidget> HeadlessWidget::CreateChild(
142 const LayoutDeviceIntRect& aRect, widget::InitData* aInitData,
143 bool aForceUseIWidgetParent) {
144 nsCOMPtr<nsIWidget> widget = nsIWidget::CreateHeadlessWidget();
145 if (!widget) {
146 return nullptr;
148 if (NS_FAILED(widget->Create(this, nullptr, aRect, aInitData))) {
149 return nullptr;
151 return widget.forget();
154 void HeadlessWidget::GetCompositorWidgetInitData(
155 mozilla::widget::CompositorWidgetInitData* aInitData) {
156 *aInitData =
157 mozilla::widget::HeadlessCompositorWidgetInitData(GetClientSize());
160 nsIWidget* HeadlessWidget::GetTopLevelWidget() { return mTopLevel; }
162 void HeadlessWidget::RaiseWindow() {
163 MOZ_ASSERT(mWindowType == WindowType::TopLevel ||
164 mWindowType == WindowType::Dialog ||
165 mWindowType == WindowType::Sheet,
166 "Raising a non-toplevel window.");
168 // Do nothing if this is the currently active window.
169 RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
170 if (activeWindow == this) {
171 return;
174 // Raise the window to the top of the stack.
175 nsWindowZ placement = nsWindowZTop;
176 nsCOMPtr<nsIWidget> actualBelow;
177 if (mWidgetListener)
178 mWidgetListener->ZLevelChanged(true, &placement, nullptr,
179 getter_AddRefs(actualBelow));
181 // Deactivate the last active window.
182 if (activeWindow && activeWindow->mWidgetListener) {
183 activeWindow->mWidgetListener->WindowDeactivated();
186 // Remove this window if it's already tracked.
187 int32_t index = sActiveWindows->IndexOf(this);
188 if (index != -1) {
189 sActiveWindows->RemoveElementAt(index);
192 // Activate this window.
193 sActiveWindows->AppendElement(this);
194 if (mWidgetListener) mWidgetListener->WindowActivated();
197 void HeadlessWidget::Show(bool aState) {
198 mVisible = aState;
200 LOG(("HeadlessWidget::Show [%p] state %d\n", (void*)this, aState));
202 // Top-level window and dialogs are activated/raised when shown.
203 // NB: alwaysontop windows are generally used for peripheral indicators,
204 // so we don't focus them by default.
205 if (aState && !mAlwaysOnTop &&
206 (mWindowType == WindowType::TopLevel ||
207 mWindowType == WindowType::Dialog || mWindowType == WindowType::Sheet)) {
208 RaiseWindow();
211 ApplySizeModeSideEffects();
214 bool HeadlessWidget::IsVisible() const { return mVisible; }
216 void HeadlessWidget::SetFocus(Raise aRaise,
217 mozilla::dom::CallerType aCallerType) {
218 LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this));
220 // This means we request activation of our toplevel window.
221 if (aRaise == Raise::Yes) {
222 HeadlessWidget* topLevel = (HeadlessWidget*)GetTopLevelWidget();
224 // The toplevel only becomes active if it's currently visible; otherwise, it
225 // will be activated anyway when it's shown.
226 if (topLevel->IsVisible()) topLevel->RaiseWindow();
230 void HeadlessWidget::Enable(bool aState) { mEnabled = aState; }
232 bool HeadlessWidget::IsEnabled() const { return mEnabled; }
234 void HeadlessWidget::Move(double aX, double aY) {
235 LOG(("HeadlessWidget::Move [%p] %f %f\n", (void*)this, aX, aY));
237 double scale =
238 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
239 int32_t x = NSToIntRound(aX * scale);
240 int32_t y = NSToIntRound(aY * scale);
242 if (mWindowType == WindowType::TopLevel ||
243 mWindowType == WindowType::Dialog) {
244 SetSizeMode(nsSizeMode_Normal);
247 MoveInternal(x, y);
250 void HeadlessWidget::MoveInternal(int32_t aX, int32_t aY) {
251 // Since a popup window's x/y coordinates are in relation to
252 // the parent, the parent might have moved so we always move a
253 // popup window.
254 if (mBounds.IsEqualXY(aX, aY) && mWindowType != WindowType::Popup) {
255 return;
258 mBounds.MoveTo(aX, aY);
259 NotifyWindowMoved(aX, aY);
262 LayoutDeviceIntPoint HeadlessWidget::WidgetToScreenOffset() {
263 return mTopLevel->GetBounds().TopLeft();
266 WindowRenderer* HeadlessWidget::GetWindowRenderer() {
267 return nsBaseWidget::GetWindowRenderer();
270 void HeadlessWidget::SetCompositorWidgetDelegate(
271 CompositorWidgetDelegate* delegate) {
272 if (delegate) {
273 mCompositorWidget = delegate->AsHeadlessCompositorWidget();
274 MOZ_ASSERT(mCompositorWidget,
275 "HeadlessWidget::SetCompositorWidgetDelegate called with a "
276 "non-HeadlessCompositorWidget");
277 } else {
278 mCompositorWidget = nullptr;
282 void HeadlessWidget::Resize(double aWidth, double aHeight, bool aRepaint) {
283 int32_t width = NSToIntRound(aWidth);
284 int32_t height = NSToIntRound(aHeight);
285 ResizeInternal(width, height, aRepaint);
288 void HeadlessWidget::ResizeInternal(int32_t aWidth, int32_t aHeight,
289 bool aRepaint) {
290 ConstrainSize(&aWidth, &aHeight);
291 mBounds.SizeTo(LayoutDeviceIntSize(aWidth, aHeight));
293 if (mCompositorWidget) {
294 mCompositorWidget->NotifyClientSizeChanged(
295 LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()));
297 if (mWidgetListener) {
298 mWidgetListener->WindowResized(this, mBounds.Width(), mBounds.Height());
300 if (mAttachedWidgetListener) {
301 mAttachedWidgetListener->WindowResized(this, mBounds.Width(),
302 mBounds.Height());
306 void HeadlessWidget::Resize(double aX, double aY, double aWidth, double aHeight,
307 bool aRepaint) {
308 MoveInternal(NSToIntRound(aX), NSToIntRound(aY));
309 Resize(aWidth, aHeight, aRepaint);
312 void HeadlessWidget::SetSizeMode(nsSizeMode aMode) {
313 LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void*)this, aMode));
315 if (aMode == mSizeMode) {
316 return;
319 if (aMode == nsSizeMode_Normal && mSizeMode == nsSizeMode_Fullscreen) {
320 MakeFullScreen(false);
321 return;
324 mSizeMode = aMode;
326 // Normally in real widget backends a window event would be triggered that
327 // would cause the window manager to handle resizing the window. In headless
328 // the window must manually be resized.
329 ApplySizeModeSideEffects();
332 void HeadlessWidget::ApplySizeModeSideEffects() {
333 if (!mVisible || mEffectiveSizeMode == mSizeMode) {
334 return;
337 if (mEffectiveSizeMode == nsSizeMode_Normal) {
338 // Store the last normal size bounds so it can be restored when entering
339 // normal mode again.
340 mRestoreBounds = mBounds;
343 switch (mSizeMode) {
344 case nsSizeMode_Normal: {
345 MoveInternal(mRestoreBounds.X(), mRestoreBounds.Y());
346 ResizeInternal(mRestoreBounds.Width(), mRestoreBounds.Height(), false);
347 break;
349 case nsSizeMode_Minimized:
350 break;
351 case nsSizeMode_Maximized: {
352 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
353 if (screen) {
354 int32_t left, top, width, height;
355 if (NS_SUCCEEDED(
356 screen->GetRectDisplayPix(&left, &top, &width, &height))) {
357 MoveInternal(0, 0);
358 ResizeInternal(width, height, true);
361 break;
363 case nsSizeMode_Fullscreen:
364 // This will take care of resizing the window.
365 nsBaseWidget::InfallibleMakeFullScreen(true);
366 break;
367 default:
368 break;
371 mEffectiveSizeMode = mSizeMode;
372 if (mWidgetListener) {
373 mWidgetListener->SizeModeChanged(mSizeMode);
377 nsresult HeadlessWidget::MakeFullScreen(bool aFullScreen) {
378 // Directly update the size mode here so a later call SetSizeMode does
379 // nothing.
380 if (aFullScreen) {
381 if (mSizeMode != nsSizeMode_Fullscreen) {
382 mLastSizeMode = mSizeMode;
384 mSizeMode = nsSizeMode_Fullscreen;
385 } else {
386 mSizeMode = mLastSizeMode;
389 // Notify the listener first so size mode change events are triggered before
390 // resize events.
391 if (mWidgetListener) {
392 mWidgetListener->SizeModeChanged(mSizeMode);
395 // Real widget backends don't seem to follow a common approach for
396 // when and how many resize events are triggered during fullscreen
397 // transitions. InfallibleMakeFullScreen will trigger a resize, but it
398 // will be ignored if still transitioning to fullscreen, so it must be
399 // triggered on the next tick.
400 RefPtr<HeadlessWidget> self(this);
401 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
402 "HeadlessWidget::MakeFullScreen", [self, aFullScreen]() -> void {
403 self->InfallibleMakeFullScreen(aFullScreen);
404 }));
406 return NS_OK;
409 nsresult HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent) {
410 HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
411 return bindings.AttachNativeKeyEvent(aEvent);
414 bool HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType,
415 const WidgetKeyboardEvent& aEvent,
416 nsTArray<CommandInt>& aCommands) {
417 // Validate the arguments.
418 if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
419 return false;
422 Maybe<WritingMode> writingMode;
423 if (aEvent.NeedsToRemapNavigationKey()) {
424 if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
425 writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
429 HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
430 bindings.GetEditCommands(aType, aEvent, writingMode, aCommands);
431 return true;
434 nsresult HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent,
435 nsEventStatus& aStatus) {
436 #ifdef DEBUG
437 debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
438 #endif
440 aStatus = nsEventStatus_eIgnore;
442 if (mAttachedWidgetListener) {
443 aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
444 } else if (mWidgetListener) {
445 aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
448 return NS_OK;
451 nsresult HeadlessWidget::SynthesizeNativeMouseEvent(
452 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
453 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
454 nsIObserver* aObserver) {
455 AutoObserverNotifier notifier(aObserver, "mouseevent");
456 EventMessage msg;
457 switch (aNativeMessage) {
458 case NativeMouseMessage::Move:
459 msg = eMouseMove;
460 break;
461 case NativeMouseMessage::ButtonDown:
462 msg = eMouseDown;
463 break;
464 case NativeMouseMessage::ButtonUp:
465 msg = eMouseUp;
466 break;
467 case NativeMouseMessage::EnterWindow:
468 case NativeMouseMessage::LeaveWindow:
469 MOZ_ASSERT_UNREACHABLE("Unsupported synthesized mouse event");
470 return NS_ERROR_UNEXPECTED;
472 WidgetMouseEvent event(true, msg, this, WidgetMouseEvent::eReal);
473 event.mRefPoint = aPoint - WidgetToScreenOffset();
474 if (msg == eMouseDown || msg == eMouseUp) {
475 event.mButton = aButton;
477 if (msg == eMouseDown) {
478 event.mClickCount = 1;
480 event.AssignEventTime(WidgetEventTime());
481 DispatchInputEvent(&event);
482 return NS_OK;
485 nsresult HeadlessWidget::SynthesizeNativeMouseScrollEvent(
486 mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
487 double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
488 uint32_t aAdditionalFlags, nsIObserver* aObserver) {
489 AutoObserverNotifier notifier(aObserver, "mousescrollevent");
490 printf(">>> DEBUG_ME: Synth: aDeltaY=%f\n", aDeltaY);
491 // The various platforms seem to handle scrolling deltas differently,
492 // but the following seems to emulate it well enough.
493 WidgetWheelEvent event(true, eWheel, this);
494 event.mDeltaMode = MOZ_HEADLESS_SCROLL_DELTA_MODE;
495 event.mIsNoLineOrPageDelta = true;
496 event.mDeltaX = -aDeltaX * MOZ_HEADLESS_SCROLL_MULTIPLIER;
497 event.mDeltaY = -aDeltaY * MOZ_HEADLESS_SCROLL_MULTIPLIER;
498 event.mDeltaZ = -aDeltaZ * MOZ_HEADLESS_SCROLL_MULTIPLIER;
499 event.mRefPoint = aPoint - WidgetToScreenOffset();
500 event.AssignEventTime(WidgetEventTime());
501 DispatchInputEvent(&event);
502 return NS_OK;
505 nsresult HeadlessWidget::SynthesizeNativeTouchPoint(
506 uint32_t aPointerId, TouchPointerState aPointerState,
507 LayoutDeviceIntPoint aPoint, double aPointerPressure,
508 uint32_t aPointerOrientation, nsIObserver* aObserver) {
509 AutoObserverNotifier notifier(aObserver, "touchpoint");
511 MOZ_ASSERT(NS_IsMainThread());
512 if (aPointerState == TOUCH_HOVER) {
513 return NS_ERROR_UNEXPECTED;
516 if (!mSynthesizedTouchInput) {
517 mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
520 LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
521 MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
522 mSynthesizedTouchInput.get(), TimeStamp::Now(), aPointerId, aPointerState,
523 pointInWindow, aPointerPressure, aPointerOrientation);
524 DispatchTouchInput(inputToDispatch);
525 return NS_OK;
528 nsresult HeadlessWidget::SynthesizeNativeTouchPadPinch(
529 TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
530 int32_t aModifierFlags) {
531 MOZ_ASSERT(NS_IsMainThread());
533 PinchGestureInput::PinchGestureType pinchGestureType =
534 PinchGestureInput::PINCHGESTURE_SCALE;
535 ScreenCoord CurrentSpan;
536 ScreenCoord PreviousSpan;
537 switch (aEventPhase) {
538 case PHASE_BEGIN:
539 pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
540 CurrentSpan = aScale;
541 PreviousSpan = 0.999;
542 break;
544 case PHASE_UPDATE:
545 pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
546 if (aScale == mLastPinchSpan) {
547 return NS_ERROR_INVALID_ARG;
549 CurrentSpan = aScale;
550 PreviousSpan = mLastPinchSpan;
551 break;
553 case PHASE_END:
554 pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
555 CurrentSpan = aScale;
556 PreviousSpan = mLastPinchSpan;
557 break;
559 default:
560 return NS_ERROR_INVALID_ARG;
563 ScreenPoint touchpadPoint = ViewAs<ScreenPixel>(
564 aPoint - WidgetToScreenOffset(),
565 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
566 // The headless widget does not support modifiers.
567 // Do not pass `aModifierFlags` because it contains native modifier values.
568 PinchGestureInput inputToDispatch(
569 pinchGestureType, PinchGestureInput::TRACKPAD, TimeStamp::Now(),
570 ExternalPoint(0, 0), touchpadPoint,
571 100.0 * ((aEventPhase == PHASE_END) ? ScreenCoord(1.f) : CurrentSpan),
572 100.0 * ((aEventPhase == PHASE_END) ? ScreenCoord(1.f) : PreviousSpan),
575 if (!inputToDispatch.SetLineOrPageDeltaY(this)) {
576 return NS_ERROR_INVALID_ARG;
579 mLastPinchSpan = aScale;
580 DispatchPinchGestureInput(inputToDispatch);
581 return NS_OK;
584 nsresult HeadlessWidget::SynthesizeNativeTouchpadPan(
585 TouchpadGesturePhase aEventPhase, LayoutDeviceIntPoint aPoint,
586 double aDeltaX, double aDeltaY, int32_t aModifierFlags,
587 nsIObserver* aObserver) {
588 AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
590 MOZ_ASSERT(NS_IsMainThread());
592 PanGestureInput::PanGestureType eventType = PanGestureInput::PANGESTURE_PAN;
593 switch (aEventPhase) {
594 case PHASE_BEGIN:
595 eventType = PanGestureInput::PANGESTURE_START;
596 break;
597 case PHASE_UPDATE:
598 eventType = PanGestureInput::PANGESTURE_PAN;
599 break;
600 case PHASE_END:
601 eventType = PanGestureInput::PANGESTURE_END;
602 break;
603 default:
604 return NS_ERROR_INVALID_ARG;
607 ScreenPoint touchpadPoint = ViewAs<ScreenPixel>(
608 aPoint - WidgetToScreenOffset(),
609 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
610 PanGestureInput input(eventType, TimeStamp::Now(), touchpadPoint,
611 ScreenPoint(float(aDeltaX), float(aDeltaY)),
612 // Same as SynthesizeNativeTouchPadPinch case we ignore
613 // aModifierFlags.
616 input.mSimulateMomentum =
617 Preferences::GetBool("apz.test.headless.simulate_momentum");
619 DispatchPanGestureInput(input);
621 return NS_OK;
624 } // namespace widget
625 } // namespace mozilla