Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/generic/Accessible.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: 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
#include "Accessible-inl.h"
7
8
#include "nsIXBLAccessible.h"
9
10
#include "EmbeddedObjCollector.h"
11
#include "AccGroupInfo.h"
12
#include "AccIterator.h"
13
#include "nsAccUtils.h"
14
#include "nsAccessibilityService.h"
15
#include "ApplicationAccessible.h"
16
#include "nsAccessiblePivot.h"
17
#include "nsGenericHTMLElement.h"
18
#include "NotificationController.h"
19
#include "nsEventShell.h"
20
#include "nsTextEquivUtils.h"
21
#include "DocAccessibleChild.h"
22
#include "EventTree.h"
23
#include "GeckoProfiler.h"
24
#include "Relation.h"
25
#include "Role.h"
26
#include "RootAccessible.h"
27
#include "States.h"
28
#include "StyleInfo.h"
29
#include "TableAccessible.h"
30
#include "TableCellAccessible.h"
31
#include "TreeWalker.h"
32
#include "XULDocument.h"
33
34
#include "nsIDOMXULButtonElement.h"
35
#include "nsIDOMXULSelectCntrlEl.h"
36
#include "nsIDOMXULSelectCntrlItemEl.h"
37
#include "nsINodeList.h"
38
#include "nsPIDOMWindow.h"
39
40
#include "nsIDocument.h"
41
#include "nsIContent.h"
42
#include "nsIForm.h"
43
#include "nsIFormControl.h"
44
45
#include "nsDeckFrame.h"
46
#include "nsLayoutUtils.h"
47
#include "nsIPresShell.h"
48
#include "nsIStringBundle.h"
49
#include "nsPresContext.h"
50
#include "nsIFrame.h"
51
#include "nsView.h"
52
#include "nsIDocShellTreeItem.h"
53
#include "nsIScrollableFrame.h"
54
#include "nsFocusManager.h"
55
56
#include "nsString.h"
57
#include "nsUnicharUtils.h"
58
#include "nsReadableUtils.h"
59
#include "prdtoa.h"
60
#include "nsAtom.h"
61
#include "nsIURI.h"
62
#include "nsArrayUtils.h"
63
#include "nsIMutableArray.h"
64
#include "nsIObserverService.h"
65
#include "nsIServiceManager.h"
66
#include "nsWhitespaceTokenizer.h"
67
#include "nsAttrName.h"
68
#include "nsPersistentProperties.h"
69
70
#include "mozilla/Assertions.h"
71
#include "mozilla/BasicEvents.h"
72
#include "mozilla/ErrorResult.h"
73
#include "mozilla/EventStateManager.h"
74
#include "mozilla/EventStates.h"
75
#include "mozilla/FloatingPoint.h"
76
#include "mozilla/MouseEvents.h"
77
#include "mozilla/Unused.h"
78
#include "mozilla/Preferences.h"
79
#include "mozilla/dom/CanvasRenderingContext2D.h"
80
#include "mozilla/dom/Element.h"
81
#include "mozilla/dom/HTMLCanvasElement.h"
82
#include "mozilla/dom/HTMLBodyElement.h"
83
#include "mozilla/dom/KeyboardEventBinding.h"
84
#include "mozilla/dom/TreeWalker.h"
85
86
using namespace mozilla;
87
using namespace mozilla::a11y;
88
89
90
////////////////////////////////////////////////////////////////////////////////
91
// Accessible: nsISupports and cycle collection
92
93
NS_IMPL_CYCLE_COLLECTION_CLASS(Accessible)
94
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Accessible)
95
0
  tmp->Shutdown();
96
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
97
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Accessible)
98
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
99
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
100
101
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible)
102
0
  NS_INTERFACE_MAP_ENTRY_CONCRETE(Accessible)
103
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, Accessible)
104
0
NS_INTERFACE_MAP_END
105
106
NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible)
107
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease())
108
109
Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) :
110
  mContent(aContent), mDoc(aDoc),
111
  mParent(nullptr), mIndexInParent(-1),
112
  mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX),
113
  mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0),
114
  mReorderEventTarget(false), mShowEventTarget(false), mHideEventTarget(false)
115
0
{
116
0
  mBits.groupInfo = nullptr;
117
0
  mInt.mIndexOfEmbeddedChild = -1;
118
0
119
0
  // Assign an ID to this Accessible for use in UniqueID().
120
0
  recordreplay::RegisterThing(this);
121
0
}
122
123
Accessible::~Accessible()
124
0
{
125
0
  NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
126
0
127
0
  recordreplay::UnregisterThing(this);
128
0
}
129
130
ENameValueFlag
131
Accessible::Name(nsString& aName) const
132
0
{
133
0
  aName.Truncate();
134
0
135
0
  if (!HasOwnContent())
136
0
    return eNameOK;
137
0
138
0
  ARIAName(aName);
139
0
  if (!aName.IsEmpty())
140
0
    return eNameOK;
141
0
142
0
  nsCOMPtr<nsIXBLAccessible> xblAccessible(do_QueryInterface(mContent));
143
0
  if (xblAccessible) {
144
0
    xblAccessible->GetAccessibleName(aName);
145
0
    if (!aName.IsEmpty())
146
0
      return eNameOK;
147
0
  }
148
0
149
0
  ENameValueFlag nameFlag = NativeName(aName);
150
0
  if (!aName.IsEmpty())
151
0
    return nameFlag;
152
0
153
0
  // In the end get the name from tooltip.
154
0
  if (mContent->IsHTMLElement()) {
155
0
    if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) {
156
0
      aName.CompressWhitespace();
157
0
      return eNameFromTooltip;
158
0
    }
159
0
  } else if (mContent->IsXULElement()) {
160
0
    if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
161
0
      aName.CompressWhitespace();
162
0
      return eNameFromTooltip;
163
0
    }
164
0
  } else if (mContent->IsSVGElement()) {
165
0
    // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
166
0
    // for processing, the user agent shall choose the first one.
167
0
    for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
168
0
         childElm = childElm->GetNextSibling()) {
169
0
      if (childElm->IsSVGElement(nsGkAtoms::desc)) {
170
0
        nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
171
0
        return eNameFromTooltip;
172
0
      }
173
0
    }
174
0
  }
175
0
176
0
  if (nameFlag != eNoNameOnPurpose)
177
0
    aName.SetIsVoid(true);
178
0
179
0
  return nameFlag;
180
0
}
181
182
void
183
Accessible::Description(nsString& aDescription)
184
0
{
185
0
  // There are 4 conditions that make an accessible have no accDescription:
186
0
  // 1. it's a text node; or
187
0
  // 2. It has no DHTML describedby property
188
0
  // 3. it doesn't have an accName; or
189
0
  // 4. its title attribute already equals to its accName nsAutoString name;
190
0
191
0
  if (!HasOwnContent() || mContent->IsText())
192
0
    return;
193
0
194
0
  nsTextEquivUtils::
195
0
    GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
196
0
                           aDescription);
197
0
198
0
  if (aDescription.IsEmpty()) {
199
0
    NativeDescription(aDescription);
200
0
201
0
    if (aDescription.IsEmpty()) {
202
0
      // Keep the Name() method logic.
203
0
      if (mContent->IsHTMLElement()) {
204
0
        mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aDescription);
205
0
      } else if (mContent->IsXULElement()) {
206
0
        mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aDescription);
207
0
      } else if (mContent->IsSVGElement()) {
208
0
        for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
209
0
             childElm = childElm->GetNextSibling()) {
210
0
          if (childElm->IsSVGElement(nsGkAtoms::desc)) {
211
0
            nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
212
0
                                                         &aDescription);
213
0
            break;
214
0
          }
215
0
        }
216
0
      }
217
0
    }
218
0
  }
219
0
220
0
  if (!aDescription.IsEmpty()) {
221
0
    aDescription.CompressWhitespace();
222
0
    nsAutoString name;
223
0
    Name(name);
224
0
    // Don't expose a description if it is the same as the name.
225
0
    if (aDescription.Equals(name))
226
0
      aDescription.Truncate();
227
0
  }
228
0
}
229
230
KeyBinding
231
Accessible::AccessKey() const
232
0
{
233
0
  if (!HasOwnContent())
234
0
    return KeyBinding();
235
0
236
0
  uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
237
0
  if (!key && mContent->IsElement()) {
238
0
    Accessible* label = nullptr;
239
0
240
0
    // Copy access key from label node.
241
0
    if (mContent->IsHTMLElement()) {
242
0
      // Unless it is labeled via an ancestor <label>, in which case that would
243
0
      // be redundant.
244
0
      HTMLLabelIterator iter(Document(), this,
245
0
                             HTMLLabelIterator::eSkipAncestorLabel);
246
0
      label = iter.Next();
247
0
248
0
    } else if (mContent->IsXULElement()) {
249
0
      XULLabelIterator iter(Document(), mContent);
250
0
      label = iter.Next();
251
0
    }
252
0
253
0
    if (label)
254
0
      key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
255
0
  }
256
0
257
0
  if (!key)
258
0
    return KeyBinding();
259
0
260
0
  // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
261
0
  switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) {
262
0
  case -1:
263
0
    break;
264
0
  case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
265
0
    return KeyBinding(key, KeyBinding::kShift);
266
0
  case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
267
0
    return KeyBinding(key, KeyBinding::kControl);
268
0
  case dom::KeyboardEvent_Binding::DOM_VK_ALT:
269
0
    return KeyBinding(key, KeyBinding::kAlt);
270
0
  case dom::KeyboardEvent_Binding::DOM_VK_META:
271
0
    return KeyBinding(key, KeyBinding::kMeta);
272
0
  default:
273
0
    return KeyBinding();
274
0
  }
275
0
276
0
  // Determine the access modifier used in this context.
277
0
  nsIDocument* document = mContent->GetUncomposedDoc();
278
0
  if (!document)
279
0
    return KeyBinding();
280
0
281
0
  nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
282
0
  if (!treeItem)
283
0
    return KeyBinding();
284
0
285
0
  nsresult rv = NS_ERROR_FAILURE;
286
0
  int32_t modifierMask = 0;
287
0
  switch (treeItem->ItemType()) {
288
0
    case nsIDocShellTreeItem::typeChrome:
289
0
      rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask);
290
0
      break;
291
0
    case nsIDocShellTreeItem::typeContent:
292
0
      rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask);
293
0
      break;
294
0
  }
295
0
296
0
  return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
297
0
}
298
299
KeyBinding
300
Accessible::KeyboardShortcut() const
301
0
{
302
0
  return KeyBinding();
303
0
}
304
305
void
306
Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
307
0
{
308
0
  nsCOMPtr<nsIStringBundleService> stringBundleService =
309
0
    services::GetStringBundleService();
310
0
  if (!stringBundleService)
311
0
    return;
312
0
313
0
  nsCOMPtr<nsIStringBundle> stringBundle;
314
0
  stringBundleService->CreateBundle(
315
0
    "chrome://global-platform/locale/accessible.properties",
316
0
    getter_AddRefs(stringBundle));
317
0
  if (!stringBundle)
318
0
    return;
319
0
320
0
  nsAutoString xsValue;
321
0
  nsresult rv =
322
0
    stringBundle->GetStringFromName(NS_ConvertUTF16toUTF8(aKey).get(), xsValue);
323
0
  if (NS_SUCCEEDED(rv))
324
0
    aStringOut.Assign(xsValue);
325
0
}
326
327
uint64_t
328
Accessible::VisibilityState() const
329
0
{
330
0
  nsIFrame* frame = GetFrame();
331
0
  if (!frame) {
332
0
    // Element having display:contents is considered visible semantically,
333
0
    // despite it doesn't have a visually visible box.
334
0
    if (mContent->IsElement() && mContent->AsElement()->IsDisplayContents()) {
335
0
      return states::OFFSCREEN;
336
0
    }
337
0
    return states::INVISIBLE;
338
0
  }
339
0
340
0
  // Walk the parent frame chain to see if there's invisible parent or the frame
341
0
  // is in background tab.
342
0
  if (!frame->StyleVisibility()->IsVisible())
343
0
    return states::INVISIBLE;
344
0
345
0
  // Offscreen state if the document's visibility state is not visible.
346
0
  if (Document()->IsHidden())
347
0
    return states::OFFSCREEN;
348
0
349
0
  nsIFrame* curFrame = frame;
350
0
  do {
351
0
    nsView* view = curFrame->GetView();
352
0
    if (view && view->GetVisibility() == nsViewVisibility_kHide)
353
0
      return states::INVISIBLE;
354
0
355
0
    if (nsLayoutUtils::IsPopup(curFrame))
356
0
      return 0;
357
0
358
0
    // Offscreen state for background tab content and invisible for not selected
359
0
    // deck panel.
360
0
    nsIFrame* parentFrame = curFrame->GetParent();
361
0
    nsDeckFrame* deckFrame = do_QueryFrame(parentFrame);
362
0
    if (deckFrame && deckFrame->GetSelectedBox() != curFrame) {
363
0
      if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels))
364
0
        return states::OFFSCREEN;
365
0
366
0
      MOZ_ASSERT_UNREACHABLE("Children of not selected deck panel are not accessible.");
367
0
      return states::INVISIBLE;
368
0
    }
369
0
370
0
    // If contained by scrollable frame then check that at least 12 pixels
371
0
    // around the object is visible, otherwise the object is offscreen.
372
0
    nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame);
373
0
    if (scrollableFrame) {
374
0
      nsRect scrollPortRect = scrollableFrame->GetScrollPortRect();
375
0
      nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
376
0
        frame, frame->GetRectRelativeToSelf(), parentFrame);
377
0
      if (!scrollPortRect.Contains(frameRect)) {
378
0
        const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
379
0
        scrollPortRect.Deflate(kMinPixels, kMinPixels);
380
0
        if (!scrollPortRect.Intersects(frameRect))
381
0
          return states::OFFSCREEN;
382
0
      }
