Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/xul/XULMenuAccessible.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 "XULMenuAccessible.h"
7
8
#include "Accessible-inl.h"
9
#include "nsAccessibilityService.h"
10
#include "nsAccUtils.h"
11
#include "DocAccessible.h"
12
#include "Role.h"
13
#include "States.h"
14
#include "XULFormControlAccessible.h"
15
16
#include "nsIMutableArray.h"
17
#include "nsIDOMXULContainerElement.h"
18
#include "nsIDOMXULSelectCntrlItemEl.h"
19
#include "nsIDOMXULMultSelectCntrlEl.h"
20
#include "nsIServiceManager.h"
21
#include "nsIPresShell.h"
22
#include "nsIContent.h"
23
#include "nsMenuBarFrame.h"
24
#include "nsMenuPopupFrame.h"
25
26
#include "mozilla/Preferences.h"
27
#include "mozilla/LookAndFeel.h"
28
#include "mozilla/dom/Element.h"
29
#include "mozilla/dom/KeyboardEventBinding.h"
30
31
using namespace mozilla;
32
using namespace mozilla::a11y;
33
34
////////////////////////////////////////////////////////////////////////////////
35
// XULMenuitemAccessible
36
////////////////////////////////////////////////////////////////////////////////
37
38
XULMenuitemAccessible::
39
  XULMenuitemAccessible(nsIContent* aContent, DocAccessible* aDoc) :
40
  AccessibleWrap(aContent, aDoc)
41
0
{
42
0
  mStateFlags |= eNoXBLKids;
43
0
}
44
45
uint64_t
46
XULMenuitemAccessible::NativeState() const
47
0
{
48
0
  uint64_t state = Accessible::NativeState();
49
0
50
0
  // Has Popup?
51
0
  if (mContent->NodeInfo()->Equals(nsGkAtoms::menu, kNameSpaceID_XUL)) {
52
0
    state |= states::HASPOPUP;
53
0
    if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::open))
54
0
      state |= states::EXPANDED;
55
0
    else
56
0
      state |= states::COLLAPSED;
57
0
  }
58
0
59
0
  // Checkable/checked?
60
0
  static Element::AttrValuesArray strings[] =
61
0
    { &nsGkAtoms::radio, &nsGkAtoms::checkbox, nullptr };
62
0
63
0
  if (mContent->AsElement()->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
64
0
                                             strings, eCaseMatters) >= 0) {
65
0
66
0
    // Checkable?
67
0
    state |= states::CHECKABLE;
68
0
69
0
    // Checked?
70
0
    if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
71
0
                                           nsGkAtoms::_true, eCaseMatters))
72
0
      state |= states::CHECKED;
73
0
  }
74
0
75
0
  // Combo box listitem
76
0
  bool isComboboxOption = (Role() == roles::COMBOBOX_OPTION);
77
0
  if (isComboboxOption) {
78
0
    // Is selected?
79
0
    bool isSelected = false;
80
0
    nsCOMPtr<nsIDOMXULSelectControlItemElement>
81
0
      item(do_QueryInterface(mContent));
82
0
    NS_ENSURE_TRUE(item, state);
83
0
    item->GetSelected(&isSelected);
84
0
85
0
    // Is collapsed?
86
0
    bool isCollapsed = false;
87
0
    Accessible* parent = Parent();
88
0
    if (parent && parent->State() & states::INVISIBLE)
89
0
      isCollapsed = true;
90
0
91
0
    if (isSelected) {
92
0
      state |= states::SELECTED;
93
0
94
0
      // Selected and collapsed?
95
0
      if (isCollapsed) {
96
0
        // Set selected option offscreen/invisible according to combobox state
97
0
        Accessible* grandParent = parent->Parent();
98
0
        if (!grandParent)
99
0
          return state;
100
0
        NS_ASSERTION(grandParent->IsCombobox(),
101
0
                     "grandparent of combobox listitem is not combobox");
102
0
        uint64_t grandParentState = grandParent->State();
103
0
        state &= ~(states::OFFSCREEN | states::INVISIBLE);
104
0
        state |= (grandParentState & states::OFFSCREEN) |
105
0
                 (grandParentState & states::INVISIBLE) |
106
0
                 (grandParentState & states::OPAQUE1);
107
0
      } // isCollapsed
108
0
    } // isSelected
109
0
  } // ROLE_COMBOBOX_OPTION
110
0
111
0
  return state;
