no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / widget / headless / HeadlessWidget.cpp
blobc6095751bc1e9bbe907e64fb634b799cac31bb0a
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(
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) {
170 return;
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);
180 if (index != -1) {
181 sActiveWindows->RemoveElementAt(index);
184 // Activate this window.
185 sActiveWindows->AppendElement(this);
186 if (mWidgetListener) mWidgetListener->WindowActivated();
189 void HeadlessWidget::Show(bool aState) {
190 mVisible = 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)) {
200 RaiseWindow();
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));
229 double scale =
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);
239 MoveInternal(x, y);
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
245 // popup window.
246 if (mBounds.IsEqualXY(aX, aY) && mWindowType != WindowType::Popup) {
247 return;
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) {
264 if (delegate) {
265 mCompositorWidget = delegate->AsHeadlessCompositorWidget();
266 MOZ_ASSERT(mCompositorWidget,
267 "HeadlessWidget::SetCompositorWidgetDelegate called with a "
268 "non-HeadlessCompositorWidget");
269 } else {
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,
281 bool aRepaint) {
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(),
294 mBounds.Height());
298 void HeadlessWidget::Resize(double aX, double aY, double aWidth, double aHeight,
299 bool aRepaint) {
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) {
308 return;
311 if (aMode == nsSizeMode_Normal && mSizeMode == nsSizeMode_Fullscreen) {
312 MakeFullScreen(false);
313 return;
316 mSizeMode = aMode;
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) {
326 return;
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;
335 switch (mSizeMode) {
336 case nsSizeMode_Normal: {
337 MoveInternal(mRestoreBounds.X(), mRestoreBounds.Y());
338 ResizeInternal(mRestoreBounds.Width(), mRestoreBounds.Height(), false);
339 break;
341 case nsSizeMode_Minimized:
342 break;
343 case nsSizeMode_Maximized: {
344 nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
345 if (screen) {
346 int32_t left, top, width, height;
347 if (NS_SUCCEEDED(
348 screen->GetRectDisplayPix(&left, &top, &width, &height))) {
349 MoveInternal(0, 0);
350 ResizeInternal(width, height, true);
353 break;
355 case nsSizeMode_Fullscreen:
356 // This will take care of resizing the window.
357 nsBaseWidget::InfallibleMakeFullScreen(true);
358 break;
359 default:
360 break;
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
371 // nothing.
372 if (aFullScreen) {
373 if (mSizeMode != nsSizeMode_Fullscreen) {
374 mLastSizeMode = mSizeMode;
376 mSizeMode = nsSizeMode_Fullscreen;
377 } else {
378 mSizeMode = mLastSizeMode;
381 // Notify the listener first so size mode change events are triggered before
382 // resize events.
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);
396 }));
398 return NS_OK;
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))) {
411 return false;
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);
423 return true;
426 nsresult HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent,
427 nsEventStatus& aStatus) {
428 #ifdef DEBUG
429 debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
430 #endif
432 aStatus = nsEventStatus_eIgnore;
434 if (mAttachedWidgetListener) {
435 aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
436 } else if (mWidgetListener) {
437 aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
440 return NS_OK;
443 nsresult HeadlessWidget::SynthesizeNativeMouseEvent(
444 LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
445 MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
446 nsIObserver* aObserver) {
447 AutoObserverNotifier notifier(aObserver, "mouseevent");
448 EventMessage msg;
449 switch (aNativeMessage) {
450 case NativeMouseMessage::Move:
451 msg = eMouseMove;
452 break;
453 case NativeMouseMessage::ButtonDown:
454 msg = eMouseDown;
455 break;
456 case NativeMouseMessage::ButtonUp:
457 msg = eMouseUp;
458 break;
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);
474 return NS_OK;
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);
494 return NS_OK;
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);
517 return NS_OK;
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) {
530 case PHASE_BEGIN:
531 pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
532 CurrentSpan = aScale;
533 PreviousSpan = 0.999;
534 break;
536 case PHASE_UPDATE:
537 pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
538 if (aScale == mLastPinchSpan) {
539 return NS_ERROR_INVALID_ARG;
541 CurrentSpan = aScale;
542 PreviousSpan = mLastPinchSpan;
543 break;
545 case PHASE_END:
546 pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
547 CurrentSpan = aScale;
548 PreviousSpan = mLastPinchSpan;
549 break;
551 default:
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);
573 return NS_OK;
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) {
586 case PHASE_BEGIN:
587 eventType = PanGestureInput::PANGESTURE_START;
588 break;
589 case PHASE_UPDATE:
590 eventType = PanGestureInput::PANGESTURE_PAN;
591 break;
592 case PHASE_END:
593 eventType = PanGestureInput::PANGESTURE_END;
594 break;
595 default:
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
605 // aModifierFlags.
608 input.mSimulateMomentum =
609 Preferences::GetBool("apz.test.headless.simulate_momentum");
611 DispatchPanGestureInput(input);
613 return NS_OK;
616 } // namespace widget
617 } // namespace mozilla