Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/html/HTMLSelectAccessible.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; 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 "HTMLSelectAccessible.h"
7
8
#include "Accessible-inl.h"
9
#include "nsAccessibilityService.h"
10
#include "nsAccUtils.h"
11
#include "DocAccessible.h"
12
#include "nsEventShell.h"
13
#include "nsTextEquivUtils.h"
14
#include "Role.h"
15
#include "States.h"
16
17
#include "nsCOMPtr.h"
18
#include "mozilla/dom/HTMLOptionElement.h"
19
#include "mozilla/dom/HTMLSelectElement.h"
20
#include "nsIComboboxControlFrame.h"
21
#include "nsContainerFrame.h"
22
#include "nsIListControlFrame.h"
23
24
using namespace mozilla::a11y;
25
using namespace mozilla::dom;
26
27
////////////////////////////////////////////////////////////////////////////////
28
// HTMLSelectListAccessible
29
////////////////////////////////////////////////////////////////////////////////
30
31
HTMLSelectListAccessible::
32
  HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc) :
33
  AccessibleWrap(aContent, aDoc)
34
0
{
35
0
  mGenericTypes |= eListControl | eSelect;
36
0
}
37
38
////////////////////////////////////////////////////////////////////////////////
39
// HTMLSelectListAccessible: Accessible public
40
41
uint64_t
42
HTMLSelectListAccessible::NativeState() const
43
0
{
44
0
  uint64_t state = AccessibleWrap::NativeState();
45
0
  if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
46
0
    state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
47
0
48
0
  return state;
49
0
}
50
51
role
52
HTMLSelectListAccessible::NativeRole() const
53
0
{
54
0
  return roles::LISTBOX;
55
0
}
56
57
////////////////////////////////////////////////////////////////////////////////
58
// HTMLSelectListAccessible: SelectAccessible
59
60
bool
61
HTMLSelectListAccessible::SelectAll()
62
0
{
63
0
  return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
64
0
    AccessibleWrap::SelectAll() : false;
65
0
}
66
67
bool
68
HTMLSelectListAccessible::UnselectAll()
69
0
{
70
0
  return mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
71
0
    AccessibleWrap::UnselectAll() : false;
72
0
}
73
74
////////////////////////////////////////////////////////////////////////////////
75
// HTMLSelectListAccessible: Widgets
76
77
bool
78
HTMLSelectListAccessible::IsWidget() const
79
0
{
80
0
  return true;
81
0
}
82
83
bool
84
HTMLSelectListAccessible::IsActiveWidget() const
85
0
{
86
0
  return FocusMgr()->HasDOMFocus(mContent);
87
0
}
88
89
bool
90
HTMLSelectListAccessible::AreItemsOperable() const
91
0
{
92
0
  return true;
93
0
}
94
95
Accessible*
96
HTMLSelectListAccessible::CurrentItem() const
97
0
{
98
0
  nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
99
0
  if (listControlFrame) {
100
0
    nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
101
0
    if (activeOptionNode) {
102
0
      DocAccessible* document = Document();
103
0
      if (document)
104
0
        return document->GetAccessible(activeOptionNode);
105
0
    }
106
0
  }
107
0
  return nullptr;
108
0
}
109
110
void
111
HTMLSelectListAccessible::SetCurrentItem(const Accessible* aItem)
112
0
{
113
0
  if (!aItem->GetContent()->IsElement())
114
0
    return;
115
0
116
0
  aItem->GetContent()->AsElement()->SetAttr(kNameSpaceID_None,
117
0
                                            nsGkAtoms::selected,
118
0
                                            NS_LITERAL_STRING("true"),
119
0
                                            true);
120
0
}
121
122
bool
123
HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const
124
0
{
125
0
  return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
126
0
}
127
128
////////////////////////////////////////////////////////////////////////////////
129
// HTMLSelectOptionAccessible
130
////////////////////////////////////////////////////////////////////////////////
131
132
HTMLSelectOptionAccessible::
133
  HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
134
  HyperTextAccessibleWrap(aContent, aDoc)
