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