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/. */
7 #include "nsAccessiblePivot.h"
9 #include "HyperTextAccessible.h"
10 #include "nsAccUtils.h"
13 #include "xpcAccessibleDocument.h"
15 #include "mozilla/Maybe.h"
17 using namespace mozilla::a11y
;
21 * An object that stores a given traversal rule during the pivot movement.
23 class RuleCache
: public PivotRule
{
25 explicit RuleCache(nsIAccessibleTraversalRule
* aRule
)
26 : mRule(aRule
), mPreFilter
{0} {}
29 virtual uint16_t Match(Accessible
* aAccessible
) override
;
32 nsCOMPtr
<nsIAccessibleTraversalRule
> mRule
;
33 Maybe
<nsTArray
<uint32_t>> mAcceptRoles
;
37 ////////////////////////////////////////////////////////////////////////////////
40 nsAccessiblePivot::nsAccessiblePivot(Accessible
* aRoot
)
46 NS_ASSERTION(aRoot
, "A root accessible is required");
49 nsAccessiblePivot::~nsAccessiblePivot() {}
51 ////////////////////////////////////////////////////////////////////////////////
54 NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot
, mRoot
, mPosition
, mObservers
)
56 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot
)
57 NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot
)
58 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIAccessiblePivot
)
61 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot
)
62 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot
)
64 ////////////////////////////////////////////////////////////////////////////////
68 nsAccessiblePivot::GetRoot(nsIAccessible
** aRoot
) {
69 NS_ENSURE_ARG_POINTER(aRoot
);
71 NS_IF_ADDREF(*aRoot
= ToXPC(mRoot
));
77 nsAccessiblePivot::GetPosition(nsIAccessible
** aPosition
) {
78 NS_ENSURE_ARG_POINTER(aPosition
);
80 NS_IF_ADDREF(*aPosition
= ToXPC(mPosition
));
86 nsAccessiblePivot::SetPosition(nsIAccessible
* aPosition
) {
87 RefPtr
<Accessible
> position
= nullptr;
90 position
= aPosition
->ToInternalAccessible();
91 if (!position
|| !IsDescendantOf(position
, GetActiveRoot()))
92 return NS_ERROR_INVALID_ARG
;
95 // Swap old position with new position, saves us an AddRef/Release.
96 mPosition
.swap(position
);
97 int32_t oldStart
= mStartOffset
, oldEnd
= mEndOffset
;
98 mStartOffset
= mEndOffset
= -1;
99 NotifyOfPivotChange(position
, oldStart
, oldEnd
,
100 nsIAccessiblePivot::REASON_NONE
,
101 nsIAccessiblePivot::NO_BOUNDARY
, false);
107 nsAccessiblePivot::GetModalRoot(nsIAccessible
** aModalRoot
) {
108 NS_ENSURE_ARG_POINTER(aModalRoot
);
110 NS_IF_ADDREF(*aModalRoot
= ToXPC(mModalRoot
));
116 nsAccessiblePivot::SetModalRoot(nsIAccessible
* aModalRoot
) {
117 Accessible
* modalRoot
= nullptr;
120 modalRoot
= aModalRoot
->ToInternalAccessible();
121 if (!modalRoot
|| !IsDescendantOf(modalRoot
, mRoot
))
122 return NS_ERROR_INVALID_ARG
;
125 mModalRoot
= modalRoot
;
130 nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset
) {
131 NS_ENSURE_ARG_POINTER(aStartOffset
);
133 *aStartOffset
= mStartOffset
;
139 nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset
) {
140 NS_ENSURE_ARG_POINTER(aEndOffset
);
142 *aEndOffset
= mEndOffset
;
148 nsAccessiblePivot::SetTextRange(nsIAccessibleText
* aTextAccessible
,
149 int32_t aStartOffset
, int32_t aEndOffset
,
150 bool aIsFromUserInput
, uint8_t aArgc
) {
151 NS_ENSURE_ARG(aTextAccessible
);
153 // Check that start offset is smaller than end offset, and that if a value is
154 // smaller than 0, both should be -1.
156 aStartOffset
<= aEndOffset
&&
157 (aStartOffset
>= 0 || (aStartOffset
!= -1 && aEndOffset
!= -1)),
158 NS_ERROR_INVALID_ARG
);
160 nsCOMPtr
<nsIAccessible
> xpcAcc
= do_QueryInterface(aTextAccessible
);
161 NS_ENSURE_ARG(xpcAcc
);
163 RefPtr
<Accessible
> acc
= xpcAcc
->ToInternalAccessible();
166 HyperTextAccessible
* position
= acc
->AsHyperText();
167 if (!position
|| !IsDescendantOf(position
, GetActiveRoot()))
168 return NS_ERROR_INVALID_ARG
;
170 // Make sure the given offsets don't exceed the character count.
171 if (aEndOffset
> static_cast<int32_t>(position
->CharacterCount()))
172 return NS_ERROR_FAILURE
;
174 int32_t oldStart
= mStartOffset
, oldEnd
= mEndOffset
;
175 mStartOffset
= aStartOffset
;
176 mEndOffset
= aEndOffset
;
179 NotifyOfPivotChange(acc
, oldStart
, oldEnd
, nsIAccessiblePivot::REASON_NONE
,
180 nsIAccessiblePivot::NO_BOUNDARY
,
181 (aArgc
> 0) ? aIsFromUserInput
: true);
186 // Traversal functions
189 nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule
* aRule
,
190 nsIAccessible
* aAnchor
, bool aIncludeStart
,
191 bool aIsFromUserInput
, uint8_t aArgc
,
193 NS_ENSURE_ARG(aResult
);
194 NS_ENSURE_ARG(aRule
);
197 Accessible
* anchor
= mPosition
;
198 if (aArgc
> 0 && aAnchor
) anchor
= aAnchor
->ToInternalAccessible();
201 (anchor
->IsDefunct() || !IsDescendantOf(anchor
, GetActiveRoot())))
202 return NS_ERROR_NOT_IN_TREE
;
204 Pivot
pivot(GetActiveRoot());
205 RuleCache
rule(aRule
);
207 if (Accessible
* newPos
=
208 pivot
.Next(anchor
, rule
, (aArgc
> 1) ? aIncludeStart
: false)) {
209 *aResult
= MovePivotInternal(newPos
, nsIAccessiblePivot::REASON_NEXT
,
210 (aArgc
> 2) ? aIsFromUserInput
: true);
217 nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule
* aRule
,
218 nsIAccessible
* aAnchor
, bool aIncludeStart
,
219 bool aIsFromUserInput
, uint8_t aArgc
,
221 NS_ENSURE_ARG(aResult
);
222 NS_ENSURE_ARG(aRule
);
225 Accessible
* anchor
= mPosition
;
226 if (aArgc
> 0 && aAnchor
) anchor
= aAnchor
->ToInternalAccessible();
229 (anchor
->IsDefunct() || !IsDescendantOf(anchor
, GetActiveRoot())))
230 return NS_ERROR_NOT_IN_TREE
;
232 Pivot
pivot(GetActiveRoot());
233 RuleCache
rule(aRule
);
235 if (Accessible
* newPos
=
236 pivot
.Prev(anchor
, rule
, (aArgc
> 1) ? aIncludeStart
: false)) {
237 *aResult
= MovePivotInternal(newPos
, nsIAccessiblePivot::REASON_PREV
,
238 (aArgc
> 2) ? aIsFromUserInput
: true);
245 nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule
* aRule
,
246 bool aIsFromUserInput
, uint8_t aArgc
,
248 NS_ENSURE_ARG(aResult
);
249 NS_ENSURE_ARG(aRule
);
251 Accessible
* root
= GetActiveRoot();
252 NS_ENSURE_TRUE(root
&& !root
->IsDefunct(), NS_ERROR_NOT_IN_TREE
);
254 Pivot
pivot(GetActiveRoot());
255 RuleCache
rule(aRule
);
257 if (Accessible
* newPos
= pivot
.First(rule
)) {
258 *aResult
= MovePivotInternal(newPos
, nsIAccessiblePivot::REASON_FIRST
,
259 (aArgc
> 0) ? aIsFromUserInput
: true);
266 nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule
* aRule
,
267 bool aIsFromUserInput
, uint8_t aArgc
,
269 NS_ENSURE_ARG(aResult
);
270 NS_ENSURE_ARG(aRule
);
272 Accessible
* root
= GetActiveRoot();
273 NS_ENSURE_TRUE(root
&& !root
->IsDefunct(), NS_ERROR_NOT_IN_TREE
);
276 RuleCache
rule(aRule
);
278 if (Accessible
* newPos
= pivot
.Last(rule
)) {
279 *aResult
= MovePivotInternal(newPos
, nsIAccessiblePivot::REASON_LAST
,
280 (aArgc
> 0) ? aIsFromUserInput
: true);
287 nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary
,
288 bool aIsFromUserInput
, uint8_t aArgc
,
290 NS_ENSURE_ARG(aResult
);
294 Pivot
pivot(GetActiveRoot());
296 int32_t newStart
= mStartOffset
, newEnd
= mEndOffset
;
297 if (Accessible
* newPos
=
298 pivot
.NextText(mPosition
, &newStart
, &newEnd
, aBoundary
)) {
300 int32_t oldStart
= mStartOffset
, oldEnd
= mEndOffset
;
301 Accessible
* oldPos
= mPosition
;
302 mStartOffset
= newStart
;
305 NotifyOfPivotChange(oldPos
, oldStart
, oldEnd
,
306 nsIAccessiblePivot::REASON_NEXT
, aBoundary
,
307 (aArgc
> 0) ? aIsFromUserInput
: true);
314 nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary
,
315 bool aIsFromUserInput
, uint8_t aArgc
,
317 NS_ENSURE_ARG(aResult
);
321 Pivot
pivot(GetActiveRoot());
323 int32_t newStart
= mStartOffset
, newEnd
= mEndOffset
;
324 if (Accessible
* newPos
=
325 pivot
.PrevText(mPosition
, &newStart
, &newEnd
, aBoundary
)) {
327 int32_t oldStart
= mStartOffset
, oldEnd
= mEndOffset
;
328 Accessible
* oldPos
= mPosition
;
329 mStartOffset
= newStart
;
332 NotifyOfPivotChange(oldPos
, oldStart
, oldEnd
,
333 nsIAccessiblePivot::REASON_PREV
, aBoundary
,
334 (aArgc
> 0) ? aIsFromUserInput
: true);
341 nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule
* aRule
, int32_t aX
,
342 int32_t aY
, bool aIgnoreNoMatch
,
343 bool aIsFromUserInput
, uint8_t aArgc
,
345 NS_ENSURE_ARG_POINTER(aResult
);
346 NS_ENSURE_ARG_POINTER(aRule
);
350 Accessible
* root
= GetActiveRoot();
351 NS_ENSURE_TRUE(root
&& !root
->IsDefunct(), NS_ERROR_NOT_IN_TREE
);
353 RuleCache
rule(aRule
);
356 Accessible
* newPos
= pivot
.AtPoint(aX
, aY
, rule
);
357 if (newPos
|| !aIgnoreNoMatch
) {
358 *aResult
= MovePivotInternal(newPos
, nsIAccessiblePivot::REASON_POINT
,
359 (aArgc
> 0) ? aIsFromUserInput
: true);
365 // Observer functions
368 nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver
* aObserver
) {
369 NS_ENSURE_ARG(aObserver
);
371 mObservers
.AppendElement(aObserver
);
377 nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver
* aObserver
) {
378 NS_ENSURE_ARG(aObserver
);
380 return mObservers
.RemoveElement(aObserver
) ? NS_OK
: NS_ERROR_FAILURE
;
383 // Private utility methods
385 bool nsAccessiblePivot::IsDescendantOf(Accessible
* aAccessible
,
386 Accessible
* aAncestor
) {
387 if (!aAncestor
|| aAncestor
->IsDefunct()) return false;
389 // XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
390 Accessible
* accessible
= aAccessible
;
392 if (accessible
== aAncestor
) return true;
393 } while ((accessible
= accessible
->Parent()));
398 bool nsAccessiblePivot::MovePivotInternal(Accessible
* aPosition
,
399 PivotMoveReason aReason
,
400 bool aIsFromUserInput
) {
401 RefPtr
<Accessible
> oldPosition
= std::move(mPosition
);
402 mPosition
= aPosition
;
403 int32_t oldStart
= mStartOffset
, oldEnd
= mEndOffset
;
404 mStartOffset
= mEndOffset
= -1;
406 return NotifyOfPivotChange(oldPosition
, oldStart
, oldEnd
, aReason
,
407 nsIAccessiblePivot::NO_BOUNDARY
, aIsFromUserInput
);
410 bool nsAccessiblePivot::NotifyOfPivotChange(Accessible
* aOldPosition
,
411 int32_t aOldStart
, int32_t aOldEnd
,
413 int16_t aBoundaryType
,
414 bool aIsFromUserInput
) {
415 if (aOldPosition
== mPosition
&& aOldStart
== mStartOffset
&&
416 aOldEnd
== mEndOffset
)
419 nsCOMPtr
<nsIAccessible
> xpcOldPos
= ToXPC(aOldPosition
); // death grip
420 nsTObserverArray
<nsCOMPtr
<nsIAccessiblePivotObserver
>>::ForwardIterator
iter(
422 while (iter
.HasMore()) {
423 nsIAccessiblePivotObserver
* obs
= iter
.GetNext();
424 obs
->OnPivotChanged(this, xpcOldPos
, aOldStart
, aOldEnd
, ToXPC(mPosition
),
425 mStartOffset
, mEndOffset
, aReason
, aBoundaryType
,
432 uint16_t RuleCache::Match(Accessible
* aAccessible
) {
433 uint16_t result
= nsIAccessibleTraversalRule::FILTER_IGNORE
;
436 mAcceptRoles
.emplace();
437 DebugOnly
<nsresult
> rv
= mRule
->GetMatchRoles(*mAcceptRoles
);
438 MOZ_ASSERT(NS_SUCCEEDED(rv
));
439 rv
= mRule
->GetPreFilter(&mPreFilter
);
440 MOZ_ASSERT(NS_SUCCEEDED(rv
));
444 uint64_t state
= aAccessible
->State();
446 if ((nsIAccessibleTraversalRule::PREFILTER_PLATFORM_PRUNED
& mPreFilter
) &&
447 nsAccUtils::MustPrune(aAccessible
)) {
448 result
|= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
451 if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE
& mPreFilter
) &&
452 (state
& states::INVISIBLE
))
455 if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN
& mPreFilter
) &&
456 (state
& states::OFFSCREEN
))
459 if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE
& mPreFilter
) &&
460 !(state
& states::FOCUSABLE
))
463 if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT
& mPreFilter
) &&
464 !(state
& states::OPAQUE1
)) {
465 nsIFrame
* frame
= aAccessible
->GetFrame();
466 if (frame
->StyleEffects()->mOpacity
== 0.0f
) {
467 return result
| nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE
;
472 if (mAcceptRoles
->Length() > 0) {
473 uint32_t accessibleRole
= aAccessible
->Role();
474 bool matchesRole
= false;
475 for (uint32_t idx
= 0; idx
< mAcceptRoles
->Length(); idx
++) {
476 matchesRole
= mAcceptRoles
->ElementAt(idx
) == accessibleRole
;
477 if (matchesRole
) break;
485 uint16_t matchResult
= nsIAccessibleTraversalRule::FILTER_IGNORE
;
486 DebugOnly
<nsresult
> rv
= mRule
->Match(ToXPC(aAccessible
), &matchResult
);
487 MOZ_ASSERT(NS_SUCCEEDED(rv
));
489 return result
| matchResult
;