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"
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
;
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)
42 # define LOGFOCUS(args)
44 #endif /* MOZ_LOGGING */
47 already_AddRefed
<nsIWidget
> nsIWidget::CreateHeadlessWidget() {
48 nsCOMPtr
<nsIWidget
> widget
= new mozilla::widget::HeadlessWidget();
49 return widget
.forget();
55 StaticAutoPtr
<nsTArray
<HeadlessWidget
*>> HeadlessWidget::sActiveWindows
;
57 already_AddRefed
<HeadlessWidget
> HeadlessWidget::GetActiveWindow() {
58 if (!sActiveWindows
) {
61 auto length
= sActiveWindows
->Length();
65 RefPtr
<HeadlessWidget
> widget
= sActiveWindows
->ElementAt(length
- 1);
66 return widget
.forget();
69 HeadlessWidget::HeadlessWidget()
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));
92 void HeadlessWidget::Destroy() {
96 LOG(("HeadlessWidget::Destroy [%p]\n", (void*)this));
100 int32_t index
= sActiveWindows
->IndexOf(this);
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
);
128 mRestoreBounds
= aRect
;
130 mAlwaysOnTop
= aInitData
&& aInitData
->mAlwaysOnTop
;
133 mTopLevel
= aParent
->GetTopLevelWidget();
141 already_AddRefed
<nsIWidget
> HeadlessWidget::CreateChild(
142 const LayoutDeviceIntRect
& aRect
, widget::InitData
* aInitData
,
143 bool aForceUseIWidgetParent
) {
144 nsCOMPtr
<nsIWidget
> widget
= nsIWidget::CreateHeadlessWidget();
148 if (NS_FAILED(widget
->Create(this, nullptr, aRect
, aInitData
))) {
151 return widget
.forget();
154 void HeadlessWidget::GetCompositorWidgetInitData(
155 mozilla::widget::CompositorWidgetInitData
* 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) {
174 // Raise the window to the top of the stack.
175 nsWindowZ placement
= nsWindowZTop
;
176 nsCOMPtr
<nsIWidget
> actualBelow
;
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);
189 sActiveWindows
->RemoveElementAt(index
);
192 // Activate this window.
193 sActiveWindows
->AppendElement(this);
194 if (mWidgetListener
) mWidgetListener
->WindowActivated();
197 void HeadlessWidget::Show(bool 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
)) {
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
));
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
);
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
254 if (mBounds
.IsEqualXY(aX
, aY
) && mWindowType
!= WindowType::Popup
) {
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
) {
273 mCompositorWidget
= delegate
->AsHeadlessCompositorWidget();
274 MOZ_ASSERT(mCompositorWidget
,
275 "HeadlessWidget::SetCompositorWidgetDelegate called with a "
276 "non-HeadlessCompositorWidget");
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
,
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(),
306 void HeadlessWidget::Resize(double aX
, double aY
, double aWidth
, double aHeight
,
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
) {
319 if (aMode
== nsSizeMode_Normal
&& mSizeMode
== nsSizeMode_Fullscreen
) {
320 MakeFullScreen(false);
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
) {
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
;
344 case nsSizeMode_Normal
: {
345 MoveInternal(mRestoreBounds
.X(), mRestoreBounds
.Y());
346 ResizeInternal(mRestoreBounds
.Width(), mRestoreBounds
.Height(), false);
349 case nsSizeMode_Minimized
:
351 case nsSizeMode_Maximized
: {
352 nsCOMPtr
<nsIScreen
> screen
= GetWidgetScreen();
354 int32_t left
, top
, width
, height
;
356 screen
->GetRectDisplayPix(&left
, &top
, &width
, &height
))) {
358 ResizeInternal(width
, height
, true);
363 case nsSizeMode_Fullscreen
:
364 // This will take care of resizing the window.
365 nsBaseWidget::InfallibleMakeFullScreen(true);
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
381 if (mSizeMode
!= nsSizeMode_Fullscreen
) {
382 mLastSizeMode
= mSizeMode
;
384 mSizeMode
= nsSizeMode_Fullscreen
;
386 mSizeMode
= mLastSizeMode
;
389 // Notify the listener first so size mode change events are triggered before
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
);
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
))) {
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
);
434 nsresult
HeadlessWidget::DispatchEvent(WidgetGUIEvent
* aEvent
,
435 nsEventStatus
& aStatus
) {
437 debug_DumpEvent(stdout
, aEvent
->mWidget
, aEvent
, "HeadlessWidget", 0);
440 aStatus
= nsEventStatus_eIgnore
;
442 if (mAttachedWidgetListener
) {
443 aStatus
= mAttachedWidgetListener
->HandleEvent(aEvent
, mUseAttachedEvents
);
444 } else if (mWidgetListener
) {
445 aStatus
= mWidgetListener
->HandleEvent(aEvent
, mUseAttachedEvents
);
451 nsresult
HeadlessWidget::SynthesizeNativeMouseEvent(
452 LayoutDeviceIntPoint aPoint
, NativeMouseMessage aNativeMessage
,
453 MouseButton aButton
, nsIWidget::Modifiers aModifierFlags
,
454 nsIObserver
* aObserver
) {
455 AutoObserverNotifier
notifier(aObserver
, "mouseevent");
457 switch (aNativeMessage
) {
458 case NativeMouseMessage::Move
:
461 case NativeMouseMessage::ButtonDown
:
464 case NativeMouseMessage::ButtonUp
:
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
);
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
);
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
);
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
) {
539 pinchGestureType
= PinchGestureInput::PINCHGESTURE_START
;
540 CurrentSpan
= aScale
;
541 PreviousSpan
= 0.999;
545 pinchGestureType
= PinchGestureInput::PINCHGESTURE_SCALE
;
546 if (aScale
== mLastPinchSpan
) {
547 return NS_ERROR_INVALID_ARG
;
549 CurrentSpan
= aScale
;
550 PreviousSpan
= mLastPinchSpan
;
554 pinchGestureType
= PinchGestureInput::PINCHGESTURE_END
;
555 CurrentSpan
= aScale
;
556 PreviousSpan
= mLastPinchSpan
;
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
);
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
) {
595 eventType
= PanGestureInput::PANGESTURE_START
;
598 eventType
= PanGestureInput::PANGESTURE_PAN
;
601 eventType
= PanGestureInput::PANGESTURE_END
;
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
616 input
.mSimulateMomentum
=
617 Preferences::GetBool("apz.test.headless.simulate_momentum");
619 DispatchPanGestureInput(input
);
624 } // namespace widget
625 } // namespace mozilla