Source code

Revision control

Other Tools

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 "mozilla/layers/FocusTarget.h"
8
9
#include "mozilla/dom/BrowserBridgeChild.h" // for BrowserBridgeChild
10
#include "mozilla/dom/EventTarget.h" // for EventTarget
11
#include "mozilla/dom/RemoteBrowser.h" // For RemoteBrowser
12
#include "mozilla/EventDispatcher.h" // for EventDispatcher
13
#include "mozilla/PresShell.h" // For PresShell
14
#include "mozilla/StaticPrefs_apz.h"
15
#include "nsIContentInlines.h" // for nsINode::IsEditable()
16
#include "nsLayoutUtils.h" // for nsLayoutUtils
17
18
static mozilla::LazyLogModule sApzFtgLog("apz.focustarget");
19
#define FT_LOG(...) MOZ_LOG(sApzFtgLog, LogLevel::Debug, (__VA_ARGS__))
20
21
using namespace mozilla::dom;
22
using namespace mozilla::layout;
23
24
namespace mozilla {
25
namespace layers {
26
27
static PresShell* GetRetargetEventPresShell(PresShell* aRootPresShell) {
28
MOZ_ASSERT(aRootPresShell);
29
30
// Use the last focused window in this PresShell and its
31
// associated PresShell
32
nsCOMPtr<nsPIDOMWindowOuter> window =
33
aRootPresShell->GetFocusedDOMWindowInOurWindow();
34
if (!window) {
35
return nullptr;
36
}
37
38
RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
39
if (!retargetEventDoc) {
40
return nullptr;
41
}
42
43
return retargetEventDoc->GetPresShell();
44
}
45
46
static bool HasListenersForKeyEvents(nsIContent* aContent) {
47
if (!aContent) {
48
return false;
49
}
50
51
WidgetEvent event(true, eVoidEvent);
52
nsTArray<EventTarget*> targets;
53
nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
54
nullptr, nullptr, &targets);
55
NS_ENSURE_SUCCESS(rv, false);
56
for (size_t i = 0; i < targets.Length(); i++) {
57
if (targets[i]->HasNonSystemGroupListenersForUntrustedKeyEvents()) {
58
return true;
59
}
60
}
61
return false;
62
}
63
64
static bool HasListenersForNonPassiveKeyEvents(nsIContent* aContent) {
65
if (!aContent) {
66
return false;
67
}
68
69
WidgetEvent event(true, eVoidEvent);
70
nsTArray<EventTarget*> targets;
71
nsresult rv = EventDispatcher::Dispatch(aContent, nullptr, &event, nullptr,
72
nullptr, nullptr, &targets);
73
NS_ENSURE_SUCCESS(rv, false);
74
for (size_t i = 0; i < targets.Length(); i++) {
75
if (targets[i]
76
->HasNonPassiveNonSystemGroupListenersForUntrustedKeyEvents()) {
77
return true;
78
}
79
}
80
return false;
81
}
82
83
static bool IsEditableNode(nsINode* aNode) {
84
return aNode && aNode->IsEditable();
85
}
86
87
FocusTarget::FocusTarget()
88
: mSequenceNumber(0),
89
mFocusHasKeyEventListeners(false),
90
mData(AsVariant(NoFocusTarget())) {}
91
92
FocusTarget::FocusTarget(PresShell* aRootPresShell,
93
uint64_t aFocusSequenceNumber)
94
: mSequenceNumber(aFocusSequenceNumber),
95
mFocusHasKeyEventListeners(false),
96
mData(AsVariant(NoFocusTarget())) {
97
MOZ_ASSERT(aRootPresShell);
98
MOZ_ASSERT(NS_IsMainThread());
99
100
// Key events can be retargeted to a child PresShell when there is an iframe
101
RefPtr<PresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
102
103
if (!presShell) {
104
FT_LOG("Creating nil target with seq=%" PRIu64
105
" (can't find retargeted presshell)\n",
106
aFocusSequenceNumber);
107
108
return;
109
}
110
111
RefPtr<Document> document = presShell->GetDocument();
112
if (!document) {
113
FT_LOG("Creating nil target with seq=%" PRIu64 " (no document)\n",
114
aFocusSequenceNumber);
115
116
return;
117
}
118
119
// Find the focused content and use it to determine whether there are key
120
// event listeners or whether key events will be targeted at a different
121
// process through a remote browser.
122
nsCOMPtr<nsIContent> focusedContent =
123
presShell->GetFocusedContentInOurWindow();
124
nsCOMPtr<nsIContent> keyEventTarget = focusedContent;
125
126
// If there is no focused element then event dispatch goes to the body of
127
// the page if it exists or the root element.
128
if (!keyEventTarget) {
129
keyEventTarget = document->GetUnfocusedKeyEventTarget();
130
}
131
132
// Check if there are key event listeners that could prevent default or change
133
// the focus or selection of the page.
134
if (StaticPrefs::apz_keyboard_passive_listeners()) {
135
mFocusHasKeyEventListeners =
136
HasListenersForNonPassiveKeyEvents(keyEventTarget.get());
137
} else {
138
mFocusHasKeyEventListeners = HasListenersForKeyEvents(keyEventTarget.get());
139
}
140
141
// Check if the key event target is content editable or if the document
142
// is in design mode.
143
if (IsEditableNode(keyEventTarget) || IsEditableNode(document)) {
144
FT_LOG("Creating nil target with seq=%" PRIu64
145
", kl=%d (disabling for editable node)\n",
146
aFocusSequenceNumber, static_cast<int>(mFocusHasKeyEventListeners));
147
148
return;
149
}
150
151
// Check if the key event target is a remote browser
152
if (RemoteBrowser* remoteBrowser = RemoteBrowser::GetFrom(keyEventTarget)) {
153
LayersId layersId = remoteBrowser->GetLayersId();
154
155
// The globally focused element for scrolling is in a remote layer tree
156
if (layersId.IsValid()) {
157
FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64
158
"\n",
159
aFocusSequenceNumber, mFocusHasKeyEventListeners, layersId.mId);
160
161
mData = AsVariant<LayersId>(std::move(layersId));
162
return;
163
}
164
165
FT_LOG("Creating nil target with seq=%" PRIu64
166
", kl=%d (remote browser missing layers id)\n",
167
aFocusSequenceNumber, mFocusHasKeyEventListeners);
168
169
return;
170
}
171
172
// The content to scroll is either the focused element or the focus node of
173
// the selection. It's difficult to determine if an element is an interactive
174
// element requiring async keyboard scrolling to be disabled. So we only
175
// allow async key scrolling based on the selection, which doesn't have
176
// this problem and is more common.
177
if (focusedContent) {
178
FT_LOG("Creating nil target with seq=%" PRIu64
179
", kl=%d (disabling for focusing an element)\n",
180
aFocusSequenceNumber, mFocusHasKeyEventListeners);
181
182
return;
183
}
184
185
nsCOMPtr<nsIContent> selectedContent =
186
presShell->GetSelectedContentForScrolling();
187
188
// Gather the scrollable frames that would be scrolled in each direction
189
// for this scroll target
190
nsIScrollableFrame* horizontal =
191
presShell->GetScrollableFrameToScrollForContent(
192
selectedContent.get(), ScrollableDirection::Horizontal);
193
nsIScrollableFrame* vertical =
194
presShell->GetScrollableFrameToScrollForContent(
195
selectedContent.get(), ScrollableDirection::Vertical);
196
197
// We might have the globally focused element for scrolling. Gather a ViewID
198
// for the horizontal and vertical scroll targets of this element.
199
ScrollTargets target;
200
target.mHorizontal = nsLayoutUtils::FindIDForScrollableFrame(horizontal);
201
target.mVertical = nsLayoutUtils::FindIDForScrollableFrame(vertical);
202
mData = AsVariant(target);
203
204
FT_LOG("Creating scroll target with seq=%" PRIu64 ", kl=%d, h=%" PRIu64
205
", v=%" PRIu64 "\n",
206
aFocusSequenceNumber, mFocusHasKeyEventListeners, target.mHorizontal,
207
target.mVertical);
208
}
209
210
bool FocusTarget::operator==(const FocusTarget& aRhs) const {
211
return mSequenceNumber == aRhs.mSequenceNumber &&
212
mFocusHasKeyEventListeners == aRhs.mFocusHasKeyEventListeners &&
213
mData == aRhs.mData;
214
}
215
216
const char* FocusTarget::Type() const {
217
if (mData.is<LayersId>()) {
218
return "LayersId";
219
}
220
if (mData.is<ScrollTargets>()) {
221
return "ScrollTargets";
222
}
223
if (mData.is<NoFocusTarget>()) {
224
return "NoFocusTarget";
225
}
226
return "<unknown>";
227
}
228
229
} // namespace layers
230
} // namespace mozilla