diff options
Diffstat (limited to 'accessible/html')
-rw-r--r-- | accessible/html/HTMLCanvasAccessible.cpp | 24 | ||||
-rw-r--r-- | accessible/html/HTMLCanvasAccessible.h | 35 | ||||
-rw-r--r-- | accessible/html/HTMLElementAccessibles.cpp | 204 | ||||
-rw-r--r-- | accessible/html/HTMLElementAccessibles.h | 120 | ||||
-rw-r--r-- | accessible/html/HTMLFormControlAccessible.cpp | 841 | ||||
-rw-r--r-- | accessible/html/HTMLFormControlAccessible.h | 284 | ||||
-rw-r--r-- | accessible/html/HTMLImageMapAccessible.cpp | 228 | ||||
-rw-r--r-- | accessible/html/HTMLImageMapAccessible.h | 89 | ||||
-rw-r--r-- | accessible/html/HTMLLinkAccessible.cpp | 147 | ||||
-rw-r--r-- | accessible/html/HTMLLinkAccessible.h | 51 | ||||
-rw-r--r-- | accessible/html/HTMLListAccessible.cpp | 199 | ||||
-rw-r--r-- | accessible/html/HTMLListAccessible.h | 105 | ||||
-rw-r--r-- | accessible/html/HTMLSelectAccessible.cpp | 610 | ||||
-rw-r--r-- | accessible/html/HTMLSelectAccessible.h | 222 | ||||
-rw-r--r-- | accessible/html/HTMLTableAccessible.cpp | 1139 | ||||
-rw-r--r-- | accessible/html/HTMLTableAccessible.h | 226 | ||||
-rw-r--r-- | accessible/html/moz.build | 50 |
17 files changed, 4574 insertions, 0 deletions
diff --git a/accessible/html/HTMLCanvasAccessible.cpp b/accessible/html/HTMLCanvasAccessible.cpp new file mode 100644 index 000000000..4cc748728 --- /dev/null +++ b/accessible/html/HTMLCanvasAccessible.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLCanvasAccessible.h" + +#include "Role.h" + +using namespace mozilla::a11y; + +HTMLCanvasAccessible:: + HTMLCanvasAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLCanvasAccessible, HyperTextAccessible) + +role +HTMLCanvasAccessible::NativeRole() +{ + return roles::CANVAS; +} diff --git a/accessible/html/HTMLCanvasAccessible.h b/accessible/html/HTMLCanvasAccessible.h new file mode 100644 index 000000000..f07b84435 --- /dev/null +++ b/accessible/html/HTMLCanvasAccessible.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_a11y_HTMLCanvasAccessible_h__ +#define mozilla_a11y_HTMLCanvasAccessible_h__ + +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +/** + * HTML canvas accessible (html:canvas). + */ +class HTMLCanvasAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLCanvasAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual a11y::role NativeRole() override; + +protected: + virtual ~HTMLCanvasAccessible() { } +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLElementAccessibles.cpp b/accessible/html/HTMLElementAccessibles.cpp new file mode 100644 index 000000000..39601151b --- /dev/null +++ b/accessible/html/HTMLElementAccessibles.cpp @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLElementAccessibles.h" + +#include "DocAccessible.h" +#include "nsAccUtils.h" +#include "nsIPersistentProperties2.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +#include "mozilla/dom/HTMLLabelElement.h" +#include "mozilla/dom/HTMLDetailsElement.h" +#include "mozilla/dom/HTMLSummaryElement.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLHRAccessible +//////////////////////////////////////////////////////////////////////////////// + +role +HTMLHRAccessible::NativeRole() +{ + return roles::SEPARATOR; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLBRAccessible +//////////////////////////////////////////////////////////////////////////////// + +role +HTMLBRAccessible::NativeRole() +{ + return roles::WHITESPACE; +} + +uint64_t +HTMLBRAccessible::NativeState() +{ + return states::READONLY; +} + +ENameValueFlag +HTMLBRAccessible::NativeName(nsString& aName) +{ + aName = static_cast<char16_t>('\n'); // Newline char + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLabelAccessible +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLLabelAccessible, HyperTextAccessible) + +ENameValueFlag +HTMLLabelAccessible::NativeName(nsString& aName) +{ + nsTextEquivUtils::GetNameFromSubtree(this, aName); + return aName.IsEmpty() ? eNameOK : eNameFromSubtree; +} + +Relation +HTMLLabelAccessible::RelationByType(RelationType aType) +{ + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABEL_FOR) { + dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromContent(mContent); + rel.AppendTarget(mDoc, label->GetControl()); + } + + return rel; +} + +uint8_t +HTMLLabelAccessible::ActionCount() +{ + return nsCoreUtils::IsLabelWithControl(mContent) ? 1 : 0; +} + +void +HTMLLabelAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex == 0) { + if (nsCoreUtils::IsLabelWithControl(mContent)) + aName.AssignLiteral("click"); + } +} + +bool +HTMLLabelAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != 0) + return false; + + DoCommand(); + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLOuputAccessible +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLOutputAccessible, HyperTextAccessible) + +Relation +HTMLOutputAccessible::RelationByType(RelationType aType) +{ + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::CONTROLLED_BY) + rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::_for)); + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLSummaryAccessible:: + HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mGenericTypes |= eButton; +} + +uint8_t +HTMLSummaryAccessible::ActionCount() +{ + return 1; +} + +void +HTMLSummaryAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex != eAction_Click) { + return; + } + + dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent); + if (!summary) { + return; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return; + } + + if (details->Open()) { + aName.AssignLiteral("collapse"); + } else { + aName.AssignLiteral("expand"); + } +} + +bool +HTMLSummaryAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != eAction_Click) + return false; + + DoCommand(); + return true; +} + +uint64_t +HTMLSummaryAccessible::NativeState() +{ + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + dom::HTMLSummaryElement* summary = dom::HTMLSummaryElement::FromContent(mContent); + if (!summary) { + return state; + } + + dom::HTMLDetailsElement* details = summary->GetDetails(); + if (!details) { + return state; + } + + if (details->Open()) { + state |= states::EXPANDED; + } else { + state |= states::COLLAPSED; + } + + return state; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSummaryAccessible: Widgets + +bool +HTMLSummaryAccessible::IsWidget() const +{ + return true; +} diff --git a/accessible/html/HTMLElementAccessibles.h b/accessible/html/HTMLElementAccessibles.h new file mode 100644 index 000000000..83abbe9e6 --- /dev/null +++ b/accessible/html/HTMLElementAccessibles.h @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_a11y_HTMLElementAccessibles_h__ +#define mozilla_a11y_HTMLElementAccessibles_h__ + +#include "BaseAccessibles.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for HTML hr element. + */ +class HTMLHRAccessible : public LeafAccessible +{ +public: + + HTMLHRAccessible(nsIContent* aContent, DocAccessible* aDoc) : + LeafAccessible(aContent, aDoc) {} + + // Accessible + virtual a11y::role NativeRole() override; +}; + +/** + * Used for HTML br element. + */ +class HTMLBRAccessible : public LeafAccessible +{ +public: + HTMLBRAccessible(nsIContent* aContent, DocAccessible* aDoc) : + LeafAccessible(aContent, aDoc) + { + mType = eHTMLBRType; + mGenericTypes |= eText; + } + + // Accessible + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; +}; + +/** + * Used for HTML label element. + */ +class HTMLLabelAccessible : public HyperTextAccessibleWrap +{ +public: + + HTMLLabelAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual Relation RelationByType(RelationType aType) override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + +protected: + virtual ~HTMLLabelAccessible() {} + virtual ENameValueFlag NativeName(nsString& aName) override; +}; + +/** + * Used for HTML output element. + */ +class HTMLOutputAccessible : public HyperTextAccessibleWrap +{ +public: + + HTMLOutputAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) {} + + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual Relation RelationByType(RelationType aType) override; + +protected: + virtual ~HTMLOutputAccessible() {} +}; + +/** + * Accessible for the HTML summary element. + */ +class HTMLSummaryAccessible : public HyperTextAccessibleWrap +{ + +public: + enum { eAction_Click = 0 }; + + HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual uint64_t NativeState() override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLFormControlAccessible.cpp b/accessible/html/HTMLFormControlAccessible.cpp new file mode 100644 index 000000000..a7b3874a1 --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.cpp @@ -0,0 +1,841 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLFormControlAccessible.h" + +#include "Accessible-inl.h" +#include "nsAccUtils.h" +#include "nsEventShell.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" + +#include "nsContentList.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "nsIDOMNSEditableElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIEditor.h" +#include "nsIFormControl.h" +#include "nsIPersistentProperties2.h" +#include "nsISelectionController.h" +#include "nsIServiceManager.h" +#include "nsITextControlFrame.h" +#include "nsNameSpaceManager.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/EventStates.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLCheckboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +role +HTMLCheckboxAccessible::NativeRole() +{ + return roles::CHECKBUTTON; +} + +uint8_t +HTMLCheckboxAccessible::ActionCount() +{ + return 1; +} + +void +HTMLCheckboxAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex == eAction_Click) { // 0 is the magic value for default action + uint64_t state = NativeState(); + if (state & states::CHECKED) + aName.AssignLiteral("uncheck"); + else if (state & states::MIXED) + aName.AssignLiteral("cycle"); + else + aName.AssignLiteral("check"); + } +} + +bool +HTMLCheckboxAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != 0) + return false; + + DoCommand(); + return true; +} + +uint64_t +HTMLCheckboxAccessible::NativeState() +{ + uint64_t state = LeafAccessible::NativeState(); + + state |= states::CHECKABLE; + HTMLInputElement* input = HTMLInputElement::FromContent(mContent); + if (!input) + return state; + + if (input->Indeterminate()) + return state | states::MIXED; + + if (input->Checked()) + return state | states::CHECKED; + + return state; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLCheckboxAccessible: Widgets + +bool +HTMLCheckboxAccessible::IsWidget() const +{ + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLRadioButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +uint64_t +HTMLRadioButtonAccessible::NativeState() +{ + uint64_t state = AccessibleWrap::NativeState(); + + state |= states::CHECKABLE; + + HTMLInputElement* input = HTMLInputElement::FromContent(mContent); + if (input && input->Checked()) + state |= states::CHECKED; + + return state; +} + +void +HTMLRadioButtonAccessible::GetPositionAndSizeInternal(int32_t* aPosInSet, + int32_t* aSetSize) +{ + int32_t namespaceId = mContent->NodeInfo()->NamespaceID(); + nsAutoString tagName; + mContent->NodeInfo()->GetName(tagName); + + nsAutoString type; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); + nsAutoString name; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); + + RefPtr<nsContentList> inputElms; + + nsCOMPtr<nsIFormControl> formControlNode(do_QueryInterface(mContent)); + dom::Element* formElm = formControlNode->GetFormElement(); + if (formElm) + inputElms = NS_GetContentList(formElm, namespaceId, tagName); + else + inputElms = NS_GetContentList(mContent->OwnerDoc(), namespaceId, tagName); + NS_ENSURE_TRUE_VOID(inputElms); + + uint32_t inputCount = inputElms->Length(false); + + // Compute posinset and setsize. + int32_t indexOf = 0; + int32_t count = 0; + + for (uint32_t index = 0; index < inputCount; index++) { + nsIContent* inputElm = inputElms->Item(index, false); + if (inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + type, eCaseMatters) && + inputElm->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, + name, eCaseMatters) && mDoc->HasAccessible(inputElm)) { + count++; + if (inputElm == mContent) + indexOf = count; + } + } + + *aPosInSet = indexOf; + *aSetSize = count; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLButtonAccessible:: + HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mGenericTypes |= eButton; +} + +uint8_t +HTMLButtonAccessible::ActionCount() +{ + return 1; +} + +void +HTMLButtonAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex == eAction_Click) + aName.AssignLiteral("press"); +} + +bool +HTMLButtonAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != eAction_Click) + return false; + + DoCommand(); + return true; +} + +uint64_t +HTMLButtonAccessible::State() +{ + uint64_t state = HyperTextAccessibleWrap::State(); + if (state == states::DEFUNCT) + return state; + + // Inherit states from input@type="file" suitable for the button. Note, + // no special processing for unavailable state since inheritance is supplied + // other code paths. + if (mParent && mParent->IsHTMLFileInput()) { + uint64_t parentState = mParent->State(); + state |= parentState & (states::BUSY | states::REQUIRED | + states::HASPOPUP | states::INVALID); + } + + return state; +} + +uint64_t +HTMLButtonAccessible::NativeState() +{ + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + EventStates elmState = mContent->AsElement()->State(); + if (elmState.HasState(NS_EVENT_STATE_DEFAULT)) + state |= states::DEFAULT; + + return state; +} + +role +HTMLButtonAccessible::NativeRole() +{ + return roles::PUSHBUTTON; +} + +ENameValueFlag +HTMLButtonAccessible::NativeName(nsString& aName) +{ + // No need to check @value attribute for buttons since this attribute results + // in native anonymous text node and the name is calculated from subtree. + // The same magic works for @alt and @value attributes in case of type="image" + // element that has no valid @src (note if input@type="image" has an image + // then neither @alt nor @value attributes are used to generate a visual label + // and thus we need to obtain the accessible name directly from attribute + // value). Also the same algorithm works in case of default labels for + // type="submit"/"reset"/"image" elements. + + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty() || !mContent->IsHTMLElement(nsGkAtoms::input) || + !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::image, eCaseMatters)) + return nameFlag; + + if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName)) + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); + + aName.CompressWhitespace(); + return eNameOK; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLButtonAccessible: Widgets + +bool +HTMLButtonAccessible::IsWidget() const +{ + return true; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTextFieldAccessible:: + HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mType = eHTMLTextFieldType; +} + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTextFieldAccessible, + HyperTextAccessible) + +role +HTMLTextFieldAccessible::NativeRole() +{ + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase)) { + return roles::PASSWORD_TEXT; + } + + return roles::ENTRY; +} + +already_AddRefed<nsIPersistentProperties> +HTMLTextFieldAccessible::NativeAttributes() +{ + nsCOMPtr<nsIPersistentProperties> attributes = + HyperTextAccessibleWrap::NativeAttributes(); + + // Expose type for text input elements as it gives some useful context, + // especially for mobile. + nsAutoString type; + if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) { + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type); + if (!ARIARoleMap() && type.EqualsLiteral("search")) { + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, + NS_LITERAL_STRING("searchbox")); + } + } + + return attributes.forget(); +} + +ENameValueFlag +HTMLTextFieldAccessible::NativeName(nsString& aName) +{ + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + // If part of compound of XUL widget then grab a name from XUL widget element. + nsIContent* widgetElm = XULWidgetElm(); + if (widgetElm) + XULElmName(mDoc, widgetElm, aName); + + if (!aName.IsEmpty()) + return eNameOK; + + // text inputs and textareas might have useful placeholder text + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName); + return eNameOK; +} + +void +HTMLTextFieldAccessible::Value(nsString& aValue) +{ + aValue.Truncate(); + if (NativeState() & states::PROTECTED) // Don't return password text! + return; + + nsCOMPtr<nsIDOMHTMLTextAreaElement> textArea(do_QueryInterface(mContent)); + if (textArea) { + textArea->GetValue(aValue); + return; + } + + HTMLInputElement* input = HTMLInputElement::FromContent(mContent); + if (input) + input->GetValue(aValue); +} + +void +HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const +{ + HyperTextAccessibleWrap::ApplyARIAState(aState); + aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState); + + // If part of compound of XUL widget then pick up ARIA stuff from XUL widget + // element. + nsIContent* widgetElm = XULWidgetElm(); + if (widgetElm) + aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState); +} + +uint64_t +HTMLTextFieldAccessible::NativeState() +{ + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + // Text fields are always editable, even if they are also read only or + // disabled. + state |= states::EDITABLE; + + // can be focusable, focused, protected. readonly, unavailable, selected + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::password, eIgnoreCase)) { + state |= states::PROTECTED; + } + + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly)) { + state |= states::READONLY; + } + + // Is it an <input> or a <textarea> ? + HTMLInputElement* input = HTMLInputElement::FromContent(mContent); + state |= input && input->IsSingleLineTextControl() ? + states::SINGLE_LINE : states::MULTI_LINE; + + if (state & (states::PROTECTED | states::MULTI_LINE | states::READONLY | + states::UNAVAILABLE)) + return state; + + // Expose autocomplete states if this input is part of autocomplete widget. + Accessible* widget = ContainerWidget(); + if (widget && widget-IsAutoComplete()) { + state |= states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION; + return state; + } + + // Expose autocomplete state if it has associated autocomplete list. + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::list)) + return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP; + + // Ordinal XUL textboxes don't support autocomplete. + if (!XULWidgetElm() && Preferences::GetBool("browser.formfill.enable")) { + // Check to see if autocompletion is allowed on this input. We don't expose + // it for password fields even though the entire password can be remembered + // for a page if the user asks it to be. However, the kind of autocomplete + // we're talking here is based on what the user types, where a popup of + // possible choices comes up. + nsAutoString autocomplete; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, + autocomplete); + + if (!autocomplete.LowerCaseEqualsLiteral("off")) { + nsIContent* formContent = input->GetFormElement(); + if (formContent) { + formContent->GetAttr(kNameSpaceID_None, + nsGkAtoms::autocomplete, autocomplete); + } + + if (!formContent || !autocomplete.LowerCaseEqualsLiteral("off")) + state |= states::SUPPORTS_AUTOCOMPLETION; + } + } + + return state; +} + +uint8_t +HTMLTextFieldAccessible::ActionCount() +{ + return 1; +} + +void +HTMLTextFieldAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + if (aIndex == eAction_Click) + aName.AssignLiteral("activate"); +} + +bool +HTMLTextFieldAccessible::DoAction(uint8_t aIndex) +{ + if (aIndex != 0) + return false; + + TakeFocus(); + return true; +} + +already_AddRefed<nsIEditor> +HTMLTextFieldAccessible::GetEditor() const +{ + nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(mContent)); + if (!editableElt) + return nullptr; + + // nsGenericHTMLElement::GetEditor has a security check. + // Make sure we're not restricted by the permissions of + // whatever script is currently running. + mozilla::dom::AutoNoJSAPI nojsapi; + + nsCOMPtr<nsIEditor> editor; + editableElt->GetEditor(getter_AddRefs(editor)); + + return editor.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTextFieldAccessible: Widgets + +bool +HTMLTextFieldAccessible::IsWidget() const +{ + return true; +} + +Accessible* +HTMLTextFieldAccessible::ContainerWidget() const +{ + if (!mParent || mParent->Role() != roles::AUTOCOMPLETE) { + return nullptr; + } + return mParent; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFileInputAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFileInputAccessible:: +HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mType = eHTMLFileInputType; +} + +role +HTMLFileInputAccessible::NativeRole() +{ + // JAWS wants a text container, others don't mind. No specific role in + // AT APIs. + return roles::TEXT_CONTAINER; +} + +nsresult +HTMLFileInputAccessible::HandleAccEvent(AccEvent* aEvent) +{ + nsresult rv = HyperTextAccessibleWrap::HandleAccEvent(aEvent); + NS_ENSURE_SUCCESS(rv, rv); + + // Redirect state change events for inherited states to child controls. Note, + // unavailable state is not redirected. That's a standard for unavailable + // state handling. + AccStateChangeEvent* event = downcast_accEvent(aEvent); + if (event && + (event->GetState() == states::BUSY || + event->GetState() == states::REQUIRED || + event->GetState() == states::HASPOPUP || + event->GetState() == states::INVALID)) { + Accessible* button = GetChildAt(0); + if (button && button->Role() == roles::PUSHBUTTON) { + RefPtr<AccStateChangeEvent> childEvent = + new AccStateChangeEvent(button, event->GetState(), + event->IsStateEnabled(), event->FromUserInput()); + nsEventShell::FireEvent(childEvent); + } + } + + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLSpinnerAccessible +//////////////////////////////////////////////////////////////////////////////// + +role +HTMLSpinnerAccessible::NativeRole() +{ + return roles::SPINBUTTON; +} + +void +HTMLSpinnerAccessible::Value(nsString& aValue) +{ + AccessibleWrap::Value(aValue); + if (!aValue.IsEmpty()) + return; + + HTMLInputElement::FromContent(mContent)->GetValue(aValue); +} + +double +HTMLSpinnerAccessible::MaxValue() const +{ + double value = AccessibleWrap::MaxValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetMaximum().toDouble(); +} + + +double +HTMLSpinnerAccessible::MinValue() const +{ + double value = AccessibleWrap::MinValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetMinimum().toDouble(); +} + +double +HTMLSpinnerAccessible::Step() const +{ + double value = AccessibleWrap::Step(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetStep().toDouble(); +} + +double +HTMLSpinnerAccessible::CurValue() const +{ + double value = AccessibleWrap::CurValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetValueAsDecimal().toDouble(); +} + +bool +HTMLSpinnerAccessible::SetCurValue(double aValue) +{ + ErrorResult er; + HTMLInputElement::FromContent(mContent)->SetValueAsNumber(aValue, er); + return !er.Failed(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLRangeAccessible +//////////////////////////////////////////////////////////////////////////////// + +role +HTMLRangeAccessible::NativeRole() +{ + return roles::SLIDER; +} + +bool +HTMLRangeAccessible::IsWidget() const +{ + return true; +} + +void +HTMLRangeAccessible::Value(nsString& aValue) +{ + LeafAccessible::Value(aValue); + if (!aValue.IsEmpty()) + return; + + HTMLInputElement::FromContent(mContent)->GetValue(aValue); +} + +double +HTMLRangeAccessible::MaxValue() const +{ + double value = LeafAccessible::MaxValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetMaximum().toDouble(); +} + +double +HTMLRangeAccessible::MinValue() const +{ + double value = LeafAccessible::MinValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetMinimum().toDouble(); +} + +double +HTMLRangeAccessible::Step() const +{ + double value = LeafAccessible::Step(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetStep().toDouble(); +} + +double +HTMLRangeAccessible::CurValue() const +{ + double value = LeafAccessible::CurValue(); + if (!IsNaN(value)) + return value; + + return HTMLInputElement::FromContent(mContent)->GetValueAsDecimal().toDouble(); +} + +bool +HTMLRangeAccessible::SetCurValue(double aValue) +{ + ErrorResult er; + HTMLInputElement::FromContent(mContent)->SetValueAsNumber(aValue, er); + return !er.Failed(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLGroupboxAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLGroupboxAccessible:: + HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +role +HTMLGroupboxAccessible::NativeRole() +{ + return roles::GROUPING; +} + +nsIContent* +HTMLGroupboxAccessible::GetLegend() const +{ + for (nsIContent* legendContent = mContent->GetFirstChild(); legendContent; + legendContent = legendContent->GetNextSibling()) { + if (legendContent->NodeInfo()->Equals(nsGkAtoms::legend, + mContent->GetNameSpaceID())) { + // Either XHTML namespace or no namespace + return legendContent; + } + } + + return nullptr; +} + +ENameValueFlag +HTMLGroupboxAccessible::NativeName(nsString& aName) +{ + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + nsIContent* legendContent = GetLegend(); + if (legendContent) + nsTextEquivUtils::AppendTextEquivFromContent(this, legendContent, &aName); + + return eNameOK; +} + +Relation +HTMLGroupboxAccessible::RelationByType(RelationType aType) +{ + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + // No override for label, so use <legend> for this <fieldset> + if (aType == RelationType::LABELLED_BY) + rel.AppendTarget(mDoc, GetLegend()); + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLegendAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLegendAccessible:: + HTMLLegendAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +Relation +HTMLLegendAccessible::RelationByType(RelationType aType) +{ + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABEL_FOR) + return rel; + + Accessible* groupbox = Parent(); + if (groupbox && groupbox->Role() == roles::GROUPING) + rel.AppendTarget(groupbox); + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFigureAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFigureAccessible:: + HTMLFigureAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +ENameValueFlag +HTMLFigureAccessible::NativeName(nsString& aName) +{ + ENameValueFlag nameFlag = HyperTextAccessibleWrap::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + nsIContent* captionContent = Caption(); + if (captionContent) + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName); + + return eNameOK; +} + +Relation +HTMLFigureAccessible::RelationByType(RelationType aType) +{ + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABELLED_BY) + rel.AppendTarget(mDoc, Caption()); + + return rel; +} + +nsIContent* +HTMLFigureAccessible::Caption() const +{ + for (nsIContent* childContent = mContent->GetFirstChild(); childContent; + childContent = childContent->GetNextSibling()) { + if (childContent->NodeInfo()->Equals(nsGkAtoms::figcaption, + mContent->GetNameSpaceID())) { + return childContent; + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLFigcaptionAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLFigcaptionAccessible:: + HTMLFigcaptionAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +Relation +HTMLFigcaptionAccessible::RelationByType(RelationType aType) +{ + Relation rel = HyperTextAccessibleWrap::RelationByType(aType); + if (aType != RelationType::LABEL_FOR) + return rel; + + Accessible* figure = Parent(); + if (figure && + figure->GetContent()->NodeInfo()->Equals(nsGkAtoms::figure, + mContent->GetNameSpaceID())) { + rel.AppendTarget(figure); + } + + return rel; +} diff --git a/accessible/html/HTMLFormControlAccessible.h b/accessible/html/HTMLFormControlAccessible.h new file mode 100644 index 000000000..c2f30d53a --- /dev/null +++ b/accessible/html/HTMLFormControlAccessible.h @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef MOZILLA_A11Y_HTMLFormControlAccessible_H_ +#define MOZILLA_A11Y_HTMLFormControlAccessible_H_ + +#include "FormControlAccessible.h" +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +/** + * Accessible for HTML progress element. + */ +typedef ProgressMeterAccessible<1> HTMLProgressMeterAccessible; + +/** + * Accessible for HTML input@type="checkbox". + */ +class HTMLCheckboxAccessible : public LeafAccessible +{ + +public: + enum { eAction_Click = 0 }; + + HTMLCheckboxAccessible(nsIContent* aContent, DocAccessible* aDoc) : + LeafAccessible(aContent, aDoc) + { + // Ignore "CheckboxStateChange" DOM event in lieu of document observer + // state change notification. + mStateFlags |= eIgnoreDOMUIEvent; + } + + // Accessible + virtual mozilla::a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; +}; + + +/** + * Accessible for HTML input@type="radio" element. + */ +class HTMLRadioButtonAccessible : public RadioButtonAccessible +{ + +public: + HTMLRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc) : + RadioButtonAccessible(aContent, aDoc) + { + // Ignore "RadioStateChange" DOM event in lieu of document observer + // state change notification. + mStateFlags |= eIgnoreDOMUIEvent; + } + + // Accessible + virtual uint64_t NativeState() override; + virtual void GetPositionAndSizeInternal(int32_t *aPosInSet, + int32_t *aSetSize) override; +}; + + +/** + * Accessible for HTML input@type="button", @type="submit", @type="image" + * and HTML button elements. + */ +class HTMLButtonAccessible : public HyperTextAccessibleWrap +{ + +public: + enum { eAction_Click = 0 }; + + HTMLButtonAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual mozilla::a11y::role NativeRole() override; + virtual uint64_t State() override; + virtual uint64_t NativeState() override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; +}; + + +/** + * Accessible for HTML input@type="text", input@type="password", textarea and + * other HTML text controls. + */ +class HTMLTextFieldAccessible final : public HyperTextAccessibleWrap +{ + +public: + enum { eAction_Click = 0 }; + + HTMLTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc); + + NS_DECL_ISUPPORTS_INHERITED + + // HyperTextAccessible + virtual already_AddRefed<nsIEditor> GetEditor() const override; + + // Accessible + virtual void Value(nsString& aValue) override; + virtual void ApplyARIAState(uint64_t* aState) const override; + virtual mozilla::a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; + virtual Accessible* ContainerWidget() const override; + +protected: + virtual ~HTMLTextFieldAccessible() {} + + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; + + /** + * Return a XUL widget element this input is part of. + */ + nsIContent* XULWidgetElm() const { return mContent->GetBindingParent(); } +}; + + +/** + * Accessible for input@type="file" element. + */ +class HTMLFileInputAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLFileInputAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual mozilla::a11y::role NativeRole() override; + virtual nsresult HandleAccEvent(AccEvent* aAccEvent) override; +}; + + +/** + * Used for HTML input@type="number". + */ +class HTMLSpinnerAccessible : public AccessibleWrap +{ +public: + HTMLSpinnerAccessible(nsIContent* aContent, DocAccessible* aDoc) : + AccessibleWrap(aContent, aDoc) + { + mStateFlags |= eHasNumericValue; +} + + // Accessible + virtual mozilla::a11y::role NativeRole() override; + virtual void Value(nsString& aValue) override; + + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual double Step() const override; + virtual bool SetCurValue(double aValue) override; +}; + + +/** + * Used for input@type="range" element. + */ +class HTMLRangeAccessible : public LeafAccessible +{ +public: + HTMLRangeAccessible(nsIContent* aContent, DocAccessible* aDoc) : + LeafAccessible(aContent, aDoc) + { + mStateFlags |= eHasNumericValue; + } + + // Accessible + virtual void Value(nsString& aValue) override; + virtual mozilla::a11y::role NativeRole() override; + + // Value + virtual double MaxValue() const override; + virtual double MinValue() const override; + virtual double CurValue() const override; + virtual double Step() const override; + virtual bool SetCurValue(double aValue) override; + + // Widgets + virtual bool IsWidget() const override; +}; + + +/** + * Accessible for HTML fieldset element. + */ +class HTMLGroupboxAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLGroupboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual mozilla::a11y::role NativeRole() override; + virtual Relation RelationByType(RelationType aType) override; + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; + + // HTMLGroupboxAccessible + nsIContent* GetLegend() const; +}; + + +/** + * Accessible for HTML legend element. + */ +class HTMLLegendAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLLegendAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual Relation RelationByType(RelationType aType) override; +}; + +/** + * Accessible for HTML5 figure element. + */ +class HTMLFigureAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLFigureAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual Relation RelationByType(RelationType aType) override; + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; + + // HTMLLegendAccessible + nsIContent* Caption() const; +}; + + +/** + * Accessible for HTML5 figcaption element. + */ +class HTMLFigcaptionAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLFigcaptionAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual Relation RelationByType(RelationType aType) override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLImageMapAccessible.cpp b/accessible/html/HTMLImageMapAccessible.cpp new file mode 100644 index 000000000..d6acbeba6 --- /dev/null +++ b/accessible/html/HTMLImageMapAccessible.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLImageMapAccessible.h" + +#include "ARIAMap.h" +#include "nsAccUtils.h" +#include "DocAccessible-inl.h" +#include "Role.h" + +#include "nsIDOMHTMLCollection.h" +#include "nsIServiceManager.h" +#include "nsIDOMElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIFrame.h" +#include "nsImageFrame.h" +#include "nsImageMap.h" +#include "nsIURI.h" + +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLImageMapAccessible:: + HTMLImageMapAccessible(nsIContent* aContent, DocAccessible* aDoc) : + ImageAccessibleWrap(aContent, aDoc) +{ + mType = eImageMapType; + + UpdateChildAreas(false); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: nsISupports + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLImageMapAccessible, ImageAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: Accessible public + +role +HTMLImageMapAccessible::NativeRole() +{ + return roles::IMAGE_MAP; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: HyperLinkAccessible + +uint32_t +HTMLImageMapAccessible::AnchorCount() +{ + return ChildCount(); +} + +Accessible* +HTMLImageMapAccessible::AnchorAt(uint32_t aAnchorIndex) +{ + return GetChildAt(aAnchorIndex); +} + +already_AddRefed<nsIURI> +HTMLImageMapAccessible::AnchorURIAt(uint32_t aAnchorIndex) +{ + Accessible* area = GetChildAt(aAnchorIndex); + if (!area) + return nullptr; + + nsIContent* linkContent = area->GetContent(); + return linkContent ? linkContent->GetHrefURI() : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: public + +void +HTMLImageMapAccessible::UpdateChildAreas(bool aDoFireEvents) +{ + nsImageFrame* imageFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + + // If image map is not initialized yet then we trigger one time more later. + nsImageMap* imageMapObj = imageFrame->GetExistingImageMap(); + if (!imageMapObj) + return; + + TreeMutation mt(this, TreeMutation::kNoEvents & !aDoFireEvents); + + // Remove areas that are not a valid part of the image map anymore. + for (int32_t childIdx = mChildren.Length() - 1; childIdx >= 0; childIdx--) { + Accessible* area = mChildren.ElementAt(childIdx); + if (area->GetContent()->GetPrimaryFrame()) + continue; + + mt.BeforeRemoval(area); + RemoveChild(area); + } + + // Insert new areas into the tree. + uint32_t areaElmCount = imageMapObj->AreaCount(); + for (uint32_t idx = 0; idx < areaElmCount; idx++) { + nsIContent* areaContent = imageMapObj->GetAreaAt(idx); + Accessible* area = mChildren.SafeElementAt(idx); + if (!area || area->GetContent() != areaContent) { + RefPtr<Accessible> area = new HTMLAreaAccessible(areaContent, mDoc); + mDoc->BindToDocument(area, aria::GetRoleMap(areaContent->AsElement())); + + if (!InsertChildAt(idx, area)) { + mDoc->UnbindFromDocument(area); + break; + } + + mt.AfterInsertion(area); + } + } + + mt.Done(); +} + +Accessible* +HTMLImageMapAccessible::GetChildAccessibleFor(const nsINode* aNode) const +{ + uint32_t length = mChildren.Length(); + for (uint32_t i = 0; i < length; i++) { + Accessible* area = mChildren[i]; + if (area->GetContent() == aNode) + return area; + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLAreaAccessible:: + HTMLAreaAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HTMLLinkAccessible(aContent, aDoc) +{ + // Make HTML area DOM element not accessible. HTML image map accessible + // manages its tree itself. + mStateFlags |= eNotNodeMapEntry; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible: Accessible + +ENameValueFlag +HTMLAreaAccessible::NativeName(nsString& aName) +{ + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + if (!mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::alt, aName)) + Value(aName); + + return eNameOK; +} + +void +HTMLAreaAccessible::Description(nsString& aDescription) +{ + aDescription.Truncate(); + + // Still to do - follow IE's standard here + nsCOMPtr<nsIDOMHTMLAreaElement> area(do_QueryInterface(mContent)); + if (area) + area->GetShape(aDescription); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLAreaAccessible: Accessible public + +Accessible* +HTMLAreaAccessible::ChildAtPoint(int32_t aX, int32_t aY, + EWhichChildAtPoint aWhichChild) +{ + // Don't walk into area accessibles. + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLImageMapAccessible: HyperLinkAccessible + +uint32_t +HTMLAreaAccessible::StartOffset() +{ + // Image map accessible is not hypertext accessible therefore + // StartOffset/EndOffset implementations of Accessible doesn't work here. + // We return index in parent because image map contains area links only which + // are embedded objects. + // XXX: image map should be a hypertext accessible. + return IndexInParent(); +} + +uint32_t +HTMLAreaAccessible::EndOffset() +{ + return IndexInParent() + 1; +} + +nsRect +HTMLAreaAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const +{ + nsIFrame* frame = GetFrame(); + if (!frame) + return nsRect(); + + nsImageFrame* imageFrame = do_QueryFrame(frame); + nsImageMap* map = imageFrame->GetImageMap(); + + nsRect bounds; + nsresult rv = map->GetBoundsForAreaContent(mContent, bounds); + if (NS_FAILED(rv)) + return nsRect(); + + // XXX Areas are screwy; they return their rects as a pair of points, one pair + // stored into the width and height. + *aBoundingFrame = frame; + bounds.width -= bounds.x; + bounds.height -= bounds.y; + return bounds; +} diff --git a/accessible/html/HTMLImageMapAccessible.h b/accessible/html/HTMLImageMapAccessible.h new file mode 100644 index 000000000..1668245dc --- /dev/null +++ b/accessible/html/HTMLImageMapAccessible.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_a11y_HTMLImageMapAccessible_h__ +#define mozilla_a11y_HTMLImageMapAccessible_h__ + +#include "HTMLLinkAccessible.h" +#include "ImageAccessibleWrap.h" +#include "nsIDOMHTMLMapElement.h" + +namespace mozilla { +namespace a11y { + +/** + * Used for HTML image maps. + */ +class HTMLImageMapAccessible final : public ImageAccessibleWrap +{ +public: + HTMLImageMapAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports and cycle collector + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual a11y::role NativeRole() override; + + // HyperLinkAccessible + virtual uint32_t AnchorCount() override; + virtual Accessible* AnchorAt(uint32_t aAnchorIndex) override; + virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override; + + /** + * Update area children of the image map. + */ + void UpdateChildAreas(bool aDoFireEvents = true); + + /** + * Return accessible of child node. + */ + Accessible* GetChildAccessibleFor(const nsINode* aNode) const; + +protected: + virtual ~HTMLImageMapAccessible() { } +}; + +/** + * Accessible for image map areas - must be child of image. + */ +class HTMLAreaAccessible final : public HTMLLinkAccessible +{ +public: + + HTMLAreaAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual void Description(nsString& aDescription) override; + virtual Accessible* ChildAtPoint(int32_t aX, int32_t aY, + EWhichChildAtPoint aWhichChild) override; + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + + // HyperLinkAccessible + virtual uint32_t StartOffset() override; + virtual uint32_t EndOffset() override; + + virtual bool IsAcceptableChild(nsIContent* aEl) const override + { return false; } + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// Accessible downcasting method + +inline HTMLImageMapAccessible* +Accessible::AsImageMap() +{ + return IsImageMap() ? static_cast<HTMLImageMapAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLLinkAccessible.cpp b/accessible/html/HTMLLinkAccessible.cpp new file mode 100644 index 000000000..6172c857b --- /dev/null +++ b/accessible/html/HTMLLinkAccessible.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLLinkAccessible.h" + +#include "nsCoreUtils.h" +#include "DocAccessible.h" +#include "Role.h" +#include "States.h" + +#include "nsContentUtils.h" +#include "mozilla/EventStates.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLinkAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLinkAccessible:: + HTMLLinkAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLLinkAccessible, HyperTextAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// nsIAccessible + +role +HTMLLinkAccessible::NativeRole() +{ + return roles::LINK; +} + +uint64_t +HTMLLinkAccessible::NativeState() +{ + return HyperTextAccessibleWrap::NativeState() & ~states::READONLY; +} + +uint64_t +HTMLLinkAccessible::NativeLinkState() const +{ + EventStates eventState = mContent->AsElement()->State(); + if (eventState.HasState(NS_EVENT_STATE_UNVISITED)) + return states::LINKED; + + if (eventState.HasState(NS_EVENT_STATE_VISITED)) + return states::LINKED | states::TRAVERSED; + + // This is a either named anchor (a link with also a name attribute) or + // it doesn't have any attributes. Check if 'click' event handler is + // registered, otherwise bail out. + return nsCoreUtils::HasClickListener(mContent) ? states::LINKED : 0; +} + +uint64_t +HTMLLinkAccessible::NativeInteractiveState() const +{ + uint64_t state = HyperTextAccessibleWrap::NativeInteractiveState(); + + // This is how we indicate it is a named anchor. In other words, this anchor + // can be selected as a location :) There is no other better state to use to + // indicate this. + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name)) + state |= states::SELECTABLE; + + return state; +} + +void +HTMLLinkAccessible::Value(nsString& aValue) +{ + aValue.Truncate(); + + HyperTextAccessible::Value(aValue); + if (aValue.IsEmpty()) + nsContentUtils::GetLinkLocation(mContent->AsElement(), aValue); +} + +uint8_t +HTMLLinkAccessible::ActionCount() +{ + return IsLinked() ? 1 : HyperTextAccessible::ActionCount(); +} + +void +HTMLLinkAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) +{ + aName.Truncate(); + + if (!IsLinked()) { + HyperTextAccessible::ActionNameAt(aIndex, aName); + return; + } + + // Action 0 (default action): Jump to link + if (aIndex == eAction_Jump) + aName.AssignLiteral("jump"); +} + +bool +HTMLLinkAccessible::DoAction(uint8_t aIndex) +{ + if (!IsLinked()) + return HyperTextAccessible::DoAction(aIndex); + + // Action 0 (default action): Jump to link + if (aIndex != eAction_Jump) + return false; + + DoCommand(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// HyperLinkAccessible + +bool +HTMLLinkAccessible::IsLink() +{ + // Expose HyperLinkAccessible unconditionally. + return true; +} + +already_AddRefed<nsIURI> +HTMLLinkAccessible::AnchorURIAt(uint32_t aAnchorIndex) +{ + return aAnchorIndex == 0 ? mContent->GetHrefURI() : nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// Protected members + +bool +HTMLLinkAccessible::IsLinked() const +{ + EventStates state = mContent->AsElement()->State(); + return state.HasAtLeastOneOfStates(NS_EVENT_STATE_VISITED | + NS_EVENT_STATE_UNVISITED); +} diff --git a/accessible/html/HTMLLinkAccessible.h b/accessible/html/HTMLLinkAccessible.h new file mode 100644 index 000000000..aa4da22be --- /dev/null +++ b/accessible/html/HTMLLinkAccessible.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_a11y_HTMLLinkAccessible_h__ +#define mozilla_a11y_HTMLLinkAccessible_h__ + +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class HTMLLinkAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLLinkAccessible(nsIContent* aContent, DocAccessible* aDoc); + + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual void Value(nsString& aValue) override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual uint64_t NativeLinkState() const override; + virtual uint64_t NativeInteractiveState() const override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // HyperLinkAccessible + virtual bool IsLink() override; + virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex) override; + +protected: + virtual ~HTMLLinkAccessible() {} + + enum { eAction_Jump = 0 }; + + /** + * Returns true if the link has href attribute. + */ + bool IsLinked() const; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLListAccessible.cpp b/accessible/html/HTMLListAccessible.cpp new file mode 100644 index 000000000..d5de25718 --- /dev/null +++ b/accessible/html/HTMLListAccessible.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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 "HTMLListAccessible.h" + +#include "DocAccessible.h" +#include "nsAccUtils.h" +#include "Role.h" +#include "States.h" + +#include "nsBlockFrame.h" +#include "nsBulletFrame.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListAccessible +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLListAccessible, HyperTextAccessible) + +role +HTMLListAccessible::NativeRole() +{ + a11y::role r = GetAccService()->MarkupRole(mContent); + return r != roles::NOTHING ? r : roles::LIST; +} + +uint64_t +HTMLListAccessible::NativeState() +{ + return HyperTextAccessibleWrap::NativeState() | states::READONLY; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLIAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLLIAccessible:: + HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc), mBullet(nullptr) +{ + mType = eHTMLLiType; + + nsBlockFrame* blockFrame = do_QueryFrame(GetFrame()); + if (blockFrame && blockFrame->HasBullet()) { + mBullet = new HTMLListBulletAccessible(mContent, mDoc); + Document()->BindToDocument(mBullet, nullptr); + AppendChild(mBullet); + } +} + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLLIAccessible, HyperTextAccessible) + +void +HTMLLIAccessible::Shutdown() +{ + mBullet = nullptr; + + HyperTextAccessibleWrap::Shutdown(); +} + +role +HTMLLIAccessible::NativeRole() +{ + a11y::role r = GetAccService()->MarkupRole(mContent); + return r != roles::NOTHING ? r : roles::LISTITEM; +} + +uint64_t +HTMLLIAccessible::NativeState() +{ + return HyperTextAccessibleWrap::NativeState() | states::READONLY; +} + +nsIntRect +HTMLLIAccessible::Bounds() const +{ + nsIntRect rect = AccessibleWrap::Bounds(); + if (rect.IsEmpty() || !mBullet || mBullet->IsInside()) + return rect; + + nsIntRect bulletRect = mBullet->Bounds(); + + rect.width += rect.x - bulletRect.x; + rect.x = bulletRect.x; // Move x coordinate of list item over to cover bullet as well + return rect; +} + +bool +HTMLLIAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) +{ + // Adjust index if there's a bullet. + if (mBullet && aIndex == 0 && aChild != mBullet) { + return HyperTextAccessible::InsertChildAt(aIndex + 1, aChild); + } + + return HyperTextAccessible::InsertChildAt(aIndex, aChild); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLLIAccessible: public + +void +HTMLLIAccessible::UpdateBullet(bool aHasBullet) +{ + if (aHasBullet == !!mBullet) { + NS_NOTREACHED("Bullet and accessible are in sync already!"); + return; + } + + TreeMutation mt(this); + if (aHasBullet) { + mBullet = new HTMLListBulletAccessible(mContent, mDoc); + mDoc->BindToDocument(mBullet, nullptr); + InsertChildAt(0, mBullet); + mt.AfterInsertion(mBullet); + } + else { + mt.BeforeRemoval(mBullet); + RemoveChild(mBullet); + mBullet = nullptr; + } + mt.Done(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListBulletAccessible +//////////////////////////////////////////////////////////////////////////////// +HTMLListBulletAccessible:: + HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc) : + LeafAccessible(aContent, aDoc) +{ + mGenericTypes |= eText; + mStateFlags |= eSharedNode; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListBulletAccessible: Accessible + +nsIFrame* +HTMLListBulletAccessible::GetFrame() const +{ + nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + return blockFrame ? blockFrame->GetBullet() : nullptr; +} + +ENameValueFlag +HTMLListBulletAccessible::Name(nsString &aName) +{ + aName.Truncate(); + + // Native anonymous content, ARIA can't be used. Get list bullet text. + nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (blockFrame) { + blockFrame->GetSpokenBulletText(aName); + } + + return eNameOK; +} + +role +HTMLListBulletAccessible::NativeRole() +{ + return roles::STATICTEXT; +} + +uint64_t +HTMLListBulletAccessible::NativeState() +{ + return LeafAccessible::NativeState() | states::READONLY; +} + +void +HTMLListBulletAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, + uint32_t aLength) +{ + nsAutoString bulletText; + nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (blockFrame) + blockFrame->GetSpokenBulletText(bulletText); + + aText.Append(Substring(bulletText, aStartOffset, aLength)); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLListBulletAccessible: public + +bool +HTMLListBulletAccessible::IsInside() const +{ + nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + return blockFrame ? blockFrame->HasInsideBullet() : false; +} diff --git a/accessible/html/HTMLListAccessible.h b/accessible/html/HTMLListAccessible.h new file mode 100644 index 000000000..ad4a2d425 --- /dev/null +++ b/accessible/html/HTMLListAccessible.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_a11y_HTMLListAccessible_h__ +#define mozilla_a11y_HTMLListAccessible_h__ + +#include "BaseAccessibles.h" +#include "HyperTextAccessibleWrap.h" + +namespace mozilla { +namespace a11y { + +class HTMLListBulletAccessible; + +/** + * Used for HTML list (like HTML ul). + */ +class HTMLListAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLListAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) { mGenericTypes |= eList; } + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + +protected: + virtual ~HTMLListAccessible() { } +}; + + +/** + * Used for HTML list item (e.g. HTML li). + */ +class HTMLLIAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLLIAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual void Shutdown() override; + virtual nsIntRect Bounds() const override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + + virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override; + + // HTMLLIAccessible + HTMLListBulletAccessible* Bullet() const { return mBullet; } + void UpdateBullet(bool aHasBullet); + +protected: + virtual ~HTMLLIAccessible() { } + +private: + RefPtr<HTMLListBulletAccessible> mBullet; +}; + + +/** + * Used for bullet of HTML list item element (for example, HTML li). + */ +class HTMLListBulletAccessible : public LeafAccessible +{ +public: + HTMLListBulletAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLListBulletAccessible() { } + + // Accessible + virtual nsIFrame* GetFrame() const override; + virtual ENameValueFlag Name(nsString& aName) override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0, + uint32_t aLength = UINT32_MAX) override; + + // HTMLListBulletAccessible + + /** + * Return true if the bullet is inside of list item element boundaries. + */ + bool IsInside() const; +}; + + +inline HTMLLIAccessible* +Accessible::AsHTMLListItem() +{ + return IsHTMLListItem() ? static_cast<HTMLLIAccessible*>(this) : nullptr; +} + +} // namespace a11y +} // namespace mozilla + +#endif 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(); +} + diff --git a/accessible/html/HTMLSelectAccessible.h b/accessible/html/HTMLSelectAccessible.h new file mode 100644 index 000000000..0c781034f --- /dev/null +++ b/accessible/html/HTMLSelectAccessible.h @@ -0,0 +1,222 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_HTMLSelectAccessible_h__ +#define mozilla_a11y_HTMLSelectAccessible_h__ + +#include "HTMLFormControlAccessible.h" + +namespace mozilla { +namespace a11y { + +/** + * Selects, Listboxes and Comboboxes, are made up of a number of different + * widgets, some of which are shared between the two. This file contains + * all of the widgets for both of the Selects, for HTML only. + * + * Listbox: + * - HTMLSelectListAccessible + * - HTMLSelectOptionAccessible + * + * Comboboxes: + * - HTMLComboboxAccessible + * - HTMLComboboxListAccessible [ inserted in accessible tree ] + * - HTMLSelectOptionAccessible(s) + */ + +/* + * The list that contains all the options in the select. + */ +class HTMLSelectListAccessible : public AccessibleWrap +{ +public: + + HTMLSelectListAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLSelectListAccessible() {} + + // Accessible + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + + // SelectAccessible + virtual bool SelectAll() override; + virtual bool UnselectAll() override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual Accessible* CurrentItem() override; + virtual void SetCurrentItem(Accessible* aItem) override; + + virtual bool IsAcceptableChild(nsIContent* aEl) const override; +}; + +/* + * Options inside the select, contained within the list + */ +class HTMLSelectOptionAccessible : public HyperTextAccessibleWrap +{ +public: + enum { eAction_Select = 0 }; + + HTMLSelectOptionAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLSelectOptionAccessible() {} + + // Accessible + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual uint64_t NativeInteractiveState() const override; + + virtual int32_t GetLevelInternal() override; + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + virtual void SetSelected(bool aSelect) override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual Accessible* ContainerWidget() const override; + +protected: + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; + +private: + + /** + * Return a select accessible the option belongs to if any. + */ + Accessible* GetSelect() const + { + Accessible* parent = mParent; + if (parent && parent->IsHTMLOptGroup()) + parent = parent->Parent(); + + if (parent && parent->IsListControl()) { + Accessible* combobox = parent->Parent(); + return combobox && combobox->IsCombobox() ? combobox : mParent; + } + + return nullptr; + } + + /** + * Return a combobox accessible the option belongs to if any. + */ + Accessible* GetCombobox() const + { + Accessible* parent = mParent; + if (parent && parent->IsHTMLOptGroup()) + parent = parent->Parent(); + + if (parent && parent->IsListControl()) { + Accessible* combobox = parent->Parent(); + return combobox && combobox->IsCombobox() ? combobox : nullptr; + } + + return nullptr; + } +}; + +/* + * Opt Groups inside the select, contained within the list + */ +class HTMLSelectOptGroupAccessible : public HTMLSelectOptionAccessible +{ +public: + + HTMLSelectOptGroupAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HTMLSelectOptionAccessible(aContent, aDoc) + { mType = eHTMLOptGroupType; } + virtual ~HTMLSelectOptGroupAccessible() {} + + // Accessible + virtual a11y::role NativeRole() override; + virtual uint64_t NativeInteractiveState() const override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; +}; + +/** ------------------------------------------------------ */ +/** Finally, the Combobox widgets */ +/** ------------------------------------------------------ */ + +class HTMLComboboxListAccessible; + +/* + * A class the represents the HTML Combobox widget. + */ +class HTMLComboboxAccessible final : public AccessibleWrap +{ +public: + enum { eAction_Click = 0 }; + + HTMLComboboxAccessible(nsIContent* aContent, DocAccessible* aDoc); + virtual ~HTMLComboboxAccessible() {} + + // Accessible + virtual void Shutdown() override; + virtual void Description(nsString& aDescription) override; + virtual void Value(nsString& aValue) override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual bool RemoveChild(Accessible* aChild) override; + + // ActionAccessible + virtual uint8_t ActionCount() override; + virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override; + virtual bool DoAction(uint8_t aIndex) override; + + // Widgets + virtual bool IsWidget() const override; + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; + virtual Accessible* CurrentItem() override; + virtual void SetCurrentItem(Accessible* aItem) override; + +protected: + /** + * Return selected option. + */ + Accessible* SelectedOption() const; + +private: + RefPtr<HTMLComboboxListAccessible> mListAccessible; +}; + +/* + * A class that represents the window that lives to the right + * of the drop down button inside the Select. This is the window + * that is made visible when the button is pressed. + */ +class HTMLComboboxListAccessible : public HTMLSelectListAccessible +{ +public: + + HTMLComboboxListAccessible(Accessible* aParent, nsIContent* aContent, + DocAccessible* aDoc); + virtual ~HTMLComboboxListAccessible() {} + + // Accessible + virtual nsIFrame* GetFrame() const override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override; + + // Widgets + virtual bool IsActiveWidget() const override; + virtual bool AreItemsOperable() const override; +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/HTMLTableAccessible.cpp b/accessible/html/HTMLTableAccessible.cpp new file mode 100644 index 000000000..b0cdc0932 --- /dev/null +++ b/accessible/html/HTMLTableAccessible.cpp @@ -0,0 +1,1139 @@ +/* -*- Mode: C++; tab-width: 2; 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 "HTMLTableAccessible.h" + +#include "mozilla/DebugOnly.h" + +#include "Accessible-inl.h" +#include "nsAccessibilityService.h" +#include "nsAccUtils.h" +#include "DocAccessible.h" +#include "nsTextEquivUtils.h" +#include "Relation.h" +#include "Role.h" +#include "States.h" +#include "TreeWalker.h" + +#include "mozilla/dom/HTMLTableElement.h" +#include "nsIDOMElement.h" +#include "nsIDOMRange.h" +#include "nsISelectionPrivate.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDocument.h" +#include "nsIMutableArray.h" +#include "nsIPersistentProperties2.h" +#include "nsIPresShell.h" +#include "nsITableCellLayout.h" +#include "nsFrameSelection.h" +#include "nsError.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsNameSpaceManager.h" +#include "nsTableCellFrame.h" +#include "nsTableWrapperFrame.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::a11y; + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTableCellAccessible:: + HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) +{ + mType = eHTMLTableCellType; + mGenericTypes |= eTableCell; +} + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableCellAccessible, HyperTextAccessible) + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: Accessible implementation + +role +HTMLTableCellAccessible::NativeRole() +{ + if (mContent->IsMathMLElement(nsGkAtoms::mtd_)) { + return roles::MATHML_CELL; + } + return roles::CELL; +} + +uint64_t +HTMLTableCellAccessible::NativeState() +{ + uint64_t state = HyperTextAccessibleWrap::NativeState(); + + nsIFrame *frame = mContent->GetPrimaryFrame(); + NS_ASSERTION(frame, "No frame for valid cell accessible!"); + + if (frame && frame->IsSelected()) + state |= states::SELECTED; + + return state; +} + +uint64_t +HTMLTableCellAccessible::NativeInteractiveState() const +{ + return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE; +} + +already_AddRefed<nsIPersistentProperties> +HTMLTableCellAccessible::NativeAttributes() +{ + nsCOMPtr<nsIPersistentProperties> attributes = + HyperTextAccessibleWrap::NativeAttributes(); + + // table-cell-index attribute + TableAccessible* table = Table(); + if (!table) + return attributes.forget(); + + int32_t rowIdx = -1, colIdx = -1; + nsresult rv = GetCellIndexes(rowIdx, colIdx); + if (NS_FAILED(rv)) + return attributes.forget(); + + nsAutoString stringIdx; + stringIdx.AppendInt(table->CellIndexAt(rowIdx, colIdx)); + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tableCellIndex, stringIdx); + + // abbr attribute + + // Pick up object attribute from abbr DOM element (a child of the cell) or + // from abbr DOM attribute. + nsAutoString abbrText; + if (ChildCount() == 1) { + Accessible* abbr = FirstChild(); + if (abbr->IsAbbreviation()) { + nsIContent* firstChildNode = abbr->GetContent()->GetFirstChild(); + if (firstChildNode) { + nsTextEquivUtils:: + AppendTextEquivFromTextContent(firstChildNode, &abbrText); + } + } + } + if (abbrText.IsEmpty()) + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::abbr, abbrText); + + if (!abbrText.IsEmpty()) + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::abbr, abbrText); + + // axis attribute + nsAutoString axisText; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::axis, axisText); + if (!axisText.IsEmpty()) + nsAccUtils::SetAccAttr(attributes, nsGkAtoms::axis, axisText); + +#ifdef DEBUG + nsAutoString unused; + attributes->SetStringProperty(NS_LITERAL_CSTRING("cppclass"), + NS_LITERAL_STRING("HTMLTableCellAccessible"), + unused); +#endif + + return attributes.forget(); +} + +GroupPos +HTMLTableCellAccessible::GroupPosition() +{ + int32_t count = 0, index = 0; + TableAccessible* table = Table(); + if (table && nsCoreUtils::GetUIntAttr(table->AsAccessible()->GetContent(), + nsGkAtoms::aria_colcount, &count) && + nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_colindex, &index)) { + return GroupPos(0, index, count); + } + + return HyperTextAccessibleWrap::GroupPosition(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: TableCellAccessible implementation + +TableAccessible* +HTMLTableCellAccessible::Table() const +{ + Accessible* parent = const_cast<HTMLTableCellAccessible*>(this); + while ((parent = parent->Parent())) { + if (parent->IsTable()) + return parent->AsTable(); + } + + return nullptr; +} + +uint32_t +HTMLTableCellAccessible::ColIdx() const +{ + nsITableCellLayout* cellLayout = GetCellLayout(); + NS_ENSURE_TRUE(cellLayout, 0); + + int32_t colIdx = 0; + cellLayout->GetColIndex(colIdx); + return colIdx > 0 ? static_cast<uint32_t>(colIdx) : 0; +} + +uint32_t +HTMLTableCellAccessible::RowIdx() const +{ + nsITableCellLayout* cellLayout = GetCellLayout(); + NS_ENSURE_TRUE(cellLayout, 0); + + int32_t rowIdx = 0; + cellLayout->GetRowIndex(rowIdx); + return rowIdx > 0 ? static_cast<uint32_t>(rowIdx) : 0; +} + +uint32_t +HTMLTableCellAccessible::ColExtent() const +{ + int32_t rowIdx = -1, colIdx = -1; + GetCellIndexes(rowIdx, colIdx); + + TableAccessible* table = Table(); + NS_ASSERTION(table, "cell not in a table!"); + if (!table) + return 0; + + return table->ColExtentAt(rowIdx, colIdx); +} + +uint32_t +HTMLTableCellAccessible::RowExtent() const +{ + int32_t rowIdx = -1, colIdx = -1; + GetCellIndexes(rowIdx, colIdx); + + TableAccessible* table = Table(); + NS_ASSERTION(table, "cell not in atable!"); + if (!table) + return 0; + + return table->RowExtentAt(rowIdx, colIdx); +} + +void +HTMLTableCellAccessible::ColHeaderCells(nsTArray<Accessible*>* aCells) +{ + IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); + while (Accessible* cell = itr.Next()) { + a11y::role cellRole = cell->Role(); + if (cellRole == roles::COLUMNHEADER) { + aCells->AppendElement(cell); + } else if (cellRole != roles::ROWHEADER) { + // If referred table cell is at the same column then treat it as a column + // header. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (tableCell && tableCell->ColIdx() == ColIdx()) + aCells->AppendElement(cell); + } + } + + if (aCells->IsEmpty()) + TableCellAccessible::ColHeaderCells(aCells); +} + +void +HTMLTableCellAccessible::RowHeaderCells(nsTArray<Accessible*>* aCells) +{ + IDRefsIterator itr(mDoc, mContent, nsGkAtoms::headers); + while (Accessible* cell = itr.Next()) { + a11y::role cellRole = cell->Role(); + if (cellRole == roles::ROWHEADER) { + aCells->AppendElement(cell); + } else if (cellRole != roles::COLUMNHEADER) { + // If referred table cell is at the same row then treat it as a column + // header. + TableCellAccessible* tableCell = cell->AsTableCell(); + if (tableCell && tableCell->RowIdx() == RowIdx()) + aCells->AppendElement(cell); + } + } + + if (aCells->IsEmpty()) + TableCellAccessible::RowHeaderCells(aCells); +} + +bool +HTMLTableCellAccessible::Selected() +{ + int32_t rowIdx = -1, colIdx = -1; + GetCellIndexes(rowIdx, colIdx); + + TableAccessible* table = Table(); + NS_ENSURE_TRUE(table, false); + + return table->IsCellSelected(rowIdx, colIdx); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableCellAccessible: protected implementation + +nsITableCellLayout* +HTMLTableCellAccessible::GetCellLayout() const +{ + return do_QueryFrame(mContent->GetPrimaryFrame()); +} + +nsresult +HTMLTableCellAccessible::GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const +{ + nsITableCellLayout *cellLayout = GetCellLayout(); + NS_ENSURE_STATE(cellLayout); + + return cellLayout->GetCellIndexes(aRowIdx, aColIdx); +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableHeaderCellAccessible +//////////////////////////////////////////////////////////////////////////////// + +HTMLTableHeaderCellAccessible:: + HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HTMLTableCellAccessible(aContent, aDoc) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableHeaderCellAccessible: Accessible implementation + +role +HTMLTableHeaderCellAccessible::NativeRole() +{ + // Check value of @scope attribute. + static nsIContent::AttrValuesArray scopeValues[] = + { &nsGkAtoms::col, &nsGkAtoms::colgroup, + &nsGkAtoms::row, &nsGkAtoms::rowgroup, nullptr }; + int32_t valueIdx = + mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::scope, + scopeValues, eCaseMatters); + + switch (valueIdx) { + case 0: + case 1: + return roles::COLUMNHEADER; + case 2: + case 3: + return roles::ROWHEADER; + } + + TableAccessible* table = Table(); + if (!table) + return roles::NOTHING; + + // If the cell next to this one is not a header cell then assume this cell is + // a row header for it. + uint32_t rowIdx = RowIdx(), colIdx = ColIdx(); + Accessible* cell = table->CellAt(rowIdx, colIdx + ColExtent()); + if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) + return roles::ROWHEADER; + + // If the cell below this one is not a header cell then assume this cell is + // a column header for it. + uint32_t rowExtent = RowExtent(); + cell = table->CellAt(rowIdx + rowExtent, colIdx); + if (cell && !nsCoreUtils::IsHTMLTableHeader(cell->GetContent())) + return roles::COLUMNHEADER; + + // Otherwise if this cell is surrounded by header cells only then make a guess + // based on its cell spanning. In other words if it is row spanned then assume + // it's a row header, otherwise it's a column header. + return rowExtent > 1 ? roles::ROWHEADER : roles::COLUMNHEADER; +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableRowAccessible +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableRowAccessible, Accessible) + +role +HTMLTableRowAccessible::NativeRole() +{ + if (mContent->IsMathMLElement(nsGkAtoms::mtr_)) { + return roles::MATHML_TABLE_ROW; + } else if (mContent->IsMathMLElement(nsGkAtoms::mlabeledtr_)) { + return roles::MATHML_LABELED_ROW; + } + return roles::ROW; +} + +GroupPos +HTMLTableRowAccessible::GroupPosition() +{ + int32_t count = 0, index = 0; + Accessible* table = nsAccUtils::TableFor(this); + if (table && nsCoreUtils::GetUIntAttr(table->GetContent(), + nsGkAtoms::aria_rowcount, &count) && + nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_rowindex, &index)) { + return GroupPos(0, index, count); + } + + return AccessibleWrap::GroupPosition(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS_INHERITED0(HTMLTableAccessible, Accessible) + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: Accessible + +bool +HTMLTableAccessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) +{ + // Move caption accessible so that it's the first child. Check for the first + // caption only, because nsAccessibilityService ensures we don't create + // accessibles for the other captions, since only the first is actually + // visible. + return Accessible::InsertChildAt(aChild->IsHTMLCaption() ? 0 : aIndex, aChild); +} + +role +HTMLTableAccessible::NativeRole() +{ + if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) { + return roles::MATHML_TABLE; + } + return roles::TABLE; +} + +uint64_t +HTMLTableAccessible::NativeState() +{ + return Accessible::NativeState() | states::READONLY; +} + +ENameValueFlag +HTMLTableAccessible::NativeName(nsString& aName) +{ + ENameValueFlag nameFlag = Accessible::NativeName(aName); + if (!aName.IsEmpty()) + return nameFlag; + + // Use table caption as a name. + Accessible* caption = Caption(); + if (caption) { + nsIContent* captionContent = caption->GetContent(); + if (captionContent) { + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, &aName); + if (!aName.IsEmpty()) + return eNameOK; + } + } + + // If no caption then use summary as a name. + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, aName); + return eNameOK; +} + +already_AddRefed<nsIPersistentProperties> +HTMLTableAccessible::NativeAttributes() +{ + nsCOMPtr<nsIPersistentProperties> attributes = + AccessibleWrap::NativeAttributes(); + + if (mContent->IsMathMLElement(nsGkAtoms::mtable_)) { + GetAccService()->MarkupAttributes(mContent, attributes); + } + + if (IsProbablyLayoutTable()) { + nsAutoString unused; + attributes->SetStringProperty(NS_LITERAL_CSTRING("layout-guess"), + NS_LITERAL_STRING("true"), unused); + } + + return attributes.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: Accessible + +Relation +HTMLTableAccessible::RelationByType(RelationType aType) +{ + Relation rel = AccessibleWrap::RelationByType(aType); + if (aType == RelationType::LABELLED_BY) + rel.AppendTarget(Caption()); + + return rel; +} + +//////////////////////////////////////////////////////////////////////////////// +// HTMLTableAccessible: Table + +Accessible* +HTMLTableAccessible::Caption() const +{ + Accessible* child = mChildren.SafeElementAt(0, nullptr); + return child && child->Role() == roles::CAPTION ? child : nullptr; +} + +void +HTMLTableAccessible::Summary(nsString& aSummary) +{ + dom::HTMLTableElement* table = dom::HTMLTableElement::FromContent(mContent); + + if (table) + table->GetSummary(aSummary); +} + +uint32_t +HTMLTableAccessible::ColCount() +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + return tableFrame ? tableFrame->GetColCount() : 0; +} + +uint32_t +HTMLTableAccessible::RowCount() +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + return tableFrame ? tableFrame->GetRowCount() : 0; +} + +uint32_t +HTMLTableAccessible::SelectedCellCount() +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return 0; + + uint32_t count = 0, rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) + continue; + + int32_t startRow = -1, startCol = -1; + cellFrame->GetRowIndex(startRow); + cellFrame->GetColIndex(startCol); + if (startRow >= 0 && (uint32_t)startRow == rowIdx && + startCol >= 0 && (uint32_t)startCol == colIdx) + count++; + } + } + + return count; +} + +uint32_t +HTMLTableAccessible::SelectedColCount() +{ + uint32_t count = 0, colCount = ColCount(); + + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) + if (IsColSelected(colIdx)) + count++; + + return count; +} + +uint32_t +HTMLTableAccessible::SelectedRowCount() +{ + uint32_t count = 0, rowCount = RowCount(); + + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) + if (IsRowSelected(rowIdx)) + count++; + + return count; +} + +void +HTMLTableAccessible::SelectedCells(nsTArray<Accessible*>* aCells) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return; + + uint32_t rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) + continue; + + int32_t startCol = -1, startRow = -1; + cellFrame->GetRowIndex(startRow); + cellFrame->GetColIndex(startCol); + if ((startRow >= 0 && (uint32_t)startRow != rowIdx) || + (startCol >= 0 && (uint32_t)startCol != colIdx)) + continue; + + Accessible* cell = mDoc->GetAccessible(cellFrame->GetContent()); + aCells->AppendElement(cell); + } + } +} + +void +HTMLTableAccessible::SelectedCellIndices(nsTArray<uint32_t>* aCells) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return; + + uint32_t rowCount = RowCount(), colCount = ColCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (!cellFrame || !cellFrame->IsSelected()) + continue; + + int32_t startRow = -1, startCol = -1; + cellFrame->GetColIndex(startCol); + cellFrame->GetRowIndex(startRow); + if (startRow >= 0 && (uint32_t)startRow == rowIdx && + startCol >= 0 && (uint32_t)startCol == colIdx) + aCells->AppendElement(CellIndexAt(rowIdx, colIdx)); + } + } +} + +void +HTMLTableAccessible::SelectedColIndices(nsTArray<uint32_t>* aCols) +{ + uint32_t colCount = ColCount(); + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) + if (IsColSelected(colIdx)) + aCols->AppendElement(colIdx); +} + +void +HTMLTableAccessible::SelectedRowIndices(nsTArray<uint32_t>* aRows) +{ + uint32_t rowCount = RowCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) + if (IsRowSelected(rowIdx)) + aRows->AppendElement(rowIdx); +} + +Accessible* +HTMLTableAccessible::CellAt(uint32_t aRowIdx, uint32_t aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return nullptr; + + nsIContent* cellContent = tableFrame->GetCellAt(aRowIdx, aColIdx); + Accessible* cell = mDoc->GetAccessible(cellContent); + + // XXX bug 576838: crazy tables (like table6 in tables/test_table2.html) may + // return itself as a cell what makes Orca hang. + return cell == this ? nullptr : cell; +} + +int32_t +HTMLTableAccessible::CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return -1; + + return tableFrame->GetIndexByRowAndColumn(aRowIdx, aColIdx); +} + +int32_t +HTMLTableAccessible::ColIndexAt(uint32_t aCellIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return -1; + + int32_t rowIdx = -1, colIdx = -1; + tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); + return colIdx; +} + +int32_t +HTMLTableAccessible::RowIndexAt(uint32_t aCellIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return -1; + + int32_t rowIdx = -1, colIdx = -1; + tableFrame->GetRowAndColumnByIndex(aCellIdx, &rowIdx, &colIdx); + return rowIdx; +} + +void +HTMLTableAccessible::RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (tableFrame) + tableFrame->GetRowAndColumnByIndex(aCellIdx, aRowIdx, aColIdx); +} + +uint32_t +HTMLTableAccessible::ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return 0; + + return tableFrame->GetEffectiveColSpanAt(aRowIdx, aColIdx); +} + +uint32_t +HTMLTableAccessible::RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return 0; + + return tableFrame->GetEffectiveRowSpanAt(aRowIdx, aColIdx); +} + +bool +HTMLTableAccessible::IsColSelected(uint32_t aColIdx) +{ + bool isSelected = false; + + uint32_t rowCount = RowCount(); + for (uint32_t rowIdx = 0; rowIdx < rowCount; rowIdx++) { + isSelected = IsCellSelected(rowIdx, aColIdx); + if (!isSelected) + return false; + } + + return isSelected; +} + +bool +HTMLTableAccessible::IsRowSelected(uint32_t aRowIdx) +{ + bool isSelected = false; + + uint32_t colCount = ColCount(); + for (uint32_t colIdx = 0; colIdx < colCount; colIdx++) { + isSelected = IsCellSelected(aRowIdx, colIdx); + if (!isSelected) + return false; + } + + return isSelected; +} + +bool +HTMLTableAccessible::IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return false; + + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(aRowIdx, aColIdx); + return cellFrame ? cellFrame->IsSelected() : false; +} + +void +HTMLTableAccessible::SelectRow(uint32_t aRowIdx) +{ + DebugOnly<nsresult> rv = + RemoveRowsOrColumnsFromSelection(aRowIdx, + nsISelectionPrivate::TABLESELECTION_ROW, + true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); + + AddRowOrColumnToSelection(aRowIdx, nsISelectionPrivate::TABLESELECTION_ROW); +} + +void +HTMLTableAccessible::SelectCol(uint32_t aColIdx) +{ + DebugOnly<nsresult> rv = + RemoveRowsOrColumnsFromSelection(aColIdx, + nsISelectionPrivate::TABLESELECTION_COLUMN, + true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "RemoveRowsOrColumnsFromSelection() Shouldn't fail!"); + + AddRowOrColumnToSelection(aColIdx, nsISelectionPrivate::TABLESELECTION_COLUMN); +} + +void +HTMLTableAccessible::UnselectRow(uint32_t aRowIdx) +{ + RemoveRowsOrColumnsFromSelection(aRowIdx, + nsISelectionPrivate::TABLESELECTION_ROW, + false); +} + +void +HTMLTableAccessible::UnselectCol(uint32_t aColIdx) +{ + RemoveRowsOrColumnsFromSelection(aColIdx, + nsISelectionPrivate::TABLESELECTION_COLUMN, + false); +} + +nsresult +HTMLTableAccessible::AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget) +{ + bool doSelectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW); + + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return NS_OK; + + uint32_t count = 0; + if (doSelectRow) + count = ColCount(); + else + count = RowCount(); + + nsIPresShell* presShell(mDoc->PresShell()); + RefPtr<nsFrameSelection> tableSelection = + const_cast<nsFrameSelection*>(presShell->ConstFrameSelection()); + + for (uint32_t idx = 0; idx < count; idx++) { + int32_t rowIdx = doSelectRow ? aIndex : idx; + int32_t colIdx = doSelectRow ? idx : aIndex; + nsTableCellFrame* cellFrame = tableFrame->GetCellFrameAt(rowIdx, colIdx); + if (cellFrame && !cellFrame->IsSelected()) { + nsresult rv = tableSelection->SelectCellElement(cellFrame->GetContent()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +HTMLTableAccessible::RemoveRowsOrColumnsFromSelection(int32_t aIndex, + uint32_t aTarget, + bool aIsOuter) +{ + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + return NS_OK; + + nsIPresShell* presShell(mDoc->PresShell()); + RefPtr<nsFrameSelection> tableSelection = + const_cast<nsFrameSelection*>(presShell->ConstFrameSelection()); + + bool doUnselectRow = (aTarget == nsISelectionPrivate::TABLESELECTION_ROW); + uint32_t count = doUnselectRow ? ColCount() : RowCount(); + + int32_t startRowIdx = doUnselectRow ? aIndex : 0; + int32_t endRowIdx = doUnselectRow ? aIndex : count - 1; + int32_t startColIdx = doUnselectRow ? 0 : aIndex; + int32_t endColIdx = doUnselectRow ? count - 1 : aIndex; + + if (aIsOuter) + return tableSelection->RestrictCellsToSelection(mContent, + startRowIdx, startColIdx, + endRowIdx, endColIdx); + + return tableSelection->RemoveCellsFromSelection(mContent, + startRowIdx, startColIdx, + endRowIdx, endColIdx); +} + +void +HTMLTableAccessible::Description(nsString& aDescription) +{ + // Helpful for debugging layout vs. data tables + aDescription.Truncate(); + Accessible::Description(aDescription); + if (!aDescription.IsEmpty()) + return; + + // Use summary as description if it weren't used as a name. + // XXX: get rid code duplication with NameInternal(). + Accessible* caption = Caption(); + if (caption) { + nsIContent* captionContent = caption->GetContent(); + if (captionContent) { + nsAutoString captionText; + nsTextEquivUtils::AppendTextEquivFromContent(this, captionContent, + &captionText); + + if (!captionText.IsEmpty()) { // summary isn't used as a name. + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, + aDescription); + } + } + } + +#ifdef SHOW_LAYOUT_HEURISTIC + if (aDescription.IsEmpty()) { + bool isProbablyForLayout = IsProbablyLayoutTable(); + aDescription = mLayoutHeuristic; + } + printf("\nTABLE: %s\n", NS_ConvertUTF16toUTF8(mLayoutHeuristic).get()); +#endif +} + +bool +HTMLTableAccessible::HasDescendant(const nsAString& aTagName, bool aAllowEmpty) +{ + nsCOMPtr<nsIHTMLCollection> elements = + mContent->AsElement()->GetElementsByTagName(aTagName); + + Element* foundItem = elements->Item(0); + if (!foundItem) + return false; + + if (aAllowEmpty) + return true; + + // Make sure that the item we found has contents and either has multiple + // children or the found item is not a whitespace-only text node. + if (foundItem->GetChildCount() > 1) + return true; // Treat multiple child nodes as non-empty + + nsIContent *innerItemContent = foundItem->GetFirstChild(); + if (innerItemContent && !innerItemContent->TextIsOnlyWhitespace()) + return true; + + // If we found more than one node then return true not depending on + // aAllowEmpty flag. + // XXX it might be dummy but bug 501375 where we changed this addresses + // performance problems only. Note, currently 'aAllowEmpty' flag is used for + // caption element only. On another hand we create accessible object for + // the first entry of caption element (see + // HTMLTableAccessible::InsertChildAt). + return !!elements->Item(1); +} + +bool +HTMLTableAccessible::IsProbablyLayoutTable() +{ + // Implement a heuristic to determine if table is most likely used for layout + // XXX do we want to look for rowspan or colspan, especialy that span all but a couple cells + // at the beginning or end of a row/col, and especially when they occur at the edge of a table? + // XXX expose this info via object attributes to AT-SPI + + // XXX For now debugging descriptions are always on via SHOW_LAYOUT_HEURISTIC + // This will allow release trunk builds to be used by testers to refine the algorithm + // Change to |#define SHOW_LAYOUT_HEURISTIC DEBUG| before final release +#ifdef SHOW_LAYOUT_HEURISTIC +#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) \ + { \ + mLayoutHeuristic = isLayout ? \ + NS_LITERAL_STRING("layout table: " heuristic) : \ + NS_LITERAL_STRING("data table: " heuristic); \ + return isLayout; \ + } +#else +#define RETURN_LAYOUT_ANSWER(isLayout, heuristic) { return isLayout; } +#endif + + DocAccessible* docAccessible = Document(); + if (docAccessible) { + uint64_t docState = docAccessible->State(); + if (docState & states::EDITABLE) { // Need to see all elements while document is being edited + RETURN_LAYOUT_ANSWER(false, "In editable document"); + } + } + + // Check to see if an ARIA role overrides the role from native markup, + // but for which we still expose table semantics (treegrid, for example). + if (Role() != roles::TABLE) + RETURN_LAYOUT_ANSWER(false, "Has role attribute"); + + if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::role)) { + // Role attribute is present, but overridden roles have already been dealt with. + // Only landmarks and other roles that don't override the role from native + // markup are left to deal with here. + RETURN_LAYOUT_ANSWER(false, "Has role attribute, weak role, and role is table"); + } + + NS_ASSERTION(mContent->IsHTMLElement(nsGkAtoms::table), + "table should not be built by CSS display:table style"); + + // Check if datatable attribute has "0" value. + if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::datatable, + NS_LITERAL_STRING("0"), eCaseMatters)) { + RETURN_LAYOUT_ANSWER(true, "Has datatable = 0 attribute, it's for layout"); + } + + // Check for legitimate data table attributes. + nsAutoString summary; + if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::summary, summary) && + !summary.IsEmpty()) + RETURN_LAYOUT_ANSWER(false, "Has summary -- legitimate table structures"); + + // Check for legitimate data table elements. + Accessible* caption = FirstChild(); + if (caption && caption->Role() == roles::CAPTION && caption->HasChildren()) + RETURN_LAYOUT_ANSWER(false, "Not empty caption -- legitimate table structures"); + + for (nsIContent* childElm = mContent->GetFirstChild(); childElm; + childElm = childElm->GetNextSibling()) { + if (!childElm->IsHTMLElement()) + continue; + + if (childElm->IsAnyOfHTMLElements(nsGkAtoms::col, + nsGkAtoms::colgroup, + nsGkAtoms::tfoot, + nsGkAtoms::thead)) { + RETURN_LAYOUT_ANSWER(false, + "Has col, colgroup, tfoot or thead -- legitimate table structures"); + } + + if (childElm->IsHTMLElement(nsGkAtoms::tbody)) { + for (nsIContent* rowElm = childElm->GetFirstChild(); rowElm; + rowElm = rowElm->GetNextSibling()) { + if (rowElm->IsHTMLElement(nsGkAtoms::tr)) { + for (nsIContent* cellElm = rowElm->GetFirstChild(); cellElm; + cellElm = cellElm->GetNextSibling()) { + if (cellElm->IsHTMLElement()) { + + if (cellElm->NodeInfo()->Equals(nsGkAtoms::th)) { + RETURN_LAYOUT_ANSWER(false, + "Has th -- legitimate table structures"); + } + + if (cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::headers) || + cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::scope) || + cellElm->HasAttr(kNameSpaceID_None, nsGkAtoms::abbr)) { + RETURN_LAYOUT_ANSWER(false, + "Has headers, scope, or abbr attribute -- legitimate table structures"); + } + + Accessible* cell = mDoc->GetAccessible(cellElm); + if (cell && cell->ChildCount() == 1 && + cell->FirstChild()->IsAbbreviation()) { + RETURN_LAYOUT_ANSWER(false, + "has abbr -- legitimate table structures"); + } + } + } + } + } + } + } + + if (HasDescendant(NS_LITERAL_STRING("table"))) { + RETURN_LAYOUT_ANSWER(true, "Has a nested table within it"); + } + + // If only 1 column or only 1 row, it's for layout + uint32_t colCount = ColCount(); + if (colCount <=1) { + RETURN_LAYOUT_ANSWER(true, "Has only 1 column"); + } + uint32_t rowCount = RowCount(); + if (rowCount <=1) { + RETURN_LAYOUT_ANSWER(true, "Has only 1 row"); + } + + // Check for many columns + if (colCount >= 5) { + RETURN_LAYOUT_ANSWER(false, ">=5 columns"); + } + + // Now we know there are 2-4 columns and 2 or more rows + // Check to see if there are visible borders on the cells + // XXX currently, we just check the first cell -- do we really need to do more? + nsTableWrapperFrame* tableFrame = do_QueryFrame(mContent->GetPrimaryFrame()); + if (!tableFrame) + RETURN_LAYOUT_ANSWER(false, "table with no frame!"); + + nsIFrame* cellFrame = tableFrame->GetCellFrameAt(0, 0); + if (!cellFrame) + RETURN_LAYOUT_ANSWER(false, "table's first cell has no frame!"); + + nsMargin border; + cellFrame->GetXULBorder(border); + if (border.top && border.bottom && border.left && border.right) { + RETURN_LAYOUT_ANSWER(false, "Has nonzero border-width on table cell"); + } + + /** + * Rules for non-bordered tables with 2-4 columns and 2+ rows from here on forward + */ + + // Check for styled background color across rows (alternating background + // color is a common feature for data tables). + uint32_t childCount = ChildCount(); + nscolor rowColor = 0; + nscolor prevRowColor; + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { + Accessible* child = GetChildAt(childIdx); + if (child->Role() == roles::ROW) { + prevRowColor = rowColor; + nsIFrame* rowFrame = child->GetFrame(); + rowColor = rowFrame->StyleBackground()->mBackgroundColor; + + if (childIdx > 0 && prevRowColor != rowColor) + RETURN_LAYOUT_ANSWER(false, "2 styles of row background color, non-bordered"); + } + } + + // Check for many rows + const uint32_t kMaxLayoutRows = 20; + if (rowCount > kMaxLayoutRows) { // A ton of rows, this is probably for data + RETURN_LAYOUT_ANSWER(false, ">= kMaxLayoutRows (20) and non-bordered"); + } + + // Check for very wide table. + nsIFrame* documentFrame = Document()->GetFrame(); + nsSize documentSize = documentFrame->GetSize(); + if (documentSize.width > 0) { + nsSize tableSize = GetFrame()->GetSize(); + int32_t percentageOfDocWidth = (100 * tableSize.width) / documentSize.width; + if (percentageOfDocWidth > 95) { + // 3-4 columns, no borders, not a lot of rows, and 95% of the doc's width + // Probably for layout + RETURN_LAYOUT_ANSWER(true, + "<= 4 columns, table width is 95% of document width"); + } + } + + // Two column rules + if (rowCount * colCount <= 10) { + RETURN_LAYOUT_ANSWER(true, "2-4 columns, 10 cells or less, non-bordered"); + } + + if (HasDescendant(NS_LITERAL_STRING("embed")) || + HasDescendant(NS_LITERAL_STRING("object")) || + HasDescendant(NS_LITERAL_STRING("applet")) || + HasDescendant(NS_LITERAL_STRING("iframe"))) { + RETURN_LAYOUT_ANSWER(true, "Has no borders, and has iframe, object, applet or iframe, typical of advertisements"); + } + + RETURN_LAYOUT_ANSWER(false, "no layout factor strong enough, so will guess data"); +} + + +//////////////////////////////////////////////////////////////////////////////// +// HTMLCaptionAccessible +//////////////////////////////////////////////////////////////////////////////// + +Relation +HTMLCaptionAccessible::RelationByType(RelationType aType) +{ + Relation rel = HyperTextAccessible::RelationByType(aType); + if (aType == RelationType::LABEL_FOR) + rel.AppendTarget(Parent()); + + return rel; +} + +role +HTMLCaptionAccessible::NativeRole() +{ + return roles::CAPTION; +} diff --git a/accessible/html/HTMLTableAccessible.h b/accessible/html/HTMLTableAccessible.h new file mode 100644 index 000000000..830d34ae9 --- /dev/null +++ b/accessible/html/HTMLTableAccessible.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_a11y_HTMLTableAccessible_h__ +#define mozilla_a11y_HTMLTableAccessible_h__ + +#include "HyperTextAccessibleWrap.h" +#include "TableAccessible.h" +#include "TableCellAccessible.h" + +class nsITableCellLayout; + +namespace mozilla { +namespace a11y { + +/** + * HTML table cell accessible (html:td). + */ +class HTMLTableCellAccessible : public HyperTextAccessibleWrap, + public TableCellAccessible +{ +public: + HTMLTableCellAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual TableCellAccessible* AsTableCell() override { return this; } + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual uint64_t NativeInteractiveState() const override; + virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override; + virtual mozilla::a11y::GroupPos GroupPosition() override; + + // TableCellAccessible + virtual TableAccessible* Table() const override; + virtual uint32_t ColIdx() const override; + virtual uint32_t RowIdx() const override; + virtual uint32_t ColExtent() const override; + virtual uint32_t RowExtent() const override; + virtual void ColHeaderCells(nsTArray<Accessible*>* aCells) override; + virtual void RowHeaderCells(nsTArray<Accessible*>* aCells) override; + virtual bool Selected() override; + +protected: + virtual ~HTMLTableCellAccessible() {} + + /** + * Return nsITableCellLayout of the table cell frame. + */ + nsITableCellLayout* GetCellLayout() const; + + /** + * Return row and column indices of the cell. + */ + nsresult GetCellIndexes(int32_t& aRowIdx, int32_t& aColIdx) const; +}; + + +/** + * HTML table row/column header accessible (html:th or html:td@scope). + */ +class HTMLTableHeaderCellAccessible : public HTMLTableCellAccessible +{ +public: + HTMLTableHeaderCellAccessible(nsIContent* aContent, DocAccessible* aDoc); + + // Accessible + virtual a11y::role NativeRole() override; +}; + + +/** + * HTML table row accessible (html:tr). + */ +class HTMLTableRowAccessible : public AccessibleWrap +{ +public: + HTMLTableRowAccessible(nsIContent* aContent, DocAccessible* aDoc) : + AccessibleWrap(aContent, aDoc) + { + mType = eHTMLTableRowType; + mGenericTypes |= eTableRow; + } + + NS_DECL_ISUPPORTS_INHERITED + + // Accessible + virtual a11y::role NativeRole() override; + virtual mozilla::a11y::GroupPos GroupPosition() override; + +protected: + virtual ~HTMLTableRowAccessible() { } +}; + + +/** + * HTML table accessible (html:table). + */ + +// To turn on table debugging descriptions define SHOW_LAYOUT_HEURISTIC +// This allow release trunk builds to be used by testers to refine the +// data vs. layout heuristic +// #define SHOW_LAYOUT_HEURISTIC + +class HTMLTableAccessible : public AccessibleWrap, + public TableAccessible +{ +public: + HTMLTableAccessible(nsIContent* aContent, DocAccessible* aDoc) : + AccessibleWrap(aContent, aDoc) + { + mType = eHTMLTableType; + mGenericTypes |= eTable; + } + + NS_DECL_ISUPPORTS_INHERITED + + // TableAccessible + virtual Accessible* Caption() const override; + virtual void Summary(nsString& aSummary) override; + virtual uint32_t ColCount() override; + virtual uint32_t RowCount() override; + virtual Accessible* CellAt(uint32_t aRowIndex, uint32_t aColumnIndex) override; + virtual int32_t CellIndexAt(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual int32_t ColIndexAt(uint32_t aCellIdx) override; + virtual int32_t RowIndexAt(uint32_t aCellIdx) override; + virtual void RowAndColIndicesAt(uint32_t aCellIdx, int32_t* aRowIdx, + int32_t* aColIdx) override; + virtual uint32_t ColExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual uint32_t RowExtentAt(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual bool IsColSelected(uint32_t aColIdx) override; + virtual bool IsRowSelected(uint32_t aRowIdx) override; + virtual bool IsCellSelected(uint32_t aRowIdx, uint32_t aColIdx) override; + virtual uint32_t SelectedCellCount() override; + virtual uint32_t SelectedColCount() override; + virtual uint32_t SelectedRowCount() override; + virtual void SelectedCells(nsTArray<Accessible*>* aCells) override; + virtual void SelectedCellIndices(nsTArray<uint32_t>* aCells) override; + virtual void SelectedColIndices(nsTArray<uint32_t>* aCols) override; + virtual void SelectedRowIndices(nsTArray<uint32_t>* aRows) override; + virtual void SelectCol(uint32_t aColIdx) override; + virtual void SelectRow(uint32_t aRowIdx) override; + virtual void UnselectCol(uint32_t aColIdx) override; + virtual void UnselectRow(uint32_t aRowIdx) override; + virtual bool IsProbablyLayoutTable() override; + virtual Accessible* AsAccessible() override { return this; } + + // Accessible + virtual TableAccessible* AsTable() override { return this; } + virtual void Description(nsString& aDescription) override; + virtual a11y::role NativeRole() override; + virtual uint64_t NativeState() override; + virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() override; + virtual Relation RelationByType(RelationType aRelationType) override; + + virtual bool InsertChildAt(uint32_t aIndex, Accessible* aChild) override; + +protected: + virtual ~HTMLTableAccessible() {} + + // Accessible + virtual ENameValueFlag NativeName(nsString& aName) override; + + // HTMLTableAccessible + + /** + * Add row or column to selection. + * + * @param aIndex [in] index of row or column to be selected + * @param aTarget [in] indicates what should be selected, either row or column + * (see nsISelectionPrivate) + */ + nsresult AddRowOrColumnToSelection(int32_t aIndex, uint32_t aTarget); + + /** + * Removes rows or columns at the given index or outside it from selection. + * + * @param aIndex [in] row or column index + * @param aTarget [in] indicates whether row or column should unselected + * @param aIsOuter [in] indicates whether all rows or column excepting + * the given one should be unselected or the given one + * should be unselected only + */ + nsresult RemoveRowsOrColumnsFromSelection(int32_t aIndex, + uint32_t aTarget, + bool aIsOuter); + + /** + * Return true if table has an element with the given tag name. + * + * @param aTagName [in] tag name of searched element + * @param aAllowEmpty [in, optional] points if found element can be empty + * or contain whitespace text only. + */ + bool HasDescendant(const nsAString& aTagName, bool aAllowEmpty = true); + +#ifdef SHOW_LAYOUT_HEURISTIC + nsString mLayoutHeuristic; +#endif +}; + +/** + * HTML caption accessible (html:caption). + */ +class HTMLCaptionAccessible : public HyperTextAccessibleWrap +{ +public: + HTMLCaptionAccessible(nsIContent* aContent, DocAccessible* aDoc) : + HyperTextAccessibleWrap(aContent, aDoc) { mType = eHTMLCaptionType; } + + // Accessible + virtual a11y::role NativeRole() override; + virtual Relation RelationByType(RelationType aRelationType) override; + +protected: + virtual ~HTMLCaptionAccessible() { } +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/html/moz.build b/accessible/html/moz.build new file mode 100644 index 000000000..8d92332cd --- /dev/null +++ b/accessible/html/moz.build @@ -0,0 +1,50 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + 'HTMLCanvasAccessible.cpp', + 'HTMLElementAccessibles.cpp', + 'HTMLFormControlAccessible.cpp', + 'HTMLImageMapAccessible.cpp', + 'HTMLLinkAccessible.cpp', + 'HTMLListAccessible.cpp', + 'HTMLSelectAccessible.cpp', + 'HTMLTableAccessible.cpp', +] + +LOCAL_INCLUDES += [ + '/accessible/base', + '/accessible/generic', + '/accessible/xpcom', + '/layout/generic', + '/layout/tables', + '/layout/xul', +] + +if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']: + LOCAL_INCLUDES += [ + '/accessible/atk', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + LOCAL_INCLUDES += [ + '/accessible/windows/ia2', + '/accessible/windows/msaa', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] +else: + LOCAL_INCLUDES += [ + '/accessible/other', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] |