383
0
    }
384
0
385
0
    if (!parentFrame) {
386
0
      parentFrame = nsLayoutUtils::GetCrossDocParentFrame(curFrame);
387
0
      if (parentFrame && !parentFrame->StyleVisibility()->IsVisible())
388
0
        return states::INVISIBLE;
389
0
    }
390
0
391
0
    curFrame = parentFrame;
392
0
  } while (curFrame);
393
0
394
0
  // Zero area rects can occur in the first frame of a multi-frame text flow,
395
0
  // in which case the rendered text is not empty and the frame should not be
396
0
  // marked invisible.
397
0
  // XXX Can we just remove this check? Why do we need to mark empty
398
0
  // text invisible?
399
0
  if (frame->IsTextFrame() &&
400
0
      !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
401
0
      frame->GetRect().IsEmpty()) {
402
0
    nsIFrame::RenderedText text = frame->GetRenderedText(0,
403
0
        UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
404
0
        nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
405
0
    if (text.mString.IsEmpty()) {
406
0
      return states::INVISIBLE;
407
0
    }
408
0
  }
409
0
410
0
  return 0;
411
0
}
412
413
uint64_t
414
Accessible::NativeState() const
415
0
{
416
0
  uint64_t state = 0;
417
0
418
0
  if (!IsInDocument())
419
0
    state |= states::STALE;
420
0
421
0
  if (HasOwnContent() && mContent->IsElement()) {
422
0
    EventStates elementState = mContent->AsElement()->State();
423
0
424
0
    if (elementState.HasState(NS_EVENT_STATE_INVALID))
425
0
      state |= states::INVALID;
426
0
427
0
    if (elementState.HasState(NS_EVENT_STATE_REQUIRED))
428
0
      state |= states::REQUIRED;
429
0
430
0
    state |= NativeInteractiveState();
431
0
    if (FocusMgr()->IsFocused(this))
432
0
      state |= states::FOCUSED;
433
0
  }
434
0
435
0
  // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
436
0
  state |= VisibilityState();
437
0
438
0
  nsIFrame *frame = GetFrame();
439
0
  if (frame) {
440
0
    if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW)
441
0
      state |= states::FLOATING;
442
0
443
0
    // XXX we should look at layout for non XUL box frames, but need to decide
444
0
    // how that interacts with ARIA.
445
0
    if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) {
446
0
      const nsStyleXUL* xulStyle = frame->StyleXUL();
447
0
      if (xulStyle && frame->IsXULBoxFrame()) {
448
0
        // In XUL all boxes are either vertical or horizontal
449
0
        if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical)
450
0
          state |= states::VERTICAL;
451
0
        else
452
0
          state |= states::HORIZONTAL;
453
0
      }
454
0
    }
455
0
  }
456
0
457
0
  // Check if a XUL element has the popup attribute (an attached popup menu).
458
0
  if (HasOwnContent() && mContent->IsXULElement() &&
459
0
      mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
460
0
    state |= states::HASPOPUP;
461
0
462
0
  // Bypass the link states specialization for non links.
463
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
464
0
  if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
465
0
      roleMapEntry->role == roles::LINK)
466
0
    state |= NativeLinkState();
467
0
468
0
  return state;
469
0
}
470
471
uint64_t
472
Accessible::NativeInteractiveState() const
473
0
{
474
0
  if (!mContent->IsElement())
475
0
    return 0;
476
0
477
0
  if (NativelyUnavailable())
478
0
    return states::UNAVAILABLE;
479
0
480
0
  nsIFrame* frame = GetFrame();
481
0
  if (frame && frame->IsFocusable())
482
0
    return states::FOCUSABLE;
483
0
484
0
  return 0;
485
0
}
486
487
uint64_t
488
Accessible::NativeLinkState() const
489
0
{
490
0
  return 0;
491
0
}
492
493
bool
494
Accessible::NativelyUnavailable() const
495
0
{
496
0
  if (mContent->IsHTMLElement())
497
0
    return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED);
498
0
499
0
  return mContent->IsElement() &&
500
0
    mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
501
0
                                       nsGkAtoms::_true, eCaseMatters);
502
0
}
503
504
Accessible*
505
Accessible::FocusedChild()
506
0
{
507
0
  Accessible* focus = FocusMgr()->FocusedAccessible();
508
0
  if (focus && (focus == this || focus->Parent() == this))
509
0
    return focus;
510
0
511
0
  return nullptr;
512
0
}
513
514
Accessible*
515
Accessible::ChildAtPoint(int32_t aX, int32_t aY,
516
                         EWhichChildAtPoint aWhichChild)
517
0
{
518
0
  // If we can't find the point in a child, we will return the fallback answer:
519
0
  // we return |this| if the point is within it, otherwise nullptr.
520
0
  Accessible* fallbackAnswer = nullptr;
521
0
  nsIntRect rect = Bounds();
522
0
  if (rect.Contains(aX, aY))
523
0
    fallbackAnswer = this;
524
0
525
0
  if (nsAccUtils::MustPrune(this))  // Do not dig any further
526
0
    return fallbackAnswer;
527
0
528
0
  // Search an accessible at the given point starting from accessible document
529
0
  // because containing block (see CSS2) for out of flow element (for example,
530
0
  // absolutely positioned element) may be different from its DOM parent and
531
0
  // therefore accessible for containing block may be different from accessible
532
0
  // for DOM parent but GetFrameForPoint() should be called for containing block
533
0
  // to get an out of flow element.
534
0
  DocAccessible* accDocument = Document();
535
0
  NS_ENSURE_TRUE(accDocument, nullptr);
536
0
537
0
  nsIFrame* rootFrame = accDocument->GetFrame();
538
0
  NS_ENSURE_TRUE(rootFrame, nullptr);
539
0
540
0
  nsIFrame* startFrame = rootFrame;
541
0
542
0
  // Check whether the point is at popup content.
543
0
  nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
544
0
  NS_ENSURE_TRUE(rootWidget, nullptr);
545
0
546
0
  LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();
547
0
548
0
  WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
549
0
                              WidgetMouseEvent::eSynthesized);
550
0
  dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());
551
0
552
0
  nsIFrame* popupFrame = nsLayoutUtils::
553
0
    GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(),
554
0
                                     &dummyEvent);
555
0
  if (popupFrame) {
556
0
    // If 'this' accessible is not inside the popup then ignore the popup when
557
0
    // searching an accessible at point.
558
0
    DocAccessible* popupDoc =
559
0
      GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
560
0
    Accessible* popupAcc =
561
0
      popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
562
0
    Accessible* popupChild = this;
563
0
    while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc)
564
0
      popupChild = popupChild->Parent();
565
0
566
0
    if (popupChild == popupAcc)
567
0
      startFrame = popupFrame;
568
0
  }
569
0
570
0
  nsPresContext* presContext = startFrame->PresContext();
571
0
  nsRect screenRect = startFrame->GetScreenRectInAppUnits();
572
0
  nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
573
0
                 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());
574
0
575
0
  // We need to take into account a non-1 resolution set on the presshell.
576
0
  // This happens in mobile platforms with async pinch zooming.
577
0
  offset = offset.RemoveResolution(presContext->PresShell()->GetResolution());
578
0
579
0
  nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset);
580
0
581
0
  nsIContent* content = nullptr;
582
0
  if (!foundFrame || !(content = foundFrame->GetContent()))
583
0
    return fallbackAnswer;
584
0
585
0
  // Get accessible for the node with the point or the first accessible in
586
0
  // the DOM parent chain.
587
0
  DocAccessible* contentDocAcc = GetAccService()->
588
0
    GetDocAccessible(content->OwnerDoc());
589
0
590
0
  // contentDocAcc in some circumstances can be nullptr. See bug 729861
591
0
  NS_ASSERTION(contentDocAcc, "could not get the document accessible");
592
0
  if (!contentDocAcc)
593
0
    return fallbackAnswer;
594
0
595
0
  Accessible* accessible = contentDocAcc->GetAccessibleOrContainer(content);
596
0
  if (!accessible)
597
0
    return fallbackAnswer;
598
0
599
0
  // Hurray! We have an accessible for the frame that layout gave us.
600
0
  // Since DOM node of obtained accessible may be out of flow then we should
601
0
  // ensure obtained accessible is a child of this accessible.
602
0
  Accessible* child = accessible;
603
0
  while (child != this) {
604
0
    Accessible* parent = child->Parent();
605
0
    if (!parent) {
606
0
      // Reached the top of the hierarchy. These bounds were inside an
607
0
      // accessible that is not a descendant of this one.
608
0
      return fallbackAnswer;
609
0
    }
610
0
611
0
    // If we landed on a legitimate child of |this|, and we want the direct
612
0
    // child, return it here.
613
0
    if (parent == this && aWhichChild == eDirectChild)
614
0
        return child;
615
0
616
0
    child = parent;
617
0
  }
618
0
619
0
  // Manually walk through accessible children and see if the are within this
620
0
  // point. Skip offscreen or invisible accessibles. This takes care of cases
621
0
  // where layout won't walk into things for us, such as image map areas and
622
0
  // sub documents (XXX: subdocuments should be handled by methods of
623
0
  // OuterDocAccessibles).
624
0
  uint32_t childCount = accessible->ChildCount();
625
0
  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
626
0
    Accessible* child = accessible->GetChildAt(childIdx);
627
0
628
0
    nsIntRect childRect = child->Bounds();
629
0
    if (childRect.Contains(aX, aY) &&
630
0
        (child->State() & states::INVISIBLE) == 0) {
631
0
632
0
      if (aWhichChild == eDeepestChild)
633
0
        return child->ChildAtPoint(aX, aY, eDeepestChild);
634
0
635
0
      return child;
636
0
    }
637
0
  }
638
0
639
0
  return accessible;
640
0
}
641
642
nsRect
643
Accessible::RelativeBounds(nsIFrame** aBoundingFrame) const
644
0
{
645
0
  nsIFrame* frame = GetFrame();
646
0
  if (frame && mContent) {
647
0
    bool* pHasHitRegionRect = static_cast<bool*>(mContent->GetProperty(nsGkAtoms::hitregion));
648
0
    MOZ_ASSERT(pHasHitRegionRect == nullptr ||
649
0
               *pHasHitRegionRect, "hitregion property is always null or true");
650
0
    bool hasHitRegionRect = pHasHitRegionRect != nullptr && *pHasHitRegionRect;
651
0
652
0
    if (hasHitRegionRect && mContent->IsElement()) {
653
0
      // This is for canvas fallback content
654
0
      // Find a canvas frame the found hit region is relative to.
655
0
      nsIFrame* canvasFrame = frame->GetParent();
656
0
      if (canvasFrame) {
657
0
        canvasFrame = nsLayoutUtils::GetClosestFrameOfType(
658
0
          canvasFrame, LayoutFrameType::HTMLCanvas);
659
0
      }
660
0
661
0
      // make the canvas the bounding frame
662
0
      if (canvasFrame) {
663
0
        *aBoundingFrame = canvasFrame;
664
0
        dom::HTMLCanvasElement *canvas =
665
0
          dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent());
666
0
667
0
        // get the bounding rect of the hit region
668
0
        nsRect bounds;
669
0
        if (canvas && canvas->CountContexts() &&
670
0
          canvas->GetContextAtIndex(0)->GetHitRegionRect(mContent->AsElement(), bounds)) {
671
0
          return bounds;
672
0
        }
673
0
      }
674
0
    }
675
0
676
0
    *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
677
0
    return nsLayoutUtils::
678
0
      GetAllInFlowRectsUnion(frame, *aBoundingFrame,
679
0
                             nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS);
680
0
  }
681
0
682
0
  return nsRect();
683
0
}
684
685
nsRect
686
Accessible::BoundsInAppUnits() const
687
0
{
688
0
  nsIFrame* boundingFrame = nullptr;
689
0
  nsRect unionRectTwips = RelativeBounds(&boundingFrame);
690
0
  if (!boundingFrame) {
691
0
    return nsRect();
692
0
  }
693
0
694
0
  // We need to take into account a non-1 resolution set on the presshell.
695
0
  // This happens in mobile platforms with async pinch zooming. Here we
696
0
  // scale the bounds before adding the screen-relative offset.
697
0
  unionRectTwips.ScaleRoundOut(mDoc->PresContext()->PresShell()->GetResolution());
698
0
  // We have the union of the rectangle, now we need to put it in absolute
699
0
  // screen coords.
700
0
  nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
701
0
  unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());
702
0
703
0
  return unionRectTwips;
704
0
}
705
706
nsIntRect
707
Accessible::Bounds() const
708
0
{
709
0
  return BoundsInAppUnits().ToNearestPixels(mDoc->PresContext()->AppUnitsPerDevPixel());
710
0
}
711
712
nsIntRect
713
Accessible::BoundsInCSSPixels() const
714
0
{
715
0
  return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel());
