summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/util/TouchActionHelper.cpp
blob: 4598e30a6a1d62d98e271d1e50059683e5ffe482 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TouchActionHelper.h"

#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/PresShell.h"
#include "mozilla/TouchEvents.h"
#include "nsContainerFrame.h"
#include "nsIFrameInlines.h"
#include "nsIScrollableFrame.h"
#include "nsLayoutUtils.h"

namespace mozilla::layers {

static void UpdateAllowedBehavior(StyleTouchAction aTouchActionValue,
                                  bool aConsiderPanning,
                                  TouchBehaviorFlags& aOutBehavior) {
  if (aTouchActionValue != StyleTouchAction::AUTO) {
    // Double-tap-zooming need property value AUTO
    aOutBehavior &= ~AllowedTouchBehavior::ANIMATING_ZOOM;
    if (aTouchActionValue != StyleTouchAction::MANIPULATION &&
        !(aTouchActionValue & StyleTouchAction::PINCH_ZOOM)) {
      // Pinch-zooming needs value AUTO or MANIPULATION, or the PINCH_ZOOM bit
      // set
      aOutBehavior &= ~AllowedTouchBehavior::PINCH_ZOOM;
    }
  }

  if (aConsiderPanning) {
    if (aTouchActionValue == StyleTouchAction::NONE) {
      aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN;
      aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
    }

    // Values pan-x and pan-y set at the same time to the same element do not
    // affect panning constraints. Therefore we need to check whether pan-x is
    // set without pan-y and the same for pan-y.
    if ((aTouchActionValue & StyleTouchAction::PAN_X) &&
        !(aTouchActionValue & StyleTouchAction::PAN_Y)) {
      aOutBehavior &= ~AllowedTouchBehavior::VERTICAL_PAN;
    } else if ((aTouchActionValue & StyleTouchAction::PAN_Y) &&
               !(aTouchActionValue & StyleTouchAction::PAN_X)) {
      aOutBehavior &= ~AllowedTouchBehavior::HORIZONTAL_PAN;
    }
  }
}

static TouchBehaviorFlags GetAllowedTouchBehaviorForPoint(
    nsIWidget* aWidget, RelativeTo aRootFrame,
    const LayoutDeviceIntPoint& aPoint) {
  nsPoint relativePoint =
      nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aPoint, aRootFrame);

  nsIFrame* target = nsLayoutUtils::GetFrameForPoint(aRootFrame, relativePoint);

  return TouchActionHelper::GetAllowedTouchBehaviorForFrame(target);
}

nsTArray<TouchBehaviorFlags> TouchActionHelper::GetAllowedTouchBehavior(
    nsIWidget* aWidget, dom::Document* aDocument,
    const WidgetTouchEvent& aEvent) {
  nsTArray<TouchBehaviorFlags> flags;
  if (!aWidget || !aDocument) {
    return flags;
  }
  if (PresShell* presShell = aDocument->GetPresShell()) {
    if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
      for (const auto& touch : aEvent.mTouches) {
        flags.AppendElement(GetAllowedTouchBehaviorForPoint(
            aWidget, RelativeTo{rootFrame, ViewportType::Visual},
            touch->mRefPoint));
      }
    }
  }
  return flags;
}

TouchBehaviorFlags TouchActionHelper::GetAllowedTouchBehaviorForFrame(
    nsIFrame* aFrame) {
  TouchBehaviorFlags behavior = AllowedTouchBehavior::VERTICAL_PAN |
                                AllowedTouchBehavior::HORIZONTAL_PAN |
                                AllowedTouchBehavior::PINCH_ZOOM |
                                AllowedTouchBehavior::ANIMATING_ZOOM;

  if (!aFrame) {
    return behavior;
  }

  nsIScrollableFrame* nearestScrollableParent =
      nsLayoutUtils::GetNearestScrollableFrame(aFrame, 0);
  nsIFrame* nearestScrollableFrame = do_QueryFrame(nearestScrollableParent);

  // We're walking up the DOM tree until we meet the element with touch behavior
  // and accumulating touch-action restrictions of all elements in this chain.
  // The exact quote from the spec, that clarifies more:
  // To determine the effect of a touch, find the nearest ancestor (starting
  // from the element itself) that has a default touch behavior. Then examine
  // the touch-action property of each element between the hit tested element
  // and the element with the default touch behavior (including both the hit
  // tested element and the element with the default touch behavior). If the
  // touch-action property of any of those elements disallows the default touch
  // behavior, do nothing. Otherwise allow the element to start considering the
  // touch for the purposes of executing a default touch behavior.

  // Currently we support only two touch behaviors: panning and zooming.
  // For panning we walk up until we meet the first scrollable element (the
  // element that supports panning) or root element. For zooming we walk up
  // until the root element since Firefox currently supports only zooming of the
  // root frame but not the subframes.

  bool considerPanning = true;

  for (nsIFrame* frame = aFrame; frame && frame->GetContent() && behavior;
       frame = frame->GetInFlowParent()) {
    UpdateAllowedBehavior(frame->UsedTouchAction(), considerPanning, behavior);

    if (frame == nearestScrollableFrame) {
      // We met the scrollable element, after it we shouldn't consider
      // touch-action values for the purpose of panning but only for zooming.
      considerPanning = false;
    }
  }

  return behavior;
}

}  // namespace mozilla::layers