Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/headless/HeadlessWidget.cpp
Line
Count
Source (jump to first uncovered line)
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 "HeadlessCompositorWidget.h"
7
#include "Layers.h"
8
#include "BasicLayers.h"
9
#include "BasicEvents.h"
10
#include "MouseEvents.h"
11
#include "mozilla/gfx/gfxVars.h"
12
#include "mozilla/ClearOnShutdown.h"
13
#include "mozilla/TextEvents.h"
14
#include "HeadlessKeyBindings.h"
15
16
using namespace mozilla::gfx;
17
using namespace mozilla::layers;
18
19
using mozilla::LogLevel;
20
21
#ifdef MOZ_LOGGING
22
23
#include "mozilla/Logging.h"
24
static mozilla::LazyLogModule sWidgetLog("Widget");
25
static mozilla::LazyLogModule sWidgetFocusLog("WidgetFocus");
26
0
#define LOG(args) MOZ_LOG(sWidgetLog, mozilla::LogLevel::Debug, args)
27
0
#define LOGFOCUS(args) MOZ_LOG(sWidgetFocusLog, mozilla::LogLevel::Debug, args)
28
29
#else
30
31
#define LOG(args)
32
#define LOGFOCUS(args)
33
34
#endif /* MOZ_LOGGING */
35
36
/*static*/ already_AddRefed<nsIWidget>
37
nsIWidget::CreateHeadlessWidget()
38
0
{
39
0
  nsCOMPtr<nsIWidget> widget = new mozilla::widget::HeadlessWidget();
40
0
  return widget.forget();
41
0
}
42
43
namespace mozilla {
44
namespace widget {
45
46
already_AddRefed<gfxContext>
47
CreateDefaultTarget(IntSize aSize)
48
0
{
49
0
  // Always use at least a 1x1 draw target to avoid gfx issues
50
0
  // with 0x0 targets.
51
0
  IntSize size = (aSize.width <= 0 || aSize.height <= 0) ? gfx::IntSize(1, 1) : aSize;
52
0
  RefPtr<DrawTarget> target = Factory::CreateDrawTarget(gfxVars::ContentBackend(), size, SurfaceFormat::B8G8R8A8);
53
0
  RefPtr<gfxContext> ctx = gfxContext::CreatePreservingTransformOrNull(target);
54
0
  return ctx.forget();
55
0
}
56
57
StaticAutoPtr<nsTArray<HeadlessWidget*>> HeadlessWidget::sActiveWindows;
58
59
already_AddRefed<HeadlessWidget>
60
HeadlessWidget::GetActiveWindow()
61
0
{
62
0
  if (!sActiveWindows) {
63
0
    return nullptr;
64
0
  }
65
0
  auto length = sActiveWindows->Length();
66
0
  if (length == 0) {
67
0
    return nullptr;
68
0
  }
69
0
  RefPtr<HeadlessWidget> widget = sActiveWindows->ElementAt(length - 1);
70
0
  return widget.forget();
71
0
}
72
73
HeadlessWidget::HeadlessWidget()
74
  : mEnabled(true)
75
  , mVisible(false)
76
  , mDestroyed(false)
77
  , mTopLevel(nullptr)
78
  , mCompositorWidget(nullptr)
79
  , mLastSizeMode(nsSizeMode_Normal)
80
  , mEffectiveSizeMode(nsSizeMode_Normal)
81
  , mRestoreBounds(0,0,0,0)
82
0
{
83
0
  if (!sActiveWindows) {
84
0
    sActiveWindows = new nsTArray<HeadlessWidget*>();
85
0
    ClearOnShutdown(&sActiveWindows);
86
0
  }
87
0
}
88
89
HeadlessWidget::~HeadlessWidget()
90
0
{
91
0
  LOG(("HeadlessWidget::~HeadlessWidget() [%p]\n", (void *)this));
92
0
93
0
  Destroy();
94
0
}
95
96
void
97
HeadlessWidget::Destroy()
98
0
{
99
0
  if (mDestroyed) {
100
0
    return;
101
0
  }
102
0
  LOG(("HeadlessWidget::Destroy [%p]\n", (void *)this));
103
0
  mDestroyed = true;
104
0
105
0
  if (sActiveWindows) {
106
0
    int32_t index = sActiveWindows->IndexOf(this);
107
0
    if (index != -1) {
108
0
      RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
109
0
      sActiveWindows->RemoveElementAt(index);
110
0
      // If this is the currently active widget and there's a previously active
111
0
      // widget, activate the previous widget.
112
0
      RefPtr<HeadlessWidget> previousActiveWindow = GetActiveWindow();
113
0
      if (this == activeWindow && previousActiveWindow &&
114
0
          previousActiveWindow->mWidgetListener) {
115
0
        previousActiveWindow->mWidgetListener->WindowActivated();
116
0
      }
117
0
    }
118
0
  }
119
0
120
0
  nsBaseWidget::OnDestroy();
121
0
122
0
  nsBaseWidget::Destroy();
123
0
}
124
125
nsresult
126
HeadlessWidget::Create(nsIWidget* aParent,
127
                       nsNativeWidget aNativeParent,
128
                       const LayoutDeviceIntRect& aRect,
129
                       nsWidgetInitData* aInitData)
130
0
{
131
0
  MOZ_ASSERT(!aNativeParent, "No native parents for headless widgets.");
132
0
133
0
  BaseCreate(nullptr, aInitData);
134
0
135
0
  mBounds = aRect;
136
0
  mRestoreBounds = aRect;
137
0
138
0
  if (aParent) {
139
0
    mTopLevel = aParent->GetTopLevelWidget();
140
0
  } else {
141
0
    mTopLevel = this;
142
0
  }
143
0
144
0
  return NS_OK;
145
0
}
146
147
already_AddRefed<nsIWidget>
148
HeadlessWidget::CreateChild(const LayoutDeviceIntRect& aRect,
149
                            nsWidgetInitData* aInitData,
150
                            bool aForceUseIWidgetParent)
151
0
{
152
0
  nsCOMPtr<nsIWidget> widget = nsIWidget::CreateHeadlessWidget();
153
0
  if (!widget) {
154
0
    return nullptr;
155
0
  }
156
0
  if (NS_FAILED(widget->Create(this, nullptr, aRect, aInitData))) {
157
0
    return nullptr;
158
0
  }
159
0
  return widget.forget();
160
0
}
161
162
void HeadlessWidget::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData)
163
0
{
164
0
  *aInitData = mozilla::widget::HeadlessCompositorWidgetInitData(GetClientSize());
165
0
}
166
167
nsIWidget*
168
HeadlessWidget::GetTopLevelWidget()
169
0
{
170
0
  return mTopLevel;
171
0
}
172
173
void
174
HeadlessWidget::RaiseWindow()
175
0
{
176
0
  MOZ_ASSERT(mTopLevel == this ||
177
0
             mWindowType == eWindowType_dialog ||
178
0
             mWindowType == eWindowType_sheet, "Raising a non-toplevel window.");
179
0
180
0
  // Do nothing if this is the currently active window.
181
0
  RefPtr<HeadlessWidget> activeWindow = GetActiveWindow();
182
0
  if (activeWindow == this) {
183
0
    return;
184
0
  }
185
0
186
0
  // Raise the window to the top of the stack.
187
0
  nsWindowZ placement = nsWindowZTop;
188
0
  nsCOMPtr<nsIWidget> actualBelow;
189
0
  if (mWidgetListener)
190
0
    mWidgetListener->ZLevelChanged(true, &placement, nullptr, getter_AddRefs(actualBelow));
191
0
192
0
  // Deactivate the last active window.
193
0
  if (activeWindow && activeWindow->mWidgetListener) {
194
0
    activeWindow->mWidgetListener->WindowDeactivated();
195
0
  }
196
0
197
0
  // Remove this window if it's already tracked.
198
0
  int32_t index = sActiveWindows->IndexOf(this);
199
0
  if (index != -1) {
200
0
    sActiveWindows->RemoveElementAt(index);
201
0
  }
202
0
203
0
  // Activate this window.
204
0
  sActiveWindows->AppendElement(this);
205
0
  if (mWidgetListener)
206
0
    mWidgetListener->WindowActivated();
207
0
}
208
209
void
210
HeadlessWidget::Show(bool aState)
211
0
{
212
0
  mVisible = aState;
213
0
214
0
  LOG(("HeadlessWidget::Show [%p] state %d\n", (void *)this, aState));
215
0
216
0
  // Top-level window and dialogs are activated/raised when shown.
217
0
  if (aState && (mTopLevel == this ||
218
0
                 mWindowType == eWindowType_dialog ||
219
0
                 mWindowType == eWindowType_sheet)) {
220
0
    RaiseWindow();
221
0
  }
222
0
223
0
  ApplySizeModeSideEffects();
224
0
}
225
226
bool
227
HeadlessWidget::IsVisible() const
228
0
{
229
0
  return mVisible;
230
0
}
231
232
nsresult
233
HeadlessWidget::SetFocus(bool aRaise)
234
0
{
235
0
  LOGFOCUS(("  SetFocus %d [%p]\n", aRaise, (void *)this));
236
0
237
0
  // aRaise == true means we request activation of our toplevel window.
238
0
  if (aRaise) {
239
0
    HeadlessWidget* topLevel = (HeadlessWidget*) GetTopLevelWidget();
240
0
241
0
    // The toplevel only becomes active if it's currently visible; otherwise, it
242
0
    // will be activated anyway when it's shown.
243
0
    if (topLevel->IsVisible())
244
0
      topLevel->RaiseWindow();
245
0
  }
246
0
  return NS_OK;
247
0
}
248
249
void
250
HeadlessWidget::Enable(bool aState)
251
0
{
252
0
  mEnabled = aState;
253
0
}
254
255
bool
256
HeadlessWidget::IsEnabled() const
257
0
{
258
0
  return mEnabled;
259
0
}
260
261
void
262
HeadlessWidget::Move(double aX, double aY)
263
0
{
264
0
  LOG(("HeadlessWidget::Move [%p] %f %f\n", (void *)this,
265
0
       aX, aY));
266
0
267
0
  double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
268
0
  int32_t x = NSToIntRound(aX * scale);
269
0
  int32_t y = NSToIntRound(aY * scale);
270
0
271
0
  if (mWindowType == eWindowType_toplevel ||
272
0
      mWindowType == eWindowType_dialog) {
273
0
      SetSizeMode(nsSizeMode_Normal);
274
0
  }
275
0
276
0
  // Since a popup window's x/y coordinates are in relation to
277
0
  // the parent, the parent might have moved so we always move a
278
0
  // popup window.
279
0
  if (mBounds.IsEqualXY(x, y) &&
280
0
      mWindowType != eWindowType_popup) {
281
0
    return;
282
0
  }
283
0
284
0
  mBounds.MoveTo(x, y);
285
0
  NotifyRollupGeometryChange();
286
0
}
287
288
LayoutDeviceIntPoint
289
HeadlessWidget::WidgetToScreenOffset()
290
0
{
291
0
  return mTopLevel->GetBounds().TopLeft();
292
0
}
293
294
LayerManager*
295
HeadlessWidget::GetLayerManager(PLayerTransactionChild* aShadowManager,
296
                                LayersBackend aBackendHint,
297
                                LayerManagerPersistence aPersistence)
298
0
{
299
0
  return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence);
300
0
}
301
302
void
303
HeadlessWidget::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate)
304
0
{
305
0
    if (delegate) {
306
0
        mCompositorWidget = delegate->AsHeadlessCompositorWidget();
307
0
        MOZ_ASSERT(mCompositorWidget,
308
0
                   "HeadlessWidget::SetCompositorWidgetDelegate called with a non-HeadlessCompositorWidget");
309
0
    } else {
310
0
        mCompositorWidget = nullptr;
311
0
    }
312
0
}
313
314
void
315
HeadlessWidget::Resize(double aWidth,
316
                       double aHeight,
317
                       bool   aRepaint)
