Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/gfx/layers/apz/util/APZCCallbackHelper.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "APZCCallbackHelper.h"
8
9
#include "TouchActionHelper.h"
10
#include "gfxPlatform.h" // For gfxPlatform::UseTiling
11
#include "gfxPrefs.h"
12
#include "LayersLogging.h"  // For Stringify
13
#include "mozilla/dom/Element.h"
14
#include "mozilla/dom/MouseEventBinding.h"
15
#include "mozilla/dom/TabParent.h"
16
#include "mozilla/IntegerPrintfMacros.h"
17
#include "mozilla/layers/LayerTransactionChild.h"
18
#include "mozilla/layers/ShadowLayers.h"
19
#include "mozilla/layers/WebRenderLayerManager.h"
20
#include "mozilla/layers/WebRenderBridgeChild.h"
21
#include "mozilla/TouchEvents.h"
22
#include "nsContainerFrame.h"
23
#include "nsContentUtils.h"
24
#include "nsIContent.h"
25
#include "nsIDOMWindow.h"
26
#include "nsIDOMWindowUtils.h"
27
#include "nsIDocument.h"
28
#include "nsIInterfaceRequestorUtils.h"
29
#include "nsIScrollableFrame.h"
30
#include "nsLayoutUtils.h"
31
#include "nsPrintfCString.h"
32
#include "nsRefreshDriver.h"
33
#include "nsString.h"
34
#include "nsView.h"
35
#include "Layers.h"
36
37
// #define APZCCH_LOGGING 1
38
#ifdef APZCCH_LOGGING
39
#define APZCCH_LOG(...) printf_stderr("APZCCH: " __VA_ARGS__)
40
#else
41
#define APZCCH_LOG(...)
42
#endif
43
44
namespace mozilla {
45
namespace layers {
46
47
using dom::TabParent;
48
49
uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
50
51
void
52
APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
53
    mozilla::layers::FrameMetrics& aFrameMetrics,
54
    const CSSPoint& aActualScrollOffset)
55
0
{
56
0
  // Correct the display-port by the difference between the requested scroll
57
0
  // offset and the resulting scroll offset after setting the requested value.
58
0
  ScreenPoint shift =
59
0
      (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
60
0
      aFrameMetrics.DisplayportPixelsPerCSSPixel();
61
0
  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
62
0
  margins.left -= shift.x;
63
0
  margins.right += shift.x;
64
0
  margins.top -= shift.y;
65
0
  margins.bottom += shift.y;
66
0
  aFrameMetrics.SetDisplayPortMargins(margins);
67
0
}
68
69
static void
70
RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
71
0
{
72
0
  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
73
0
  margins.right = margins.left = margins.LeftRight() / 2;
74
0
  margins.top = margins.bottom = margins.TopBottom() / 2;
75
0
  aFrameMetrics.SetDisplayPortMargins(margins);
76
0
}
77
78
static already_AddRefed<nsIPresShell>
79
GetPresShell(const nsIContent* aContent)
80
0
{
81
0
  nsCOMPtr<nsIPresShell> result;
82
0
  if (nsIDocument* doc = aContent->GetComposedDoc()) {
83
0
    result = doc->GetShell();
84
0
  }
85
0
  return result.forget();
86
0
}
87
88
static CSSPoint
89
ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
90
0
{
91
0
  aSuccessOut = false;
92
0
  CSSPoint targetScrollPosition = aMetrics.IsRootContent()
93
0
    ? aMetrics.GetViewport().TopLeft()
94
0
    : aMetrics.GetScrollOffset();
95
0
96
0
  if (!aFrame) {
97
0
    return targetScrollPosition;
98
0
  }
99
0
100
0
  CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
101
0
102
0
  // If the repaint request was triggered due to a previous main-thread scroll
103
0
  // offset update sent to the APZ, then we don't need to do another scroll here
104
0
  // and we can just return.
105
0
  if (!aMetrics.GetScrollOffsetUpdated()) {
106
0
    return geckoScrollPosition;
107
0
  }
108
0
109
0
  // If this frame is overflow:hidden, then the expectation is that it was
110
0
  // sized in a way that respects its scrollable boundaries. For the root
111
0
  // frame, this means that it cannot be scrolled in such a way that it moves
112
0
  // the layout viewport. For a non-root frame, this means that it cannot be
113
0
  // scrolled at all.
114
0
  //
115
0
  // In either case, |targetScrollPosition| should be the same as
116
0
  // |geckoScrollPosition| here.
117
0
  //
118
0
  // However, this is slightly racy. We query the overflow property of the
119
0
  // scroll frame at the time the repaint request arrives at the main thread
120
0
  // (i.e., right now), but APZ made the decision of whether or not to allow
121
0
  // scrolling based on the information it had at the time it processed the
122
0
  // scroll event. The overflow property could have changed at some time
123
0
  // between the two events and so APZ may have computed a scrollable region
124
0
  // that is larger than what is actually allowed.
125
0
  //
126
0
  // Currently, we allow the scroll position to change even though the frame is
127
0
  // overflow:hidden (that is, we take |targetScrollPosition|). If this turns
128
0
  // out to be problematic, an alternative solution would be to ignore the
129
0
  // scroll position change (that is, use |geckoScrollPosition|).
130
0
  if (aFrame->GetScrollStyles().mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
131
0
      targetScrollPosition.y != geckoScrollPosition.y) {
132
0
    NS_WARNING(nsPrintfCString(
133
0
          "APZCCH: targetScrollPosition.y (%f) != geckoScrollPosition.y (%f)",
134
0
          targetScrollPosition.y, geckoScrollPosition.y).get());
135
0
  }
136
0
  if (aFrame->GetScrollStyles().mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
137
0
      targetScrollPosition.x != geckoScrollPosition.x) {
138
0
    NS_WARNING(nsPrintfCString(
139
0
          "APZCCH: targetScrollPosition.x (%f) != geckoScrollPosition.x (%f)",
140
0
          targetScrollPosition.x, geckoScrollPosition.x).get());
141
0
  }
142
0
143
0
  // If the scrollable frame is currently in the middle of an async or smooth
144
0
  // scroll then we don't want to interrupt it (see bug 961280).
145
0
  // Also if the scrollable frame got a scroll request from a higher priority origin
146
0
  // since the last layers update, then we don't want to push our scroll request
147
0
  // because we'll clobber that one, which is bad.
148
0
  bool scrollInProgress = APZCCallbackHelper::IsScrollInProgress(aFrame);
149
0
  if (!scrollInProgress) {
150
0
    aFrame->ScrollToCSSPixelsApproximate(targetScrollPosition, nsGkAtoms::apz);
151
0
    geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
152
0
    aSuccessOut = true;
153
0
  }
154
0
  // Return the final scroll position after setting it so that anything that relies
155
0
  // on it can have an accurate value. Note that even if we set it above re-querying it
156
0
  // is a good idea because it may have gotten clamped or rounded.
157
0
  return geckoScrollPosition;
158
0
}
159
160
/**
161
 * Scroll the scroll frame associated with |aContent| to the scroll position
162
 * requested in |aMetrics|.
163
 * The scroll offset in |aMetrics| is updated to reflect the actual scroll
164
 * position.
165
 * The displayport stored in |aMetrics| and the callback-transform stored on
166
 * the content are updated to reflect any difference between the requested
167
 * and actual scroll positions.
168
 */
169
static void
170
ScrollFrame(nsIContent* aContent,
171
            FrameMetrics& aMetrics)
172
0
{
173
0
  // Scroll the window to the desired spot
174
0
  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
175
0
  if (sf) {
176
0
    sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
177
0
    sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
178
0
    if (sf->IsRootScrollFrameOfDocument()) {
179
0
      if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
180
0
        shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aMetrics.GetScrollOffset()));
181
0
      }
182
0
    }
183
0
  }
