1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Kyle Yuan (kyle.yuan@sun.com)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsHTMLSelectAccessible.h"
40 :
41 : #include "nsAccessibilityService.h"
42 : #include "nsAccUtils.h"
43 : #include "nsDocAccessible.h"
44 : #include "nsEventShell.h"
45 : #include "nsIAccessibleEvent.h"
46 : #include "nsTextEquivUtils.h"
47 : #include "Role.h"
48 : #include "States.h"
49 :
50 : #include "nsCOMPtr.h"
51 : #include "nsIFrame.h"
52 : #include "nsIComboboxControlFrame.h"
53 : #include "nsIDocument.h"
54 : #include "nsIDOMHTMLInputElement.h"
55 : #include "nsIDOMHTMLOptGroupElement.h"
56 : #include "nsIDOMHTMLSelectElement.h"
57 : #include "nsIListControlFrame.h"
58 : #include "nsIServiceManager.h"
59 : #include "nsIMutableArray.h"
60 :
61 : using namespace mozilla::a11y;
62 :
63 : ////////////////////////////////////////////////////////////////////////////////
64 : // nsHTMLSelectListAccessible
65 : ////////////////////////////////////////////////////////////////////////////////
66 :
67 0 : nsHTMLSelectListAccessible::
68 : nsHTMLSelectListAccessible(nsIContent* aContent, nsDocAccessible* aDoc) :
69 0 : nsAccessibleWrap(aContent, aDoc)
70 : {
71 0 : mFlags |= eListControlAccessible;
72 0 : }
73 :
74 : ////////////////////////////////////////////////////////////////////////////////
75 : // nsHTMLSelectListAccessible: nsAccessible public
76 :
77 : PRUint64
78 0 : nsHTMLSelectListAccessible::NativeState()
79 : {
80 0 : PRUint64 state = nsAccessibleWrap::NativeState();
81 0 : if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
82 0 : state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
83 :
84 0 : return state;
85 : }
86 :
87 : role
88 0 : nsHTMLSelectListAccessible::NativeRole()
89 : {
90 0 : if (mParent && mParent->Role() == roles::COMBOBOX)
91 0 : return roles::COMBOBOX_LIST;
92 :
93 0 : return roles::LISTBOX;
94 : }
95 :
96 : ////////////////////////////////////////////////////////////////////////////////
97 : // nsHTMLSelectListAccessible: SelectAccessible
98 :
99 : bool
100 0 : nsHTMLSelectListAccessible::IsSelect()
101 : {
102 0 : return true;
103 : }
104 :
105 : bool
106 0 : nsHTMLSelectListAccessible::SelectAll()
107 : {
108 0 : return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
109 0 : nsAccessibleWrap::SelectAll() : false;
110 : }
111 :
112 : bool
113 0 : nsHTMLSelectListAccessible::UnselectAll()
114 : {
115 0 : return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
116 0 : nsAccessibleWrap::UnselectAll() : false;
117 : }
118 :
119 : ////////////////////////////////////////////////////////////////////////////////
120 : // nsHTMLSelectListAccessible: Widgets
121 :
122 : bool
123 0 : nsHTMLSelectListAccessible::IsWidget() const
124 : {
125 0 : return true;
126 : }
127 :
128 : bool
129 0 : nsHTMLSelectListAccessible::IsActiveWidget() const
130 : {
131 0 : return FocusMgr()->HasDOMFocus(mContent);
132 : }
133 :
134 : bool
135 0 : nsHTMLSelectListAccessible::AreItemsOperable() const
136 : {
137 0 : return true;
138 : }
139 :
140 : nsAccessible*
141 0 : nsHTMLSelectListAccessible::CurrentItem()
142 : {
143 0 : nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
144 0 : if (listControlFrame) {
145 0 : nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
146 0 : if (activeOptionNode) {
147 0 : nsDocAccessible* document = Document();
148 0 : if (document)
149 0 : return document->GetAccessible(activeOptionNode);
150 : }
151 : }
152 0 : return nsnull;
153 : }
154 :
155 : void
156 0 : nsHTMLSelectListAccessible::SetCurrentItem(nsAccessible* aItem)
157 : {
158 : aItem->GetContent()->SetAttr(kNameSpaceID_None,
159 0 : nsGkAtoms::selected, NS_LITERAL_STRING("true"),
160 0 : true);
161 0 : }
162 :
163 : ////////////////////////////////////////////////////////////////////////////////
164 : // nsHTMLSelectListAccessible: nsAccessible protected
165 :
166 : void
167 0 : nsHTMLSelectListAccessible::CacheChildren()
168 : {
169 : // Cache accessibles for <optgroup> and <option> DOM decendents as children,
170 : // as well as the accessibles for them. Avoid whitespace text nodes. We want
171 : // to count all the <optgroup>s and <option>s as children because we want
172 : // a flat tree under the Select List.
173 0 : CacheOptSiblings(mContent);
174 0 : }
175 :
176 : ////////////////////////////////////////////////////////////////////////////////
177 : // nsHTMLSelectListAccessible protected
178 :
179 : void
180 0 : nsHTMLSelectListAccessible::CacheOptSiblings(nsIContent *aParentContent)
181 : {
182 0 : for (nsIContent* childContent = aParentContent->GetFirstChild(); childContent;
183 0 : childContent = childContent->GetNextSibling()) {
184 0 : if (!childContent->IsHTML()) {
185 0 : continue;
186 : }
187 :
188 0 : nsCOMPtr<nsIAtom> tag = childContent->Tag();
189 0 : if (tag == nsGkAtoms::option ||
190 0 : tag == nsGkAtoms::optgroup) {
191 :
192 : // Get an accessible for option or optgroup and cache it.
193 : nsRefPtr<nsAccessible> accessible =
194 0 : GetAccService()->GetOrCreateAccessible(childContent, mDoc);
195 0 : if (accessible)
196 0 : AppendChild(accessible);
197 :
198 : // Deep down into optgroup element.
199 0 : if (tag == nsGkAtoms::optgroup)
200 0 : CacheOptSiblings(childContent);
201 : }
202 : }
203 0 : }
204 :
205 :
206 : ////////////////////////////////////////////////////////////////////////////////
207 : // nsHTMLSelectOptionAccessible
208 : ////////////////////////////////////////////////////////////////////////////////
209 :
210 0 : nsHTMLSelectOptionAccessible::
211 : nsHTMLSelectOptionAccessible(nsIContent* aContent, nsDocAccessible* aDoc) :
212 0 : nsHyperTextAccessibleWrap(aContent, aDoc)
213 : {
214 0 : }
215 :
216 : ////////////////////////////////////////////////////////////////////////////////
217 : // nsHTMLSelectOptionAccessible: nsAccessible public
218 :
219 : role
220 0 : nsHTMLSelectOptionAccessible::NativeRole()
221 : {
222 0 : if (mParent && mParent->Role() == roles::COMBOBOX_LIST)
223 0 : return roles::COMBOBOX_OPTION;
224 :
225 0 : return roles::OPTION;
226 : }
227 :
228 : nsresult
229 0 : nsHTMLSelectOptionAccessible::GetNameInternal(nsAString& aName)
230 : {
231 : // CASE #1 -- great majority of the cases
232 : // find the label attribute - this is what the W3C says we should use
233 0 : mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
234 0 : if (!aName.IsEmpty())
235 0 : return NS_OK;
236 :
237 : // CASE #2 -- no label parameter, get the first child,
238 : // use it if it is a text node
239 0 : nsIContent* text = mContent->GetFirstChild();
240 0 : if (!text)
241 0 : return NS_OK;
242 :
243 0 : if (text->IsNodeOfType(nsINode::eTEXT)) {
244 0 : nsAutoString txtValue;
245 : nsresult rv = nsTextEquivUtils::
246 0 : AppendTextEquivFromTextContent(text, &txtValue);
247 0 : NS_ENSURE_SUCCESS(rv, rv);
248 :
249 : // Temp var (txtValue) needed until CompressWhitespace built for nsAString
250 0 : txtValue.CompressWhitespace();
251 0 : aName.Assign(txtValue);
252 0 : return NS_OK;
253 : }
254 :
255 0 : return NS_OK;
256 : }
257 :
258 : // nsAccessible protected
259 0 : nsIFrame* nsHTMLSelectOptionAccessible::GetBoundsFrame()
260 : {
261 0 : PRUint64 state = 0;
262 0 : nsIContent* content = GetSelectState(&state);
263 0 : if (state & states::COLLAPSED) {
264 0 : if (content) {
265 0 : return content->GetPrimaryFrame();
266 : }
267 :
268 0 : return nsnull;
269 : }
270 :
271 0 : return nsAccessible::GetBoundsFrame();
272 : }
273 :
274 : PRUint64
275 0 : nsHTMLSelectOptionAccessible::NativeState()
276 : {
277 : // As a nsHTMLSelectOptionAccessible we can have the following states:
278 : // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
279 : // Upcall to nsAccessible, but skip nsHyperTextAccessible impl
280 : // because we don't want EDITABLE or SELECTABLE_TEXT
281 0 : PRUint64 state = nsAccessible::NativeState();
282 :
283 0 : PRUint64 selectState = 0;
284 0 : nsIContent* selectContent = GetSelectState(&selectState);
285 0 : if (!selectContent || selectState & states::INVISIBLE)
286 0 : return state;
287 :
288 : // Focusable and selectable
289 0 : if (!(state & states::UNAVAILABLE))
290 0 : state |= (states::FOCUSABLE | states::SELECTABLE);
291 :
292 : // Are we selected?
293 0 : bool isSelected = false;
294 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent));
295 0 : if (option) {
296 0 : option->GetSelected(&isSelected);
297 0 : if (isSelected)
298 0 : state |= states::SELECTED;
299 : }
300 :
301 0 : if (selectState & states::OFFSCREEN) {
302 0 : state |= states::OFFSCREEN;
303 : }
304 0 : else if (selectState & states::COLLAPSED) {
305 : // <select> is COLLAPSED: add OFFSCREEN, if not the currently
306 : // visible option
307 0 : if (!isSelected) {
308 0 : state |= states::OFFSCREEN;
309 : }
310 : else {
311 : // Clear offscreen and invisible for currently showing option
312 0 : state &= ~(states::OFFSCREEN | states::INVISIBLE);
313 0 : state |= selectState & states::OPAQUE1;
314 : }
315 : }
316 : else {
317 : // XXX list frames are weird, don't rely on nsAccessible's general
318 : // visibility implementation unless they get reimplemented in layout
319 0 : state &= ~states::OFFSCREEN;
320 : // <select> is not collapsed: compare bounds to calculate OFFSCREEN
321 0 : nsAccessible* listAcc = Parent();
322 0 : if (listAcc) {
323 : PRInt32 optionX, optionY, optionWidth, optionHeight;
324 : PRInt32 listX, listY, listWidth, listHeight;
325 0 : GetBounds(&optionX, &optionY, &optionWidth, &optionHeight);
326 0 : listAcc->GetBounds(&listX, &listY, &listWidth, &listHeight);
327 0 : if (optionY < listY || optionY + optionHeight > listY + listHeight) {
328 0 : state |= states::OFFSCREEN;
329 : }
330 : }
331 : }
332 :
333 0 : return state;
334 : }
335 :
336 : PRInt32
337 0 : nsHTMLSelectOptionAccessible::GetLevelInternal()
338 : {
339 0 : nsIContent *parentContent = mContent->GetParent();
340 :
341 : PRInt32 level =
342 0 : parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
343 :
344 0 : if (level == 1 && Role() != roles::HEADING)
345 0 : level = 0; // In a single level list, the level is irrelevant
346 :
347 0 : return level;
348 : }
349 :
350 : void
351 0 : nsHTMLSelectOptionAccessible::GetPositionAndSizeInternal(PRInt32 *aPosInSet,
352 : PRInt32 *aSetSize)
353 : {
354 0 : PRInt32 posInSet = 0, setSize = 0;
355 0 : bool isContentFound = false;
356 :
357 0 : nsIContent* parentContent = mContent->GetParent();
358 0 : for (nsIContent* childContent = parentContent->GetFirstChild(); childContent;
359 0 : childContent = childContent->GetNextSibling()) {
360 0 : if (childContent->NodeInfo()->Equals(mContent->NodeInfo())) {
361 0 : if (!isContentFound) {
362 0 : if (childContent == mContent)
363 0 : isContentFound = true;
364 :
365 0 : posInSet++;
366 : }
367 0 : setSize++;
368 : }
369 : }
370 :
371 0 : *aSetSize = setSize;
372 0 : *aPosInSet = posInSet;
373 0 : }
374 :
375 : ////////////////////////////////////////////////////////////////////////////////
376 : // nsHTMLSelectOptionAccessible: nsIAccessible
377 :
378 : /** select us! close combo box if necessary*/
379 0 : NS_IMETHODIMP nsHTMLSelectOptionAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
380 : {
381 0 : if (aIndex == eAction_Select) {
382 0 : aName.AssignLiteral("select");
383 0 : return NS_OK;
384 : }
385 0 : return NS_ERROR_INVALID_ARG;
386 : }
387 :
388 : PRUint8
389 0 : nsHTMLSelectOptionAccessible::ActionCount()
390 : {
391 0 : return 1;
392 : }
393 :
394 : NS_IMETHODIMP
395 0 : nsHTMLSelectOptionAccessible::DoAction(PRUint8 aIndex)
396 : {
397 0 : if (aIndex != eAction_Select)
398 0 : return NS_ERROR_INVALID_ARG;
399 :
400 0 : if (IsDefunct())
401 0 : return NS_ERROR_FAILURE;
402 :
403 0 : DoCommand();
404 0 : return NS_OK;
405 : }
406 :
407 : NS_IMETHODIMP
408 0 : nsHTMLSelectOptionAccessible::SetSelected(bool aSelect)
409 : {
410 0 : if (IsDefunct())
411 0 : return NS_ERROR_FAILURE;
412 :
413 0 : nsCOMPtr<nsIDOMHTMLOptionElement> optionElm(do_QueryInterface(mContent));
414 0 : return optionElm->SetSelected(aSelect);
415 : }
416 :
417 : ////////////////////////////////////////////////////////////////////////////////
418 : // nsHTMLSelectOptionAccessible: Widgets
419 :
420 : nsAccessible*
421 0 : nsHTMLSelectOptionAccessible::ContainerWidget() const
422 : {
423 0 : return mParent && mParent->IsListControl() ? mParent : nsnull;
424 : }
425 :
426 : ////////////////////////////////////////////////////////////////////////////////
427 : // nsHTMLSelectOptionAccessible: private methods
428 :
429 : nsIContent*
430 0 : nsHTMLSelectOptionAccessible::GetSelectState(PRUint64* aState)
431 : {
432 0 : *aState = 0;
433 :
434 0 : nsIContent* selectNode = mContent;
435 0 : while (selectNode && selectNode->Tag() != nsGkAtoms::select) {
436 0 : selectNode = selectNode->GetParent();
437 : }
438 :
439 0 : if (selectNode) {
440 0 : nsAccessible* select = mDoc->GetAccessible(selectNode);
441 0 : if (select) {
442 0 : *aState = select->State();
443 0 : return selectNode;
444 : }
445 : }
446 0 : return nsnull;
447 : }
448 :
449 :
450 : ////////////////////////////////////////////////////////////////////////////////
451 : // nsHTMLSelectOptGroupAccessible
452 : ////////////////////////////////////////////////////////////////////////////////
453 :
454 0 : nsHTMLSelectOptGroupAccessible::
455 : nsHTMLSelectOptGroupAccessible(nsIContent* aContent,
456 : nsDocAccessible* aDoc) :
457 0 : nsHTMLSelectOptionAccessible(aContent, aDoc)
458 : {
459 0 : }
460 :
461 : role
462 0 : nsHTMLSelectOptGroupAccessible::NativeRole()
463 : {
464 0 : return roles::HEADING;
465 : }
466 :
467 : PRUint64
468 0 : nsHTMLSelectOptGroupAccessible::NativeState()
469 : {
470 0 : PRUint64 state = nsHTMLSelectOptionAccessible::NativeState();
471 :
472 0 : state &= ~(states::FOCUSABLE | states::SELECTABLE);
473 :
474 0 : return state;
475 : }
476 :
477 0 : NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::DoAction(PRUint8 index)
478 : {
479 0 : return NS_ERROR_NOT_IMPLEMENTED;
480 : }
481 :
482 0 : NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
483 : {
484 0 : return NS_ERROR_NOT_IMPLEMENTED;
485 : }
486 :
487 : PRUint8
488 0 : nsHTMLSelectOptGroupAccessible::ActionCount()
489 : {
490 0 : return 0;
491 : }
492 :
493 : ////////////////////////////////////////////////////////////////////////////////
494 : // nsHTMLSelectOptGroupAccessible: nsAccessible protected
495 :
496 : void
497 0 : nsHTMLSelectOptGroupAccessible::CacheChildren()
498 : {
499 : // XXX To do (bug 378612) - create text child for the anonymous attribute
500 : // content, so that nsIAccessibleText is supported for the <optgroup> as it is
501 : // for an <option>. Attribute content is what layout creates for
502 : // the label="foo" on the <optgroup>. See eStyleContentType_Attr and
503 : // CreateAttributeContent() in nsCSSFrameConstructor
504 0 : }
505 :
506 :
507 : ////////////////////////////////////////////////////////////////////////////////
508 : // nsHTMLComboboxAccessible
509 : ////////////////////////////////////////////////////////////////////////////////
510 :
511 0 : nsHTMLComboboxAccessible::
512 : nsHTMLComboboxAccessible(nsIContent* aContent, nsDocAccessible* aDoc) :
513 0 : nsAccessibleWrap(aContent, aDoc)
514 : {
515 0 : mFlags |= eComboboxAccessible;
516 0 : }
517 :
518 : ////////////////////////////////////////////////////////////////////////////////
519 : // nsHTMLComboboxAccessible: nsAccessible
520 :
521 : role
522 0 : nsHTMLComboboxAccessible::NativeRole()
523 : {
524 0 : return roles::COMBOBOX;
525 : }
526 :
527 : void
528 0 : nsHTMLComboboxAccessible::InvalidateChildren()
529 : {
530 0 : nsAccessibleWrap::InvalidateChildren();
531 :
532 0 : if (mListAccessible)
533 0 : mListAccessible->InvalidateChildren();
534 0 : }
535 :
536 : void
537 0 : nsHTMLComboboxAccessible::CacheChildren()
538 : {
539 0 : nsIFrame* frame = GetFrame();
540 0 : if (!frame)
541 0 : return;
542 :
543 0 : nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
544 0 : if (!comboFrame)
545 0 : return;
546 :
547 0 : nsIFrame *listFrame = comboFrame->GetDropDown();
548 0 : if (!listFrame)
549 0 : return;
550 :
551 0 : if (!mListAccessible) {
552 : mListAccessible =
553 0 : new nsHTMLComboboxListAccessible(mParent, mContent, mDoc);
554 :
555 : // Initialize and put into cache.
556 0 : if (!Document()->BindToDocument(mListAccessible, nsnull))
557 0 : return;
558 : }
559 :
560 0 : if (AppendChild(mListAccessible)) {
561 : // Cache combobox option accessibles so that we build complete accessible
562 : // tree for combobox.
563 0 : mListAccessible->EnsureChildren();
564 : }
565 : }
566 :
567 : void
568 0 : nsHTMLComboboxAccessible::Shutdown()
569 : {
570 0 : nsAccessibleWrap::Shutdown();
571 :
572 0 : if (mListAccessible) {
573 0 : mListAccessible->Shutdown();
574 0 : mListAccessible = nsnull;
575 : }
576 0 : }
577 :
578 : /**
579 : */
580 : PRUint64
581 0 : nsHTMLComboboxAccessible::NativeState()
582 : {
583 : // As a nsHTMLComboboxAccessible we can have the following states:
584 : // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
585 : // Get focus status from base class
586 0 : PRUint64 state = nsAccessible::NativeState();
587 :
588 0 : nsIFrame *frame = GetBoundsFrame();
589 0 : nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
590 0 : if (comboFrame && comboFrame->IsDroppedDown())
591 0 : state |= states::EXPANDED;
592 : else
593 0 : state |= states::COLLAPSED;
594 :
595 0 : state |= states::HASPOPUP;
596 0 : return state;
597 : }
598 :
599 : void
600 0 : nsHTMLComboboxAccessible::Description(nsString& aDescription)
601 : {
602 0 : aDescription.Truncate();
603 : // First check to see if combo box itself has a description, perhaps through
604 : // tooltip (title attribute) or via aria-describedby
605 0 : nsAccessible::Description(aDescription);
606 0 : if (!aDescription.IsEmpty())
607 0 : return;
608 :
609 : // Otherwise use description of selected option.
610 0 : nsAccessible* option = SelectedOption();
611 0 : if (option)
612 0 : option->Description(aDescription);
613 : }
614 :
615 0 : NS_IMETHODIMP nsHTMLComboboxAccessible::GetValue(nsAString& aValue)
616 : {
617 : // Use accessible name of selected option.
618 0 : nsAccessible* option = SelectedOption();
619 0 : return option ? option->GetName(aValue) : NS_OK;
620 : }
621 :
622 : PRUint8
623 0 : nsHTMLComboboxAccessible::ActionCount()
624 : {
625 0 : return 1;
626 : }
627 :
628 : NS_IMETHODIMP
629 0 : nsHTMLComboboxAccessible::DoAction(PRUint8 aIndex)
630 : {
631 0 : if (aIndex != eAction_Click)
632 0 : return NS_ERROR_INVALID_ARG;
633 :
634 0 : if (IsDefunct())
635 0 : return NS_ERROR_FAILURE;
636 :
637 0 : DoCommand();
638 0 : return NS_OK;
639 : }
640 :
641 : /**
642 : * Our action name is the reverse of our state:
643 : * if we are closed -> open is our name.
644 : * if we are open -> closed is our name.
645 : * Uses the frame to get the state, updated on every click
646 : */
647 0 : NS_IMETHODIMP nsHTMLComboboxAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
648 : {
649 0 : if (aIndex != nsHTMLComboboxAccessible::eAction_Click) {
650 0 : return NS_ERROR_INVALID_ARG;
651 : }
652 0 : nsIFrame *frame = GetFrame();
653 0 : if (!frame) {
654 0 : return NS_ERROR_FAILURE;
655 : }
656 0 : nsIComboboxControlFrame *comboFrame = do_QueryFrame(frame);
657 0 : if (!comboFrame) {
658 0 : return NS_ERROR_FAILURE;
659 : }
660 0 : if (comboFrame->IsDroppedDown())
661 0 : aName.AssignLiteral("close");
662 : else
663 0 : aName.AssignLiteral("open");
664 :
665 0 : return NS_OK;
666 : }
667 :
668 : ////////////////////////////////////////////////////////////////////////////////
669 : // nsHTMLComboboxAccessible: Widgets
670 :
671 : bool
672 0 : nsHTMLComboboxAccessible::IsWidget() const
673 : {
674 0 : return true;
675 : }
676 :
677 : bool
678 0 : nsHTMLComboboxAccessible::IsActiveWidget() const
679 : {
680 0 : return FocusMgr()->HasDOMFocus(mContent);
681 : }
682 :
683 : bool
684 0 : nsHTMLComboboxAccessible::AreItemsOperable() const
685 : {
686 0 : nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
687 0 : return comboboxFrame && comboboxFrame->IsDroppedDown();
688 : }
689 :
690 : nsAccessible*
691 0 : nsHTMLComboboxAccessible::CurrentItem()
692 : {
693 0 : return AreItemsOperable() ? mListAccessible->CurrentItem() : nsnull;
694 : }
695 :
696 : void
697 0 : nsHTMLComboboxAccessible::SetCurrentItem(nsAccessible* aItem)
698 : {
699 0 : if (AreItemsOperable())
700 0 : mListAccessible->SetCurrentItem(aItem);
701 0 : }
702 :
703 : ////////////////////////////////////////////////////////////////////////////////
704 : // nsHTMLComboboxAccessible: protected
705 :
706 : nsAccessible*
707 0 : nsHTMLComboboxAccessible::SelectedOption() const
708 : {
709 0 : nsIFrame* frame = GetFrame();
710 0 : nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame);
711 0 : if (!comboboxFrame)
712 0 : return nsnull;
713 :
714 : nsIListControlFrame* listControlFrame =
715 0 : do_QueryFrame(comboboxFrame->GetDropDown());
716 0 : if (listControlFrame) {
717 0 : nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
718 0 : if (activeOptionNode) {
719 0 : nsDocAccessible* document = Document();
720 0 : if (document)
721 0 : return document->GetAccessible(activeOptionNode);
722 : }
723 : }
724 :
725 0 : return nsnull;
726 : }
727 :
728 :
729 : ////////////////////////////////////////////////////////////////////////////////
730 : // nsHTMLComboboxListAccessible
731 : ////////////////////////////////////////////////////////////////////////////////
732 :
733 0 : nsHTMLComboboxListAccessible::
734 : nsHTMLComboboxListAccessible(nsIAccessible* aParent, nsIContent* aContent,
735 : nsDocAccessible* aDoc) :
736 0 : nsHTMLSelectListAccessible(aContent, aDoc)
737 : {
738 0 : }
739 :
740 : ////////////////////////////////////////////////////////////////////////////////
741 : // nsHTMLComboboxAccessible: nsAccessNode
742 :
743 : nsIFrame*
744 0 : nsHTMLComboboxListAccessible::GetFrame() const
745 : {
746 0 : nsIFrame* frame = nsHTMLSelectListAccessible::GetFrame();
747 :
748 0 : if (frame) {
749 0 : nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
750 0 : if (comboBox) {
751 0 : return comboBox->GetDropDown();
752 : }
753 : }
754 :
755 0 : return nsnull;
756 : }
757 :
758 : bool
759 0 : nsHTMLComboboxListAccessible::IsPrimaryForNode() const
760 : {
761 0 : return false;
762 : }
763 :
764 : ////////////////////////////////////////////////////////////////////////////////
765 : // nsHTMLComboboxAccessible: nsAccessible
766 :
767 : PRUint64
768 0 : nsHTMLComboboxListAccessible::NativeState()
769 : {
770 : // As a nsHTMLComboboxListAccessible we can have the following states:
771 : // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
772 : // Get focus status from base class
773 0 : PRUint64 state = nsAccessible::NativeState();
774 :
775 0 : nsIFrame *boundsFrame = GetBoundsFrame();
776 0 : nsIComboboxControlFrame* comboFrame = do_QueryFrame(boundsFrame);
777 0 : if (comboFrame && comboFrame->IsDroppedDown())
778 0 : state |= states::FLOATING;
779 : else
780 0 : state |= states::INVISIBLE;
781 :
782 0 : return state;
783 : }
784 :
785 : /**
786 : * Gets the bounds for the areaFrame.
787 : * Walks the Frame tree and checks for proper frames.
788 : */
789 0 : void nsHTMLComboboxListAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame)
790 : {
791 0 : *aBoundingFrame = nsnull;
792 :
793 0 : nsAccessible* comboAcc = Parent();
794 0 : if (!comboAcc)
795 0 : return;
796 :
797 0 : if (0 == (comboAcc->State() & states::COLLAPSED)) {
798 0 : nsHTMLSelectListAccessible::GetBoundsRect(aBounds, aBoundingFrame);
799 0 : return;
800 : }
801 :
802 : // Get the first option.
803 0 : nsIContent* content = mContent->GetFirstChild();
804 0 : if (!content) {
805 0 : return;
806 : }
807 0 : nsIFrame* frame = content->GetPrimaryFrame();
808 0 : if (!frame) {
809 0 : *aBoundingFrame = nsnull;
810 0 : return;
811 : }
812 :
813 0 : *aBoundingFrame = frame->GetParent();
814 0 : aBounds = (*aBoundingFrame)->GetRect();
815 : }
816 :
817 : ////////////////////////////////////////////////////////////////////////////////
818 : // nsHTMLComboboxListAccessible: Widgets
819 :
820 : bool
821 0 : nsHTMLComboboxListAccessible::IsActiveWidget() const
822 : {
823 0 : return mParent && mParent->IsActiveWidget();
824 : }
825 :
826 : bool
827 0 : nsHTMLComboboxListAccessible::AreItemsOperable() const
828 : {
829 0 : return mParent && mParent->AreItemsOperable();
830 : }
831 :
|