318
0
{
319
0
  int32_t width = NSToIntRound(aWidth);
320
0
  int32_t height = NSToIntRound(aHeight);
321
0
  ConstrainSize(&width, &height);
322
0
  mBounds.SizeTo(LayoutDeviceIntSize(width, height));
323
0
324
0
  if (mCompositorWidget) {
325
0
    mCompositorWidget->NotifyClientSizeChanged(LayoutDeviceIntSize(mBounds.Width(), mBounds.Height()));
326
0
  }
327
0
  if (mWidgetListener) {
328
0
    mWidgetListener->WindowResized(this, mBounds.Width(), mBounds.Height());
329
0
  }
330
0
  if (mAttachedWidgetListener) {
331
0
    mAttachedWidgetListener->WindowResized(this, mBounds.Width(), mBounds.Height());
332
0
  }
333
0
}
334
335
void
336
HeadlessWidget::Resize(double aX,
337
                       double aY,
338
                       double aWidth,
339
                       double aHeight,
340
                       bool   aRepaint)
341
0
{
342
0
  if (!mBounds.IsEqualXY(aX, aY)) {
343
0
    NotifyWindowMoved(aX, aY);
344
0
  }
345
0
  return Resize(aWidth, aHeight, aRepaint);
346
0
}
347
348
void
349
HeadlessWidget::SetSizeMode(nsSizeMode aMode)
350
0
{
351
0
  LOG(("HeadlessWidget::SetSizeMode [%p] %d\n", (void *)this, aMode));
352
0
353
0
  if (aMode == mSizeMode) {
354
0
    return;
355
0
  }
356
0
357
0
  nsBaseWidget::SetSizeMode(aMode);
358
0
359
0
  // Normally in real widget backends a window event would be triggered that
360
0
  // would cause the window manager to handle resizing the window. In headless
361
0
  // the window must manually be resized.
362
0
  ApplySizeModeSideEffects();
363
0
}
364
365
void
366
HeadlessWidget::ApplySizeModeSideEffects()
367
0
{
368
0
  if (!mVisible || mEffectiveSizeMode == mSizeMode) {
369
0
    return;
370
0
  }
371
0
372
0
  if (mEffectiveSizeMode == nsSizeMode_Normal) {
373
0
    // Store the last normal size bounds so it can be restored when entering
374
0
    // normal mode again.
375
0
    mRestoreBounds = mBounds;
376
0
  }
377
0
378
0
  switch(mSizeMode) {
379
0
  case nsSizeMode_Normal: {
380
0
    Resize(mRestoreBounds.X(), mRestoreBounds.Y(), mRestoreBounds.Width(), mRestoreBounds.Height(), false);
381
0
    break;
382
0
  }
383
0
  case nsSizeMode_Minimized:
384
0
    break;
385
0
  case nsSizeMode_Maximized: {
386
0
    nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
387
0
    if (screen) {
388
0
      int32_t left, top, width, height;
389
0
      if (NS_SUCCEEDED(screen->GetRectDisplayPix(&left, &top, &width, &height))) {
390
0
        Resize(0, 0, width, height, true);
391
0
      }
392
0
    }
393
0
    break;
394
0
  }
395
0
  case nsSizeMode_Fullscreen:
396
0
    // This will take care of resizing the window.
397
0
    nsBaseWidget::InfallibleMakeFullScreen(true);
398
0
    break;
399
0
  default:
400
0
    break;
401
0
  }
402
0
403
0
  mEffectiveSizeMode = mSizeMode;
404
0
}
405
406
nsresult
407
HeadlessWidget::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen)
408
0
{
409
0
  // Directly update the size mode here so a later call SetSizeMode does
410
0
  // nothing.
411
0
  if (aFullScreen) {
412
0
    if (mSizeMode != nsSizeMode_Fullscreen) {
413
0
      mLastSizeMode = mSizeMode;
414
0
    }
415
0
    mSizeMode = nsSizeMode_Fullscreen;
416
0
  } else {
417
0
    mSizeMode = mLastSizeMode;
418
0
  }
419
0
420
0
  // Notify the listener first so size mode change events are triggered before
421
0
  // resize events.
422
0
  if (mWidgetListener) {
423
0
    mWidgetListener->SizeModeChanged(mSizeMode);
424
0
    mWidgetListener->FullscreenChanged(aFullScreen);
425
0
  }
426
0
427
0
  // Real widget backends don't seem to follow a common approach for
428
0
  // when and how many resize events are triggered during fullscreen
429
0
  // transitions. InfallibleMakeFullScreen will trigger a resize, but it
430
0
  // will be ignored if still transitioning to fullscreen, so it must be
431
0
  // triggered on the next tick.
432
0
  RefPtr<HeadlessWidget> self(this);
433
0
  nsCOMPtr<nsIScreen> targetScreen(aTargetScreen);
434
0
  NS_DispatchToCurrentThread(NS_NewRunnableFunction(
435
0
    "HeadlessWidget::MakeFullScreen",
436
0
    [self, targetScreen, aFullScreen]() -> void {
437
0
      self->InfallibleMakeFullScreen(aFullScreen, targetScreen);
438
0
    }));
439
0
440
0
  return NS_OK;
441
0
}
442
443
nsresult
444
HeadlessWidget::AttachNativeKeyEvent(WidgetKeyboardEvent& aEvent)
445
0
{
446
0
  HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
447
0
  return bindings.AttachNativeKeyEvent(aEvent);
448
0
}
449
450
void
451
HeadlessWidget::GetEditCommands(NativeKeyBindingsType aType,
452
                                const WidgetKeyboardEvent& aEvent,
453
                                nsTArray<CommandInt>& aCommands)