184
0
  bool scrollUpdated = false;
185
0
  CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
186
0
  CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
187
0
188
0
  if (scrollUpdated) {
189
0
    if (aMetrics.IsScrollInfoLayer()) {
190
0
      // In cases where the APZ scroll offset is different from the content scroll
191
0
      // offset, we want to interpret the margins as relative to the APZ scroll
192
0
      // offset except when the frame is not scrollable by APZ. Therefore, if the
193
0
      // layer is a scroll info layer, we leave the margins as-is and they will
194
0
      // be interpreted as relative to the content scroll offset.
195
0
      if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
196
0
        frame->SchedulePaint();
197
0
      }
198
0
    } else {
199
0
      // Correct the display port due to the difference between mScrollOffset and the
200
0
      // actual scroll offset.
201
0
      APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
202
0
    }
203
0
  } else if (aMetrics.IsRootContent() &&
204
0
             aMetrics.GetScrollOffset() != aMetrics.GetViewport().TopLeft()) {
205
0
    // APZ uses the visual viewport's offset to calculate where to place the
206
0
    // display port, so the display port is misplaced when a pinch zoom occurs.
207
0
    //
208
0
    // We need to force a display port adjustment in the following paint to
209
0
    // account for a difference between mScrollOffset and the actual scroll
210
0
    // offset in repaints requested by AsyncPanZoomController::NotifyLayersUpdated.
211
0
    APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
212
0
  } else {
213
0
    // For whatever reason we couldn't update the scroll offset on the scroll frame,
214
0
    // which means the data APZ used for its displayport calculation is stale. Fall
215
0
    // back to a sane default behaviour. Note that we don't tile-align the recentered
216
0
    // displayport because tile-alignment depends on the scroll position, and the
217
0
    // scroll position here is out of our control. See bug 966507 comment 21 for a
218
0
    // more detailed explanation.
219
0
    RecenterDisplayPort(aMetrics);
220
0
  }
221
0
222
0
  aMetrics.SetScrollOffset(actualScrollOffset);
223
0
224
0
  // APZ transforms inputs assuming we applied the exact scroll offset it
225
0
  // requested (|apzScrollOffset|). Since we may not have, record the difference
226
0
  // between what APZ asked for and what we actually applied, and apply it to
227
0
  // input events to compensate.
228
0
  // Note that if the main-thread had a change in its scroll position, we don't
229
0
  // want to record that difference here, because it can be large and throw off
230
0
  // input events by a large amount. It is also going to be transient, because
231
0
  // any main-thread scroll position change will be synced to APZ and we will
232
0
  // get another repaint request when APZ confirms. In the interval while this
233
0
  // is happening we can just leave the callback transform as it was.
234
0
  bool mainThreadScrollChanged =
235
0
    sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
236
0
  if (aContent && !mainThreadScrollChanged) {
237
0
    CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
238
0
    aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
239
0
                          nsINode::DeleteProperty<CSSPoint>);
240
0
  }
241
0
}
242
243
static void
244
SetDisplayPortMargins(nsIPresShell* aPresShell,
245
                      nsIContent* aContent,
246
                      const FrameMetrics& aMetrics)
