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