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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Eitan Isaacson <eitan@monotonous.org> (original author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsAccessiblePivot.h"
41 :
42 : #include "nsAccessible.h"
43 : #include "nsAccUtils.h"
44 : #include "nsHyperTextAccessible.h"
45 : #include "States.h"
46 :
47 : #include "nsArrayUtils.h"
48 : #include "nsComponentManagerUtils.h"
49 : #include "nsISupportsPrimitives.h"
50 :
51 : using namespace mozilla::a11y;
52 :
53 :
54 : /**
55 : * An object that stores a given traversal rule during
56 : */
57 : class RuleCache
58 : {
59 : public:
60 0 : RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
61 0 : mAcceptRoles(nsnull) { }
62 0 : ~RuleCache () {
63 0 : if (mAcceptRoles)
64 0 : nsMemory::Free(mAcceptRoles);
65 0 : }
66 :
67 : nsresult ApplyFilter(nsAccessible* aAccessible, PRUint16* aResult);
68 :
69 : private:
70 : nsCOMPtr<nsIAccessibleTraversalRule> mRule;
71 : PRUint32* mAcceptRoles;
72 : PRUint32 mAcceptRolesLength;
73 : PRUint32 mPreFilter;
74 : };
75 :
76 : ////////////////////////////////////////////////////////////////////////////////
77 : // nsAccessiblePivot
78 :
79 0 : nsAccessiblePivot::nsAccessiblePivot(nsAccessible* aRoot) :
80 : mRoot(aRoot), mPosition(nsnull),
81 0 : mStartOffset(-1), mEndOffset(-1)
82 : {
83 0 : NS_ASSERTION(aRoot, "A root accessible is required");
84 0 : }
85 :
86 : ////////////////////////////////////////////////////////////////////////////////
87 : // nsISupports
88 :
89 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccessiblePivot)
90 :
91 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccessiblePivot)
92 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mRoot, nsIAccessible)
93 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mPosition, nsIAccessible)
94 0 : PRUint32 i, length = tmp->mObservers.Length(); \
95 0 : for (i = 0; i < length; ++i) {
96 0 : cb.NoteXPCOMChild(tmp->mObservers.ElementAt(i).get());
97 : }
98 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
99 :
100 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccessiblePivot)
101 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRoot)
102 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPosition)
103 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mObservers)
104 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
105 :
106 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
107 0 : NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
108 0 : NS_INTERFACE_MAP_ENTRY(nsAccessiblePivot)
109 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
110 0 : NS_INTERFACE_MAP_END
111 :
112 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
113 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
114 :
115 : ////////////////////////////////////////////////////////////////////////////////
116 : // nsIAccessiblePivot
117 :
118 : NS_IMETHODIMP
119 0 : nsAccessiblePivot::GetRoot(nsIAccessible** aRoot)
120 : {
121 0 : NS_ENSURE_ARG_POINTER(aRoot);
122 :
123 0 : NS_IF_ADDREF(*aRoot = mRoot);
124 :
125 0 : return NS_OK;
126 : }
127 :
128 : NS_IMETHODIMP
129 0 : nsAccessiblePivot::GetPosition(nsIAccessible** aPosition)
130 : {
131 0 : NS_ENSURE_ARG_POINTER(aPosition);
132 :
133 0 : NS_IF_ADDREF(*aPosition = mPosition);
134 :
135 0 : return NS_OK;
136 : }
137 :
138 : NS_IMETHODIMP
139 0 : nsAccessiblePivot::SetPosition(nsIAccessible* aPosition)
140 : {
141 0 : nsRefPtr<nsAccessible> secondPosition;
142 :
143 0 : if (aPosition) {
144 0 : secondPosition = do_QueryObject(aPosition);
145 0 : if (!secondPosition || !IsRootDescendant(secondPosition))
146 0 : return NS_ERROR_INVALID_ARG;
147 : }
148 :
149 : // Swap old position with new position, saves us an AddRef/Release.
150 0 : mPosition.swap(secondPosition);
151 0 : PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
152 0 : mStartOffset = mEndOffset = -1;
153 0 : NotifyPivotChanged(secondPosition, oldStart, oldEnd);
154 :
155 0 : return NS_OK;
156 : }
157 :
158 : NS_IMETHODIMP
159 0 : nsAccessiblePivot::GetStartOffset(PRInt32* aStartOffset)
160 : {
161 0 : NS_ENSURE_ARG_POINTER(aStartOffset);
162 :
163 0 : *aStartOffset = mStartOffset;
164 :
165 0 : return NS_OK;
166 : }
167 :
168 : NS_IMETHODIMP
169 0 : nsAccessiblePivot::GetEndOffset(PRInt32* aEndOffset)
170 : {
171 0 : NS_ENSURE_ARG_POINTER(aEndOffset);
172 :
173 0 : *aEndOffset = mEndOffset;
174 :
175 0 : return NS_OK;
176 : }
177 :
178 : NS_IMETHODIMP
179 0 : nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
180 : PRInt32 aStartOffset, PRInt32 aEndOffset)
181 : {
182 0 : NS_ENSURE_ARG(aTextAccessible);
183 :
184 : // Check that start offset is smaller than end offset, and that if a value is
185 : // smaller than 0, both should be -1.
186 0 : NS_ENSURE_TRUE(aStartOffset <= aEndOffset &&
187 : (aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
188 : NS_ERROR_INVALID_ARG);
189 :
190 0 : nsRefPtr<nsHyperTextAccessible> newPosition = do_QueryObject(aTextAccessible);
191 0 : if (!newPosition || !IsRootDescendant(newPosition))
192 0 : return NS_ERROR_INVALID_ARG;
193 :
194 : // Make sure the given offsets don't exceed the character count.
195 0 : PRInt32 charCount = newPosition->CharacterCount();
196 :
197 0 : if (aEndOffset > charCount)
198 0 : return NS_ERROR_FAILURE;
199 :
200 0 : PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
201 0 : mStartOffset = aStartOffset;
202 0 : mEndOffset = aEndOffset;
203 :
204 0 : nsRefPtr<nsAccessible> oldPosition = mPosition.forget();
205 0 : mPosition = newPosition.forget();
206 :
207 0 : NotifyPivotChanged(oldPosition, oldStart, oldEnd);
208 :
209 0 : return NS_OK;
210 : }
211 :
212 : // Traversal functions
213 :
214 : NS_IMETHODIMP
215 0 : nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule, bool* aResult)
216 : {
217 0 : NS_ENSURE_ARG(aResult);
218 0 : NS_ENSURE_ARG(aRule);
219 :
220 0 : nsresult rv = NS_OK;
221 0 : nsAccessible* accessible = SearchForward(mPosition, aRule, false, &rv);
222 0 : NS_ENSURE_SUCCESS(rv, rv);
223 :
224 0 : *aResult = accessible;
225 0 : if (*aResult)
226 0 : MovePivotInternal(accessible);
227 :
228 0 : return NS_OK;
229 : }
230 :
231 : NS_IMETHODIMP
232 0 : nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule, bool* aResult)
233 : {
234 0 : NS_ENSURE_ARG(aResult);
235 0 : NS_ENSURE_ARG(aRule);
236 :
237 0 : nsresult rv = NS_OK;
238 0 : nsAccessible* accessible = SearchBackward(mPosition, aRule, false, &rv);
239 0 : NS_ENSURE_SUCCESS(rv, rv);
240 :
241 0 : *aResult = accessible;
242 0 : if (*aResult)
243 0 : MovePivotInternal(accessible);
244 :
245 0 : return NS_OK;
246 : }
247 :
248 : NS_IMETHODIMP
249 0 : nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule, bool* aResult)
250 : {
251 0 : NS_ENSURE_ARG(aResult);
252 0 : NS_ENSURE_ARG(aRule);
253 0 : nsresult rv = NS_OK;
254 0 : nsAccessible* accessible = SearchForward(mRoot, aRule, true, &rv);
255 0 : NS_ENSURE_SUCCESS(rv, rv);
256 :
257 0 : *aResult = accessible;
258 0 : if (*aResult)
259 0 : MovePivotInternal(accessible);
260 :
261 0 : return NS_OK;
262 : }
263 :
264 : NS_IMETHODIMP
265 0 : nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule, bool* aResult)
266 : {
267 0 : NS_ENSURE_ARG(aResult);
268 0 : NS_ENSURE_ARG(aRule);
269 :
270 0 : *aResult = false;
271 0 : nsresult rv = NS_OK;
272 0 : nsAccessible* lastAccessible = mRoot;
273 0 : nsAccessible* accessible = nsnull;
274 :
275 : // First got to the last accessible in pre-order
276 0 : while (lastAccessible->HasChildren())
277 0 : lastAccessible = lastAccessible->LastChild();
278 :
279 : // Search backwards from last accessible and find the last occurrence in the doc
280 0 : accessible = SearchBackward(lastAccessible, aRule, true, &rv);
281 0 : NS_ENSURE_SUCCESS(rv, rv);
282 :
283 0 : *aResult = accessible;
284 0 : if (*aResult)
285 0 : MovePivotInternal(accessible);
286 :
287 0 : return NS_OK;
288 : }
289 :
290 : // TODO: Implement
291 : NS_IMETHODIMP
292 0 : nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult)
293 : {
294 0 : NS_ENSURE_ARG(aResult);
295 :
296 0 : *aResult = false;
297 :
298 0 : return NS_ERROR_NOT_IMPLEMENTED;
299 : }
300 :
301 : // TODO: Implement
302 : NS_IMETHODIMP
303 0 : nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult)
304 : {
305 0 : NS_ENSURE_ARG(aResult);
306 :
307 0 : *aResult = false;
308 :
309 0 : return NS_ERROR_NOT_IMPLEMENTED;
310 : }
311 :
312 : // Observer functions
313 :
314 : NS_IMETHODIMP
315 0 : nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver)
316 : {
317 0 : NS_ENSURE_ARG(aObserver);
318 :
319 0 : mObservers.AppendElement(aObserver);
320 :
321 0 : return NS_OK;
322 : }
323 :
324 : NS_IMETHODIMP
325 0 : nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver)
326 : {
327 0 : NS_ENSURE_ARG(aObserver);
328 :
329 0 : return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
330 : }
331 :
332 : // Private utility methods
333 :
334 : bool
335 0 : nsAccessiblePivot::IsRootDescendant(nsAccessible* aAccessible)
336 : {
337 0 : nsAccessible* accessible = aAccessible;
338 0 : do {
339 0 : if (accessible == mRoot)
340 0 : return true;
341 : } while ((accessible = accessible->Parent()));
342 :
343 0 : return false;
344 : }
345 :
346 : void
347 0 : nsAccessiblePivot::MovePivotInternal(nsAccessible* aPosition)
348 : {
349 0 : nsRefPtr<nsAccessible> oldPosition = mPosition.forget();
350 0 : mPosition = aPosition;
351 0 : PRInt32 oldStart = mStartOffset, oldEnd = mEndOffset;
352 0 : mStartOffset = mEndOffset = -1;
353 :
354 0 : NotifyPivotChanged(oldPosition, oldStart, oldEnd);
355 0 : }
356 :
357 : nsAccessible*
358 0 : nsAccessiblePivot::SearchBackward(nsAccessible* aAccessible,
359 : nsIAccessibleTraversalRule* aRule,
360 : bool searchCurrent,
361 : nsresult* rv)
362 : {
363 0 : *rv = NS_OK;
364 :
365 : // Initial position could be unset, in that case return null.
366 0 : if (!aAccessible)
367 0 : return nsnull;
368 :
369 0 : RuleCache cache(aRule);
370 0 : nsAccessible* accessible = aAccessible;
371 :
372 0 : PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
373 :
374 0 : if (searchCurrent) {
375 0 : *rv = cache.ApplyFilter(accessible, &filtered);
376 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
377 0 : if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
378 0 : return accessible;
379 : }
380 :
381 0 : while (accessible != mRoot) {
382 0 : nsAccessible* parent = accessible->Parent();
383 0 : PRInt32 idxInParent = accessible->IndexInParent();
384 0 : while (idxInParent > 0) {
385 0 : if (!(accessible = parent->GetChildAt(--idxInParent)))
386 0 : continue;
387 :
388 0 : *rv = cache.ApplyFilter(accessible, &filtered);
389 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
390 :
391 : nsAccessible* lastChild;
392 0 : while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
393 : (lastChild = accessible->LastChild())) {
394 0 : parent = accessible;
395 0 : accessible = lastChild;
396 0 : *rv = cache.ApplyFilter(accessible, &filtered);
397 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
398 : }
399 :
400 0 : if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
401 0 : return accessible;
402 : }
403 :
404 0 : if (!(accessible = parent))
405 0 : break;
406 :
407 0 : *rv = cache.ApplyFilter(accessible, &filtered);
408 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
409 :
410 0 : if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
411 0 : return accessible;
412 : }
413 :
414 0 : return nsnull;
415 : }
416 :
417 : nsAccessible*
418 0 : nsAccessiblePivot::SearchForward(nsAccessible* aAccessible,
419 : nsIAccessibleTraversalRule* aRule,
420 : bool searchCurrent,
421 : nsresult* rv)
422 : {
423 0 : *rv = NS_OK;
424 :
425 : // Initial position could be not set, in that case begin search from root.
426 0 : nsAccessible *accessible = (!aAccessible) ? mRoot.get() : aAccessible;
427 :
428 0 : RuleCache cache(aRule);
429 :
430 0 : PRUint16 filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
431 0 : *rv = cache.ApplyFilter(accessible, &filtered);
432 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
433 0 : if (searchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
434 0 : return accessible;
435 :
436 0 : while (true) {
437 0 : nsAccessible* firstChild = nsnull;
438 0 : while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
439 : (firstChild = accessible->FirstChild())) {
440 0 : accessible = firstChild;
441 0 : *rv = cache.ApplyFilter(accessible, &filtered);
442 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
443 :
444 0 : if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
445 0 : return accessible;
446 : }
447 :
448 0 : nsAccessible* sibling = nsnull;
449 0 : nsAccessible* temp = accessible;
450 0 : do {
451 0 : if (temp == mRoot)
452 0 : break;
453 :
454 0 : sibling = temp->NextSibling();
455 :
456 0 : if (sibling)
457 0 : break;
458 : } while ((temp = temp->Parent()));
459 :
460 0 : if (!sibling)
461 : break;
462 :
463 0 : accessible = sibling;
464 0 : *rv = cache.ApplyFilter(accessible, &filtered);
465 0 : NS_ENSURE_SUCCESS(*rv, nsnull);
466 :
467 0 : if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
468 0 : return accessible;
469 : }
470 :
471 0 : return nsnull;
472 : }
473 :
474 : void
475 0 : nsAccessiblePivot::NotifyPivotChanged(nsAccessible* aOldPosition,
476 : PRInt32 aOldStart, PRInt32 aOldEnd)
477 : {
478 0 : nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
479 0 : while (iter.HasMore()) {
480 0 : nsIAccessiblePivotObserver* obs = iter.GetNext();
481 0 : obs->OnPivotChanged(this, aOldPosition, aOldStart, aOldEnd);
482 : }
483 0 : }
484 :
485 : nsresult
486 0 : RuleCache::ApplyFilter(nsAccessible* aAccessible, PRUint16* aResult)
487 : {
488 0 : *aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
489 :
490 0 : if (!mAcceptRoles) {
491 0 : nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
492 0 : NS_ENSURE_SUCCESS(rv, rv);
493 0 : rv = mRule->GetPreFilter(&mPreFilter);
494 0 : NS_ENSURE_SUCCESS(rv, rv);
495 : }
496 :
497 0 : if (mPreFilter) {
498 0 : PRUint64 state = aAccessible->State();
499 :
500 0 : if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
501 : (state & states::INVISIBLE))
502 0 : return NS_OK;
503 :
504 0 : if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
505 : (state & states::OFFSCREEN))
506 0 : return NS_OK;
507 :
508 0 : if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
509 0 : !(state & states::FOCUSABLE))
510 0 : return NS_OK;
511 : }
512 :
513 0 : if (mAcceptRolesLength > 0) {
514 0 : PRUint32 accessibleRole = aAccessible->Role();
515 0 : bool matchesRole = false;
516 0 : for (PRUint32 idx = 0; idx < mAcceptRolesLength; idx++) {
517 0 : matchesRole = mAcceptRoles[idx] == accessibleRole;
518 0 : if (matchesRole)
519 0 : break;
520 : }
521 0 : if (!matchesRole)
522 0 : return NS_OK;
523 : }
524 :
525 0 : return mRule->Match(aAccessible, aResult);
526 4392 : }
|