247
0
{
248
0
  if (!aContent) {
249
0
    return;
250
0
  }
251
0
252
0
  bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
253
0
  ScreenMargin margins = aMetrics.GetDisplayPortMargins();
254
0
  nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
255
0
  if (!hadDisplayPort) {
256
0
    nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
257
0
        aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
258
0
  }
259
0
260
0
  CSSSize baseSize = aMetrics.CalculateCompositedSizeInCssPixels();
261
0
  nsRect base(0, 0,
262
0
              baseSize.width * AppUnitsPerCSSPixel(),
263
0
              baseSize.height * AppUnitsPerCSSPixel());
264
0
  nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
265
0
}
266
267
static void
268
SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
269
0
{
270
0
  aContent->SetProperty(nsGkAtoms::paintRequestTime,
271
0
                        new TimeStamp(aPaintRequestTime),
272
0
                        nsINode::DeleteProperty<TimeStamp>);
273
0
}
274
275
void
276
APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
277
0
{
278
0
  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
279
0
    return;
280
0
  }
281
0
  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
282
0
  if (!content) {
283
0
    return;
284
0
  }
285
0
286
0
  nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
287
0
  if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
288
0
    return;
289
0
  }
290
0
291
0
  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
292
0
293
0
  if (gfxPrefs::APZAllowZooming() && aMetrics.GetScrollOffsetUpdated()) {
294
0
    // If zooming is disabled then we don't really want to let APZ fiddle
295
0
    // with these things. In theory setting the resolution here should be a
296
0
    // no-op, but setting the visual viewport size is bad because it can cause a
297
0
    // stale value to be returned by window.innerWidth/innerHeight (see bug 1187792).
298
0
    //
299
0
    // We also skip this codepath unless the metrics has a scroll offset update
300
0
    // type other eNone, because eNone just means that this repaint request
301
0
    // was triggered by APZ in response to a main-thread update. In this
302
0
    // scenario we don't want to update the main-thread resolution because
303
0
    // it can trigger unnecessary reflows.
304
0
305
0
    float presShellResolution = shell->GetResolution();
306
0
307
0
    // If the pres shell resolution has changed on the content side side
308
0
    // the time this repaint request was fired, consider this request out of date
309
0
    // and drop it; setting a zoom based on the out-of-date resolution can have
310
0
    // the effect of getting us stuck with the stale resolution.
311
0
    if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
312
0
      return;
313
0
    }
314
0
315
0
    // The pres shell resolution is updated by the the async zoom since the
316
0
    // last paint.
317
0
    presShellResolution = aMetrics.GetPresShellResolution()
318
0
                        * aMetrics.GetAsyncZoom().scale;
319
0
    shell->SetResolutionAndScaleTo(presShellResolution);
320
0
  }
321
0
322
0
  // Do this as late as possible since scrolling can flush layout. It also
323
0
  // adjusts the display port margins, so do it before we set those.
324
0
  ScrollFrame(content, aMetrics);
325
0
326
0
  SetDisplayPortMargins(shell, content, aMetrics);
327
0
  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
328
0
}
329
330
void
331
APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
332
0
{
333
0
  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
334
0
    return;
335
0
  }
336
0
  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
337
0
  if (!content) {
338
0
    return;
339
0
  }
340
0
341
0
  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
342
0
343
0
  // We don't currently support zooming for subframes, so nothing extra
344
0
  // needs to be done beyond the tasks common to this and UpdateRootFrame.
345
0
  ScrollFrame(content, aMetrics);
346
0
  if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
347
0
    SetDisplayPortMargins(shell, content, aMetrics);
348
0
  }
349
0
  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
350
0
}
351
352
bool
353
APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
354
                                                 uint32_t* aPresShellIdOut,
355
                                                 FrameMetrics::ViewID* aViewIdOut)
356
0
{
357
0
    if (!aContent) {
358
0
      return false;
359
0
    }
360
0
    *aViewIdOut = nsLayoutUtils::FindOrCreateIDFor(aContent);
361
0
    if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
362
0
      *aPresShellIdOut = shell->GetPresShellId();
363
0
      return true;
364
0
    }
365
0
    return false;
366
0
}
367
368
void
369
APZCCallbackHelper::InitializeRootDisplayport(nsIPresShell* aPresShell)
370
0
{
371
0
  // Create a view-id and set a zero-margin displayport for the root element
372
0
  // of the root document in the chrome process. This ensures that the scroll
373
0
  // frame for this element gets an APZC, which in turn ensures that all content
374
0
  // in the chrome processes is covered by an APZC.
375
0
  // The displayport is zero-margin because this element is generally not
376
0
  // actually scrollable (if it is, APZC will set proper margins when it's
377
0
  // scrolled).
378
0
  if (!aPresShell) {
379
0
    return;
380
0
  }
381
0
382
0
  MOZ_ASSERT(aPresShell->GetDocument());
383
0
  nsIContent* content = aPresShell->GetDocument()->GetDocumentElement();
384
0
  if (!content) {
385
0
    return;
386
0
  }
387
0
388
0
  uint32_t presShellId;
389
0
  FrameMetrics::ViewID viewId;
390
0
  if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, &viewId)) {
391
0
    nsPresContext* pc = aPresShell->GetPresContext();
392
0
    // This code is only correct for root content or toplevel documents.
393
0
    MOZ_ASSERT(!pc || pc->IsRootContentDocument() || !pc->GetParentPresContext());
394
0
    nsIFrame* frame = aPresShell->GetRootScrollFrame();
395
0
    if (!frame) {
396
0
      frame = aPresShell->GetRootFrame();
397
0
    }
398
0
    nsRect baseRect;
399
0
    if (frame) {
400
0
      baseRect =
401
0
        nsRect(nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
402
0
    } else if (pc) {
403
0
      baseRect = nsRect(nsPoint(0, 0), pc->GetVisibleArea().Size());
404
0
    }
405
0
    nsLayoutUtils::SetDisplayPortBaseIfNotSet(content, baseRect);
406
0
    // Note that we also set the base rect that goes with these margins in
407
0
    // nsRootBoxFrame::BuildDisplayList.
408
0
    nsLayoutUtils::SetDisplayPortMargins(content, aPresShell, ScreenMargin(), 0,
409
0
        nsLayoutUtils::RepaintMode::DoNotRepaint);
410
0
    nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
411
0
        content->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::DoNotRepaint);
412
0
  }