135
0
{
136
0
}
137
138
////////////////////////////////////////////////////////////////////////////////
139
// HTMLSelectOptionAccessible: Accessible public
140
141
role
142
HTMLSelectOptionAccessible::NativeRole() const
143
0
{
144
0
  if (GetCombobox())
145
0
    return roles::COMBOBOX_OPTION;
146
0
147
0
  return roles::OPTION;
148
0
}
149
150
ENameValueFlag
151
HTMLSelectOptionAccessible::NativeName(nsString& aName) const
152
0
{
153
0
  // CASE #1 -- great majority of the cases
154
0
  // find the label attribute - this is what the W3C says we should use
155
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
156
0
  if (!aName.IsEmpty())
157
0
    return eNameOK;
158
0
159
0
  // CASE #2 -- no label parameter, get the first child,
160
0
  // use it if it is a text node
161
0
  nsIContent* text = mContent->GetFirstChild();
162
0
  if (text && text->IsText()) {
163
0
    nsTextEquivUtils::AppendTextEquivFromTextContent(text, &aName);
164
0
    aName.CompressWhitespace();
165
0
    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
166
0
  }
167
0
168
0
  return eNameOK;
169
0
}
170
171
uint64_t
172
HTMLSelectOptionAccessible::NativeState() const
173
0
{
174
0
  // As a HTMLSelectOptionAccessible we can have the following states:
175
0
  // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
176
0
  // Upcall to Accessible, but skip HyperTextAccessible impl
177
0
  // because we don't want EDITABLE or SELECTABLE_TEXT
178
0
  uint64_t state = Accessible::NativeState();
179
0
180
0
  Accessible* select = GetSelect();
181
0
  if (!select)
182
0
    return state;
183
0
184
0
  uint64_t selectState = select->State();
185
0
  if (selectState & states::INVISIBLE)
186
0
    return state;
187
0
188
0
  // Are we selected?
189
0
  HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
190
0
  bool selected = option && option->Selected();
191
0
  if (selected)
192
0
    state |= states::SELECTED;
193
0
194
0
  if (selectState & states::OFFSCREEN) {
195
0
    state |= states::OFFSCREEN;
196
0
  } else if (selectState & states::COLLAPSED) {
197
0
    // <select> is COLLAPSED: add OFFSCREEN, if not the currently
198
0
    // visible option
199
0
    if (!selected) {
200
0
      state |= states::OFFSCREEN;
201
0
      // Ensure the invisible state is removed. Otherwise, group info will skip
202
0
      // this option. Furthermore, this gets cached and this doesn't get
203
0
      // invalidated even once the select is expanded.
204
0
      state &= ~states::INVISIBLE;
205
0
    } else {
206
0
      // Clear offscreen and invisible for currently showing option
207
0
      state &= ~(states::OFFSCREEN | states::INVISIBLE);
208
0
      state |= selectState & states::OPAQUE1;
209
0
    }
210
0
  } else {
211
0
    // XXX list frames are weird, don't rely on Accessible's general
212
0
    // visibility implementation unless they get reimplemented in layout
213
0
    state &= ~states::OFFSCREEN;
214
0
    // <select> is not collapsed: compare bounds to calculate OFFSCREEN
215
0
    Accessible* listAcc = Parent();
216
0
    if (listAcc) {
217
0
      nsIntRect optionRect = Bounds();
218
0
      nsIntRect listRect = listAcc->Bounds();
219
0
      if (optionRect.Y() < listRect.Y() ||
220
0
          optionRect.YMost() > listRect.YMost()) {
221
0
        state |= states::OFFSCREEN;
222
0
      }
223
0
    }
224
0
  }
225
0
226
0
  return state;
227
0
}
228
229
uint64_t
230
HTMLSelectOptionAccessible::NativeInteractiveState() const
231
0
{
232
0
  return NativelyUnavailable() ?
233
0
    states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
234
0
}
235
236
int32_t
237
HTMLSelectOptionAccessible::GetLevelInternal()
238
0
{
239
0
  nsIContent* parentContent = mContent->GetParent();
240
0
241
0
  int32_t level =
242
0
    parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
243
0
244
0
  if (level == 1 && Role() != roles::HEADING)
245
0
    level = 0; // In a single level list, the level is irrelevant
246
0
247
0
  return level;
248
0
}
249
250
nsRect
251
HTMLSelectOptionAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
252
0
{
253
0
  Accessible* combobox = GetCombobox();
254
0
  if (combobox && (combobox->State() & states::COLLAPSED))
255
0
    return combobox->RelativeBounds(aBoundingFrame);
256
0
257
0
  return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame);
