summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/src/FocusState.h
blob: 14d536be3e558bdeb5b4aea311443e81a0a3f4ae (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/* -*- 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/. */

#ifndef mozilla_layers_FocusState_h
#define mozilla_layers_FocusState_h

#include <unordered_map>  // for std::unordered_map
#include <unordered_set>  // for std::unordered_set

#include "mozilla/layers/FocusTarget.h"          // for FocusTarget
#include "mozilla/layers/ScrollableLayerGuid.h"  // for ViewID
#include "mozilla/Mutex.h"                       // for Mutex

namespace mozilla {
namespace layers {

/**
 * This class is used for tracking chrome and content focus targets and
 * calculating global focus information from them for use by APZCTreeManager
 * for async keyboard scrolling.
 *
 * # Calculating the element to scroll
 *
 * Chrome and content processes have independently focused elements. This makes
 * it difficult to calculate the global focused element and its scrollable
 * frame from the chrome or content side. So instead we send the local focus
 * information from each process to here and then calculate the global focus
 * information. This local information resides in a `focus target`.
 *
 * A focus target indicates that either:
 *    1. The focused element is a remote browser along with its layer tree ID
 *    2. The focused element is not scrollable
 *    3. The focused element is scrollable along with the ViewID's of its
         scrollable layers
 *
 * Using this information we can determine the global focus information by
 * starting at the focus target of the root layer tree ID and following remote
 * browsers until we reach a scrollable or non-scrollable focus target.
 *
 * # Determinism and sequence numbers
 *
 * The focused element in content can be changed within any javascript code. And
 * javascript can run in response to an event or at any moment from `setTimeout`
 * and others. This makes it impossible to always have the current focus
 * information in APZ as it can be changed asynchronously at any moment. If we
 * don't have the latest focus information, we may incorrectly scroll a target
 * when we shouldn't.
 *
 * A tradeoff is designed here whereby we will maintain deterministic focus
 * changes for user input, but not for other javascript code. The reasoning
 * here is that `setTimeout` and others are already non-deterministic and so it
 * might not be as breaking to web content.
 *
 * To maintain deterministic focus changes for a given stream of user inputs,
 * we invalidate our focus state whenever we receive a user input that may
 * trigger event listeners. We then attach a new sequence number to these
 * events and dispatch them to content. Content will then include the latest
 * sequence number it has processed to every focus update. Using this we can
 * determine whether any potentially focus changing events have yet to be
 * handled by content.
 *
 * Once we have received the latest focus sequence number from content, we know
 * that all event listeners triggered by user inputs, and their resulting focus
 * changes, have been processed and so we have a current target that we can use
 * again.
 */
class FocusState final {
 public:
  FocusState();

  /**
   * The sequence number of the last potentially focus changing event processed
   * by APZ. This number starts at one and increases monotonically. This number
   * will never be zero as that is used to catch uninitialized focus sequence
   * numbers on input events.
   */
  uint64_t LastAPZProcessedEvent() const;

  /**
   * Notify focus state of a potentially focus changing event. This will
   * increment the current focus sequence number. The new value can be gotten
   * from LastAPZProcessedEvent().
   */
  void ReceiveFocusChangingEvent();

  /**
   * Update the internal focus tree and recalculate the global focus target for
   * a focus target update received from chrome or content.
   *
   * @param aRootLayerTreeId the layer tree ID of the root layer for the
                             parent APZCTreeManager
   * @param aOriginatingLayersId the layer tree ID that this focus target
                                 belongs to
   */
  void Update(LayersId aRootLayerTreeId, LayersId aOriginatingLayersId,
              const FocusTarget& aTarget);

  /**
   * Removes a focus target by its layer tree ID.
   */
  void RemoveFocusTarget(LayersId aLayersId);

  /**
   * Gets the scrollable layer that should be horizontally scrolled for a key
   * event, if any. The returned ScrollableLayerGuid doesn't contain a
   * presShellId, and so it should not be used in comparisons.
   *
   * No scrollable layer is returned if any of the following are true:
   *   1. We don't have a current focus target
   *   2. There are event listeners that could change the focus
   *   3. The target has not been layerized
   */
  Maybe<ScrollableLayerGuid> GetHorizontalTarget() const;
  /**
   * The same as GetHorizontalTarget() but for vertical scrolling.
   */
  Maybe<ScrollableLayerGuid> GetVerticalTarget() const;

  /**
   * Gets whether it is safe to not increment the focus sequence number for an
   * unmatched keyboard event.
   */
  bool CanIgnoreKeyboardShortcutMisses() const;

 private:
  /**
   * Whether the current focus state is known to be current or else if an event
   * has been processed that could change the focus but we have not received an
   * update with a new confirmed target.
   * This can only be called by methods that have already acquired mMutex; they
   * have to pass their lock as compile-time proof.
   */
  bool IsCurrent(const MutexAutoLock& aLock) const;

 private:
  // All methods should hold this lock, since this class is accessed via both
  // the updater and controller threads.
  mutable Mutex mMutex MOZ_UNANNOTATED;

  // The set of focus targets received indexed by their layer tree ID
  std::unordered_map<LayersId, FocusTarget, LayersId::HashFn> mFocusTree;

  // The focus sequence number of the last potentially focus changing event
  // processed by APZ. This number starts at one and increases monotonically.
  // We don't worry about wrap around here because at a pace of 100
  // increments/sec, it would take 5.85*10^9 years before we would wrap around.
  // This number will never be zero as that is used to catch uninitialized focus
  // sequence numbers on input events.
  uint64_t mLastAPZProcessedEvent;
  // The focus sequence number last received in a focus update.
  uint64_t mLastContentProcessedEvent;

  // A flag whether there is a key listener on the event target chain for the
  // focused element
  bool mFocusHasKeyEventListeners;
  // A flag that is false until the first call to Update().
  bool mReceivedUpdate;

  // The layer tree ID which contains the scrollable frame of the focused
  // element
  LayersId mFocusLayersId;
  // The scrollable layer corresponding to the scrollable frame that is used to
  // scroll the focused element. This depends on the direction the user is
  // scrolling.
  ScrollableLayerGuid::ViewID mFocusHorizontalTarget;
  ScrollableLayerGuid::ViewID mFocusVerticalTarget;
};

}  // namespace layers
}  // namespace mozilla

#endif  // mozilla_layers_FocusState_h