112
0
}
113
114
uint64_t
115
XULMenuitemAccessible::NativeInteractiveState() const
116
0
{
117
0
  if (NativelyUnavailable()) {
118
0
    // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic.
119
0
    bool skipNavigatingDisabledMenuItem = true;
120
0
    nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
121
0
    if (!menuFrame || !menuFrame->IsOnMenuBar()) {
122
0
      skipNavigatingDisabledMenuItem = LookAndFeel::
123
0
        GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, 0) != 0;
124
0
    }
125
0
126
0
    if (skipNavigatingDisabledMenuItem)
127
0
      return states::UNAVAILABLE;
128
0
129
0
    return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE;
130
0
  }
131
0
132
0
  return states::FOCUSABLE | states::SELECTABLE;
133
0
}
134
135
ENameValueFlag
136
XULMenuitemAccessible::NativeName(nsString& aName) const
137
0
{
138
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
139
0
  return eNameOK;
140
0
}
141
142
void
143
XULMenuitemAccessible::Description(nsString& aDescription)
144
0
{
145
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::description,
146
0
                                 aDescription);
147
0
}
148
149
KeyBinding
150
XULMenuitemAccessible::AccessKey() const
151
0
{
152
0
  // Return menu accesskey: N or Alt+F.
153
0
  static int32_t gMenuAccesskeyModifier = -1;  // magic value of -1 indicates unitialized state
154
0
155
0
  // We do not use nsCoreUtils::GetAccesskeyFor() because accesskeys for
156
0
  // menu are't registered by EventStateManager.
157
0
  nsAutoString accesskey;
158
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
159
0
                                 accesskey);
160
0
  if (accesskey.IsEmpty())
161
0
    return KeyBinding();
162
0
163
0
  uint32_t modifierKey = 0;
164
0
165
0
  Accessible* parentAcc = Parent();
166
0
  if (parentAcc) {
167
0
    if (parentAcc->NativeRole() == roles::MENUBAR) {
168
0
      // If top level menu item, add Alt+ or whatever modifier text to string
169
0
      // No need to cache pref service, this happens rarely
170
0
      if (gMenuAccesskeyModifier == -1) {
171
0
        // Need to initialize cached global accesskey pref
172
0
        gMenuAccesskeyModifier = Preferences::GetInt("ui.key.menuAccessKey", 0);
173
0
      }
174
0
175
0
      switch (gMenuAccesskeyModifier) {
176
0
        case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
177
0
          modifierKey = KeyBinding::kControl;
178
0
          break;
179
0
        case dom::KeyboardEvent_Binding::DOM_VK_ALT:
180
0
          modifierKey = KeyBinding::kAlt;
181
0
          break;
182
0
        case dom::KeyboardEvent_Binding::DOM_VK_META:
183
0
          modifierKey = KeyBinding::kMeta;
184
0
          break;
185
0
        case dom::KeyboardEvent_Binding::DOM_VK_WIN:
186
0
          modifierKey = KeyBinding::kOS;
187
0
          break;
188
0
      }
189
0
    }
190
0
  }
191
0
192
0
  return KeyBinding(accesskey[0], modifierKey);
193
0
}
194
195
KeyBinding
196
XULMenuitemAccessible::KeyboardShortcut() const
197
0
{
198
0
  nsAutoString keyElmId;
199
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyElmId);
200
0
  if (keyElmId.IsEmpty())
201
0
    return KeyBinding();
202
0
203
0
  Element* keyElm = mContent->OwnerDoc()->GetElementById(keyElmId);
204
0
  if (!keyElm)
205
0
    return KeyBinding();
206
0
207
0
  uint32_t key = 0;
208
0
209
0
  nsAutoString keyStr;
210
0
  keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
211
0
  if (keyStr.IsEmpty()) {
212
0
    nsAutoString keyCodeStr;
213
0
    keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyCodeStr);
214
0
    nsresult errorCode;
215
0
    key = keyStr.ToInteger(&errorCode, /* aRadix = */ 10);
216
0
    if (NS_FAILED(errorCode)) {
217
0
      key = keyStr.ToInteger(&errorCode, /* aRadix = */ 16);
218
0
    }
219
0
  } else {
220
0
    key = keyStr[0];
221
0
  }
222
0
223
0
  nsAutoString modifiersStr;
224
0
  keyElm->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiersStr);
225
0
226
0
  uint32_t modifierMask = 0;