258
0
}
259
260
void
261
HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
262
0
{
263
0
  if (aIndex == eAction_Select)
264
0
    aName.AssignLiteral("select");
265
0
}
266
267
uint8_t
268
HTMLSelectOptionAccessible::ActionCount() const
269
0
{
270
0
  return 1;
271
0
}
272
273
bool
274
HTMLSelectOptionAccessible::DoAction(uint8_t aIndex) const
275
0
{
276
0
  if (aIndex != eAction_Select)
277
0
    return false;
278
0
279
0
  DoCommand();
280
0
  return true;
281
0
}
282
283
void
284
HTMLSelectOptionAccessible::SetSelected(bool aSelect)
285
0
{
286
0
  HTMLOptionElement* option = HTMLOptionElement::FromNode(mContent);
287
0
  if (option)
288
0
    option->SetSelected(aSelect);
289
0
}
290
291
////////////////////////////////////////////////////////////////////////////////
292
// HTMLSelectOptionAccessible: Widgets
293
294
Accessible*
295
HTMLSelectOptionAccessible::ContainerWidget() const
296
0
{
297
0
  Accessible* parent = Parent();
298
0
  if (parent && parent->IsHTMLOptGroup())
299
0
    parent = parent->Parent();
300
0
301
0
  return parent && parent->IsListControl() ? parent : nullptr;
302
0
}
303
304
////////////////////////////////////////////////////////////////////////////////
305
// HTMLSelectOptGroupAccessible
306
////////////////////////////////////////////////////////////////////////////////
307
308
role
309
HTMLSelectOptGroupAccessible::NativeRole() const
310
0
{
311
0
  return roles::GROUPING;
312
0
}
313
314
uint64_t
315
HTMLSelectOptGroupAccessible::NativeInteractiveState() const
316
0
{
317
0
  return NativelyUnavailable() ? states::UNAVAILABLE : 0;
318
0
}
319
320
bool
321
HTMLSelectOptGroupAccessible::IsAcceptableChild(nsIContent* aEl) const
322
0
{
323
0
  return aEl->IsCharacterData() || aEl->IsHTMLElement(nsGkAtoms::option);
324
0
}
325
326
uint8_t
327
HTMLSelectOptGroupAccessible::ActionCount() const
328
0
{
329
0
  return 0;
330
0
}
331
332
void
333
HTMLSelectOptGroupAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
334
0
{
335
0
  aName.Truncate();
336
0
}
337
338
bool
339
HTMLSelectOptGroupAccessible::DoAction(uint8_t aIndex) const
340
0
{
341
0
  return false;
342
0
}
343
344
////////////////////////////////////////////////////////////////////////////////
345
// HTMLComboboxAccessible
346
////////////////////////////////////////////////////////////////////////////////
347
348
HTMLComboboxAccessible::
349
  HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
350
  AccessibleWrap(aContent, aDoc)