413
0
}
414
415
nsPresContext*
416
APZCCallbackHelper::GetPresContextForContent(nsIContent* aContent)
417
0
{
418
0
  nsIDocument* doc = aContent->GetComposedDoc();
419
0
  if (!doc) {
420
0
      return nullptr;
421
0
  }
422
0
  nsIPresShell* shell = doc->GetShell();
423
0
  if (!shell) {
424
0
      return nullptr;
425
0
  }
426
0
  return shell->GetPresContext();
427
0
}
428
429
nsIPresShell*
430
APZCCallbackHelper::GetRootContentDocumentPresShellForContent(nsIContent* aContent)
431
0
{
432
0
    nsPresContext* context = GetPresContextForContent(aContent);
433
0
    if (!context) {
434
0
        return nullptr;
435
0
    }
436
0
    context = context->GetToplevelContentDocumentPresContext();
437
0
    if (!context) {
438
0
        return nullptr;
439
0
    }
440
0
    return context->PresShell();
441
0
}
442
443
static nsIPresShell*
444
GetRootDocumentPresShell(nsIContent* aContent)
445
0
{
446
0
    nsIDocument* doc = aContent->GetComposedDoc();
447
0
    if (!doc) {
448
0
        return nullptr;
449
0
    }
450
0
    nsIPresShell* shell = doc->GetShell();
451
0
    if (!shell) {
452
0
        return nullptr;
453
0
    }
454
0
    nsPresContext* context = shell->GetPresContext();
455
0
    if (!context) {
456
0
        return nullptr;
457
0
    }
458
0
    context = context->GetRootPresContext();
459
0
    if (!context) {
460
0
        return nullptr;
461
0
    }
462
0
    return context->PresShell();
463
0
}
464
465
CSSPoint
466
APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
467
                                           const ScrollableLayerGuid& aGuid)
468
0
{
469
0
    CSSPoint input = aInput;
470
0
    if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
471
0
        return input;
472
0
    }
473
0
    nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
474
0
    if (!content) {
475
0
        return input;
476
0
    }
477
0
478
0
    // First, scale inversely by the root content document's pres shell
479
0
    // resolution to cancel the scale-to-resolution transform that the
480
0
    // compositor adds to the layer with the pres shell resolution. The points
481
0
    // sent to Gecko by APZ don't have this transform unapplied (unlike other
482
0
    // compositor-side transforms) because APZ doesn't know about it.
483
0
    if (nsIPresShell* shell = GetRootDocumentPresShell(content)) {
484
0
        input = input / shell->GetResolution();
485
0
    }
486
0
487
0
    // This represents any resolution on the Root Content Document (RCD)
488
0
    // that's not on the Root Document (RD). That is, on platforms where
489
0
    // RCD == RD, it's 1, and on platforms where RCD != RD, it's the RCD
490
0
    // resolution. 'input' has this resolution applied, but the scroll
491
0
    // delta retrieved below do not, so we need to apply them to the
492
0
    // delta before adding the delta to 'input'. (Technically, deltas
493
0
    // from scroll frames outside the RCD would already have this
494
0
    // resolution applied, but we don't have such scroll frames in
495
0
    // practice.)
496
0
    float nonRootResolution = 1.0f;
497
0
    if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
498
0
      nonRootResolution = shell->GetCumulativeNonRootScaleResolution();
499
0
    }
500
0
    // Now apply the callback-transform. This is only approximately correct,
501
0
    // see the comment on GetCumulativeApzCallbackTransform for details.
502
0
    CSSPoint transform = nsLayoutUtils::GetCumulativeApzCallbackTransform(content->GetPrimaryFrame());
503
0
    return input + transform * nonRootResolution;
504
0
}
505
506
LayoutDeviceIntPoint
507
APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
508
                                           const ScrollableLayerGuid& aGuid,
509
                                           const CSSToLayoutDeviceScale& aScale)
510
0
{
511
0
    LayoutDevicePoint point = LayoutDevicePoint(aPoint.x, aPoint.y);
512
0
    point = ApplyCallbackTransform(point / aScale, aGuid) * aScale;
513
0
    return LayoutDeviceIntPoint::Round(point);
514
0
}
515
516
void
517
APZCCallbackHelper::ApplyCallbackTransform(WidgetEvent& aEvent,
518
                                           const ScrollableLayerGuid& aGuid,
519
                                           const CSSToLayoutDeviceScale& aScale)