227
0
  if (modifiersStr.Find("shift") != -1)
228
0
    modifierMask |= KeyBinding::kShift;
229
0
  if (modifiersStr.Find("alt") != -1)
230
0
    modifierMask |= KeyBinding::kAlt;
231
0
  if (modifiersStr.Find("meta") != -1)
232
0
    modifierMask |= KeyBinding::kMeta;
233
0
  if (modifiersStr.Find("os") != -1)
234
0
    modifierMask |= KeyBinding::kOS;
235
0
  if (modifiersStr.Find("control") != -1)
236
0
    modifierMask |= KeyBinding::kControl;
237
0
  if (modifiersStr.Find("accel") != -1) {
238
0
    modifierMask |= KeyBinding::AccelModifier();
239
0
  }
240
0
241
0
  return KeyBinding(key, modifierMask);
242
0
}
243
244
role
245
XULMenuitemAccessible::NativeRole() const
246
0
{
247
0
  nsCOMPtr<nsIDOMXULContainerElement> xulContainer(do_QueryInterface(mContent));
248
0
  if (xulContainer)
249
0
    return roles::PARENT_MENUITEM;
250
0
251
0
  if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
252
0
    return roles::COMBOBOX_OPTION;
253
0
254
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
255
0
                                         nsGkAtoms::radio, eCaseMatters))
256
0
    return roles::RADIO_MENU_ITEM;
257
0
258
0
  if (mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
259
0
                                         nsGkAtoms::checkbox, eCaseMatters))
260
0
    return roles::CHECK_MENU_ITEM;
261
0
262
0
  return roles::MENUITEM;
263
0
}
264
265
int32_t
266
XULMenuitemAccessible::GetLevelInternal()
267
0
{
268
0
  return nsAccUtils::GetLevelForXULContainerItem(mContent);
269
0
}
270
271
bool
272
XULMenuitemAccessible::DoAction(uint8_t index) const
273
0
{
274
0
  if (index == eAction_Click) {   // default action
275
0
    DoCommand();
276
0
    return true;
277
0
  }
278
0
279
0
  return false;
280
0
}
281
282
void
283
XULMenuitemAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
284
0
{
285
0
  if (aIndex == eAction_Click)
286
0
    aName.AssignLiteral("click");
287
0
}
288
289
uint8_t
290
XULMenuitemAccessible::ActionCount() const
291
0
{
292
0
  return 1;
293
0
}
294
295
////////////////////////////////////////////////////////////////////////////////
296
// XULMenuitemAccessible: Widgets
297
298
bool
299
XULMenuitemAccessible::IsActiveWidget() const
300
0
{
301
0
  // Parent menu item is a widget, it's active when its popup is open.
302
0
  nsIContent* menuPopupContent = mContent->GetFirstChild();
303
0
  if (menuPopupContent) {
304
0
    nsMenuPopupFrame* menuPopupFrame =
305
0
      do_QueryFrame(menuPopupContent->GetPrimaryFrame());
306
0
    return menuPopupFrame && menuPopupFrame->IsOpen();
307
0
  }
308
0
  return false;
309
0
}
310
311
bool
312
XULMenuitemAccessible::AreItemsOperable() const
313
0
{
314
0
  // Parent menu item is a widget, its items are operable when its popup is open.
315
0
  nsIContent* menuPopupContent = mContent->GetFirstChild();
316
0
  if (menuPopupContent) {
317
0
    nsMenuPopupFrame* menuPopupFrame =
318
0
      do_QueryFrame(menuPopupContent->GetPrimaryFrame());
319
0
    return menuPopupFrame && menuPopupFrame->IsOpen();
320
0
  }
321
0
  return false;
322
0
}
323
324
Accessible*
325
XULMenuitemAccessible::ContainerWidget() const
326
0
{
327
0
  nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
328
0
  if (menuFrame) {
329
0
    nsMenuParent* menuParent = menuFrame->GetMenuParent();
330
0
    if (menuParent) {
331
0
      if (menuParent->IsMenuBar()) // menubar menu
332
0
        return mParent;
333
0
334
0
      // a menupoup or parent menu item
335
0
      if (menuParent->IsMenu())
336
0
        return mParent;
337
0
338
0
      // otherwise it's different kind of popups (like panel or tooltip), it
339
0
      // shouldn't be a real case.
340
0
    }
341
0
  }
342
0
  return nullptr;
343
0
}
344
345
346
////////////////////////////////////////////////////////////////////////////////
347
// XULMenuSeparatorAccessible
348
////////////////////////////////////////////////////////////////////////////////
349
350
XULMenuSeparatorAccessible::
351
  XULMenuSeparatorAccessible(nsIContent* aContent, DocAccessible* aDoc) :
