Line data Source code
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 :
6 : #include "ActiveElementManager.h"
7 : #include "mozilla/EventStateManager.h"
8 : #include "mozilla/EventStates.h"
9 : #include "mozilla/StyleSetHandle.h"
10 : #include "mozilla/StyleSetHandleInlines.h"
11 : #include "mozilla/Preferences.h"
12 : #include "base/message_loop.h"
13 : #include "base/task.h"
14 : #include "mozilla/dom/Element.h"
15 : #include "nsIDocument.h"
16 : #include "nsStyleSet.h"
17 :
18 : #define AEM_LOG(...)
19 : // #define AEM_LOG(...) printf_stderr("AEM: " __VA_ARGS__)
20 :
21 : namespace mozilla {
22 : namespace layers {
23 :
24 : static int32_t sActivationDelayMs = 100;
25 : static bool sActivationDelayMsSet = false;
26 :
27 2 : ActiveElementManager::ActiveElementManager()
28 : : mCanBePan(false),
29 : mCanBePanSet(false),
30 : mSetActiveTask(nullptr),
31 2 : mActiveElementUsesStyle(false)
32 : {
33 2 : if (!sActivationDelayMsSet) {
34 : Preferences::AddIntVarCache(&sActivationDelayMs,
35 : "ui.touch_activation.delay_ms",
36 2 : sActivationDelayMs);
37 2 : sActivationDelayMsSet = true;
38 : }
39 2 : }
40 :
41 0 : ActiveElementManager::~ActiveElementManager() {}
42 :
43 : void
44 0 : ActiveElementManager::SetTargetElement(dom::EventTarget* aTarget)
45 : {
46 0 : if (mTarget) {
47 : // Multiple fingers on screen (since HandleTouchEnd clears mTarget).
48 : AEM_LOG("Multiple fingers on-screen, clearing target element\n");
49 0 : CancelTask();
50 0 : ResetActive();
51 0 : ResetTouchBlockState();
52 0 : return;
53 : }
54 :
55 0 : mTarget = do_QueryInterface(aTarget);
56 : AEM_LOG("Setting target element to %p\n", mTarget.get());
57 0 : TriggerElementActivation();
58 : }
59 :
60 : void
61 0 : ActiveElementManager::HandleTouchStart(bool aCanBePan)
62 : {
63 : AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan);
64 0 : if (mCanBePanSet) {
65 : // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet).
66 : AEM_LOG("Multiple fingers on-screen, clearing touch block state\n");
67 0 : CancelTask();
68 0 : ResetActive();
69 0 : ResetTouchBlockState();
70 0 : return;
71 : }
72 :
73 0 : mCanBePan = aCanBePan;
74 0 : mCanBePanSet = true;
75 0 : TriggerElementActivation();
76 : }
77 :
78 : void
79 0 : ActiveElementManager::TriggerElementActivation()
80 : {
81 : // Both HandleTouchStart() and SetTargetElement() call this. They can be
82 : // called in either order. One will set mCanBePanSet, and the other, mTarget.
83 : // We want to actually trigger the activation once both are set.
84 0 : if (!(mTarget && mCanBePanSet)) {
85 0 : return;
86 : }
87 :
88 : // If the touch cannot be a pan, make mTarget :active right away.
89 : // Otherwise, wait a bit to see if the user will pan or not.
90 0 : if (!mCanBePan) {
91 0 : SetActive(mTarget);
92 : } else {
93 0 : CancelTask(); // this is only needed because of bug 1169802. Fixing that
94 : // bug properly should make this unnecessary.
95 0 : MOZ_ASSERT(mSetActiveTask == nullptr);
96 :
97 : RefPtr<CancelableRunnable> task =
98 0 : NewCancelableRunnableMethod<nsCOMPtr<dom::Element>>(
99 : "layers::ActiveElementManager::SetActiveTask",
100 : this,
101 : &ActiveElementManager::SetActiveTask,
102 0 : mTarget);
103 0 : mSetActiveTask = task;
104 0 : MessageLoop::current()->PostDelayedTask(task.forget(), sActivationDelayMs);
105 : AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask);
106 : }
107 : }
108 :
109 : void
110 0 : ActiveElementManager::ClearActivation()
111 : {
112 : AEM_LOG("Clearing element activation\n");
113 0 : CancelTask();
114 0 : ResetActive();
115 0 : }
116 :
117 : void
118 0 : ActiveElementManager::HandleTouchEndEvent(bool aWasClick)
119 : {
120 : AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick);
121 :
122 : // If the touch was a click, make mTarget :active right away.
123 : // nsEventStateManager will reset the active element when processing
124 : // the mouse-down event generated by the click.
125 0 : CancelTask();
126 0 : if (aWasClick) {
127 0 : SetActive(mTarget);
128 : } else {
129 : // We might reach here if mCanBePan was false on touch-start and
130 : // so we set the element active right away. Now it turns out the
131 : // action was not a click so we need to reset the active element.
132 0 : ResetActive();
133 : }
134 :
135 0 : ResetTouchBlockState();
136 0 : }
137 :
138 : void
139 0 : ActiveElementManager::HandleTouchEnd()
140 : {
141 : AEM_LOG("Touch end, clearing pan state\n");
142 0 : mCanBePanSet = false;
143 0 : }
144 :
145 : bool
146 0 : ActiveElementManager::ActiveElementUsesStyle() const
147 : {
148 0 : return mActiveElementUsesStyle;
149 : }
150 :
151 : static nsPresContext*
152 0 : GetPresContextFor(nsIContent* aContent)
153 : {
154 0 : if (!aContent) {
155 0 : return nullptr;
156 : }
157 0 : nsIPresShell* shell = aContent->OwnerDoc()->GetShell();
158 0 : if (!shell) {
159 0 : return nullptr;
160 : }
161 0 : return shell->GetPresContext();
162 : }
163 :
164 : static bool
165 0 : ElementHasActiveStyle(dom::Element* aElement)
166 : {
167 0 : nsPresContext* pc = GetPresContextFor(aElement);
168 0 : if (!pc) {
169 0 : return false;
170 : }
171 0 : StyleSetHandle styleSet = pc->StyleSet();
172 0 : for (dom::Element* e = aElement; e; e = e->GetParentElement()) {
173 0 : if (styleSet->HasStateDependentStyle(e, NS_EVENT_STATE_ACTIVE)) {
174 : AEM_LOG("Element %p's style is dependent on the active state\n", e);
175 0 : return true;
176 : }
177 : }
178 : AEM_LOG("Element %p doesn't use active styles\n", aElement);
179 0 : return false;
180 : }
181 :
182 : void
183 0 : ActiveElementManager::SetActive(dom::Element* aTarget)
184 : {
185 : AEM_LOG("Setting active %p\n", aTarget);
186 :
187 0 : if (nsPresContext* pc = GetPresContextFor(aTarget)) {
188 0 : pc->EventStateManager()->SetContentState(aTarget, NS_EVENT_STATE_ACTIVE);
189 0 : mActiveElementUsesStyle = ElementHasActiveStyle(aTarget);
190 : }
191 0 : }
192 :
193 : void
194 0 : ActiveElementManager::ResetActive()
195 : {
196 : AEM_LOG("Resetting active from %p\n", mTarget.get());
197 :
198 : // Clear the :active flag from mTarget by setting it on the document root.
199 0 : if (mTarget) {
200 0 : dom::Element* root = mTarget->OwnerDoc()->GetDocumentElement();
201 0 : if (root) {
202 : AEM_LOG("Found root %p, making active\n", root);
203 0 : SetActive(root);
204 : }
205 : }
206 0 : }
207 :
208 : void
209 0 : ActiveElementManager::ResetTouchBlockState()
210 : {
211 0 : mTarget = nullptr;
212 0 : mCanBePanSet = false;
213 0 : }
214 :
215 : void
216 0 : ActiveElementManager::SetActiveTask(const nsCOMPtr<dom::Element>& aTarget)
217 : {
218 : AEM_LOG("mSetActiveTask %p running\n", mSetActiveTask);
219 :
220 : // This gets called from mSetActiveTask's Run() method. The message loop
221 : // deletes the task right after running it, so we need to null out
222 : // mSetActiveTask to make sure we're not left with a dangling pointer.
223 0 : mSetActiveTask = nullptr;
224 0 : SetActive(aTarget);
225 0 : }
226 :
227 : void
228 0 : ActiveElementManager::CancelTask()
229 : {
230 : AEM_LOG("Cancelling task %p\n", mSetActiveTask);
231 :
232 0 : if (mSetActiveTask) {
233 0 : mSetActiveTask->Cancel();
234 0 : mSetActiveTask = nullptr;
235 : }
236 0 : }
237 :
238 : } // namespace layers
239 : } // namespace mozilla
|