716
0
}
717
718
void
719
Accessible::SetSelected(bool aSelect)
720
0
{
721
0
  if (!HasOwnContent())
722
0
    return;
723
0
724
0
  Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
725
0
  if (select) {
726
0
    if (select->State() & states::MULTISELECTABLE) {
727
0
      if (mContent->IsElement() && ARIARoleMap()) {
728
0
        if (aSelect) {
729
0
          mContent->AsElement()->SetAttr(kNameSpaceID_None,
730
0
                                         nsGkAtoms::aria_selected,
731
0
                                         NS_LITERAL_STRING("true"), true);
732
0
        } else {
733
0
          mContent->AsElement()->UnsetAttr(kNameSpaceID_None,
734
0
                                           nsGkAtoms::aria_selected, true);
735
0
        }
736
0
      }
737
0
      return;
738
0
    }
739
0
740
0
    if (aSelect)
741
0
      TakeFocus();
742
0
  }
743
0
}
744
745
void
746
Accessible::TakeSelection()
747
0
{
748
0
  Accessible* select = nsAccUtils::GetSelectableContainer(this, State());
749
0
  if (select) {
750
0
    if (select->State() & states::MULTISELECTABLE)
751
0
      select->UnselectAll();
752
0
    SetSelected(true);
753
0
  }
754
0
}
755
756
void
757
Accessible::TakeFocus() const
758
0
{
759
0
  nsIFrame* frame = GetFrame();
760
0
  if (!frame)
761
0
    return;
762
0
763
0
  nsIContent* focusContent = mContent;
764
0
765
0
  // If the accessible focus is managed by container widget then focus the
766
0
  // widget and set the accessible as its current item.
767
0
  if (!frame->IsFocusable()) {
768
0
    Accessible* widget = ContainerWidget();
769
0
    if (widget && widget->AreItemsOperable()) {
770
0
      nsIContent* widgetElm = widget->GetContent();
771
0
      nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
772
0
      if (widgetFrame && widgetFrame->IsFocusable()) {
773
0
        focusContent = widgetElm;
774
0
        widget->SetCurrentItem(this);
775
0
      }
776
0
    }
777
0
  }
778
0
779
0
  nsFocusManager* fm = nsFocusManager::GetFocusManager();
780
0
  if (fm) {
781
0
    AutoHandlingUserInputStatePusher inputStatePusher(true, nullptr, focusContent->OwnerDoc());
782
0
    // XXXbz: Can we actually have a non-element content here?
783
0
    RefPtr<Element> element =
784
0
      focusContent->IsElement() ? focusContent->AsElement() : nullptr;
785
0
    fm->SetFocus(element, 0);
786
0
  }
787
0
}
788
789
void
790
Accessible::XULElmName(DocAccessible* aDocument,
791
                       nsIContent* aElm, nsString& aName)
792
0
{
793
0
  /**
794
0
   * 3 main cases for XUL Controls to be labeled
795
0
   *   1 - control contains label="foo"
796
0
   *   2 - control has, as a child, a label element
797
0
   *        - label has either value="foo" or children
798
0
   *   3 - non-child label contains control="controlID"
799
0
   *        - label has either value="foo" or children
800
0
   * Once a label is found, the search is discontinued, so a control
801
0
   *  that has a label child as well as having a label external to
802
0
   *  the control that uses the control="controlID" syntax will use
803
0
   *  the child label for its Name.
804
0
   */
805
0
806
0
  // CASE #1 (via label attribute) -- great majority of the cases
807
0
  nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm);
808
0
  if (itemEl) {
809
0
    itemEl->GetLabel(aName);
810
0
  } else {
811
0
    // Use @label if this is not a select control element, which uses label
812
0
    // attribute to indicate, which option is selected.
813
0
    nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm);
814
0
    if (!select) {
815
0
      aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
816
0
    }
817
0
  }
818
0
819
0
  // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
820
0
  if (aName.IsEmpty()) {
821
0
    Accessible* label = nullptr;
822
0
    XULLabelIterator iter(aDocument, aElm);
823
0
    while ((label = iter.Next())) {
824
0
      // Check if label's value attribute is used
825
0
      label->Elm()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName);
826
0
      if (aName.IsEmpty()) {
827
0
        // If no value attribute, a non-empty label must contain
828
0
        // children that define its text -- possibly using HTML
829
0
        nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
830
0
      }
831
0
    }
832
0
  }
833
0
834
0
  aName.CompressWhitespace();
835
0
  if (!aName.IsEmpty())
836
0
    return;
837
0
838
0
  // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem>
839
0
  nsIContent *bindingParent = aElm->GetBindingParent();
840
0
  nsIContent* parent =
841
0
    bindingParent? bindingParent->GetParent() : aElm->GetParent();
842
0
  nsAutoString ancestorTitle;
843
0
  while (parent) {
844
0
    if (parent->IsXULElement(nsGkAtoms::toolbaritem) &&
845
0
        parent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, ancestorTitle)) {
846
0
      // Before returning this, check if the element itself has a tooltip:
847
0
      if (aElm->IsElement() &&
848
0
          aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) {
849
0
        aName.CompressWhitespace();
850
0
        return;
851
0
      }
852
0
853
0
      aName.Assign(ancestorTitle);
854
0
      aName.CompressWhitespace();
855
0
      return;
856
0
    }
857
0
    parent = parent->GetParent();
858
0
  }
859
0
}
860
861
nsresult
862
Accessible::HandleAccEvent(AccEvent* aEvent)
863
0
{
864
0
  NS_ENSURE_ARG_POINTER(aEvent);
865
0
866
0
#ifdef MOZ_GECKO_PROFILER
867
0
  if (profiler_is_active()) {
868
0
    nsAutoCString strEventType;
869
0
    GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
870
0
    nsAutoCString strMarker;
871
0
    strMarker.AppendLiteral("A11y Event - ");
872
0
    strMarker.Append(strEventType);
873
0
    profiler_add_marker(strMarker.get());
874
0
  }
875
0
#endif
876
0
877
0
  if (IPCAccessibilityActive() && Document()) {
878
0
    DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
879
0
    MOZ_ASSERT(ipcDoc);
880
0
    if (ipcDoc) {
881
0
      uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 :
882
0
        reinterpret_cast<uintptr_t>(aEvent->GetAccessible()->UniqueID());
883
0
884
0
      switch(aEvent->GetEventType()) {
885
0
        case nsIAccessibleEvent::EVENT_SHOW:
886
0
          ipcDoc->ShowEvent(downcast_accEvent(aEvent));
887
0
          break;
888
0
889
0
        case nsIAccessibleEvent::EVENT_HIDE:
890
0
          ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
891
0
          break;
892
0
893
0
        case nsIAccessibleEvent::EVENT_REORDER:
894
0
          // reorder events on the application acc aren't necessary to tell the parent
895
0
          // about new top level documents.
896
0
          if (!aEvent->GetAccessible()->IsApplication())
897
0
            ipcDoc->SendEvent(id, aEvent->GetEventType());
898
0
          break;
899
0
        case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
900
0
          AccStateChangeEvent* event = downcast_accEvent(aEvent);
901
0
          ipcDoc->SendStateChangeEvent(id, event->GetState(),
902
0
                                       event->IsStateEnabled());
903
0
          break;
904
0
        }
905
0
        case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
906
0
          AccCaretMoveEvent* event = downcast_accEvent(aEvent);
907
0
          ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
908
0
          break;
909
0
        }
910
0
        case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
911
0
        case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
912
0
          AccTextChangeEvent* event = downcast_accEvent(aEvent);
913
0
          ipcDoc->SendTextChangeEvent(id, event->ModifiedText(),
914
0
                                      event->GetStartOffset(),
915
0
                                      event->GetLength(),
916
0
                                      event->IsTextInserted(),
917
0
                                      event->IsFromUserInput());
918
0
          break;
919
0
        }
920
0
        case nsIAccessibleEvent::EVENT_SELECTION:
921
0
        case nsIAccessibleEvent::EVENT_SELECTION_ADD:
922
0
        case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
923
0
          AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
924
0
          uint64_t widgetID = selEvent->Widget()->IsDoc() ? 0 :
925
0
            reinterpret_cast<uintptr_t>(selEvent->Widget()->UniqueID());
926
0
          ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType());
927
0
          break;
928
0
        }
929
0
        case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
930
0
          AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
931
0
          Accessible* position = vcEvent->NewAccessible();
932
0
          Accessible* oldPosition = vcEvent->OldAccessible();
933
0
          ipcDoc->SendVirtualCursorChangeEvent(id,
934
0
            oldPosition ? reinterpret_cast<uintptr_t>(oldPosition->UniqueID()) : 0,
935
0
            vcEvent->OldStartOffset(), vcEvent->OldEndOffset(),
936
0
            position ? reinterpret_cast<uintptr_t>(position->UniqueID()) : 0,
937
0
            vcEvent->NewStartOffset(), vcEvent->NewEndOffset(),
938
0
            vcEvent->Reason(), vcEvent->BoundaryType(),
939
0
            vcEvent->IsFromUserInput());
940
0
          break;
941
0
        }
942
#if defined(XP_WIN)
943
        case nsIAccessibleEvent::EVENT_FOCUS: {
944
          ipcDoc->SendFocusEvent(id);
945
          break;
946
        }
947
#endif
948
0
        case nsIAccessibleEvent::EVENT_SCROLLING_END:
949
0
        case nsIAccessibleEvent::EVENT_SCROLLING: {
950
0
          AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
951
0
          ipcDoc->SendScrollingEvent(id, aEvent->GetEventType(),
952
0
            scrollingEvent->ScrollX(), scrollingEvent->ScrollY(),
953
0
            scrollingEvent->MaxScrollX(), scrollingEvent->MaxScrollY());
954
0
          break;
955
0
        }
956
0
        default:
957
0
          ipcDoc->SendEvent(id, aEvent->GetEventType());
958
0
      }
959
0
    }
960
0
  }
961
0
962
0
  if (nsCoreUtils::AccEventObserversExist()) {
963
0
    nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
964
0
  }
965
0
966
0
  return NS_OK;
967
0
}
968
969
already_AddRefed<nsIPersistentProperties>
970
Accessible::Attributes()
971
0
{
972
0
  nsCOMPtr<nsIPersistentProperties> attributes = NativeAttributes();
973
0
  if (!HasOwnContent() || !mContent->IsElement())
974
0
    return attributes.forget();
975
0
976
0
  // 'xml-roles' attribute for landmark.
977
0
  nsAtom* landmark = LandmarkRole();
978
0
  if (landmark) {
979
0
    nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, landmark);
980
0
981
0
  } else {
982
0
    // 'xml-roles' attribute coming from ARIA.
983
0
    nsAutoString xmlRoles;
984
0
    if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles))
985
0
      nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
986
0
  }
987
0
988
0
  // Expose object attributes from ARIA attributes.
989
0
  nsAutoString unused;
990
0
  aria::AttrIterator attribIter(mContent);
991
0
  nsAutoString name, value;
992
0
  while(attribIter.Next(name, value))
993
0
    attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused);
994
0
995
0
  // If there is no aria-live attribute then expose default value of 'live'
996
0
  // object attribute used for ARIA role of this accessible.
997
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
998
0
  if (roleMapEntry) {
999
0
    if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
1000
0
      nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType,
1001
0
                             NS_LITERAL_STRING("search"));
1002
0
    }
1003
0
1004
0
    nsAutoString live;
1005
0
    nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live);
1006
0
    if (live.IsEmpty()) {
1007
0
      if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live))
1008
0
        nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live);
1009
0
    }
1010
0
  }
1011
0
1012
0
  return attributes.forget();
1013
0
}
1014
1015
already_AddRefed<nsIPersistentProperties>
1016
Accessible::NativeAttributes()
1017
0
{
1018
0
  RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties();
1019
0
1020
0
  nsAutoString unused;
1021
0
1022
0
  // We support values, so expose the string value as well, via the valuetext
1023
0
  // object attribute. We test for the value interface because we don't want
1024
0
  // to expose traditional Value() information such as URL's on links and
1025
0
  // documents, or text in an input.
1026
0
  if (HasNumericValue()) {
1027
0
    nsAutoString valuetext;
1028
0
    Value(valuetext);
1029
0
    attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext,
1030
0
                                  unused);
1031
0
  }
1032
0
1033
0
  // Expose checkable object attribute if the accessible has checkable state
1034
0
  if (State() & states::CHECKABLE) {
1035
0
    nsAccUtils::SetAccAttr(attributes, nsGkAtoms::checkable,
1036
0
                           NS_LITERAL_STRING("true"));
1037
0
  }
1038
0
1039
0
  // Expose 'explicit-name' attribute.
1040
0
  nsAutoString name;
1041
0
  if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
1042
0
    attributes->SetStringProperty(NS_LITERAL_CSTRING("explicit-name"),
1043
0
                                  NS_LITERAL_STRING("true"), unused);
1044
0
  }
1045
0
1046
0
  // Group attributes (level/setsize/posinset)
1047
0
  GroupPos groupPos = GroupPosition();
1048
0
  nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level,
1049
0
                               groupPos.setSize, groupPos.posInSet);
1050
0
1051
0
  bool hierarchical = false;
1052
0
  uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
1053
0
  if (itemCount) {
1054
0
    nsAutoString itemCountStr;
1055
0
    itemCountStr.AppendInt(itemCount);
1056
0
    attributes->SetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
1057
0
      itemCountStr, unused);
1058
0
  }
1059
0
1060
0
  if (hierarchical) {
1061
0
    attributes->SetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
1062
0
      NS_LITERAL_STRING("true"), unused);
1063
0
  }
1064
0
1065
0
  // If the accessible doesn't have own content (such as list item bullet or
1066
0
  // xul tree item) then don't calculate content based attributes.
1067
0
  if (!HasOwnContent())
1068
0
    return attributes.forget();
1069
0
1070
0
  nsEventShell::GetEventAttributes(GetNode(), attributes);
1071
0
1072
0
  // Get container-foo computed live region properties based on the closest
1073
0
  // container with the live region attribute. Inner nodes override outer nodes
1074
0
  // within the same document. The inner nodes can be used to override live
1075
0
  // region behavior on more general outer nodes. However, nodes in outer
1076
0
  // documents override nodes in inner documents: outer doc author may want to
1077
0
  // override properties on a widget they used in an iframe.
1078
0
  nsIContent* startContent = mContent;
1079
0
  while (startContent) {
1080
0
    nsIDocument* doc = startContent->GetComposedDoc();
1081
0
    if (!doc)
1082
0
      break;
1083
0
1084
0
    nsAccUtils::SetLiveContainerAttributes(attributes, startContent,
1085
0
                                           doc->GetRootElement());
1086
0
1087
0
    // Allow ARIA live region markup from outer documents to override
1088
0
    nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
1089
0
    if (!docShellTreeItem)
1090
0
      break;
1091
0
1092
0
    nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
1093
0
    docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
1094
0
    if (!sameTypeParent || sameTypeParent == docShellTreeItem)
1095
0
      break;
1096
0
1097
0
    nsIDocument* parentDoc = doc->GetParentDocument();
1098
0
    if (!parentDoc)
1099
0
      break;
1100
0
1101
0
    startContent = parentDoc->FindContentForSubDocument(doc);
1102
0
  }
