summaryrefslogtreecommitdiffstats
path: root/accessible/html/HTMLSelectAccessible.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/html/HTMLSelectAccessible.cpp')
-rw-r--r--accessible/html/HTMLSelectAccessible.cpp610
1 files changed, 610 insertions, 0 deletions
diff --git a/accessible/html/HTMLSelectAccessible.cpp b/accessible/html/HTMLSelectAccessible.cpp
new file mode 100644
index 000000000..cb98a0038
--- /dev/null
+++ b/accessible/html/HTMLSelectAccessible.cpp
@@ -0,0 +1,610 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLSelectAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "DocAccessible.h"
+#include "nsEventShell.h"
+#include "nsTextEquivUtils.h"
+#include "Role.h"
+#include "States.h"
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "nsIComboboxControlFrame.h"
+#include "nsContainerFrame.h"
+#include "nsIListControlFrame.h"
+
+using namespace mozilla::a11y;
+using namespace mozilla::dom;
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSelectListAccessible::
+ HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mGenericTypes |= eListControl | eSelect;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: Accessible public
+
+uint64_t
+HTMLSelectListAccessible::NativeState()
+{
+ uint64_t state = AccessibleWrap::NativeState();
+ if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple))
+ state |= states::MULTISELECTABLE | states::EXTSELECTABLE;
+
+ return state;
+}
+
+role
+HTMLSelectListAccessible::NativeRole()
+{
+ return roles::LISTBOX;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: SelectAccessible
+
+bool
+HTMLSelectListAccessible::SelectAll()
+{
+ return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+ AccessibleWrap::SelectAll() : false;
+}
+
+bool
+HTMLSelectListAccessible::UnselectAll()
+{
+ return mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple) ?
+ AccessibleWrap::UnselectAll() : false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectListAccessible: Widgets
+
+bool
+HTMLSelectListAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+HTMLSelectListAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+HTMLSelectListAccessible::AreItemsOperable() const
+{
+ return true;
+}
+
+Accessible*
+HTMLSelectListAccessible::CurrentItem()
+{
+ nsIListControlFrame* listControlFrame = do_QueryFrame(GetFrame());
+ if (listControlFrame) {
+ nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
+ if (activeOptionNode) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(activeOptionNode);
+ }
+ }
+ return nullptr;
+}
+
+void
+HTMLSelectListAccessible::SetCurrentItem(Accessible* aItem)
+{
+ aItem->GetContent()->SetAttr(kNameSpaceID_None,
+ nsGkAtoms::selected, NS_LITERAL_STRING("true"),
+ true);
+}
+
+bool
+HTMLSelectListAccessible::IsAcceptableChild(nsIContent* aEl) const
+{
+ return aEl->IsAnyOfHTMLElements(nsGkAtoms::option, nsGkAtoms::optgroup);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLSelectOptionAccessible::
+ HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ HyperTextAccessibleWrap(aContent, aDoc)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible: Accessible public
+
+role
+HTMLSelectOptionAccessible::NativeRole()
+{
+ if (GetCombobox())
+ return roles::COMBOBOX_OPTION;
+
+ return roles::OPTION;
+}
+
+ENameValueFlag
+HTMLSelectOptionAccessible::NativeName(nsString& aName)
+{
+ // CASE #1 -- great majority of the cases
+ // find the label attribute - this is what the W3C says we should use
+ mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
+ if (!aName.IsEmpty())
+ return eNameOK;
+
+ // CASE #2 -- no label parameter, get the first child,
+ // use it if it is a text node
+ nsIContent* text = mContent->GetFirstChild();
+ if (text && text->IsNodeOfType(nsINode::eTEXT)) {
+ nsTextEquivUtils::AppendTextEquivFromTextContent(text, &aName);
+ aName.CompressWhitespace();
+ return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+ }
+
+ return eNameOK;
+}
+
+uint64_t
+HTMLSelectOptionAccessible::NativeState()
+{
+ // As a HTMLSelectOptionAccessible we can have the following states:
+ // SELECTABLE, SELECTED, FOCUSED, FOCUSABLE, OFFSCREEN
+ // Upcall to Accessible, but skip HyperTextAccessible impl
+ // because we don't want EDITABLE or SELECTABLE_TEXT
+ uint64_t state = Accessible::NativeState();
+
+ Accessible* select = GetSelect();
+ if (!select)
+ return state;
+
+ uint64_t selectState = select->State();
+ if (selectState & states::INVISIBLE)
+ return state;
+
+ // Are we selected?
+ HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
+ bool selected = option && option->Selected();
+ if (selected)
+ state |= states::SELECTED;
+
+ if (selectState & states::OFFSCREEN) {
+ state |= states::OFFSCREEN;
+ } else if (selectState & states::COLLAPSED) {
+ // <select> is COLLAPSED: add OFFSCREEN, if not the currently
+ // visible option
+ if (!selected) {
+ state |= states::OFFSCREEN;
+ state ^= states::INVISIBLE;
+ } else {
+ // Clear offscreen and invisible for currently showing option
+ state &= ~(states::OFFSCREEN | states::INVISIBLE);
+ state |= selectState & states::OPAQUE1;
+ }
+ } else {
+ // XXX list frames are weird, don't rely on Accessible's general
+ // visibility implementation unless they get reimplemented in layout
+ state &= ~states::OFFSCREEN;
+ // <select> is not collapsed: compare bounds to calculate OFFSCREEN
+ Accessible* listAcc = Parent();
+ if (listAcc) {
+ nsIntRect optionRect = Bounds();
+ nsIntRect listRect = listAcc->Bounds();
+ if (optionRect.y < listRect.y ||
+ optionRect.y + optionRect.height > listRect.y + listRect.height) {
+ state |= states::OFFSCREEN;
+ }
+ }
+ }
+
+ return state;
+}
+
+uint64_t
+HTMLSelectOptionAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ?
+ states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
+int32_t
+HTMLSelectOptionAccessible::GetLevelInternal()
+{
+ nsIContent* parentContent = mContent->GetParent();
+
+ int32_t level =
+ parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
+
+ if (level == 1 && Role() != roles::HEADING)
+ level = 0; // In a single level list, the level is irrelevant
+
+ return level;
+}
+
+nsRect
+HTMLSelectOptionAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ Accessible* combobox = GetCombobox();
+ if (combobox && (combobox->State() & states::COLLAPSED))
+ return combobox->RelativeBounds(aBoundingFrame);
+
+ return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame);
+}
+
+void
+HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex == eAction_Select)
+ aName.AssignLiteral("select");
+}
+
+uint8_t
+HTMLSelectOptionAccessible::ActionCount()
+{
+ return 1;
+}
+
+bool
+HTMLSelectOptionAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Select)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+void
+HTMLSelectOptionAccessible::SetSelected(bool aSelect)
+{
+ HTMLOptionElement* option = HTMLOptionElement::FromContent(mContent);
+ if (option)
+ option->SetSelected(aSelect);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptionAccessible: Widgets
+
+Accessible*
+HTMLSelectOptionAccessible::ContainerWidget() const
+{
+ Accessible* parent = Parent();
+ if (parent && parent->IsHTMLOptGroup())
+ parent = parent->Parent();
+
+ return parent && parent->IsListControl() ? parent : nullptr;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLSelectOptGroupAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+role
+HTMLSelectOptGroupAccessible::NativeRole()
+{
+ return roles::GROUPING;
+}
+
+uint64_t
+HTMLSelectOptGroupAccessible::NativeInteractiveState() const
+{
+ return NativelyUnavailable() ? states::UNAVAILABLE : 0;
+}
+
+uint8_t
+HTMLSelectOptGroupAccessible::ActionCount()
+{
+ return 0;
+}
+
+void
+HTMLSelectOptGroupAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ aName.Truncate();
+}
+
+bool
+HTMLSelectOptGroupAccessible::DoAction(uint8_t aIndex)
+{
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLComboboxAccessible::
+ HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc) :
+ AccessibleWrap(aContent, aDoc)
+{
+ mType = eHTMLComboboxType;
+ mGenericTypes |= eCombobox;
+ mStateFlags |= eNoKidsFromDOM;
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (comboFrame) {
+ nsIFrame* listFrame = comboFrame->GetDropDown();
+ if (listFrame) {
+ mListAccessible = new HTMLComboboxListAccessible(mParent, mContent, mDoc);
+ Document()->BindToDocument(mListAccessible, nullptr);
+ AppendChild(mListAccessible);
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Accessible
+
+role
+HTMLComboboxAccessible::NativeRole()
+{
+ return roles::COMBOBOX;
+}
+
+bool
+HTMLComboboxAccessible::RemoveChild(Accessible* aChild)
+{
+ MOZ_ASSERT(aChild == mListAccessible);
+ if (AccessibleWrap::RemoveChild(aChild)) {
+ mListAccessible = nullptr;
+ return true;
+ }
+ return false;
+}
+
+void
+HTMLComboboxAccessible::Shutdown()
+{
+ MOZ_ASSERT(mDoc->IsDefunct() || !mListAccessible);
+ if (mListAccessible) {
+ mListAccessible->Shutdown();
+ mListAccessible = nullptr;
+ }
+
+ AccessibleWrap::Shutdown();
+}
+
+uint64_t
+HTMLComboboxAccessible::NativeState()
+{
+ // As a HTMLComboboxAccessible we can have the following states:
+ // FOCUSED, FOCUSABLE, HASPOPUP, EXPANDED, COLLAPSED
+ // Get focus status from base class
+ uint64_t state = Accessible::NativeState();
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (comboFrame && comboFrame->IsDroppedDown())
+ state |= states::EXPANDED;
+ else
+ state |= states::COLLAPSED;
+
+ state |= states::HASPOPUP;
+ return state;
+}
+
+void
+HTMLComboboxAccessible::Description(nsString& aDescription)
+{
+ aDescription.Truncate();
+ // First check to see if combo box itself has a description, perhaps through
+ // tooltip (title attribute) or via aria-describedby
+ Accessible::Description(aDescription);
+ if (!aDescription.IsEmpty())
+ return;
+
+ // Otherwise use description of selected option.
+ Accessible* option = SelectedOption();
+ if (option)
+ option->Description(aDescription);
+}
+
+void
+HTMLComboboxAccessible::Value(nsString& aValue)
+{
+ // Use accessible name of selected option.
+ Accessible* option = SelectedOption();
+ if (option)
+ option->Name(aValue);
+}
+
+uint8_t
+HTMLComboboxAccessible::ActionCount()
+{
+ return 1;
+}
+
+bool
+HTMLComboboxAccessible::DoAction(uint8_t aIndex)
+{
+ if (aIndex != eAction_Click)
+ return false;
+
+ DoCommand();
+ return true;
+}
+
+void
+HTMLComboboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName)
+{
+ if (aIndex != HTMLComboboxAccessible::eAction_Click)
+ return;
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(GetFrame());
+ if (!comboFrame)
+ return;
+
+ if (comboFrame->IsDroppedDown())
+ aName.AssignLiteral("close");
+ else
+ aName.AssignLiteral("open");
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Widgets
+
+bool
+HTMLComboboxAccessible::IsWidget() const
+{
+ return true;
+}
+
+bool
+HTMLComboboxAccessible::IsActiveWidget() const
+{
+ return FocusMgr()->HasDOMFocus(mContent);
+}
+
+bool
+HTMLComboboxAccessible::AreItemsOperable() const
+{
+ nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(GetFrame());
+ return comboboxFrame && comboboxFrame->IsDroppedDown();
+}
+
+Accessible*
+HTMLComboboxAccessible::CurrentItem()
+{
+ return AreItemsOperable() ? mListAccessible->CurrentItem() : nullptr;
+}
+
+void
+HTMLComboboxAccessible::SetCurrentItem(Accessible* aItem)
+{
+ if (AreItemsOperable())
+ mListAccessible->SetCurrentItem(aItem);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: protected
+
+Accessible*
+HTMLComboboxAccessible::SelectedOption() const
+{
+ HTMLSelectElement* select = HTMLSelectElement::FromContent(mContent);
+ int32_t selectedIndex = select->SelectedIndex();
+
+ if (selectedIndex >= 0) {
+ HTMLOptionElement* option = select->Item(selectedIndex);
+ if (option) {
+ DocAccessible* document = Document();
+ if (document)
+ return document->GetAccessible(option);
+ }
+ }
+
+ return nullptr;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxListAccessible
+////////////////////////////////////////////////////////////////////////////////
+
+HTMLComboboxListAccessible::
+ HTMLComboboxListAccessible(Accessible* aParent, nsIContent* aContent,
+ DocAccessible* aDoc) :
+ HTMLSelectListAccessible(aContent, aDoc)
+{
+ mStateFlags |= eSharedNode;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxAccessible: Accessible
+
+nsIFrame*
+HTMLComboboxListAccessible::GetFrame() const
+{
+ nsIFrame* frame = HTMLSelectListAccessible::GetFrame();
+ nsIComboboxControlFrame* comboBox = do_QueryFrame(frame);
+ if (comboBox) {
+ return comboBox->GetDropDown();
+ }
+
+ return nullptr;
+}
+
+role
+HTMLComboboxListAccessible::NativeRole()
+{
+ return roles::COMBOBOX_LIST;
+}
+
+uint64_t
+HTMLComboboxListAccessible::NativeState()
+{
+ // As a HTMLComboboxListAccessible we can have the following states:
+ // FOCUSED, FOCUSABLE, FLOATING, INVISIBLE
+ // Get focus status from base class
+ uint64_t state = Accessible::NativeState();
+
+ nsIComboboxControlFrame* comboFrame = do_QueryFrame(mParent->GetFrame());
+ if (comboFrame && comboFrame->IsDroppedDown())
+ state |= states::FLOATING;
+ else
+ state |= states::INVISIBLE;
+
+ return state;
+}
+
+nsRect
+HTMLComboboxListAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const
+{
+ *aBoundingFrame = nullptr;
+
+ Accessible* comboAcc = Parent();
+ if (!comboAcc)
+ return nsRect();
+
+ if (0 == (comboAcc->State() & states::COLLAPSED)) {
+ return HTMLSelectListAccessible::RelativeBounds(aBoundingFrame);
+ }
+
+ // Get the first option.
+ nsIContent* content = mContent->GetFirstChild();
+ if (!content)
+ return nsRect();
+
+ nsIFrame* frame = content->GetPrimaryFrame();
+ if (!frame) {
+ *aBoundingFrame = nullptr;
+ return nsRect();
+ }
+
+ *aBoundingFrame = frame->GetParent();
+ return (*aBoundingFrame)->GetRect();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// HTMLComboboxListAccessible: Widgets
+
+bool
+HTMLComboboxListAccessible::IsActiveWidget() const
+{
+ return mParent && mParent->IsActiveWidget();
+}
+
+bool
+HTMLComboboxListAccessible::AreItemsOperable() const
+{
+ return mParent && mParent->AreItemsOperable();
+}
+