351
0
{
352
0
  mType = eHTMLComboboxType;
353
0
  mGenericTypes |= eCombobox;
354
0
  mStateFlags |= eNoKidsFromDOM;
355
0
356
0
  nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
357
0
  if (comboFrame) {
358
0
    nsIFrame* listFrame = comboFrame->GetDropDown();
359
0
    if (listFrame) {
360
0
      mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
361
0
      Document()->BindToDocument(mListAccessible, nullptr);
362
0
      AppendChild(mListAccessible);
363
0
    }
364
0
  }
365
0
}
366
367
////////////////////////////////////////////////////////////////////////////////
368
// HTMLComboboxAccessible: Accessible
369
370
role
371
HTMLComboboxAccessible::NativeRole() const
372
0
{
373
0
  return roles::COMBOBOX;
374
0
}
375
376
bool
377
HTMLComboboxAccessible::RemoveChild(Accessible* aChild)
378
0
{
379
0
  MOZ_ASSERT(aChild == mListAccessible);
380
0
  if (AccessibleWrap::RemoveChild(aChild)) {
381
0
    mListAccessible = nullptr;
382
0
    return true;
383
0
  }
384
0
  return false;
385
0
}
386
387
void
388
HTMLComboboxAccessible::Shutdown()
389
0
{
390
0
  MOZ_ASSERT(mDoc->IsDefunct() || !mListAccessible);
391
0
  if (mListAccessible) {
392
0
    mListAccessible->Shutdown();
393
0
    mListAccessible = nullptr;
394
0
  }
395
0
396
0
  AccessibleWrap::Shutdown();
397
0
}
398
399
uint64_t
400
HTMLComboboxAccessible::NativeState() const
401
0
{
402
0
  // As a HTMLComboboxAccessible we can have the following states:
403
0
  // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
404
0
  // Get focus status from base class
405
0
  uint64_t state = Accessible::NativeState();
406
0
407
0
  nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
408
0
  if (comboFrame && comboFrame->IsDroppedDown())
409
0
    state |= states::EXPANDED;
410
0
  else
411
0
    state |= states::COLLAPSED;
412
0
413
0
  state |= states::HASPOPUP;
414
0
  return state;
415
0
}
416
417
void
418
HTMLComboboxAccessible::Description(nsString& aDescription)
419
0
{
420
0
  aDescription.Truncate();
421
0
  // First check to see if combo box itself has a description, perhaps through
422
0
  // tooltip (title attribute) or via aria-describedby
423
0
  Accessible::Description(aDescription);
424
0
  if (!aDescription.IsEmpty())
425
0
    return;
426
0
427
0
  // Otherwise use description of selected option.
428
0
  Accessible* option = SelectedOption();
429
0
  if (option)
430
0
    option->Description(aDescription);
431
0
}
432
433
void
434
HTMLComboboxAccessible::Value(nsString& aValue) const
435
0
{
436
0
  // Use accessible name of selected option.
437
0
  Accessible* option = SelectedOption();
438
0
  if (option)
439
0
    option->Name(aValue);
440
0
}
441
442
uint8_t
443
HTMLComboboxAccessible::ActionCount() const
444
0
{
445
0
  return 1;
446
0
}
447
448
bool
449
HTMLComboboxAccessible::DoAction(uint8_t aIndex) const
450
0
{
451
0
  if (aIndex != eAction_Click)
452
0
    return false;
453
0
454
0
  DoCommand();
455
0
  return true;
456
0
}
457
458
void
459
HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
460
0
{
461
0
  if (aIndex != HTMLComboboxAccessible::eAction_Click)
462
0
    return;
463
0
464
0
  nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
465
0
  if (!comboFrame)
466
0
    return;
467
0
468
0
  if (comboFrame->IsDroppedDown())
469
0
    aName.AssignLiteral("close");
470
0
  else
471
0
    aName.AssignLiteral("open");
472
0
}
473
474
bool
475
HTMLComboboxAccessible::IsAcceptableChild(nsIContent* aEl) const
476
0
{
477
0
  return false;
478
0
}
479
480
////////////////////////////////////////////////////////////////////////////////
481
// HTMLComboboxAccessible: Widgets
482
483
bool
484
HTMLComboboxAccessible::IsWidget() const
485
0
{
486
0
  return true;
487
0
}
488
489
bool
490
HTMLComboboxAccessible::IsActiveWidget() const
491
0
{
492
0
  return FocusMgr()->HasDOMFocus(mContent);
493
0
}
494
495
bool
496
HTMLComboboxAccessible::AreItemsOperable() const
497
0
{
498
0
  nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
499
0
  return comboboxFrame && comboboxFrame->IsDroppedDown();
500
0
}
501
502
Accessible*
503
HTMLComboboxAccessible::CurrentItem() const
504
0
{
505
0
  return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
506
0
}
507
508
void
509
HTMLComboboxAccessible::SetCurrentItem(const Accessible* aItem)
510
0
{
511
0
  if (AreItemsOperable())
512
0
    mListAccessible->SetCurrentItem(aItem);
513
0
}
514
515
////////////////////////////////////////////////////////////////////////////////
516
// HTMLComboboxAccessible: protected
517
518
Accessible*
519
HTMLComboboxAccessible::SelectedOption() const
520
0
{
521
0
  HTMLSelectElement* select = HTMLSelectElement::FromNode(mContent);
522
0
  int32_t selectedIndex = select->SelectedIndex();
523
0
524
0
  if (selectedIndex >= 0) {
525
0
    HTMLOptionElement* option = select->Item(selectedIndex);
526
0
    if (option) {
527
0
      DocAccessible* document = Document();
528
0
      if (document)
529
0
        return document->GetAccessible(option);
530
0
    }
531
0
  }
532
0
533
0
  return nullptr;
534
0
}
535
536
537
////////////////////////////////////////////////////////////////////////////////
538
// HTMLComboboxListAccessible
539
////////////////////////////////////////////////////////////////////////////////
540
541
HTMLComboboxListAccessible::
542
  HTMLComboboxListAccessible(Accessible* aParent, nsIContent* aContent,
543
                             DocAccessible* aDoc) :