1103
0
1104
0
  if (!mContent->IsElement())
1105
0
    return attributes.forget();
1106
0
1107
0
  nsAutoString id;
1108
0
  if (nsCoreUtils::GetID(mContent, id))
1109
0
    attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, unused);
1110
0
1111
0
  // Expose class because it may have useful microformat information.
1112
0
  nsAutoString _class;
1113
0
  if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, _class))
1114
0
    nsAccUtils::SetAccAttr(attributes, nsGkAtoms::_class, _class);
1115
0
1116
0
  // Expose tag.
1117
0
  nsAutoString tagName;
1118
0
  mContent->NodeInfo()->GetName(tagName);
1119
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tag, tagName);
1120
0
1121
0
  // Expose draggable object attribute.
1122
0
  if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
1123
0
    if (htmlElement->Draggable()) {
1124
0
      nsAccUtils::SetAccAttr(attributes, nsGkAtoms::draggable,
1125
0
                             NS_LITERAL_STRING("true"));
1126
0
    }
1127
0
  }
1128
0
1129
0
  // Don't calculate CSS-based object attributes when no frame (i.e.
1130
0
  // the accessible is unattached from the tree).
1131
0
  if (!mContent->GetPrimaryFrame())
1132
0
    return attributes.forget();
1133
0
1134
0
  // CSS style based object attributes.
1135
0
  nsAutoString value;
1136
0
  StyleInfo styleInfo(mContent->AsElement());
1137
0
1138
0
  // Expose 'display' attribute.
1139
0
  styleInfo.Display(value);
1140
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::display, value);
1141
0
1142
0
  // Expose 'text-align' attribute.
1143
0
  styleInfo.TextAlign(value);
1144
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textAlign, value);
1145
0
1146
0
  // Expose 'text-indent' attribute.
1147
0
  styleInfo.TextIndent(value);
1148
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textIndent, value);
1149
0
1150
0
  // Expose 'margin-left' attribute.
1151
0
  styleInfo.MarginLeft(value);
1152
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginLeft, value);
1153
0
1154
0
  // Expose 'margin-right' attribute.
1155
0
  styleInfo.MarginRight(value);
1156
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginRight, value);
1157
0
1158
0
  // Expose 'margin-top' attribute.
1159
0
  styleInfo.MarginTop(value);
1160
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginTop, value);
1161
0
1162
0
  // Expose 'margin-bottom' attribute.
1163
0
  styleInfo.MarginBottom(value);
1164
0
  nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginBottom, value);
1165
0
1166
0
  return attributes.forget();
1167
0
}
1168
1169
GroupPos
1170
Accessible::GroupPosition()
1171
0
{
1172
0
  GroupPos groupPos;
1173
0
  if (!HasOwnContent())
1174
0
    return groupPos;
1175
0
1176
0
  // Get group position from ARIA attributes.
1177
0
  nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level);
1178
0
  nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize);
1179
0
  nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &groupPos.posInSet);
1180
0
1181
0
  // If ARIA is missed and the accessible is visible then calculate group
1182
0
  // position from hierarchy.
1183
0
  if (State() & states::INVISIBLE)
1184
0
    return groupPos;
1185
0
1186
0
  // Calculate group level if ARIA is missed.
1187
0
  if (groupPos.level == 0) {
1188
0
    int32_t level = GetLevelInternal();
1189
0
    if (level != 0)
1190
0
      groupPos.level = level;
1191
0
  }
1192
0
1193
0
  // Calculate position in group and group size if ARIA is missed.
1194
0
  if (groupPos.posInSet == 0 || groupPos.setSize == 0) {
1195
0
    int32_t posInSet = 0, setSize = 0;
1196
0
    GetPositionAndSizeInternal(&posInSet, &setSize);
1197
0
    if (posInSet != 0 && setSize != 0) {
1198
0
      if (groupPos.posInSet == 0)
1199
0
        groupPos.posInSet = posInSet;
1200
0
1201
0
      if (groupPos.setSize == 0)
1202
0
        groupPos.setSize = setSize;
1203
0
    }
1204
0
  }
1205
0
1206
0
  return groupPos;
1207
0
}
1208
1209
uint64_t
1210
Accessible::State()
1211
0
{
1212
0
  if (IsDefunct())
1213
0
    return states::DEFUNCT;
1214
0
1215
0
  uint64_t state = NativeState();
1216
0
  // Apply ARIA states to be sure accessible states will be overridden.
1217
0
  ApplyARIAState(&state);
1218
0
1219
0
  // If this is an ARIA item of the selectable widget and if it's focused and
1220
0
  // not marked unselected explicitly (i.e. aria-selected="false") then expose
1221
0
  // it as selected to make ARIA widget authors life easier.
1222
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1223
0
  if (roleMapEntry && !(state & states::SELECTED) &&
1224
0
      (!mContent->IsElement() ||
1225
0
       !mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1226
0
                                           nsGkAtoms::aria_selected,
1227
0
                                           nsGkAtoms::_false, eCaseMatters))) {
1228
0
    // Special case for tabs: focused tab or focus inside related tab panel
1229
0
    // implies selected state.
1230
0
    if (roleMapEntry->role == roles::PAGETAB) {
1231
0
      if (state & states::FOCUSED) {
1232
0
        state |= states::SELECTED;
1233
0
      } else {
1234
0
        // If focus is in a child of the tab panel surely the tab is selected!
1235
0
        Relation rel = RelationByType(RelationType::LABEL_FOR);
1236
0
        Accessible* relTarget = nullptr;
1237
0
        while ((relTarget = rel.Next())) {
1238
0
          if (relTarget->Role() == roles::PROPERTYPAGE &&
1239
0
              FocusMgr()->IsFocusWithin(relTarget))
1240
0
            state |= states::SELECTED;
1241
0
        }
1242
0
      }
1243
0
    } else if (state & states::FOCUSED) {
1244
0
      Accessible* container = nsAccUtils::GetSelectableContainer(this, state);
1245
0
      if (container &&
1246
0
          !nsAccUtils::HasDefinedARIAToken(container->GetContent(),
1247
0
                                           nsGkAtoms::aria_multiselectable)) {
1248
0
        state |= states::SELECTED;
1249
0
      }
1250
0
    }
1251
0
  }
1252
0
1253
0
  const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
1254
0
  if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
1255
0
    // Cannot be both expanded and collapsed -- this happens in ARIA expanded
1256
0
    // combobox because of limitation of ARIAMap.
1257
0
    // XXX: Perhaps we will be able to make this less hacky if we support
1258
0
    // extended states in ARIAMap, e.g. derive COLLAPSED from
1259
0
    // EXPANDABLE && !EXPANDED.
1260
0
    state &= ~states::COLLAPSED;
1261
0
  }
1262
0
1263
0
  if (!(state & states::UNAVAILABLE)) {
1264
0
    state |= states::ENABLED | states::SENSITIVE;
1265
0
1266
0
    // If the object is a current item of container widget then mark it as
1267
0
    // ACTIVE. This allows screen reader virtual buffer modes to know which
1268
0
    // descendant is the current one that would get focus if the user navigates
1269
0
    // to the container widget.
1270
0
    Accessible* widget = ContainerWidget();
1271
0
    if (widget && widget->CurrentItem() == this)
1272
0
      state |= states::ACTIVE;
1273
0
  }
1274
0
1275
0
  if ((state & states::COLLAPSED) || (state & states::EXPANDED))
1276
0
    state |= states::EXPANDABLE;
1277
0
1278
0
  // For some reasons DOM node may have not a frame. We tract such accessibles
1279
0
  // as invisible.
1280
0
  nsIFrame *frame = GetFrame();
1281
0
  if (!frame)
1282
0
    return state;
1283
0
1284
0
  if (frame->StyleEffects()->mOpacity == 1.0f &&
1285
0
      !(state & states::INVISIBLE)) {
1286
0
    state |= states::OPAQUE1;
1287
0
  }
1288
0
1289
0
  return state;
1290
0
}
1291
1292
void
1293
Accessible::ApplyARIAState(uint64_t* aState) const
1294
0
{
1295
0
  if (!mContent->IsElement())
1296
0
    return;
1297
0
1298
0
  dom::Element* element = mContent->AsElement();
1299
0
1300
0
  // Test for universal states first
1301
0
  *aState |= aria::UniversalStatesFor(element);
1302
0
1303
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1304
0
  if (roleMapEntry) {
1305
0
1306
0
    // We only force the readonly bit off if we have a real mapping for the aria
1307
0
    // role. This preserves the ability for screen readers to use readonly
1308
0
    // (primarily on the document) as the hint for creating a virtual buffer.
1309
0
    if (roleMapEntry->role != roles::NOTHING)
1310
0
      *aState &= ~states::READONLY;
1311
0
1312
0
    if (mContent->HasID()) {
1313
0
      // If has a role & ID and aria-activedescendant on the container, assume
1314
0
      // focusable.
1315
0
      const Accessible* ancestor = this;
1316
0
      while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
1317
0
        dom::Element* el = ancestor->Elm();
1318
0
        if (el &&
1319
0
            el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) {
1320
0
          *aState |= states::FOCUSABLE;
1321
0
          break;
1322
0
        }
1323
0
      }
1324
0
    }
1325
0
  }
1326
0
1327
0
  if (*aState & states::FOCUSABLE) {
1328
0
    // Propogate aria-disabled from ancestors down to any focusable descendant.
1329
0
    const Accessible* ancestor = this;
1330
0
    while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) {
1331
0
      dom::Element* el = ancestor->Elm();
1332
0
      if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
1333
0
                                nsGkAtoms::_true, eCaseMatters)) {
1334
0
        *aState |= states::UNAVAILABLE;
1335
0
        break;
1336
0
      }
1337
0
    }
1338
0
  }
1339
0
1340
0
  // special case: A native button element whose role got transformed by ARIA to a toggle button
1341
0
  // Also applies to togglable button menus, like in the Dev Tools Web Console.
1342
0
  if (IsButton() || IsMenuButton())
1343
0
    aria::MapToState(aria::eARIAPressed, element, aState);
1344
0
1345
0
  if (!roleMapEntry)
1346
0
    return;
1347
0
1348
0
  *aState |= roleMapEntry->state;
1349
0
1350
0
  if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
1351
0
      aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
1352
0
      aria::MapToState(roleMapEntry->attributeMap3, element, aState))
1353
0
    aria::MapToState(roleMapEntry->attributeMap4, element, aState);
1354
0
1355
0
  // ARIA gridcell inherits editable/readonly states from the grid until it's
1356
0
  // overridden.
1357
0
  if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
1358
0
       roleMapEntry->Is(nsGkAtoms::columnheader) ||
1359
0
       roleMapEntry->Is(nsGkAtoms::rowheader)) &&
1360
0
      !(*aState & (states::READONLY | states::EDITABLE))) {
1361
0
    const TableCellAccessible* cell = AsTableCell();
1362
0
    if (cell) {
1363
0
      TableAccessible* table = cell->Table();
1364
0
      if (table) {
1365
0
        Accessible* grid = table->AsAccessible();
1366
0
        uint64_t gridState = 0;
1367
0
        grid->ApplyARIAState(&gridState);
1368
0
        *aState |= (gridState & (states::READONLY | states::EDITABLE));
1369
0
      }
1370
0
    }
1371
0
  }
1372
0
}
1373
1374
void
1375
Accessible::Value(nsString& aValue) const
1376
0
{
1377
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1378
0
1379
0
  if ((roleMapEntry && roleMapEntry->valueRule != eNoValue) ||
1380
0
      // Bug 1475376: aria-valuetext should also be supported for implicit ARIA
1381
0
      // roles; e.g. <input type="range">.
1382
0
      HasNumericValue()) {
1383
0
    // aria-valuenow is a number, and aria-valuetext is the optional text
1384
0
    // equivalent. For the string value, we will try the optional text
1385
0
    // equivalent first.
1386
0
    if (!mContent->IsElement()) {
1387
0
      return;
1388
0
    }
1389
0
1390
0
    if (!mContent->AsElement()->GetAttr(kNameSpaceID_None,
1391
0
                                        nsGkAtoms::aria_valuetext, aValue)) {
1392
0
      mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow,
1393
0
                                     aValue);
1394
0
    }
1395
0
    return;
1396
0
  }
1397
0
1398
0
  if (!roleMapEntry) {
1399
0
    return;
1400
0
  }
1401
0
1402
0
  // Value of textbox is a textified subtree.
1403
0
  if (roleMapEntry->Is(nsGkAtoms::textbox)) {
1404
0
    nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
1405
0
    return;
1406
0
  }
1407
0
1408
0
  // Value of combobox is a text of current or selected item.
1409
0
  if (roleMapEntry->Is(nsGkAtoms::combobox)) {
1410
0
    Accessible* option = CurrentItem();
1411
0
    if (!option) {
1412
0
      uint32_t childCount = ChildCount();
1413
0
      for (uint32_t idx = 0; idx < childCount; idx++) {
1414
0
        Accessible* child = mChildren.ElementAt(idx);
1415
0
        if (child->IsListControl()) {
1416
0
          option = child->GetSelectedItem(0);
1417
0
          break;
1418
0
        }
1419
0
      }
1420
0
    }
1421
0
1422
0
    if (option)
1423
0
      nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue);
1424
0
  }