454
0
{
455
0
  // Validate the arguments.
456
0
  nsIWidget::GetEditCommands(aType, aEvent, aCommands);
457
0
458
0
  HeadlessKeyBindings& bindings = HeadlessKeyBindings::GetInstance();
459
0
  bindings.GetEditCommands(aType, aEvent, aCommands);
460
0
}
461
462
nsresult
463
HeadlessWidget::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
464
0
{
465
#ifdef DEBUG
466
  debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "HeadlessWidget", 0);
467
#endif
468
469
0
  aStatus = nsEventStatus_eIgnore;
470
0
471
0
  if (mAttachedWidgetListener) {
472
0
    aStatus = mAttachedWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
473
0
  } else if (mWidgetListener) {
474
0
    aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
475
0
  }
476
0
477
0
  return NS_OK;
478
0
}
479
480
nsresult
481
HeadlessWidget::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
482
                                           uint32_t aNativeMessage,
483
                                           uint32_t aModifierFlags,
484
                                           nsIObserver* aObserver)
485
0
{
486
0
  AutoObserverNotifier notifier(aObserver, "mouseevent");
487
0
  EventMessage msg;
488
0
  switch (aNativeMessage) {
489
0
    case MOZ_HEADLESS_MOUSE_MOVE:
490
0
      msg = eMouseMove;
491
0
      break;
492
0
    case MOZ_HEADLESS_MOUSE_DOWN:
493
0
      msg = eMouseDown;
494
0
      break;
495
0
    case MOZ_HEADLESS_MOUSE_UP:
496
0
      msg = eMouseUp;
497
0
      break;
498
0
    default:
499
0
      MOZ_ASSERT_UNREACHABLE("Unsupported synthesized mouse event");
500
0
      return NS_ERROR_UNEXPECTED;
501
0
  }
502
0
  WidgetMouseEvent event(true, msg, this, WidgetMouseEvent::eReal);
503
0
  event.mRefPoint = aPoint - WidgetToScreenOffset();
504
0
  if (msg == eMouseDown || msg == eMouseUp) {
505
0
    event.button = WidgetMouseEvent::eLeftButton;
506
0
  }
507
0
  if (msg == eMouseDown) {
508
0
    event.mClickCount = 1;
509
0
  }
510
0
  event.AssignEventTime(WidgetEventTime());
511
0
  DispatchInputEvent(&event);
512
0
  return NS_OK;
513
0
}
514
515
nsresult
516
HeadlessWidget::SynthesizeNativeMouseScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
517
                                                 uint32_t aNativeMessage,
