1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 "nsAccessiblePivot.h"
8
9 #include "HyperTextAccessible.h"
10 #include "nsAccUtils.h"
11 #include "States.h"
12 #include "Pivot.h"
13 #include "xpcAccessibleDocument.h"
14 #include "nsTArray.h"
15 #include "mozilla/Maybe.h"
16
17 using namespace mozilla::a11y;
18 using mozilla::DebugOnly;
19 using mozilla::Maybe;
20
21 /**
22 * An object that stores a given traversal rule during the pivot movement.
23 */
24 class RuleCache : public PivotRule {
25 public:
RuleCache(nsIAccessibleTraversalRule * aRule)26 explicit RuleCache(nsIAccessibleTraversalRule* aRule)
27 : mRule(aRule), mPreFilter{0} {}
~RuleCache()28 ~RuleCache() {}
29
30 virtual uint16_t Match(Accessible* aAccessible) override;
31
32 private:
33 nsCOMPtr<nsIAccessibleTraversalRule> mRule;
34 Maybe<nsTArray<uint32_t>> mAcceptRoles;
35 uint32_t mPreFilter;
36 };
37
38 ////////////////////////////////////////////////////////////////////////////////
39 // nsAccessiblePivot
40
nsAccessiblePivot(Accessible * aRoot)41 nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot)
42 : mRoot(aRoot),
43 mModalRoot(nullptr),
44 mPosition(nullptr),
45 mStartOffset(-1),
46 mEndOffset(-1) {
47 NS_ASSERTION(aRoot, "A root accessible is required");
48 }
49
~nsAccessiblePivot()50 nsAccessiblePivot::~nsAccessiblePivot() {}
51
52 ////////////////////////////////////////////////////////////////////////////////
53 // nsISupports
54
NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot,mRoot,mPosition,mObservers)55 NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers)
56
57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
58 NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
59 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
60 NS_INTERFACE_MAP_END
61
62 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
63 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
64
65 ////////////////////////////////////////////////////////////////////////////////
66 // nsIAccessiblePivot
67
68 NS_IMETHODIMP
69 nsAccessiblePivot::GetRoot(nsIAccessible** aRoot) {
70 NS_ENSURE_ARG_POINTER(aRoot);
71
72 NS_IF_ADDREF(*aRoot = ToXPC(mRoot));
73
74 return NS_OK;
75 }
76
77 NS_IMETHODIMP
GetPosition(nsIAccessible ** aPosition)78 nsAccessiblePivot::GetPosition(nsIAccessible** aPosition) {
79 NS_ENSURE_ARG_POINTER(aPosition);
80
81 NS_IF_ADDREF(*aPosition = ToXPC(mPosition));
82
83 return NS_OK;
84 }
85
86 NS_IMETHODIMP
SetPosition(nsIAccessible * aPosition)87 nsAccessiblePivot::SetPosition(nsIAccessible* aPosition) {
88 RefPtr<Accessible> position = nullptr;
89
90 if (aPosition) {
91 position = aPosition->ToInternalAccessible();
92 if (!position || !IsDescendantOf(position, GetActiveRoot()))
93 return NS_ERROR_INVALID_ARG;
94 }
95
96 // Swap old position with new position, saves us an AddRef/Release.
97 mPosition.swap(position);
98 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
99 mStartOffset = mEndOffset = -1;
100 NotifyOfPivotChange(position, oldStart, oldEnd,
101 nsIAccessiblePivot::REASON_NONE,
102 nsIAccessiblePivot::NO_BOUNDARY, false);
103
104 return NS_OK;
105 }
106
107 NS_IMETHODIMP
GetModalRoot(nsIAccessible ** aModalRoot)108 nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot) {
109 NS_ENSURE_ARG_POINTER(aModalRoot);
110
111 NS_IF_ADDREF(*aModalRoot = ToXPC(mModalRoot));
112
113 return NS_OK;
114 }
115
116 NS_IMETHODIMP
SetModalRoot(nsIAccessible * aModalRoot)117 nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot) {
118 Accessible* modalRoot = nullptr;
119
120 if (aModalRoot) {
121 modalRoot = aModalRoot->ToInternalAccessible();
122 if (!modalRoot || !IsDescendantOf(modalRoot, mRoot))
123 return NS_ERROR_INVALID_ARG;
124 }
125
126 mModalRoot = modalRoot;
127 return NS_OK;
128 }
129
130 NS_IMETHODIMP
GetStartOffset(int32_t * aStartOffset)131 nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset) {
132 NS_ENSURE_ARG_POINTER(aStartOffset);
133
134 *aStartOffset = mStartOffset;
135
136 return NS_OK;
137 }
138
139 NS_IMETHODIMP
GetEndOffset(int32_t * aEndOffset)140 nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset) {
141 NS_ENSURE_ARG_POINTER(aEndOffset);
142
143 *aEndOffset = mEndOffset;
144
145 return NS_OK;
146 }
147
148 NS_IMETHODIMP
SetTextRange(nsIAccessibleText * aTextAccessible,int32_t aStartOffset,int32_t aEndOffset,bool aIsFromUserInput,uint8_t aArgc)149 nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
150 int32_t aStartOffset, int32_t aEndOffset,
151 bool aIsFromUserInput, uint8_t aArgc) {
152 NS_ENSURE_ARG(aTextAccessible);
153
154 // Check that start offset is smaller than end offset, and that if a value is
155 // smaller than 0, both should be -1.
156 NS_ENSURE_TRUE(
157 aStartOffset <= aEndOffset &&
158 (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
159 NS_ERROR_INVALID_ARG);
160
161 nsCOMPtr<nsIAccessible> xpcAcc = do_QueryInterface(aTextAccessible);
162 NS_ENSURE_ARG(xpcAcc);
163
164 RefPtr<Accessible> acc = xpcAcc->ToInternalAccessible();
165 NS_ENSURE_ARG(acc);
166
167 HyperTextAccessible* position = acc->AsHyperText();
168 if (!position || !IsDescendantOf(position, GetActiveRoot()))
169 return NS_ERROR_INVALID_ARG;
170
171 // Make sure the given offsets don't exceed the character count.
172 if (aEndOffset > static_cast<int32_t>(position->CharacterCount()))
173 return NS_ERROR_FAILURE;
174
175 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
176 mStartOffset = aStartOffset;
177 mEndOffset = aEndOffset;
178
179 mPosition.swap(acc);
180 NotifyOfPivotChange(acc, oldStart, oldEnd, nsIAccessiblePivot::REASON_NONE,
181 nsIAccessiblePivot::NO_BOUNDARY,
182 (aArgc > 0) ? aIsFromUserInput : true);
183
184 return NS_OK;
185 }
186
187 // Traversal functions
188
189 NS_IMETHODIMP
MoveNext(nsIAccessibleTraversalRule * aRule,nsIAccessible * aAnchor,bool aIncludeStart,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)190 nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule,
191 nsIAccessible* aAnchor, bool aIncludeStart,
192 bool aIsFromUserInput, uint8_t aArgc,
193 bool* aResult) {
194 NS_ENSURE_ARG(aResult);
195 NS_ENSURE_ARG(aRule);
196 *aResult = false;
197
198 Accessible* anchor = mPosition;
199 if (aArgc > 0 && aAnchor) anchor = aAnchor->ToInternalAccessible();
200
201 if (anchor &&
202 (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
203 return NS_ERROR_NOT_IN_TREE;
204
205 Pivot pivot(GetActiveRoot());
206 RuleCache rule(aRule);
207
208 if (Accessible* newPos =
209 pivot.Next(anchor, rule, (aArgc > 1) ? aIncludeStart : false)) {
210 *aResult = MovePivotInternal(newPos, nsIAccessiblePivot::REASON_NEXT,
211 (aArgc > 2) ? aIsFromUserInput : true);
212 }
213
214 return NS_OK;
215 }
216
217 NS_IMETHODIMP
MovePrevious(nsIAccessibleTraversalRule * aRule,nsIAccessible * aAnchor,bool aIncludeStart,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)218 nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule,
219 nsIAccessible* aAnchor, bool aIncludeStart,
220 bool aIsFromUserInput, uint8_t aArgc,
221 bool* aResult) {
222 NS_ENSURE_ARG(aResult);
223 NS_ENSURE_ARG(aRule);
224 *aResult = false;
225
226 Accessible* anchor = mPosition;
227 if (aArgc > 0 && aAnchor) anchor = aAnchor->ToInternalAccessible();
228
229 if (anchor &&
230 (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
231 return NS_ERROR_NOT_IN_TREE;
232
233 Pivot pivot(GetActiveRoot());
234 RuleCache rule(aRule);
235
236 if (Accessible* newPos =
237 pivot.Prev(anchor, rule, (aArgc > 1) ? aIncludeStart : false)) {
238 *aResult = MovePivotInternal(newPos, nsIAccessiblePivot::REASON_PREV,
239 (aArgc > 2) ? aIsFromUserInput : true);
240 }
241
242 return NS_OK;
243 }
244
245 NS_IMETHODIMP
MoveFirst(nsIAccessibleTraversalRule * aRule,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)246 nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule,
247 bool aIsFromUserInput, uint8_t aArgc,
248 bool* aResult) {
249 NS_ENSURE_ARG(aResult);
250 NS_ENSURE_ARG(aRule);
251
252 Accessible* root = GetActiveRoot();
253 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
254
255 Pivot pivot(GetActiveRoot());
256 RuleCache rule(aRule);
257
258 if (Accessible* newPos = pivot.First(rule)) {
259 *aResult = MovePivotInternal(newPos, nsIAccessiblePivot::REASON_FIRST,
260 (aArgc > 0) ? aIsFromUserInput : true);
261 }
262
263 return NS_OK;
264 }
265
266 NS_IMETHODIMP
MoveLast(nsIAccessibleTraversalRule * aRule,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)267 nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule,
268 bool aIsFromUserInput, uint8_t aArgc,
269 bool* aResult) {
270 NS_ENSURE_ARG(aResult);
271 NS_ENSURE_ARG(aRule);
272
273 Accessible* root = GetActiveRoot();
274 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
275
276 Pivot pivot(root);
277 RuleCache rule(aRule);
278
279 if (Accessible* newPos = pivot.Last(rule)) {
280 *aResult = MovePivotInternal(newPos, nsIAccessiblePivot::REASON_LAST,
281 (aArgc > 0) ? aIsFromUserInput : true);
282 }
283
284 return NS_OK;
285 }
286
287 NS_IMETHODIMP
MoveNextByText(TextBoundaryType aBoundary,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)288 nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary,
289 bool aIsFromUserInput, uint8_t aArgc,
290 bool* aResult) {
291 NS_ENSURE_ARG(aResult);
292
293 *aResult = false;
294
295 Pivot pivot(GetActiveRoot());
296
297 int32_t newStart = mStartOffset, newEnd = mEndOffset;
298 if (Accessible* newPos =
299 pivot.NextText(mPosition, &newStart, &newEnd, aBoundary)) {
300 *aResult = true;
301 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
302 Accessible* oldPos = mPosition;
303 mStartOffset = newStart;
304 mEndOffset = newEnd;
305 mPosition = newPos;
306 NotifyOfPivotChange(oldPos, oldStart, oldEnd,
307 nsIAccessiblePivot::REASON_NEXT, aBoundary,
308 (aArgc > 0) ? aIsFromUserInput : true);
309 }
310
311 return NS_OK;
312 }
313
314 NS_IMETHODIMP
MovePreviousByText(TextBoundaryType aBoundary,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)315 nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary,
316 bool aIsFromUserInput, uint8_t aArgc,
317 bool* aResult) {
318 NS_ENSURE_ARG(aResult);
319
320 *aResult = false;
321
322 Pivot pivot(GetActiveRoot());
323
324 int32_t newStart = mStartOffset, newEnd = mEndOffset;
325 if (Accessible* newPos =
326 pivot.PrevText(mPosition, &newStart, &newEnd, aBoundary)) {
327 *aResult = true;
328 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
329 Accessible* oldPos = mPosition;
330 mStartOffset = newStart;
331 mEndOffset = newEnd;
332 mPosition = newPos;
333 NotifyOfPivotChange(oldPos, oldStart, oldEnd,
334 nsIAccessiblePivot::REASON_PREV, aBoundary,
335 (aArgc > 0) ? aIsFromUserInput : true);
336 }
337
338 return NS_OK;
339 }
340
341 NS_IMETHODIMP
MoveToPoint(nsIAccessibleTraversalRule * aRule,int32_t aX,int32_t aY,bool aIgnoreNoMatch,bool aIsFromUserInput,uint8_t aArgc,bool * aResult)342 nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule, int32_t aX,
343 int32_t aY, bool aIgnoreNoMatch,
344 bool aIsFromUserInput, uint8_t aArgc,
345 bool* aResult) {
346 NS_ENSURE_ARG_POINTER(aResult);
347 NS_ENSURE_ARG_POINTER(aRule);
348
349 *aResult = false;
350
351 Accessible* root = GetActiveRoot();
352 NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
353
354 RuleCache rule(aRule);
355 Pivot pivot(root);
356
357 Accessible* newPos = pivot.AtPoint(aX, aY, rule);
358 if (newPos || !aIgnoreNoMatch) {
359 *aResult = MovePivotInternal(newPos, nsIAccessiblePivot::REASON_POINT,
360 (aArgc > 0) ? aIsFromUserInput : true);
361 }
362
363 return NS_OK;
364 }
365
366 // Observer functions
367
368 NS_IMETHODIMP
AddObserver(nsIAccessiblePivotObserver * aObserver)369 nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver) {
370 NS_ENSURE_ARG(aObserver);
371
372 mObservers.AppendElement(aObserver);
373
374 return NS_OK;
375 }
376
377 NS_IMETHODIMP
RemoveObserver(nsIAccessiblePivotObserver * aObserver)378 nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver) {
379 NS_ENSURE_ARG(aObserver);
380
381 return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
382 }
383
384 // Private utility methods
385
IsDescendantOf(Accessible * aAccessible,Accessible * aAncestor)386 bool nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible,
387 Accessible* aAncestor) {
388 if (!aAncestor || aAncestor->IsDefunct()) return false;
389
390 // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
391 Accessible* accessible = aAccessible;
392 do {
393 if (accessible == aAncestor) return true;
394 } while ((accessible = accessible->Parent()));
395
396 return false;
397 }
398
MovePivotInternal(Accessible * aPosition,PivotMoveReason aReason,bool aIsFromUserInput)399 bool nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
400 PivotMoveReason aReason,
401 bool aIsFromUserInput) {
402 RefPtr<Accessible> oldPosition = std::move(mPosition);
403 mPosition = aPosition;
404 int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
405 mStartOffset = mEndOffset = -1;
406
407 return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason,
408 nsIAccessiblePivot::NO_BOUNDARY, aIsFromUserInput);
409 }
410
NotifyOfPivotChange(Accessible * aOldPosition,int32_t aOldStart,int32_t aOldEnd,int16_t aReason,int16_t aBoundaryType,bool aIsFromUserInput)411 bool nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
412 int32_t aOldStart, int32_t aOldEnd,
413 int16_t aReason,
414 int16_t aBoundaryType,
415 bool aIsFromUserInput) {
416 if (aOldPosition == mPosition && aOldStart == mStartOffset &&
417 aOldEnd == mEndOffset)
418 return false;
419
420 nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition); // death grip
421 nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver>>::ForwardIterator iter(
422 mObservers);
423 while (iter.HasMore()) {
424 nsIAccessiblePivotObserver* obs = iter.GetNext();
425 obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, ToXPC(mPosition),
426 mStartOffset, mEndOffset, aReason, aBoundaryType,
427 aIsFromUserInput);
428 }
429
430 return true;
431 }
432
Match(Accessible * aAccessible)433 uint16_t RuleCache::Match(Accessible* aAccessible) {
434 uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
435
436 if (!mAcceptRoles) {
437 mAcceptRoles.emplace();
438 DebugOnly<nsresult> rv = mRule->GetMatchRoles(*mAcceptRoles);
439 MOZ_ASSERT(NS_SUCCEEDED(rv));
440 rv = mRule->GetPreFilter(&mPreFilter);
441 MOZ_ASSERT(NS_SUCCEEDED(rv));
442 }
443
444 if (mPreFilter) {
445 uint64_t state = aAccessible->State();
446
447 if ((nsIAccessibleTraversalRule::PREFILTER_PLATFORM_PRUNED & mPreFilter) &&
448 nsAccUtils::MustPrune(aAccessible)) {
449 result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
450 }
451
452 if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
453 (state & states::INVISIBLE))
454 return result;
455
456 if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
457 (state & states::OFFSCREEN))
458 return result;
459
460 if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
461 !(state & states::FOCUSABLE))
462 return result;
463
464 if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
465 !(state & states::OPAQUE1)) {
466 nsIFrame* frame = aAccessible->GetFrame();
467 if (frame->StyleEffects()->mOpacity == 0.0f) {
468 return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
469 }
470 }
471 }
472
473 if (mAcceptRoles->Length() > 0) {
474 uint32_t accessibleRole = aAccessible->Role();
475 bool matchesRole = false;
476 for (uint32_t idx = 0; idx < mAcceptRoles->Length(); idx++) {
477 matchesRole = mAcceptRoles->ElementAt(idx) == accessibleRole;
478 if (matchesRole) break;
479 }
480
481 if (!matchesRole) {
482 return result;
483 }
484 }
485
486 uint16_t matchResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
487 DebugOnly<nsresult> rv = mRule->Match(ToXPC(aAccessible), &matchResult);
488 MOZ_ASSERT(NS_SUCCEEDED(rv));
489
490 return result | matchResult;
491 }
492