1425
0
}
1426
1427
double
1428
Accessible::MaxValue() const
1429
0
{
1430
0
  return AttrNumericValue(nsGkAtoms::aria_valuemax);
1431
0
}
1432
1433
double
1434
Accessible::MinValue() const
1435
0
{
1436
0
  return AttrNumericValue(nsGkAtoms::aria_valuemin);
1437
0
}
1438
1439
double
1440
Accessible::Step() const
1441
0
{
1442
0
  return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1443
0
}
1444
1445
double
1446
Accessible::CurValue() const
1447
0
{
1448
0
  return AttrNumericValue(nsGkAtoms::aria_valuenow);
1449
0
}
1450
1451
bool
1452
Accessible::SetCurValue(double aValue)
1453
0
{
1454
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1455
0
  if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
1456
0
    return false;
1457
0
1458
0
  const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
1459
0
  if (State() & kValueCannotChange)
1460
0
    return false;
1461
0
1462
0
  double checkValue = MinValue();
1463
0
  if (!IsNaN(checkValue) && aValue < checkValue)
1464
0
    return false;
1465
0
1466
0
  checkValue = MaxValue();
1467
0
  if (!IsNaN(checkValue) && aValue > checkValue)
1468
0
    return false;
1469
0
1470
0
  nsAutoString strValue;
1471
0
  strValue.AppendFloat(aValue);
1472
0
1473
0
  if (!mContent->IsElement())
1474
0
    return true;
1475
0
1476
0
  return NS_SUCCEEDED(
1477
0
    mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow,
1478
0
                                   strValue, true));
1479
0
}
1480
1481
role
1482
Accessible::ARIATransformRole(role aRole) const
1483
0
{
1484
0
  // Beginning with ARIA 1.1, user agents are expected to use the native host
1485
0
  // language role of the element when the region role is used without a name.
1486
0
  // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region
1487
0
  //
1488
0
  // XXX: While the name computation algorithm can be non-trivial in the general
1489
0
  // case, it should not be especially bad here: If the author hasn't used the
1490
0
  // region role, this calculation won't occur. And the region role's name
1491
0
  // calculation rule excludes name from content. That said, this use case is
1492
0
  // another example of why we should consider caching the accessible name. See:
1493
0
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235.
1494
0
  if (aRole == roles::REGION) {
1495
0
    nsAutoString name;
1496
0
    Name(name);
1497
0
    return name.IsEmpty() ? NativeRole() : aRole;
1498
0
  }
1499
0
1500
0
  // XXX: these unfortunate exceptions don't fit into the ARIA table. This is
1501
0
  // where the accessible role depends on both the role and ARIA state.
1502
0
  if (aRole == roles::PUSHBUTTON) {
1503
0
    if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) {
1504
0
      // For simplicity, any existing pressed attribute except "" or "undefined"
1505
0
      // indicates a toggle.
1506
0
      return roles::TOGGLE_BUTTON;
1507
0
    }
1508
0
1509
0
    if (mContent->IsElement() &&
1510
0
        mContent->AsElement()->AttrValueIs(kNameSpaceID_None,
1511
0
                                           nsGkAtoms::aria_haspopup,
1512
0
                                           nsGkAtoms::_true,
1513
0
                                           eCaseMatters)) {
1514
0
      // For button with aria-haspopup="true".
1515
0
      return roles::BUTTONMENU;
1516
0
    }
1517
0
1518
0
  } else if (aRole == roles::LISTBOX) {
1519
0
    // A listbox inside of a combobox needs a special role because of ATK
1520
0
    // mapping to menu.
1521
0
    if (mParent && mParent->IsCombobox()) {
1522
0
      return roles::COMBOBOX_LIST;
1523
0
    } else {
1524
0
      // Listbox is owned by a combobox
1525
0
      Relation rel = RelationByType(RelationType::NODE_CHILD_OF);
1526
0
      Accessible* targetAcc = nullptr;
1527
0
      while ((targetAcc = rel.Next()))
1528
0
        if (targetAcc->IsCombobox())
1529
0
          return roles::COMBOBOX_LIST;
1530
0
    }
1531
0
1532
0
  } else if (aRole == roles::OPTION) {
1533
0
    if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
1534
0
      return roles::COMBOBOX_OPTION;
1535
0
1536
0
  } else if (aRole == roles::MENUITEM) {
1537
0
    // Menuitem has a submenu.
1538
0
    if (mContent->IsElement() &&
1539
0
        mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_haspopup,
1540
0
                                           nsGkAtoms::_true, eCaseMatters)) {
1541
0
      return roles::PARENT_MENUITEM;
1542
0
    }
1543
0
  }
1544
0
1545
0
  return aRole;
1546
0
}
1547
1548
nsAtom*
1549
Accessible::LandmarkRole() const
1550
0
{
1551
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1552
0
  return roleMapEntry && roleMapEntry->IsOfType(eLandmark) ?
1553
0
    *(roleMapEntry->roleAtom) : nullptr;
1554
0
}
1555
1556
role
1557
Accessible::NativeRole() const
1558
0
{
1559
0
  return roles::NOTHING;
1560
0
}
1561
1562
uint8_t
1563
Accessible::ActionCount() const
1564
0
{
1565
0
  return GetActionRule() == eNoAction ? 0 : 1;
1566
0
}
1567
1568
void
1569
Accessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
1570
0
{
1571
0
  aName.Truncate();
1572
0
1573
0
  if (aIndex != 0)
1574
0
    return;
1575
0
1576
0
  uint32_t actionRule = GetActionRule();
1577
0
1578
0
 switch (actionRule) {
1579
0
   case eActivateAction:
1580
0
     aName.AssignLiteral("activate");
1581
0
     return;
1582
0
1583
0
   case eClickAction:
1584
0
     aName.AssignLiteral("click");
1585
0
     return;
1586
0
1587
0
   case ePressAction:
1588
0
     aName.AssignLiteral("press");
1589
0
     return;
1590
0
1591
0
   case eCheckUncheckAction:
1592
0
   {
1593
0
     uint64_t state = State();
1594
0
     if (state & states::CHECKED)
1595
0
       aName.AssignLiteral("uncheck");
1596
0
     else if (state & states::MIXED)
1597
0
       aName.AssignLiteral("cycle");
1598
0
     else
1599
0
       aName.AssignLiteral("check");
1600
0
     return;
1601
0
   }
1602
0
1603
0
   case eJumpAction:
1604
0
     aName.AssignLiteral("jump");
1605
0
     return;
1606
0
1607
0
   case eOpenCloseAction:
1608
0
     if (State() & states::COLLAPSED)
1609
0
       aName.AssignLiteral("open");
1610
0
     else
1611
0
       aName.AssignLiteral("close");
1612
0
     return;
1613
0
1614
0
   case eSelectAction:
1615
0
     aName.AssignLiteral("select");
1616
0
     return;
1617
0
1618
0
   case eSwitchAction:
1619
0
     aName.AssignLiteral("switch");
1620
0
     return;
1621
0
1622
0
   case eSortAction:
1623
0
     aName.AssignLiteral("sort");
1624
0
     return;
1625
0
1626
0
   case eExpandAction:
1627
0
     if (State() & states::COLLAPSED)
1628
0
       aName.AssignLiteral("expand");
1629
0
     else
1630
0
       aName.AssignLiteral("collapse");
1631
0
     return;
1632
0
  }
1633
0
}
1634
1635
bool
1636
Accessible::DoAction(uint8_t aIndex) const
1637
0
{
1638
0
  if (aIndex != 0)
1639
0
    return false;
1640
0
1641
0
  if (GetActionRule() != eNoAction) {
1642
0
    DoCommand();
1643
0
    return true;
1644
0
  }
1645
0
1646
0
  return false;
1647
0
}
1648
1649
nsIContent*
1650
Accessible::GetAtomicRegion() const
1651
0
{
1652
0
  nsIContent *loopContent = mContent;
1653
0
  nsAutoString atomic;
1654
0
  while (loopContent &&
1655
0
         (!loopContent->IsElement() ||
1656
0
          !loopContent->AsElement()->GetAttr(kNameSpaceID_None,
1657
0
                                             nsGkAtoms::aria_atomic, atomic)))
1658
0
    loopContent = loopContent->GetParent();
1659
0
1660
0
  return atomic.EqualsLiteral("true") ? loopContent : nullptr;
1661
0
}
1662
1663
Relation
1664
Accessible::RelationByType(RelationType aType) const
1665
0
{
1666
0
  if (!HasOwnContent())
1667
0
    return Relation();
1668
0
1669
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
1670
0
1671
0
  // Relationships are defined on the same content node that the role would be
1672
0
  // defined on.
1673
0
  switch (aType) {
1674
0
    case RelationType::LABELLED_BY: {
1675
0
      Relation rel(new IDRefsIterator(mDoc, mContent,
1676
0
                                      nsGkAtoms::aria_labelledby));
1677
0
      if (mContent->IsHTMLElement()) {
1678
0
        rel.AppendIter(new HTMLLabelIterator(Document(), this));
1679
0
      } else if (mContent->IsXULElement()) {
1680
0
        rel.AppendIter(new XULLabelIterator(Document(), mContent));
1681
0
      }
1682
0
1683
0
      return rel;
1684
0
    }
1685
0
1686
0
    case RelationType::LABEL_FOR: {
1687
0
      Relation rel(new RelatedAccIterator(Document(), mContent,
1688
0
                                          nsGkAtoms::aria_labelledby));
1689
0
      if (mContent->IsXULElement(nsGkAtoms::label))
1690
0
        rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
1691
0
1692
0
      return rel;
1693
0
    }
1694
0
1695
0
    case RelationType::DESCRIBED_BY: {
1696
0
      Relation rel(new IDRefsIterator(mDoc, mContent,
1697
0
                                      nsGkAtoms::aria_describedby));
1698
0
      if (mContent->IsXULElement())
1699
0
        rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
1700
0
1701
0
      return rel;
1702
0
    }
1703
0
1704
0
    case RelationType::DESCRIPTION_FOR: {
1705
0
      Relation rel(new RelatedAccIterator(Document(), mContent,
1706
0
                                          nsGkAtoms::aria_describedby));
1707
0
1708
0
      // This affectively adds an optional control attribute to xul:description,
1709
0
      // which only affects accessibility, by allowing the description to be
1710
0
      // tied to a control.
1711
0
      if (mContent->IsXULElement(nsGkAtoms::description))
1712
0
        rel.AppendIter(new IDRefsIterator(mDoc, mContent,
1713
0
                                          nsGkAtoms::control));
1714
0
1715
0
      return rel;
1716
0
    }
1717
0
1718
0
    case RelationType::NODE_CHILD_OF: {
1719
0
      Relation rel;
1720
0
      // This is an ARIA tree or treegrid that doesn't use owns, so we need to
1721
0
      // get the parent the hard way.
1722
0
      if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
1723
0
                            roleMapEntry->role == roles::LISTITEM ||
1724
0
                            roleMapEntry->role == roles::ROW)) {
1725
0
        rel.AppendTarget(GetGroupInfo()->ConceptualParent());
1726
0
      }
1727
0
1728
0
      // If accessible is in its own Window, or is the root of a document,
1729
0
      // then we should provide NODE_CHILD_OF relation so that MSAA clients
1730
0
      // can easily get to true parent instead of getting to oleacc's
1731
0
      // ROLE_WINDOW accessible which will prevent us from going up further
1732
0
      // (because it is system generated and has no idea about the hierarchy
1733
0
      // above it).
1734
0
      nsIFrame *frame = GetFrame();
1735
0
      if (frame) {
1736
0
        nsView *view = frame->GetView();
1737
0
        if (view) {
1738
0
          nsIScrollableFrame *scrollFrame = do_QueryFrame(frame);
1739
0
          if (scrollFrame || view->GetWidget() || !frame->GetParent())
1740
0
            rel.AppendTarget(Parent());
1741
0
        }
1742
0
      }
1743
0
1744
0
      return rel;
1745
0
    }
1746
0
1747
0
    case RelationType::NODE_PARENT_OF: {
1748
0
      // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
1749
0
      // also can be organized by groups.
1750
0
      if (roleMapEntry &&
1751
0
          (roleMapEntry->role == roles::OUTLINEITEM ||
1752
0
           roleMapEntry->role == roles::LISTITEM ||
1753
0
           roleMapEntry->role == roles::ROW ||
1754
0
           roleMapEntry->role == roles::OUTLINE ||
1755
0
           roleMapEntry->role == roles::LIST ||
1756
0
           roleMapEntry->role == roles::TREE_TABLE)) {
1757
0
        return Relation(new ItemIterator(this));
1758
0
      }
1759
0
1760
0
      return Relation();
1761
0
    }
1762
0
1763
0
    case RelationType::CONTROLLED_BY:
1764
0
      return Relation(new RelatedAccIterator(Document(), mContent,
1765
0
                                             nsGkAtoms::aria_controls));
1766
0
1767
0
    case RelationType::CONTROLLER_FOR: {
1768
0
      Relation rel(new IDRefsIterator(mDoc, mContent,
1769
0
                                      nsGkAtoms::aria_controls));
1770
0
      rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
1771
0
      return rel;
1772
0
    }
1773
0
1774
0
    case RelationType::FLOWS_TO:
1775
0
      return Relation(new IDRefsIterator(mDoc, mContent,
1776
0
                                         nsGkAtoms::aria_flowto));
1777
0
1778
0
    case RelationType::FLOWS_FROM:
1779
0
      return Relation(new RelatedAccIterator(Document(), mContent,
1780
0
                                             nsGkAtoms::aria_flowto));
1781
0
1782
0
    case RelationType::MEMBER_OF:
1783
0
          return Relation(mDoc, GetAtomicRegion());
1784
0
1785
0
    case RelationType::SUBWINDOW_OF:
1786
0
    case RelationType::EMBEDS:
1787
0
    case RelationType::EMBEDDED_BY:
1788
0
    case RelationType::POPUP_FOR:
1789
0
    case RelationType::PARENT_WINDOW_OF:
1790
0
      return Relation();
1791
0
1792
0
    case RelationType::DEFAULT_BUTTON: {
1793
0
      if (mContent->IsHTMLElement()) {
1794
0
        // HTML form controls implements nsIFormControl interface.
1795
0
        nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent));