518
                                                 double aDeltaX,
519
                                                 double aDeltaY,
520
                                                 double aDeltaZ,
521
                                                 uint32_t aModifierFlags,
522
                                                 uint32_t aAdditionalFlags,
523
                                                 nsIObserver* aObserver)
524
0
{
525
0
  AutoObserverNotifier notifier(aObserver, "mousescrollevent");
526
0
  printf(">>> DEBUG_ME: Synth: aDeltaY=%f\n", aDeltaY);
527
0
  // The various platforms seem to handle scrolling deltas differently,
528
0
  // but the following seems to emulate it well enough.
529
0
  WidgetWheelEvent event(true, eWheel, this);
530
0
  event.mDeltaMode = MOZ_HEADLESS_SCROLL_DELTA_MODE;
531
0
  event.mIsNoLineOrPageDelta = true;
532
0
  event.mDeltaX = -aDeltaX * MOZ_HEADLESS_SCROLL_MULTIPLIER;
533
0
  event.mDeltaY = -aDeltaY * MOZ_HEADLESS_SCROLL_MULTIPLIER;
534
0
  event.mDeltaZ = -aDeltaZ * MOZ_HEADLESS_SCROLL_MULTIPLIER;
535
0
  event.mRefPoint = aPoint - WidgetToScreenOffset();
536
0
  event.AssignEventTime(WidgetEventTime());
537
0
  DispatchInputEvent(&event);
538
0
  return NS_OK;
539
0
}
540
541
nsresult
542
HeadlessWidget::SynthesizeNativeTouchPoint(uint32_t aPointerId,
543
                                           TouchPointerState aPointerState,
544
                                           LayoutDeviceIntPoint aPoint,
545
                                           double aPointerPressure,
546
                                           uint32_t aPointerOrientation,
547
                                           nsIObserver* aObserver)
548
0
{
549
0
  AutoObserverNotifier notifier(aObserver, "touchpoint");
550
0
551
0
  MOZ_ASSERT(NS_IsMainThread());
552
0
  if (aPointerState == TOUCH_HOVER) {
553
0
    return NS_ERROR_UNEXPECTED;
554
0
  }
555
0
556
0
  if (!mSynthesizedTouchInput) {
557
0
    mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
558
0
  }
559
0
560
0
  LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
561
0
  MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
562
0
      mSynthesizedTouchInput.get(), PR_IntervalNow(), TimeStamp::Now(),
563
0
      aPointerId, aPointerState, pointInWindow, aPointerPressure,
564
0
      aPointerOrientation);
565
0
  DispatchTouchInput(inputToDispatch);
566
0
  return NS_OK;
567
0
}
568
569
} // namespace widget
570
} // namespace mozilla