520
0
{
521
0
  if (aEvent.AsTouchEvent()) {
522
0
    WidgetTouchEvent& event = *(aEvent.AsTouchEvent());
523
0
    for (size_t i = 0; i < event.mTouches.Length(); i++) {
524
0
      event.mTouches[i]->mRefPoint = ApplyCallbackTransform(
525
0
          event.mTouches[i]->mRefPoint, aGuid, aScale);
526
0
    }
527
0
  } else {
528
0
    aEvent.mRefPoint = ApplyCallbackTransform(aEvent.mRefPoint, aGuid, aScale);
529
0
  }
530
0
}
531
532
nsEventStatus
533
APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent)
534
0
{
535
0
  nsEventStatus status = nsEventStatus_eConsumeNoDefault;
536
0
  if (aEvent.mWidget) {
537
0
    aEvent.mWidget->DispatchEvent(&aEvent, status);
538
0
  }
539
0
  return status;
540
0
}
541
542
nsEventStatus
543
APZCCallbackHelper::DispatchSynthesizedMouseEvent(EventMessage aMsg,
544
                                                  uint64_t aTime,
545
                                                  const LayoutDevicePoint& aRefPoint,
546
                                                  Modifiers aModifiers,
547
                                                  int32_t aClickCount,
548
                                                  nsIWidget* aWidget)
549
0
{
550
0
  MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown ||
551
0
             aMsg == eMouseUp || aMsg == eMouseLongTap);
552
0
553
0
  WidgetMouseEvent event(true, aMsg, aWidget,
554
0
                         WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
555
0
  event.mRefPoint = LayoutDeviceIntPoint::Truncate(aRefPoint.x, aRefPoint.y);
556
0
  event.mTime = aTime;
557
0
  event.button = WidgetMouseEvent::eLeftButton;
558
0
  event.inputSource = dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH;
559
0
  if (aMsg == eMouseLongTap) {
560
0
    event.mFlags.mOnlyChromeDispatch = true;
561
0
  }
562
0
  event.mIgnoreRootScrollFrame = true;
563
0
  if (aMsg != eMouseMove) {
564
0
    event.mClickCount = aClickCount;
565
0
  }
566
0
  event.mModifiers = aModifiers;
567
0
  // Real touch events will generate corresponding pointer events. We set
568
0
  // convertToPointer to false to prevent the synthesized mouse events generate
569
0
  // pointer events again.
570
0
  event.convertToPointer = false;
571
0
  return DispatchWidgetEvent(event);
572
0
}
573
574
bool
575
APZCCallbackHelper::DispatchMouseEvent(const nsCOMPtr<nsIPresShell>& aPresShell,
576
                                       const nsString& aType,
577
                                       const CSSPoint& aPoint,
578
                                       int32_t aButton,
579
                                       int32_t aClickCount,
580
                                       int32_t aModifiers,
581
                                       bool aIgnoreRootScrollFrame,
582
                                       unsigned short aInputSourceArg,
583
                                       uint32_t aPointerId)
584
0
{
585
0
  NS_ENSURE_TRUE(aPresShell, true);
586
0
587
0
  bool defaultPrevented = false;
588
0
  nsContentUtils::SendMouseEvent(aPresShell, aType, aPoint.x, aPoint.y,
589
0
      aButton, nsIDOMWindowUtils::MOUSE_BUTTONS_NOT_SPECIFIED, aClickCount,
590
0
      aModifiers, aIgnoreRootScrollFrame, 0, aInputSourceArg, aPointerId, false,
591
0
      &defaultPrevented, false, /* aIsWidgetEventSynthesized = */ false);
592
0
  return defaultPrevented;
593
0
}
594
595
596
void
597
APZCCallbackHelper::FireSingleTapEvent(const LayoutDevicePoint& aPoint,
598
                                       Modifiers aModifiers,
599
                                       int32_t aClickCount,
600
                                       nsIWidget* aWidget)
601
0
{
602
0
  if (aWidget->Destroyed()) {
603
0
    return;
604
0
  }
605
0
  APZCCH_LOG("Dispatching single-tap component events to %s\n",
606
0
    Stringify(aPoint).c_str());
607
0
  int time = 0;
608
0
  DispatchSynthesizedMouseEvent(eMouseMove, time, aPoint, aModifiers, aClickCount, aWidget);
609
0
  DispatchSynthesizedMouseEvent(eMouseDown, time, aPoint, aModifiers, aClickCount, aWidget);
610
0
  DispatchSynthesizedMouseEvent(eMouseUp, time, aPoint, aModifiers, aClickCount, aWidget);
611
0
}
612
613
static dom::Element*
614
GetDisplayportElementFor(nsIScrollableFrame* aScrollableFrame)
615
0
{
616
0
  if (!aScrollableFrame) {
617
0
    return nullptr;
618
0
  }
619
0
  nsIFrame* scrolledFrame = aScrollableFrame->GetScrolledFrame();
620
0
  if (!scrolledFrame) {
621
0
    return nullptr;
622
0
  }
623
0
  // |scrolledFrame| should at this point be the root content frame of the
624
0
  // nearest ancestor scrollable frame. The element corresponding to this
625
0
  // frame should be the one with the displayport set on it, so find that
626
0
  // element and return it.
627
0
  nsIContent* content = scrolledFrame->GetContent();
628
0
  MOZ_ASSERT(content->IsElement()); // roc says this must be true
629
0
  return content->AsElement();
630
0
}
631
632
633
static dom::Element*
634
GetRootDocumentElementFor(nsIWidget* aWidget)
635
0
{
636
0
  // This returns the root element that ChromeProcessController sets the
637
0
  // displayport on during initialization.
638
0
  if (nsView* view = nsView::GetViewFor(aWidget)) {
639
0
    if (nsIPresShell* shell = view->GetPresShell()) {
640
0
      MOZ_ASSERT(shell->GetDocument());
641
0
      return shell->GetDocument()->GetDocumentElement();
642
0
    }
643
0
  }
644
0
  return nullptr;
645
0
}
646
647
static nsIFrame*
648
UpdateRootFrameForTouchTargetDocument(nsIFrame* aRootFrame)
649
0
{
650
#if defined(MOZ_WIDGET_ANDROID)
651
  // Re-target so that the hit test is performed relative to the frame for the
652
  // Root Content Document instead of the Root Document which are different in
653
  // Android. See bug 1229752 comment 16 for an explanation of why this is necessary.
654
  if (nsIDocument* doc = aRootFrame->PresShell()->GetPrimaryContentDocument()) {
655
    if (nsIPresShell* shell = doc->GetShell()) {
656
      if (nsIFrame* frame = shell->GetRootFrame()) {
657
        return frame;
658
      }
659
    }
660
  }
661
#endif
662
  return aRootFrame;
663
0
}
664
665
// Determine the scrollable target frame for the given point and add it to
666
// the target list. If the frame doesn't have a displayport, set one.
667
// Return whether or not a displayport was set.
668
static bool
669
PrepareForSetTargetAPZCNotification(nsIWidget* aWidget,
670
                                    const ScrollableLayerGuid& aGuid,
671
                                    nsIFrame* aRootFrame,
672
                                    const LayoutDeviceIntPoint& aRefPoint,
673
                                    nsTArray<ScrollableLayerGuid>* aTargets)