352
  XULMenuitemAccessible(aContent, aDoc)
353
0
{
354
0
}
355
356
uint64_t
357
XULMenuSeparatorAccessible::NativeState() const
358
0
{
359
0
  // Isn't focusable, but can be offscreen/invisible -- only copy those states
360
0
  return XULMenuitemAccessible::NativeState() &
361
0
    (states::OFFSCREEN | states::INVISIBLE);
362
0
}
363
364
ENameValueFlag
365
XULMenuSeparatorAccessible::NativeName(nsString& aName) const
366
0
{
367
0
  return eNameOK;
368
0
}
369
370
role
371
XULMenuSeparatorAccessible::NativeRole() const
372
0
{
373
0
  return roles::SEPARATOR;
374
0
}
375
376
bool
377
XULMenuSeparatorAccessible::DoAction(uint8_t index) const
378
0
{
379
0
  return false;
380
0
}
381
382
void
383
XULMenuSeparatorAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
384
0
{
385
0
  aName.Truncate();
386
0
}
387
388
uint8_t
389
XULMenuSeparatorAccessible::ActionCount() const
390
0
{
391
0
  return 0;
392
0
}
393
394
////////////////////////////////////////////////////////////////////////////////
395
// XULMenupopupAccessible
396
////////////////////////////////////////////////////////////////////////////////
397
398
XULMenupopupAccessible::
399
  XULMenupopupAccessible(nsIContent* aContent, DocAccessible* aDoc) :
400
  XULSelectControlAccessible(aContent, aDoc)
401
0
{
402
0
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
403
0
  if (menuPopupFrame && menuPopupFrame->IsMenu())
404
0
    mType = eMenuPopupType;
405
0
406
0
  // May be the anonymous <menupopup> inside <menulist> (a combobox)
407
0
  mSelectControl = do_QueryInterface(mContent->GetFlattenedTreeParent());
408
0
  if (!mSelectControl)
409
0
    mGenericTypes &= ~eSelect;
410
0
411
0
  mStateFlags |= eNoXBLKids;
412
0
}
413
414
uint64_t
415
XULMenupopupAccessible::NativeState() const
416
0
{
417
0
  uint64_t state = Accessible::NativeState();
418
0
419
#ifdef DEBUG
420
  // We are onscreen if our parent is active
421
  bool isActive =
422
    mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::menuactive);
423
  if (!isActive) {
424
    Accessible* parent = Parent();
425
    if (parent) {
426
      nsIContent* parentContent = parent->GetContent();
427
      if (parentContent && parentContent->IsElement())
428
        isActive = parentContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::open);
429
    }
430
  }
431
432
  NS_ASSERTION(isActive || (state & states::INVISIBLE),
433
               "XULMenupopup doesn't have INVISIBLE when it's inactive");
434
#endif
435
436
0
  if (state & states::INVISIBLE)
437
0
    state |= states::OFFSCREEN | states::COLLAPSED;
438
0
439
0
  return state;
