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