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