674
0
{
675
0
  ScrollableLayerGuid guid(aGuid.mLayersId, 0, FrameMetrics::NULL_SCROLL_ID);
676
0
  nsPoint point =
677
0
    nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aRefPoint, aRootFrame);
678
0
  uint32_t flags = 0;
679
#ifdef MOZ_WIDGET_ANDROID
680
  // On Android, we need IGNORE_ROOT_SCROLL_FRAME for correct hit testing
681
  // when zoomed out. On desktop, don't use it because it interferes with
682
  // hit testing for some purposes such as scrollbar dragging.
683
  flags = nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME;
684
#endif
685
  nsIFrame* target =
686
0
    nsLayoutUtils::GetFrameForPoint(aRootFrame, point, flags);
687
0
  nsIScrollableFrame* scrollAncestor = target
688
0
    ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
689
0
    : aRootFrame->PresShell()->GetRootScrollFrameAsScrollable();
690
0
691
0
  // Assuming that if there's no scrollAncestor, there's already a displayPort.
692
0
  nsCOMPtr<dom::Element> dpElement = scrollAncestor
693
0
    ? GetDisplayportElementFor(scrollAncestor)
694
0
    : GetRootDocumentElementFor(aWidget);
695
0
696
#ifdef APZCCH_LOGGING
697
  nsAutoString dpElementDesc;
698
  if (dpElement) {
699
    dpElement->Describe(dpElementDesc);
700
  }
701
  APZCCH_LOG("For event at %s found scrollable element %p (%s)\n",
702
      Stringify(aRefPoint).c_str(), dpElement.get(),
703
      NS_LossyConvertUTF16toASCII(dpElementDesc).get());
704
#endif
705
706
0
  bool guidIsValid = APZCCallbackHelper::GetOrCreateScrollIdentifiers(
707
0
    dpElement, &(guid.mPresShellId), &(guid.mScrollId));
708
0
  aTargets->AppendElement(guid);
709
0
710
0
  if (!guidIsValid || nsLayoutUtils::HasDisplayPort(dpElement)) {
711
0
    return false;
712
0
  }
713
0
714
0
  if (!scrollAncestor) {
715
0
    // This can happen if the document element gets swapped out after ChromeProcessController
716
0
    // runs InitializeRootDisplayport. In this case let's try to set a displayport again and
717
0
    // bail out on this operation.
718
0
    APZCCH_LOG("Widget %p's document element %p didn't have a displayport\n",
719
0
        aWidget, dpElement.get());
720
0
    APZCCallbackHelper::InitializeRootDisplayport(aRootFrame->PresShell());
721
0
    return false;
722
0
  }
723
0
724
0
  APZCCH_LOG("%p didn't have a displayport, so setting one...\n", dpElement.get());
725
0
  bool activated = nsLayoutUtils::CalculateAndSetDisplayPortMargins(
726
0
      scrollAncestor, nsLayoutUtils::RepaintMode::Repaint);
727
0
  if (!activated) {
728
0
    return false;
729
0
  }
730
0
731
0
  nsIFrame* frame = do_QueryFrame(scrollAncestor);
732
0
  nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(frame,
733
0
    nsLayoutUtils::RepaintMode::Repaint);
734
0
735
0
  return true;
736
0
}
737
738
static void
739
SendLayersDependentApzcTargetConfirmation(nsIPresShell* aShell, uint64_t aInputBlockId,
740
                                          const nsTArray<ScrollableLayerGuid>& aTargets)
