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() {
164 mWindowType
== WindowType::TopLevel
|| mWindowType
== WindowType::Dialog
,
165 "Raising a non-toplevel window.");
167 // Do nothing if this is the currently active window.
168 RefPtr
<HeadlessWidget
> activeWindow
= GetActiveWindow();
169 if (activeWindow
== this) {
173 // Deactivate the last active window.
174 if (activeWindow
&& activeWindow
->mWidgetListener
) {
175 activeWindow
->mWidgetListener
->WindowDeactivated();
178 // Remove this window if it's already tracked.
179 int32_t index
= sActiveWindows
->IndexOf(this);
181 sActiveWindows
->RemoveElementAt(index
);
184 // Activate this window.
185 sActiveWindows
->AppendElement(this);
186 if (mWidgetListener
) mWidgetListener
->WindowActivated();
189 void HeadlessWidget::Show(bool aState
) {
192 LOG(("HeadlessWidget::Show [%p] state %d\n", (void*)this, aState
));
194 // Top-level window and dialogs are activated/raised when shown.
195 // NB: alwaysontop windows are generally used for peripheral indicators,
196 // so we don't focus them by default.
197 if (aState
&& !mAlwaysOnTop
&&
198 (mWindowType
== WindowType::TopLevel
||
199 mWindowType
== WindowType::Dialog
)) {
203 ApplySizeModeSideEffects();
206 bool HeadlessWidget::IsVisible() const { return mVisible
; }
208 void HeadlessWidget::SetFocus(Raise aRaise
,
209 mozilla::dom::CallerType aCallerType
) {
210 LOGFOCUS((" SetFocus %d [%p]\n", aRaise
== Raise::Yes
, (void*)this));
212 // This means we request activation of our toplevel window.
213 if (aRaise
== Raise::Yes
) {
214 HeadlessWidget
* topLevel
= (HeadlessWidget
*)GetTopLevelWidget();
216 // The toplevel only becomes active if it's currently visible; otherwise, it
217 // will be activated anyway when it's shown.
218 if (topLevel
->IsVisible()) topLevel
->RaiseWindow();
222 void HeadlessWidget::Enable(bool aState
) { mEnabled
= aState
; }
224 bool HeadlessWidget::IsEnabled() const { return mEnabled
; }
226 void HeadlessWidget::Move(double aX
, double aY
) {
227 LOG(("HeadlessWidget::Move [%p] %f %f\n", (void*)this, aX
, aY
));
230 BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale
: 1.0;
231 int32_t x
= NSToIntRound(aX
* scale
);
232 int32_t y
= NSToIntRound(aY
* scale
);
234 if (mWindowType
== WindowType::TopLevel
||
235 mWindowType
== WindowType::Dialog
) {
236 SetSizeMode(nsSizeMode_Normal
);
242 void HeadlessWidget::MoveInternal(int32_t aX
, int32_t aY
) {
243 // Since a popup window's x/y coordinates are in relation to
244 // the parent, the parent might have moved so we always move a
246 if (mBounds
.IsEqualXY(aX
, aY
) && mWindowType
!= WindowType::Popup
) {
250 mBounds
.MoveTo(aX
, aY
);
251 NotifyWindowMoved(aX
, aY
);
254 LayoutDeviceIntPoint
HeadlessWidget::WidgetToScreenOffset() {
255 return mTopLevel
->GetBounds().TopLeft();
258 WindowRenderer
* HeadlessWidget::GetWindowRenderer() {
259 return nsBaseWidget::GetWindowRenderer();
262 void HeadlessWidget::SetCompositorWidgetDelegate(
263 CompositorWidgetDelegate
* delegate
) {
265 mCompositorWidget
= delegate
->AsHeadlessCompositorWidget();
266 MOZ_ASSERT(mCompositorWidget
,
267 "HeadlessWidget::SetCompositorWidgetDelegate called with a "
268 "non-HeadlessCompositorWidget");
270 mCompositorWidget
= nullptr;
274 void HeadlessWidget::Resize(double aWidth
, double aHeight
, bool aRepaint
) {
275 int32_t width
= NSToIntRound(aWidth
);
276 int32_t height
= NSToIntRound(aHeight
);
277 ResizeInternal(width
, height
, aRepaint
);
280 void HeadlessWidget::ResizeInternal(int32_t aWidth
, int32_t aHeight
,
282 ConstrainSize(&aWidth
, &aHeight
);
283 mBounds
.SizeTo(LayoutDeviceIntSize(aWidth
, aHeight
));
285 if (mCompositorWidget
) {
286 mCompositorWidget
->NotifyClientSizeChanged(
287 LayoutDeviceIntSize(mBounds
.Width(), mBounds
.Height()));
289 if (mWidgetListener
) {
290 mWidgetListener
->WindowResized(this, mBounds
.Width(), mBounds
.Height());
292 if (mAttachedWidgetListener
) {
293 mAttachedWidgetListener
->WindowResized(this, mBounds
.Width(),
298 void HeadlessWidget::Resize(double aX
, double aY
, double aWidth
, double aHeight
,
300 MoveInternal(NSToIntRound(aX
), NSToIntRound(aY
));
301 Resize(aWidth
, aHeight
, aRepaint
);
304 void HeadlessWidget::SetSizeMode(nsSizeMode aMode
) {
305 LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void*)this, aMode
));
307 if (aMode
== mSizeMode
) {
311 if (aMode
== nsSizeMode_Normal
&& mSizeMode
== nsSizeMode_Fullscreen
) {
312 MakeFullScreen(false);
318 // Normally in real widget backends a window event would be triggered that
319 // would cause the window manager to handle resizing the window. In headless
320 // the window must manually be resized.
321 ApplySizeModeSideEffects();
324 void HeadlessWidget::ApplySizeModeSideEffects() {
325 if (!mVisible
|| mEffectiveSizeMode
== mSizeMode
) {
329 if (mEffectiveSizeMode
== nsSizeMode_Normal
) {
330 // Store the last normal size bounds so it can be restored when entering
331 // normal mode again.
332 mRestoreBounds
= mBounds
;
336 case nsSizeMode_Normal
: {
337 MoveInternal(mRestoreBounds
.X(), mRestoreBounds
.Y());
338 ResizeInternal(mRestoreBounds
.Width(), mRestoreBounds
.Height(), false);
341 case nsSizeMode_Minimized
:
343 case nsSizeMode_Maximized
: {
344 nsCOMPtr
<nsIScreen
> screen
= GetWidgetScreen();
346 int32_t left
, top
, width
, height
;
348 screen
->GetRectDisplayPix(&left
, &top
, &width
, &height
))) {
350 ResizeInternal(width
, height
, true);
355 case nsSizeMode_Fullscreen
:
356 // This will take care of resizing the window.
357 nsBaseWidget::InfallibleMakeFullScreen(true);
363 mEffectiveSizeMode
= mSizeMode
;
364 if (mWidgetListener
) {
365 mWidgetListener
->SizeModeChanged(mSizeMode
);
369 nsresult
HeadlessWidget::MakeFullScreen(bool aFullScreen
) {
370 // Directly update the size mode here so a later call SetSizeMode does
373 if (mSizeMode
!= nsSizeMode_Fullscreen
) {
374 mLastSizeMode
= mSizeMode
;
376 mSizeMode
= nsSizeMode_Fullscreen
;
378 mSizeMode
= mLastSizeMode
;
381 // Notify the listener first so size mode change events are triggered before
383 if (mWidgetListener
) {
384 mWidgetListener
->SizeModeChanged(mSizeMode
);
387 // Real widget backends don't seem to follow a common approach for
388 // when and how many resize events are triggered during fullscreen
389 // transitions. InfallibleMakeFullScreen will trigger a resize, but it
390 // will be ignored if still transitioning to fullscreen, so it must be
391 // triggered on the next tick.
392 RefPtr
<HeadlessWidget
> self(this);
393 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
394 "HeadlessWidget::MakeFullScreen", [self
, aFullScreen
]() -> void {
395 self
->InfallibleMakeFullScreen(aFullScreen
);
401 nsresult
HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent
& aEvent
) {
402 HeadlessKeyBindings
& bindings
= HeadlessKeyBindings::GetInstance();
403 return bindings
.AttachNativeKeyEvent(aEvent
);
406 bool HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType
,
407 const WidgetKeyboardEvent
& aEvent
,
408 nsTArray
<CommandInt
>& aCommands
) {
409 // Validate the arguments.
410 if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType
, aEvent
, aCommands
))) {
414 Maybe
<WritingMode
> writingMode
;
415 if (aEvent
.NeedsToRemapNavigationKey()) {
416 if (RefPtr
<TextEventDispatcher
> dispatcher
= GetTextEventDispatcher()) {
417 writingMode
= dispatcher
->MaybeQueryWritingModeAtSelection();
421 HeadlessKeyBindings
& bindings
= HeadlessKeyBindings::GetInstance();
422 bindings
.GetEditCommands(aType
, aEvent
, writingMode
, aCommands
);
426 nsresult
HeadlessWidget::DispatchEvent(WidgetGUIEvent
* aEvent
,
427 nsEventStatus
& aStatus
) {
429 debug_DumpEvent(stdout
, aEvent
->mWidget
, aEvent
, "HeadlessWidget", 0);
432 aStatus
= nsEventStatus_eIgnore
;
434 if (mAttachedWidgetListener
) {
435 aStatus
= mAttachedWidgetListener
->HandleEvent(aEvent
, mUseAttachedEvents
);
436 } else if (mWidgetListener
) {
437 aStatus
= mWidgetListener
->HandleEvent(aEvent
, mUseAttachedEvents
);
443 nsresult
HeadlessWidget::SynthesizeNativeMouseEvent(
444 LayoutDeviceIntPoint aPoint
, NativeMouseMessage aNativeMessage
,
445 MouseButton aButton
, nsIWidget::Modifiers aModifierFlags
,
446 nsIObserver
* aObserver
) {
447 AutoObserverNotifier
notifier(aObserver
, "mouseevent");
449 switch (aNativeMessage
) {
450 case NativeMouseMessage::Move
:
453 case NativeMouseMessage::ButtonDown
:
456 case NativeMouseMessage::ButtonUp
:
459 case NativeMouseMessage::EnterWindow
:
460 case NativeMouseMessage::LeaveWindow
:
461 MOZ_ASSERT_UNREACHABLE("Unsupported synthesized mouse event");
462 return NS_ERROR_UNEXPECTED
;
464 WidgetMouseEvent
event(true, msg
, this, WidgetMouseEvent::eReal
);
465 event
.mRefPoint
= aPoint
- WidgetToScreenOffset();
466 if (msg
== eMouseDown
|| msg
== eMouseUp
) {
467 event
.mButton
= aButton
;
469 if (msg
== eMouseDown
) {
470 event
.mClickCount
= 1;
472 event
.AssignEventTime(WidgetEventTime());
473 DispatchInputEvent(&event
);
477 nsresult
HeadlessWidget::SynthesizeNativeMouseScrollEvent(
478 mozilla::LayoutDeviceIntPoint aPoint
, uint32_t aNativeMessage
,
479 double aDeltaX
, double aDeltaY
, double aDeltaZ
, uint32_t aModifierFlags
,
480 uint32_t aAdditionalFlags
, nsIObserver
* aObserver
) {
481 AutoObserverNotifier
notifier(aObserver
, "mousescrollevent");
482 printf(">>> DEBUG_ME: Synth: aDeltaY=%f\n", aDeltaY
);
483 // The various platforms seem to handle scrolling deltas differently,
484 // but the following seems to emulate it well enough.
485 WidgetWheelEvent
event(true, eWheel
, this);
486 event
.mDeltaMode
= MOZ_HEADLESS_SCROLL_DELTA_MODE
;
487 event
.mIsNoLineOrPageDelta
= true;
488 event
.mDeltaX
= -aDeltaX
* MOZ_HEADLESS_SCROLL_MULTIPLIER
;
489 event
.mDeltaY
= -aDeltaY
* MOZ_HEADLESS_SCROLL_MULTIPLIER
;
490 event
.mDeltaZ
= -aDeltaZ
* MOZ_HEADLESS_SCROLL_MULTIPLIER
;
491 event
.mRefPoint
= aPoint
- WidgetToScreenOffset();
492 event
.AssignEventTime(WidgetEventTime());
493 DispatchInputEvent(&event
);
497 nsresult
HeadlessWidget::SynthesizeNativeTouchPoint(
498 uint32_t aPointerId
, TouchPointerState aPointerState
,
499 LayoutDeviceIntPoint aPoint
, double aPointerPressure
,
500 uint32_t aPointerOrientation
, nsIObserver
* aObserver
) {
501 AutoObserverNotifier
notifier(aObserver
, "touchpoint");
503 MOZ_ASSERT(NS_IsMainThread());
504 if (aPointerState
== TOUCH_HOVER
) {
505 return NS_ERROR_UNEXPECTED
;
508 if (!mSynthesizedTouchInput
) {
509 mSynthesizedTouchInput
= MakeUnique
<MultiTouchInput
>();
512 LayoutDeviceIntPoint pointInWindow
= aPoint
- WidgetToScreenOffset();
513 MultiTouchInput inputToDispatch
= UpdateSynthesizedTouchState(
514 mSynthesizedTouchInput
.get(), TimeStamp::Now(), aPointerId
, aPointerState
,
515 pointInWindow
, aPointerPressure
, aPointerOrientation
);
516 DispatchTouchInput(inputToDispatch
);
520 nsresult
HeadlessWidget::SynthesizeNativeTouchPadPinch(
521 TouchpadGesturePhase aEventPhase
, float aScale
, LayoutDeviceIntPoint aPoint
,
522 int32_t aModifierFlags
) {
523 MOZ_ASSERT(NS_IsMainThread());
525 PinchGestureInput::PinchGestureType pinchGestureType
=
526 PinchGestureInput::PINCHGESTURE_SCALE
;
527 ScreenCoord CurrentSpan
;
528 ScreenCoord PreviousSpan
;
529 switch (aEventPhase
) {
531 pinchGestureType
= PinchGestureInput::PINCHGESTURE_START
;
532 CurrentSpan
= aScale
;
533 PreviousSpan
= 0.999;
537 pinchGestureType
= PinchGestureInput::PINCHGESTURE_SCALE
;
538 if (aScale
== mLastPinchSpan
) {
539 return NS_ERROR_INVALID_ARG
;
541 CurrentSpan
= aScale
;
542 PreviousSpan
= mLastPinchSpan
;
546 pinchGestureType
= PinchGestureInput::PINCHGESTURE_END
;
547 CurrentSpan
= aScale
;
548 PreviousSpan
= mLastPinchSpan
;
552 return NS_ERROR_INVALID_ARG
;
555 ScreenPoint touchpadPoint
= ViewAs
<ScreenPixel
>(
556 aPoint
- WidgetToScreenOffset(),
557 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
);
558 // The headless widget does not support modifiers.
559 // Do not pass `aModifierFlags` because it contains native modifier values.
560 PinchGestureInput
inputToDispatch(
561 pinchGestureType
, PinchGestureInput::TRACKPAD
, TimeStamp::Now(),
562 ExternalPoint(0, 0), touchpadPoint
,
563 100.0 * ((aEventPhase
== PHASE_END
) ? ScreenCoord(1.f
) : CurrentSpan
),
564 100.0 * ((aEventPhase
== PHASE_END
) ? ScreenCoord(1.f
) : PreviousSpan
),
567 if (!inputToDispatch
.SetLineOrPageDeltaY(this)) {
568 return NS_ERROR_INVALID_ARG
;
571 mLastPinchSpan
= aScale
;
572 DispatchPinchGestureInput(inputToDispatch
);
576 nsresult
HeadlessWidget::SynthesizeNativeTouchpadPan(
577 TouchpadGesturePhase aEventPhase
, LayoutDeviceIntPoint aPoint
,
578 double aDeltaX
, double aDeltaY
, int32_t aModifierFlags
,
579 nsIObserver
* aObserver
) {
580 AutoObserverNotifier
notifier(aObserver
, "touchpadpanevent");
582 MOZ_ASSERT(NS_IsMainThread());
584 PanGestureInput::PanGestureType eventType
= PanGestureInput::PANGESTURE_PAN
;
585 switch (aEventPhase
) {
587 eventType
= PanGestureInput::PANGESTURE_START
;
590 eventType
= PanGestureInput::PANGESTURE_PAN
;
593 eventType
= PanGestureInput::PANGESTURE_END
;
596 return NS_ERROR_INVALID_ARG
;
599 ScreenPoint touchpadPoint
= ViewAs
<ScreenPixel
>(
600 aPoint
- WidgetToScreenOffset(),
601 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent
);
602 PanGestureInput
input(eventType
, TimeStamp::Now(), touchpadPoint
,
603 ScreenPoint(float(aDeltaX
), float(aDeltaY
)),
604 // Same as SynthesizeNativeTouchPadPinch case we ignore
608 input
.mSimulateMomentum
=
609 Preferences::GetBool("apz.test.headless.simulate_momentum");
611 DispatchPanGestureInput(input
);
616 } // namespace widget
617 } // namespace mozilla