XULMenuAccessible.cpp 16 KB

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