1796
0
        if (control) {
1797
0
          nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement()));
1798
0
          if (form) {
1799
0
            nsCOMPtr<nsIContent> formContent =
1800
0
              do_QueryInterface(form->GetDefaultSubmitElement());
1801
0
            return Relation(mDoc, formContent);
1802
0
          }
1803
0
        }
1804
0
      } else {
1805
0
        // In XUL, use first <button default="true" .../> in the document
1806
0
        nsIDocument* doc = mContent->OwnerDoc();
1807
0
        nsCOMPtr<nsIDOMXULButtonElement> buttonEl;
1808
0
        if (doc->IsXULDocument()) {
1809
0
          dom::XULDocument* xulDoc = doc->AsXULDocument();
1810
0
          nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons =
1811
0
            xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"),
1812
0
                                           NS_LITERAL_STRING("true"));
1813
0
          if (possibleDefaultButtons) {
1814
0
            uint32_t length = possibleDefaultButtons->Length();
1815
0
            // Check for button in list of default="true" elements
1816
0
            for (uint32_t count = 0; count < length && !buttonEl; count ++) {
1817
0
              buttonEl = do_QueryInterface(possibleDefaultButtons->Item(count));
1818
0
            }
1819
0
          }
1820
0
          if (!buttonEl) { // Check for anonymous accept button in <dialog>
1821
0
            dom::Element* rootElm = mContent->OwnerDoc()->GetRootElement();
1822
0
            if (rootElm) {
1823
0
              nsIContent* possibleButtonEl = rootElm->OwnerDoc()->
1824
0
                GetAnonymousElementByAttribute(rootElm, nsGkAtoms::_default,
1825
0
                                               NS_LITERAL_STRING("true"));
1826
0
              buttonEl = do_QueryInterface(possibleButtonEl);
1827
0
            }
1828
0
          }
1829
0
          nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl));
1830
0
          return Relation(mDoc, relatedContent);
1831
0
        }
1832
0
      }
1833
0
      return Relation();
1834
0
    }
1835
0
1836
0
    case RelationType::CONTAINING_DOCUMENT:
1837
0
      return Relation(mDoc);
1838
0
1839
0
    case RelationType::CONTAINING_TAB_PANE: {
1840
0
      nsCOMPtr<nsIDocShell> docShell =
1841
0
        nsCoreUtils::GetDocShellFor(GetNode());
1842
0
      if (docShell) {
1843
0
        // Walk up the parent chain without crossing the boundary at which item
1844
0
        // types change, preventing us from walking up out of tab content.
1845
0
        nsCOMPtr<nsIDocShellTreeItem> root;
1846
0
        docShell->GetSameTypeRootTreeItem(getter_AddRefs(root));
1847
0
        if (root) {
1848
0
          // If the item type is typeContent, we assume we are in browser tab
1849
0
          // content. Note, this includes content such as about:addons,
1850
0
          // for consistency.
1851
0
          if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
1852
0
            return Relation(nsAccUtils::GetDocAccessibleFor(root));
1853
0
          }
1854
0
        }
1855
0
      }
1856
0
      return Relation();
1857
0
    }
1858
0
1859
0
    case RelationType::CONTAINING_APPLICATION:
1860
0
      return Relation(ApplicationAcc());
1861
0
1862
0
    case RelationType::DETAILS:
1863
0
      return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
1864
0
1865
0
    case RelationType::DETAILS_FOR:
1866
0
      return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
1867
0
1868
0
    case RelationType::ERRORMSG:
1869
0
      return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
1870
0
1871
0
    case RelationType::ERRORMSG_FOR:
1872
0
      return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
1873
0
1874
0
    default:
1875
0
      return Relation();
1876
0
  }
1877
0
}
1878
1879
void
1880
Accessible::GetNativeInterface(void** aNativeAccessible)
1881
0
{
1882
0
}
1883
1884
void
1885
Accessible::DoCommand(nsIContent* aContent, uint32_t aActionIndex) const
1886
{
1887
  class Runnable final : public mozilla::Runnable
1888
  {
1889
  public:
1890
    Runnable(const Accessible* aAcc, nsIContent* aContent, uint32_t aIdx)
1891
      : mozilla::Runnable("Runnable")
1892
      , mAcc(aAcc)
1893
      , mContent(aContent)
1894
      , mIdx(aIdx)
1895
0
    {
1896
0
    }
1897
1898
    NS_IMETHOD Run() override
1899
0
    {
1900
0
      if (mAcc)
1901
0
        mAcc->DispatchClickEvent(mContent, mIdx);
1902
0
1903
0
      return NS_OK;
1904
0
    }
1905
1906
    void Revoke()
1907
0
    {
1908
0
      mAcc = nullptr;
1909
0
      mContent = nullptr;
1910
0
    }
1911
1912
  private:
1913
    RefPtr<const Accessible> mAcc;
1914
    nsCOMPtr<nsIContent> mContent;
1915
    uint32_t mIdx;
1916
  };
1917
1918
  nsIContent* content = aContent ? aContent : mContent.get();
1919
  nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
1920
  NS_DispatchToMainThread(runnable);
1921
}
1922
1923
void
1924
Accessible::DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex) const
1925
0
{
1926
0
  if (IsDefunct())
1927
0
    return;
1928
0
1929
0
  nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell();
1930
0
1931
0
  // Scroll into view.
1932
0
  presShell->ScrollContentIntoView(aContent,
1933
0
                                   nsIPresShell::ScrollAxis(),
1934
0
                                   nsIPresShell::ScrollAxis(),
1935
0
                                   nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
1936
0
1937
0
  AutoWeakFrame frame = aContent->GetPrimaryFrame();
1938
0
  if (!frame)
1939
0
    return;
1940
0
1941
0
  // Compute x and y coordinates.
1942
0
  nsPoint point;
1943
0
  nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
1944
0
  if (!widget)
1945
0
    return;
1946
0
1947
0
  nsSize size = frame->GetSize();
1948
0
1949
0
  RefPtr<nsPresContext> presContext = presShell->GetPresContext();
1950
0
  int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
1951
0
  int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
1952
0
1953
0
  // Simulate a touch interaction by dispatching touch events with mouse events.
1954
0
  nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame,
1955
0
                                  presShell, widget);
1956
0
  nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame,
1957
0
                                  presShell, widget);
1958
0
  nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame,
1959
0
                                  presShell, widget);
1960
0
  nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame,
1961
0
                                  presShell, widget);
1962
0
}
1963
1964
void
1965
Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY)
1966
0
{
1967
0
  nsIFrame* frame = GetFrame();
1968
0
  if (!frame)
1969
0
    return;
1970
0
1971
0
  nsIntPoint coords =
1972
0
    nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
1973
0
1974
0
  nsIFrame* parentFrame = frame;
1975
0
  while ((parentFrame = parentFrame->GetParent()))
1976
0
    nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
1977
0
}
1978
1979
void
1980
Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
1981
                         uint32_t aLength)
1982
0
{
1983
0
  // Return text representation of non-text accessible within hypertext
1984
0
  // accessible. Text accessible overrides this method to return enclosed text.
1985
0
  if (aStartOffset != 0 || aLength == 0)
1986
0
    return;
1987
0
1988
0
  nsIFrame *frame = GetFrame();
1989
0
  if (!frame) {
1990
0
    if (mContent->IsElement() && mContent->AsElement()->IsDisplayContents()) {
1991
0
      aText += kEmbeddedObjectChar;
1992
0
    }
1993
0
    return;
1994
0
  }
1995
0
1996
0
  MOZ_ASSERT(mParent,
1997
0
             "Called on accessible unbound from tree. Result can be wrong.");
1998
0
1999
0
  if (frame->IsBrFrame()) {
2000
0
    aText += kForcedNewLineChar;
2001
0
  } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2002
0
    // Expose the embedded object accessible as imaginary embedded object
2003
0
    // character if its parent hypertext accessible doesn't expose children to
2004
0
    // AT.
2005
0
    aText += kImaginaryEmbeddedObjectChar;
2006
0
  } else {
2007
0
    aText += kEmbeddedObjectChar;
2008
0
  }
2009
0
}
2010
2011
void
2012
Accessible::Shutdown()
2013
0
{
2014
0
  // Mark the accessible as defunct, invalidate the child count and pointers to
2015
0
  // other accessibles, also make sure none of its children point to this parent
2016
0
  mStateFlags |= eIsDefunct;
2017
0
2018
0
  int32_t childCount = mChildren.Length();
2019
0
  for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2020
0
    mChildren.ElementAt(childIdx)->UnbindFromParent();
2021
0
  }
2022
0
  mChildren.Clear();
2023
0
2024
0
  mEmbeddedObjCollector = nullptr;
2025
0
2026
0
  if (mParent)
2027
0
    mParent->RemoveChild(this);
2028
0
2029
0
  mContent = nullptr;
2030
0
  mDoc = nullptr;
2031
0
  if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this)
2032
0
    SelectionMgr()->ResetCaretOffset();
2033
0
}
2034
2035
// Accessible protected
2036
void
2037
Accessible::ARIAName(nsString& aName) const
2038
0
{
2039
0
  // aria-labelledby now takes precedence over aria-label
2040
0
  nsresult rv = nsTextEquivUtils::
2041
0
    GetTextEquivFromIDRefs(this, nsGkAtoms::aria_labelledby, aName);
2042
0
  if (NS_SUCCEEDED(rv)) {
2043
0
    aName.CompressWhitespace();
2044
0
  }
2045
0
2046
0
  if (aName.IsEmpty() &&
2047
0
      mContent->IsElement() &&
2048
0
      mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label,
2049
0
                                     aName)) {
2050
0
    aName.CompressWhitespace();
2051
0
  }
2052
0
}
2053
2054
// Accessible protected
2055
ENameValueFlag
2056
Accessible::NativeName(nsString& aName) const
2057
0
{
2058
0
  if (mContent->IsHTMLElement()) {
2059
0
    Accessible* label = nullptr;
2060
0
    HTMLLabelIterator iter(Document(), this);
2061
0
    while ((label = iter.Next())) {
2062
0
      nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
2063
0
                                                   &aName);
2064
0
      aName.CompressWhitespace();
2065
0
    }
2066
0
2067
0
    if (!aName.IsEmpty())
2068
0
      return eNameOK;
2069
0
2070
0
    nsTextEquivUtils::GetNameFromSubtree(this, aName);
2071
0
    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2072
0
  }
2073
0
2074
0
  if (mContent->IsXULElement()) {
2075
0
    XULElmName(mDoc, mContent, aName);
2076
0
    if (!aName.IsEmpty())
2077
0
      return eNameOK;
2078
0
2079
0
    nsTextEquivUtils::GetNameFromSubtree(this, aName);
2080
0
    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2081
0
  }
2082
0
2083
0
  if (mContent->IsSVGElement()) {
2084
0
    // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
2085
0
    // for processing, the user agent shall choose the first one.
2086
0
    for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2087
0
         childElm = childElm->GetNextSibling()) {
2088
0
      if (childElm->IsSVGElement(nsGkAtoms::title)) {
2089
0
        nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2090
0
        return eNameOK;
2091
0
      }
2092
0
    }
2093
0
  }
2094
0
2095
0
  return eNameOK;
2096
0
}
2097
2098
// Accessible protected
2099
void
2100
Accessible::NativeDescription(nsString& aDescription)
2101
0
{
2102
0
  bool isXUL = mContent->IsXULElement();
2103
0
  if (isXUL) {
2104
0
    // Try XUL <description control="[id]">description text</description>
2105
0
    XULDescriptionIterator iter(Document(), mContent);
2106
0
    Accessible* descr = nullptr;
2107
0
    while ((descr = iter.Next())) {
2108
0
      nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2109
0
                                                   &aDescription);
2110
0
    }
2111
0
  }
2112
0
}
2113
2114
// Accessible protected
2115
void
2116
Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent)
2117
0
{
2118
0
  MOZ_ASSERT(aParent, "This method isn't used to set null parent");
2119
0
  MOZ_ASSERT(!mParent, "The child was expected to be moved");
2120
0
2121
0
#ifdef A11Y_LOG
2122
0
  if (mParent) {
2123
0
    logging::TreeInfo("BindToParent: stealing accessible", 0,
2124
0
                      "old parent", mParent,
2125
0
                      "new parent", aParent,
2126
0
                      "child", this, nullptr);
2127
0
  }
2128
0
#endif
2129
0
2130
0
  mParent = aParent;
2131
0
  mIndexInParent = aIndexInParent;
2132
0
2133
0
  // Note: this is currently only used for richlistitems and their children.
2134
0
  if (mParent->HasNameDependentParent() || mParent->IsXULListItem())
2135
0
    mContextFlags |= eHasNameDependentParent;
2136
0
  else
2137
0
    mContextFlags &= ~eHasNameDependentParent;
2138
0
2139
0
  mContextFlags |=
2140
0
    static_cast<uint32_t>((mParent->IsAlert() ||
2141
0
                           mParent->IsInsideAlert())) & eInsideAlert;
2142
0
}
2143
2144
// Accessible protected
2145
void
2146
Accessible::UnbindFromParent()
2147
0
{
2148
0
  mParent = nullptr;
2149
0
  mIndexInParent = -1;
2150
0
  mInt.mIndexOfEmbeddedChild = -1;
2151
0
  if (IsProxy())
2152
0
    MOZ_CRASH("this should never be called on proxy wrappers");
2153
0
2154
0
  delete mBits.groupInfo;
2155
0
  mBits.groupInfo = nullptr;
2156
0
  mContextFlags &= ~eHasNameDependentParent & ~eInsideAlert;
2157
0
}
2158
2159
////////////////////////////////////////////////////////////////////////////////
2160
// Accessible public methods
2161
2162
RootAccessible*
2163
Accessible::RootAccessible() const
2164
0
{
2165
0
  nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2166
0
  NS_ASSERTION(docShell, "No docshell for mContent");
2167
0
  if (!docShell) {
2168
0
    return nullptr;
2169
0
  }
2170
0
2171
0
  nsCOMPtr<nsIDocShellTreeItem> root;
2172
0
  docShell->GetRootTreeItem(getter_AddRefs(root));
2173
0
  NS_ASSERTION(root, "No root content tree item");
2174
0
  if (!root) {
2175
0
    return nullptr;
2176
0
  }
2177
0
2178
0
  DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
2179
0
  return docAcc ? docAcc->AsRoot() : nullptr;
2180
0
}
2181
2182
nsIFrame*
2183
Accessible::GetFrame() const
2184
0
{
2185
0
  return mContent ? mContent->GetPrimaryFrame() : nullptr;
2186
0
}
2187
2188
nsINode*
2189
Accessible::GetNode() const
2190
0
{
2191
0
  return mContent;
2192
0
}
2193
2194
void
2195
Accessible::Language(nsAString& aLanguage)
2196
0
{
2197
0
  aLanguage.Truncate();
2198
0
2199
0
  if (!mDoc)
2200
0
    return;
2201
0
2202
0
  nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
2203
0
  if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
2204
0
    mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
2205
0
                                        aLanguage);