741
0
{
742
0
  LayerManager* lm = aShell->GetLayerManager();
743
0
  if (!lm) {
744
0
    return;
745
0
  }
746
0
747
0
  if (WebRenderLayerManager* wrlm = lm->AsWebRenderLayerManager()) {
748
0
    if (WebRenderBridgeChild* wrbc = wrlm->WrBridge()) {
749
0
      wrbc->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
750
0
    }
751
0
    return;
752
0
  }
753
0
754
0
  LayerTransactionChild* shadow = lm->AsShadowForwarder()->GetShadowManager();
755
0
  if (!shadow) {
756
0
    return;
757
0
  }
758
0
759
0
  shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
760
0
}
761
762
DisplayportSetListener::DisplayportSetListener(nsIWidget* aWidget,
763
                                               nsIPresShell* aPresShell,
764
                                               const uint64_t& aInputBlockId,
765
                                               const nsTArray<ScrollableLayerGuid>& aTargets)
766
  : mWidget(aWidget)
767
  , mPresShell(aPresShell)
768
  , mInputBlockId(aInputBlockId)
769
  , mTargets(aTargets)
770
0
{
771
0
}
772
773
DisplayportSetListener:: ~DisplayportSetListener()
774
0
{
775
0
}
776
777
bool
778
DisplayportSetListener::Register()
779
0
{
780
0
  if (mPresShell->AddPostRefreshObserver(this)) {
781
0
    APZCCH_LOG("Successfully registered post-refresh observer\n");
782
0
    return true;
783
0
  }
784
0
  // In case of failure just send the notification right away
785
0
  APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", mInputBlockId);
786
0
  mWidget->SetConfirmedTargetAPZC(mInputBlockId, mTargets);
787
0
  return false;
788
0
}
789
790
void
791
0
DisplayportSetListener::DidRefresh() {
792
0
  if (!mPresShell) {
793
0
    MOZ_ASSERT_UNREACHABLE("Post-refresh observer fired again after failed attempt at unregistering it");
794
0
    return;
795
0
  }
796
0
797
0
  APZCCH_LOG("Got refresh, sending target APZCs for input block %" PRIu64 "\n", mInputBlockId);
798
0
  SendLayersDependentApzcTargetConfirmation(mPresShell, mInputBlockId, std::move(mTargets));
799
0
800
0
  if (!mPresShell->RemovePostRefreshObserver(this)) {
801
0
    MOZ_ASSERT_UNREACHABLE("Unable to unregister post-refresh observer! Leaking it instead of leaving garbage registered");
802
0
    // Graceful handling, just in case...
803
0
    mPresShell = nullptr;
804
0
    return;
805
0
  }
806
0
807
0
  delete this;
808
0
}
809
810
UniquePtr<DisplayportSetListener>
811
APZCCallbackHelper::SendSetTargetAPZCNotification(nsIWidget* aWidget,
812
                                                  nsIDocument* aDocument,
813
                                                  const WidgetGUIEvent& aEvent,
814
                                                  const ScrollableLayerGuid& aGuid,
815
                                                  uint64_t aInputBlockId)
816
0
{
817
0
  if (!aWidget || !aDocument) {
818
0
    return nullptr;
819
0
  }
820
0
  if (aInputBlockId == sLastTargetAPZCNotificationInputBlock) {
821
0
    // We have already confirmed the target APZC for a previous event of this
822
0
    // input block. If we activated a scroll frame for this input block,
823
0
    // sending another target APZC confirmation would be harmful, as it might
824
0
    // race the original confirmation (which needs to go through a layers
825
0
    // transaction).
826
0
    APZCCH_LOG("Not resending target APZC confirmation for input block %" PRIu64 "\n", aInputBlockId);
827
0
    return nullptr;
828
0
  }
829
0
  sLastTargetAPZCNotificationInputBlock = aInputBlockId;
830
0
  if (nsIPresShell* shell = aDocument->GetShell()) {
831
0
    if (nsIFrame* rootFrame = shell->GetRootFrame()) {
832
0
      rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
833
0
834
0
      bool waitForRefresh = false;
835
0
      nsTArray<ScrollableLayerGuid> targets;
836
0
837
0
      if (const WidgetTouchEvent* touchEvent = aEvent.AsTouchEvent()) {
838
0
        for (size_t i = 0; i < touchEvent->mTouches.Length(); i++) {
839
0
          waitForRefresh |= PrepareForSetTargetAPZCNotification(aWidget, aGuid,
840
0
              rootFrame, touchEvent->mTouches[i]->mRefPoint, &targets);
841
0
        }
842
0
      } else if (const WidgetWheelEvent* wheelEvent = aEvent.AsWheelEvent()) {
843
0
        waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
844
0
            rootFrame, wheelEvent->mRefPoint, &targets);
845
0
      } else if (const WidgetMouseEvent* mouseEvent = aEvent.AsMouseEvent()) {
846
0
        waitForRefresh = PrepareForSetTargetAPZCNotification(aWidget, aGuid,
847
0
            rootFrame, mouseEvent->mRefPoint, &targets);
848
0
      }
849
0
      // TODO: Do other types of events need to be handled?
850
0
851
0
      if (!targets.IsEmpty()) {
852
0
        if (waitForRefresh) {
853
0
          APZCCH_LOG("At least one target got a new displayport, need to wait for refresh\n");
854
0
          return MakeUnique<DisplayportSetListener>(aWidget, shell, aInputBlockId, std::move(targets));
855
0
        }
856
0
        APZCCH_LOG("Sending target APZCs for input block %" PRIu64 "\n", aInputBlockId);
857
0
        aWidget->SetConfirmedTargetAPZC(aInputBlockId, targets);
858
0
      }
859
0
    }
860
0
  }
861
0
  return nullptr;