440
0
}
441
442
ENameValueFlag
443
XULMenupopupAccessible::NativeName(nsString& aName) const
444
0
{
445
0
  nsIContent* content = mContent;
446
0
  while (content && aName.IsEmpty()) {
447
0
    if (content->IsElement()) {
448
0
      content->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
449
0
    }
450
0
    content = content->GetFlattenedTreeParent();
451
0
  }
452
0
453
0
  return eNameOK;
454
0
}
455
456
role
457
XULMenupopupAccessible::NativeRole() const
458
0
{
459
0
  // If accessible is not bound to the tree (this happens while children are
460
0
  // cached) return general role.
461
0
  if (mParent) {
462
0
    if (mParent->IsCombobox() || mParent->IsAutoComplete())
463
0
      return roles::COMBOBOX_LIST;
464
0
465
0
    if (mParent->Role() == roles::PUSHBUTTON) {
466
0
      // Some widgets like the search bar have several popups, owned by buttons.
467
0
      Accessible* grandParent = mParent->Parent();
468
0
      if (grandParent && grandParent->IsAutoComplete())
469
0
        return roles::COMBOBOX_LIST;
470
0
    }
471
0
  }
472
0
473
0
  return roles::MENUPOPUP;
474
0
}
475
476
////////////////////////////////////////////////////////////////////////////////
477
// XULMenupopupAccessible: Widgets
478
479
bool
480
XULMenupopupAccessible::IsWidget() const
481
0
{
482
0
  return true;
483
0
}
484
485
bool
486
XULMenupopupAccessible::IsActiveWidget() const
487
0
{
488
0
  // If menupopup is a widget (the case of context menus) then active when open.
489
0
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
490
0
  return menuPopupFrame && menuPopupFrame->IsOpen();
491
0
}
492
493
bool
494
XULMenupopupAccessible::AreItemsOperable() const
495
0
{
496
0
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
497
0
  return menuPopupFrame && menuPopupFrame->IsOpen();
498
0
}
499
500
Accessible*
501
XULMenupopupAccessible::ContainerWidget() const
502
0
{
503
0
  DocAccessible* document = Document();
504
0
505
0
  nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(GetFrame());
506
0
  while (menuPopupFrame) {
507
0
    Accessible* menuPopup =
508
0
      document->GetAccessible(menuPopupFrame->GetContent());
509
0
    if (!menuPopup) // shouldn't be a real case
510
0
      return nullptr;
511
0
512
0
    nsMenuFrame* menuFrame = do_QueryFrame(menuPopupFrame->GetParent());
513
0
    if (!menuFrame) // context menu or popups
514
0
      return nullptr;
515
0
516
0
    nsMenuParent* menuParent = menuFrame->GetMenuParent();
517
0
    if (!menuParent) // menulist or menubutton
518
0
      return menuPopup->Parent();
519
0
520
0
    if (menuParent->IsMenuBar()) { // menubar menu
521
0
      nsMenuBarFrame* menuBarFrame = static_cast<nsMenuBarFrame*>(menuParent);
522
0
      return document->GetAccessible(menuBarFrame->GetContent());
523
0
    }
524
0
525
0
    // different kind of popups like panel or tooltip
526
0
    if (!menuParent->IsMenu())
527
0
      return nullptr;
528
0
529
0
    menuPopupFrame = static_cast<nsMenuPopupFrame*>(menuParent);
530
0
  }
531
0
532
0
  MOZ_ASSERT_UNREACHABLE("Shouldn't be a real case.");
533
0
  return nullptr;
534
0
}
535
536
////////////////////////////////////////////////////////////////////////////////
537
// XULMenubarAccessible
538
////////////////////////////////////////////////////////////////////////////////
539
540
XULMenubarAccessible::
541
  XULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc) :
542
  AccessibleWrap(aContent, aDoc)
543
0
{
544
0
}
545
546
ENameValueFlag
547
XULMenubarAccessible::NativeName(nsString& aName) const
548
0
{
549
0
  aName.AssignLiteral("Application");
550
0
  return eNameOK;
551
0
}
552
553
role
554
XULMenubarAccessible::NativeRole() const
555
0
{
556
0
  return roles::MENUBAR;
557
0
}
558
559
////////////////////////////////////////////////////////////////////////////////
560
// XULMenubarAccessible: Widgets
561
562
bool
563
XULMenubarAccessible::IsActiveWidget() const
564
0
{
565
0
  nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
566
0
  return menuBarFrame && menuBarFrame->IsActive();
567
0
}
568
569
bool
570
XULMenubarAccessible::AreItemsOperable() const
571
0
{
572
0
  return true;
573
0
}
574
575
Accessible*
576
XULMenubarAccessible::CurrentItem() const
577
0
{
578
0
  nsMenuBarFrame* menuBarFrame = do_QueryFrame(GetFrame());
579
0
  if (menuBarFrame) {
580
0
    nsMenuFrame* menuFrame = menuBarFrame->GetCurrentMenuItem();
581
0
    if (menuFrame) {
582
0
      nsIContent* menuItemNode = menuFrame->GetContent();
583
0
      return mDoc->GetAccessible(menuItemNode);
584
0
    }
585
0
  }
586
0
  return nullptr;
587
0
}
588
589
void
590
XULMenubarAccessible::SetCurrentItem(const Accessible* aItem)
591
0
{
592
0
  NS_ERROR("XULMenubarAccessible::SetCurrentItem not implemented");
593
0
}