544
  HTMLSelectListAccessible(aContent, aDoc)
545
0
{
546
0
  mStateFlags |= eSharedNode;
547
0
}
548
549
////////////////////////////////////////////////////////////////////////////////
550
// HTMLComboboxAccessible: Accessible
551
552
nsIFrame*
553
HTMLComboboxListAccessible::GetFrame() const
554
0
{
555
0
  nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
556
0
  nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
557
0
  if (comboBox) {
558
0
    return comboBox->GetDropDown();
559
0
  }
560
0
561
0
  return nullptr;
562
0
}
563
564
role
565
HTMLComboboxListAccessible::NativeRole() const
566
0
{
567
0
  return roles::COMBOBOX_LIST;
568
0
}
569
570
uint64_t
571
HTMLComboboxListAccessible::NativeState() const
572
0
{
573
0
  // As a HTMLComboboxListAccessible we can have the following states:
574
0
  // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
575
0
  // Get focus status from base class
576
0
  uint64_t state = Accessible::NativeState();
577
0
578
0
  nsIComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
579
0
  if (comboFrame && comboFrame->IsDroppedDown())
580
0
    state |= states::FLOATING;
581
0
  else
582
0
    state |= states::INVISIBLE;
583
0
584
0
  return state;
585
0
}
586
587
nsRect
588
HTMLComboboxListAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
589
0
{
590
0
  *aBoundingFrame = nullptr;
591
0
592
0
  Accessible* comboAcc = Parent();
593
0
  if (!comboAcc)
594
0
    return nsRect();
595
0
596
0
  if (0 == (comboAcc->State() & states::COLLAPSED)) {
597
0
    return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
598
0
  }
599
0
600
0
  // Get the first option.
601
0
  nsIContent* content = mContent->GetFirstChild();
602
0
  if (!content)
603
0
    return nsRect();
604
0
605
0
  nsIFrame* frame = content->GetPrimaryFrame();
606
0
  if (!frame) {
607
0
    *aBoundingFrame = nullptr;
608
0
    return nsRect();
609
0
  }
610
0
611
0
  *aBoundingFrame = frame->GetParent();
612
0
  return (*aBoundingFrame)->GetRect();
613
0
}
614
615
bool
616
HTMLComboboxListAccessible::IsAcceptableChild(nsIContent* aEl) const
617
0
{
618
0
  return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
619
0
}
620
621
////////////////////////////////////////////////////////////////////////////////
622
// HTMLComboboxListAccessible: Widgets
623
624
bool
625
HTMLComboboxListAccessible::IsActiveWidget() const
626
0
{
627
0
  return mParent && mParent->IsActiveWidget();
628
0
}
629
630
bool
631
HTMLComboboxListAccessible::AreItemsOperable() const
632
0
{
633
0
  return mParent && mParent->AreItemsOperable();
634
0
}