1/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2/* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6#import "mozAccessible.h"
7
8#import "MacUtils.h"
9#import "mozView.h"
10
11#include "Accessible-inl.h"
12#include "nsAccUtils.h"
13#include "nsIAccessibleRelation.h"
14#include "nsIAccessibleEditableText.h"
15#include "nsIPersistentProperties2.h"
16#include "DocAccessibleParent.h"
17#include "Relation.h"
18#include "Role.h"
19#include "RootAccessible.h"
20#include "TableAccessible.h"
21#include "TableCellAccessible.h"
22#include "mozilla/a11y/PDocAccessible.h"
23#include "OuterDocAccessible.h"
24
25#include "mozilla/Services.h"
26#include "nsRect.h"
27#include "nsCocoaUtils.h"
28#include "nsCoord.h"
29#include "nsObjCExceptions.h"
30#include "nsWhitespaceTokenizer.h"
31#include <prdtoa.h>
32
33using namespace mozilla;
34using namespace mozilla::a11y;
35
36#define NSAccessibilityDOMIdentifierAttribute @"AXDOMIdentifier"
37#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand"
38#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex"
39#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator"
40#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator"
41#define NSAccessibilityMathBaseAttribute @"AXMathBase"
42#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript"
43#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
44#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
45#define NSAccessibilityMathOverAttribute @"AXMathOver"
46#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
47// XXX WebKit also defines the following attributes.
48// See bugs 1176970 and 1176983.
49// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
50// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
51// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
52// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
53
54// convert an array of Gecko accessibles to an NSArray of native accessibles
55static inline NSMutableArray*
56ConvertToNSArray(nsTArray<Accessible*>& aArray)
57{
58  NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
59
60  // iterate through the list, and get each native accessible.
61  size_t totalCount = aArray.Length();
62  for (size_t i = 0; i < totalCount; i++) {
63    Accessible* curAccessible = aArray.ElementAt(i);
64    mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
65    if (curNative)
66      [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
67  }
68
69  return nativeArray;
70}
71
72// convert an array of Gecko proxy accessibles to an NSArray of native accessibles
73static inline NSMutableArray*
74ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray)
75{
76  NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
77
78  // iterate through the list, and get each native accessible.
79  size_t totalCount = aArray.Length();
80  for (size_t i = 0; i < totalCount; i++) {
81    ProxyAccessible* curAccessible = aArray.ElementAt(i);
82    mozAccessible* curNative = GetNativeFromProxy(curAccessible);
83    if (curNative)
84      [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
85  }
86
87  return nativeArray;
88}
89
90#pragma mark -
91
92@implementation mozAccessible
93
94- (id)initWithAccessible:(uintptr_t)aGeckoAccessible
95{
96  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
97
98  if ((self = [super init])) {
99    mGeckoAccessible = aGeckoAccessible;
100    if (aGeckoAccessible & IS_PROXY)
101      mRole = [self getProxyAccessible]->Role();
102    else
103      mRole = [self getGeckoAccessible]->Role();
104  }
105
106  return self;
107
108  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
109}
110
111- (void)dealloc
112{
113  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
114
115  [mChildren release];
116  [super dealloc];
117
118  NS_OBJC_END_TRY_ABORT_BLOCK;
119}
120
121- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible
122{
123  // Check if mGeckoAccessible points at a proxy
124  if (mGeckoAccessible & IS_PROXY)
125    return nil;
126
127  return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible);
128}
129
130- (mozilla::a11y::ProxyAccessible*)getProxyAccessible
131{
132  // Check if mGeckoAccessible points at a proxy
133  if (!(mGeckoAccessible & IS_PROXY))
134    return nil;
135
136  return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY);
137}
138
139#pragma mark -
140
141- (BOOL)accessibilityIsIgnored
142{
143  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
144
145  // unknown (either unimplemented, or irrelevant) elements are marked as ignored
146  // as well as expired elements.
147
148  bool noRole = [[self role] isEqualToString:NSAccessibilityUnknownRole];
149  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
150    return (noRole && !(accWrap->InteractiveState() & states::FOCUSABLE));
151
152  if (ProxyAccessible* proxy = [self getProxyAccessible])
153    return (noRole && !(proxy->State() & states::FOCUSABLE));
154
155  return true;
156
157  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
158}
159
160- (NSArray*)additionalAccessibilityAttributeNames
161{
162  NSMutableArray* additional = [NSMutableArray array];
163  [additional addObject:NSAccessibilityDOMIdentifierAttribute];
164  switch (mRole) {
165    case roles::MATHML_ROOT:
166      [additional addObject:NSAccessibilityMathRootIndexAttribute];
167      [additional addObject:NSAccessibilityMathRootRadicandAttribute];
168      break;
169    case roles::MATHML_SQUARE_ROOT:
170      [additional addObject:NSAccessibilityMathRootRadicandAttribute];
171      break;
172    case roles::MATHML_FRACTION:
173      [additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
174      [additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
175      [additional addObject:NSAccessibilityMathLineThicknessAttribute];
176      break;
177    case roles::MATHML_SUB:
178    case roles::MATHML_SUP:
179    case roles::MATHML_SUB_SUP:
180      [additional addObject:NSAccessibilityMathBaseAttribute];
181      [additional addObject:NSAccessibilityMathSubscriptAttribute];
182      [additional addObject:NSAccessibilityMathSuperscriptAttribute];
183      break;
184    case roles::MATHML_UNDER:
185    case roles::MATHML_OVER:
186    case roles::MATHML_UNDER_OVER:
187      [additional addObject:NSAccessibilityMathBaseAttribute];
188      [additional addObject:NSAccessibilityMathUnderAttribute];
189      [additional addObject:NSAccessibilityMathOverAttribute];
190      break;
191    // XXX bug 1176983
192    // roles::MATHML_MULTISCRIPTS should also have the following attributes:
193    // - NSAccessibilityMathPrescriptsAttribute
194    // - NSAccessibilityMathPostscriptsAttribute
195    // XXX bug 1176970
196    // roles::MATHML_FENCED should also have the following attributes:
197    // - NSAccessibilityMathFencedOpenAttribute
198    // - NSAccessibilityMathFencedCloseAttribute
199    default:
200      break;
201  }
202
203  return additional;
204}
205
206- (NSArray*)accessibilityAttributeNames
207{
208  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
209
210  // if we're expired, we don't support any attributes.
211  AccessibleWrap* accWrap = [self getGeckoAccessible];
212  ProxyAccessible* proxy = [self getProxyAccessible];
213  if (!accWrap && !proxy)
214    return [NSArray array];
215
216  static NSArray* generalAttributes = nil;
217
218  if (!generalAttributes) {
219    // standard attributes that are shared and supported by all generic elements.
220    generalAttributes = [[NSArray alloc] initWithObjects:  NSAccessibilityChildrenAttribute,
221                                                           NSAccessibilityParentAttribute,
222                                                           NSAccessibilityRoleAttribute,
223                                                           NSAccessibilityTitleAttribute,
224                                                           NSAccessibilityValueAttribute,
225                                                           NSAccessibilitySubroleAttribute,
226                                                           NSAccessibilityRoleDescriptionAttribute,
227                                                           NSAccessibilityPositionAttribute,
228                                                           NSAccessibilityEnabledAttribute,
229                                                           NSAccessibilitySizeAttribute,
230                                                           NSAccessibilityWindowAttribute,
231                                                           NSAccessibilityFocusedAttribute,
232                                                           NSAccessibilityHelpAttribute,
233                                                           NSAccessibilityTitleUIElementAttribute,
234                                                           NSAccessibilityTopLevelUIElementAttribute,
235#if DEBUG
236                                                           @"AXMozDescription",
237#endif
238                                                           nil];
239  }
240
241  NSArray* objectAttributes = generalAttributes;
242
243  NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames];
244  if ([additionalAttributes count])
245    objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes];
246
247  return objectAttributes;
248
249  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
250}
251
252- (id)childAt:(uint32_t)i
253{
254  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
255
256  if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
257    Accessible* child = accWrap->GetChildAt(i);
258    return child ? GetNativeFromGeckoAccessible(child) : nil;
259  } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
260    ProxyAccessible* child = proxy->ChildAt(i);
261    return child ? GetNativeFromProxy(child) : nil;
262  }
263
264  return nil;
265
266  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
267}
268
269- (id)accessibilityAttributeValue:(NSString*)attribute
270{
271  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
272
273  AccessibleWrap* accWrap = [self getGeckoAccessible];
274  ProxyAccessible* proxy = [self getProxyAccessible];
275  if (!accWrap && !proxy)
276    return nil;
277
278#if DEBUG
279  if ([attribute isEqualToString:@"AXMozDescription"])
280    return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]];
281#endif
282
283  if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
284    return [self children];
285  if ([attribute isEqualToString:NSAccessibilityParentAttribute])
286    return [self parent];
287
288#ifdef DEBUG_hakan
289  NSLog (@"(%@ responding to attr %@)", self, attribute);
290#endif
291
292  if ([attribute isEqualToString:NSAccessibilityRoleAttribute])
293    return [self role];
294  if ([attribute isEqualToString:NSAccessibilityPositionAttribute])
295    return [self position];
296  if ([attribute isEqualToString:NSAccessibilitySubroleAttribute])
297    return [self subrole];
298  if ([attribute isEqualToString:NSAccessibilityEnabledAttribute])
299    return [NSNumber numberWithBool:[self isEnabled]];
300  if ([attribute isEqualToString:NSAccessibilityValueAttribute])
301    return [self value];
302  if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute])
303    return [self roleDescription];
304  if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
305    return [NSNumber numberWithBool:[self isFocused]];
306  if ([attribute isEqualToString:NSAccessibilitySizeAttribute])
307    return [self size];
308  if ([attribute isEqualToString:NSAccessibilityWindowAttribute])
309    return [self window];
310  if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute])
311    return [self window];
312  if ([attribute isEqualToString:NSAccessibilityTitleAttribute])
313    return [self title];
314  if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) {
315    if (accWrap) {
316      Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY);
317      Accessible* tempAcc = rel.Next();
318      return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil;
319    }
320    nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY);
321    ProxyAccessible* tempProxy = rel.SafeElementAt(0);
322    return tempProxy ? GetNativeFromProxy(tempProxy) : nil;
323  }
324  if ([attribute isEqualToString:NSAccessibilityHelpAttribute])
325    return [self help];
326  if ([attribute isEqualToString:NSAccessibilityOrientationAttribute])
327    return [self orientation];
328
329  if ([attribute isEqualToString:NSAccessibilityDOMIdentifierAttribute]) {
330    nsAutoString id;
331    if (accWrap)
332      nsCoreUtils::GetID(accWrap->GetContent(), id);
333    else
334      proxy->DOMNodeID(id);
335    return nsCocoaUtils::ToNSString(id);
336  }
337
338  switch (mRole) {
339  case roles::MATHML_ROOT:
340    if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
341      return [self childAt:0];
342    if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute])
343      return [self childAt:1];
344    break;
345  case roles::MATHML_SQUARE_ROOT:
346    if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute])
347      return [self childAt:0];
348    break;
349  case roles::MATHML_FRACTION:
350    if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute])
351      return [self childAt:0];
352    if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
353      return [self childAt:1];
354    if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) {
355      // WebKit sets line thickness to some logical value parsed in the
356      // renderer object of the <mfrac> element. It's not clear whether the
357      // exact value is relevant to assistive technologies. From a semantic
358      // point of view, the only important point is to distinguish between
359      // <mfrac> elements that have a fraction bar and those that do not.
360      // Per the MathML 3 spec, the latter happens iff the linethickness
361      // attribute is of the form [zero-float][optional-unit]. In that case we
362      // set line thickness to zero and in the other cases we set it to one.
363      nsAutoString thickness;
364      if (accWrap) {
365        nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes();
366        nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness);
367      } else {
368        AutoTArray<Attribute, 10> attrs;
369        proxy->Attributes(&attrs);
370        for (size_t i = 0 ; i < attrs.Length() ; i++) {
371          if (attrs.ElementAt(i).Name() == "thickness") {
372            thickness = attrs.ElementAt(i).Value();
373            break;
374          }
375        }
376      }
377      double value = 1.0;
378      if (!thickness.IsEmpty())
379        value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(),
380                          nullptr);
381      return [NSNumber numberWithInteger:(value ? 1 : 0)];
382    }
383    break;
384  case roles::MATHML_SUB:
385    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
386      return [self childAt:0];
387    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
388      return [self childAt:1];
389#ifdef DEBUG
390    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
391      return nil;
392#endif
393    break;
394  case roles::MATHML_SUP:
395    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
396      return [self childAt:0];
397#ifdef DEBUG
398    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
399      return nil;
400#endif
401    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
402      return [self childAt:1];
403    break;
404  case roles::MATHML_SUB_SUP:
405    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
406      return [self childAt:0];
407    if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute])
408      return [self childAt:1];
409    if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute])
410      return [self childAt:2];
411    break;
412  case roles::MATHML_UNDER:
413    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
414      return [self childAt:0];
415    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
416      return [self childAt:1];
417#ifdef DEBUG
418    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
419      return nil;
420#endif
421    break;
422  case roles::MATHML_OVER:
423    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
424      return [self childAt:0];
425#ifdef DEBUG
426    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
427      return nil;
428#endif
429    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
430      return [self childAt:1];
431    break;
432  case roles::MATHML_UNDER_OVER:
433    if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
434      return [self childAt:0];
435    if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute])
436      return [self childAt:1];
437    if ([attribute isEqualToString:NSAccessibilityMathOverAttribute])
438      return [self childAt:2];
439    break;
440  // XXX bug 1176983
441  // roles::MATHML_MULTISCRIPTS should also have the following attributes:
442  // - NSAccessibilityMathPrescriptsAttribute
443  // - NSAccessibilityMathPostscriptsAttribute
444  // XXX bug 1176970
445  // roles::MATHML_FENCED should also have the following attributes:
446  // - NSAccessibilityMathFencedOpenAttribute
447  // - NSAccessibilityMathFencedCloseAttribute
448  default:
449    break;
450  }
451
452#ifdef DEBUG
453 NSLog (@"!!! %@ can't respond to attribute %@", self, attribute);
454#endif
455  return nil;
456
457  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
458}
459
460- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute
461{
462  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
463
464  if ([attribute isEqualToString:NSAccessibilityFocusedAttribute])
465    return [self canBeFocused];
466
467  return NO;
468
469  NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
470}
471
472- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute
473{
474  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
475
476#ifdef DEBUG_hakan
477  NSLog (@"[%@] %@='%@'", self, attribute, value);
478#endif
479
480  // we only support focusing elements so far.
481  if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue])
482    [self focus];
483
484  NS_OBJC_END_TRY_ABORT_BLOCK;
485}
486
487- (id)accessibilityHitTest:(NSPoint)point
488{
489  AccessibleWrap* accWrap = [self getGeckoAccessible];
490  ProxyAccessible* proxy = [self getProxyAccessible];
491  if (!accWrap && !proxy)
492    return nil;
493
494  // Convert the given screen-global point in the cocoa coordinate system (with
495  // origin in the bottom-left corner of the screen) into point in the Gecko
496  // coordinate system (with origin in a top-left screen point).
497  NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
498  NSPoint tmpPoint = NSMakePoint(point.x,
499                                 [mainView frame].size.height - point.y);
500  LayoutDeviceIntPoint geckoPoint = nsCocoaUtils::
501    CocoaPointsToDevPixels(tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
502
503  mozAccessible* nativeChild = nil;
504  if (accWrap) {
505    Accessible* child = accWrap->ChildAtPoint(geckoPoint.x, geckoPoint.y,
506                                  Accessible::eDeepestChild);
507    if (child)
508      nativeChild = GetNativeFromGeckoAccessible(child);
509  } else if (proxy) {
510    ProxyAccessible* child = proxy->ChildAtPoint(geckoPoint.x, geckoPoint.y,
511                                  Accessible::eDeepestChild);
512    if (child)
513      nativeChild = GetNativeFromProxy(child);
514  }
515
516  if (nativeChild)
517    return nativeChild;
518
519  // if we didn't find anything, return ourself or child view.
520  return GetObjectOrRepresentedView(self);
521}
522
523- (NSArray*)accessibilityActionNames
524{
525  return nil;
526}
527
528- (NSString*)accessibilityActionDescription:(NSString*)action
529{
530  // by default we return whatever the MacOS API know about.
531  // if you have custom actions, override.
532  return NSAccessibilityActionDescription(action);
533}
534
535- (void)accessibilityPerformAction:(NSString*)action
536{
537}
538
539- (id)accessibilityFocusedUIElement
540{
541  AccessibleWrap* accWrap = [self getGeckoAccessible];
542  ProxyAccessible* proxy = [self getProxyAccessible];
543  if (!accWrap && !proxy)
544    return nil;
545
546  mozAccessible* focusedChild = nil;
547  if (accWrap) {
548    Accessible* focusedGeckoChild = accWrap->FocusedChild();
549    if (focusedGeckoChild)
550      focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
551  } else if (proxy) {
552    ProxyAccessible* focusedGeckoChild = proxy->FocusedChild();
553    if (focusedGeckoChild)
554      focusedChild = GetNativeFromProxy(focusedGeckoChild);
555  }
556
557  if (focusedChild)
558    return GetObjectOrRepresentedView(focusedChild);
559
560  // return ourself if we can't get a native focused child.
561  return GetObjectOrRepresentedView(self);
562}
563
564#pragma mark -
565
566- (id <mozAccessible>)parent
567{
568  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
569
570  id nativeParent = nil;
571  if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
572    Accessible* accessibleParent = accWrap->Parent();
573    if (accessibleParent)
574      nativeParent = GetNativeFromGeckoAccessible(accessibleParent);
575    if (nativeParent)
576      return GetObjectOrRepresentedView(nativeParent);
577
578    // Return native of root accessible if we have no direct parent
579    nativeParent = GetNativeFromGeckoAccessible(accWrap->RootAccessible());
580  } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
581    if (ProxyAccessible* proxyParent = proxy->Parent()) {
582      nativeParent = GetNativeFromProxy(proxyParent);
583    }
584
585    if (nativeParent)
586      return GetObjectOrRepresentedView(nativeParent);
587
588    Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
589    nativeParent = outerDoc ?
590      GetNativeFromGeckoAccessible(outerDoc) : nil;
591  } else {
592    return nil;
593  }
594
595  NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self);
596
597  return GetObjectOrRepresentedView(nativeParent);
598
599  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
600}
601
602- (BOOL)hasRepresentedView
603{
604  return NO;
605}
606
607- (id)representedView
608{
609  return nil;
610}
611
612- (BOOL)isRoot
613{
614  return NO;
615}
616
617// gets our native children lazily.
618// returns nil when there are no children.
619- (NSArray*)children
620{
621  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
622
623  if (mChildren)
624    return mChildren;
625
626  // get the array of children.
627  mChildren = [[NSMutableArray alloc] init];
628
629  AccessibleWrap* accWrap = [self getGeckoAccessible];
630  if (accWrap) {
631    uint32_t childCount = accWrap->ChildCount();
632    for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
633      mozAccessible* nativeChild = GetNativeFromGeckoAccessible(accWrap->GetChildAt(childIdx));
634      if (nativeChild)
635        [mChildren addObject:nativeChild];
636    }
637
638    // children from child if this is an outerdoc
639    OuterDocAccessible* docOwner = accWrap->AsOuterDoc();
640    if (docOwner) {
641      if (ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc()) {
642        mozAccessible* nativeRemoteChild = GetNativeFromProxy(proxyDoc);
643        [mChildren insertObject:nativeRemoteChild atIndex:0];
644        NSAssert1 (nativeRemoteChild, @"%@ found a child remote doc missing a native\n", self);
645      }
646    }
647  } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
648      uint32_t childCount = proxy->ChildrenCount();
649      for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
650        mozAccessible* nativeChild = GetNativeFromProxy(proxy->ChildAt(childIdx));
651        if (nativeChild)
652          [mChildren addObject:nativeChild];
653      }
654
655  }
656
657  return mChildren;
658
659  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
660}
661
662- (NSValue*)position
663{
664  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
665
666  nsIntRect rect;
667  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
668    rect = accWrap->Bounds();
669  else if (ProxyAccessible* proxy = [self getProxyAccessible])
670    rect = proxy->Bounds();
671  else
672    return nil;
673
674  NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
675  CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
676  NSPoint p = NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor,
677                         [mainView frame].size.height - static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
678
679  return [NSValue valueWithPoint:p];
680
681  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
682}
683
684- (NSValue*)size
685{
686  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
687
688  nsIntRect rect;
689  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
690    rect = accWrap->Bounds();
691  else if (ProxyAccessible* proxy = [self getProxyAccessible])
692    rect = proxy->Bounds();
693  else
694    return nil;
695
696  CGFloat scaleFactor =
697    nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
698  return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor,
699                                           static_cast<CGFloat>(rect.height) / scaleFactor)];
700
701  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
702}
703
704- (NSString*)role
705{
706  AccessibleWrap* accWrap = [self getGeckoAccessible];
707  if (accWrap) {
708    #ifdef DEBUG_A11Y
709      NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
710                   "Does not support Text when it should");
711    #endif
712  } else if (![self getProxyAccessible]) {
713    return nil;
714  }
715
716#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
717  case roles::geckoRole: \
718    return macRole;
719
720  switch (mRole) {
721#include "RoleMap.h"
722    default:
723      NS_NOTREACHED("Unknown role.");
724      return NSAccessibilityUnknownRole;
725  }
726
727#undef ROLE
728}
729
730- (NSString*)subrole
731{
732  AccessibleWrap* accWrap = [self getGeckoAccessible];
733  ProxyAccessible* proxy = [self getProxyAccessible];
734
735  // Deal with landmarks first
736  nsAtom* landmark = nullptr;
737  if (accWrap)
738    landmark = accWrap->LandmarkRole();
739  else if (proxy)
740    landmark = proxy->LandmarkRole();
741
742  // HTML Elements treated as landmarks
743  // XXX bug 1371712
744  if (landmark) {
745    if (landmark == nsGkAtoms::application)
746      return @"AXLandmarkApplication";
747    if (landmark == nsGkAtoms::banner)
748      return @"AXLandmarkBanner";
749    if (landmark == nsGkAtoms::complementary)
750      return @"AXLandmarkComplementary";
751    if (landmark == nsGkAtoms::contentinfo)
752      return @"AXLandmarkContentInfo";
753    if (landmark == nsGkAtoms::form)
754      return @"AXLandmarkForm";
755    if (landmark == nsGkAtoms::main)
756      return @"AXLandmarkMain";
757    if (landmark == nsGkAtoms::navigation)
758      return @"AXLandmarkNavigation";
759    if (landmark == nsGkAtoms::search)
760      return @"AXLandmarkSearch";
761    if (landmark == nsGkAtoms::searchbox)
762      return @"AXSearchField";
763  }
764
765  // macOS groups the specific landmark types of DPub ARIA into two broad
766  // categories with corresponding subroles: Navigation and region/container.
767  if (mRole == roles::NAVIGATION)
768    return @"AXLandmarkNavigation";
769  if (mRole == roles::LANDMARK)
770    return @"AXLandmarkRegion";
771
772  // Now, deal with widget roles
773  nsAtom* roleAtom = nullptr;
774  if (accWrap && accWrap->HasARIARole()) {
775    const nsRoleMapEntry* roleMap = accWrap->ARIARoleMap();
776    roleAtom = *roleMap->roleAtom;
777  }
778  if (proxy)
779    roleAtom = proxy->ARIARoleAtom();
780
781  if (roleAtom) {
782    if (roleAtom == nsGkAtoms::alert)
783      return @"AXApplicationAlert";
784    if (roleAtom == nsGkAtoms::alertdialog)
785      return @"AXApplicationAlertDialog";
786    if (roleAtom == nsGkAtoms::article)
787      return @"AXDocumentArticle";
788    if (roleAtom == nsGkAtoms::dialog)
789      return @"AXApplicationDialog";
790    if (roleAtom == nsGkAtoms::document)
791      return @"AXDocument";
792    if (roleAtom == nsGkAtoms::log_)
793      return @"AXApplicationLog";
794    if (roleAtom == nsGkAtoms::marquee)
795      return @"AXApplicationMarquee";
796    if (roleAtom == nsGkAtoms::math)
797      return @"AXDocumentMath";
798    if (roleAtom == nsGkAtoms::note_)
799      return @"AXDocumentNote";
800    if (roleAtom == nsGkAtoms::region)
801      return mRole == roles::REGION ? @"AXLandmarkRegion" : nil;
802    if (roleAtom == nsGkAtoms::status)
803      return @"AXApplicationStatus";
804    if (roleAtom == nsGkAtoms::tabpanel)
805      return @"AXTabPanel";
806    if (roleAtom == nsGkAtoms::timer)
807      return @"AXApplicationTimer";
808    if (roleAtom == nsGkAtoms::tooltip)
809      return @"AXUserInterfaceTooltip";
810  }
811
812  switch (mRole) {
813    case roles::LIST:
814      return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
815
816    case roles::ENTRY:
817      if ((accWrap && accWrap->IsSearchbox()) ||
818          (proxy && proxy->IsSearchbox()))
819        return @"AXSearchField";
820      break;
821
822    case roles::DEFINITION_LIST:
823      return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
824
825    case roles::TERM:
826      return @"AXTerm";
827
828    case roles::DEFINITION:
829      return @"AXDefinition";
830
831    case roles::MATHML_MATH:
832      return @"AXDocumentMath";
833
834    case roles::MATHML_FRACTION:
835      return @"AXMathFraction";
836
837    case roles::MATHML_FENCED:
838      // XXX bug 1176970
839      // This should be AXMathFence, but doing so without implementing the
840      // whole fence interface seems to make VoiceOver crash, so we present it
841      // as a row for now.
842      return @"AXMathRow";
843
844    case roles::MATHML_SUB:
845    case roles::MATHML_SUP:
846    case roles::MATHML_SUB_SUP:
847      return @"AXMathSubscriptSuperscript";
848
849    case roles::MATHML_ROW:
850    case roles::MATHML_STYLE:
851    case roles::MATHML_ERROR:
852      return @"AXMathRow";
853
854    case roles::MATHML_UNDER:
855    case roles::MATHML_OVER:
856    case roles::MATHML_UNDER_OVER:
857      return @"AXMathUnderOver";
858
859    case roles::MATHML_SQUARE_ROOT:
860      return @"AXMathSquareRoot";
861
862    case roles::MATHML_ROOT:
863      return @"AXMathRoot";
864
865    case roles::MATHML_TEXT:
866      return @"AXMathText";
867
868    case roles::MATHML_NUMBER:
869      return @"AXMathNumber";
870
871    case roles::MATHML_IDENTIFIER:
872      return @"AXMathIdentifier";
873
874    case roles::MATHML_TABLE:
875      return @"AXMathTable";
876
877    case roles::MATHML_TABLE_ROW:
878      return @"AXMathTableRow";
879
880    case roles::MATHML_CELL:
881      return @"AXMathTableCell";
882
883    // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and
884    // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and
885    // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they
886    // are only available from the MathML layout code. Hence we just fallback
887    // to subrole AXMathOperator for now.
888    // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced>
889    // which have subroles AXMathSeparatorOperator and AXMathFenceOperator.
890    case roles::MATHML_OPERATOR:
891      return @"AXMathOperator";
892
893    case roles::MATHML_MULTISCRIPTS:
894      return @"AXMathMultiscript";
895
896    case roles::SWITCH:
897      return @"AXSwitch";
898
899    case roles::ALERT:
900      return @"AXApplicationAlert";
901
902    case roles::PROPERTYPAGE:
903      return @"AXTabPanel";
904
905    case roles::DETAILS:
906      return @"AXDetails";
907
908    case roles::SUMMARY:
909      return @"AXSummary";
910
911    case roles::NOTE:
912      return @"AXDocumentNote";
913
914    case roles::OUTLINEITEM:
915      return @"AXOutlineRow";
916
917    case roles::ARTICLE:
918      return @"AXDocumentArticle";
919
920    case roles::NON_NATIVE_DOCUMENT:
921      return @"AXDocument";
922
923    // macOS added an AXSubrole value to distinguish generic AXGroup objects
924    // from those which are AXGroups as a result of an explicit ARIA role,
925    // such as the non-landmark, non-listitem text containers in DPub ARIA.
926    case roles::FOOTNOTE:
927    case roles::SECTION:
928      if (roleAtom)
929        return @"AXApplicationGroup";
930      break;
931
932    default:
933      break;
934  }
935
936  return nil;
937}
938
939struct RoleDescrMap
940{
941  NSString* role;
942  const nsString description;
943};
944
945static const RoleDescrMap sRoleDescrMap[] = {
946  { @"AXApplicationAlert", NS_LITERAL_STRING("alert") },
947  { @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") },
948  { @"AXApplicationLog", NS_LITERAL_STRING("log") },
949  { @"AXApplicationMarquee", NS_LITERAL_STRING("marquee") },
950  { @"AXApplicationStatus", NS_LITERAL_STRING("status") },
951  { @"AXApplicationTimer", NS_LITERAL_STRING("timer") },
952  { @"AXContentSeparator", NS_LITERAL_STRING("separator") },
953  { @"AXDefinition", NS_LITERAL_STRING("definition") },
954  { @"AXDocument", NS_LITERAL_STRING("document") },
955  { @"AXDocumentArticle", NS_LITERAL_STRING("article") },
956  { @"AXDocumentMath", NS_LITERAL_STRING("math") },
957  { @"AXDocumentNote", NS_LITERAL_STRING("note") },
958  { @"AXLandmarkApplication", NS_LITERAL_STRING("application") },
959  { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
960  { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
961  { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
962  { @"AXLandmarkMain", NS_LITERAL_STRING("main") },
963  { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
964  { @"AXLandmarkRegion", NS_LITERAL_STRING("region") },
965  { @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
966  { @"AXSearchField", NS_LITERAL_STRING("searchTextField") },
967  { @"AXTabPanel", NS_LITERAL_STRING("tabPanel") },
968  { @"AXTerm", NS_LITERAL_STRING("term") },
969  { @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") }
970};
971
972struct RoleDescrComparator
973{
974  const NSString* mRole;
975  explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
976  int operator()(const RoleDescrMap& aEntry) const {
977    return [mRole compare:aEntry.role];
978  }
979};
980
981- (NSString*)roleDescription
982{
983  if (mRole == roles::DOCUMENT)
984    return utils::LocalizedString(NS_LITERAL_STRING("htmlContent"));
985
986  if (mRole == roles::FIGURE)
987    return utils::LocalizedString(NS_LITERAL_STRING("figure"));
988
989  if (mRole == roles::HEADING)
990    return utils::LocalizedString(NS_LITERAL_STRING("heading"));
991
992  NSString* subrole = [self subrole];
993
994  if (subrole) {
995    size_t idx = 0;
996    if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
997                       RoleDescrComparator(subrole), &idx)) {
998      return utils::LocalizedString(sRoleDescrMap[idx].description);
999    }
1000  }
1001
1002  return NSAccessibilityRoleDescription([self role], subrole);
1003}
1004
1005- (NSString*)title
1006{
1007  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1008
1009  nsAutoString title;
1010  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1011    accWrap->Name(title);
1012  else if (ProxyAccessible* proxy = [self getProxyAccessible])
1013    proxy->Name(title);
1014
1015  return nsCocoaUtils::ToNSString(title);
1016
1017  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1018}
1019
1020- (id)value
1021{
1022  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1023
1024  nsAutoString value;
1025  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1026    accWrap->Value(value);
1027  else if (ProxyAccessible* proxy = [self getProxyAccessible])
1028    proxy->Value(value);
1029
1030  return nsCocoaUtils::ToNSString(value);
1031
1032  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1033}
1034
1035- (void)valueDidChange
1036{
1037  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1038
1039#ifdef DEBUG_hakan
1040  NSLog(@"%@'s value changed!", self);
1041#endif
1042  // sending out a notification is expensive, so we don't do it other than for really important objects,
1043  // like mozTextAccessible.
1044
1045  NS_OBJC_END_TRY_ABORT_BLOCK;
1046}
1047
1048- (void)selectedTextDidChange
1049{
1050  // Do nothing. mozTextAccessible will.
1051}
1052
1053- (void)documentLoadComplete
1054{
1055  id realSelf = GetObjectOrRepresentedView(self);
1056  NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification);
1057  NSAccessibilityPostNotification(realSelf, @"AXLoadComplete");
1058  NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete");
1059}
1060
1061- (NSString*)help
1062{
1063  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1064
1065  // What needs to go here is actually the accDescription of an item.
1066  // The MSAA acc_help method has nothing to do with this one.
1067  nsAutoString helpText;
1068  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1069    accWrap->Description(helpText);
1070  else if (ProxyAccessible* proxy = [self getProxyAccessible])
1071    proxy->Description(helpText);
1072
1073  return nsCocoaUtils::ToNSString(helpText);
1074
1075  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1076}
1077
1078- (NSString*)orientation
1079{
1080  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1081
1082  uint64_t state;
1083  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1084    state = accWrap->InteractiveState();
1085  else if (ProxyAccessible* proxy = [self getProxyAccessible])
1086    state = proxy->State();
1087  else
1088    state = 0;
1089
1090  if (state & states::HORIZONTAL)
1091    return NSAccessibilityHorizontalOrientationValue;
1092  if (state & states::VERTICAL)
1093    return NSAccessibilityVerticalOrientationValue;
1094
1095  return NSAccessibilityUnknownOrientationValue;
1096
1097  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1098}
1099
1100// objc-style description (from NSObject); not to be confused with the accessible description above.
1101- (NSString*)description
1102{
1103  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1104
1105  return [NSString stringWithFormat:@"(%p) %@", self, [self role]];
1106
1107  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1108}
1109
1110- (BOOL)isFocused
1111{
1112  if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
1113    return FocusMgr()->IsFocused(accWrap);
1114  }
1115
1116  return false; //XXX: proxy implementation is needed.
1117}
1118
1119- (BOOL)canBeFocused
1120{
1121  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1122      return accWrap->InteractiveState() & states::FOCUSABLE;
1123
1124  if (ProxyAccessible* proxy = [self getProxyAccessible])
1125    return proxy->State() & states::FOCUSABLE;
1126
1127  return false;
1128}
1129
1130- (BOOL)focus
1131{
1132  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1133    accWrap->TakeFocus();
1134  else if (ProxyAccessible* proxy = [self getProxyAccessible])
1135    proxy->TakeFocus();
1136  else
1137    return NO;
1138
1139  return YES;
1140}
1141
1142- (BOOL)isEnabled
1143{
1144  if (AccessibleWrap* accWrap = [self getGeckoAccessible])
1145    return ((accWrap->InteractiveState() & states::UNAVAILABLE) == 0);
1146
1147  if (ProxyAccessible* proxy = [self getProxyAccessible])
1148    return ((proxy->State() & states::UNAVAILABLE) == 0);
1149
1150  return false;
1151}
1152
1153// The root accessible calls this when the focused node was
1154// changed to us.
1155- (void)didReceiveFocus
1156{
1157  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1158
1159#ifdef DEBUG_hakan
1160  NSLog (@"%@ received focus!", self);
1161#endif
1162  NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
1163                                  NSAccessibilityFocusedUIElementChangedNotification);
1164
1165  NS_OBJC_END_TRY_ABORT_BLOCK;
1166}
1167
1168- (NSWindow*)window
1169{
1170  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
1171
1172  // Get a pointer to the native window (NSWindow) we reside in.
1173  NSWindow *nativeWindow = nil;
1174  DocAccessible* docAcc = nullptr;
1175  if (AccessibleWrap* accWrap = [self getGeckoAccessible]) {
1176    docAcc = accWrap->Document();
1177  } else if (ProxyAccessible* proxy = [self getProxyAccessible]) {
1178    Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
1179    if (outerDoc)
1180      docAcc = outerDoc->Document();
1181  }
1182
1183  if (docAcc)
1184    nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
1185
1186  NSAssert1(nativeWindow, @"Could not get native window for %@", self);
1187  return nativeWindow;
1188
1189  NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
1190}
1191
1192- (void)invalidateChildren
1193{
1194  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1195
1196  // make room for new children
1197  [mChildren release];
1198  mChildren = nil;
1199
1200  NS_OBJC_END_TRY_ABORT_BLOCK;
1201}
1202
1203- (void)appendChild:(Accessible*)aAccessible
1204{
1205  // if mChildren is nil, then we don't even need to bother
1206  if (!mChildren)
1207    return;
1208
1209  mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible);
1210  if (curNative)
1211    [mChildren addObject:curNative];
1212}
1213
1214- (void)expire
1215{
1216  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1217
1218  [self invalidateChildren];
1219
1220  mGeckoAccessible = 0;
1221
1222  NS_OBJC_END_TRY_ABORT_BLOCK;
1223}
1224
1225- (BOOL)isExpired
1226{
1227  return ![self getGeckoAccessible] && ![self getProxyAccessible];
1228}
1229
1230#pragma mark -
1231#pragma mark Debug methods
1232#pragma mark -
1233
1234#ifdef DEBUG
1235
1236// will check that our children actually reference us as their
1237// parent.
1238- (void)sanityCheckChildren:(NSArray *)children
1239{
1240  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1241
1242  NSEnumerator *iter = [children objectEnumerator];
1243  mozAccessible *curObj = nil;
1244
1245  NSLog(@"sanity checking %@", self);
1246
1247  while ((curObj = [iter nextObject])) {
1248    id realSelf = GetObjectOrRepresentedView(self);
1249    NSLog(@"checking %@", realSelf);
1250    NSAssert2([curObj parent] == realSelf,
1251              @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf);
1252  }
1253
1254  NS_OBJC_END_TRY_ABORT_BLOCK;
1255}
1256
1257- (void)sanityCheckChildren
1258{
1259  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1260
1261  [self sanityCheckChildren:[self children]];
1262
1263  NS_OBJC_END_TRY_ABORT_BLOCK;
1264}
1265
1266- (void)printHierarchy
1267{
1268  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1269
1270  [self printHierarchyWithLevel:0];
1271
1272  NS_OBJC_END_TRY_ABORT_BLOCK;
1273}
1274
1275- (void)printHierarchyWithLevel:(unsigned)level
1276{
1277  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1278
1279  NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!");
1280
1281  // print this node
1282  NSMutableString *indent = [NSMutableString stringWithCapacity:level];
1283  unsigned i=0;
1284  for (;i<level;i++)
1285    [indent appendString:@" "];
1286
1287  NSLog (@"%@(#%i) %@", indent, level, self);
1288
1289  // use |children| method to make sure our children are lazily fetched first.
1290  NSArray *children = [self children];
1291  if (!children)
1292    return;
1293
1294  [self sanityCheckChildren];
1295
1296  NSEnumerator *iter = [children objectEnumerator];
1297  mozAccessible *object = nil;
1298
1299  while (iter && (object = [iter nextObject]))
1300    // print every child node's subtree, increasing the indenting
1301    // by two for every level.
1302    [object printHierarchyWithLevel:(level+1)];
1303
1304  NS_OBJC_END_TRY_ABORT_BLOCK;
1305}
1306
1307#endif /* DEBUG */
1308
1309@end
1310