Bug 1724541 [wpt PR 29937] - App history: implement reload() instead of no-arg naviga...
[gecko.git] / accessible / mac / MOXSearchInfo.mm
blob205ce1d0209fe8ecc8457745d9b4eac33658662b
1 /* clang-format off */
2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* clang-format on */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #import "MOXSearchInfo.h"
9 #import "MOXWebAreaAccessible.h"
10 #import "RotorRules.h"
12 #include "nsCocoaUtils.h"
13 #include "DocAccessibleParent.h"
15 using namespace mozilla::a11y;
17 @interface MOXSearchInfo ()
18 - (NSArray*)getMatchesForRule:(PivotRule&)rule;
20 - (NSArray<mozAccessible*>*)applyPostFilter:(NSArray<mozAccessible*>*)matches;
22 - (Accessible*)rootGeckoAccessible;
24 - (Accessible*)startGeckoAccessible;
26 - (BOOL)shouldApplyPostFilter;
27 @end
29 @implementation MOXSearchInfo
31 - (id)initWithParameters:(NSDictionary*)params
32                  andRoot:(MOXAccessibleBase*)root {
33   if (id searchKeyParam = [params objectForKey:@"AXSearchKey"]) {
34     mSearchKeys = [searchKeyParam isKindOfClass:[NSString class]]
35                       ? [[NSArray alloc] initWithObjects:searchKeyParam, nil]
36                       : [searchKeyParam retain];
37   }
39   if (id startElemParam = [params objectForKey:@"AXStartElement"]) {
40     mStartElem = startElemParam;
41   } else {
42     mStartElem = root;
43   }
45   mRoot = root;
47   mResultLimit = [[params objectForKey:@"AXResultsLimit"] intValue];
49   mSearchForward =
50       [[params objectForKey:@"AXDirection"] isEqualToString:@"AXDirectionNext"];
52   mImmediateDescendantsOnly =
53       [[params objectForKey:@"AXImmediateDescendantsOnly"] boolValue];
55   mSearchText = [params objectForKey:@"AXSearchText"];
57   return [super init];
60 - (Accessible*)rootGeckoAccessible {
61   id root =
62       [mRoot isKindOfClass:[mozAccessible class]] ? mRoot : [mRoot moxParent];
64   return [static_cast<mozAccessible*>(root) geckoAccessible];
67 - (Accessible*)startGeckoAccessible {
68   if ([mStartElem isKindOfClass:[mozAccessible class]]) {
69     return [static_cast<mozAccessible*>(mStartElem) geckoAccessible];
70   }
72   // If it isn't a mozAccessible, it doesn't have a gecko accessible
73   // this is most likely the root group. Use the gecko doc as the start
74   // accessible.
75   return [self rootGeckoAccessible];
78 - (NSArray*)getMatchesForRule:(PivotRule&)rule {
79   // If we will apply a post-filter, don't limit search so we
80   // don't come up short on the final result count.
81   int resultLimit = [self shouldApplyPostFilter] ? -1 : mResultLimit;
83   NSMutableArray<mozAccessible*>* matches =
84       [[[NSMutableArray alloc] init] autorelease];
85   Accessible* geckoRootAcc = [self rootGeckoAccessible];
86   Accessible* geckoStartAcc = [self startGeckoAccessible];
87   Pivot p = Pivot(geckoRootAcc);
88   Accessible* match;
89   if (mSearchForward) {
90     match = p.Next(geckoStartAcc, rule);
91   } else {
92     // Search backwards
93     if (geckoRootAcc == geckoStartAcc) {
94       // If we have no explicit start accessible, start from the last match.
95       match = p.Last(rule);
96     } else {
97       match = p.Prev(geckoStartAcc, rule);
98     }
99   }
101   while (match && resultLimit != 0) {
102     if (!mSearchForward && match == geckoRootAcc) {
103       // If searching backwards, don't include root.
104       break;
105     }
107     // we use mResultLimit != 0 to capture the case where mResultLimit is -1
108     // when it is set from the params dictionary. If that's true, we want
109     // to return all matches (ie. have no limit)
110     mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(match);
111     if (nativeMatch) {
112       // only add/count results for which there is a matching
113       // native accessible
114       [matches addObject:nativeMatch];
115       resultLimit -= 1;
116     }
118     match = mSearchForward ? p.Next(match, rule) : p.Prev(match, rule);
119   }
121   return [self applyPostFilter:matches];
124 - (BOOL)shouldApplyPostFilter {
125   // We currently only support AXSearchText as a post-search filter.
126   return !!mSearchText;
129 - (NSArray<mozAccessible*>*)applyPostFilter:(NSArray<mozAccessible*>*)matches {
130   if (![self shouldApplyPostFilter]) {
131     return matches;
132   }
134   NSMutableArray<mozAccessible*>* postMatches =
135       [[[NSMutableArray alloc] init] autorelease];
137   nsString searchText;
138   nsCocoaUtils::GetStringForNSString(mSearchText, searchText);
140   __block DocAccessibleParent* ipcDoc = nullptr;
141   __block nsTArray<uint64_t> accIds;
143   [matches enumerateObjectsUsingBlock:^(mozAccessible* match, NSUInteger idx,
144                                         BOOL* stop) {
145     Accessible* geckoAcc = [match geckoAccessible];
146     if (!geckoAcc) {
147       return;
148     }
150     switch (geckoAcc->Role()) {
151       case roles::LANDMARK:
152       case roles::COMBOBOX:
153       case roles::LISTITEM:
154       case roles::COMBOBOX_LIST:
155       case roles::MENUBAR:
156       case roles::MENUPOPUP:
157       case roles::DOCUMENT:
158       case roles::APPLICATION:
159         // XXX: These roles either have AXTitle/AXDescription overridden as
160         // empty, or should never be returned in search text results. This
161         // should be integrated into a pivot rule in the future, and possibly
162         // better mapped somewhere.
163         return;
164       default:
165         break;
166     }
168     if (geckoAcc->IsLocal()) {
169       AccessibleWrap* acc = static_cast<AccessibleWrap*>(geckoAcc->AsLocal());
170       if (acc->ApplyPostFilter(EWhichPostFilter::eContainsText, searchText)) {
171         if (mozAccessible* nativePostMatch =
172                 GetNativeFromGeckoAccessible(acc)) {
173           [postMatches addObject:nativePostMatch];
174           if (mResultLimit > 0 &&
175               [postMatches count] >= static_cast<NSUInteger>(mResultLimit)) {
176             // If we reached the result limit, alter the `stop` pointer to YES
177             // to stop iteration.
178             *stop = YES;
179           }
180         }
181       }
183       return;
184     }
186     RemoteAccessible* proxy = geckoAcc->AsRemote();
187     if (ipcDoc &&
188         ((ipcDoc != proxy->Document()) || (idx + 1 == [matches count]))) {
189       // If the ipcDoc doesn't match the current proxy's doc, we crossed into a
190       // new document. ..or this is the last match. Apply the filter on the list
191       // of the current ipcDoc.
192       nsTArray<uint64_t> matchIds;
193       Unused << ipcDoc->GetPlatformExtension()->SendApplyPostSearchFilter(
194           accIds, mResultLimit, EWhichPostFilter::eContainsText, searchText,
195           &matchIds);
196       for (size_t i = 0; i < matchIds.Length(); i++) {
197         if (RemoteAccessible* postMatch =
198                 ipcDoc->GetAccessible(matchIds.ElementAt(i))) {
199           if (mozAccessible* nativePostMatch =
200                   GetNativeFromGeckoAccessible(postMatch)) {
201             [postMatches addObject:nativePostMatch];
202             if (mResultLimit > 0 &&
203                 [postMatches count] >= static_cast<NSUInteger>(mResultLimit)) {
204               // If we reached the result limit, alter the `stop` pointer to YES
205               // to stop iteration.
206               *stop = YES;
207               return;
208             }
209           }
210         }
211       }
213       ipcDoc = nullptr;
214       accIds.Clear();
215     }
217     if (!ipcDoc) {
218       ipcDoc = proxy->Document();
219     }
220     accIds.AppendElement(proxy->ID());
221   }];
223   return postMatches;
226 - (NSArray*)performSearch {
227   Accessible* geckoRootAcc = [self rootGeckoAccessible];
228   Accessible* geckoStartAcc = [self startGeckoAccessible];
229   NSMutableArray* matches = [[[NSMutableArray alloc] init] autorelease];
230   for (id key in mSearchKeys) {
231     if ([key isEqualToString:@"AXAnyTypeSearchKey"]) {
232       RotorRule rule =
233           mImmediateDescendantsOnly ? RotorRule(geckoRootAcc) : RotorRule();
235       if ([mStartElem isKindOfClass:[MOXWebAreaAccessible class]]) {
236         if (id rootGroup =
237                 [static_cast<MOXWebAreaAccessible*>(mStartElem) rootGroup]) {
238           // Moving forward from web area, rootgroup; if it exists, is next.
239           [matches addObject:rootGroup];
240           if (mResultLimit == 1) {
241             // Found one match, continue in search keys for block.
242             continue;
243           }
244         }
245       }
247       if (mImmediateDescendantsOnly && mStartElem != mRoot &&
248           [mStartElem isKindOfClass:[MOXRootGroup class]]) {
249         // Moving forward from root group. If we don't match descendants,
250         // there is no match. Continue.
251         continue;
252       }
253       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
254     }
256     if ([key isEqualToString:@"AXHeadingSearchKey"]) {
257       RotorRoleRule rule = mImmediateDescendantsOnly
258                                ? RotorRoleRule(roles::HEADING, geckoRootAcc)
259                                : RotorRoleRule(roles::HEADING);
260       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
261     }
263     if ([key isEqualToString:@"AXArticleSearchKey"]) {
264       RotorRoleRule rule = mImmediateDescendantsOnly
265                                ? RotorRoleRule(roles::ARTICLE, geckoRootAcc)
266                                : RotorRoleRule(roles::ARTICLE);
267       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
268     }
270     if ([key isEqualToString:@"AXTableSearchKey"]) {
271       RotorRoleRule rule = mImmediateDescendantsOnly
272                                ? RotorRoleRule(roles::TABLE, geckoRootAcc)
273                                : RotorRoleRule(roles::TABLE);
274       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
275     }
277     if ([key isEqualToString:@"AXLandmarkSearchKey"]) {
278       RotorRoleRule rule = mImmediateDescendantsOnly
279                                ? RotorRoleRule(roles::LANDMARK, geckoRootAcc)
280                                : RotorRoleRule(roles::LANDMARK);
281       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
282     }
284     if ([key isEqualToString:@"AXListSearchKey"]) {
285       RotorRoleRule rule = mImmediateDescendantsOnly
286                                ? RotorRoleRule(roles::LIST, geckoRootAcc)
287                                : RotorRoleRule(roles::LIST);
288       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
289     }
291     if ([key isEqualToString:@"AXLinkSearchKey"]) {
292       RotorLinkRule rule = mImmediateDescendantsOnly
293                                ? RotorLinkRule(geckoRootAcc)
294                                : RotorLinkRule();
295       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
296     }
298     if ([key isEqualToString:@"AXVisitedLinkSearchKey"]) {
299       RotorVisitedLinkRule rule = mImmediateDescendantsOnly
300                                       ? RotorVisitedLinkRule(geckoRootAcc)
301                                       : RotorVisitedLinkRule();
302       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
303     }
305     if ([key isEqualToString:@"AXUnvisitedLinkSearchKey"]) {
306       RotorUnvisitedLinkRule rule = mImmediateDescendantsOnly
307                                         ? RotorUnvisitedLinkRule(geckoRootAcc)
308                                         : RotorUnvisitedLinkRule();
309       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
310     }
312     if ([key isEqualToString:@"AXButtonSearchKey"]) {
313       RotorRoleRule rule = mImmediateDescendantsOnly
314                                ? RotorRoleRule(roles::PUSHBUTTON, geckoRootAcc)
315                                : RotorRoleRule(roles::PUSHBUTTON);
316       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
317     }
319     if ([key isEqualToString:@"AXControlSearchKey"]) {
320       RotorControlRule rule = mImmediateDescendantsOnly
321                                   ? RotorControlRule(geckoRootAcc)
322                                   : RotorControlRule();
323       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
324     }
326     if ([key isEqualToString:@"AXSameTypeSearchKey"]) {
327       mozAccessible* native = GetNativeFromGeckoAccessible(geckoStartAcc);
328       NSString* macRole = [native moxRole];
329       RotorMacRoleRule rule = mImmediateDescendantsOnly
330                                   ? RotorMacRoleRule(macRole, geckoRootAcc)
331                                   : RotorMacRoleRule(macRole);
332       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
333     }
335     if ([key isEqualToString:@"AXDifferentTypeSearchKey"]) {
336       mozAccessible* native = GetNativeFromGeckoAccessible(geckoStartAcc);
337       NSString* macRole = [native moxRole];
338       RotorNotMacRoleRule rule =
339           mImmediateDescendantsOnly ? RotorNotMacRoleRule(macRole, geckoRootAcc)
340                                     : RotorNotMacRoleRule(macRole);
341       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
342     }
344     if ([key isEqualToString:@"AXRadioGroupSearchKey"]) {
345       RotorRoleRule rule = mImmediateDescendantsOnly
346                                ? RotorRoleRule(roles::RADIO_GROUP, geckoRootAcc)
347                                : RotorRoleRule(roles::RADIO_GROUP);
348       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
349     }
351     if ([key isEqualToString:@"AXFrameSearchKey"]) {
352       RotorRoleRule rule = mImmediateDescendantsOnly
353                                ? RotorRoleRule(roles::DOCUMENT, geckoRootAcc)
354                                : RotorRoleRule(roles::DOCUMENT);
355       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
356     }
358     if ([key isEqualToString:@"AXImageSearchKey"] ||
359         [key isEqualToString:@"AXGraphicSearchKey"]) {
360       RotorRoleRule rule = mImmediateDescendantsOnly
361                                ? RotorRoleRule(roles::GRAPHIC, geckoRootAcc)
362                                : RotorRoleRule(roles::GRAPHIC);
363       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
364     }
366     if ([key isEqualToString:@"AXCheckBoxSearchKey"]) {
367       RotorRoleRule rule = mImmediateDescendantsOnly
368                                ? RotorRoleRule(roles::CHECKBUTTON, geckoRootAcc)
369                                : RotorRoleRule(roles::CHECKBUTTON);
370       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
371     }
373     if ([key isEqualToString:@"AXStaticTextSearchKey"]) {
374       RotorStaticTextRule rule = mImmediateDescendantsOnly
375                                      ? RotorStaticTextRule(geckoRootAcc)
376                                      : RotorStaticTextRule();
377       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
378     }
380     if ([key isEqualToString:@"AXHeadingLevel1SearchKey"]) {
381       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
382                                        ? RotorHeadingLevelRule(1, geckoRootAcc)
383                                        : RotorHeadingLevelRule(1);
384       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
385     }
387     if ([key isEqualToString:@"AXHeadingLevel2SearchKey"]) {
388       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
389                                        ? RotorHeadingLevelRule(2, geckoRootAcc)
390                                        : RotorHeadingLevelRule(2);
391       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
392     }
394     if ([key isEqualToString:@"AXHeadingLevel3SearchKey"]) {
395       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
396                                        ? RotorHeadingLevelRule(3, geckoRootAcc)
397                                        : RotorHeadingLevelRule(3);
398       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
399     }
401     if ([key isEqualToString:@"AXHeadingLevel4SearchKey"]) {
402       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
403                                        ? RotorHeadingLevelRule(4, geckoRootAcc)
404                                        : RotorHeadingLevelRule(4);
405       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
406     }
408     if ([key isEqualToString:@"AXHeadingLevel5SearchKey"]) {
409       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
410                                        ? RotorHeadingLevelRule(5, geckoRootAcc)
411                                        : RotorHeadingLevelRule(5);
412       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
413     }
415     if ([key isEqualToString:@"AXHeadingLevel6SearchKey"]) {
416       RotorHeadingLevelRule rule = mImmediateDescendantsOnly
417                                        ? RotorHeadingLevelRule(6, geckoRootAcc)
418                                        : RotorHeadingLevelRule(6);
419       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
420     }
422     if ([key isEqualToString:@"AXBlockquoteSearchKey"]) {
423       RotorRoleRule rule = mImmediateDescendantsOnly
424                                ? RotorRoleRule(roles::BLOCKQUOTE, geckoRootAcc)
425                                : RotorRoleRule(roles::BLOCKQUOTE);
426       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
427     }
429     if ([key isEqualToString:@"AXTextFieldSearchKey"]) {
430       RotorTextEntryRule rule = mImmediateDescendantsOnly
431                                     ? RotorTextEntryRule(geckoRootAcc)
432                                     : RotorTextEntryRule();
433       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
434     }
436     if ([key isEqualToString:@"AXLiveRegionSearchKey"]) {
437       RotorLiveRegionRule rule = mImmediateDescendantsOnly
438                                      ? RotorLiveRegionRule(geckoRootAcc)
439                                      : RotorLiveRegionRule();
440       [matches addObjectsFromArray:[self getMatchesForRule:rule]];
441     }
442   }
444   return matches;
447 - (void)dealloc {
448   [mSearchKeys release];
449   [super dealloc];
452 @end