2206
0
  }
2207
0
}
2208
2209
bool
2210
Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild)
2211
0
{
2212
0
  if (!aChild)
2213
0
    return false;
2214
0
2215
0
  if (aIndex == mChildren.Length()) {
2216
0
    if (!mChildren.AppendElement(aChild))
2217
0
      return false;
2218
0
2219
0
  } else {
2220
0
    if (!mChildren.InsertElementAt(aIndex, aChild))
2221
0
      return false;
2222
0
2223
0
    MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change");
2224
0
2225
0
    for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
2226
0
      mChildren[idx]->mIndexInParent = idx;
2227
0
    }
2228
0
  }
2229
0
2230
0
  if (aChild->IsText()) {
2231
0
    mStateFlags |= eHasTextKids;
2232
0
  }
2233
0
2234
0
  aChild->BindToParent(this, aIndex);
2235
0
  return true;
2236
0
}
2237
2238
bool
2239
Accessible::RemoveChild(Accessible* aChild)
2240
0
{
2241
0
  MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2242
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent");
2243
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent");
2244
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
2245
0
  MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||
2246
0
                        aChild->IsDoc() || IsApplication(),
2247
0
                        "Illicit children change");
2248
0
2249
0
  int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
2250
0
  if (mChildren.SafeElementAt(index) != aChild) {
2251
0
    MOZ_ASSERT_UNREACHABLE("A wrong child index");
2252
0
    index = mChildren.IndexOf(aChild);
2253
0
    if (index == -1) {
2254
0
      MOZ_ASSERT_UNREACHABLE("No child was found");
2255
0
      return false;
2256
0
    }
2257
0
  }
2258
0
2259
0
  aChild->UnbindFromParent();
2260
0
  mChildren.RemoveElementAt(index);
2261
0
2262
0
  for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
2263
0
    mChildren[idx]->mIndexInParent = idx;
2264
0
  }
2265
0
2266
0
  return true;
2267
0
}
2268
2269
void
2270
Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild)
2271
0
{
2272
0
  MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given");
2273
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "A child from different subtree was given");
2274
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given");
2275
0
  MOZ_DIAGNOSTIC_ASSERT(aChild->mParent->GetChildAt(aChild->mIndexInParent) == aChild, "Wrong index in parent");
2276
0
  MOZ_DIAGNOSTIC_ASSERT(static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,
2277
0
             "No move, same index");
2278
0
  MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given");
2279
0
2280
0
  RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
2281
0
  if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
2282
0
    aChild->SetHideEventTarget(true);
2283
0
  }
2284
0
2285
0
  mEmbeddedObjCollector = nullptr;
2286
0
  mChildren.RemoveElementAt(aChild->mIndexInParent);
2287
0
2288
0
  uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
2289
0
2290
0
  // If the child is moved after its current position.
2291
0
  if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
2292
0
    startIdx = aChild->mIndexInParent;
2293
0
    if (aNewIndex == mChildren.Length() + 1) {
2294
0
      // The child is moved to the end.
2295
0
      mChildren.AppendElement(aChild);
2296
0
      endIdx = mChildren.Length() - 1;
2297
0
    }
2298
0
    else {
2299
0
      mChildren.InsertElementAt(aNewIndex - 1, aChild);
2300
0
      endIdx = aNewIndex;
2301
0
    }
2302
0
  }
2303
0
  else {
2304
0
    // The child is moved prior its current position.
2305
0
    mChildren.InsertElementAt(aNewIndex, aChild);
2306
0
  }
2307
0
2308
0
  for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
2309
0
    mChildren[idx]->mIndexInParent = idx;
2310
0
    mChildren[idx]->mStateFlags |= eGroupInfoDirty;
2311
0
    mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
2312
0
  }
2313
0
2314
0
  RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
2315
0
  DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
2316
0
  MOZ_ASSERT(added);
2317
0
  aChild->SetShowEventTarget(true);
2318
0
}
2319
2320
Accessible*
2321
Accessible::GetChildAt(uint32_t aIndex) const
2322
0
{
2323
0
  Accessible* child = mChildren.SafeElementAt(aIndex, nullptr);
2324
0
  if (!child)
2325
0
    return nullptr;
2326
0
2327
#ifdef DEBUG
2328
  Accessible* realParent = child->mParent;
2329
  NS_ASSERTION(!realParent || realParent == this,
2330
               "Two accessibles have the same first child accessible!");
2331
#endif
2332
2333
0
  return child;
2334
0
}
2335
2336
uint32_t
2337
Accessible::ChildCount() const
2338
0
{
2339
0
  return mChildren.Length();
2340
0
}
2341
2342
int32_t
2343
Accessible::IndexInParent() const
2344
0
{
2345
0
  return mIndexInParent;
2346
0
}
2347
2348
uint32_t
2349
Accessible::EmbeddedChildCount()
2350
0
{
2351
0
  if (mStateFlags & eHasTextKids) {
2352
0
    if (!mEmbeddedObjCollector)
2353
0
      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2354
0
    return mEmbeddedObjCollector->Count();
2355
0
  }
2356
0
2357
0
  return ChildCount();
2358
0
}
2359
2360
Accessible*
2361
Accessible::GetEmbeddedChildAt(uint32_t aIndex)
2362
0
{
2363
0
  if (mStateFlags & eHasTextKids) {
2364
0
    if (!mEmbeddedObjCollector)
2365
0
      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2366
0
    return mEmbeddedObjCollector.get() ?
2367
0
      mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr;
2368
0
  }
2369
0
2370
0
  return GetChildAt(aIndex);
2371
0
}
2372
2373
int32_t
2374
Accessible::GetIndexOfEmbeddedChild(Accessible* aChild)
2375
0
{
2376
0
  if (mStateFlags & eHasTextKids) {
2377
0
    if (!mEmbeddedObjCollector)
2378
0
      mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2379
0
    return mEmbeddedObjCollector.get() ?
2380
0
      mEmbeddedObjCollector->GetIndexAt(aChild) : -1;
2381
0
  }
2382
0
2383
0
  return GetIndexOf(aChild);
2384
0
}
2385
2386
////////////////////////////////////////////////////////////////////////////////
2387
// HyperLinkAccessible methods
2388
2389
bool
2390
Accessible::IsLink() const
2391
0
{
2392
0
  // Every embedded accessible within hypertext accessible implements
2393
0
  // hyperlink interface.
2394
0
  return mParent && mParent->IsHyperText() && !IsText();
2395
0
}
2396
2397
uint32_t
2398
Accessible::StartOffset()
2399
0
{
2400
0
  MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!");
2401
0
2402
0
  HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
2403
0
  return hyperText ? hyperText->GetChildOffset(this) : 0;
2404
0
}
2405
2406
uint32_t
2407
Accessible::EndOffset()
2408
0
{
2409
0
  MOZ_ASSERT(IsLink(), "EndOffset is called on not hyper link!");
2410
0
2411
0
  HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr;
2412
0
  return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0;
2413
0
}
2414
2415
uint32_t
2416
Accessible::AnchorCount()
2417
0
{
2418
0
  MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!");
2419
0
  return 1;
2420
0
}
2421
2422
Accessible*
2423
Accessible::AnchorAt(uint32_t aAnchorIndex)
2424
0
{
2425
0
  MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!");
2426
0
  return aAnchorIndex == 0 ? this : nullptr;
2427
0
}
2428
2429
already_AddRefed<nsIURI>
2430
Accessible::AnchorURIAt(uint32_t aAnchorIndex) const
2431
0
{
2432
0
  MOZ_ASSERT(IsLink(), "AnchorURIAt is called on not hyper link!");
2433
0
  return nullptr;
2434
0
}
2435
2436
void
2437
Accessible::ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
2438
                        bool aIsBefore) const
2439
0
{
2440
0
  if (IsHyperText()) {
2441
0
    *aContainer = const_cast<Accessible*>(this)->AsHyperText();
2442
0
    *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
2443
0
    return;
2444
0
  }
2445
0
2446
0
  const Accessible* child = nullptr;
2447
0
  const Accessible* parent = this;
2448
0
  do {
2449
0
    child = parent;
2450
0
    parent = parent->Parent();
2451
0
  } while (parent && !parent->IsHyperText());
2452
0
2453
0
  if (parent) {
2454
0
    *aContainer = const_cast<Accessible*>(parent)->AsHyperText();
2455
0
    *aOffset = (*aContainer)->GetChildOffset(
2456
0
      child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
2457
0
  }
2458
0
}
2459
2460
2461
////////////////////////////////////////////////////////////////////////////////
2462
// SelectAccessible
2463
2464
void
2465
Accessible::SelectedItems(nsTArray<Accessible*>* aItems)
2466
0
{
2467
0
  AccIterator iter(this, filters::GetSelected);
2468
0
  Accessible* selected = nullptr;
2469
0
  while ((selected = iter.Next()))
2470
0
    aItems->AppendElement(selected);
2471
0
}
2472
2473
uint32_t
2474
Accessible::SelectedItemCount()
2475
0
{
2476
0
  uint32_t count = 0;
2477
0
  AccIterator iter(this, filters::GetSelected);
2478
0
  Accessible* selected = nullptr;
2479
0
  while ((selected = iter.Next()))
2480
0
    ++count;
2481
0
2482
0
  return count;
2483
0
}
2484
2485
Accessible*
2486
Accessible::GetSelectedItem(uint32_t aIndex)
2487
0
{
2488
0
  AccIterator iter(this, filters::GetSelected);
2489
0
  Accessible* selected = nullptr;
2490
0
2491
0
  uint32_t index = 0;
2492
0
  while ((selected = iter.Next()) && index < aIndex)
2493
0
    index++;
2494
0
2495
0
  return selected;
2496
0
}
2497
2498
bool
2499
Accessible::IsItemSelected(uint32_t aIndex)
2500
0
{
2501
0
  uint32_t index = 0;
2502
0
  AccIterator iter(this, filters::GetSelectable);
2503
0
  Accessible* selected = nullptr;
2504
0
  while ((selected = iter.Next()) && index < aIndex)
2505
0
    index++;
2506
0
2507
0
  return selected &&
2508
0
    selected->State() & states::SELECTED;
2509
0
}
2510
2511
bool
2512
Accessible::AddItemToSelection(uint32_t aIndex)
2513
0
{
2514
0
  uint32_t index = 0;
2515
0
  AccIterator iter(this, filters::GetSelectable);
2516
0
  Accessible* selected = nullptr;
2517
0
  while ((selected = iter.Next()) && index < aIndex)
2518
0
    index++;
2519
0
2520
0
  if (selected)
2521
0
    selected->SetSelected(true);
2522
0
2523
0
  return static_cast<bool>(selected);
2524
0
}
2525
2526
bool
2527
Accessible::RemoveItemFromSelection(uint32_t aIndex)
2528
0
{
2529
0
  uint32_t index = 0;
2530
0
  AccIterator iter(this, filters::GetSelectable);
2531
0
  Accessible* selected = nullptr;
2532
0
  while ((selected = iter.Next()) && index < aIndex)
2533
0
    index++;
2534
0
2535
0
  if (selected)
2536
0
    selected->SetSelected(false);
2537
0
2538
0
  return static_cast<bool>(selected);
2539
0
}
2540
2541
bool
2542
Accessible::SelectAll()
2543
0
{
2544
0
  bool success = false;
2545
0
  Accessible* selectable = nullptr;
2546
0
2547
0
  AccIterator iter(this, filters::GetSelectable);
2548
0
  while((selectable = iter.Next())) {
2549
0
    success = true;
2550
0
    selectable->SetSelected(true);
2551
0
  }
2552
0
  return success;
2553
0
}
2554
2555
bool
2556
Accessible::UnselectAll()
2557
0
{
2558
0
  bool success = false;
2559
0
  Accessible* selected = nullptr;
2560
0
2561
0
  AccIterator iter(this, filters::GetSelected);
2562
0
  while ((selected = iter.Next())) {
2563
0
    success = true;
2564
0
    selected->SetSelected(false);
2565
0
  }
2566
0
  return success;
2567
0
}
2568
2569
////////////////////////////////////////////////////////////////////////////////
2570
// Widgets
2571
2572
bool
2573
Accessible::IsWidget() const
2574
0
{
2575
0
  return false;
2576
0
}
2577
2578
bool
2579
Accessible::IsActiveWidget() const
2580
0
{
2581
0
  if (FocusMgr()->HasDOMFocus(mContent))
2582
0
    return true;
2583
0
2584
0
  // If text entry of combobox widget has a focus then the combobox widget is
2585
0
  // active.
2586
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2587
0
  if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
2588
0
    uint32_t childCount = ChildCount();
2589
0
    for (uint32_t idx = 0; idx < childCount; idx++) {
2590
0
      Accessible* child = mChildren.ElementAt(idx);
2591
0
      if (child->Role() == roles::ENTRY)
2592
0
        return FocusMgr()->HasDOMFocus(child->GetContent());
2593
0
    }
2594
0
  }
2595
0
2596
0
  return false;
2597
0
}
2598
2599
bool
2600
Accessible::AreItemsOperable() const
2601
0
{
2602
0
  return HasOwnContent() &&
2603
0
    mContent->IsElement() &&
2604
0
    mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant);