862
0
}
863
864
void
865
APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(
866
        nsIWidget* aWidget,
867
        nsIDocument* aDocument,
868
        const WidgetTouchEvent& aEvent,
869
        uint64_t aInputBlockId,
870
        const SetAllowedTouchBehaviorCallback& aCallback)
871
0
{
872
0
  if (nsIPresShell* shell = aDocument->GetShell()) {
873
0
    if (nsIFrame* rootFrame = shell->GetRootFrame()) {
874
0
      rootFrame = UpdateRootFrameForTouchTargetDocument(rootFrame);
875
0
876
0
      nsTArray<TouchBehaviorFlags> flags;
877
0
      for (uint32_t i = 0; i < aEvent.mTouches.Length(); i++) {
878
0
        flags.AppendElement(
879
0
          TouchActionHelper::GetAllowedTouchBehavior(aWidget,
880
0
                rootFrame, aEvent.mTouches[i]->mRefPoint));
881
0
      }
882
0
      aCallback(aInputBlockId, std::move(flags));
883
0
    }
884
0
  }
885
0
}
886
887
void
888
APZCCallbackHelper::NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId, const nsString& aEvent)
889
0
{
890
0
  nsCOMPtr<nsIContent> targetContent = nsLayoutUtils::FindContentFor(aScrollId);
891
0
  if (!targetContent) {
892
0
    return;
893
0
  }
894
0
  nsCOMPtr<nsIDocument> ownerDoc = targetContent->OwnerDoc();
895
0
  if (!ownerDoc) {
896
0
    return;
897
0
  }
898
0
899
0
  nsContentUtils::DispatchTrustedEvent(
900
0
    ownerDoc, targetContent,
901
0
    aEvent,
902
0
    CanBubble::eYes,
903
0
    Cancelable::eYes);
904
0
}
905
906
void
907
APZCCallbackHelper::NotifyFlushComplete(nsIPresShell* aShell)
908
0
{
909
0
  MOZ_ASSERT(NS_IsMainThread());
910
0
  // In some cases, flushing the APZ state to the main thread doesn't actually
911
0
  // trigger a flush and repaint (this is an intentional optimization - the stuff
912
0
  // visible to the user is still correct). However, reftests update their
913
0
  // snapshot based on invalidation events that are emitted during paints,
914
0
  // so we ensure that we kick off a paint when an APZ flush is done. Note that
915
0
  // only chrome/testing code can trigger this behaviour.
916
0
  if (aShell && aShell->GetRootFrame()) {
917
0
    aShell->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
918
0
  }
919
0
920
0
  nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
921
0
  MOZ_ASSERT(observerService);
922
0
  observerService->NotifyObservers(nullptr, "apz-repaints-flushed", nullptr);
923
0
}
924
925
/* static */ bool
926
APZCCallbackHelper::IsScrollInProgress(nsIScrollableFrame* aFrame)
927
0
{
928
0
  return aFrame->IsProcessingAsyncScroll()
929
0
         || nsLayoutUtils::CanScrollOriginClobberApz(aFrame->LastScrollOrigin())
930
0
         || aFrame->LastSmoothScrollOrigin();
931
0
}
932
933
/* static */ void
934
APZCCallbackHelper::NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId)
935
0
{
936
0
  MOZ_ASSERT(NS_IsMainThread());
937
0
  if (nsIScrollableFrame* scrollFrame = nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
938
0
    scrollFrame->AsyncScrollbarDragRejected();
939
0
  }
940
0
}
941
942
/* static */ void
943
APZCCallbackHelper::NotifyAsyncAutoscrollRejected(const FrameMetrics::ViewID& aScrollId)
944
0
{
945
0
  MOZ_ASSERT(NS_IsMainThread());
946
0
  nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
947
0
  MOZ_ASSERT(observerService);
948
0
949
0
  nsAutoString data;
950
0
  data.AppendInt(aScrollId);
951
0
  observerService->NotifyObservers(nullptr, "autoscroll-rejected-by-apz", data.get());
952
0
}
953
954
/* static */ void
955
APZCCallbackHelper::CancelAutoscroll(const FrameMetrics::ViewID& aScrollId)
956
0
{
957
0
  MOZ_ASSERT(NS_IsMainThread());
958
0
  nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
959
0
  MOZ_ASSERT(observerService);
960
0
961
0
  nsAutoString data;
962
0
  data.AppendInt(aScrollId);
963
0
  observerService->NotifyObservers(nullptr, "apz:cancel-autoscroll", data.get());
964
0
}
965
966
/* static */ void
967
APZCCallbackHelper::NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
968
                                       LayoutDeviceCoord aSpanChange,
969
                                       Modifiers aModifiers,
970
                                       nsIWidget* aWidget)
971
0
{
972
0
  EventMessage msg;
973
0
  switch (aType) {
974
0
    case PinchGestureInput::PINCHGESTURE_START:
975
0
      msg = eMagnifyGestureStart;
976
0
      break;
977
0
    case PinchGestureInput::PINCHGESTURE_SCALE:
978
0
      msg = eMagnifyGestureUpdate;
979
0
      break;
980
0
    case PinchGestureInput::PINCHGESTURE_END:
981
0
      msg = eMagnifyGesture;
982
0
      break;
983
0
  }
984
0
985
0
  WidgetSimpleGestureEvent event(true, msg, aWidget);
986
0
  event.mDelta = aSpanChange;
987
0
  event.mModifiers = aModifiers;
988
0
  DispatchWidgetEvent(event);
989
0
}
990
991
} // namespace layers
992
} // namespace mozilla
993