2605
0
}
2606
2607
Accessible*
2608
Accessible::CurrentItem() const
2609
0
{
2610
0
  // Check for aria-activedescendant, which changes which element has focus.
2611
0
  // For activedescendant, the ARIA spec does not require that the user agent
2612
0
  // checks whether pointed node is actually a DOM descendant of the element
2613
0
  // with the aria-activedescendant attribute.
2614
0
  nsAutoString id;
2615
0
  if (HasOwnContent() &&
2616
0
      mContent->IsElement() &&
2617
0
      mContent->AsElement()->GetAttr(kNameSpaceID_None,
2618
0
                                     nsGkAtoms::aria_activedescendant, id)) {
2619
0
    nsIDocument* DOMDoc = mContent->OwnerDoc();
2620
0
    dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
2621
0
    if (activeDescendantElm) {
2622
0
      DocAccessible* document = Document();
2623
0
      if (document)
2624
0
        return document->GetAccessible(activeDescendantElm);
2625
0
    }
2626
0
  }
2627
0
  return nullptr;
2628
0
}
2629
2630
void
2631
Accessible::SetCurrentItem(const Accessible* aItem)
2632
0
{
2633
0
  nsAtom* id = aItem->GetContent()->GetID();
2634
0
  if (id) {
2635
0
    nsAutoString idStr;
2636
0
    id->ToString(idStr);
2637
0
    mContent->AsElement()->SetAttr(kNameSpaceID_None,
2638
0
                                   nsGkAtoms::aria_activedescendant,
2639
0
                                   idStr,
2640
0
                                   true);
2641
0
  }
2642
0
}
2643
2644
Accessible*
2645
Accessible::ContainerWidget() const
2646
0
{
2647
0
  if (HasARIARole() && mContent->HasID()) {
2648
0
    for (Accessible* parent = Parent(); parent; parent = parent->Parent()) {
2649
0
      nsIContent* parentContent = parent->GetContent();
2650
0
      if (parentContent &&
2651
0
          parentContent->IsElement() &&
2652
0
          parentContent->AsElement()->HasAttr(kNameSpaceID_None,
2653
0
                                              nsGkAtoms::aria_activedescendant)) {
2654
0
        return parent;
2655
0
      }
2656
0
2657
0
      // Don't cross DOM document boundaries.
2658
0
      if (parent->IsDoc())
2659
0
        break;
2660
0
    }
2661
0
  }
2662
0
  return nullptr;
2663
0
}
2664
2665
////////////////////////////////////////////////////////////////////////////////
2666
// Accessible protected methods
2667
2668
void
2669
Accessible::LastRelease()
2670
0
{
2671
0
  // First cleanup if needed...
2672
0
  if (mDoc) {
2673
0
    Shutdown();
2674
0
    NS_ASSERTION(!mDoc,
2675
0
                 "A Shutdown() impl forgot to call its parent's Shutdown?");
2676
0
  }
2677
0
  // ... then die.
2678
0
  delete this;
2679
0
}
2680
2681
Accessible*
2682
Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const
2683
0
{
2684
0
  if (!mParent || mIndexInParent == -1) {
2685
0
    if (aError)
2686
0
      *aError = NS_ERROR_UNEXPECTED;
2687
0
2688
0
    return nullptr;
2689
0
  }
2690
0
2691
0
  if (aError &&
2692
0
      mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
2693
0
    *aError = NS_OK; // fail peacefully
2694
0
    return nullptr;
2695
0
  }
2696
0
2697
0
  Accessible* child = mParent->GetChildAt(mIndexInParent + aOffset);
2698
0
  if (aError && !child)
2699
0
    *aError = NS_ERROR_UNEXPECTED;
2700
0
2701
0
  return child;
2702
0
}
2703
2704
double
2705
Accessible::AttrNumericValue(nsAtom* aAttr) const
2706
0
{
2707
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2708
0
  if (!roleMapEntry || roleMapEntry->valueRule == eNoValue)
2709
0
    return UnspecifiedNaN<double>();
2710
0
2711
0
  nsAutoString attrValue;
2712
0
  if (!mContent->IsElement() ||
2713
0
      !mContent->AsElement()->GetAttr(kNameSpaceID_None, aAttr, attrValue))
2714
0
    return UnspecifiedNaN<double>();
2715
0
2716
0
  nsresult error = NS_OK;
2717
0
  double value = attrValue.ToDouble(&error);
2718
0
  return NS_FAILED(error) ? UnspecifiedNaN<double>() : value;
2719
0
}
2720
2721
uint32_t
2722
Accessible::GetActionRule() const
2723
0
{
2724
0
  if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE))
2725
0
    return eNoAction;
2726
0
2727
0
  // Return "click" action on elements that have an attached popup menu.
2728
0
  if (mContent->IsXULElement())
2729
0
    if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
2730
0
      return eClickAction;
2731
0
2732
0
  // Has registered 'click' event handler.
2733
0
  bool isOnclick = nsCoreUtils::HasClickListener(mContent);
2734
0
2735
0
  if (isOnclick)
2736
0
    return eClickAction;
2737
0
2738
0
  // Get an action based on ARIA role.
2739
0
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
2740
0
  if (roleMapEntry &&
2741
0
      roleMapEntry->actionRule != eNoAction)
2742
0
    return roleMapEntry->actionRule;
2743
0
2744
0
  // Get an action based on ARIA attribute.
2745
0
  if (nsAccUtils::HasDefinedARIAToken(mContent,
2746
0
                                      nsGkAtoms::aria_expanded))
2747
0
    return eExpandAction;
2748
0
2749
0
  return eNoAction;
2750
0
}
2751
2752
AccGroupInfo*
2753
Accessible::GetGroupInfo() const
2754
0
{
2755
0
  if (IsProxy())
2756
0
    MOZ_CRASH("This should never be called on proxy wrappers");
2757
0
2758
0
  if (mBits.groupInfo){
2759
0
    if (HasDirtyGroupInfo()) {
2760
0
      mBits.groupInfo->Update();
2761
0
      mStateFlags &= ~eGroupInfoDirty;
2762
0
    }
2763
0
2764
0
    return mBits.groupInfo;
2765
0
  }
2766
0
2767
0
  mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this);
2768
0
  return mBits.groupInfo;
2769
0
}
2770
2771
void
2772
Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize)
2773
0
{
2774
0
  AccGroupInfo* groupInfo = GetGroupInfo();
2775
0
  if (groupInfo) {
2776
0
    *aPosInSet = groupInfo->PosInSet();
2777
0
    *aSetSize = groupInfo->SetSize();
2778
0
  }
2779
0
}
2780
2781
int32_t
2782
Accessible::GetLevelInternal()
2783
0
{
2784
0
  int32_t level = nsAccUtils::GetDefaultLevel(this);
2785
0
2786
0
  if (!IsBoundToParent())
2787
0
    return level;
2788
0
2789
0
  roles::Role role = Role();
2790
0
  if (role == roles::OUTLINEITEM) {
2791
0
    // Always expose 'level' attribute for 'outlineitem' accessible. The number
2792
0
    // of nested 'grouping' accessibles containing 'outlineitem' accessible is
2793
0
    // its level.
2794
0
    level = 1;
2795
0
2796
0
    Accessible* parent = this;
2797
0
    while ((parent = parent->Parent())) {
2798
0
      roles::Role parentRole = parent->Role();
2799
0
2800
0
      if (parentRole == roles::OUTLINE)
2801
0
        break;
2802
0
      if (parentRole == roles::GROUPING)
2803
0
        ++ level;
2804
0
2805
0
    }
2806
0
2807
0
  } else if (role == roles::LISTITEM) {
2808
0
    // Expose 'level' attribute on nested lists. We support two hierarchies:
2809
0
    // a) list -> listitem -> list -> listitem (nested list is a last child
2810
0
    //   of listitem of the parent list);
2811
0
    // b) list -> listitem -> group -> listitem (nested listitems are contained
2812
0
    //   by group that is a last child of the parent listitem).
2813
0
2814
0
    // Calculate 'level' attribute based on number of parent listitems.
2815
0
    level = 0;
2816
0
    Accessible* parent = this;
2817
0
    while ((parent = parent->Parent())) {
2818
0
      roles::Role parentRole = parent->Role();
2819
0
2820
0
      if (parentRole == roles::LISTITEM)
2821
0
        ++ level;
2822
0
      else if (parentRole != roles::LIST && parentRole != roles::GROUPING)
2823
0
        break;
2824
0
    }
2825
0
2826
0
    if (level == 0) {
2827
0
      // If this listitem is on top of nested lists then expose 'level'
2828
0
      // attribute.
2829
0
      parent = Parent();
2830
0
      uint32_t siblingCount = parent->ChildCount();
2831
0
      for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) {
2832
0
        Accessible* sibling = parent->GetChildAt(siblingIdx);
2833
0
2834
0
        Accessible* siblingChild = sibling->LastChild();
2835
0
        if (siblingChild) {
2836
0
          roles::Role lastChildRole = siblingChild->Role();
2837
0
          if (lastChildRole == roles::LIST || lastChildRole == roles::GROUPING)
2838
0
            return 1;
2839
0
        }
2840
0
      }
2841
0
    } else {
2842
0
      ++ level; // level is 1-index based
2843
0
    }
2844
0
  }
2845
0
2846
0
  return level;
2847
0
}
2848
2849
void
2850
Accessible::StaticAsserts() const
2851
0
{
2852
0
  static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1,
2853
0
                "Accessible::mStateFlags was oversized by eLastStateFlag!");
2854
0
  static_assert(eLastAccType <= (1 << kTypeBits) - 1,
2855
0
                "Accessible::mType was oversized by eLastAccType!");
2856
0
  static_assert(eLastContextFlag <= (1 << kContextFlagsBits) - 1,
2857
0
                "Accessible::mContextFlags was oversized by eLastContextFlag!");
2858
0
  static_assert(eLastAccGenericType <= (1 << kGenericTypesBits) - 1,
2859
0
                "Accessible::mGenericType was oversized by eLastAccGenericType!");
2860
0
}
2861
2862
////////////////////////////////////////////////////////////////////////////////
2863
// KeyBinding class
2864
2865
// static
2866
uint32_t
2867
KeyBinding::AccelModifier()
2868
0
{
2869
0
  switch (WidgetInputEvent::AccelModifier()) {
2870
0
    case MODIFIER_ALT:
2871
0
      return kAlt;
2872
0
    case MODIFIER_CONTROL:
2873
0
      return kControl;
2874
0
    case MODIFIER_META:
2875
0
      return kMeta;
2876
0
    case MODIFIER_OS:
2877
0
      return kOS;
2878
0
    default:
2879
0
      MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()");
2880
0
      return 0;
2881
0
  }
2882
0
}
2883
2884
void
2885
KeyBinding::ToPlatformFormat(nsAString& aValue) const
2886
0
{
2887
0
  nsCOMPtr<nsIStringBundle> keyStringBundle;
2888
0
  nsCOMPtr<nsIStringBundleService> stringBundleService =
2889
0
      mozilla::services::GetStringBundleService();
2890
0
  if (stringBundleService)
2891
0
    stringBundleService->CreateBundle(
2892
0
      "chrome://global-platform/locale/platformKeys.properties",
2893
0
      getter_AddRefs(keyStringBundle));
2894
0
2895
0
  if (!keyStringBundle)
2896
0
    return;
2897
0
2898
0
  nsAutoString separator;
2899
0
  keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator);
2900
0
2901
0
  nsAutoString modifierName;
2902
0
  if (mModifierMask & kControl) {
2903
0
    keyStringBundle->GetStringFromName("VK_CONTROL", modifierName);
2904
0
2905
0
    aValue.Append(modifierName);
2906
0
    aValue.Append(separator);
2907
0
  }
2908
0
2909
0
  if (mModifierMask & kAlt) {
2910
0
    keyStringBundle->GetStringFromName("VK_ALT", modifierName);
2911
0
2912
0
    aValue.Append(modifierName);
2913
0
    aValue.Append(separator);
2914
0
  }
2915
0
2916
0
  if (mModifierMask & kShift) {
2917
0
    keyStringBundle->GetStringFromName("VK_SHIFT", modifierName);
2918
0
2919
0
    aValue.Append(modifierName);
2920
0
    aValue.Append(separator);
2921
0
  }
2922
0
2923
0
  if (mModifierMask & kMeta) {
2924
0
    keyStringBundle->GetStringFromName("VK_META", modifierName);
2925
0
2926
0
    aValue.Append(modifierName);
2927
0
    aValue.Append(separator);
2928
0
  }
2929
0
2930
0
  aValue.Append(mKey);
2931
0
}
2932
2933
void
2934
KeyBinding::ToAtkFormat(nsAString& aValue) const
2935
0
{
2936
0
  nsAutoString modifierName;
2937
0
  if (mModifierMask & kControl)
2938
0
    aValue.AppendLiteral("<Control>");
2939
0
2940
0
  if (mModifierMask & kAlt)
2941
0
    aValue.AppendLiteral("<Alt>");
2942
0
2943
0
  if (mModifierMask & kShift)
2944
0
    aValue.AppendLiteral("<Shift>");
2945
0
2946
0
  if (mModifierMask & kMeta)
2947
0
      aValue.AppendLiteral("<Meta>");
2948
0
2949
0
  aValue.Append(mKey);
2950
0
}