summaryrefslogtreecommitdiffstats
path: root/accessible/mac
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/mac/.clang-format11
-rw-r--r--accessible/mac/ARIAGridAccessibleWrap.h22
-rw-r--r--accessible/mac/AccessibleWrap.h94
-rw-r--r--accessible/mac/AccessibleWrap.mm400
-rw-r--r--accessible/mac/ApplicationAccessibleWrap.h21
-rw-r--r--accessible/mac/DocAccessibleWrap.h45
-rw-r--r--accessible/mac/DocAccessibleWrap.mm104
-rw-r--r--accessible/mac/GeckoTextMarker.h122
-rw-r--r--accessible/mac/GeckoTextMarker.mm481
-rw-r--r--accessible/mac/HTMLTableAccessibleWrap.h23
-rw-r--r--accessible/mac/HyperTextAccessibleWrap.h89
-rw-r--r--accessible/mac/HyperTextAccessibleWrap.mm704
-rw-r--r--accessible/mac/ImageAccessibleWrap.h21
-rw-r--r--accessible/mac/MOXAccessibleBase.h138
-rw-r--r--accessible/mac/MOXAccessibleBase.mm553
-rw-r--r--accessible/mac/MOXAccessibleProtocol.h502
-rw-r--r--accessible/mac/MOXLandmarkAccessibles.h15
-rw-r--r--accessible/mac/MOXLandmarkAccessibles.mm15
-rw-r--r--accessible/mac/MOXMathAccessibles.h64
-rw-r--r--accessible/mac/MOXMathAccessibles.mm116
-rw-r--r--accessible/mac/MOXSearchInfo.h43
-rw-r--r--accessible/mac/MOXSearchInfo.mm451
-rw-r--r--accessible/mac/MOXTextMarkerDelegate.h158
-rw-r--r--accessible/mac/MOXTextMarkerDelegate.mm440
-rw-r--r--accessible/mac/MOXWebAreaAccessible.h105
-rw-r--r--accessible/mac/MOXWebAreaAccessible.mm275
-rw-r--r--accessible/mac/MacUtils.h43
-rw-r--r--accessible/mac/MacUtils.mm89
-rw-r--r--accessible/mac/Platform.mm212
-rw-r--r--accessible/mac/PlatformExtTypes.h27
-rw-r--r--accessible/mac/RootAccessibleWrap.h36
-rw-r--r--accessible/mac/RootAccessibleWrap.mm51
-rw-r--r--accessible/mac/RotorRules.h142
-rw-r--r--accessible/mac/RotorRules.mm370
-rwxr-xr-xaccessible/mac/SelectorMapGen.py63
-rw-r--r--accessible/mac/TextLeafAccessibleWrap.h19
-rw-r--r--accessible/mac/XULListboxAccessibleWrap.h19
-rw-r--r--accessible/mac/XULMenuAccessibleWrap.h19
-rw-r--r--accessible/mac/XULTreeGridAccessibleWrap.h20
-rw-r--r--accessible/mac/moz.build73
-rw-r--r--accessible/mac/mozAccessible.h273
-rw-r--r--accessible/mac/mozAccessible.mm1074
-rw-r--r--accessible/mac/mozAccessibleProtocol.h65
-rw-r--r--accessible/mac/mozActionElements.h83
-rw-r--r--accessible/mac/mozActionElements.mm208
-rw-r--r--accessible/mac/mozHTMLAccessible.h48
-rw-r--r--accessible/mac/mozHTMLAccessible.mm97
-rw-r--r--accessible/mac/mozRootAccessible.h58
-rw-r--r--accessible/mac/mozRootAccessible.mm83
-rw-r--r--accessible/mac/mozSelectableElements.h125
-rw-r--r--accessible/mac/mozSelectableElements.mm332
-rw-r--r--accessible/mac/mozTableAccessible.h165
-rw-r--r--accessible/mac/mozTableAccessible.mm591
-rw-r--r--accessible/mac/mozTextAccessible.h113
-rw-r--r--accessible/mac/mozTextAccessible.mm448
55 files changed, 9958 insertions, 0 deletions
diff --git a/accessible/mac/.clang-format b/accessible/mac/.clang-format
new file mode 100644
index 0000000000..269bce4d0f
--- /dev/null
+++ b/accessible/mac/.clang-format
@@ -0,0 +1,11 @@
+---
+# Objective C formatting rules.
+# Since this doesn't derive from the Cpp section, we need to redifine the root rules here.
+Language: ObjC
+BasedOnStyle: Google
+
+DerivePointerAlignment: false
+PointerAlignment: Left
+SortIncludes: false
+ColumnLimit: 80
+IndentPPDirectives: AfterHash
diff --git a/accessible/mac/ARIAGridAccessibleWrap.h b/accessible/mac/ARIAGridAccessibleWrap.h
new file mode 100644
index 0000000000..f6380f2b2e
--- /dev/null
+++ b/accessible/mac/ARIAGridAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.h b/accessible/mac/AccessibleWrap.h
new file mode 100644
index 0000000000..ecbead67dd
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.h
@@ -0,0 +1,94 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+#ifndef _AccessibleWrap_H_
+#define _AccessibleWrap_H_
+
+#include <objc/objc.h>
+
+#include "Accessible.h"
+#include "PlatformExtTypes.h"
+#include "States.h"
+
+#include "nsCOMPtr.h"
+
+#include "nsTArray.h"
+
+#if defined(__OBJC__)
+@class mozAccessible;
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public Accessible {
+ public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap();
+
+ /**
+ * Get the native Obj-C object (mozAccessible).
+ */
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ /**
+ * The objective-c |Class| type that this accessible's native object
+ * should be instantied with. used on runtime to determine the
+ * right type for this accessible's associated native object.
+ */
+ virtual Class GetNativeType();
+
+ virtual void Shutdown() override;
+
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
+ bool ApplyPostFilter(const EWhichPostFilter& aSearchKey,
+ const nsString& aSearchText);
+
+ protected:
+ friend class xpcAccessibleMacInterface;
+
+ /**
+ * Get the native object. Create it if needed.
+ */
+#if defined(__OBJC__)
+ mozAccessible* GetNativeObject();
+#else
+ id GetNativeObject();
+#endif
+
+ private:
+ /**
+ * Our native object. Private because its creation is done lazily.
+ * Don't access it directly. Ever. Unless you are GetNativeObject() or
+ * Shutdown()
+ */
+#if defined(__OBJC__)
+ // if we are in Objective-C, we use the actual Obj-C class.
+ mozAccessible* mNativeObject;
+#else
+ id mNativeObject;
+#endif
+
+ /**
+ * We have created our native. This does not mean there is one.
+ * This can never go back to false.
+ * We need it because checking whether we need a native object cost time.
+ */
+ bool mNativeInited;
+};
+
+Class GetTypeFromRole(roles::Role aRole);
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm
new file mode 100644
index 0000000000..9774fbc3cc
--- /dev/null
+++ b/accessible/mac/AccessibleWrap.mm
@@ -0,0 +1,400 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "DocAccessibleWrap.h"
+#include "nsObjCExceptions.h"
+#include "nsCocoaUtils.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "Role.h"
+#include "TextRange.h"
+#include "gfxPlatform.h"
+
+#import "MOXLandmarkAccessibles.h"
+#import "MOXMathAccessibles.h"
+#import "MOXTextMarkerDelegate.h"
+#import "MOXWebAreaAccessible.h"
+#import "mozAccessible.h"
+#import "mozActionElements.h"
+#import "mozHTMLAccessible.h"
+#import "mozSelectableElements.h"
+#import "mozTableAccessible.h"
+#import "mozTextAccessible.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : Accessible(aContent, aDoc), mNativeObject(nil), mNativeInited(false) {
+ if (aContent && aContent->IsElement() && aDoc) {
+ // Check if this accessible is a live region and queue it
+ // it for dispatching an event after it has been inserted.
+ DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
+ static const dom::Element::AttrValuesArray sLiveRegionValues[] = {
+ nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr};
+ int32_t attrValue = aContent->AsElement()->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::aria_live, sLiveRegionValues,
+ eIgnoreCase);
+ if (attrValue == 0) {
+ // aria-live is "off", do nothing.
+ } else if (attrValue > 0) {
+ // aria-live attribute is polite or assertive. It's live!
+ doc->QueueNewLiveRegion(this);
+ } else if (const nsRoleMapEntry* roleMap =
+ aria::GetRoleMap(aContent->AsElement())) {
+ // aria role defines it as a live region. It's live!
+ if (roleMap->liveAttRule == ePoliteLiveAttr ||
+ roleMap->liveAttRule == eAssertiveLiveAttr) {
+ doc->QueueNewLiveRegion(this);
+ }
+ } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
+ aContent, nsGkAtoms::live)) {
+ // HTML element defines it as a live region. It's live!
+ if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) {
+ doc->QueueNewLiveRegion(this);
+ }
+ }
+ }
+}
+
+AccessibleWrap::~AccessibleWrap() {}
+
+mozAccessible* AccessibleWrap::GetNativeObject() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (!mNativeInited && !mNativeObject) {
+ // We don't creat OSX accessibles for xul tooltips, defunct accessibles,
+ // <br> (whitespace) elements, or pruned children.
+ //
+ // To maintain a scripting environment where the XPCOM accessible hierarchy
+ // look the same on all platforms, we still let the C++ objects be created
+ // though.
+ if (!IsXULTooltip() && !IsDefunct() && Role() != roles::WHITESPACE) {
+ mNativeObject = [[GetNativeType() alloc] initWithAccessible:this];
+ }
+ }
+
+ mNativeInited = true;
+
+ return mNativeObject;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void AccessibleWrap::GetNativeInterface(void** aOutInterface) {
+ *aOutInterface = static_cast<void*>(GetNativeObject());
+}
+
+// overridden in subclasses to create the right kind of object. by default we
+// create a generic 'mozAccessible' node.
+Class AccessibleWrap::GetNativeType() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if (IsXULTabpanels()) {
+ return [mozPaneAccessible class];
+ }
+
+ if (IsTable()) {
+ return [mozTableAccessible class];
+ }
+
+ if (IsTableRow()) {
+ return [mozTableRowAccessible class];
+ }
+
+ if (IsTableCell()) {
+ return [mozTableCellAccessible class];
+ }
+
+ if (IsDoc()) {
+ return [MOXWebAreaAccessible class];
+ }
+
+ return GetTypeFromRole(Role());
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// this method is very important. it is fired when an accessible object "dies".
+// after this point the object might still be around (because some 3rd party
+// still has a ref to it), but it is in fact 'dead'.
+void AccessibleWrap::Shutdown() {
+ // this ensure we will not try to re-create the native object.
+ mNativeInited = true;
+
+ // we really intend to access the member directly.
+ if (mNativeObject) {
+ [mNativeObject expire];
+ [mNativeObject release];
+ mNativeObject = nil;
+ }
+
+ Accessible::Shutdown();
+}
+
+nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ nsresult rv = Accessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t eventType = aEvent->GetEventType();
+
+ if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
+ DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document());
+ doc->ProcessNewLiveRegions();
+ }
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ Accessible* eventTarget = nullptr;
+
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
+ AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
+ // The "widget" is the selected widget's container. In OSX
+ // it is the target of the selection changed event.
+ eventTarget = selEvent->Widget();
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ Accessible* acc = aEvent->GetAccessible();
+ // If there is a text input ancestor, use it as the event source.
+ while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
+ acc = acc->Parent();
+ }
+ eventTarget = acc ? acc : aEvent->GetAccessible();
+ break;
+ }
+ default:
+ eventTarget = aEvent->GetAccessible();
+ break;
+ }
+
+ mozAccessible* nativeAcc = nil;
+ eventTarget->GetNativeInterface((void**)&nativeAcc);
+ if (!nativeAcc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ [nativeAcc stateChanged:event->GetState()
+ isEnabled:event->IsStateEnabled()];
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
+ MOXTextMarkerDelegate* delegate =
+ [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
+ AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
+ AutoTArray<TextRange, 1> ranges;
+ event->SelectionRanges(&ranges);
+
+ if (ranges.Length()) {
+ // Cache selection in delegate.
+ [delegate setSelectionFrom:ranges[0].StartContainer()
+ at:ranges[0].StartOffset()
+ to:ranges[0].EndContainer()
+ at:ranges[0].EndOffset()];
+ }
+
+ [nativeAcc handleAccessibleEvent:eventType];
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ AccCaretMoveEvent* event = downcast_accEvent(aEvent);
+ int32_t caretOffset = event->GetCaretOffset();
+ MOXTextMarkerDelegate* delegate =
+ [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
+ [delegate setCaretOffset:eventTarget at:caretOffset];
+ if (event->IsSelectionCollapsed()) {
+ // If the selection is collapsed, invalidate our text selection cache.
+ [delegate setSelectionFrom:eventTarget
+ at:caretOffset
+ to:eventTarget
+ at:caretOffset];
+ }
+
+ if (mozTextAccessible* textAcc = static_cast<mozTextAccessible*>(
+ [nativeAcc moxEditableAncestor])) {
+ [textAcc
+ handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
+ } else {
+ [nativeAcc
+ handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
+ }
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ AccTextChangeEvent* tcEvent = downcast_accEvent(aEvent);
+ [nativeAcc handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(
+ tcEvent->ModifiedText())
+ inserted:tcEvent->IsTextInserted()
+ inContainer:aEvent->GetAccessible()
+ at:tcEvent->GetStartOffset()];
+ break;
+ }
+
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
+ case nsIAccessibleEvent::EVENT_REORDER:
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
+ case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED:
+ case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED:
+ case nsIAccessibleEvent::EVENT_NAME_CHANGE:
+ [nativeAcc handleAccessibleEvent:eventType];
+ break;
+
+ default:
+ break;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+bool AccessibleWrap::ApplyPostFilter(const EWhichPostFilter& aSearchKey,
+ const nsString& aSearchText) {
+ // We currently only support the eContainsText post filter.
+ MOZ_ASSERT(aSearchKey == EWhichPostFilter::eContainsText,
+ "Only search text supported");
+ nsAutoString name;
+ Name(name);
+ return name.Find(aSearchText, true) != kNotFound;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// AccessibleWrap protected
+
+Class a11y::GetTypeFromRole(roles::Role aRole) {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ switch (aRole) {
+ case roles::COMBOBOX:
+ return [mozPopupButtonAccessible class];
+
+ case roles::PUSHBUTTON:
+ return [mozButtonAccessible class];
+
+ case roles::PAGETAB:
+ return [mozTabAccessible class];
+
+ case roles::CHECKBUTTON:
+ case roles::TOGGLE_BUTTON:
+ return [mozCheckboxAccessible class];
+
+ case roles::RADIOBUTTON:
+ return [mozRadioButtonAccessible class];
+
+ case roles::SPINBUTTON:
+ case roles::SLIDER:
+ return [mozIncrementableAccessible class];
+
+ case roles::HEADING:
+ return [mozHeadingAccessible class];
+
+ case roles::PAGETABLIST:
+ return [mozTabGroupAccessible class];
+
+ case roles::ENTRY:
+ case roles::CAPTION:
+ case roles::ACCEL_LABEL:
+ case roles::EDITCOMBOBOX:
+ case roles::PASSWORD_TEXT:
+ // normal textfield (static or editable)
+ return [mozTextAccessible class];
+
+ case roles::TEXT_LEAF:
+ case roles::STATICTEXT:
+ return [mozTextLeafAccessible class];
+
+ case roles::LANDMARK:
+ return [MOXLandmarkAccessible class];
+
+ case roles::LINK:
+ return [mozLinkAccessible class];
+
+ case roles::LISTBOX:
+ return [mozListboxAccessible class];
+
+ case roles::LISTITEM:
+ return [MOXListItemAccessible class];
+
+ case roles::OPTION: {
+ return [mozOptionAccessible class];
+ }
+
+ case roles::RICH_OPTION: {
+ return [mozSelectableChildAccessible class];
+ }
+
+ case roles::COMBOBOX_LIST:
+ case roles::MENUBAR:
+ case roles::MENUPOPUP: {
+ return [mozMenuAccessible class];
+ }
+
+ case roles::COMBOBOX_OPTION:
+ case roles::PARENT_MENUITEM:
+ case roles::MENUITEM: {
+ return [mozMenuItemAccessible class];
+ }
+
+ case roles::MATHML_ROOT:
+ return [MOXMathRootAccessible class];
+
+ case roles::MATHML_SQUARE_ROOT:
+ return [MOXMathSquareRootAccessible class];
+
+ case roles::MATHML_FRACTION:
+ return [MOXMathFractionAccessible class];
+
+ case roles::MATHML_SUB:
+ case roles::MATHML_SUP:
+ case roles::MATHML_SUB_SUP:
+ return [MOXMathSubSupAccessible class];
+
+ case roles::MATHML_UNDER:
+ case roles::MATHML_OVER:
+ case roles::MATHML_UNDER_OVER:
+ return [MOXMathUnderOverAccessible class];
+
+ case roles::SUMMARY:
+ return [MOXSummaryAccessible class];
+
+ case roles::OUTLINE:
+ case roles::TREE_TABLE:
+ return [mozOutlineAccessible class];
+
+ case roles::OUTLINEITEM:
+ return [mozOutlineRowAccessible class];
+
+ default:
+ return [mozAccessible class];
+ }
+
+ return nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
diff --git a/accessible/mac/ApplicationAccessibleWrap.h b/accessible/mac/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..a4b2fd70c7
--- /dev/null
+++ b/accessible/mac/ApplicationAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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_ApplicationAccessibleWrap_h__
+#define mozilla_a11y_ApplicationAccessibleWrap_h__
+
+#include "ApplicationAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef ApplicationAccessible ApplicationAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/DocAccessibleWrap.h b/accessible/mac/DocAccessibleWrap.h
new file mode 100644
index 0000000000..b46fa9189a
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.h
@@ -0,0 +1,45 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible {
+ public:
+ DocAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+
+ virtual ~DocAccessibleWrap();
+
+ virtual void Shutdown() override;
+
+ virtual void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) override;
+
+ void QueueNewLiveRegion(Accessible* aAccessible);
+
+ void ProcessNewLiveRegions();
+
+ protected:
+ virtual void DoInitialUpdate() override;
+
+ private:
+ nsTHashtable<nsVoidPtrHashKey> mNewLiveRegions;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/DocAccessibleWrap.mm b/accessible/mac/DocAccessibleWrap.mm
new file mode 100644
index 0000000000..b00f48a284
--- /dev/null
+++ b/accessible/mac/DocAccessibleWrap.mm
@@ -0,0 +1,104 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "DocAccessibleWrap.h"
+#include "DocAccessible-inl.h"
+
+#import "mozAccessible.h"
+#import "MOXTextMarkerDelegate.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+DocAccessibleWrap::DocAccessibleWrap(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : DocAccessible(aDocument, aPresShell) {}
+
+void DocAccessibleWrap::Shutdown() {
+ [MOXTextMarkerDelegate destroyForDoc:this];
+ DocAccessible::Shutdown();
+}
+
+DocAccessibleWrap::~DocAccessibleWrap() {}
+
+void DocAccessibleWrap::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ DocAccessible::AttributeChanged(aElement, aNameSpaceID, aAttribute, aModType,
+ aOldValue);
+ if (aAttribute == nsGkAtoms::aria_live) {
+ Accessible* accessible =
+ mContent != aElement ? GetAccessible(aElement) : this;
+ if (!accessible) {
+ return;
+ }
+
+ static const dom::Element::AttrValuesArray sLiveRegionValues[] = {
+ nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr};
+ int32_t attrValue =
+ aElement->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::aria_live,
+ sLiveRegionValues, eIgnoreCase);
+ if (attrValue > 0) {
+ if (!aOldValue || aOldValue->IsEmptyString() ||
+ aOldValue->Equals(nsGkAtoms::OFF, eIgnoreCase)) {
+ // This element just got an active aria-live attribute value
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED,
+ accessible);
+ }
+ } else {
+ if (aOldValue && (aOldValue->Equals(nsGkAtoms::polite, eIgnoreCase) ||
+ aOldValue->Equals(nsGkAtoms::assertive, eIgnoreCase))) {
+ // This element lost an active live region
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED,
+ accessible);
+ } else if (attrValue == 0) {
+ // aria-live="off", check if its a role-based live region that
+ // needs to be removed.
+ if (const nsRoleMapEntry* roleMap = accessible->ARIARoleMap()) {
+ // aria role defines it as a live region. It's live!
+ if (roleMap->liveAttRule == ePoliteLiveAttr ||
+ roleMap->liveAttRule == eAssertiveLiveAttr) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED,
+ accessible);
+ }
+ } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
+ aElement, nsGkAtoms::live)) {
+ // HTML element defines it as a live region. It's live!
+ if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED,
+ accessible);
+ }
+ }
+ }
+ }
+ }
+}
+
+void DocAccessibleWrap::QueueNewLiveRegion(Accessible* aAccessible) {
+ if (!aAccessible) {
+ return;
+ }
+
+ mNewLiveRegions.PutEntry(aAccessible->UniqueID());
+}
+
+void DocAccessibleWrap::ProcessNewLiveRegions() {
+ for (auto iter = mNewLiveRegions.Iter(); !iter.Done(); iter.Next()) {
+ if (Accessible* liveRegion =
+ GetAccessibleByUniqueID(const_cast<void*>(iter.Get()->GetKey()))) {
+ FireDelayedEvent(nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED, liveRegion);
+ }
+ }
+
+ mNewLiveRegions.Clear();
+}
+
+void DocAccessibleWrap::DoInitialUpdate() {
+ DocAccessible::DoInitialUpdate();
+ ProcessNewLiveRegions();
+}
diff --git a/accessible/mac/GeckoTextMarker.h b/accessible/mac/GeckoTextMarker.h
new file mode 100644
index 0000000000..4818e98138
--- /dev/null
+++ b/accessible/mac/GeckoTextMarker.h
@@ -0,0 +1,122 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 _GeckoTextMarker_H_
+#define _GeckoTextMarker_H_
+
+typedef CFTypeRef AXTextMarkerRef;
+typedef CFTypeRef AXTextMarkerRangeRef;
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleOrProxy;
+class GeckoTextMarkerRange;
+
+class GeckoTextMarker final {
+ public:
+ GeckoTextMarker(const AccessibleOrProxy& aContainer, int32_t aOffset)
+ : mContainer(aContainer), mOffset(aOffset) {}
+
+ GeckoTextMarker(const GeckoTextMarker& aPoint)
+ : mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) {}
+
+ GeckoTextMarker(AccessibleOrProxy aDoc, AXTextMarkerRef aTextMarker);
+
+ GeckoTextMarker() : mContainer(nullptr), mOffset(0) {}
+
+ static GeckoTextMarker MarkerFromIndex(const AccessibleOrProxy& aRoot,
+ int32_t aIndex);
+
+ id CreateAXTextMarker();
+
+ bool Next();
+
+ bool Previous();
+
+ // Return a range with the given type relative to this marker.
+ GeckoTextMarkerRange Range(EWhichRange aRangeType);
+
+ AccessibleOrProxy Leaf();
+
+ bool IsValid() const { return !mContainer.IsNull(); };
+
+ bool operator<(const GeckoTextMarker& aPoint) const;
+
+ bool operator==(const GeckoTextMarker& aPoint) const {
+ return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset;
+ }
+
+ AccessibleOrProxy mContainer;
+ int32_t mOffset;
+
+ HyperTextAccessibleWrap* ContainerAsHyperTextWrap() const {
+ return mContainer.IsAccessible()
+ ? static_cast<HyperTextAccessibleWrap*>(
+ mContainer.AsAccessible()->AsHyperText())
+ : nullptr;
+ }
+
+ private:
+ bool IsEditableRoot();
+};
+
+class GeckoTextMarkerRange final {
+ public:
+ GeckoTextMarkerRange(const GeckoTextMarker& aStart,
+ const GeckoTextMarker& aEnd)
+ : mStart(aStart), mEnd(aEnd) {}
+
+ GeckoTextMarkerRange() {}
+
+ GeckoTextMarkerRange(AccessibleOrProxy aDoc,
+ AXTextMarkerRangeRef aTextMarkerRange);
+
+ explicit GeckoTextMarkerRange(const AccessibleOrProxy& aAccessible);
+
+ id CreateAXTextMarkerRange();
+
+ bool IsValid() const {
+ return !mStart.mContainer.IsNull() && !mEnd.mContainer.IsNull();
+ };
+
+ /**
+ * Return text enclosed by the range.
+ */
+ NSString* Text() const;
+
+ /**
+ * Return length of characters enclosed by the range.
+ */
+ int32_t Length() const;
+
+ /**
+ * Return screen bounds of range.
+ */
+ NSValue* Bounds() const;
+
+ /**
+ * Set the current range as the DOM selection.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Select() const;
+
+ /**
+ * Crops the range if it overlaps the given accessible element boundaries.
+ * Return true if successfully cropped. false if the range does not intersect
+ * with the container.
+ */
+ bool Crop(const AccessibleOrProxy& aContainer);
+
+ GeckoTextMarker mStart;
+ GeckoTextMarker mEnd;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/GeckoTextMarker.mm b/accessible/mac/GeckoTextMarker.mm
new file mode 100644
index 0000000000..0481eae83b
--- /dev/null
+++ b/accessible/mac/GeckoTextMarker.mm
@@ -0,0 +1,481 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "DocAccessibleParent.h"
+#include "AccessibleOrProxy.h"
+#include "nsCocoaUtils.h"
+
+#include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
+
+#import "GeckoTextMarker.h"
+
+extern "C" {
+
+CFTypeID AXTextMarkerGetTypeID();
+
+AXTextMarkerRef AXTextMarkerCreate(CFAllocatorRef allocator, const UInt8* bytes,
+ CFIndex length);
+
+const UInt8* AXTextMarkerGetBytePtr(AXTextMarkerRef text_marker);
+
+size_t AXTextMarkerGetLength(AXTextMarkerRef text_marker);
+
+CFTypeID AXTextMarkerRangeGetTypeID();
+
+AXTextMarkerRangeRef AXTextMarkerRangeCreate(CFAllocatorRef allocator,
+ AXTextMarkerRef start_marker,
+ AXTextMarkerRef end_marker);
+
+AXTextMarkerRef AXTextMarkerRangeCopyStartMarker(
+ AXTextMarkerRangeRef text_marker_range);
+
+AXTextMarkerRef AXTextMarkerRangeCopyEndMarker(
+ AXTextMarkerRangeRef text_marker_range);
+}
+
+namespace mozilla {
+namespace a11y {
+
+struct OpaqueGeckoTextMarker {
+ OpaqueGeckoTextMarker(uintptr_t aDoc, uintptr_t aID, int32_t aOffset)
+ : mDoc(aDoc), mID(aID), mOffset(aOffset) {}
+ OpaqueGeckoTextMarker() {}
+ uintptr_t mDoc;
+ uintptr_t mID;
+ int32_t mOffset;
+};
+
+static bool DocumentExists(AccessibleOrProxy aDoc, uintptr_t aDocPtr) {
+ if (aDoc.Bits() == aDocPtr) {
+ return true;
+ }
+
+ if (aDoc.IsAccessible()) {
+ DocAccessible* docAcc = aDoc.AsAccessible()->AsDoc();
+ uint32_t docCount = docAcc->ChildDocumentCount();
+ for (uint32_t i = 0; i < docCount; i++) {
+ if (DocumentExists(docAcc->GetChildDocumentAt(i), aDocPtr)) {
+ return true;
+ }
+ }
+ } else {
+ DocAccessibleParent* docProxy = aDoc.AsProxy()->AsDoc();
+ size_t docCount = docProxy->ChildDocCount();
+ for (uint32_t i = 0; i < docCount; i++) {
+ if (DocumentExists(docProxy->ChildDocAt(i), aDocPtr)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// GeckoTextMarker
+
+GeckoTextMarker::GeckoTextMarker(AccessibleOrProxy aDoc,
+ AXTextMarkerRef aTextMarker) {
+ MOZ_ASSERT(!aDoc.IsNull());
+ OpaqueGeckoTextMarker opaqueMarker;
+ if (aTextMarker &&
+ AXTextMarkerGetLength(aTextMarker) == sizeof(OpaqueGeckoTextMarker)) {
+ memcpy(&opaqueMarker, AXTextMarkerGetBytePtr(aTextMarker),
+ sizeof(OpaqueGeckoTextMarker));
+ if (DocumentExists(aDoc, opaqueMarker.mDoc)) {
+ AccessibleOrProxy doc;
+ doc.SetBits(opaqueMarker.mDoc);
+ if (doc.IsProxy()) {
+ mContainer = doc.AsProxy()->AsDoc()->GetAccessible(opaqueMarker.mID);
+ } else {
+ mContainer = doc.AsAccessible()->AsDoc()->GetAccessibleByUniqueID(
+ reinterpret_cast<void*>(opaqueMarker.mID));
+ }
+ }
+
+ mOffset = opaqueMarker.mOffset;
+ }
+}
+
+GeckoTextMarker GeckoTextMarker::MarkerFromIndex(const AccessibleOrProxy& aRoot,
+ int32_t aIndex) {
+ if (aRoot.IsProxy()) {
+ int32_t offset = 0;
+ uint64_t containerID = 0;
+ DocAccessibleParent* ipcDoc = aRoot.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendOffsetAtIndex(
+ aRoot.AsProxy()->ID(), aIndex, &containerID, &offset);
+ ProxyAccessible* container = ipcDoc->GetAccessible(containerID);
+ return GeckoTextMarker(container, offset);
+ } else if (auto htWrap = static_cast<HyperTextAccessibleWrap*>(
+ aRoot.AsAccessible()->AsHyperText())) {
+ int32_t offset = 0;
+ HyperTextAccessible* container = nullptr;
+ htWrap->OffsetAtIndex(aIndex, &container, &offset);
+ return GeckoTextMarker(container, offset);
+ }
+
+ return GeckoTextMarker();
+}
+
+id GeckoTextMarker::CreateAXTextMarker() {
+ if (!IsValid()) {
+ return nil;
+ }
+
+ AccessibleOrProxy doc;
+ if (mContainer.IsProxy()) {
+ doc = mContainer.AsProxy()->Document();
+ } else {
+ doc = mContainer.AsAccessible()->Document();
+ }
+
+ uintptr_t identifier =
+ mContainer.IsProxy()
+ ? mContainer.AsProxy()->ID()
+ : reinterpret_cast<uintptr_t>(mContainer.AsAccessible()->UniqueID());
+
+ OpaqueGeckoTextMarker opaqueMarker(doc.Bits(), identifier, mOffset);
+ AXTextMarkerRef cf_text_marker = AXTextMarkerCreate(
+ kCFAllocatorDefault, reinterpret_cast<const UInt8*>(&opaqueMarker),
+ sizeof(OpaqueGeckoTextMarker));
+
+ return [static_cast<id>(cf_text_marker) autorelease];
+}
+
+bool GeckoTextMarker::operator<(const GeckoTextMarker& aPoint) const {
+ if (mContainer == aPoint.mContainer) return mOffset < aPoint.mOffset;
+
+ // Build the chain of parents
+ AutoTArray<AccessibleOrProxy, 30> parents1, parents2;
+ AccessibleOrProxy p1 = mContainer;
+ while (!p1.IsNull()) {
+ parents1.AppendElement(p1);
+ p1 = p1.Parent();
+ }
+
+ AccessibleOrProxy p2 = aPoint.mContainer;
+ while (!p2.IsNull()) {
+ parents2.AppendElement(p2);
+ p2 = p2.Parent();
+ }
+
+ // An empty chain of parents means one of the containers was null.
+ MOZ_ASSERT(parents1.Length() != 0 && parents2.Length() != 0,
+ "have empty chain of parents!");
+
+ // Find where the parent chain differs
+ uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
+ for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+ AccessibleOrProxy child1 = parents1.ElementAt(--pos1);
+ AccessibleOrProxy child2 = parents2.ElementAt(--pos2);
+ if (child1 != child2) {
+ return child1.IndexInParent() < child2.IndexInParent();
+ }
+ }
+
+ if (pos1 != 0) {
+ // If parents1 is a superset of parents2 then mContainer is a
+ // descendant of aPoint.mContainer. The next element down in parents1
+ // is mContainer's ancestor that is the child of aPoint.mContainer.
+ // We compare its end offset in aPoint.mContainer with aPoint.mOffset.
+ AccessibleOrProxy child = parents1.ElementAt(pos1 - 1);
+ MOZ_ASSERT(child.Parent() == aPoint.mContainer);
+ bool unused;
+ uint32_t endOffset = child.IsProxy() ? child.AsProxy()->EndOffset(&unused)
+ : child.AsAccessible()->EndOffset();
+ return endOffset < static_cast<uint32_t>(aPoint.mOffset);
+ }
+
+ if (pos2 != 0) {
+ // If parents2 is a superset of parents1 then aPoint.mContainer is a
+ // descendant of mContainer. The next element down in parents2
+ // is aPoint.mContainer's ancestor that is the child of mContainer.
+ // We compare its start offset in mContainer with mOffset.
+ AccessibleOrProxy child = parents2.ElementAt(pos2 - 1);
+ MOZ_ASSERT(child.Parent() == mContainer);
+ bool unused;
+ uint32_t startOffset = child.IsProxy()
+ ? child.AsProxy()->StartOffset(&unused)
+ : child.AsAccessible()->StartOffset();
+ return static_cast<uint32_t>(mOffset) <= startOffset;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Broken tree?!");
+ return false;
+}
+
+bool GeckoTextMarker::IsEditableRoot() {
+ uint64_t state = mContainer.IsProxy() ? mContainer.AsProxy()->State()
+ : mContainer.AsAccessible()->State();
+ if ((state & states::EDITABLE) == 0) {
+ return false;
+ }
+
+ AccessibleOrProxy parent = mContainer.Parent();
+ if (parent.IsNull()) {
+ // Not sure when this can happen, but it would technically be an editable
+ // root.
+ return true;
+ }
+
+ state = parent.IsProxy() ? parent.AsProxy()->State()
+ : parent.AsAccessible()->State();
+
+ return (state & states::EDITABLE) == 0;
+}
+
+bool GeckoTextMarker::Next() {
+ if (mContainer.IsProxy()) {
+ int32_t nextOffset = 0;
+ uint64_t nextContainerID = 0;
+ DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendNextClusterAt(
+ mContainer.AsProxy()->ID(), mOffset, &nextContainerID, &nextOffset);
+ ProxyAccessible* nextContainer = ipcDoc->GetAccessible(nextContainerID);
+ bool moved = nextContainer != mContainer.AsProxy() || nextOffset != mOffset;
+ mContainer = nextContainer;
+ mOffset = nextOffset;
+ return moved;
+ } else if (auto htWrap = ContainerAsHyperTextWrap()) {
+ HyperTextAccessible* nextContainer = nullptr;
+ int32_t nextOffset = 0;
+ htWrap->NextClusterAt(mOffset, &nextContainer, &nextOffset);
+ bool moved = nextContainer != htWrap || nextOffset != mOffset;
+ mContainer = nextContainer;
+ mOffset = nextOffset;
+ return moved;
+ }
+
+ return false;
+}
+
+bool GeckoTextMarker::Previous() {
+ if (mContainer.IsProxy()) {
+ int32_t prevOffset = 0;
+ uint64_t prevContainerID = 0;
+ DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendPreviousClusterAt(
+ mContainer.AsProxy()->ID(), mOffset, &prevContainerID, &prevOffset);
+ ProxyAccessible* prevContainer = ipcDoc->GetAccessible(prevContainerID);
+ bool moved = prevContainer != mContainer.AsProxy() || prevOffset != mOffset;
+ mContainer = prevContainer;
+ mOffset = prevOffset;
+ return moved;
+ } else if (auto htWrap = ContainerAsHyperTextWrap()) {
+ HyperTextAccessible* prevContainer = nullptr;
+ int32_t prevOffset = 0;
+ htWrap->PreviousClusterAt(mOffset, &prevContainer, &prevOffset);
+ bool moved = prevContainer != htWrap || prevOffset != mOffset;
+ mContainer = prevContainer;
+ mOffset = prevOffset;
+ return moved;
+ }
+
+ return false;
+}
+
+static uint32_t CharacterCount(const AccessibleOrProxy& aContainer) {
+ if (aContainer.IsProxy()) {
+ return aContainer.AsProxy()->CharacterCount();
+ }
+
+ if (aContainer.AsAccessible()->IsHyperText()) {
+ return aContainer.AsAccessible()->AsHyperText()->CharacterCount();
+ }
+
+ return 0;
+}
+
+GeckoTextMarkerRange GeckoTextMarker::Range(EWhichRange aRangeType) {
+ MOZ_ASSERT(!mContainer.IsNull());
+ if (mContainer.IsProxy()) {
+ int32_t startOffset = 0, endOffset = 0;
+ uint64_t startContainerID = 0, endContainerID = 0;
+ DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
+ bool success = ipcDoc->GetPlatformExtension()->SendRangeAt(
+ mContainer.AsProxy()->ID(), mOffset, aRangeType, &startContainerID,
+ &startOffset, &endContainerID, &endOffset);
+ if (success) {
+ return GeckoTextMarkerRange(
+ GeckoTextMarker(ipcDoc->GetAccessible(startContainerID), startOffset),
+ GeckoTextMarker(ipcDoc->GetAccessible(endContainerID), endOffset));
+ }
+ } else if (auto htWrap = ContainerAsHyperTextWrap()) {
+ int32_t startOffset = 0, endOffset = 0;
+ HyperTextAccessible* startContainer = nullptr;
+ HyperTextAccessible* endContainer = nullptr;
+ htWrap->RangeAt(mOffset, aRangeType, &startContainer, &startOffset,
+ &endContainer, &endOffset);
+ return GeckoTextMarkerRange(GeckoTextMarker(startContainer, startOffset),
+ GeckoTextMarker(endContainer, endOffset));
+ }
+
+ return GeckoTextMarkerRange(GeckoTextMarker(), GeckoTextMarker());
+}
+
+AccessibleOrProxy GeckoTextMarker::Leaf() {
+ MOZ_ASSERT(!mContainer.IsNull());
+ if (mContainer.IsProxy()) {
+ uint64_t leafID = 0;
+ DocAccessibleParent* ipcDoc = mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendLeafAtOffset(
+ mContainer.AsProxy()->ID(), mOffset, &leafID);
+ return ipcDoc->GetAccessible(leafID);
+ } else if (auto htWrap = ContainerAsHyperTextWrap()) {
+ return htWrap->LeafAtOffset(mOffset);
+ }
+
+ return mContainer;
+}
+
+// GeckoTextMarkerRange
+
+GeckoTextMarkerRange::GeckoTextMarkerRange(
+ AccessibleOrProxy aDoc, AXTextMarkerRangeRef aTextMarkerRange) {
+ if (!aTextMarkerRange ||
+ CFGetTypeID(aTextMarkerRange) != AXTextMarkerRangeGetTypeID()) {
+ return;
+ }
+
+ AXTextMarkerRef start_marker(
+ AXTextMarkerRangeCopyStartMarker(aTextMarkerRange));
+ AXTextMarkerRef end_marker(AXTextMarkerRangeCopyEndMarker(aTextMarkerRange));
+
+ mStart = GeckoTextMarker(aDoc, start_marker);
+ mEnd = GeckoTextMarker(aDoc, end_marker);
+
+ CFRelease(start_marker);
+ CFRelease(end_marker);
+}
+
+GeckoTextMarkerRange::GeckoTextMarkerRange(
+ const AccessibleOrProxy& aAccessible) {
+ if ((aAccessible.IsAccessible() &&
+ aAccessible.AsAccessible()->IsHyperText()) ||
+ (aAccessible.IsProxy() && aAccessible.AsProxy()->mIsHyperText)) {
+ // The accessible is a hypertext. Initialize range to its inner text range.
+ mStart = GeckoTextMarker(aAccessible, 0);
+ mEnd = GeckoTextMarker(aAccessible, (CharacterCount(aAccessible)));
+ } else {
+ // The accessible is not a hypertext (maybe a text leaf?). Initialize range
+ // to its offsets in its container.
+ mStart = GeckoTextMarker(aAccessible.Parent(), 0);
+ mEnd = GeckoTextMarker(aAccessible.Parent(), 0);
+ if (mStart.mContainer.IsProxy()) {
+ DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendRangeOfChild(
+ mStart.mContainer.AsProxy()->ID(), aAccessible.AsProxy()->ID(),
+ &mStart.mOffset, &mEnd.mOffset);
+ } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
+ htWrap->RangeOfChild(aAccessible.AsAccessible(), &mStart.mOffset,
+ &mEnd.mOffset);
+ }
+ }
+}
+
+id GeckoTextMarkerRange::CreateAXTextMarkerRange() {
+ if (!IsValid()) {
+ return nil;
+ }
+
+ AXTextMarkerRangeRef cf_text_marker_range =
+ AXTextMarkerRangeCreate(kCFAllocatorDefault, mStart.CreateAXTextMarker(),
+ mEnd.CreateAXTextMarker());
+ return [static_cast<id>(cf_text_marker_range) autorelease];
+}
+
+NSString* GeckoTextMarkerRange::Text() const {
+ nsAutoString text;
+ if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
+ DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendTextForRange(
+ mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
+ mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &text);
+ } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
+ htWrap->TextForRange(text, mStart.mOffset, mEnd.ContainerAsHyperTextWrap(),
+ mEnd.mOffset);
+ }
+ return nsCocoaUtils::ToNSString(text);
+}
+
+int32_t GeckoTextMarkerRange::Length() const {
+ int32_t length = 0;
+ if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
+ DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendLengthForRange(
+ mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
+ mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &length);
+ } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
+ length = htWrap->LengthForRange(
+ mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
+ }
+
+ return length;
+}
+
+NSValue* GeckoTextMarkerRange::Bounds() const {
+ nsIntRect rect;
+ if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
+ DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendBoundsForRange(
+ mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
+ mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset, &rect);
+ } else if (auto htWrap = mStart.ContainerAsHyperTextWrap()) {
+ rect = htWrap->BoundsForRange(
+ mStart.mOffset, mEnd.ContainerAsHyperTextWrap(), mEnd.mOffset);
+ }
+
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
+ NSRect r =
+ NSMakeRect(static_cast<CGFloat>(rect.x) / scaleFactor,
+ [mainView frame].size.height -
+ static_cast<CGFloat>(rect.y + rect.height) / scaleFactor,
+ static_cast<CGFloat>(rect.width) / scaleFactor,
+ static_cast<CGFloat>(rect.height) / scaleFactor);
+
+ return [NSValue valueWithRect:r];
+}
+
+void GeckoTextMarkerRange::Select() const {
+ if (mStart.mContainer.IsProxy() && mEnd.mContainer.IsProxy()) {
+ DocAccessibleParent* ipcDoc = mStart.mContainer.AsProxy()->Document();
+ Unused << ipcDoc->GetPlatformExtension()->SendSelectRange(
+ mStart.mContainer.AsProxy()->ID(), mStart.mOffset,
+ mEnd.mContainer.AsProxy()->ID(), mEnd.mOffset);
+ } else if (RefPtr<HyperTextAccessibleWrap> htWrap =
+ mStart.ContainerAsHyperTextWrap()) {
+ RefPtr<HyperTextAccessibleWrap> end = mEnd.ContainerAsHyperTextWrap();
+ htWrap->SelectRange(mStart.mOffset, end, mEnd.mOffset);
+ }
+}
+
+bool GeckoTextMarkerRange::Crop(const AccessibleOrProxy& aContainer) {
+ GeckoTextMarker containerStart(aContainer, 0);
+ GeckoTextMarker containerEnd(aContainer, CharacterCount(aContainer));
+
+ if (mEnd < containerStart || containerEnd < mStart) {
+ // The range ends before the container, or starts after it.
+ return false;
+ }
+
+ if (mStart < containerStart) {
+ // If range start is before container start, adjust range start to
+ // start of container.
+ mStart = containerStart;
+ }
+
+ if (containerEnd < mEnd) {
+ // If range end is after container end, adjust range end to end of
+ // container.
+ mEnd = containerEnd;
+ }
+
+ return true;
+}
+}
+}
diff --git a/accessible/mac/HTMLTableAccessibleWrap.h b/accessible/mac/HTMLTableAccessibleWrap.h
new file mode 100644
index 0000000000..f62e626109
--- /dev/null
+++ b/accessible/mac/HTMLTableAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/HyperTextAccessibleWrap.h b/accessible/mac/HyperTextAccessibleWrap.h
new file mode 100644
index 0000000000..1c25e8a4e6
--- /dev/null
+++ b/accessible/mac/HyperTextAccessibleWrap.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_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+#include "PlatformExtTypes.h"
+
+namespace mozilla {
+namespace a11y {
+
+struct TextPoint;
+
+class HyperTextAccessibleWrap : public HyperTextAccessible {
+ public:
+ HyperTextAccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : HyperTextAccessible(aContent, aDoc) {}
+
+ void TextForRange(nsAString& aText, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset);
+
+ nsIntRect BoundsForRange(int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset);
+
+ int32_t LengthForRange(int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset);
+
+ void OffsetAtIndex(int32_t aIndex, HyperTextAccessible** aContainer,
+ int32_t* aOffset);
+
+ void RangeAt(int32_t aOffset, EWhichRange aRangeType,
+ HyperTextAccessible** aStartContainer, int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer, int32_t* aEndOffset);
+
+ void NextClusterAt(int32_t aOffset, HyperTextAccessible** aNextContainer,
+ int32_t* aNextOffset);
+
+ void PreviousClusterAt(int32_t aOffset, HyperTextAccessible** aPrevContainer,
+ int32_t* aPrevOffset);
+
+ void RangeOfChild(Accessible* aChild, int32_t* aStartOffset,
+ int32_t* aEndOffset);
+
+ Accessible* LeafAtOffset(int32_t aOffset);
+
+ MOZ_CAN_RUN_SCRIPT void SelectRange(int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset);
+
+ protected:
+ ~HyperTextAccessibleWrap() {}
+
+ private:
+ TextPoint FindTextPoint(int32_t aOffset, nsDirection aDirection,
+ nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType);
+
+ HyperTextAccessibleWrap* EditableRoot();
+
+ void LeftWordAt(int32_t aOffset, HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset, HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset);
+
+ void RightWordAt(int32_t aOffset, HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset, HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset);
+
+ void LineAt(int32_t aOffset, bool aNextLine,
+ HyperTextAccessible** aStartContainer, int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer, int32_t* aEndOffset);
+
+ void ParagraphAt(int32_t aOffset, HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset, HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset);
+
+ void StyleAt(int32_t aOffset, HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset, HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/HyperTextAccessibleWrap.mm b/accessible/mac/HyperTextAccessibleWrap.mm
new file mode 100644
index 0000000000..0fff4621ea
--- /dev/null
+++ b/accessible/mac/HyperTextAccessibleWrap.mm
@@ -0,0 +1,704 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "HyperTextAccessibleWrap.h"
+
+#include "Accessible-inl.h"
+#include "HTMLListAccessible.h"
+#include "nsAccUtils.h"
+#include "nsFrameSelection.h"
+#include "TextRange.h"
+#include "TreeWalker.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+// HyperTextIterator
+
+class HyperTextIterator {
+ public:
+ HyperTextIterator(HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer, int32_t aEndOffset)
+ : mCurrentContainer(aStartContainer),
+ mCurrentStartOffset(aStartOffset),
+ mCurrentEndOffset(aStartOffset),
+ mEndContainer(aEndContainer),
+ mEndOffset(aEndOffset) {}
+
+ bool Next();
+
+ int32_t SegmentLength();
+
+ // If offset is set to a child hyperlink, adjust it so it set on the first
+ // offset in the deepest link. Or, if the offset to the last character, set it
+ // to the outermost end offset in an ancestor. Returns true if iterator was
+ // mutated.
+ bool NormalizeForward();
+
+ // If offset is set right after child hyperlink, adjust it so it set on the
+ // last offset in the deepest link. Or, if the offset is on the first
+ // character of a link, set it to the outermost start offset in an ancestor.
+ // Returns true if iterator was mutated.
+ bool NormalizeBackward();
+
+ HyperTextAccessible* mCurrentContainer;
+ int32_t mCurrentStartOffset;
+ int32_t mCurrentEndOffset;
+
+ private:
+ int32_t NextLinkOffset();
+
+ HyperTextAccessible* mEndContainer;
+ int32_t mEndOffset;
+};
+
+bool HyperTextIterator::NormalizeForward() {
+ if (mCurrentStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT ||
+ mCurrentStartOffset >=
+ static_cast<int32_t>(mCurrentContainer->CharacterCount())) {
+ // If this is the end of the current container, mutate to its parent's
+ // end offset.
+ if (!mCurrentContainer->IsLink()) {
+ // If we are not a link, it is a root hypertext accessible.
+ return false;
+ }
+ if (!mCurrentContainer->Parent() ||
+ !mCurrentContainer->Parent()->IsHyperText()) {
+ // If we are a link, but our parent is not a hypertext accessible
+ // treat the current container as the root hypertext accessible.
+ // This can be the case with some XUL containers that are not
+ // hypertext accessibles.
+ return false;
+ }
+ uint32_t endOffset = mCurrentContainer->EndOffset();
+ if (endOffset != 0) {
+ mCurrentContainer = mCurrentContainer->Parent()->AsHyperText();
+ mCurrentStartOffset = endOffset;
+
+ if (mCurrentContainer == mEndContainer &&
+ mCurrentStartOffset >= mEndOffset) {
+ // Reached end boundary.
+ return false;
+ }
+
+ // Call NormalizeForward recursively to get top-most link if at the end of
+ // one, or innermost link if at the beginning.
+ NormalizeForward();
+ return true;
+ }
+ } else {
+ Accessible* link = mCurrentContainer->LinkAt(
+ mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset));
+
+ // If there is a link at this offset, mutate into it.
+ if (link && link->IsHyperText()) {
+ if (mCurrentStartOffset > 0 &&
+ mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset) ==
+ mCurrentContainer->LinkIndexAtOffset(mCurrentStartOffset - 1)) {
+ MOZ_ASSERT_UNREACHABLE("Same link for previous offset");
+ return false;
+ }
+
+ mCurrentContainer = link->AsHyperText();
+ if (link->IsHTMLListItem()) {
+ Accessible* bullet = link->AsHTMLListItem()->Bullet();
+ mCurrentStartOffset = bullet ? nsAccUtils::TextLength(bullet) : 0;
+ } else {
+ mCurrentStartOffset = 0;
+ }
+
+ if (mCurrentContainer == mEndContainer &&
+ mCurrentStartOffset >= mEndOffset) {
+ // Reached end boundary.
+ return false;
+ }
+
+ // Call NormalizeForward recursively to get top-most embedding ancestor
+ // if at the end of one, or innermost link if at the beginning.
+ NormalizeForward();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool HyperTextIterator::NormalizeBackward() {
+ if (mCurrentStartOffset == 0) {
+ // If this is the start of the current container, mutate to its parent's
+ // start offset.
+ if (!mCurrentContainer->IsLink()) {
+ // If we are not a link, it is a root hypertext accessible.
+ return false;
+ }
+ if (!mCurrentContainer->Parent() ||
+ !mCurrentContainer->Parent()->IsHyperText()) {
+ // If we are a link, but our parent is not a hypertext accessible
+ // treat the current container as the root hypertext accessible.
+ // This can be the case with some XUL containers that are not
+ // hypertext accessibles.
+ return false;
+ }
+
+ uint32_t startOffset = mCurrentContainer->StartOffset();
+ mCurrentContainer = mCurrentContainer->Parent()->AsHyperText();
+ mCurrentStartOffset = startOffset;
+
+ // Call NormalizeBackward recursively to get top-most link if at the
+ // beginning of one, or innermost link if at the end.
+ NormalizeBackward();
+ return true;
+ } else {
+ Accessible* link =
+ mCurrentContainer->GetChildAtOffset(mCurrentStartOffset - 1);
+
+ // If there is a link before this offset, mutate into it,
+ // and set the offset to its last character.
+ if (link && link->IsHyperText()) {
+ mCurrentContainer = link->AsHyperText();
+ mCurrentStartOffset = mCurrentContainer->CharacterCount();
+
+ // Call NormalizeBackward recursively to get top-most top-most embedding
+ // ancestor if at the beginning of one, or innermost link if at the end.
+ NormalizeBackward();
+ return true;
+ }
+
+ if (mCurrentContainer->IsHTMLListItem() &&
+ mCurrentContainer->AsHTMLListItem()->Bullet() == link) {
+ mCurrentStartOffset = 0;
+ NormalizeBackward();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int32_t HyperTextIterator::SegmentLength() {
+ int32_t endOffset = mCurrentEndOffset < 0
+ ? mCurrentContainer->CharacterCount()
+ : mCurrentEndOffset;
+
+ return endOffset - mCurrentStartOffset;
+}
+
+int32_t HyperTextIterator::NextLinkOffset() {
+ int32_t linkCount = mCurrentContainer->LinkCount();
+ for (int32_t i = 0; i < linkCount; i++) {
+ Accessible* link = mCurrentContainer->LinkAt(i);
+ MOZ_ASSERT(link);
+ int32_t linkStartOffset = link->StartOffset();
+ if (mCurrentStartOffset < linkStartOffset) {
+ return linkStartOffset;
+ }
+ }
+
+ return -1;
+}
+
+bool HyperTextIterator::Next() {
+ if (!mCurrentContainer->Document()->HasLoadState(
+ DocAccessible::eTreeConstructed)) {
+ // If the accessible tree is still being constructed the text tree
+ // is not in a traversable state yet.
+ return false;
+ }
+
+ if (mCurrentContainer == mEndContainer &&
+ (mCurrentEndOffset == -1 || mEndOffset <= mCurrentEndOffset)) {
+ return false;
+ } else {
+ mCurrentStartOffset = mCurrentEndOffset;
+ NormalizeForward();
+ }
+
+ int32_t nextLinkOffset = NextLinkOffset();
+ if (mCurrentContainer == mEndContainer &&
+ (nextLinkOffset == -1 || nextLinkOffset > mEndOffset)) {
+ mCurrentEndOffset =
+ mEndOffset < 0 ? mEndContainer->CharacterCount() : mEndOffset;
+ } else {
+ mCurrentEndOffset = nextLinkOffset < 0 ? mCurrentContainer->CharacterCount()
+ : nextLinkOffset;
+ }
+
+ return mCurrentStartOffset != mCurrentEndOffset;
+}
+
+void HyperTextAccessibleWrap::TextForRange(nsAString& aText,
+ int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset) {
+ if (IsHTMLListItem()) {
+ Accessible* maybeBullet = GetChildAtOffset(aStartOffset - 1);
+ if (maybeBullet) {
+ Accessible* bullet = AsHTMLListItem()->Bullet();
+ if (maybeBullet == bullet) {
+ TextSubstring(0, nsAccUtils::TextLength(bullet), aText);
+ }
+ }
+ }
+
+ HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
+ while (iter.Next()) {
+ nsAutoString text;
+ iter.mCurrentContainer->TextSubstring(iter.mCurrentStartOffset,
+ iter.mCurrentEndOffset, text);
+ aText.Append(text);
+ }
+}
+
+nsIntRect HyperTextAccessibleWrap::BoundsForRange(
+ int32_t aStartOffset, HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset) {
+ nsIntRect rect;
+ HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
+ while (iter.Next()) {
+ nsIntRect stringRect = iter.mCurrentContainer->TextBounds(
+ iter.mCurrentStartOffset, iter.mCurrentEndOffset);
+ rect.UnionRect(rect, stringRect);
+ }
+
+ return rect;
+}
+
+int32_t HyperTextAccessibleWrap::LengthForRange(
+ int32_t aStartOffset, HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset) {
+ int32_t length = 0;
+ HyperTextIterator iter(this, aStartOffset, aEndContainer, aEndOffset);
+ while (iter.Next()) {
+ length += iter.SegmentLength();
+ }
+
+ return length;
+}
+
+void HyperTextAccessibleWrap::OffsetAtIndex(int32_t aIndex,
+ HyperTextAccessible** aContainer,
+ int32_t* aOffset) {
+ int32_t index = aIndex;
+ HyperTextIterator iter(this, 0, this, CharacterCount());
+ while (iter.Next()) {
+ int32_t segmentLength = iter.SegmentLength();
+ if (index <= segmentLength) {
+ *aContainer = iter.mCurrentContainer;
+ *aOffset = iter.mCurrentStartOffset + index;
+ break;
+ }
+ index -= segmentLength;
+ }
+}
+
+void HyperTextAccessibleWrap::RangeAt(int32_t aOffset, EWhichRange aRangeType,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ switch (aRangeType) {
+ case EWhichRange::eLeftWord:
+ LeftWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ case EWhichRange::eRightWord:
+ RightWordAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ case EWhichRange::eLine:
+ case EWhichRange::eLeftLine:
+ LineAt(aOffset, false, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ case EWhichRange::eRightLine:
+ LineAt(aOffset, true, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ case EWhichRange::eParagraph:
+ ParagraphAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ case EWhichRange::eStyle:
+ StyleAt(aOffset, aStartContainer, aStartOffset, aEndContainer,
+ aEndOffset);
+ break;
+ default:
+ break;
+ }
+}
+
+void HyperTextAccessibleWrap::LeftWordAt(int32_t aOffset,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ TextPoint here(this, aOffset);
+ TextPoint start =
+ FindTextPoint(aOffset, eDirPrevious, eSelectWord, eStartWord);
+ if (!start.mContainer) {
+ return;
+ }
+
+ if ((NativeState() & states::EDITABLE) &&
+ !(start.mContainer->NativeState() & states::EDITABLE)) {
+ // The word search crossed an editable boundary. Return the first word of
+ // the editable root.
+ return EditableRoot()->RightWordAt(0, aStartContainer, aStartOffset,
+ aEndContainer, aEndOffset);
+ }
+
+ TextPoint end =
+ static_cast<HyperTextAccessibleWrap*>(start.mContainer)
+ ->FindTextPoint(start.mOffset, eDirNext, eSelectWord, eEndWord);
+ if (end < here) {
+ *aStartContainer = end.mContainer;
+ *aEndContainer = here.mContainer;
+ *aStartOffset = end.mOffset;
+ *aEndOffset = here.mOffset;
+ } else {
+ *aStartContainer = start.mContainer;
+ *aEndContainer = end.mContainer;
+ *aStartOffset = start.mOffset;
+ *aEndOffset = end.mOffset;
+ }
+}
+
+void HyperTextAccessibleWrap::RightWordAt(int32_t aOffset,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ TextPoint here(this, aOffset);
+ TextPoint end = FindTextPoint(aOffset, eDirNext, eSelectWord, eEndWord);
+ if (!end.mContainer || end < here || here == end) {
+ // If we didn't find a word end, or if we wrapped around (bug 1652833),
+ // return with no result.
+ return;
+ }
+
+ if ((NativeState() & states::EDITABLE) &&
+ !(end.mContainer->NativeState() & states::EDITABLE)) {
+ // The word search crossed an editable boundary. Return with no result.
+ return;
+ }
+
+ TextPoint start =
+ static_cast<HyperTextAccessibleWrap*>(end.mContainer)
+ ->FindTextPoint(end.mOffset, eDirPrevious, eSelectWord, eStartWord);
+
+ if (here < start) {
+ *aStartContainer = here.mContainer;
+ *aEndContainer = start.mContainer;
+ *aStartOffset = here.mOffset;
+ *aEndOffset = start.mOffset;
+ } else {
+ *aStartContainer = start.mContainer;
+ *aEndContainer = end.mContainer;
+ *aStartOffset = start.mOffset;
+ *aEndOffset = end.mOffset;
+ }
+}
+
+void HyperTextAccessibleWrap::LineAt(int32_t aOffset, bool aNextLine,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ TextPoint here(this, aOffset);
+ TextPoint end =
+ FindTextPoint(aOffset, eDirNext, eSelectEndLine, eDefaultBehavior);
+ if (!end.mContainer || end < here) {
+ // If we didn't find a word end, or if we wrapped around (bug 1652833),
+ // return with no result.
+ return;
+ }
+
+ TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer)
+ ->FindTextPoint(end.mOffset, eDirPrevious,
+ eSelectBeginLine, eDefaultBehavior);
+
+ if (!aNextLine && here < start) {
+ start = FindTextPoint(aOffset, eDirPrevious, eSelectBeginLine,
+ eDefaultBehavior);
+ if (!start.mContainer) {
+ return;
+ }
+
+ end = static_cast<HyperTextAccessibleWrap*>(start.mContainer)
+ ->FindTextPoint(start.mOffset, eDirNext, eSelectEndLine,
+ eDefaultBehavior);
+ }
+
+ *aStartContainer = start.mContainer;
+ *aEndContainer = end.mContainer;
+ *aStartOffset = start.mOffset;
+ *aEndOffset = end.mOffset;
+}
+
+void HyperTextAccessibleWrap::ParagraphAt(int32_t aOffset,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ TextPoint here(this, aOffset);
+ TextPoint end =
+ FindTextPoint(aOffset, eDirNext, eSelectParagraph, eDefaultBehavior);
+
+ if (!end.mContainer || end < here) {
+ // If we didn't find a word end, or if we wrapped around (bug 1652833),
+ // return with no result.
+ return;
+ }
+
+ if (end.mOffset == -1 && Parent() && Parent()->IsHyperText()) {
+ // If end offset is -1 we didn't find a paragraph boundary.
+ // This must be an inline container, go to its parent to
+ // retrieve paragraph boundaries.
+ static_cast<HyperTextAccessibleWrap*>(Parent()->AsHyperText())
+ ->ParagraphAt(StartOffset(), aStartContainer, aStartOffset,
+ aEndContainer, aEndOffset);
+ return;
+ }
+
+ TextPoint start = static_cast<HyperTextAccessibleWrap*>(end.mContainer)
+ ->FindTextPoint(end.mOffset, eDirPrevious,
+ eSelectParagraph, eDefaultBehavior);
+
+ *aStartContainer = start.mContainer;
+ *aEndContainer = end.mContainer;
+ *aStartOffset = start.mOffset;
+ *aEndOffset = end.mOffset;
+}
+
+void HyperTextAccessibleWrap::StyleAt(int32_t aOffset,
+ HyperTextAccessible** aStartContainer,
+ int32_t* aStartOffset,
+ HyperTextAccessible** aEndContainer,
+ int32_t* aEndOffset) {
+ // Get the range of the text leaf at this offset.
+ // A text leaf represents a stretch of like-styled text.
+ auto leaf = LeafAtOffset(aOffset);
+ if (!leaf) {
+ return;
+ }
+
+ MOZ_ASSERT(leaf->Parent()->IsHyperText());
+ HyperTextAccessibleWrap* container =
+ static_cast<HyperTextAccessibleWrap*>(leaf->Parent()->AsHyperText());
+ if (!container) {
+ return;
+ }
+
+ *aStartContainer = *aEndContainer = container;
+ container->RangeOfChild(leaf, aStartOffset, aEndOffset);
+}
+
+void HyperTextAccessibleWrap::NextClusterAt(
+ int32_t aOffset, HyperTextAccessible** aNextContainer,
+ int32_t* aNextOffset) {
+ TextPoint here(this, aOffset);
+ TextPoint next =
+ FindTextPoint(aOffset, eDirNext, eSelectCluster, eDefaultBehavior);
+
+ if ((next.mOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT &&
+ next.mContainer == Document()) ||
+ (next < here)) {
+ // If we reached the end of the doc, or if we wrapped to the start of the
+ // doc return given offset as-is.
+ *aNextContainer = this;
+ *aNextOffset = aOffset;
+ } else {
+ *aNextContainer = next.mContainer;
+ *aNextOffset = next.mOffset;
+ }
+}
+
+void HyperTextAccessibleWrap::PreviousClusterAt(
+ int32_t aOffset, HyperTextAccessible** aPrevContainer,
+ int32_t* aPrevOffset) {
+ TextPoint prev =
+ FindTextPoint(aOffset, eDirPrevious, eSelectCluster, eDefaultBehavior);
+ *aPrevContainer = prev.mContainer;
+ *aPrevOffset = prev.mOffset;
+}
+
+void HyperTextAccessibleWrap::RangeOfChild(Accessible* aChild,
+ int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ MOZ_ASSERT(aChild->Parent() == this);
+ *aStartOffset = *aEndOffset = -1;
+ int32_t index = GetIndexOf(aChild);
+ if (index != -1) {
+ *aStartOffset = GetChildOffset(index);
+ // If this is the last child index + 1 will return the total
+ // chracter count.
+ *aEndOffset = GetChildOffset(index + 1);
+ }
+}
+
+Accessible* HyperTextAccessibleWrap::LeafAtOffset(int32_t aOffset) {
+ HyperTextAccessible* text = this;
+ Accessible* child = nullptr;
+ // The offset needed should "attach" the previous accessible if
+ // in between two accessibles.
+ int32_t innerOffset = aOffset > 0 ? aOffset - 1 : aOffset;
+ do {
+ int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
+ if (childIdx == -1) {
+ return text;
+ }
+
+ child = text->GetChildAt(childIdx);
+ if (!child || nsAccUtils::MustPrune(text)) {
+ return text;
+ }
+
+ innerOffset -= text->GetChildOffset(childIdx);
+
+ text = child->AsHyperText();
+ } while (text);
+
+ return child;
+}
+
+void HyperTextAccessibleWrap::SelectRange(int32_t aStartOffset,
+ HyperTextAccessible* aEndContainer,
+ int32_t aEndOffset) {
+ TextRange range(this, this, aStartOffset, aEndContainer, aEndOffset);
+ range.SetSelectionAt(0);
+}
+
+TextPoint HyperTextAccessibleWrap::FindTextPoint(
+ int32_t aOffset, nsDirection aDirection, nsSelectionAmount aAmount,
+ EWordMovementType aWordMovementType) {
+ // Layout can remain trapped in an editable. We normalize out of
+ // it if we are in its last offset.
+ HyperTextIterator iter(this, aOffset, this, CharacterCount());
+ if (aDirection == eDirNext) {
+ iter.NormalizeForward();
+ } else {
+ iter.NormalizeBackward();
+ }
+
+ // Find a leaf accessible frame to start with. PeekOffset wants this.
+ HyperTextAccessible* text = iter.mCurrentContainer;
+ Accessible* child = nullptr;
+ int32_t innerOffset = iter.mCurrentStartOffset;
+
+ do {
+ int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
+
+ // We can have an empty text leaf as our only child. Since empty text
+ // leaves are not accessible we then have no children, but 0 is a valid
+ // innerOffset.
+ if (childIdx == -1) {
+ NS_ASSERTION(innerOffset == 0 && !text->ChildCount(), "No childIdx?");
+ return TextPoint(text, 0);
+ }
+
+ child = text->GetChildAt(childIdx);
+ if (child->IsHyperText() && !child->ChildCount()) {
+ // If this is a childless hypertext, jump to its
+ // previous or next sibling, depending on
+ // direction.
+ if (aDirection == eDirPrevious && childIdx > 0) {
+ child = text->GetChildAt(--childIdx);
+ } else if (aDirection == eDirNext &&
+ childIdx + 1 < static_cast<int32_t>(text->ChildCount())) {
+ child = text->GetChildAt(++childIdx);
+ }
+ }
+
+ int32_t childOffset = text->GetChildOffset(childIdx);
+
+ if (child->IsHyperText() && aDirection == eDirPrevious && childIdx > 0 &&
+ innerOffset - childOffset == 0) {
+ // If we are searching backwards, and this is the begining of a
+ // segment, get the previous sibling so that layout will start
+ // its search there.
+ childIdx--;
+ innerOffset -= text->GetChildOffset(childIdx);
+ child = text->GetChildAt(childIdx);
+ } else {
+ innerOffset -= childOffset;
+ }
+
+ text = child->AsHyperText();
+ } while (text);
+
+ nsIFrame* childFrame = child->GetFrame();
+ if (!childFrame) {
+ NS_ERROR("No child frame");
+ return TextPoint(this, aOffset);
+ }
+
+ int32_t innerContentOffset = innerOffset;
+ if (child->IsTextLeaf()) {
+ NS_ASSERTION(childFrame->IsTextFrame(), "Wrong frame!");
+ RenderedToContentOffset(childFrame, innerOffset, &innerContentOffset);
+ }
+
+ nsIFrame* frameAtOffset = childFrame;
+ int32_t offsetInFrame = 0;
+ childFrame->GetChildFrameContainingOffset(innerContentOffset, true,
+ &offsetInFrame, &frameAtOffset);
+ if (aDirection == eDirPrevious && offsetInFrame == 0) {
+ // If we are searching backwards, and we are at the start of a frame,
+ // get the previous continuation frame.
+ if (nsIFrame* prevInContinuation = frameAtOffset->GetPrevContinuation()) {
+ frameAtOffset = prevInContinuation;
+ }
+ }
+
+ const bool kIsJumpLinesOk = true; // okay to jump lines
+ const bool kIsScrollViewAStop = false; // do not stop at scroll views
+ const bool kIsKeyboardSelect = true; // is keyboard selection
+ const bool kIsVisualBidi = false; // use visual order for bidi text
+ nsPeekOffsetStruct pos(
+ aAmount, aDirection, innerContentOffset, nsPoint(0, 0), kIsJumpLinesOk,
+ kIsScrollViewAStop, kIsKeyboardSelect, kIsVisualBidi, false,
+ nsPeekOffsetStruct::ForceEditableRegion::No, aWordMovementType, false);
+ nsresult rv = frameAtOffset->PeekOffset(&pos);
+
+ // PeekOffset fails on last/first lines of the text in certain cases.
+ if (NS_FAILED(rv) && aAmount == eSelectLine) {
+ pos.mAmount = aDirection == eDirNext ? eSelectEndLine : eSelectBeginLine;
+ frameAtOffset->PeekOffset(&pos);
+ }
+ if (!pos.mResultContent) {
+ NS_ERROR("No result content!");
+ return TextPoint(this, aOffset);
+ }
+
+ if (aDirection == eDirNext &&
+ nsContentUtils::PositionIsBefore(pos.mResultContent, mContent, nullptr,
+ nullptr)) {
+ // Bug 1652833 makes us sometimes return the first element on the doc.
+ return TextPoint(Document(), nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT);
+ }
+
+ HyperTextAccessible* container =
+ nsAccUtils::GetTextContainer(pos.mResultContent);
+ int32_t offset = container ? container->DOMPointToOffset(
+ pos.mResultContent, pos.mContentOffset,
+ aDirection == eDirNext)
+ : 0;
+ return TextPoint(container, offset);
+}
+
+HyperTextAccessibleWrap* HyperTextAccessibleWrap::EditableRoot() {
+ Accessible* editable = nullptr;
+ for (Accessible* acc = this; acc && acc != Document(); acc = acc->Parent()) {
+ if (acc->NativeState() & states::EDITABLE) {
+ editable = acc;
+ } else {
+ break;
+ }
+ }
+
+ return static_cast<HyperTextAccessibleWrap*>(editable->AsHyperText());
+}
diff --git a/accessible/mac/ImageAccessibleWrap.h b/accessible/mac/ImageAccessibleWrap.h
new file mode 100644
index 0000000000..a5621c7313
--- /dev/null
+++ b/accessible/mac/ImageAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/MOXAccessibleBase.h b/accessible/mac/MOXAccessibleBase.h
new file mode 100644
index 0000000000..b88b8f2857
--- /dev/null
+++ b/accessible/mac/MOXAccessibleBase.h
@@ -0,0 +1,138 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozAccessibleProtocol.h"
+#import "MOXAccessibleProtocol.h"
+
+#include "Platform.h"
+
+inline id<mozAccessible> GetObjectOrRepresentedView(id<mozAccessible> aObject) {
+ if (!mozilla::a11y::ShouldA11yBeEnabled()) {
+ // If platform a11y is not enabled, don't return represented view.
+ // This is mostly for our mochitest environment because the represented
+ // ChildView checks `ShouldA11yBeEnabled` before proxying accessibility
+ // methods to mozAccessibles.
+ return aObject;
+ }
+
+ return [aObject hasRepresentedView] ? [aObject representedView] : aObject;
+}
+
+@interface MOXAccessibleBase : NSObject <mozAccessible, MOXAccessible> {
+ BOOL mIsExpired;
+}
+
+#pragma mark - mozAccessible/widget
+
+// override
+- (BOOL)hasRepresentedView;
+
+// override
+- (id)representedView;
+
+// override
+- (BOOL)isRoot;
+
+#pragma mark - mozAccessible/NSAccessibility
+
+// The methods below interface with the platform through NSAccessibility.
+// They should not be called directly or overridden in subclasses.
+
+// override, final
+- (NSArray*)accessibilityAttributeNames;
+
+// override, final
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+// override, final
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+
+// override, final
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+
+// override, final
+- (NSArray*)accessibilityActionNames;
+
+// override, final
+- (void)accessibilityPerformAction:(NSString*)action;
+
+// override, final
+- (NSString*)accessibilityActionDescription:(NSString*)action;
+
+// override, final
+- (NSArray*)accessibilityParameterizedAttributeNames;
+
+// override, final
+- (id)accessibilityAttributeValue:(NSString*)attribute
+ forParameter:(id)parameter;
+
+// override, final
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// override, final
+- (id)accessibilityFocusedUIElement;
+
+// override, final
+- (BOOL)isAccessibilityElement;
+
+// final
+- (BOOL)accessibilityNotifiesWhenDestroyed;
+
+#pragma mark - MOXAccessible protocol
+
+// override
+- (id)moxHitTest:(NSPoint)point;
+
+// override
+- (id)moxFocusedUIElement;
+
+// override
+- (void)moxPostNotification:(NSString*)notification;
+
+// override
+- (void)moxPostNotification:(NSString*)notification
+ withUserInfo:(NSDictionary*)userInfo;
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (NSArray*)moxUnignoredChildren;
+
+// override
+- (NSArray*)moxChildren;
+
+// override
+- (id<mozAccessible>)moxUnignoredParent;
+
+// override
+- (id<mozAccessible>)moxParent;
+
+// override
+- (NSNumber*)moxIndexForChildUIElement:(id)child;
+
+// override
+- (id)moxTopLevelUIElement;
+
+// override
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate;
+
+- (BOOL)moxIsLiveRegion;
+
+#pragma mark -
+
+- (NSString*)description;
+
+- (BOOL)isExpired;
+
+// makes ourselves "expired". after this point, we might be around if someone
+// has retained us (e.g., a third-party), but we really contain no information.
+- (void)expire;
+
+@end
diff --git a/accessible/mac/MOXAccessibleBase.mm b/accessible/mac/MOXAccessibleBase.mm
new file mode 100644
index 0000000000..0e671659a2
--- /dev/null
+++ b/accessible/mac/MOXAccessibleBase.mm
@@ -0,0 +1,553 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MOXAccessibleBase.h"
+
+#import "MacSelectorMap.h"
+
+#include "nsObjCExceptions.h"
+#include "xpcAccessibleMacInterface.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla::a11y;
+
+#undef LOG
+mozilla::LogModule* GetMacAccessibilityLog() {
+ static mozilla::LazyLogModule sLog("MacAccessibility");
+
+ return sLog;
+}
+#define LOG(type, format, ...) \
+ do { \
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), type)) { \
+ NSString* msg = [NSString stringWithFormat:(format), ##__VA_ARGS__]; \
+ MOZ_LOG(GetMacAccessibilityLog(), type, ("%s", [msg UTF8String])); \
+ } \
+ } while (0)
+
+@interface NSObject (MOXAccessible)
+
+// This NSObject conforms to MOXAccessible.
+// This is needed to we know to mutate the value
+// (get represented view, check isAccessibilityElement)
+// before forwarding it to NSAccessibility.
+- (BOOL)isMOXAccessible;
+
+// Same as above, but this checks if the NSObject is an array with
+// mozAccessible conforming objects.
+- (BOOL)hasMOXAccessibles;
+
+@end
+
+@implementation NSObject (MOXAccessible)
+
+- (BOOL)isMOXAccessible {
+ return [self conformsToProtocol:@protocol(MOXAccessible)];
+}
+
+- (BOOL)hasMOXAccessibles {
+ return [self isKindOfClass:[NSArray class]] &&
+ [[(NSArray*)self firstObject] isMOXAccessible];
+}
+
+@end
+
+// Private methods
+@interface MOXAccessibleBase ()
+
+- (BOOL)isSelectorSupported:(SEL)selector;
+
+@end
+
+@implementation MOXAccessibleBase
+
+#pragma mark - mozAccessible/widget
+
+- (BOOL)hasRepresentedView {
+ return NO;
+}
+
+- (id)representedView {
+ return nil;
+}
+
+- (BOOL)isRoot {
+ return NO;
+}
+
+#pragma mark - mozAccessible/NSAccessibility
+
+- (NSArray*)accessibilityAttributeNames {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ static NSMutableDictionary* attributesForEachClass = nil;
+
+ if (!attributesForEachClass) {
+ attributesForEachClass = [[NSMutableDictionary alloc] init];
+ }
+
+ NSMutableArray* attributes =
+ attributesForEachClass [[self class]] ?: [[NSMutableArray alloc] init];
+
+ NSDictionary* getters = mac::AttributeGetters();
+ if (![attributes count]) {
+ // Go through all our attribute getters, if they are supported by this class
+ // advertise the attribute name.
+ for (NSString* attribute in getters) {
+ SEL selector = NSSelectorFromString(getters[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ [attributes addObject:attribute];
+ }
+ }
+
+ // If we have a delegate add all the text marker attributes.
+ if ([self moxTextMarkerDelegate]) {
+ [attributes addObjectsFromArray:[mac::TextAttributeGetters() allKeys]];
+ }
+
+ // We store a hash table with types as keys, and atttribute lists as values.
+ // This lets us cache the atttribute list of each subclass so we only
+ // need to gather its MOXAccessible methods once.
+ // XXX: Uncomment when accessibilityAttributeNames is removed from all
+ // subclasses. attributesForEachClass[[self class]] = attributes;
+ }
+
+ return attributes;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ id value = nil;
+ NSDictionary* getters = mac::AttributeGetters();
+ if (getters[attribute]) {
+ SEL selector = NSSelectorFromString(getters[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ value = [self performSelector:selector];
+ }
+ } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+ // If we have a delegate, check if attribute is a text marker
+ // attribute and call the associated selector on the delegate
+ // if so.
+ NSDictionary* textMarkerGetters = mac::TextAttributeGetters();
+ if (textMarkerGetters[attribute]) {
+ SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
+ if ([textMarkerDelegate respondsToSelector:selector]) {
+ value = [textMarkerDelegate performSelector:selector];
+ }
+ }
+ }
+
+ if ([value isMOXAccessible]) {
+ // If this is a MOXAccessible, get its represented view or filter it if
+ // it should be ignored.
+ value = [value isAccessibilityElement] ? GetObjectOrRepresentedView(value)
+ : nil;
+ }
+
+ if ([value hasMOXAccessibles]) {
+ // If this is an array of mozAccessibles, get each element's represented
+ // view and remove it from the returned array if it should be ignored.
+ NSUInteger arrSize = [value count];
+ NSMutableArray* arr = [[NSMutableArray alloc] initWithCapacity:arrSize];
+ for (NSUInteger i = 0; i < arrSize; i++) {
+ id<mozAccessible> mozAcc = GetObjectOrRepresentedView(value[i]);
+ if ([mozAcc isAccessibilityElement]) {
+ [arr addObject:mozAcc];
+ }
+ }
+
+ value = arr;
+ }
+
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Debug)) {
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
+ LOG(LogLevel::Verbose, @"%@ attributeValue %@ => %@", self, attribute,
+ value);
+ } else if (![attribute isEqualToString:@"AXParent"] &&
+ ![attribute isEqualToString:@"AXRole"] &&
+ ![attribute isEqualToString:@"AXSubrole"] &&
+ ![attribute isEqualToString:@"AXSize"] &&
+ ![attribute isEqualToString:@"AXPosition"] &&
+ ![attribute isEqualToString:@"AXRole"] &&
+ ![attribute isEqualToString:@"AXChildren"]) {
+ LOG(LogLevel::Debug, @"%@ attributeValue %@", self, attribute);
+ }
+ }
+
+ return value;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([self isExpired]) {
+ return NO;
+ }
+
+ NSDictionary* setters = mac::AttributeSetters();
+ if (setters[attribute]) {
+ SEL selector = NSSelectorFromString(setters[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ return YES;
+ }
+ } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+ // If we have a delegate, check text setters on delegate
+ NSDictionary* textMarkerSetters = mac::TextAttributeSetters();
+ if (textMarkerSetters[attribute]) {
+ SEL selector = NSSelectorFromString(textMarkerSetters[attribute]);
+ if ([textMarkerDelegate respondsToSelector:selector]) {
+ return YES;
+ }
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self isExpired]) {
+ return;
+ }
+
+ LOG(LogLevel::Debug, @"%@ setValueForattribute %@ = %@", self, attribute,
+ value);
+
+ NSDictionary* setters = mac::AttributeSetters();
+ if (setters[attribute]) {
+ SEL selector = NSSelectorFromString(setters[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ [self performSelector:selector withObject:value];
+ }
+ } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+ // If we have a delegate, check if attribute is a text marker
+ // attribute and call the associated selector on the delegate
+ // if so.
+ NSDictionary* textMarkerSetters = mac::TextAttributeSetters();
+ if (textMarkerSetters[attribute]) {
+ SEL selector = NSSelectorFromString(textMarkerSetters[attribute]);
+ if ([textMarkerDelegate respondsToSelector:selector]) {
+ [textMarkerDelegate performSelector:selector withObject:value];
+ }
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSArray*)accessibilityActionNames {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ NSMutableArray* actionNames = [[NSMutableArray alloc] init];
+
+ NSDictionary* actions = mac::Actions();
+ for (NSString* action in actions) {
+ SEL selector = NSSelectorFromString(actions[action]);
+ if ([self isSelectorSupported:selector]) {
+ [actionNames addObject:action];
+ }
+ }
+
+ return actionNames;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)accessibilityPerformAction:(NSString*)action {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if ([self isExpired]) {
+ return;
+ }
+
+ LOG(LogLevel::Debug, @"%@ performAction %@ ", self, action);
+
+ NSDictionary* actions = mac::Actions();
+ if (actions[action]) {
+ SEL selector = NSSelectorFromString(actions[action]);
+ if ([self isSelectorSupported:selector]) {
+ [self performSelector:selector];
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSString*)accessibilityActionDescription:(NSString*)action {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ // by default we return whatever the MacOS API know about.
+ // if you have custom actions, override.
+ return NSAccessibilityActionDescription(action);
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSArray*)accessibilityParameterizedAttributeNames {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ NSMutableArray* attributeNames = [[NSMutableArray alloc] init];
+
+ NSDictionary* attributes = mac::ParameterizedAttributeGetters();
+ for (NSString* attribute in attributes) {
+ SEL selector = NSSelectorFromString(attributes[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ [attributeNames addObject:attribute];
+ }
+ }
+
+ // If we have a delegate add all the text marker attributes.
+ if ([self moxTextMarkerDelegate]) {
+ [attributeNames
+ addObjectsFromArray:[mac::ParameterizedTextAttributeGetters() allKeys]];
+ }
+
+ return attributeNames;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityAttributeValue:(NSString*)attribute
+ forParameter:(id)parameter {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ id value = nil;
+
+ NSDictionary* getters = mac::ParameterizedAttributeGetters();
+ if (getters[attribute]) {
+ SEL selector = NSSelectorFromString(getters[attribute]);
+ if ([self isSelectorSupported:selector]) {
+ value = [self performSelector:selector withObject:parameter];
+ }
+ } else if (id textMarkerDelegate = [self moxTextMarkerDelegate]) {
+ // If we have a delegate, check if attribute is a text marker
+ // attribute and call the associated selector on the delegate
+ // if so.
+ NSDictionary* textMarkerGetters = mac::ParameterizedTextAttributeGetters();
+ if (textMarkerGetters[attribute]) {
+ SEL selector = NSSelectorFromString(textMarkerGetters[attribute]);
+ if ([textMarkerDelegate respondsToSelector:selector]) {
+ value = [textMarkerDelegate performSelector:selector
+ withObject:parameter];
+ }
+ }
+ }
+
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
+ LOG(LogLevel::Verbose, @"%@ attributeValueForParam %@(%@) => %@", self,
+ attribute, parameter, value);
+ } else {
+ LOG(LogLevel::Debug, @"%@ attributeValueForParam %@", self, attribute);
+ }
+
+ return value;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ return [self moxHitTest:point];
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)accessibilityFocusedUIElement {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ return [self moxFocusedUIElement];
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isAccessibilityElement {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
+
+ if ([self isExpired]) {
+ return YES;
+ }
+
+ id parent = [self moxParent];
+ if (![parent isMOXAccessible]) {
+ return YES;
+ }
+
+ return ![self moxIgnoreWithParent:parent];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
+}
+
+- (BOOL)accessibilityNotifiesWhenDestroyed {
+ return YES;
+}
+
+#pragma mark - MOXAccessible protocol
+
+- (NSNumber*)moxIndexForChildUIElement:(id)child {
+ return @([[self moxUnignoredChildren] indexOfObject:child]);
+}
+
+- (id)moxTopLevelUIElement {
+ return [self moxWindow];
+}
+
+- (id)moxHitTest:(NSPoint)point {
+ return GetObjectOrRepresentedView(self);
+}
+
+- (id)moxFocusedUIElement {
+ return GetObjectOrRepresentedView(self);
+}
+
+- (void)moxPostNotification:(NSString*)notification {
+ [self moxPostNotification:notification withUserInfo:nil];
+}
+
+- (void)moxPostNotification:(NSString*)notification
+ withUserInfo:(NSDictionary*)userInfo {
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Verbose)) {
+ LOG(LogLevel::Verbose, @"%@ notify %@ %@", self, notification, userInfo);
+ } else {
+ LOG(LogLevel::Debug, @"%@ notify %@", self, notification);
+ }
+
+ // This sends events via nsIObserverService to be consumed by our mochitests.
+ xpcAccessibleMacEvent::FireEvent(self, notification, userInfo);
+
+ if (gfxPlatform::IsHeadless()) {
+ // Using a headless toolkit for tests and whatnot, posting accessibility
+ // notification won't work.
+ return;
+ }
+
+ if (![self isAccessibilityElement]) {
+ // If this is an ignored object, don't expose it to system.
+ return;
+ }
+
+ if (userInfo) {
+ NSAccessibilityPostNotificationWithUserInfo(
+ GetObjectOrRepresentedView(self), notification, userInfo);
+ } else {
+ NSAccessibilityPostNotification(GetObjectOrRepresentedView(self),
+ notification);
+ }
+}
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ return NO;
+}
+
+- (NSArray*)moxChildren {
+ return @[];
+}
+
+- (NSArray*)moxUnignoredChildren {
+ NSMutableArray* unignoredChildren = [[NSMutableArray alloc] init];
+ NSArray* allChildren = [self moxChildren];
+
+ for (MOXAccessibleBase* nativeChild in allChildren) {
+ if ([nativeChild moxIgnoreWithParent:self]) {
+ // If this child should be ignored get its unignored children.
+ // This will in turn recurse to any unignored descendants if the
+ // child is ignored.
+ [unignoredChildren
+ addObjectsFromArray:[nativeChild moxUnignoredChildren]];
+ } else {
+ [unignoredChildren addObject:nativeChild];
+ }
+ }
+
+ return unignoredChildren;
+}
+
+- (id<mozAccessible>)moxParent {
+ return nil;
+}
+
+- (id<mozAccessible>)moxUnignoredParent {
+ id nativeParent = [self moxParent];
+
+ if (![nativeParent isAccessibilityElement]) {
+ return [nativeParent moxUnignoredParent];
+ }
+
+ return GetObjectOrRepresentedView(nativeParent);
+}
+
+- (BOOL)moxIgnoreWithParent:(MOXAccessibleBase*)parent {
+ return [parent moxIgnoreChild:self];
+}
+
+- (BOOL)moxIgnoreChild:(MOXAccessibleBase*)child {
+ return NO;
+}
+
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
+ return nil;
+}
+
+- (BOOL)moxIsLiveRegion {
+ return NO;
+}
+
+#pragma mark -
+
+// objc-style description (from NSObject); not to be confused with the
+// accessible description above.
+- (NSString*)description {
+ if (MOZ_LOG_TEST(GetMacAccessibilityLog(), LogLevel::Debug)) {
+ if ([self isSelectorSupported:@selector(moxMozDebugDescription)]) {
+ return [self moxMozDebugDescription];
+ }
+ }
+
+ return [NSString stringWithFormat:@"<%@: %p %@>",
+ NSStringFromClass([self class]), self,
+ [self moxRole]];
+}
+
+- (BOOL)isExpired {
+ return mIsExpired;
+}
+
+- (void)expire {
+ MOZ_ASSERT(!mIsExpired, "expire called an expired mozAccessible!");
+
+ mIsExpired = YES;
+
+ [self moxPostNotification:NSAccessibilityUIElementDestroyedNotification];
+}
+
+#pragma mark - Private
+
+- (BOOL)isSelectorSupported:(SEL)selector {
+ return
+ [self respondsToSelector:selector] && ![self moxBlockSelector:selector];
+}
+
+@end
diff --git a/accessible/mac/MOXAccessibleProtocol.h b/accessible/mac/MOXAccessibleProtocol.h
new file mode 100644
index 0000000000..8b9a55b07a
--- /dev/null
+++ b/accessible/mac/MOXAccessibleProtocol.h
@@ -0,0 +1,502 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+@protocol MOXTextMarkerSupport;
+
+// This protocol's primary use is for abstracting the NSAccessibility informal
+// protocol into a formal internal API. Conforming classes get to choose a
+// subset of the optional methods to implement. Those methods will be mapped to
+// NSAccessibility attributes or actions. A conforming class can implement
+// moxBlockSelector to control which of its implemented methods should be
+// exposed to NSAccessibility.
+
+@protocol MOXAccessible
+
+// The deepest descendant of the accessible subtree that contains the specified
+// point. Forwarded from accessibilityHitTest.
+- (id _Nullable)moxHitTest:(NSPoint)point;
+
+// The deepest descendant of the accessible subtree that has the focus.
+// Forwarded from accessibilityFocusedUIElement.
+- (id _Nullable)moxFocusedUIElement;
+
+// Sends a notification to any observing assistive applications.
+- (void)moxPostNotification:(NSString* _Nonnull)notification;
+
+- (void)moxPostNotification:(NSString* _Nonnull)notification
+ withUserInfo:(NSDictionary* _Nullable)userInfo;
+
+// Return YES if selector should be considered not supported.
+// Used in implementations such as:
+// - accessibilityAttributeNames
+// - accessibilityActionNames
+// - accessibilityIsAttributeSettable
+- (BOOL)moxBlockSelector:(SEL _Nonnull)selector;
+
+// Returns a list of all children, doesn't do ignore filtering.
+- (NSArray* _Nullable)moxChildren;
+
+// Returns our parent, doesn't do ignore filtering.
+- (id<mozAccessible> _Nullable)moxParent;
+
+// This is called by isAccessibilityElement. If a subclass wants
+// to alter the isAccessibilityElement return value, it must
+// override this and not isAccessibilityElement directly.
+- (BOOL)moxIgnoreWithParent:(id<MOXAccessible> _Nullable)parent;
+
+// Should the child be ignored. This allows subclasses to determine
+// what kinds of accessibles are valid children. This causes the child
+// to be skipped, but the unignored descendants will be added to the
+// container in the default children getter.
+- (BOOL)moxIgnoreChild:(id<MOXAccessible> _Nullable)child;
+
+// Return text delegate if it exists.
+- (id<MOXTextMarkerSupport> _Nullable)moxTextMarkerDelegate;
+
+// Return true if this accessible is a live region
+- (BOOL)moxIsLiveRegion;
+
+@optional
+
+#pragma mark - AttributeGetters
+
+// AXChildren
+- (NSArray* _Nullable)moxUnignoredChildren;
+
+// AXParent
+- (id _Nullable)moxUnignoredParent;
+
+// AXRole
+- (NSString* _Nullable)moxRole;
+
+// AXRoleDescription
+- (NSString* _Nullable)moxRoleDescription;
+
+// AXSubrole
+- (NSString* _Nullable)moxSubrole;
+
+// AXTitle
+- (NSString* _Nullable)moxTitle;
+
+// AXDescription
+- (NSString* _Nullable)moxLabel;
+
+// AXHelp
+- (NSString* _Nullable)moxHelp;
+
+// AXValue
+- (id _Nullable)moxValue;
+
+// AXSize
+- (NSValue* _Nullable)moxSize;
+
+// AXPosition
+- (NSValue* _Nullable)moxPosition;
+
+// AXEnabled
+- (NSNumber* _Nullable)moxEnabled;
+
+// AXFocused
+- (NSNumber* _Nullable)moxFocused;
+
+// AXWindow
+- (id _Nullable)moxWindow;
+
+// AXTitleUIElement
+- (id _Nullable)moxTitleUIElement;
+
+// AXTopLevelUIElement
+- (id _Nullable)moxTopLevelUIElement;
+
+// AXHasPopup
+- (NSNumber* _Nullable)moxHasPopup;
+
+// AXARIACurrent
+- (NSString* _Nullable)moxARIACurrent;
+
+// AXSelected
+- (NSNumber* _Nullable)moxSelected;
+
+// AXRequired
+- (NSNumber* _Nullable)moxRequired;
+
+// AXElementBusy
+- (NSNumber* _Nullable)moxElementBusy;
+
+// AXLinkedUIElements
+- (NSArray* _Nullable)moxLinkedUIElements;
+
+// AXARIAControls
+- (NSArray* _Nullable)moxARIAControls;
+
+// AXDOMIdentifier
+- (NSString* _Nullable)moxDOMIdentifier;
+
+// AXURL
+- (NSURL* _Nullable)moxURL;
+
+// AXLinkUIElements
+- (NSArray* _Nullable)moxLinkUIElements;
+
+// AXPopupValue
+- (NSString* _Nullable)moxPopupValue;
+
+// AXVisited
+- (NSNumber* _Nullable)moxVisited;
+
+// AXExpanded
+- (NSNumber* _Nullable)moxExpanded;
+
+// AXMain
+- (NSNumber* _Nullable)moxMain;
+
+// AXMinimized
+- (NSNumber* _Nullable)moxMinimized;
+
+// AXSelectedChildren
+- (NSArray* _Nullable)moxSelectedChildren;
+
+// AXTabs
+- (NSArray* _Nullable)moxTabs;
+
+// AXContents
+- (NSArray* _Nullable)moxContents;
+
+// AXOrientation
+- (NSString* _Nullable)moxOrientation;
+
+// AXMenuItemMarkChar
+- (NSString* _Nullable)moxMenuItemMarkChar;
+
+// AXLoaded
+- (NSNumber* _Nullable)moxLoaded;
+
+// AXLoadingProgress
+- (NSNumber* _Nullable)moxLoadingProgress;
+
+// Webkit also implements the following:
+// // AXCaretBrowsingEnabled
+// - (NSString* _Nullable)moxCaretBrowsingEnabled;
+
+// // AXLayoutCount
+// - (NSString* _Nullable)moxLayoutCount;
+
+// // AXWebSessionID
+// - (NSString* _Nullable)moxWebSessionID;
+
+// // AXPreventKeyboardDOMEventDispatch
+// - (NSString* _Nullable)moxPreventKeyboardDOMEventDispatch;
+
+// Table Attributes
+
+// AXRowCount
+- (NSNumber* _Nullable)moxRowCount;
+
+// AXColumnCount
+- (NSNumber* _Nullable)moxColumnCount;
+
+// AXRows
+- (NSArray* _Nullable)moxRows;
+
+// AXColumns
+- (NSArray* _Nullable)moxColumns;
+
+// AXIndex
+- (NSNumber* _Nullable)moxIndex;
+
+// AXRowIndexRange
+- (NSValue* _Nullable)moxRowIndexRange;
+
+// AXColumnIndexRange
+- (NSValue* _Nullable)moxColumnIndexRange;
+
+// AXRowHeaderUIElements
+- (NSArray* _Nullable)moxRowHeaderUIElements;
+
+// AXColumnHeaderUIElements
+- (NSArray* _Nullable)moxColumnHeaderUIElements;
+
+// AXIdentifier
+- (NSString* _Nullable)moxIdentifier;
+
+// AXVisibleChildren
+- (NSArray* _Nullable)moxVisibleChildren;
+
+// Outline Attributes
+
+// AXDisclosing
+- (NSNumber* _Nullable)moxDisclosing;
+
+// AXDisclosedByRow
+- (id _Nullable)moxDisclosedByRow;
+
+// AXDisclosureLevel
+- (NSNumber* _Nullable)moxDisclosureLevel;
+
+// AXDisclosedRows
+- (NSArray* _Nullable)moxDisclosedRows;
+
+// AXSelectedRows
+- (NSArray* _Nullable)moxSelectedRows;
+
+// Math Attributes
+
+// AXMathRootRadicand
+- (id _Nullable)moxMathRootRadicand;
+
+// AXMathRootIndex
+- (id _Nullable)moxMathRootIndex;
+
+// AXMathFractionNumerator
+- (id _Nullable)moxMathFractionNumerator;
+
+// AXMathFractionDenominator
+- (id _Nullable)moxMathFractionDenominator;
+
+// AXMathLineThickness
+- (NSNumber* _Nullable)moxMathLineThickness;
+
+// AXMathBase
+- (id _Nullable)moxMathBase;
+
+// AXMathSubscript
+- (id _Nullable)moxMathSubscript;
+
+// AXMathSuperscript
+- (id _Nullable)moxMathSuperscript;
+
+// AXMathUnder
+- (id _Nullable)moxMathUnder;
+
+// AXMathOver
+- (id _Nullable)moxMathOver;
+
+// AXInvalid
+- (NSString* _Nullable)moxInvalid;
+
+// AXSelectedText
+- (NSString* _Nullable)moxSelectedText;
+
+// AXSelectedTextRange
+- (NSValue* _Nullable)moxSelectedTextRange;
+
+// AXNumberOfCharacters
+- (NSNumber* _Nullable)moxNumberOfCharacters;
+
+// AXVisibleCharacterRange
+- (NSValue* _Nullable)moxVisibleCharacterRange;
+
+// AXInsertionPointLineNumber
+- (NSNumber* _Nullable)moxInsertionPointLineNumber;
+
+// AXEditableAncestor
+- (id _Nullable)moxEditableAncestor;
+
+// AXHighestEditableAncestor
+- (id _Nullable)moxHighestEditableAncestor;
+
+// AXFocusableAncestor
+- (id _Nullable)moxFocusableAncestor;
+
+// AXARIAAtomic
+- (NSNumber* _Nullable)moxARIAAtomic;
+
+// AXARIALive
+- (NSString* _Nullable)moxARIALive;
+
+// AXARIARelevant
+- (NSString* _Nullable)moxARIARelevant;
+
+// AXMozDebugDescription
+- (NSString* _Nullable)moxMozDebugDescription;
+
+#pragma mark - AttributeSetters
+
+// AXDisclosing
+- (void)moxSetDisclosing:(NSNumber* _Nullable)disclosing;
+
+// AXValue
+- (void)moxSetValue:(id _Nullable)value;
+
+// AXFocused
+- (void)moxSetFocused:(NSNumber* _Nullable)focused;
+
+// AXSelected
+- (void)moxSetSelected:(NSNumber* _Nullable)selected;
+
+// AXSelectedChildren
+- (void)moxSetSelectedChildren:(NSArray* _Nullable)selectedChildren;
+
+// AXSelectedText
+- (void)moxSetSelectedText:(NSString* _Nullable)selectedText;
+
+// AXSelectedTextRange
+- (void)moxSetSelectedTextRange:(NSValue* _Nullable)selectedTextRange;
+
+// AXVisibleCharacterRange
+- (void)moxSetVisibleCharacterRange:(NSValue* _Nullable)visibleCharacterRange;
+
+#pragma mark - Actions
+
+// AXPress
+- (void)moxPerformPress;
+
+// AXShowMenu
+- (void)moxPerformShowMenu;
+
+// AXScrollToVisible
+- (void)moxPerformScrollToVisible;
+
+// AXIncrement
+- (void)moxPerformIncrement;
+
+// AXDecrement
+- (void)moxPerformDecrement;
+
+#pragma mark - ParameterizedAttributeGetters
+
+// AXLineForIndex
+- (NSNumber* _Nullable)moxLineForIndex:(NSNumber* _Nonnull)index;
+
+// AXRangeForLine
+- (NSValue* _Nullable)moxRangeForLine:(NSNumber* _Nonnull)line;
+
+// AXStringForRange
+- (NSString* _Nullable)moxStringForRange:(NSValue* _Nonnull)range;
+
+// AXRangeForPosition
+- (NSValue* _Nullable)moxRangeForPosition:(NSValue* _Nonnull)position;
+
+// AXRangeForIndex
+- (NSValue* _Nullable)moxRangeForIndex:(NSNumber* _Nonnull)index;
+
+// AXBoundsForRange
+- (NSValue* _Nullable)moxBoundsForRange:(NSValue* _Nonnull)range;
+
+// AXRTFForRange
+- (NSData* _Nullable)moxRTFForRange:(NSValue* _Nonnull)range;
+
+// AXStyleRangeForIndex
+- (NSValue* _Nullable)moxStyleRangeForIndex:(NSNumber* _Nonnull)index;
+
+// AttributedStringForRange
+- (NSAttributedString* _Nullable)moxAttributedStringForRange:
+ (NSValue* _Nonnull)range;
+
+// AXUIElementsForSearchPredicate
+- (NSArray* _Nullable)moxUIElementsForSearchPredicate:
+ (NSDictionary* _Nonnull)searchPredicate;
+
+// AXUIElementCountForSearchPredicate
+- (NSNumber* _Nullable)moxUIElementCountForSearchPredicate:
+ (NSDictionary* _Nonnull)searchPredicate;
+
+// AXCellForColumnAndRow
+- (id _Nullable)moxCellForColumnAndRow:(NSArray* _Nonnull)columnAndRow;
+
+// AXIndexForChildUIElement
+- (NSNumber* _Nullable)moxIndexForChildUIElement:(id _Nonnull)child;
+
+@end
+
+// This protocol maps text marker and text marker range parameters to
+// methods. It is implemented by a delegate of a MOXAccessible.
+@protocol MOXTextMarkerSupport
+
+#pragma mark - TextAttributeGetters
+
+// AXStartTextMarker
+- (id _Nullable)moxStartTextMarker;
+
+// AXEndTextMarker
+- (id _Nullable)moxEndTextMarker;
+
+// AXSelectedTextMarkerRange
+- (id _Nullable)moxSelectedTextMarkerRange;
+
+#pragma mark - ParameterizedTextAttributeGetters
+
+// AXLengthForTextMarkerRange
+- (NSNumber* _Nullable)moxLengthForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXStringForTextMarkerRange
+- (NSString* _Nullable)moxStringForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXTextMarkerRangeForUnorderedTextMarkers
+- (id _Nullable)moxTextMarkerRangeForUnorderedTextMarkers:
+ (NSArray* _Nonnull)textMarkers;
+
+// AXLeftWordTextMarkerRangeForTextMarker
+- (id _Nullable)moxLeftWordTextMarkerRangeForTextMarker:(id _Nonnull)textMarker;
+
+// AXRightWordTextMarkerRangeForTextMarker
+- (id _Nullable)moxRightWordTextMarkerRangeForTextMarker:
+ (id _Nonnull)textMarker;
+
+// AXStartTextMarkerForTextMarkerRange
+- (id _Nullable)moxStartTextMarkerForTextMarkerRange:
+ (id _Nonnull)textMarkerRange;
+
+// AXEndTextMarkerForTextMarkerRange
+- (id _Nullable)moxEndTextMarkerForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXNextTextMarkerForTextMarker
+- (id _Nullable)moxNextTextMarkerForTextMarker:(id _Nonnull)textMarker;
+
+// AXPreviousTextMarkerForTextMarker
+- (id _Nullable)moxPreviousTextMarkerForTextMarker:(id _Nonnull)textMarker;
+
+// AXAttributedStringForTextMarkerRange
+- (NSAttributedString* _Nullable)moxAttributedStringForTextMarkerRange:
+ (id _Nonnull)textMarkerRange;
+
+// AXBoundsForTextMarkerRange
+- (NSValue* _Nullable)moxBoundsForTextMarkerRange:(id _Nonnull)textMarkerRange;
+
+// AXIndexForTextMarker
+- (NSNumber* _Nullable)moxIndexForTextMarker:(id _Nonnull)textMarker;
+
+// AXTextMarkerForIndex
+- (id _Nullable)moxTextMarkerForIndex:(NSNumber* _Nonnull)index;
+
+// AXUIElementForTextMarker
+- (id _Nullable)moxUIElementForTextMarker:(id _Nonnull)textMarker;
+
+// AXTextMarkerRangeForUIElement
+- (id _Nullable)moxTextMarkerRangeForUIElement:(id _Nonnull)element;
+
+// AXLineTextMarkerRangeForTextMarker
+- (id _Nullable)moxLineTextMarkerRangeForTextMarker:(id _Nonnull)textMarker;
+
+// AXLeftLineTextMarkerRangeForTextMarker
+- (id _Nullable)moxLeftLineTextMarkerRangeForTextMarker:(id _Nonnull)textMarker;
+
+// AXRightLineTextMarkerRangeForTextMarker
+- (id _Nullable)moxRightLineTextMarkerRangeForTextMarker:
+ (id _Nonnull)textMarker;
+
+// AXParagraphTextMarkerRangeForTextMarker
+- (id _Nullable)moxParagraphTextMarkerRangeForTextMarker:
+ (id _Nonnull)textMarker;
+
+// AXStyleTextMarkerRangeForTextMarker
+- (id _Nullable)moxStyleTextMarkerRangeForTextMarker:(id _Nonnull)textMarker;
+
+// AXMozDebugDescriptionForTextMarker
+- (NSString* _Nullable)moxMozDebugDescriptionForTextMarker:
+ (id _Nonnull)textMarker;
+
+// AXMozDebugDescriptionForTextMarkerRange
+- (NSString* _Nullable)moxMozDebugDescriptionForTextMarkerRange:
+ (id _Nonnull)textMarkerRange;
+
+#pragma mark - TextAttributeSetters
+
+// AXSelectedTextMarkerRange
+- (void)moxSetSelectedTextMarkerRange:(id _Nullable)textMarkerRange;
+
+@end
diff --git a/accessible/mac/MOXLandmarkAccessibles.h b/accessible/mac/MOXLandmarkAccessibles.h
new file mode 100644
index 0000000000..bea44e7a8f
--- /dev/null
+++ b/accessible/mac/MOXLandmarkAccessibles.h
@@ -0,0 +1,15 @@
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset:
+ * 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#import "mozAccessible.h"
+
+@interface MOXLandmarkAccessible : mozAccessible
+// overrides
+- (NSString*)moxTitle;
+
+@end
diff --git a/accessible/mac/MOXLandmarkAccessibles.mm b/accessible/mac/MOXLandmarkAccessibles.mm
new file mode 100644
index 0000000000..4a3aa8f597
--- /dev/null
+++ b/accessible/mac/MOXLandmarkAccessibles.mm
@@ -0,0 +1,15 @@
+/* -*- (Mode: Objective-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/. */
+
+#import "MOXLandmarkAccessibles.h"
+
+@implementation MOXLandmarkAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+@end
diff --git a/accessible/mac/MOXMathAccessibles.h b/accessible/mac/MOXMathAccessibles.h
new file mode 100644
index 0000000000..7661ad5c6a
--- /dev/null
+++ b/accessible/mac/MOXMathAccessibles.h
@@ -0,0 +1,64 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface MOXMathRootAccessible : mozAccessible
+
+// overrides
+- (id)moxMathRootRadicand;
+
+// overrides
+- (id)moxMathRootIndex;
+
+@end
+
+@interface MOXMathSquareRootAccessible : mozAccessible
+
+// overrides
+- (id)moxMathRootRadicand;
+
+@end
+
+@interface MOXMathFractionAccessible : mozAccessible
+
+// overrides
+- (id)moxMathFractionNumerator;
+
+// overrides
+- (id)moxMathFractionDenominator;
+
+// overrides
+- (NSNumber*)moxMathLineThickness;
+
+@end
+
+@interface MOXMathSubSupAccessible : mozAccessible
+
+// overrides
+- (id)moxMathBase;
+
+// overrides
+- (id)moxMathSubscript;
+
+// overrides
+- (id)moxMathSuperscript;
+
+@end
+
+@interface MOXMathUnderOverAccessible : mozAccessible
+
+// overrides
+- (id)moxMathBase;
+
+// overrides
+- (id)moxMathUnder;
+
+// overrides
+- (id)moxMathOver;
+
+@end
diff --git a/accessible/mac/MOXMathAccessibles.mm b/accessible/mac/MOXMathAccessibles.mm
new file mode 100644
index 0000000000..1296becd93
--- /dev/null
+++ b/accessible/mac/MOXMathAccessibles.mm
@@ -0,0 +1,116 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MOXMathAccessibles.h"
+
+#import "MacUtils.h"
+
+using namespace mozilla::a11y;
+
+// XXX WebKit also defines the following attributes.
+// See bugs 1176970 and 1176983.
+// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
+// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
+// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
+// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
+
+@implementation MOXMathRootAccessible
+
+- (id)moxMathRootRadicand {
+ return [self childAt:0];
+}
+
+- (id)moxMathRootIndex {
+ return [self childAt:1];
+}
+
+@end
+
+@implementation MOXMathSquareRootAccessible
+
+- (id)moxMathRootRadicand {
+ return [self childAt:0];
+}
+
+@end
+
+@implementation MOXMathFractionAccessible
+
+- (id)moxMathFractionNumerator {
+ return [self childAt:0];
+}
+
+- (id)moxMathFractionDenominator {
+ return [self childAt:1];
+}
+
+// Bug 1639745: This doesn't actually work.
+- (NSNumber*)moxMathLineThickness {
+ // WebKit sets line thickness to some logical value parsed in the
+ // renderer object of the <mfrac> element. It's not clear whether the
+ // exact value is relevant to assistive technologies. From a semantic
+ // point of view, the only important point is to distinguish between
+ // <mfrac> elements that have a fraction bar and those that do not.
+ // Per the MathML 3 spec, the latter happens iff the linethickness
+ // attribute is of the form [zero-float][optional-unit]. In that case we
+ // set line thickness to zero and in the other cases we set it to one.
+ if (NSString* thickness = utils::GetAccAttr(self, "thickness")) {
+ NSNumberFormatter* formatter =
+ [[[NSNumberFormatter alloc] init] autorelease];
+ NSNumber* value = [formatter numberFromString:thickness];
+ return [NSNumber numberWithBool:[value boolValue]];
+ } else {
+ return [NSNumber numberWithInteger:0];
+ }
+}
+
+@end
+
+@implementation MOXMathSubSupAccessible
+- (id)moxMathBase {
+ return [self childAt:0];
+}
+
+- (id)moxMathSubscript {
+ if (mRole == roles::MATHML_SUP) {
+ return nil;
+ }
+
+ return [self childAt:1];
+}
+
+- (id)moxMathSuperscript {
+ if (mRole == roles::MATHML_SUB) {
+ return nil;
+ }
+
+ return [self childAt:mRole == roles::MATHML_SUP ? 1 : 2];
+}
+
+@end
+
+@implementation MOXMathUnderOverAccessible
+- (id)moxMathBase {
+ return [self childAt:0];
+}
+
+- (id)moxMathUnder {
+ if (mRole == roles::MATHML_OVER) {
+ return nil;
+ }
+
+ return [self childAt:1];
+}
+
+- (id)moxMathOver {
+ if (mRole == roles::MATHML_UNDER) {
+ return nil;
+ }
+
+ return [self childAt:mRole == roles::MATHML_OVER ? 1 : 2];
+}
+@end
diff --git a/accessible/mac/MOXSearchInfo.h b/accessible/mac/MOXSearchInfo.h
new file mode 100644
index 0000000000..8f5e6f414d
--- /dev/null
+++ b/accessible/mac/MOXSearchInfo.h
@@ -0,0 +1,43 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+#include "Pivot.h"
+
+using namespace mozilla::a11y;
+
+@interface MOXSearchInfo : NSObject {
+ // The MOX accessible of the web area, we need a reference
+ // to set the pivot's root. This is a weak ref.
+ MOXAccessibleBase* mRoot;
+
+ // The MOX accessible we should start searching from.
+ // This is a weak ref.
+ MOXAccessibleBase* mStartElem;
+
+ // The amount of matches we should return
+ int mResultLimit;
+
+ // The array of search keys to use during this search
+ NSMutableArray* mSearchKeys;
+
+ // Set to YES if we should search forward, NO if backward
+ BOOL mSearchForward;
+
+ // Set to YES if we should match on immediate descendants only, NO otherwise
+ BOOL mImmediateDescendantsOnly;
+
+ NSString* mSearchText;
+}
+
+- (id)initWithParameters:(NSDictionary*)params andRoot:(MOXAccessibleBase*)root;
+
+- (NSArray*)performSearch;
+
+- (void)dealloc;
+
+@end
diff --git a/accessible/mac/MOXSearchInfo.mm b/accessible/mac/MOXSearchInfo.mm
new file mode 100644
index 0000000000..c2cb07fced
--- /dev/null
+++ b/accessible/mac/MOXSearchInfo.mm
@@ -0,0 +1,451 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MOXSearchInfo.h"
+#import "MOXWebAreaAccessible.h"
+#import "RotorRules.h"
+
+#include "nsCocoaUtils.h"
+#include "DocAccessibleParent.h"
+
+using namespace mozilla::a11y;
+
+@interface MOXSearchInfo ()
+- (NSArray*)getMatchesForRule:(PivotRule&)rule;
+
+- (NSArray<mozAccessible*>*)applyPostFilter:(NSArray<mozAccessible*>*)matches;
+
+- (AccessibleOrProxy)rootGeckoAccessible;
+
+- (AccessibleOrProxy)startGeckoAccessible;
+
+- (BOOL)shouldApplyPostFilter;
+@end
+
+@implementation MOXSearchInfo
+
+- (id)initWithParameters:(NSDictionary*)params
+ andRoot:(MOXAccessibleBase*)root {
+ if (id searchKeyParam = [params objectForKey:@"AXSearchKey"]) {
+ mSearchKeys = [searchKeyParam isKindOfClass:[NSString class]]
+ ? @[ searchKeyParam ]
+ : searchKeyParam;
+ }
+
+ if (id startElemParam = [params objectForKey:@"AXStartElement"]) {
+ mStartElem = startElemParam;
+ } else {
+ mStartElem = root;
+ }
+
+ mRoot = root;
+
+ mResultLimit = [[params objectForKey:@"AXResultsLimit"] intValue];
+
+ mSearchForward =
+ [[params objectForKey:@"AXDirection"] isEqualToString:@"AXDirectionNext"];
+
+ mImmediateDescendantsOnly =
+ [[params objectForKey:@"AXImmediateDescendantsOnly"] boolValue];
+
+ mSearchText = [params objectForKey:@"AXSearchText"];
+
+ return [super init];
+}
+
+- (AccessibleOrProxy)rootGeckoAccessible {
+ id root =
+ [mRoot isKindOfClass:[mozAccessible class]] ? mRoot : [mRoot moxParent];
+
+ return [static_cast<mozAccessible*>(root) geckoAccessible];
+}
+
+- (AccessibleOrProxy)startGeckoAccessible {
+ if ([mStartElem isKindOfClass:[mozAccessible class]]) {
+ return [static_cast<mozAccessible*>(mStartElem) geckoAccessible];
+ }
+
+ // If it isn't a mozAccessible, it doesn't have a gecko accessible
+ // this is most likely the root group. Use the gecko doc as the start
+ // accessible.
+ return [self rootGeckoAccessible];
+}
+
+- (NSArray*)getMatchesForRule:(PivotRule&)rule {
+ // If we will apply a post-filter, don't limit search so we
+ // don't come up short on the final result count.
+ int resultLimit = [self shouldApplyPostFilter] ? -1 : mResultLimit;
+
+ NSMutableArray<mozAccessible*>* matches = [[NSMutableArray alloc] init];
+ AccessibleOrProxy geckoRootAcc = [self rootGeckoAccessible];
+ AccessibleOrProxy geckoStartAcc = [self startGeckoAccessible];
+ Pivot p = Pivot(geckoRootAcc);
+ AccessibleOrProxy match;
+ if (mSearchForward) {
+ match = p.Next(geckoStartAcc, rule);
+ } else {
+ // Search backwards
+ if (geckoRootAcc == geckoStartAcc) {
+ // If we have no explicit start accessible, start from the last match.
+ match = p.Last(rule);
+ } else {
+ match = p.Prev(geckoStartAcc, rule);
+ }
+ }
+
+ while (!match.IsNull() && resultLimit != 0) {
+ if (!mSearchForward && match == geckoRootAcc) {
+ // If searching backwards, don't include root.
+ break;
+ }
+
+ // we use mResultLimit != 0 to capture the case where mResultLimit is -1
+ // when it is set from the params dictionary. If that's true, we want
+ // to return all matches (ie. have no limit)
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(match);
+ if (nativeMatch) {
+ // only add/count results for which there is a matching
+ // native accessible
+ [matches addObject:nativeMatch];
+ resultLimit -= 1;
+ }
+
+ match = mSearchForward ? p.Next(match, rule) : p.Prev(match, rule);
+ }
+
+ return [self applyPostFilter:matches];
+}
+
+- (BOOL)shouldApplyPostFilter {
+ // We currently only support AXSearchText as a post-search filter.
+ return !!mSearchText;
+}
+
+- (NSArray<mozAccessible*>*)applyPostFilter:(NSArray<mozAccessible*>*)matches {
+ if (![self shouldApplyPostFilter]) {
+ return matches;
+ }
+
+ NSMutableArray<mozAccessible*>* postMatches = [[NSMutableArray alloc] init];
+
+ nsString searchText;
+ nsCocoaUtils::GetStringForNSString(mSearchText, searchText);
+
+ __block DocAccessibleParent* ipcDoc = nullptr;
+ __block nsTArray<uint64_t> accIds;
+
+ [matches enumerateObjectsUsingBlock:^(mozAccessible* match, NSUInteger idx,
+ BOOL* stop) {
+ AccessibleOrProxy geckoAcc = [match geckoAccessible];
+ if (geckoAcc.IsNull()) {
+ return;
+ }
+
+ switch (geckoAcc.Role()) {
+ case roles::LANDMARK:
+ case roles::COMBOBOX:
+ case roles::LISTITEM:
+ case roles::COMBOBOX_LIST:
+ case roles::MENUBAR:
+ case roles::MENUPOPUP:
+ case roles::DOCUMENT:
+ case roles::APPLICATION:
+ // XXX: These roles either have AXTitle/AXDescription overridden as
+ // empty, or should never be returned in search text results. This
+ // should be integrated into a pivot rule in the future, and possibly
+ // better mapped somewhere.
+ return;
+ default:
+ break;
+ }
+
+ if (geckoAcc.IsAccessible()) {
+ AccessibleWrap* acc =
+ static_cast<AccessibleWrap*>(geckoAcc.AsAccessible());
+ if (acc->ApplyPostFilter(EWhichPostFilter::eContainsText, searchText)) {
+ if (mozAccessible* nativePostMatch =
+ GetNativeFromGeckoAccessible(acc)) {
+ [postMatches addObject:nativePostMatch];
+ if (mResultLimit > 0 &&
+ [postMatches count] >= static_cast<NSUInteger>(mResultLimit)) {
+ // If we reached the result limit, alter the `stop` pointer to YES
+ // to stop iteration.
+ *stop = YES;
+ }
+ }
+ }
+
+ return;
+ }
+
+ ProxyAccessible* proxy = geckoAcc.AsProxy();
+ if (ipcDoc &&
+ ((ipcDoc != proxy->Document()) || (idx + 1 == [matches count]))) {
+ // If the ipcDoc doesn't match the current proxy's doc, we crossed into a
+ // new document. ..or this is the last match. Apply the filter on the list
+ // of the current ipcDoc.
+ nsTArray<uint64_t> matchIds;
+ Unused << ipcDoc->GetPlatformExtension()->SendApplyPostSearchFilter(
+ accIds, mResultLimit, EWhichPostFilter::eContainsText, searchText,
+ &matchIds);
+ for (size_t i = 0; i < matchIds.Length(); i++) {
+ if (ProxyAccessible* postMatch =
+ ipcDoc->GetAccessible(matchIds.ElementAt(i))) {
+ if (mozAccessible* nativePostMatch =
+ GetNativeFromGeckoAccessible(postMatch)) {
+ [postMatches addObject:nativePostMatch];
+ if (mResultLimit > 0 &&
+ [postMatches count] >= static_cast<NSUInteger>(mResultLimit)) {
+ // If we reached the result limit, alter the `stop` pointer to YES
+ // to stop iteration.
+ *stop = YES;
+ return;
+ }
+ }
+ }
+ }
+
+ ipcDoc = nullptr;
+ accIds.Clear();
+ }
+
+ if (!ipcDoc) {
+ ipcDoc = proxy->Document();
+ }
+ accIds.AppendElement(proxy->ID());
+ }];
+
+ return postMatches;
+}
+
+- (NSArray*)performSearch {
+ AccessibleOrProxy geckoRootAcc = [self rootGeckoAccessible];
+ AccessibleOrProxy geckoStartAcc = [self startGeckoAccessible];
+ NSMutableArray* matches = [[NSMutableArray alloc] init];
+ for (id key in mSearchKeys) {
+ if ([key isEqualToString:@"AXAnyTypeSearchKey"]) {
+ RotorRule rule =
+ mImmediateDescendantsOnly ? RotorRule(geckoRootAcc) : RotorRule();
+
+ if ([mStartElem isKindOfClass:[MOXWebAreaAccessible class]]) {
+ if (id rootGroup =
+ [static_cast<MOXWebAreaAccessible*>(mStartElem) rootGroup]) {
+ // Moving forward from web area, rootgroup; if it exists, is next.
+ [matches addObject:rootGroup];
+ if (mResultLimit == 1) {
+ // Found one match, continue in search keys for block.
+ continue;
+ }
+ }
+ }
+
+ if (mImmediateDescendantsOnly && mStartElem != mRoot &&
+ [mStartElem isKindOfClass:[MOXRootGroup class]]) {
+ // Moving forward from root group. If we don't match descendants,
+ // there is no match. Continue.
+ continue;
+ }
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::HEADING, geckoRootAcc)
+ : RotorRoleRule(roles::HEADING);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXArticleSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::ARTICLE, geckoRootAcc)
+ : RotorRoleRule(roles::ARTICLE);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXTableSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::TABLE, geckoRootAcc)
+ : RotorRoleRule(roles::TABLE);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXLandmarkSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::LANDMARK, geckoRootAcc)
+ : RotorRoleRule(roles::LANDMARK);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXListSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::LIST, geckoRootAcc)
+ : RotorRoleRule(roles::LIST);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXLinkSearchKey"]) {
+ RotorLinkRule rule = mImmediateDescendantsOnly
+ ? RotorLinkRule(geckoRootAcc)
+ : RotorLinkRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXVisitedLinkSearchKey"]) {
+ RotorVisitedLinkRule rule = mImmediateDescendantsOnly
+ ? RotorVisitedLinkRule(geckoRootAcc)
+ : RotorVisitedLinkRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXUnvisitedLinkSearchKey"]) {
+ RotorUnvisitedLinkRule rule = mImmediateDescendantsOnly
+ ? RotorUnvisitedLinkRule(geckoRootAcc)
+ : RotorUnvisitedLinkRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXButtonSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::PUSHBUTTON, geckoRootAcc)
+ : RotorRoleRule(roles::PUSHBUTTON);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXControlSearchKey"]) {
+ RotorControlRule rule = mImmediateDescendantsOnly
+ ? RotorControlRule(geckoRootAcc)
+ : RotorControlRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXSameTypeSearchKey"]) {
+ mozAccessible* native = GetNativeFromGeckoAccessible(geckoStartAcc);
+ NSString* macRole = [native moxRole];
+ RotorMacRoleRule rule = mImmediateDescendantsOnly
+ ? RotorMacRoleRule(macRole, geckoRootAcc)
+ : RotorMacRoleRule(macRole);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXDifferentTypeSearchKey"]) {
+ mozAccessible* native = GetNativeFromGeckoAccessible(geckoStartAcc);
+ NSString* macRole = [native moxRole];
+ RotorNotMacRoleRule rule =
+ mImmediateDescendantsOnly ? RotorNotMacRoleRule(macRole, geckoRootAcc)
+ : RotorNotMacRoleRule(macRole);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXRadioGroupSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::RADIO_GROUP, geckoRootAcc)
+ : RotorRoleRule(roles::RADIO_GROUP);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXFrameSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::DOCUMENT, geckoRootAcc)
+ : RotorRoleRule(roles::DOCUMENT);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXImageSearchKey"] ||
+ [key isEqualToString:@"AXGraphicSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::GRAPHIC, geckoRootAcc)
+ : RotorRoleRule(roles::GRAPHIC);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXCheckboxSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::CHECKBUTTON, geckoRootAcc)
+ : RotorRoleRule(roles::CHECKBUTTON);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXStaticTextSearchKey"]) {
+ RotorStaticTextRule rule = mImmediateDescendantsOnly
+ ? RotorStaticTextRule(geckoRootAcc)
+ : RotorStaticTextRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel1SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(1, geckoRootAcc)
+ : RotorHeadingLevelRule(1);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel2SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(2, geckoRootAcc)
+ : RotorHeadingLevelRule(2);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel3SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(3, geckoRootAcc)
+ : RotorHeadingLevelRule(3);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel4SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(4, geckoRootAcc)
+ : RotorHeadingLevelRule(4);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel5SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(5, geckoRootAcc)
+ : RotorHeadingLevelRule(5);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXHeadingLevel6SearchKey"]) {
+ RotorHeadingLevelRule rule = mImmediateDescendantsOnly
+ ? RotorHeadingLevelRule(6, geckoRootAcc)
+ : RotorHeadingLevelRule(6);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXBlockquoteSearchKey"]) {
+ RotorRoleRule rule = mImmediateDescendantsOnly
+ ? RotorRoleRule(roles::BLOCKQUOTE, geckoRootAcc)
+ : RotorRoleRule(roles::BLOCKQUOTE);
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXTextFieldSearchKey"]) {
+ RotorTextEntryRule rule = mImmediateDescendantsOnly
+ ? RotorTextEntryRule(geckoRootAcc)
+ : RotorTextEntryRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+
+ if ([key isEqualToString:@"AXLiveRegionSearchKey"]) {
+ RotorLiveRegionRule rule = mImmediateDescendantsOnly
+ ? RotorLiveRegionRule(geckoRootAcc)
+ : RotorLiveRegionRule();
+ [matches addObjectsFromArray:[self getMatchesForRule:rule]];
+ }
+ }
+
+ return matches;
+}
+
+- (void)dealloc {
+ [mSearchKeys release];
+ [super dealloc];
+}
+
+@end
diff --git a/accessible/mac/MOXTextMarkerDelegate.h b/accessible/mac/MOXTextMarkerDelegate.h
new file mode 100644
index 0000000000..cd5c78cb9a
--- /dev/null
+++ b/accessible/mac/MOXTextMarkerDelegate.h
@@ -0,0 +1,158 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "MOXAccessibleProtocol.h"
+#import "GeckoTextMarker.h"
+
+#include "AccessibleOrProxy.h"
+
+@interface MOXTextMarkerDelegate : NSObject <MOXTextMarkerSupport> {
+ mozilla::a11y::AccessibleOrProxy mGeckoDocAccessible;
+ id mSelection;
+ id mCaret;
+ id mPrevCaret;
+}
+
++ (id)getOrCreateForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
++ (void)destroyForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
+- (id)initWithDoc:(mozilla::a11y::AccessibleOrProxy)aDoc;
+
+- (void)dealloc;
+
+- (void)setSelectionFrom:(mozilla::a11y::AccessibleOrProxy)startContainer
+ at:(int32_t)startOffset
+ to:(mozilla::a11y::AccessibleOrProxy)endContainer
+ at:(int32_t)endOffset;
+
+- (void)setCaretOffset:(mozilla::a11y::AccessibleOrProxy)container
+ at:(int32_t)offset;
+
+- (NSDictionary*)selectionChangeInfo;
+
+- (void)invalidateSelection;
+
+- (mozilla::a11y::GeckoTextMarkerRange)selection;
+
+// override
+- (id)moxStartTextMarker;
+
+// override
+- (id)moxEndTextMarker;
+
+// override
+- (id)moxSelectedTextMarkerRange;
+
+// override
+- (NSNumber*)moxLengthForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (NSString*)moxStringForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (id)moxTextMarkerRangeForUnorderedTextMarkers:(NSArray*)textMarkers;
+
+// override
+- (id)moxStartTextMarkerForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (id)moxEndTextMarkerForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (id)moxLeftWordTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxRightWordTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxLineTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxLeftLineTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxRightLineTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxParagraphTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxStyleTextMarkerRangeForTextMarker:(id)textMarker;
+
+// override
+- (id)moxNextTextMarkerForTextMarker:(id)textMarker;
+
+// override
+- (id)moxPreviousTextMarkerForTextMarker:(id)textMarker;
+
+// override
+- (NSAttributedString*)moxAttributedStringForTextMarkerRange:
+ (id)textMarkerRange;
+
+// override
+- (NSValue*)moxBoundsForTextMarkerRange:(id)textMarkerRange;
+
+// override
+- (id)moxUIElementForTextMarker:(id)textMarker;
+
+// override
+- (id)moxTextMarkerRangeForUIElement:(id)element;
+
+// override
+- (NSString*)moxMozDebugDescriptionForTextMarker:(id)textMarker;
+
+// override
+- (void)moxSetSelectedTextMarkerRange:(id)textMarkerRange;
+
+@end
+
+namespace mozilla {
+namespace a11y {
+
+enum AXTextEditType {
+ AXTextEditTypeUnknown,
+ AXTextEditTypeDelete,
+ AXTextEditTypeInsert,
+ AXTextEditTypeTyping,
+ AXTextEditTypeDictation,
+ AXTextEditTypeCut,
+ AXTextEditTypePaste,
+ AXTextEditTypeAttributesChange
+};
+
+enum AXTextStateChangeType {
+ AXTextStateChangeTypeUnknown,
+ AXTextStateChangeTypeEdit,
+ AXTextStateChangeTypeSelectionMove,
+ AXTextStateChangeTypeSelectionExtend
+};
+
+enum AXTextSelectionDirection {
+ AXTextSelectionDirectionUnknown,
+ AXTextSelectionDirectionBeginning,
+ AXTextSelectionDirectionEnd,
+ AXTextSelectionDirectionPrevious,
+ AXTextSelectionDirectionNext,
+ AXTextSelectionDirectionDiscontiguous
+};
+
+enum AXTextSelectionGranularity {
+ AXTextSelectionGranularityUnknown,
+ AXTextSelectionGranularityCharacter,
+ AXTextSelectionGranularityWord,
+ AXTextSelectionGranularityLine,
+ AXTextSelectionGranularitySentence,
+ AXTextSelectionGranularityParagraph,
+ AXTextSelectionGranularityPage,
+ AXTextSelectionGranularityDocument,
+ AXTextSelectionGranularityAll
+};
+}
+}
diff --git a/accessible/mac/MOXTextMarkerDelegate.mm b/accessible/mac/MOXTextMarkerDelegate.mm
new file mode 100644
index 0000000000..2aea7232b2
--- /dev/null
+++ b/accessible/mac/MOXTextMarkerDelegate.mm
@@ -0,0 +1,440 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#include "mozilla/Preferences.h"
+
+#import "MOXTextMarkerDelegate.h"
+
+using namespace mozilla::a11y;
+
+#define PREF_ACCESSIBILITY_MAC_DEBUG "accessibility.mac.debug"
+
+static nsDataHashtable<nsUint64HashKey, MOXTextMarkerDelegate*> sDelegates;
+
+@implementation MOXTextMarkerDelegate
+
++ (id)getOrCreateForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc {
+ MOZ_ASSERT(!aDoc.IsNull());
+
+ MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc.Bits());
+ if (!delegate) {
+ delegate = [[MOXTextMarkerDelegate alloc] initWithDoc:aDoc];
+ sDelegates.Put(aDoc.Bits(), delegate);
+ [delegate retain];
+ }
+
+ return delegate;
+}
+
++ (void)destroyForDoc:(mozilla::a11y::AccessibleOrProxy)aDoc {
+ MOZ_ASSERT(!aDoc.IsNull());
+
+ MOXTextMarkerDelegate* delegate = sDelegates.Get(aDoc.Bits());
+ if (delegate) {
+ sDelegates.Remove(aDoc.Bits());
+ [delegate release];
+ }
+}
+
+- (id)initWithDoc:(AccessibleOrProxy)aDoc {
+ MOZ_ASSERT(!aDoc.IsNull(), "Cannot init MOXTextDelegate with null");
+ if ((self = [super init])) {
+ mGeckoDocAccessible = aDoc;
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self invalidateSelection];
+ [super dealloc];
+}
+
+- (void)setSelectionFrom:(AccessibleOrProxy)startContainer
+ at:(int32_t)startOffset
+ to:(AccessibleOrProxy)endContainer
+ at:(int32_t)endOffset {
+ GeckoTextMarkerRange selection(GeckoTextMarker(startContainer, startOffset),
+ GeckoTextMarker(endContainer, endOffset));
+
+ // We store it as an AXTextMarkerRange because it is a safe
+ // way to keep a weak reference - when we need to use the
+ // range we can convert it back to a GeckoTextMarkerRange
+ // and check that it's valid.
+ mSelection = [selection.CreateAXTextMarkerRange() retain];
+}
+
+- (void)setCaretOffset:(mozilla::a11y::AccessibleOrProxy)container
+ at:(int32_t)offset {
+ GeckoTextMarker caretMarker(container, offset);
+
+ mPrevCaret = mCaret;
+ mCaret = [caretMarker.CreateAXTextMarker() retain];
+}
+
+// This returns an info object to pass with AX SelectedTextChanged events.
+// It uses the current and previous caret position to make decisions
+// regarding which attributes to add to the info object.
+- (NSDictionary*)selectionChangeInfo {
+ GeckoTextMarkerRange selectedGeckoRange =
+ GeckoTextMarkerRange(mGeckoDocAccessible, mSelection);
+ int32_t stateChangeType = selectedGeckoRange.mStart == selectedGeckoRange.mEnd
+ ? AXTextStateChangeTypeSelectionMove
+ : AXTextStateChangeTypeSelectionExtend;
+
+ // This is the base info object, includes the selected marker range and
+ // the change type depending on the collapsed state of the selection.
+ NSMutableDictionary* info = [@{
+ @"AXSelectedTextMarkerRange" : selectedGeckoRange.IsValid() ? mSelection
+ : [NSNull null],
+ @"AXTextStateChangeType" : @(stateChangeType),
+ } mutableCopy];
+
+ GeckoTextMarker caretMarker(mGeckoDocAccessible, mCaret);
+ GeckoTextMarker prevCaretMarker(mGeckoDocAccessible, mPrevCaret);
+ if (!caretMarker.IsValid()) {
+ // If the current caret is invalid, stop here and return base info.
+ return info;
+ }
+
+ mozAccessible* caretEditable =
+ [GetNativeFromGeckoAccessible(caretMarker.mContainer)
+ moxEditableAncestor];
+
+ if (!caretEditable && stateChangeType == AXTextStateChangeTypeSelectionMove) {
+ // If we are not in an editable, VO expects AXTextStateSync to be present
+ // and true.
+ info[@"AXTextStateSync"] = @YES;
+ }
+
+ if (!prevCaretMarker.IsValid() || caretMarker == prevCaretMarker) {
+ // If we have no stored previous marker, stop here.
+ return info;
+ }
+
+ mozAccessible* prevCaretEditable =
+ [GetNativeFromGeckoAccessible(prevCaretMarker.mContainer)
+ moxEditableAncestor];
+
+ if (prevCaretEditable != caretEditable) {
+ // If the caret goes in or out of an editable, consider the
+ // move direction "discontiguous".
+ info[@"AXTextSelectionDirection"] =
+ @(AXTextSelectionDirectionDiscontiguous);
+ if ([[caretEditable moxFocused] boolValue]) {
+ // If the caret is in a new focused editable, VO expects this attribute to
+ // be present and to be true.
+ info[@"AXTextSelectionChangedFocus"] = @YES;
+ }
+
+ return info;
+ }
+
+ bool isForward = prevCaretMarker < caretMarker;
+ uint32_t deltaLength =
+ GeckoTextMarkerRange(isForward ? prevCaretMarker : caretMarker,
+ isForward ? caretMarker : prevCaretMarker)
+ .Length();
+
+ // Determine selection direction with marker comparison.
+ // If the delta between the two markers is more than one, consider it
+ // a word. Not accurate, but good enough for VO.
+ [info addEntriesFromDictionary:@{
+ @"AXTextSelectionDirection" : isForward
+ ? @(AXTextSelectionDirectionNext)
+ : @(AXTextSelectionDirectionPrevious),
+ @"AXTextSelectionGranularity" : deltaLength == 1
+ ? @(AXTextSelectionGranularityCharacter)
+ : @(AXTextSelectionGranularityWord)
+ }];
+
+ return info;
+}
+
+- (void)invalidateSelection {
+ [mSelection release];
+ [mCaret release];
+ [mPrevCaret release];
+ mSelection = nil;
+}
+
+- (mozilla::a11y::GeckoTextMarkerRange)selection {
+ return mozilla::a11y::GeckoTextMarkerRange(mGeckoDocAccessible, mSelection);
+}
+
+- (id)moxStartTextMarker {
+ GeckoTextMarker geckoTextPoint(mGeckoDocAccessible, 0);
+ return geckoTextPoint.CreateAXTextMarker();
+}
+
+- (id)moxEndTextMarker {
+ uint32_t characterCount =
+ mGeckoDocAccessible.IsProxy()
+ ? mGeckoDocAccessible.AsProxy()->CharacterCount()
+ : mGeckoDocAccessible.AsAccessible()
+ ->Document()
+ ->AsHyperText()
+ ->CharacterCount();
+ GeckoTextMarker geckoTextPoint(mGeckoDocAccessible, characterCount);
+ return geckoTextPoint.CreateAXTextMarker();
+}
+
+- (id)moxSelectedTextMarkerRange {
+ return mSelection &&
+ GeckoTextMarkerRange(mGeckoDocAccessible, mSelection).IsValid()
+ ? mSelection
+ : nil;
+}
+
+- (NSString*)moxStringForTextMarkerRange:(id)textMarkerRange {
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+ if (!range.IsValid()) {
+ return @"";
+ }
+
+ return range.Text();
+}
+
+- (NSNumber*)moxLengthForTextMarkerRange:(id)textMarkerRange {
+ return @([[self moxStringForTextMarkerRange:textMarkerRange] length]);
+}
+
+- (id)moxTextMarkerRangeForUnorderedTextMarkers:(NSArray*)textMarkers {
+ if ([textMarkers count] != 2) {
+ // Don't allow anything but a two member array.
+ return nil;
+ }
+
+ GeckoTextMarker p1(mGeckoDocAccessible, textMarkers[0]);
+ GeckoTextMarker p2(mGeckoDocAccessible, textMarkers[1]);
+
+ if (!p1.IsValid() || !p2.IsValid()) {
+ // If either marker is invalid, return nil.
+ return nil;
+ }
+
+ bool ordered = p1 < p2;
+ GeckoTextMarkerRange range(ordered ? p1 : p2, ordered ? p2 : p1);
+
+ return range.CreateAXTextMarkerRange();
+}
+
+- (id)moxStartTextMarkerForTextMarkerRange:(id)textMarkerRange {
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+
+ return range.IsValid() ? range.mStart.CreateAXTextMarker() : nil;
+}
+
+- (id)moxEndTextMarkerForTextMarkerRange:(id)textMarkerRange {
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+
+ return range.IsValid() ? range.mEnd.CreateAXTextMarker() : nil;
+}
+
+- (id)moxLeftWordTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eLeftWord)
+ .CreateAXTextMarkerRange();
+}
+
+- (id)moxRightWordTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eRightWord)
+ .CreateAXTextMarkerRange();
+}
+
+- (id)moxLineTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eLine).CreateAXTextMarkerRange();
+}
+
+- (id)moxLeftLineTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eLeftLine)
+ .CreateAXTextMarkerRange();
+}
+
+- (id)moxRightLineTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eRightLine)
+ .CreateAXTextMarkerRange();
+}
+
+- (id)moxParagraphTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eParagraph)
+ .CreateAXTextMarkerRange();
+}
+
+// override
+- (id)moxStyleTextMarkerRangeForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.Range(EWhichRange::eStyle).CreateAXTextMarkerRange();
+}
+
+- (id)moxNextTextMarkerForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ if (!geckoTextMarker.Next()) {
+ return nil;
+ }
+
+ return geckoTextMarker.CreateAXTextMarker();
+}
+
+- (id)moxPreviousTextMarkerForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ if (!geckoTextMarker.Previous()) {
+ return nil;
+ }
+
+ return geckoTextMarker.CreateAXTextMarker();
+}
+
+- (NSAttributedString*)moxAttributedStringForTextMarkerRange:
+ (id)textMarkerRange {
+ return [[[NSAttributedString alloc]
+ initWithString:[self moxStringForTextMarkerRange:textMarkerRange]]
+ autorelease];
+}
+
+- (NSValue*)moxBoundsForTextMarkerRange:(id)textMarkerRange {
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+ if (!range.IsValid()) {
+ return nil;
+ }
+
+ return range.Bounds();
+}
+
+- (NSNumber*)moxIndexForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ GeckoTextMarkerRange range(GeckoTextMarker(mGeckoDocAccessible, 0),
+ geckoTextMarker);
+
+ return @(range.Length());
+}
+
+- (id)moxTextMarkerForIndex:(NSNumber*)index {
+ GeckoTextMarker geckoTextMarker = GeckoTextMarker::MarkerFromIndex(
+ mGeckoDocAccessible, [index integerValue]);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ return geckoTextMarker.CreateAXTextMarker();
+}
+
+- (id)moxUIElementForTextMarker:(id)textMarker {
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return nil;
+ }
+
+ AccessibleOrProxy leaf = geckoTextMarker.Leaf();
+ if (leaf.IsNull()) {
+ return nil;
+ }
+
+ return GetNativeFromGeckoAccessible(leaf);
+}
+
+- (id)moxTextMarkerRangeForUIElement:(id)element {
+ if (![element isKindOfClass:[mozAccessible class]]) {
+ return nil;
+ }
+
+ GeckoTextMarkerRange range([element geckoAccessible]);
+ return range.CreateAXTextMarkerRange();
+}
+
+- (NSString*)moxMozDebugDescriptionForTextMarker:(id)textMarker {
+ if (!Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
+ return nil;
+ }
+
+ GeckoTextMarker geckoTextMarker(mGeckoDocAccessible, textMarker);
+ if (!geckoTextMarker.IsValid()) {
+ return @"<GeckoTextMarker 0x0 [0]>";
+ }
+
+ return [NSString stringWithFormat:@"<GeckoTextMarker 0x%lx [%d]>",
+ geckoTextMarker.mContainer.Bits(),
+ geckoTextMarker.mOffset];
+}
+
+- (NSString*)moxMozDebugDescriptionForTextMarkerRange:(id)textMarkerRange {
+ if (!Preferences::GetBool(PREF_ACCESSIBILITY_MAC_DEBUG)) {
+ return nil;
+ }
+
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+ if (!range.IsValid()) {
+ return @"<GeckoTextMarkerRange 0x0 [0] - 0x0 [0]>";
+ }
+
+ return [NSString
+ stringWithFormat:@"<GeckoTextMarkerRange 0x%lx [%d] - 0x%lx [%d]>",
+ range.mStart.mContainer.Bits(), range.mStart.mOffset,
+ range.mEnd.mContainer.Bits(), range.mEnd.mOffset];
+}
+
+- (void)moxSetSelectedTextMarkerRange:(id)textMarkerRange {
+ mozilla::a11y::GeckoTextMarkerRange range(mGeckoDocAccessible,
+ textMarkerRange);
+ if (range.IsValid()) {
+ range.Select();
+ }
+}
+
+@end
diff --git a/accessible/mac/MOXWebAreaAccessible.h b/accessible/mac/MOXWebAreaAccessible.h
new file mode 100644
index 0000000000..1ef11af50c
--- /dev/null
+++ b/accessible/mac/MOXWebAreaAccessible.h
@@ -0,0 +1,105 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+using namespace mozilla::a11y;
+
+@class MOXRootGroup;
+
+@interface MOXWebAreaAccessible : mozAccessible {
+ MOXRootGroup* mRootGroup;
+}
+// overrides
+- (NSString*)moxRole;
+
+// overrides
+- (NSString*)moxRoleDescription;
+
+// overrides
+- (NSURL*)moxURL;
+
+// override
+- (NSNumber*)moxLoaded;
+
+// override
+- (NSNumber*)moxLoadingProgress;
+
+// override
+- (NSArray*)moxLinkUIElements;
+
+// override
+- (NSArray*)moxUnignoredChildren;
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (void)moxPostNotification:(NSString*)notification;
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+// override
+- (void)dealloc;
+
+- (NSArray*)rootGroupChildren;
+
+- (id)rootGroup;
+
+@end
+
+@interface MOXRootGroup : MOXAccessibleBase {
+ MOXWebAreaAccessible* mParent;
+}
+
+// override
+- (id)initWithParent:(MOXWebAreaAccessible*)parent;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSString*)moxRoleDescription;
+
+// override
+- (id<mozAccessible>)moxParent;
+
+// override
+- (NSArray*)moxChildren;
+
+// override
+- (NSString*)moxIdentifier;
+
+// override
+- (NSString*)moxSubrole;
+
+// override
+- (id)moxHitTest:(NSPoint)point;
+
+// override
+- (NSValue*)moxPosition;
+
+// override
+- (NSValue*)moxSize;
+
+// override
+- (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate;
+
+// override
+- (NSNumber*)moxUIElementCountForSearchPredicate:(NSDictionary*)searchPredicate;
+
+// override
+- (BOOL)disableChild:(id)child;
+
+// override
+- (void)expire;
+
+// override
+- (BOOL)isExpired;
+
+@end
diff --git a/accessible/mac/MOXWebAreaAccessible.mm b/accessible/mac/MOXWebAreaAccessible.mm
new file mode 100644
index 0000000000..b91732325c
--- /dev/null
+++ b/accessible/mac/MOXWebAreaAccessible.mm
@@ -0,0 +1,275 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MOXWebAreaAccessible.h"
+
+#import "MOXSearchInfo.h"
+
+#include "nsCocoaUtils.h"
+#include "DocAccessibleParent.h"
+
+using namespace mozilla::a11y;
+
+@implementation MOXRootGroup
+
+- (id)initWithParent:(MOXWebAreaAccessible*)parent {
+ // The parent is always a MOXWebAreaAccessible
+ mParent = parent;
+ return [super init];
+}
+
+- (NSString*)moxRole {
+ return NSAccessibilityGroupRole;
+}
+
+- (NSString*)moxRoleDescription {
+ if ([[self moxSubrole] isEqualToString:@"AXLandmarkApplication"]) {
+ return utils::LocalizedString(u"application"_ns);
+ }
+
+ return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
+}
+
+- (id<mozAccessible>)moxParent {
+ return mParent;
+}
+
+- (NSArray*)moxChildren {
+ // Reparent the children of the web area here.
+ return [mParent rootGroupChildren];
+}
+
+- (NSString*)moxIdentifier {
+ // This is mostly for testing purposes to assert that this is the generated
+ // root group.
+ return @"root-group";
+}
+
+- (NSString*)moxSubrole {
+ // Steal the subrole internally mapped to the web area.
+ return [mParent moxSubrole];
+}
+
+- (id)moxHitTest:(NSPoint)point {
+ return [mParent moxHitTest:point];
+}
+
+- (NSValue*)moxPosition {
+ return [mParent moxPosition];
+}
+
+- (NSValue*)moxSize {
+ return [mParent moxSize];
+}
+
+- (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
+ MOXSearchInfo* search =
+ [[MOXSearchInfo alloc] initWithParameters:searchPredicate andRoot:self];
+
+ return [search performSearch];
+}
+
+- (NSNumber*)moxUIElementCountForSearchPredicate:
+ (NSDictionary*)searchPredicate {
+ return [NSNumber
+ numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
+ count]];
+}
+
+- (BOOL)disableChild:(id)child {
+ return NO;
+}
+
+- (void)expire {
+ mParent = nil;
+ [super expire];
+}
+
+- (BOOL)isExpired {
+ MOZ_ASSERT((mParent == nil) == mIsExpired);
+
+ return [super isExpired];
+}
+
+@end
+
+@implementation MOXWebAreaAccessible
+
+- (NSString*)moxRole {
+ // The OS role is AXWebArea regardless of the gecko role
+ // (APPLICATION or DOCUMENT).
+ // If the web area has a role of APPLICATION, its root group will
+ // reflect that in a subrole/description.
+ return @"AXWebArea";
+}
+
+- (NSString*)moxRoleDescription {
+ // The role description is "HTML Content" regardless of the gecko role
+ // (APPLICATION or DOCUMENT)
+ return utils::LocalizedString(u"htmlContent"_ns);
+}
+
+- (NSURL*)moxURL {
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ nsAutoString url;
+ if (mGeckoAccessible.IsAccessible()) {
+ MOZ_ASSERT(mGeckoAccessible.AsAccessible()->IsDoc());
+ DocAccessible* acc = mGeckoAccessible.AsAccessible()->AsDoc();
+ acc->URL(url);
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ proxy->URL(url);
+ }
+
+ if (url.IsEmpty()) {
+ return nil;
+ }
+
+ return [NSURL URLWithString:nsCocoaUtils::ToNSString(url)];
+}
+
+- (NSNumber*)moxLoaded {
+ if ([self isExpired]) {
+ return nil;
+ }
+ // We are loaded if we aren't busy or stale
+ return @([self stateWithMask:(states::BUSY & states::STALE)] == 0);
+}
+
+// overrides
+- (NSNumber*)moxLoadingProgress {
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ if ([self stateWithMask:states::STALE] != 0) {
+ // We expose stale state until the document is ready (DOM is loaded and tree
+ // is constructed) so we indicate load hasn't started while this state is
+ // present.
+ return @0.0;
+ }
+
+ if ([self stateWithMask:states::BUSY] != 0) {
+ // We expose state busy until the document and all its subdocuments are
+ // completely loaded, so we indicate partial loading here
+ return @0.5;
+ }
+
+ // if we are not busy and not stale, we are loaded
+ return @1.0;
+}
+
+- (NSArray*)moxLinkUIElements {
+ NSDictionary* searchPredicate = @{
+ @"AXSearchKey" : @"AXLinkSearchKey",
+ @"AXImmediateDescendantsOnly" : @NO,
+ @"AXResultsLimit" : @(-1),
+ @"AXDirection" : @"AXDirectionNext",
+ };
+
+ return [self moxUIElementsForSearchPredicate:searchPredicate];
+}
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
+ [self moxPostNotification:
+ NSAccessibilityFocusedUIElementChangedNotification];
+ if ((mGeckoAccessible.IsProxy() && mGeckoAccessible.AsProxy()->IsDoc() &&
+ mGeckoAccessible.AsProxy()->AsDoc()->IsTopLevel()) ||
+ (mGeckoAccessible.IsAccessible() &&
+ !mGeckoAccessible.AsAccessible()->IsRoot() &&
+ mGeckoAccessible.AsAccessible()
+ ->AsDoc()
+ ->ParentDocument()
+ ->IsRoot())) {
+ // we fire an AXLoadComplete event on top-level documents only
+ [self moxPostNotification:@"AXLoadComplete"];
+ } else {
+ // otherwise the doc belongs to an iframe (IsTopLevelInContentProcess)
+ // and we fire AXLayoutComplete instead
+ [self moxPostNotification:@"AXLayoutComplete"];
+ }
+ break;
+ }
+
+ [super handleAccessibleEvent:eventType];
+}
+
+- (NSArray*)rootGroupChildren {
+ // This method is meant to expose the doc's children to the root group.
+ return [super moxChildren];
+}
+
+- (NSArray*)moxUnignoredChildren {
+ if (id rootGroup = [self rootGroup]) {
+ return @[ [self rootGroup] ];
+ }
+
+ // There is no root group, expose the children here directly.
+ return [super moxUnignoredChildren];
+}
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ if (selector == @selector(moxSubrole)) {
+ // Never expose a subrole for a web area.
+ return YES;
+ }
+
+ if (selector == @selector(moxElementBusy)) {
+ // Don't confuse aria-busy with a document's busy state.
+ return YES;
+ }
+
+ return [super moxBlockSelector:selector];
+}
+
+- (void)moxPostNotification:(NSString*)notification {
+ if (![notification isEqualToString:@"AXElementBusyChanged"]) {
+ // Suppress AXElementBusyChanged since it uses gecko's BUSY state
+ // to tell VoiceOver about aria-busy changes. We use that state
+ // differently in documents.
+ [super moxPostNotification:notification];
+ }
+}
+
+- (id)rootGroup {
+ NSArray* children = [super moxUnignoredChildren];
+ if (mRole != roles::APPLICATION && [children count] == 1 &&
+ [[[children firstObject] moxUnignoredChildren] count] != 0) {
+ // We only need a root group if our document:
+ // (1) has multiple children, or
+ // (2) a one child that is a leaf, or
+ // (3) has a role of APPLICATION
+ return nil;
+ }
+
+ if (!mRootGroup) {
+ mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
+ }
+
+ return mRootGroup;
+}
+
+- (void)expire {
+ [mRootGroup expire];
+ [super expire];
+}
+
+- (void)dealloc {
+ // This object can only be dealoced after the gecko accessible wrapper
+ // reference is released, and that happens after expire is called.
+ MOZ_ASSERT([self isExpired]);
+ [mRootGroup release];
+
+ [super dealloc];
+}
+
+@end
diff --git a/accessible/mac/MacUtils.h b/accessible/mac/MacUtils.h
new file mode 100644
index 0000000000..fdb146df25
--- /dev/null
+++ b/accessible/mac/MacUtils.h
@@ -0,0 +1,43 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 _MacUtils_H_
+#define _MacUtils_H_
+
+#include "nsStringFwd.h"
+
+@class NSString;
+@class mozAccessible;
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+// convert an array of Gecko accessibles to an NSArray of native accessibles
+NSArray<mozAccessible*>* ConvertToNSArray(nsTArray<Accessible*>& aArray);
+
+// convert an array of Gecko proxy accessibles to an NSArray of native
+// accessibles
+NSArray<mozAccessible*>* ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray);
+
+/**
+ * Get a localized string from the string bundle.
+ * Return nil if not found.
+ */
+NSString* LocalizedString(const nsString& aString);
+
+/**
+ * Gets an accessible atttribute from the mozAccessible's associated
+ * accessible wrapper or proxy, and returns the value as an NSString.
+ * nil if no attribute is found.
+ */
+NSString* GetAccAttr(mozAccessible* aNativeAccessible, const char* aAttrName);
+}
+}
+}
+
+#endif
diff --git a/accessible/mac/MacUtils.mm b/accessible/mac/MacUtils.mm
new file mode 100644
index 0000000000..a6c906413c
--- /dev/null
+++ b/accessible/mac/MacUtils.mm
@@ -0,0 +1,89 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "MacUtils.h"
+
+#include "Accessible.h"
+#include "nsCocoaUtils.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "nsIPersistentProperties2.h"
+
+namespace mozilla {
+namespace a11y {
+namespace utils {
+
+// convert an array of Gecko accessibles to an NSArray of native accessibles
+NSArray<mozAccessible*>* ConvertToNSArray(nsTArray<Accessible*>& aArray) {
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ Accessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+// convert an array of Gecko proxy accessibles to an NSArray of native
+// accessibles
+NSArray<mozAccessible*>* ConvertToNSArray(nsTArray<ProxyAccessible*>& aArray) {
+ NSMutableArray* nativeArray = [[NSMutableArray alloc] init];
+
+ // iterate through the list, and get each native accessible.
+ size_t totalCount = aArray.Length();
+ for (size_t i = 0; i < totalCount; i++) {
+ ProxyAccessible* curAccessible = aArray.ElementAt(i);
+ mozAccessible* curNative = GetNativeFromGeckoAccessible(curAccessible);
+ if (curNative)
+ [nativeArray addObject:GetObjectOrRepresentedView(curNative)];
+ }
+
+ return nativeArray;
+}
+
+/**
+ * Get a localized string from the a11y string bundle.
+ * Return nil if not found.
+ */
+NSString* LocalizedString(const nsString& aString) {
+ nsString text;
+
+ Accessible::TranslateString(aString, text);
+
+ return text.IsEmpty() ? nil : nsCocoaUtils::ToNSString(text);
+}
+
+NSString* GetAccAttr(mozAccessible* aNativeAccessible, const char* aAttrName) {
+ nsAutoString result;
+ if (Accessible* acc = [aNativeAccessible geckoAccessible].AsAccessible()) {
+ nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes();
+ attributes->GetStringProperty(nsCString(aAttrName), result);
+ } else if (ProxyAccessible* proxy =
+ [aNativeAccessible geckoAccessible].AsProxy()) {
+ AutoTArray<Attribute, 10> attrs;
+ proxy->Attributes(&attrs);
+ for (size_t i = 0; i < attrs.Length(); i++) {
+ if (attrs.ElementAt(i).Name() == aAttrName) {
+ result = attrs.ElementAt(i).Value();
+ break;
+ }
+ }
+ }
+
+ if (!result.IsEmpty()) {
+ return nsCocoaUtils::ToNSString(result);
+ }
+
+ return nil;
+}
+}
+}
+}
diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm
new file mode 100644
index 0000000000..47ef41fe20
--- /dev/null
+++ b/accessible/mac/Platform.mm
@@ -0,0 +1,212 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "MOXTextMarkerDelegate.h"
+
+#include "Platform.h"
+#include "ProxyAccessible.h"
+#include "AccessibleOrProxy.h"
+#include "DocAccessibleParent.h"
+#include "mozTableAccessible.h"
+#include "MOXWebAreaAccessible.h"
+
+#include "nsAppShell.h"
+#include "mozilla/Telemetry.h"
+
+// Available from 10.13 onwards; test availability at runtime before using
+@interface NSWorkspace (AvailableSinceHighSierra)
+@property(readonly) BOOL isVoiceOverEnabled;
+@end
+
+namespace mozilla {
+namespace a11y {
+
+// Mac a11y whitelisting
+static bool sA11yShouldBeEnabled = false;
+
+bool ShouldA11yBeEnabled() {
+ EPlatformDisabledState disabledState = PlatformDisabledState();
+ return (disabledState == ePlatformIsForceEnabled) ||
+ ((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
+}
+
+void PlatformInit() {}
+
+void PlatformShutdown() {}
+
+void ProxyCreated(ProxyAccessible* aProxy, uint32_t) {
+ if (aProxy->Role() == roles::WHITESPACE) {
+ // We don't create a native object if we're child of a "flat" accessible;
+ // for example, on OS X buttons shouldn't have any children, because that
+ // makes the OS confused. We also don't create accessibles for <br>
+ // (whitespace) elements.
+ return;
+ }
+
+ // Pass in dummy state for now as retrieving proxy state requires IPC.
+ // Note that we can use ProxyAccessible::IsTable* functions here because they
+ // do not use IPC calls but that might change after bug 1210477.
+ Class type;
+ if (aProxy->IsTable()) {
+ type = [mozTableAccessible class];
+ } else if (aProxy->IsTableRow()) {
+ type = [mozTableRowAccessible class];
+ } else if (aProxy->IsTableCell()) {
+ type = [mozTableCellAccessible class];
+ } else if (aProxy->IsDoc()) {
+ type = [MOXWebAreaAccessible class];
+ } else {
+ type = GetTypeFromRole(aProxy->Role());
+ }
+
+ mozAccessible* mozWrapper = [[type alloc] initWithAccessible:aProxy];
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
+}
+
+void ProxyDestroyed(ProxyAccessible* aProxy) {
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
+ [wrapper expire];
+ [wrapper release];
+ aProxy->SetWrapper(0);
+
+ if (aProxy->IsDoc()) {
+ [MOXTextMarkerDelegate destroyForDoc:aProxy];
+ }
+}
+
+void ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType) {
+ // Ignore event that we don't escape below, they aren't yet supported.
+ if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
+ aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
+ aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
+ aEventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
+ aEventType != nsIAccessibleEvent::EVENT_REORDER &&
+ aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED &&
+ aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED &&
+ aEventType != nsIAccessibleEvent::EVENT_NAME_CHANGE)
+ return;
+
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
+ if (wrapper) {
+ [wrapper handleAccessibleEvent:aEventType];
+ }
+}
+
+void ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t aState,
+ bool aEnabled) {
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
+ if (wrapper) {
+ [wrapper stateChanged:aState isEnabled:aEnabled];
+ }
+}
+
+void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset,
+ bool aIsSelectionCollapsed) {
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
+ MOXTextMarkerDelegate* delegate =
+ [MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
+ [delegate setCaretOffset:aTarget at:aOffset];
+ if (aIsSelectionCollapsed) {
+ // If selection is collapsed, invalidate selection.
+ [delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];
+ }
+
+ if (wrapper) {
+ if (mozTextAccessible* textAcc =
+ static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) {
+ [textAcc
+ handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
+ } else {
+ [wrapper
+ handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
+ }
+ }
+}
+
+void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aIsInsert,
+ bool aFromUser) {
+ ProxyAccessible* acc = aTarget;
+ // If there is a text input ancestor, use it as the event source.
+ while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
+ acc = acc->Parent();
+ }
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(acc ? acc : aTarget);
+ [wrapper handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
+ inserted:aIsInsert
+ inContainer:aTarget
+ at:aStart];
+}
+
+void ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool) {}
+
+void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
+ uint32_t aEventType) {
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aWidget);
+ if (wrapper) {
+ [wrapper handleAccessibleEvent:aEventType];
+ }
+}
+
+void ProxyTextSelectionChangeEvent(ProxyAccessible* aTarget,
+ const nsTArray<TextRangeData>& aSelection) {
+ if (aSelection.Length()) {
+ MOXTextMarkerDelegate* delegate =
+ [MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
+ DocAccessibleParent* doc = aTarget->Document();
+ ProxyAccessible* startContainer =
+ doc->GetAccessible(aSelection[0].StartID());
+ ProxyAccessible* endContainer = doc->GetAccessible(aSelection[0].EndID());
+ // Cache the selection.
+ [delegate setSelectionFrom:startContainer
+ at:aSelection[0].StartOffset()
+ to:endContainer
+ at:aSelection[0].EndOffset()];
+ }
+
+ mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
+ if (wrapper) {
+ [wrapper
+ handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED];
+ }
+}
+
+void ProxyRoleChangedEvent(ProxyAccessible* aTarget, const a11y::role& aRole) {
+ if (mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget)) {
+ [wrapper handleRoleChanged:aRole];
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+@interface GeckoNSApplication (a11y)
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+@end
+
+@implementation GeckoNSApplication (a11y)
+
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
+ if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
+ mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
+#if defined(MOZ_TELEMETRY_REPORTING)
+ if ([[NSWorkspace sharedWorkspace]
+ respondsToSelector:@selector(isVoiceOverEnabled)] &&
+ [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) {
+ Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS,
+ u"VoiceOver"_ns);
+ }
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+ }
+
+ return [super accessibilitySetValue:value forAttribute:attribute];
+}
+
+@end
diff --git a/accessible/mac/PlatformExtTypes.h b/accessible/mac/PlatformExtTypes.h
new file mode 100644
index 0000000000..fdd33a4341
--- /dev/null
+++ b/accessible/mac/PlatformExtTypes.h
@@ -0,0 +1,27 @@
+/* -*- 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_PlatformExtTypes_h__
+#define mozilla_a11y_PlatformExtTypes_h__
+
+namespace mozilla {
+namespace a11y {
+
+enum class EWhichRange {
+ eLeftWord,
+ eRightWord,
+ eLine,
+ eLeftLine,
+ eRightLine,
+ eParagraph,
+ eStyle
+};
+
+enum class EWhichPostFilter { eContainsText };
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_PlatformExtTypes_h__
diff --git a/accessible/mac/RootAccessibleWrap.h b/accessible/mac/RootAccessibleWrap.h
new file mode 100644
index 0000000000..0d165e3236
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+/* For documentation of the accessibility architecture,
+ * see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
+ */
+
+#ifndef mozilla_a11y_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+class RootAccessibleWrap : public RootAccessible {
+ public:
+ RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ Class GetNativeType();
+
+ // let's our native accessible get in touch with the
+ // native cocoa view that is our accessible parent.
+ void GetNativeWidget(void** aOutView);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/RootAccessibleWrap.mm b/accessible/mac/RootAccessibleWrap.mm
new file mode 100644
index 0000000000..4086b3bf30
--- /dev/null
+++ b/accessible/mac/RootAccessibleWrap.mm
@@ -0,0 +1,51 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "RootAccessibleWrap.h"
+
+#include "mozRootAccessible.h"
+
+#include "gfxPlatform.h"
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsIFrame.h"
+#include "nsView.h"
+#include "nsIWidget.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : RootAccessible(aDocument, aPresShell) {}
+
+RootAccessibleWrap::~RootAccessibleWrap() {}
+
+Class RootAccessibleWrap::GetNativeType() {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [mozRootAccessible class];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+void RootAccessibleWrap::GetNativeWidget(void** aOutView) {
+ nsIFrame* frame = GetFrame();
+ if (frame) {
+ nsView* view = frame->GetView();
+ if (view) {
+ nsIWidget* widget = view->GetWidget();
+ if (widget) {
+ *aOutView = (void**)widget->GetNativeData(NS_NATIVE_WIDGET);
+ MOZ_ASSERT(
+ *aOutView || gfxPlatform::IsHeadless(),
+ "Couldn't get the native NSView parent we need to connect the "
+ "accessibility hierarchy!");
+ }
+ }
+ }
+}
diff --git a/accessible/mac/RotorRules.h b/accessible/mac/RotorRules.h
new file mode 100644
index 0000000000..e158d6e764
--- /dev/null
+++ b/accessible/mac/RotorRules.h
@@ -0,0 +1,142 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+#include "Pivot.h"
+
+using namespace mozilla::a11y;
+
+/**
+ * This rule matches all accessibles that satisfy the "boilerplate"
+ * pivot conditions and have a corresponding native accessible.
+ */
+class RotorRule : public PivotRule {
+ public:
+ explicit RotorRule(AccessibleOrProxy& aDirectDescendantsFrom);
+ explicit RotorRule();
+ uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ private:
+ AccessibleOrProxy mDirectDescendantsFrom;
+};
+
+/**
+ * This rule matches all accessibles of a given role.
+ */
+class RotorRoleRule : public RotorRule {
+ public:
+ explicit RotorRoleRule(role aRole, AccessibleOrProxy& aDirectDescendantsFrom);
+ explicit RotorRoleRule(role aRole);
+ uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ private:
+ role mRole;
+};
+
+class RotorMacRoleRule : public RotorRule {
+ public:
+ explicit RotorMacRoleRule(NSString* aRole);
+ explicit RotorMacRoleRule(NSString* aRole,
+ AccessibleOrProxy& aDirectDescendantsFrom);
+ ~RotorMacRoleRule();
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ protected:
+ NSString* mMacRole;
+};
+
+class RotorControlRule final : public RotorRule {
+ public:
+ explicit RotorControlRule(AccessibleOrProxy& aDirectDescendantsFrom);
+ explicit RotorControlRule();
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorTextEntryRule final : public RotorRule {
+ public:
+ explicit RotorTextEntryRule(AccessibleOrProxy& aDirectDescendantsFrom);
+ explicit RotorTextEntryRule();
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorLinkRule : public RotorRule {
+ public:
+ explicit RotorLinkRule();
+ explicit RotorLinkRule(AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorVisitedLinkRule final : public RotorLinkRule {
+ public:
+ explicit RotorVisitedLinkRule();
+ explicit RotorVisitedLinkRule(AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorUnvisitedLinkRule final : public RotorLinkRule {
+ public:
+ explicit RotorUnvisitedLinkRule();
+ explicit RotorUnvisitedLinkRule(AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+/**
+ * This rule matches all accessibles that satisfy the "boilerplate"
+ * pivot conditions and have a corresponding native accessible.
+ */
+class RotorNotMacRoleRule : public RotorMacRoleRule {
+ public:
+ explicit RotorNotMacRoleRule(NSString* aMacRole,
+ AccessibleOrProxy& aDirectDescendantsFrom);
+ explicit RotorNotMacRoleRule(NSString* aMacRole);
+ uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorStaticTextRule : public RotorRule {
+ public:
+ explicit RotorStaticTextRule();
+ explicit RotorStaticTextRule(AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+class RotorHeadingLevelRule : public RotorRoleRule {
+ public:
+ explicit RotorHeadingLevelRule(int32_t aLevel);
+ explicit RotorHeadingLevelRule(int32_t aLevel,
+ AccessibleOrProxy& aDirectDescendantsFrom);
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ private:
+ int32_t mLevel;
+};
+
+class RotorLiveRegionRule : public RotorRule {
+ public:
+ explicit RotorLiveRegionRule(AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom) {}
+ explicit RotorLiveRegionRule() : RotorRule() {}
+
+ uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
+
+/**
+ * This rule matches all accessibles with roles::OUTLINEITEM. If
+ * outlines are nested, it ignores the nested subtree and returns
+ * only items which are descendants of the primary outline.
+ */
+class OutlineRule : public RotorRule {
+ public:
+ explicit OutlineRule();
+ uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+};
diff --git a/accessible/mac/RotorRules.mm b/accessible/mac/RotorRules.mm
new file mode 100644
index 0000000000..e42d768608
--- /dev/null
+++ b/accessible/mac/RotorRules.mm
@@ -0,0 +1,370 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "RotorRules.h"
+
+#include "nsCocoaUtils.h"
+#include "DocAccessibleParent.h"
+
+using namespace mozilla::a11y;
+
+// Generic Rotor Rule
+
+RotorRule::RotorRule(AccessibleOrProxy& aDirectDescendantsFrom)
+ : mDirectDescendantsFrom(aDirectDescendantsFrom) {}
+
+RotorRule::RotorRule() : mDirectDescendantsFrom(nullptr) {}
+
+uint16_t RotorRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (nsAccUtils::MustPrune(aAccOrProxy)) {
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ if (!mDirectDescendantsFrom.IsNull() &&
+ (aAccOrProxy != mDirectDescendantsFrom)) {
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ if ([GetNativeFromGeckoAccessible(aAccOrProxy) isAccessibilityElement]) {
+ result |= nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return result;
+}
+
+// Rotor Role Rule
+
+RotorRoleRule::RotorRoleRule(role aRole,
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom), mRole(aRole){};
+
+RotorRoleRule::RotorRoleRule(role aRole) : RotorRule(), mRole(aRole){};
+
+uint16_t RotorRoleRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH) &&
+ aAccOrProxy.Role() != mRole) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return result;
+}
+
+// Rotor Mac Role Rule
+
+RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole,
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom), mMacRole(aMacRole) {
+ [mMacRole retain];
+};
+
+RotorMacRoleRule::RotorMacRoleRule(NSString* aMacRole)
+ : RotorRule(), mMacRole(aMacRole) {
+ [mMacRole retain];
+};
+
+RotorMacRoleRule::~RotorMacRoleRule() { [mMacRole release]; }
+
+uint16_t RotorMacRoleRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if (![[nativeMatch moxRole] isEqualToString:mMacRole]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+// Rotor Control Rule
+
+RotorControlRule::RotorControlRule(AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom){};
+
+RotorControlRule::RotorControlRule() : RotorRule(){};
+
+uint16_t RotorControlRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ switch (aAccOrProxy.Role()) {
+ case roles::PUSHBUTTON:
+ case roles::SPINBUTTON:
+ case roles::DETAILS:
+ case roles::CHECKBUTTON:
+ case roles::COLOR_CHOOSER:
+ case roles::BUTTONDROPDOWNGRID: // xul colorpicker
+ case roles::LISTBOX:
+ case roles::COMBOBOX:
+ case roles::EDITCOMBOBOX:
+ case roles::RADIOBUTTON:
+ case roles::RADIO_GROUP:
+ case roles::PAGETAB:
+ case roles::SLIDER:
+ case roles::SWITCH:
+ case roles::ENTRY:
+ case roles::OUTLINE:
+ case roles::PASSWORD_TEXT:
+ return result;
+
+ case roles::DATE_EDITOR:
+ case roles::TIME_EDITOR:
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ return result;
+
+ case roles::GROUPING: {
+ // Groupings are sometimes used (like radio groups) to denote
+ // sets of controls. If that's the case, we want to surface
+ // them. We also want to surface grouped time and date controls.
+ for (unsigned int i = 0; i < aAccOrProxy.ChildCount(); i++) {
+ AccessibleOrProxy currChild = aAccOrProxy.ChildAt(i);
+ if (currChild.Role() == roles::CHECKBUTTON ||
+ currChild.Role() == roles::SWITCH ||
+ currChild.Role() == roles::SPINBUTTON ||
+ currChild.Role() == roles::RADIOBUTTON) {
+ return result;
+ }
+ }
+
+ // if we iterated through the groups children and didn't
+ // find a control with one of the roles above, we should
+ // ignore this grouping
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ return result;
+ }
+
+ default:
+ // if we did not match on any above role, we should
+ // ignore this accessible.
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+// Rotor TextEntry Rule
+
+RotorTextEntryRule::RotorTextEntryRule(
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom){};
+
+RotorTextEntryRule::RotorTextEntryRule() : RotorRule(){};
+
+uint16_t RotorTextEntryRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ if (aAccOrProxy.Role() != roles::PASSWORD_TEXT &&
+ aAccOrProxy.Role() != roles::ENTRY) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+// Rotor Link Rule
+
+RotorLinkRule::RotorLinkRule(AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom){};
+
+RotorLinkRule::RotorLinkRule() : RotorRule(){};
+
+uint16_t RotorLinkRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if (![[nativeMatch moxRole] isEqualToString:@"AXLink"]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+RotorVisitedLinkRule::RotorVisitedLinkRule() : RotorLinkRule() {}
+
+RotorVisitedLinkRule::RotorVisitedLinkRule(
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorLinkRule(aDirectDescendantsFrom) {}
+
+uint16_t RotorVisitedLinkRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorLinkRule::Match(aAccOrProxy);
+
+ if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if (![[nativeMatch moxVisited] boolValue]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+RotorUnvisitedLinkRule::RotorUnvisitedLinkRule() : RotorLinkRule() {}
+
+RotorUnvisitedLinkRule::RotorUnvisitedLinkRule(
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorLinkRule(aDirectDescendantsFrom) {}
+
+uint16_t RotorUnvisitedLinkRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorLinkRule::Match(aAccOrProxy);
+
+ if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if ([[nativeMatch moxVisited] boolValue]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+// Match Not Rule
+
+RotorNotMacRoleRule::RotorNotMacRoleRule(
+ NSString* aMacRole, AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorMacRoleRule(aMacRole, aDirectDescendantsFrom) {}
+
+RotorNotMacRoleRule::RotorNotMacRoleRule(NSString* aMacRole)
+ : RotorMacRoleRule(aMacRole) {}
+
+uint16_t RotorNotMacRoleRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not different from the desired role, we flip the
+ // match bit to "unmatch" otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if ([[nativeMatch moxRole] isEqualToString:mMacRole]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+ return result;
+}
+
+// Rotor Static Text Rule
+
+RotorStaticTextRule::RotorStaticTextRule(
+ AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRule(aDirectDescendantsFrom){};
+
+RotorStaticTextRule::RotorStaticTextRule() : RotorRule(){};
+
+uint16_t RotorStaticTextRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired role, we flip the match bit to "unmatch"
+ // otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if (![[nativeMatch moxRole] isEqualToString:@"AXStaticText"]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+// Rotor Heading Level Rule
+
+RotorHeadingLevelRule::RotorHeadingLevelRule(
+ int32_t aLevel, AccessibleOrProxy& aDirectDescendantsFrom)
+ : RotorRoleRule(roles::HEADING, aDirectDescendantsFrom), mLevel(aLevel){};
+
+RotorHeadingLevelRule::RotorHeadingLevelRule(int32_t aLevel)
+ : RotorRoleRule(roles::HEADING), mLevel(aLevel){};
+
+uint16_t RotorHeadingLevelRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRoleRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here. if it is
+ // not of the desired heading level, we flip the match bit to
+ // "unmatch" otherwise, the match persists.
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ int32_t currLevel = 0;
+ if (Accessible* acc = aAccOrProxy.AsAccessible()) {
+ currLevel = acc->GroupPosition().level;
+ } else if (ProxyAccessible* proxy = aAccOrProxy.AsProxy()) {
+ currLevel = proxy->GroupPosition().level;
+ }
+
+ if (currLevel != mLevel) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
+
+uint16_t RotorLiveRegionRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ if ((result & nsIAccessibleTraversalRule::FILTER_MATCH)) {
+ mozAccessible* nativeMatch = GetNativeFromGeckoAccessible(aAccOrProxy);
+ if (![nativeMatch moxIsLiveRegion]) {
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+ return result;
+}
+
+// Outline Rule
+
+OutlineRule::OutlineRule() : RotorRule(){};
+
+uint16_t OutlineRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ uint16_t result = RotorRule::Match(aAccOrProxy);
+
+ // if a match was found in the base-class's Match function,
+ // it is valid to consider that match again here.
+ if (result & nsIAccessibleTraversalRule::FILTER_MATCH) {
+ if (aAccOrProxy.Role() == roles::OUTLINE) {
+ // if the match is an outline, we ignore all children here
+ // and unmatch the outline itself
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ } else if (aAccOrProxy.Role() != roles::OUTLINEITEM) {
+ // if the match is not an outline item, we unmatch here
+ result &= ~nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ }
+
+ return result;
+}
diff --git a/accessible/mac/SelectorMapGen.py b/accessible/mac/SelectorMapGen.py
new file mode 100755
index 0000000000..c3504a1751
--- /dev/null
+++ b/accessible/mac/SelectorMapGen.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env 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/.
+
+from __future__ import absolute_import
+
+import re
+
+
+def write_map(fd, name, text):
+ matches = re.findall(r"^//\s(AX\w+)\n-\s?\(.*?\)([\w:]+)", text, re.MULTILINE)
+ entries = [' @"%s" : @"%s"' % (a, s) for [a, s] in matches]
+
+ fd.write("NSDictionary* %s() {\n" % name)
+ fd.write(" // Create an autoreleased NSDictionary object once, and leak it.\n")
+ fd.write(" static NSDictionary* s%s = [@{\n" % name)
+ fd.write(",\n".join(entries))
+ fd.write("\n } retain];\n\n")
+ fd.write(" return s%s;\n" % name)
+ fd.write("}\n\n")
+
+
+def gen_mm(fd, protocol_file):
+ protocol = open(protocol_file).read()
+ fd.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n")
+ fd.write("#import <Foundation/Foundation.h>\n\n")
+ fd.write("namespace mozilla {\nnamespace a11y {\nnamespace mac {\n\n")
+
+ sections = re.findall(
+ r"#pragma mark - (\w+)\n(.*?)(?=(?:#pragma mark|@end))", protocol, re.DOTALL
+ )
+ for name, text in sections:
+ write_map(fd, name, text)
+
+ fd.write("}\n}\n}\n")
+
+
+def gen_h(fd, protocol_file):
+ protocol = open(protocol_file).read()
+ sections = re.findall(
+ r"#pragma mark - (\w+)\n(.*?)(?=(?:#pragma mark|@end))", protocol, re.DOTALL
+ )
+
+ fd.write("/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n")
+ fd.write("#ifndef _MacSelectorMap_H_\n")
+ fd.write("#define _MacSelectorMap_H_\n")
+ fd.write("\n@class NSDictionary;\n")
+ fd.write("\nnamespace mozilla {\nnamespace a11y {\nnamespace mac {\n\n")
+ for name, text in sections:
+ fd.write("NSDictionary* %s();\n\n" % name)
+ fd.write("}\n}\n}\n")
+ fd.write("\n#endif\n")
+
+
+# For debugging
+if __name__ == "__main__":
+ import sys
+
+ gen_mm(sys.stdout, "accessible/mac/MOXAccessibleProtocol.h")
+
+ gen_h(sys.stdout, "accessible/mac/MOXAccessibleProtocol.h")
diff --git a/accessible/mac/TextLeafAccessibleWrap.h b/accessible/mac/TextLeafAccessibleWrap.h
new file mode 100644
index 0000000000..27de110160
--- /dev/null
+++ b/accessible/mac/TextLeafAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULListboxAccessibleWrap.h b/accessible/mac/XULListboxAccessibleWrap.h
new file mode 100644
index 0000000000..da6dd13ede
--- /dev/null
+++ b/accessible/mac/XULListboxAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULMenuAccessibleWrap.h b/accessible/mac/XULMenuAccessibleWrap.h
new file mode 100644
index 0000000000..81cdaf94ad
--- /dev/null
+++ b/accessible/mac/XULMenuAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/XULTreeGridAccessibleWrap.h b/accessible/mac/XULTreeGridAccessibleWrap.h
new file mode 100644
index 0000000000..e991d8f4f4
--- /dev/null
+++ b/accessible/mac/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/mac/moz.build b/accessible/mac/moz.build
new file mode 100644
index 0000000000..c28c89826b
--- /dev/null
+++ b/accessible/mac/moz.build
@@ -0,0 +1,73 @@
+# -*- 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/.
+
+EXPORTS += [
+ "mozAccessibleProtocol.h",
+]
+
+EXPORTS.mozilla.a11y += [
+ "AccessibleWrap.h",
+ "HyperTextAccessibleWrap.h",
+ "PlatformExtTypes.h",
+]
+
+UNIFIED_SOURCES += [
+ "AccessibleWrap.mm",
+ "DocAccessibleWrap.mm",
+ "GeckoTextMarker.mm",
+ "HyperTextAccessibleWrap.mm",
+ "MacUtils.mm",
+ "MOXAccessibleBase.mm",
+ "MOXLandmarkAccessibles.mm",
+ "MOXMathAccessibles.mm",
+ "MOXSearchInfo.mm",
+ "MOXTextMarkerDelegate.mm",
+ "MOXWebAreaAccessible.mm",
+ "mozAccessible.mm",
+ "mozActionElements.mm",
+ "mozHTMLAccessible.mm",
+ "mozRootAccessible.mm",
+ "mozSelectableElements.mm",
+ "mozTableAccessible.mm",
+ "mozTextAccessible.mm",
+ "Platform.mm",
+ "RootAccessibleWrap.mm",
+ "RotorRules.mm",
+]
+
+SOURCES += [
+ "!MacSelectorMap.mm",
+]
+
+LOCAL_INCLUDES += [
+ "/accessible/base",
+ "/accessible/generic",
+ "/accessible/html",
+ "/accessible/ipc",
+ "/accessible/ipc/other",
+ "/accessible/xul",
+ "/layout/generic",
+ "/layout/xul",
+ "/widget",
+ "/widget/cocoa",
+]
+
+GeneratedFile(
+ "MacSelectorMap.h",
+ script="/accessible/mac/SelectorMapGen.py",
+ entry_point="gen_h",
+ inputs=["MOXAccessibleProtocol.h"],
+)
+GeneratedFile(
+ "MacSelectorMap.mm",
+ script="/accessible/mac/SelectorMapGen.py",
+ entry_point="gen_mm",
+ inputs=["MOXAccessibleProtocol.h"],
+)
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h
new file mode 100644
index 0000000000..ee8cd65e77
--- /dev/null
+++ b/accessible/mac/mozAccessible.h
@@ -0,0 +1,273 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "AccessibleWrap.h"
+#include "ProxyAccessible.h"
+#include "AccessibleOrProxy.h"
+
+#import <Cocoa/Cocoa.h>
+
+#import "MOXAccessibleBase.h"
+
+@class mozRootAccessible;
+
+/**
+ * All mozAccessibles are either abstract objects (that correspond to XUL
+ * widgets, HTML frames, etc) or are attached to a certain view; for example
+ * a document view. When we hand an object off to an AT, we always want
+ * to give it the represented view, in the latter case.
+ */
+
+namespace mozilla {
+namespace a11y {
+
+inline mozAccessible* GetNativeFromGeckoAccessible(
+ mozilla::a11y::AccessibleOrProxy aAccOrProxy) {
+ if (aAccOrProxy.IsNull()) {
+ return nil;
+ }
+ if (Accessible* acc = aAccOrProxy.AsAccessible()) {
+ mozAccessible* native = nil;
+ acc->GetNativeInterface((void**)&native);
+ return native;
+ }
+
+ ProxyAccessible* proxy = aAccOrProxy.AsProxy();
+ return reinterpret_cast<mozAccessible*>(proxy->GetWrapper());
+}
+
+} // a11y
+} // mozilla
+
+@interface mozAccessible : MOXAccessibleBase {
+ /**
+ * Reference to the accessible we were created with;
+ * either a proxy accessible or an accessible wrap.
+ */
+ mozilla::a11y::AccessibleOrProxy mGeckoAccessible;
+
+ /**
+ * The role of our gecko accessible.
+ */
+ mozilla::a11y::role mRole;
+
+ /**
+ * A cache of a subset of our states.
+ */
+ uint64_t mCachedState;
+
+ nsStaticAtom* mARIARole;
+
+ bool mIsLiveRegion;
+}
+
+// inits with the given wrap or proxy accessible
+- (id)initWithAccessible:(mozilla::a11y::AccessibleOrProxy)aAccOrProxy;
+
+// allows for gecko accessible access outside of the class
+- (mozilla::a11y::AccessibleOrProxy)geckoAccessible;
+
+- (mozilla::a11y::AccessibleOrProxy)geckoDocument;
+
+// override
+- (void)dealloc;
+
+// should a child be disabled
+- (BOOL)disableChild:(mozAccessible*)child;
+
+// Given a gecko accessibility event type, post the relevant
+// system accessibility notification.
+// Note: when overriding or adding new events, make sure your events aren't
+// filtered out in Platform::ProxyEvent or AccessibleWrap::HandleAccEvent!
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+- (void)handleAccessibleTextChangeEvent:(NSString*)change
+ inserted:(BOOL)isInserted
+ inContainer:
+ (const mozilla::a11y::AccessibleOrProxy&)
+ container
+ at:(int32_t)start;
+
+// internal method to retrieve a child at a given index.
+- (id)childAt:(uint32_t)i;
+
+// Get gecko accessible's state.
+- (uint64_t)state;
+
+// Get gecko accessible's state filtered through given mask.
+- (uint64_t)stateWithMask:(uint64_t)mask;
+
+// Notify of a state change, so the cache can be altered.
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled;
+
+// Invalidate cached state.
+- (void)invalidateState;
+
+// Get top level (tab) web area.
+- (mozAccessible*)topWebArea;
+
+// Handle a role change
+- (void)handleRoleChanged:(mozilla::a11y::role)newRole;
+
+// Get ARIA role
+- (nsStaticAtom*)ARIARole;
+
+// Get array of related mozAccessibles
+- (NSArray<mozAccessible*>*)getRelationsByType:
+ (mozilla::a11y::RelationType)relationType;
+
+#pragma mark - mozAccessible protocol / widget
+
+// override
+- (BOOL)hasRepresentedView;
+
+// override
+- (id)representedView;
+
+// override
+- (BOOL)isRoot;
+
+#pragma mark - MOXAccessible protocol
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (id)moxHitTest:(NSPoint)point;
+
+// override
+- (id)moxFocusedUIElement;
+
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate;
+
+- (BOOL)moxIsLiveRegion;
+
+// Attribute getters
+
+// override
+- (id<mozAccessible>)moxParent;
+
+// override
+- (NSArray*)moxChildren;
+
+// override
+- (NSValue*)moxSize;
+
+// override
+- (NSValue*)moxPosition;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSString*)moxSubrole;
+
+// override
+- (NSString*)moxRoleDescription;
+
+// override
+- (NSWindow*)moxWindow;
+
+// override
+- (id)moxValue;
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (NSString*)moxLabel;
+
+// override
+- (NSString*)moxHelp;
+
+// override
+- (NSNumber*)moxEnabled;
+
+// override
+- (NSNumber*)moxFocused;
+
+// override
+- (NSNumber*)moxSelected;
+
+// override
+- (NSString*)moxARIACurrent;
+
+// override
+- (NSNumber*)moxARIAAtomic;
+
+// override
+- (NSString*)moxARIALive;
+
+// override
+- (NSString*)moxARIARelevant;
+
+// override
+- (id)moxTitleUIElement;
+
+// override
+- (NSString*)moxDOMIdentifier;
+
+// override
+- (NSNumber*)moxRequired;
+
+// override
+- (NSNumber*)moxElementBusy;
+
+// override
+- (NSArray*)moxLinkedUIElements;
+
+// override
+- (NSArray*)moxARIAControls;
+
+// override
+- (id)moxEditableAncestor;
+
+// override
+- (id)moxHighestEditableAncestor;
+
+// override
+- (id)moxFocusableAncestor;
+
+#ifndef RELEASE_OR_BETA
+// override
+- (NSString*)moxMozDebugDescription;
+#endif
+
+// override
+- (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate;
+
+// override
+- (NSNumber*)moxUIElementCountForSearchPredicate:(NSDictionary*)searchPredicate;
+
+// override
+- (void)moxSetFocused:(NSNumber*)focused;
+
+// override
+- (void)moxPerformScrollToVisible;
+
+// override
+- (void)moxPerformShowMenu;
+
+// override
+- (void)moxPerformPress;
+
+// override
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent;
+
+// override
+- (BOOL)moxIgnoreChild:(mozAccessible*)child;
+
+#pragma mark -
+
+// makes ourselves "expired". after this point, we might be around if someone
+// has retained us (e.g., a third-party), but we really contain no information.
+// override
+- (void)expire;
+// override
+- (BOOL)isExpired;
+
+@end
diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm
new file mode 100644
index 0000000000..717513dce6
--- /dev/null
+++ b/accessible/mac/mozAccessible.mm
@@ -0,0 +1,1074 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+#include "MOXAccessibleBase.h"
+
+#import "MacUtils.h"
+#import "mozView.h"
+#import "MOXSearchInfo.h"
+#import "mozTextAccessible.h"
+
+#include "Accessible-inl.h"
+#include "nsAccUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "DocAccessibleParent.h"
+#include "Relation.h"
+#include "Role.h"
+#include "RootAccessible.h"
+#include "TableAccessible.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "OuterDocAccessible.h"
+#include "nsChildView.h"
+
+#include "nsRect.h"
+#include "nsCocoaUtils.h"
+#include "nsCoord.h"
+#include "nsObjCExceptions.h"
+#include "nsWhitespaceTokenizer.h"
+#include <prdtoa.h>
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+#pragma mark -
+
+@interface mozAccessible ()
+- (BOOL)providesLabelNotTitle;
+
+- (void)maybePostLiveRegionChanged;
+@end
+
+@implementation mozAccessible
+
+- (id)initWithAccessible:(AccessibleOrProxy)aAccOrProxy {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ MOZ_ASSERT(!aAccOrProxy.IsNull(), "Cannot init mozAccessible with null");
+ if ((self = [super init])) {
+ mGeckoAccessible = aAccOrProxy;
+ mRole = aAccOrProxy.Role();
+ }
+
+ return self;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+#pragma mark - mozAccessible widget
+
+- (BOOL)hasRepresentedView {
+ return NO;
+}
+
+- (id)representedView {
+ return nil;
+}
+
+- (BOOL)isRoot {
+ return NO;
+}
+
+#pragma mark -
+
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
+ if (acc->VisibilityState() & states::INVISIBLE) {
+ return YES;
+ }
+ }
+ }
+
+ return [parent moxIgnoreChild:self];
+}
+
+- (BOOL)moxIgnoreChild:(mozAccessible*)child {
+ return nsAccUtils::MustPrune(mGeckoAccessible);
+}
+
+- (id)childAt:(uint32_t)i {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ AccessibleOrProxy child = mGeckoAccessible.ChildAt(i);
+ return !child.IsNull() ? GetNativeFromGeckoAccessible(child) : nil;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+static const uint64_t kCachedStates =
+ states::CHECKED | states::PRESSED | states::MIXED | states::EXPANDED |
+ states::CURRENT | states::SELECTED | states::TRAVERSED | states::LINKED |
+ states::HASPOPUP | states::BUSY;
+static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63;
+
+- (uint64_t)state {
+ uint64_t state = 0;
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ state = acc->State();
+ }
+
+ if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ state = proxy->State();
+ }
+
+ if (!(mCachedState & kCacheInitialized)) {
+ mCachedState = state & kCachedStates;
+ mCachedState |= kCacheInitialized;
+ }
+
+ return state;
+}
+
+- (uint64_t)stateWithMask:(uint64_t)mask {
+ if ((mask & kCachedStates) == mask &&
+ (mCachedState & kCacheInitialized) != 0) {
+ return mCachedState & mask;
+ }
+
+ return [self state] & mask;
+}
+
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
+ if ((state & kCachedStates) != 0) {
+ if (!(mCachedState & kCacheInitialized)) {
+ [self state];
+ } else {
+ if (enabled) {
+ mCachedState |= state;
+ } else {
+ mCachedState &= ~state;
+ }
+ }
+ }
+
+ if (state == states::BUSY) {
+ [self moxPostNotification:@"AXElementBusyChanged"];
+ }
+}
+
+- (void)invalidateState {
+ mCachedState = 0;
+}
+
+- (BOOL)providesLabelNotTitle {
+ // These accessible types are the exception to the rule of label vs. title:
+ // They may be named explicitly, but they still provide a label not a title.
+ return mRole == roles::GROUPING || mRole == roles::RADIO_GROUP ||
+ mRole == roles::FIGURE || mRole == roles::GRAPHIC ||
+ mRole == roles::DOCUMENT || mRole == roles::OUTLINE;
+}
+
+- (mozilla::a11y::AccessibleOrProxy)geckoAccessible {
+ return mGeckoAccessible;
+}
+
+- (mozilla::a11y::AccessibleOrProxy)geckoDocument {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ if (mGeckoAccessible.AsAccessible()->IsDoc()) {
+ return mGeckoAccessible;
+ }
+ return mGeckoAccessible.AsAccessible()->Document();
+ }
+
+ if (mGeckoAccessible.AsProxy()->IsDoc()) {
+ return mGeckoAccessible;
+ }
+
+ return mGeckoAccessible.AsProxy()->Document();
+}
+
+#pragma mark - MOXAccessible protocol
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ if (selector == @selector(moxPerformPress)) {
+ uint8_t actionCount = mGeckoAccessible.IsAccessible()
+ ? mGeckoAccessible.AsAccessible()->ActionCount()
+ : mGeckoAccessible.AsProxy()->ActionCount();
+
+ // If we have no action, we don't support press, so return YES.
+ return actionCount == 0;
+ }
+
+ if (selector == @selector(moxSetFocused:)) {
+ return [self stateWithMask:states::FOCUSABLE] == 0;
+ }
+
+ if (selector == @selector(moxARIALive) ||
+ selector == @selector(moxARIAAtomic) ||
+ selector == @selector(moxARIARelevant)) {
+ return ![self moxIsLiveRegion];
+ }
+
+ return [super moxBlockSelector:selector];
+}
+
+- (id)moxFocusedUIElement {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ Accessible* acc = mGeckoAccessible.AsAccessible();
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+
+ mozAccessible* focusedChild = nil;
+ if (acc) {
+ Accessible* focusedGeckoChild = acc->FocusedChild();
+ if (focusedGeckoChild) {
+ focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
+ } else {
+ dom::BrowserParent* browser = dom::BrowserParent::GetFocused();
+ if (browser) {
+ a11y::DocAccessibleParent* proxyDoc =
+ browser->GetTopLevelDocAccessible();
+ if (proxyDoc) {
+ mozAccessible* nativeRemoteChild =
+ GetNativeFromGeckoAccessible(proxyDoc);
+ return [nativeRemoteChild accessibilityFocusedUIElement];
+ }
+ }
+ }
+ } else if (proxy) {
+ ProxyAccessible* focusedGeckoChild = proxy->FocusedChild();
+ if (focusedGeckoChild) {
+ focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild);
+ }
+ }
+
+ if ([focusedChild isAccessibilityElement]) {
+ return focusedChild;
+ }
+
+ // return ourself if we can't get a native focused child.
+ return self;
+}
+
+- (id<MOXTextMarkerSupport>)moxTextMarkerDelegate {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ return [MOXTextMarkerDelegate
+ getOrCreateForDoc:mGeckoAccessible.AsAccessible()->Document()];
+ }
+
+ return [MOXTextMarkerDelegate
+ getOrCreateForDoc:mGeckoAccessible.AsProxy()->Document()];
+}
+
+- (BOOL)moxIsLiveRegion {
+ return mIsLiveRegion;
+}
+
+- (id)moxHitTest:(NSPoint)point {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ // Convert the given screen-global point in the cocoa coordinate system (with
+ // origin in the bottom-left corner of the screen) into point in the Gecko
+ // coordinate system (with origin in a top-left screen point).
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ NSPoint tmpPoint =
+ NSMakePoint(point.x, [mainView frame].size.height - point.y);
+ LayoutDeviceIntPoint geckoPoint = nsCocoaUtils::CocoaPointsToDevPixels(
+ tmpPoint, nsCocoaUtils::GetBackingScaleFactor(mainView));
+
+ AccessibleOrProxy child = mGeckoAccessible.ChildAtPoint(
+ geckoPoint.x, geckoPoint.y, Accessible::eDeepestChild);
+
+ if (!child.IsNull()) {
+ mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
+ return [nativeChild isAccessibilityElement]
+ ? nativeChild
+ : [nativeChild moxUnignoredParent];
+ }
+
+ // if we didn't find anything, return ourself or child view.
+ return self;
+}
+
+- (id<mozAccessible>)moxParent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ AccessibleOrProxy parent = mGeckoAccessible.Parent();
+
+ if (parent.IsNull()) {
+ return nil;
+ }
+
+ id nativeParent = GetNativeFromGeckoAccessible(parent);
+ if (parent.Role() == roles::DOCUMENT &&
+ [nativeParent respondsToSelector:@selector(rootGroup)]) {
+ // Before returning a WebArea as parent, check to see if
+ // there is a generated root group that is an intermediate container.
+ if (id<mozAccessible> rootGroup = [nativeParent rootGroup]) {
+ nativeParent = rootGroup;
+ }
+ }
+
+ if (!nativeParent && mGeckoAccessible.IsAccessible()) {
+ // Return native of root accessible if we have no direct parent.
+ // XXX: need to return a sensible fallback in proxy case as well
+ nativeParent = GetNativeFromGeckoAccessible(
+ mGeckoAccessible.AsAccessible()->RootAccessible());
+ }
+
+ return GetObjectOrRepresentedView(nativeParent);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+// gets all our native children lazily, including those that are ignored.
+- (NSArray*)moxChildren {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSMutableArray* children =
+ [[NSMutableArray alloc] initWithCapacity:mGeckoAccessible.ChildCount()];
+
+ for (uint32_t childIdx = 0; childIdx < mGeckoAccessible.ChildCount();
+ childIdx++) {
+ AccessibleOrProxy child = mGeckoAccessible.ChildAt(childIdx);
+ mozAccessible* nativeChild = GetNativeFromGeckoAccessible(child);
+ if (!nativeChild) {
+ continue;
+ }
+
+ [children addObject:nativeChild];
+ }
+
+ return children;
+}
+
+- (NSValue*)moxPosition {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ nsIntRect rect = mGeckoAccessible.IsAccessible()
+ ? mGeckoAccessible.AsAccessible()->Bounds()
+ : mGeckoAccessible.AsProxy()->Bounds();
+
+ NSScreen* mainView = [[NSScreen screens] objectAtIndex:0];
+ CGFloat scaleFactor = nsCocoaUtils::GetBackingScaleFactor(mainView);
+ NSPoint p =
+ NSMakePoint(static_cast<CGFloat>(rect.x) / scaleFactor,
+ [mainView frame].size.height -
+ static_cast<CGFloat>(rect.y + rect.height) / scaleFactor);
+
+ return [NSValue valueWithPoint:p];
+}
+
+- (NSValue*)moxSize {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ nsIntRect rect = mGeckoAccessible.IsAccessible()
+ ? mGeckoAccessible.AsAccessible()->Bounds()
+ : mGeckoAccessible.AsProxy()->Bounds();
+
+ CGFloat scaleFactor =
+ nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]);
+ return [NSValue
+ valueWithSize:NSMakeSize(
+ static_cast<CGFloat>(rect.width) / scaleFactor,
+ static_cast<CGFloat>(rect.height) / scaleFactor)];
+}
+
+- (NSString*)moxRole {
+#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ case roles::geckoRole: \
+ return macRole;
+
+ switch (mRole) {
+#include "RoleMap.h"
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown role.");
+ return NSAccessibilityUnknownRole;
+ }
+
+#undef ROLE
+}
+
+- (nsStaticAtom*)ARIARole {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ if (acc->HasARIARole()) {
+ const nsRoleMapEntry* roleMap = acc->ARIARoleMap();
+ return roleMap->roleAtom;
+ }
+
+ return nsGkAtoms::_empty;
+ }
+
+ if (!mARIARole) {
+ mARIARole = mGeckoAccessible.AsProxy()->ARIARoleAtom();
+ if (!mARIARole) {
+ mARIARole = nsGkAtoms::_empty;
+ }
+ }
+
+ return mARIARole;
+}
+
+- (NSString*)moxSubrole {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ Accessible* acc = mGeckoAccessible.AsAccessible();
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+
+ // Deal with landmarks first
+ // macOS groups the specific landmark types of DPub ARIA into two broad
+ // categories with corresponding subroles: Navigation and region/container.
+ if (mRole == roles::LANDMARK) {
+ nsAtom* landmark = acc ? acc->LandmarkRole() : proxy->LandmarkRole();
+ // HTML Elements treated as landmarks, and ARIA landmarks.
+ if (landmark) {
+ if (landmark == nsGkAtoms::banner) return @"AXLandmarkBanner";
+ if (landmark == nsGkAtoms::complementary)
+ return @"AXLandmarkComplementary";
+ if (landmark == nsGkAtoms::contentinfo) return @"AXLandmarkContentInfo";
+ if (landmark == nsGkAtoms::main) return @"AXLandmarkMain";
+ if (landmark == nsGkAtoms::navigation) return @"AXLandmarkNavigation";
+ if (landmark == nsGkAtoms::search) return @"AXLandmarkSearch";
+ }
+
+ // None of the above, so assume DPub ARIA.
+ return @"AXLandmarkRegion";
+ }
+
+ // Now, deal with widget roles
+ nsStaticAtom* roleAtom = nullptr;
+
+ if (mRole == roles::DIALOG) {
+ roleAtom = [self ARIARole];
+
+ if (roleAtom == nsGkAtoms::alertdialog) {
+ return @"AXApplicationAlertDialog";
+ }
+ if (roleAtom == nsGkAtoms::dialog) {
+ return @"AXApplicationDialog";
+ }
+ }
+
+ if (mRole == roles::FORM) {
+ roleAtom = [self ARIARole];
+
+ if (roleAtom == nsGkAtoms::form) {
+ return @"AXLandmarkForm";
+ }
+ }
+
+#define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
+ ia2Role, androidClass, nameRule) \
+ case roles::geckoRole: \
+ if (![macSubrole isEqualToString:NSAccessibilityUnknownSubrole]) { \
+ return macSubrole; \
+ } else { \
+ break; \
+ }
+
+ switch (mRole) {
+#include "RoleMap.h"
+ }
+
+ // These are special. They map to roles::NOTHING
+ // and are instructed by the ARIA map to use the native host role.
+ roleAtom = [self ARIARole];
+
+ if (roleAtom == nsGkAtoms::log_) {
+ return @"AXApplicationLog";
+ }
+
+ if (roleAtom == nsGkAtoms::timer) {
+ return @"AXApplicationTimer";
+ }
+ // macOS added an AXSubrole value to distinguish generic AXGroup objects
+ // from those which are AXGroups as a result of an explicit ARIA role,
+ // such as the non-landmark, non-listitem text containers in DPub ARIA.
+ if (mRole == roles::FOOTNOTE || mRole == roles::SECTION) {
+ return @"AXApplicationGroup";
+ }
+
+ return NSAccessibilityUnknownSubrole;
+
+#undef ROLE
+}
+
+struct RoleDescrMap {
+ NSString* role;
+ const nsString description;
+};
+
+static const RoleDescrMap sRoleDescrMap[] = {
+ {@"AXApplicationAlert", u"alert"_ns},
+ {@"AXApplicationAlertDialog", u"alertDialog"_ns},
+ {@"AXApplicationDialog", u"dialog"_ns},
+ {@"AXApplicationLog", u"log"_ns},
+ {@"AXApplicationMarquee", u"marquee"_ns},
+ {@"AXApplicationStatus", u"status"_ns},
+ {@"AXApplicationTimer", u"timer"_ns},
+ {@"AXContentSeparator", u"separator"_ns},
+ {@"AXDefinition", u"definition"_ns},
+ {@"AXDetails", u"details"_ns},
+ {@"AXDocument", u"document"_ns},
+ {@"AXDocumentArticle", u"article"_ns},
+ {@"AXDocumentMath", u"math"_ns},
+ {@"AXDocumentNote", u"note"_ns},
+ {@"AXLandmarkApplication", u"application"_ns},
+ {@"AXLandmarkBanner", u"banner"_ns},
+ {@"AXLandmarkComplementary", u"complementary"_ns},
+ {@"AXLandmarkContentInfo", u"content"_ns},
+ {@"AXLandmarkMain", u"main"_ns},
+ {@"AXLandmarkNavigation", u"navigation"_ns},
+ {@"AXLandmarkRegion", u"region"_ns},
+ {@"AXLandmarkSearch", u"search"_ns},
+ {@"AXSearchField", u"searchTextField"_ns},
+ {@"AXSummary", u"summary"_ns},
+ {@"AXTabPanel", u"tabPanel"_ns},
+ {@"AXTerm", u"term"_ns},
+ {@"AXUserInterfaceTooltip", u"tooltip"_ns}};
+
+struct RoleDescrComparator {
+ const NSString* mRole;
+ explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
+ int operator()(const RoleDescrMap& aEntry) const {
+ return [mRole compare:aEntry.role];
+ }
+};
+
+- (NSString*)moxRoleDescription {
+ if (NSString* ariaRoleDescription =
+ utils::GetAccAttr(self, "roledescription")) {
+ if ([ariaRoleDescription length]) {
+ return ariaRoleDescription;
+ }
+ }
+
+ if (mRole == roles::FIGURE) return utils::LocalizedString(u"figure"_ns);
+
+ if (mRole == roles::HEADING) return utils::LocalizedString(u"heading"_ns);
+
+ if (mRole == roles::MARK) {
+ return utils::LocalizedString(u"highlight"_ns);
+ }
+
+ NSString* subrole = [self moxSubrole];
+
+ if (subrole) {
+ size_t idx = 0;
+ if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
+ RoleDescrComparator(subrole), &idx)) {
+ return utils::LocalizedString(sRoleDescrMap[idx].description);
+ }
+ }
+
+ return NSAccessibilityRoleDescription([self moxRole], subrole);
+}
+
+- (NSString*)moxLabel {
+ if ([self isExpired]) {
+ return nil;
+ }
+
+ Accessible* acc = mGeckoAccessible.AsAccessible();
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ nsAutoString name;
+
+ /* If our accessible is:
+ * 1. Named by invisible text, or
+ * 2. Has more than one labeling relation, or
+ * 3. Is a special role defined in providesLabelNotTitle
+ * ... return its name as a label (AXDescription).
+ */
+ if (acc) {
+ ENameValueFlag flag = acc->Name(name);
+ if (flag == eNameFromSubtree) {
+ return nil;
+ }
+ } else if (proxy) {
+ uint32_t flag = proxy->Name(name);
+ if (flag == eNameFromSubtree) {
+ return nil;
+ }
+ }
+
+ if (![self providesLabelNotTitle]) {
+ NSArray* relations = [self getRelationsByType:RelationType::LABELLED_BY];
+ if ([relations count] == 1) {
+ return nil;
+ }
+ }
+
+ return nsCocoaUtils::ToNSString(name);
+}
+
+- (NSString*)moxTitle {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // In some special cases we provide the name in the label (AXDescription).
+ if ([self providesLabelNotTitle]) {
+ return nil;
+ }
+
+ nsAutoString title;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->Name(title);
+ } else {
+ mGeckoAccessible.AsProxy()->Name(title);
+ }
+
+ return nsCocoaUtils::ToNSString(title);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (id)moxValue {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ nsAutoString value;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->Value(value);
+ } else {
+ mGeckoAccessible.AsProxy()->Value(value);
+ }
+
+ return nsCocoaUtils::ToNSString(value);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSString*)moxHelp {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // What needs to go here is actually the accDescription of an item.
+ // The MSAA acc_help method has nothing to do with this one.
+ nsAutoString helpText;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->Description(helpText);
+ } else {
+ mGeckoAccessible.AsProxy()->Description(helpText);
+ }
+
+ return nsCocoaUtils::ToNSString(helpText);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSWindow*)moxWindow {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // Get a pointer to the native window (NSWindow) we reside in.
+ NSWindow* nativeWindow = nil;
+ DocAccessible* docAcc = nullptr;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ docAcc = acc->Document();
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser();
+ if (outerDoc) docAcc = outerDoc->Document();
+ }
+
+ if (docAcc) nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow());
+
+ MOZ_ASSERT(nativeWindow, "Couldn't get native window");
+ return nativeWindow;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSNumber*)moxEnabled {
+ if ([self stateWithMask:states::UNAVAILABLE]) {
+ return @NO;
+ }
+
+ if (![self isRoot]) {
+ mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
+ if (![parent isRoot]) {
+ return @(![parent disableChild:self]);
+ }
+ }
+
+ return @YES;
+}
+
+- (NSNumber*)moxFocused {
+ return @([self stateWithMask:states::FOCUSED] != 0);
+}
+
+- (NSNumber*)moxSelected {
+ return @NO;
+}
+
+- (NSString*)moxARIACurrent {
+ if (![self stateWithMask:states::CURRENT]) {
+ return nil;
+ }
+
+ return utils::GetAccAttr(self, "current");
+}
+
+- (NSNumber*)moxARIAAtomic {
+ return @(utils::GetAccAttr(self, "atomic") != nil);
+}
+
+- (NSString*)moxARIALive {
+ return utils::GetAccAttr(self, "live");
+}
+
+- (NSString*)moxARIARelevant {
+ if (NSString* relevant = utils::GetAccAttr(self, "container-relevant")) {
+ return relevant;
+ }
+
+ // Default aria-relevant value
+ return @"additions text";
+}
+
+- (id)moxTitleUIElement {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSArray* relations = [self getRelationsByType:RelationType::LABELLED_BY];
+ if ([relations count] == 1) {
+ return [relations firstObject];
+ }
+
+ return nil;
+}
+
+- (NSString*)moxDOMIdentifier {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ nsAutoString id;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ if (acc->GetContent()) {
+ nsCoreUtils::GetID(acc->GetContent(), id);
+ }
+ } else {
+ mGeckoAccessible.AsProxy()->DOMNodeID(id);
+ }
+
+ return nsCocoaUtils::ToNSString(id);
+}
+
+- (NSNumber*)moxRequired {
+ return @([self stateWithMask:states::REQUIRED] != 0);
+}
+
+- (NSNumber*)moxElementBusy {
+ return @([self stateWithMask:states::BUSY] != 0);
+}
+
+- (NSArray*)moxLinkedUIElements {
+ return [self getRelationsByType:RelationType::FLOWS_TO];
+}
+
+- (NSArray*)moxARIAControls {
+ return [self getRelationsByType:RelationType::CONTROLLER_FOR];
+}
+
+- (mozAccessible*)topWebArea {
+ AccessibleOrProxy doc = [self geckoDocument];
+ while (!doc.IsNull()) {
+ if (doc.IsAccessible()) {
+ DocAccessible* docAcc = doc.AsAccessible()->AsDoc();
+ if (docAcc->DocumentNode()->GetBrowsingContext()->IsTopContent()) {
+ return GetNativeFromGeckoAccessible(docAcc);
+ }
+
+ doc = docAcc->ParentDocument();
+ } else {
+ DocAccessibleParent* docProxy = doc.AsProxy()->AsDoc();
+ if (docProxy->IsTopLevel()) {
+ return GetNativeFromGeckoAccessible(docProxy);
+ }
+ doc = docProxy->ParentDoc();
+ }
+ }
+
+ return nil;
+}
+
+- (void)handleRoleChanged:(mozilla::a11y::role)newRole {
+ mRole = newRole;
+ mARIARole = nullptr;
+
+ // For testing purposes
+ [self moxPostNotification:@"AXMozRoleChanged"];
+}
+
+- (id)moxEditableAncestor {
+ for (id element = self; [element conformsToProtocol:@protocol(MOXAccessible)];
+ element = [element moxUnignoredParent]) {
+ if ([element isKindOfClass:[mozTextAccessible class]]) {
+ return element;
+ }
+ }
+
+ return nil;
+}
+
+- (id)moxHighestEditableAncestor {
+ id highestAncestor = [self moxEditableAncestor];
+ while ([highestAncestor conformsToProtocol:@protocol(MOXAccessible)]) {
+ id ancestorParent = [highestAncestor moxUnignoredParent];
+ if (![ancestorParent conformsToProtocol:@protocol(MOXAccessible)]) {
+ break;
+ }
+
+ id higherAncestor = [ancestorParent moxEditableAncestor];
+
+ if (!higherAncestor) {
+ break;
+ }
+
+ highestAncestor = higherAncestor;
+ }
+
+ return highestAncestor;
+}
+
+- (id)moxFocusableAncestor {
+ // XXX: Checking focusable state up the chain can be expensive. For now,
+ // we can just return AXEditableAncestor since the main use case for this
+ // is rich text editing with links.
+ return [self moxEditableAncestor];
+}
+
+#ifndef RELEASE_OR_BETA
+- (NSString*)moxMozDebugDescription {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ NSMutableString* domInfo = [NSMutableString string];
+ if (NSString* tagName = utils::GetAccAttr(self, "tag")) {
+ [domInfo appendFormat:@" %@", tagName];
+ NSString* domID = [self moxDOMIdentifier];
+ if ([domID length]) {
+ [domInfo appendFormat:@"#%@", domID];
+ }
+ if (NSString* className = utils::GetAccAttr(self, "class")) {
+ [domInfo
+ appendFormat:@".%@",
+ [className stringByReplacingOccurrencesOfString:@" "
+ withString:@"."]];
+ }
+ }
+
+ return [NSString stringWithFormat:@"<%@: %p %@%@>",
+ NSStringFromClass([self class]), self,
+ [self moxRole], domInfo];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+#endif
+
+- (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate {
+ // Create our search object and set it up with the searchPredicate
+ // params. The init function does additional parsing. We pass a
+ // reference to the web area to use as a start element if one is not
+ // specified.
+ MOXSearchInfo* search =
+ [[MOXSearchInfo alloc] initWithParameters:searchPredicate andRoot:self];
+
+ return [search performSearch];
+}
+
+- (NSNumber*)moxUIElementCountForSearchPredicate:
+ (NSDictionary*)searchPredicate {
+ return [NSNumber
+ numberWithDouble:[[self moxUIElementsForSearchPredicate:searchPredicate]
+ count]];
+}
+
+- (void)moxSetFocused:(NSNumber*)focused {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if ([focused boolValue]) {
+ if (mGeckoAccessible.IsAccessible()) {
+ mGeckoAccessible.AsAccessible()->TakeFocus();
+ } else {
+ mGeckoAccessible.AsProxy()->TakeFocus();
+ }
+ }
+}
+
+- (void)moxPerformScrollToVisible {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ // Need strong ref because of MOZ_CAN_RUN_SCRIPT
+ RefPtr<Accessible> acc = mGeckoAccessible.AsAccessible();
+ acc->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+ } else {
+ mGeckoAccessible.AsProxy()->ScrollTo(
+ nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+ }
+}
+
+- (void)moxPerformShowMenu {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ nsIntRect bounds = mGeckoAccessible.IsAccessible()
+ ? mGeckoAccessible.AsAccessible()->Bounds()
+ : mGeckoAccessible.AsProxy()->Bounds();
+ // We don't need to convert this rect into mac coordinates because the
+ // mouse event synthesizer expects layout (gecko) coordinates.
+ LayoutDeviceIntRect geckoRect = LayoutDeviceIntRect::FromUnknownRect(bounds);
+
+ Accessible* rootAcc = mGeckoAccessible.IsAccessible()
+ ? mGeckoAccessible.AsAccessible()->RootAccessible()
+ : mGeckoAccessible.AsProxy()
+ ->OuterDocOfRemoteBrowser()
+ ->RootAccessible();
+ id objOrView =
+ GetObjectOrRepresentedView(GetNativeFromGeckoAccessible(rootAcc));
+
+ LayoutDeviceIntPoint p =
+ LayoutDeviceIntPoint(geckoRect.X() + (geckoRect.Width() / 2),
+ geckoRect.Y() + (geckoRect.Height() / 2));
+ nsIWidget* widget = [objOrView widget];
+ widget->SynthesizeNativeMouseEvent(p, NSEventTypeRightMouseDown, 0, nullptr);
+}
+
+- (void)moxPerformPress {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ mGeckoAccessible.AsAccessible()->DoAction(0);
+ } else {
+ mGeckoAccessible.AsProxy()->DoAction(0);
+ }
+
+ // Activating accessible may alter its state.
+ [self invalidateState];
+}
+
+#pragma mark -
+
+- (BOOL)disableChild:(mozAccessible*)child {
+ return NO;
+}
+
+- (void)maybePostLiveRegionChanged {
+ for (id element = self; [element conformsToProtocol:@protocol(MOXAccessible)];
+ element = [element moxUnignoredParent]) {
+ if ([element moxIsLiveRegion]) {
+ [element moxPostNotification:@"AXLiveRegionChanged"];
+ return;
+ }
+ }
+}
+
+- (NSArray<mozAccessible*>*)getRelationsByType:(RelationType)relationType {
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ NSMutableArray<mozAccessible*>* relations = [[NSMutableArray alloc] init];
+ Relation rel = acc->RelationByType(relationType);
+ while (Accessible* relAcc = rel.Next()) {
+ if (mozAccessible* relNative = GetNativeFromGeckoAccessible(relAcc)) {
+ [relations addObject:relNative];
+ }
+ }
+
+ return relations;
+ }
+
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ nsTArray<ProxyAccessible*> rel = proxy->RelationByType(relationType);
+ return utils::ConvertToNSArray(rel);
+}
+
+- (void)handleAccessibleTextChangeEvent:(NSString*)change
+ inserted:(BOOL)isInserted
+ inContainer:(const AccessibleOrProxy&)container
+ at:(int32_t)start {
+}
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ [self moxPostNotification:
+ NSAccessibilityFocusedUIElementChangedNotification];
+ break;
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
+ [self moxPostNotification:@"AXMenuOpened"];
+ break;
+ case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
+ [self moxPostNotification:@"AXMenuClosed"];
+ break;
+ case nsIAccessibleEvent::EVENT_SELECTION:
+ case nsIAccessibleEvent::EVENT_SELECTION_ADD:
+ case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
+ case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
+ [self moxPostNotification:
+ NSAccessibilitySelectedChildrenChangedNotification];
+ break;
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ if (![self stateWithMask:states::SELECTABLE_TEXT]) {
+ break;
+ }
+ // We consider any caret move event to be a selected text change event.
+ // So dispatching an event for EVENT_TEXT_SELECTION_CHANGED would be
+ // reduntant.
+ MOXTextMarkerDelegate* delegate =
+ static_cast<MOXTextMarkerDelegate*>([self moxTextMarkerDelegate]);
+ NSMutableDictionary* userInfo =
+ [[delegate selectionChangeInfo] mutableCopy];
+ userInfo[@"AXTextChangeElement"] = self;
+
+ mozAccessible* webArea = [self topWebArea];
+ [webArea
+ moxPostNotification:NSAccessibilitySelectedTextChangedNotification
+ withUserInfo:userInfo];
+ [self moxPostNotification:NSAccessibilitySelectedTextChangedNotification
+ withUserInfo:userInfo];
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED:
+ mIsLiveRegion = true;
+ [self moxPostNotification:@"AXLiveRegionCreated"];
+ break;
+ case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED:
+ mIsLiveRegion = false;
+ break;
+ case nsIAccessibleEvent::EVENT_REORDER:
+ [self maybePostLiveRegionChanged];
+ break;
+ case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
+ if (![self providesLabelNotTitle]) {
+ [self moxPostNotification:NSAccessibilityTitleChangedNotification];
+ }
+ [self maybePostLiveRegionChanged];
+ break;
+ }
+ }
+}
+
+- (void)expire {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateState];
+
+ mGeckoAccessible.SetBits(0);
+
+ [self moxPostNotification:NSAccessibilityUIElementDestroyedNotification];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)isExpired {
+ return !mGeckoAccessible.AsAccessible() && !mGeckoAccessible.AsProxy();
+}
+
+@end
diff --git a/accessible/mac/mozAccessibleProtocol.h b/accessible/mac/mozAccessibleProtocol.h
new file mode 100644
index 0000000000..3fc46fb7b6
--- /dev/null
+++ b/accessible/mac/mozAccessibleProtocol.h
@@ -0,0 +1,65 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+
+#import "mozView.h"
+
+/* This protocol's primary use is so widget/cocoa can talk back to us
+ properly.
+
+ ChildView owns the topmost mozRootAccessible, and needs to take care of
+ setting up that parent/child relationship.
+
+ This protocol is thus used to make sure it knows it's talking to us, and not
+ just some random |id|.
+*/
+
+@protocol mozAccessible
+
+// returns whether this accessible is the root accessible. there is one
+// root accessible per window.
+- (BOOL)isRoot;
+
+// some mozAccessibles implement accessibility support in place of another
+// object. for example, ChildView gets its support from us.
+//
+// instead of returning a mozAccessible to the OS when it wants an object, we
+// need to pass the view we represent, so the OS doesn't get confused and think
+// we return some random object.
+- (BOOL)hasRepresentedView;
+- (id)representedView;
+
+/*** general ***/
+
+// returns the accessible at the specified point.
+- (id)accessibilityHitTest:(NSPoint)point;
+
+// whether this element should be exposed to platform.
+- (BOOL)isAccessibilityElement;
+
+// currently focused UI element (possibly a child accessible)
+- (id)accessibilityFocusedUIElement;
+
+/*** attributes ***/
+
+// all supported attributes
+- (NSArray*)accessibilityAttributeNames;
+
+// value for given attribute.
+- (id)accessibilityAttributeValue:(NSString*)attribute;
+
+// whether a particular attribute can be modified
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+
+/*** actions ***/
+
+- (NSArray*)accessibilityActionNames;
+- (NSString*)accessibilityActionDescription:(NSString*)action;
+- (void)accessibilityPerformAction:(NSString*)action;
+
+@end
diff --git a/accessible/mac/mozActionElements.h b/accessible/mac/mozActionElements.h
new file mode 100644
index 0000000000..fce2778e87
--- /dev/null
+++ b/accessible/mac/mozActionElements.h
@@ -0,0 +1,83 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+/* Simple subclasses for things like checkboxes, buttons, etc. */
+
+@interface mozButtonAccessible : mozAccessible
+
+// override
+- (NSNumber*)moxHasPopup;
+
+// override
+- (NSString*)moxPopupValue;
+
+@end
+
+@interface mozPopupButtonAccessible : mozButtonAccessible
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (NSArray*)moxChildren;
+
+// override
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled;
+
+@end
+
+@interface mozCheckboxAccessible : mozButtonAccessible
+
+// override
+- (id)moxValue;
+
+// override
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled;
+
+@end
+
+// Accessible for a radio button
+@interface mozRadioButtonAccessible : mozCheckboxAccessible
+
+// override
+- (NSArray*)moxLinkedUIElements;
+
+@end
+
+/**
+ * Accessible for a PANE
+ */
+@interface mozPaneAccessible : mozAccessible
+
+// override
+- (NSArray*)moxChildren;
+
+@end
+
+/**
+ * Base accessible for an incrementable
+ */
+@interface mozIncrementableAccessible : mozAccessible
+
+// override
+- (void)moxPerformIncrement;
+
+// override
+- (void)moxPerformDecrement;
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+- (void)changeValueBySteps:(int)factor;
+
+@end
diff --git a/accessible/mac/mozActionElements.mm b/accessible/mac/mozActionElements.mm
new file mode 100644
index 0000000000..ee62b29fcb
--- /dev/null
+++ b/accessible/mac/mozActionElements.mm
@@ -0,0 +1,208 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozActionElements.h"
+
+#import "MacUtils.h"
+#include "Accessible-inl.h"
+#include "DocAccessible.h"
+#include "XULTabAccessible.h"
+#include "HTMLFormControlAccessible.h"
+
+#include "nsDeckFrame.h"
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+enum CheckboxValue {
+ // these constants correspond to the values in the OS
+ kUnchecked = 0,
+ kChecked = 1,
+ kMixed = 2
+};
+
+@implementation mozButtonAccessible
+
+- (NSNumber*)moxHasPopup {
+ return @([self stateWithMask:states::HASPOPUP] != 0);
+}
+
+- (NSString*)moxPopupValue {
+ if ([self stateWithMask:states::HASPOPUP] != 0) {
+ return utils::GetAccAttr(self, "haspopup");
+ }
+
+ return nil;
+}
+
+@end
+
+@implementation mozPopupButtonAccessible
+
+- (NSString*)moxTitle {
+ // Popup buttons don't have titles.
+ return @"";
+}
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ if (selector == @selector(moxHasPopup)) {
+ return YES;
+ }
+
+ return [super moxBlockSelector:selector];
+}
+
+- (NSArray*)moxChildren {
+ if ([self stateWithMask:states::EXPANDED] == 0) {
+ // If the popup button is collapsed don't return its children.
+ return @[];
+ }
+
+ return [super moxChildren];
+}
+
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
+ [super stateChanged:state isEnabled:enabled];
+
+ if (state == states::EXPANDED) {
+ // If the EXPANDED state is updated, fire AXMenu events on the
+ // popups child which is the actual menu.
+ if (mozAccessible* popup = (mozAccessible*)[self childAt:0]) {
+ [popup moxPostNotification:(enabled ? @"AXMenuOpened" : @"AXMenuClosed")];
+ }
+ }
+}
+
+@end
+
+@implementation mozRadioButtonAccessible
+
+- (NSArray*)moxLinkedUIElements {
+ return [[self getRelationsByType:RelationType::MEMBER_OF]
+ arrayByAddingObjectsFromArray:[super moxLinkedUIElements]];
+}
+
+@end
+
+@implementation mozCheckboxAccessible
+
+- (int)isChecked {
+ // check if we're checked or in a mixed state
+ uint64_t state =
+ [self stateWithMask:(states::CHECKED | states::PRESSED | states::MIXED)];
+ if (state & (states::CHECKED | states::PRESSED)) {
+ return kChecked;
+ }
+
+ if (state & states::MIXED) {
+ return kMixed;
+ }
+
+ return kUnchecked;
+}
+
+- (id)moxValue {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ return [NSNumber numberWithInt:[self isChecked]];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
+ [super stateChanged:state isEnabled:enabled];
+
+ if (state & (states::CHECKED | states::PRESSED | states::MIXED)) {
+ [self moxPostNotification:NSAccessibilityValueChangedNotification];
+ }
+}
+
+@end
+
+@implementation mozPaneAccessible
+
+- (NSArray*)moxChildren {
+ if (!mGeckoAccessible.AsAccessible()) return nil;
+
+ nsDeckFrame* deckFrame =
+ do_QueryFrame(mGeckoAccessible.AsAccessible()->GetFrame());
+ nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
+
+ Accessible* selectedAcc = nullptr;
+ if (selectedFrame) {
+ nsINode* node = selectedFrame->GetContent();
+ selectedAcc =
+ mGeckoAccessible.AsAccessible()->Document()->GetAccessible(node);
+ }
+
+ if (selectedAcc) {
+ mozAccessible* curNative = GetNativeFromGeckoAccessible(selectedAcc);
+ if (curNative)
+ return
+ [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil];
+ }
+
+ return nil;
+}
+
+@end
+
+@implementation mozIncrementableAccessible
+
+- (void)moxPerformIncrement {
+ [self changeValueBySteps:1];
+}
+
+- (void)moxPerformDecrement {
+ [self changeValueBySteps:-1];
+}
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
+ case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
+ [self moxPostNotification:NSAccessibilityValueChangedNotification];
+ break;
+ default:
+ [super handleAccessibleEvent:eventType];
+ break;
+ }
+}
+
+/*
+ * Updates the accessible's current value by (factor * step).
+ * If incrementing factor should be positive, if decrementing
+ * factor should be negative.
+ */
+
+- (void)changeValueBySteps:(int)factor {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ double newVal = acc->CurValue() + (acc->Step() * factor);
+ double min = acc->MinValue();
+ double max = acc->MaxValue();
+ if ((IsNaN(min) || newVal >= min) && (IsNaN(max) || newVal <= max)) {
+ acc->SetCurValue(newVal);
+ }
+ } else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ double newVal = proxy->CurValue() + (proxy->Step() * factor);
+ double min = proxy->MinValue();
+ double max = proxy->MaxValue();
+ // Because min and max are not required attributes, we first check
+ // if the value is undefined. If this check fails,
+ // the value is defined, and we we verify our new value falls
+ // within the bound (inclusive).
+ if ((IsNaN(min) || newVal >= min) && (IsNaN(max) || newVal <= max)) {
+ proxy->SetCurValue(newVal);
+ }
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.h b/accessible/mac/mozHTMLAccessible.h
new file mode 100644
index 0000000000..7288f8a108
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.h
@@ -0,0 +1,48 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface mozHeadingAccessible : mozAccessible
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (id)moxValue;
+
+@end
+
+@interface mozLinkAccessible : mozAccessible
+
+// override
+- (id)moxValue;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSURL*)moxURL;
+
+// override
+- (NSNumber*)moxVisited;
+
+@end
+
+@interface MOXSummaryAccessible : mozAccessible
+
+// override
+- (NSNumber*)moxExpanded;
+
+@end
+
+@interface MOXListItemAccessible : mozAccessible
+
+// override
+- (NSString*)moxTitle;
+
+@end
diff --git a/accessible/mac/mozHTMLAccessible.mm b/accessible/mac/mozHTMLAccessible.mm
new file mode 100644
index 0000000000..aee3886ec6
--- /dev/null
+++ b/accessible/mac/mozHTMLAccessible.mm
@@ -0,0 +1,97 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozHTMLAccessible.h"
+
+#import "Accessible-inl.h"
+#import "HyperTextAccessible.h"
+
+#import "nsCocoaUtils.h"
+
+using namespace mozilla::a11y;
+
+@implementation mozHeadingAccessible
+
+- (NSString*)moxTitle {
+ nsAutoString title;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ mozilla::ErrorResult rv;
+ // XXX use the flattening API when there are available
+ // see bug 768298
+ acc->GetContent()->GetTextContent(title, rv);
+ } else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ proxy->Title(title);
+ }
+
+ return nsCocoaUtils::ToNSString(title);
+}
+
+- (id)moxValue {
+ GroupPos groupPos;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ groupPos = acc->GroupPosition();
+ } else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ groupPos = proxy->GroupPosition();
+ }
+
+ return [NSNumber numberWithInt:groupPos.level];
+}
+
+@end
+
+@implementation mozLinkAccessible
+
+- (NSString*)moxValue {
+ return @"";
+}
+
+- (NSURL*)moxURL {
+ nsAutoString value;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->Value(value);
+ } else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ proxy->Value(value);
+ }
+
+ NSString* urlString = value.IsEmpty() ? nil : nsCocoaUtils::ToNSString(value);
+ if (!urlString) return nil;
+
+ return [NSURL URLWithString:urlString];
+}
+
+- (NSNumber*)moxVisited {
+ return @([self stateWithMask:states::TRAVERSED] != 0);
+}
+
+- (NSString*)moxRole {
+ // If this is not LINKED, just expose this as a generic group accessible.
+ // Chrome and Safari expose this as a childless AXStaticText, but
+ // the HTML Accessibility API Mappings spec says this should be an AXGroup.
+ if (![self stateWithMask:states::LINKED]) {
+ return NSAccessibilityGroupRole;
+ }
+
+ return [super moxRole];
+}
+
+@end
+
+@implementation MOXSummaryAccessible
+
+- (NSNumber*)moxExpanded {
+ return @([self stateWithMask:states::EXPANDED] != 0);
+}
+
+@end
+
+@implementation MOXListItemAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+@end
diff --git a/accessible/mac/mozRootAccessible.h b/accessible/mac/mozRootAccessible.h
new file mode 100644
index 0000000000..3b0f4fc25e
--- /dev/null
+++ b/accessible/mac/mozRootAccessible.h
@@ -0,0 +1,58 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+// our protocol that we implement (so cocoa widgets can talk to us)
+#import "mozAccessibleProtocol.h"
+
+/*
+ The root accessible. There is one per window.
+ Created by the RootAccessibleWrap.
+*/
+@interface mozRootAccessible : mozAccessible {
+ // the mozView that we're representing.
+ // all outside communication goes through the mozView.
+ // in reality, it's just piping all calls to us, and we're
+ // doing its dirty work!
+ //
+ // whenever someone asks who we are (e.g., a child asking
+ // for its parent, or our parent asking for its child), we'll
+ // respond the mozView. it is absolutely necessary for third-
+ // party tools that we do this!
+ //
+ // /hwaara
+ id<mozView, mozAccessible> mParallelView; // weak ref
+}
+
+// override
+- (id)initWithAccessible:(mozilla::a11y::AccessibleOrProxy)aAccOrProxy;
+
+#pragma mark - MOXAccessible
+
+// override
+- (NSNumber*)moxMain;
+
+// override
+- (NSNumber*)moxMinimized;
+
+// override
+- (id)moxUnignoredParent;
+
+#pragma mark - mozAccessible/widget
+
+// override
+- (BOOL)hasRepresentedView;
+
+// override
+- (id)representedView;
+
+// override
+- (BOOL)isRoot;
+
+@end
diff --git a/accessible/mac/mozRootAccessible.mm b/accessible/mac/mozRootAccessible.mm
new file mode 100644
index 0000000000..910509f09d
--- /dev/null
+++ b/accessible/mac/mozRootAccessible.mm
@@ -0,0 +1,83 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "RootAccessibleWrap.h"
+
+#import "mozRootAccessible.h"
+
+#import "mozView.h"
+
+// This must be included last:
+#include "nsObjCExceptions.h"
+
+using namespace mozilla::a11y;
+
+static id<mozAccessible, mozView> getNativeViewFromRootAccessible(
+ Accessible* aAccessible) {
+ RootAccessibleWrap* root =
+ static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
+ id<mozAccessible, mozView> nativeView = nil;
+ root->GetNativeWidget((void**)&nativeView);
+ return nativeView;
+}
+
+#pragma mark -
+
+@implementation mozRootAccessible
+
+- (id)initWithAccessible:(mozilla::a11y::AccessibleOrProxy)aAccOrProxy {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_ASSERT(!aAccOrProxy.IsProxy(), "mozRootAccessible is never a proxy");
+
+ mParallelView = getNativeViewFromRootAccessible(aAccOrProxy.AsAccessible());
+
+ return [super initWithAccessible:aAccOrProxy];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (NSNumber*)moxMain {
+ return @([[self moxWindow] isMainWindow]);
+}
+
+- (NSNumber*)moxMinimized {
+ return @([[self moxWindow] isMiniaturized]);
+}
+
+// return the AXParent that our parallell NSView tells us about.
+- (id)moxUnignoredParent {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ // If there is no represented view (eg. headless), this will return nil.
+ return [[self representedView]
+ accessibilityAttributeValue:NSAccessibilityParentAttribute];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)hasRepresentedView {
+ return YES;
+}
+
+// this will return our parallell NSView. see mozDocAccessible.h
+- (id)representedView {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
+
+ MOZ_ASSERT(mParallelView,
+ "root accessible does not have a native parallel view.");
+
+ return mParallelView;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
+}
+
+- (BOOL)isRoot {
+ return YES;
+}
+
+@end
diff --git a/accessible/mac/mozSelectableElements.h b/accessible/mac/mozSelectableElements.h
new file mode 100644
index 0000000000..2baf07c942
--- /dev/null
+++ b/accessible/mac/mozSelectableElements.h
@@ -0,0 +1,125 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import <Cocoa/Cocoa.h>
+#import "mozAccessible.h"
+
+@interface mozSelectableAccessible : mozAccessible
+
+- (NSArray*)selectableChildren;
+
+// override
+- (void)moxSetSelectedChildren:(NSArray*)selectedChildren;
+
+// override
+- (NSArray*)moxSelectedChildren;
+
+@end
+
+@interface mozSelectableChildAccessible : mozAccessible
+
+// override
+- (NSNumber*)moxSelected;
+
+// override
+- (void)moxSetSelected:(NSNumber*)selected;
+
+@end
+
+@interface mozTabGroupAccessible : mozSelectableAccessible
+
+// override
+- (NSArray*)moxTabs;
+
+// override
+- (NSArray*)moxContents;
+
+// override
+- (id)moxValue;
+
+@end
+
+@interface mozTabAccessible : mozSelectableChildAccessible
+
+// override
+- (NSString*)moxRoleDescription;
+
+// override
+- (id)moxValue;
+
+@end
+
+@interface mozListboxAccessible : mozSelectableAccessible
+
+// override
+- (BOOL)moxIgnoreChild:(mozAccessible*)child;
+
+// override
+- (BOOL)disableChild:(mozAccessible*)child;
+
+// override
+- (NSString*)moxOrientation;
+
+@end
+
+@interface mozOptionAccessible : mozSelectableChildAccessible
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (id)moxValue;
+
+@end
+
+@interface mozMenuAccessible : mozSelectableAccessible {
+ BOOL mIsOpened;
+}
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (NSString*)moxLabel;
+
+// override
+- (NSArray*)moxVisibleChildren;
+
+// override
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent;
+
+// override
+- (id)moxTitleUIElement;
+
+// override
+- (void)moxPostNotification:(NSString*)notification;
+
+// override
+- (void)expire;
+
+- (BOOL)isOpened;
+
+@end
+
+@interface mozMenuItemAccessible : mozSelectableChildAccessible
+
+// override
+- (NSString*)moxLabel;
+
+// override
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent;
+
+// override
+- (NSString*)moxMenuItemMarkChar;
+
+// override
+- (NSNumber*)moxSelected;
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+@end
diff --git a/accessible/mac/mozSelectableElements.mm b/accessible/mac/mozSelectableElements.mm
new file mode 100644
index 0000000000..fdfeda198e
--- /dev/null
+++ b/accessible/mac/mozSelectableElements.mm
@@ -0,0 +1,332 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozSelectableElements.h"
+#import "MOXWebAreaAccessible.h"
+#import "MacUtils.h"
+#include "Accessible-inl.h"
+#include "nsCocoaUtils.h"
+
+using namespace mozilla::a11y;
+
+@implementation mozSelectableAccessible
+
+/**
+ * Return the mozAccessibles that are selectable.
+ */
+- (NSArray*)selectableChildren {
+ NSArray* toFilter;
+ if ([self isKindOfClass:[mozMenuAccessible class]]) {
+ // If we are a menu, our children are only selectable if they are visible
+ // so we filter this array instead of our unignored children list, which may
+ // contain invisible items.
+ toFilter = [static_cast<mozMenuAccessible*>(self) moxVisibleChildren];
+ } else {
+ toFilter = [self moxUnignoredChildren];
+ }
+ return [toFilter
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
+ mozAccessible* child,
+ NSDictionary* bindings) {
+ return [child isKindOfClass:[mozSelectableChildAccessible class]];
+ }]];
+}
+
+- (void)moxSetSelectedChildren:(NSArray*)selectedChildren {
+ for (id child in [self selectableChildren]) {
+ BOOL selected =
+ [selectedChildren indexOfObjectIdenticalTo:child] != NSNotFound;
+ [child moxSetSelected:@(selected)];
+ }
+}
+
+/**
+ * Return the mozAccessibles that are actually selected.
+ */
+- (NSArray*)moxSelectedChildren {
+ return [[self selectableChildren]
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
+ mozAccessible* child,
+ NSDictionary* bindings) {
+ // Return mozSelectableChildAccessibles that have are selected (truthy
+ // value).
+ return [[(mozSelectableChildAccessible*)child moxSelected] boolValue];
+ }]];
+}
+
+@end
+
+@implementation mozSelectableChildAccessible
+
+- (NSNumber*)moxSelected {
+ return @([self stateWithMask:states::SELECTED] != 0);
+}
+
+- (void)moxSetSelected:(NSNumber*)selected {
+ // Get SELECTABLE and UNAVAILABLE state.
+ uint64_t state =
+ [self stateWithMask:(states::SELECTABLE | states::UNAVAILABLE)];
+ if ((state & states::SELECTABLE) == 0 || (state & states::UNAVAILABLE) != 0) {
+ // The object is either not selectable or is unavailable. Don't do anything.
+ return;
+ }
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->SetSelected([selected boolValue]);
+ } else {
+ mGeckoAccessible.AsProxy()->SetSelected([selected boolValue]);
+ }
+
+ // We need to invalidate the state because the accessibility service
+ // may check the selected attribute synchornously and not wait for
+ // selection events.
+ [self invalidateState];
+}
+
+@end
+
+@implementation mozTabGroupAccessible
+
+- (NSArray*)moxTabs {
+ return [self selectableChildren];
+}
+
+- (NSArray*)moxContents {
+ return [self moxUnignoredChildren];
+}
+
+- (id)moxValue {
+ // The value of a tab group is its selected child. In the case
+ // of multiple selections this will return the first one.
+ return [[self moxSelectedChildren] firstObject];
+}
+
+@end
+
+@implementation mozTabAccessible
+
+- (NSString*)moxRoleDescription {
+ return utils::LocalizedString(u"tab"_ns);
+}
+
+- (id)moxValue {
+ // Retuens 1 if item is selected, 0 if not.
+ return [self moxSelected];
+}
+
+@end
+
+@implementation mozListboxAccessible
+
+- (BOOL)moxIgnoreChild:(mozAccessible*)child {
+ if (!child || child->mRole == roles::GROUPING) {
+ return YES;
+ }
+
+ return [super moxIgnoreChild:child];
+}
+
+- (BOOL)disableChild:(mozAccessible*)child {
+ return ![child isKindOfClass:[mozSelectableChildAccessible class]];
+}
+
+- (NSString*)moxOrientation {
+ return NSAccessibilityUnknownOrientationValue;
+}
+
+@end
+
+@implementation mozOptionAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+- (id)moxValue {
+ // Swap title and value of option so it behaves more like a AXStaticText.
+ return [super moxTitle];
+}
+
+@end
+
+@implementation mozMenuAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+- (NSString*)moxLabel {
+ return @"";
+}
+
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
+ // This helps us generate the correct moxChildren array for
+ // a sub menu -- that returned array should contain all
+ // menu items, regardless of if they are visible or not.
+ // Because moxChildren does ignore filtering, and because
+ // our base ignore method filters out invisible accessibles,
+ // we override this method.
+ if ([parent isKindOfClass:[MOXWebAreaAccessible class]] ||
+ [parent isKindOfClass:[MOXRootGroup class]]) {
+ // We are a top level menu. Check our visibility the normal way
+ return [super moxIgnoreWithParent:parent];
+ }
+
+ if ([parent isKindOfClass:[mozMenuItemAccessible class]] &&
+ [parent geckoAccessible].Role() == roles::PARENT_MENUITEM) {
+ // We are a submenu. If our parent menu item is in an open menu
+ // we should not be ignored
+ id grandparent = [parent moxParent];
+ if ([grandparent isKindOfClass:[mozMenuAccessible class]]) {
+ mozMenuAccessible* parentMenu =
+ static_cast<mozMenuAccessible*>(grandparent);
+ return ![parentMenu isOpened];
+ }
+ }
+
+ // Otherwise, we call into our superclass's ignore method
+ // to handle menus that are not submenus
+ return [super moxIgnoreWithParent:parent];
+}
+
+- (NSArray*)moxVisibleChildren {
+ // VO expects us to expose two lists of children on menus: all children
+ // (done in moxUnignoredChildren), and children which are visible (here).
+ // We implement ignoreWithParent for both menus and menu items
+ // to ensure moxUnignoredChildren returns a complete list of children
+ // regardless of visibility, see comments in those methods for additional
+ // info.
+ return [[self moxChildren]
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
+ mozAccessible* child,
+ NSDictionary* bindings) {
+ if (Accessible* acc = [child geckoAccessible].AsAccessible()) {
+ if (acc->IsContent() && acc->GetContent()->IsXULElement()) {
+ return ((acc->VisibilityState() & states::INVISIBLE) == 0);
+ }
+ }
+ return true;
+ }]];
+}
+
+- (id)moxTitleUIElement {
+ id parent = [self moxUnignoredParent];
+ if (parent && [parent isKindOfClass:[mozAccessible class]]) {
+ return parent;
+ }
+
+ return nil;
+}
+
+- (void)moxPostNotification:(NSString*)notification {
+ if ([notification isEqualToString:@"AXMenuOpened"]) {
+ mIsOpened = YES;
+ } else if ([notification isEqualToString:@"AXMenuClosed"]) {
+ mIsOpened = NO;
+ }
+
+ [super moxPostNotification:notification];
+}
+
+- (void)expire {
+ if (mIsOpened) {
+ // VO needs to receive a menu closed event when the menu goes away.
+ // If the menu is being destroyed, send a menu closed event first.
+ [self moxPostNotification:@"AXMenuClosed"];
+ }
+
+ [super expire];
+}
+
+- (BOOL)isOpened {
+ return mIsOpened;
+}
+
+@end
+
+@implementation mozMenuItemAccessible
+
+- (NSString*)moxLabel {
+ return @"";
+}
+
+- (BOOL)moxIgnoreWithParent:(mozAccessible*)parent {
+ // This helps us generate the correct moxChildren array for
+ // a mozMenuAccessible; the returned array should contain all
+ // menu items, regardless of if they are visible or not.
+ // Because moxChildren does ignore filtering, and because
+ // our base ignore method filters out invisible accessibles,
+ // we override this method.
+ AccessibleOrProxy parentAcc = [parent geckoAccessible];
+ if (!parentAcc.IsNull()) {
+ AccessibleOrProxy grandparentAcc = parentAcc.Parent();
+ if (mozAccessible* directGrandparent =
+ GetNativeFromGeckoAccessible(grandparentAcc)) {
+ if ([directGrandparent isKindOfClass:[MOXWebAreaAccessible class]]) {
+ return [parent moxIgnoreWithParent:directGrandparent];
+ }
+ }
+ }
+
+ id grandparent = [parent moxParent];
+ if ([grandparent isKindOfClass:[mozMenuItemAccessible class]]) {
+ mozMenuItemAccessible* acc =
+ static_cast<mozMenuItemAccessible*>(grandparent);
+ if ([acc geckoAccessible].Role() == roles::PARENT_MENUITEM) {
+ mozMenuAccessible* parentMenu = static_cast<mozMenuAccessible*>(parent);
+ // if we are a menu item in a submenu, display only when
+ // parent menu item is open
+ return ![parentMenu isOpened];
+ }
+ }
+
+ // Otherwise, we call into our superclass's method to handle
+ // menuitems that are not within submenus
+ return [super moxIgnoreWithParent:parent];
+}
+
+- (NSString*)moxMenuItemMarkChar {
+ Accessible* acc = mGeckoAccessible.AsAccessible();
+ if (acc && acc->IsContent() &&
+ acc->GetContent()->IsXULElement(nsGkAtoms::menuitem)) {
+ // We need to provide a marker character. This is the visible "√" you see
+ // on dropdown menus. In our a11y tree this is a single child text node
+ // of the menu item.
+ // We do this only with XUL menuitems that conform to the native theme, and
+ // not with aria menu items that might have a pseudo element or something.
+ if (acc->ChildCount() == 1 &&
+ acc->FirstChild()->Role() == roles::STATICTEXT) {
+ nsAutoString marker;
+ acc->FirstChild()->Name(marker);
+ if (marker.Length() == 1) {
+ return nsCocoaUtils::ToNSString(marker);
+ }
+ }
+ }
+
+ return nil;
+}
+
+- (NSNumber*)moxSelected {
+ // Our focused state is equivelent to native selected states for menus.
+ return @([self stateWithMask:states::FOCUSED] != 0);
+}
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ switch (eventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ // Our focused state is equivelent to native selected states for menus.
+ mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
+ [parent moxPostNotification:
+ NSAccessibilitySelectedChildrenChangedNotification];
+ break;
+ }
+
+ [super handleAccessibleEvent:eventType];
+}
+
+@end
diff --git a/accessible/mac/mozTableAccessible.h b/accessible/mac/mozTableAccessible.h
new file mode 100644
index 0000000000..756359e5cf
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.h
@@ -0,0 +1,165 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface mozColumnContainer : MOXAccessibleBase {
+ uint32_t mIndex;
+ mozAccessible* mParent;
+ NSMutableArray* mChildren;
+}
+
+// override
+- (id)initWithIndex:(uint32_t)aIndex andParent:(mozAccessible*)aParent;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSString*)moxRoleDescription;
+
+// override
+- (mozAccessible*)moxParent;
+
+// override
+- (NSArray*)moxUnignoredChildren;
+
+// override
+- (void)dealloc;
+
+// override
+- (void)expire;
+
+// override
+- (BOOL)isExpired;
+
+- (void)invalidateChildren;
+
+@end
+
+@interface mozTablePartAccessible : mozAccessible
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (NSString*)moxRole;
+
+- (BOOL)isLayoutTablePart;
+
+@end
+
+@interface mozTableAccessible : mozTablePartAccessible {
+ NSMutableArray* mColContainers;
+}
+
+- (void)invalidateColumns;
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+// override
+- (void)dealloc;
+
+// override
+- (NSNumber*)moxRowCount;
+
+// override
+- (NSNumber*)moxColumnCount;
+
+// override
+- (NSArray*)moxRows;
+
+// override
+- (NSArray*)moxColumns;
+
+// override
+- (NSArray*)moxUnignoredChildren;
+
+// override
+- (NSArray*)moxColumnHeaderUIElements;
+
+// override
+- (id)moxCellForColumnAndRow:(NSArray*)columnAndRow;
+
+@end
+
+@interface mozTableRowAccessible : mozTablePartAccessible
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+// override
+- (NSNumber*)moxIndex;
+
+@end
+
+@interface mozTableCellAccessible : mozTablePartAccessible
+
+// override
+- (NSValue*)moxRowIndexRange;
+
+// override
+- (NSValue*)moxColumnIndexRange;
+
+// override
+- (NSArray*)moxRowHeaderUIElements;
+
+// override
+- (NSArray*)moxColumnHeaderUIElements;
+
+@end
+
+@interface mozOutlineAccessible : mozAccessible
+
+// override
+- (NSArray*)moxRows;
+
+// override
+- (NSArray*)moxColumns;
+
+// override
+- (NSArray*)moxSelectedRows;
+
+// override
+- (NSString*)moxOrientation;
+
+@end
+
+@interface mozOutlineRowAccessible : mozTableRowAccessible
+
+// override
+- (BOOL)isLayoutTablePart;
+
+// override
+- (NSNumber*)moxDisclosing;
+
+// override
+- (void)moxSetDisclosing:(NSNumber*)disclosing;
+
+// override
+- (NSNumber*)moxExpanded;
+
+// override
+- (id)moxDisclosedByRow;
+
+// override
+- (NSNumber*)moxDisclosureLevel;
+
+// override
+- (NSArray*)moxDisclosedRows;
+
+// override
+- (NSNumber*)moxIndex;
+
+// override
+- (NSString*)moxLabel;
+
+// override
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled;
+
+@end
diff --git a/accessible/mac/mozTableAccessible.mm b/accessible/mac/mozTableAccessible.mm
new file mode 100644
index 0000000000..292970323a
--- /dev/null
+++ b/accessible/mac/mozTableAccessible.mm
@@ -0,0 +1,591 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozTableAccessible.h"
+#import "nsCocoaUtils.h"
+#import "MacUtils.h"
+#import "RotorRules.h"
+
+#include "AccIterator.h"
+#include "Accessible.h"
+#include "TableAccessible.h"
+#include "TableCellAccessible.h"
+#include "XULTreeAccessible.h"
+#include "Pivot.h"
+#include "Relation.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+@implementation mozColumnContainer
+
+- (id)initWithIndex:(uint32_t)aIndex andParent:(mozAccessible*)aParent {
+ self = [super init];
+ mIndex = aIndex;
+ mParent = aParent;
+ return self;
+}
+
+- (NSString*)moxRole {
+ return NSAccessibilityColumnRole;
+}
+
+- (NSString*)moxRoleDescription {
+ return NSAccessibilityRoleDescription(NSAccessibilityColumnRole, nil);
+}
+
+- (mozAccessible*)moxParent {
+ return mParent;
+}
+
+- (NSArray*)moxUnignoredChildren {
+ if (mChildren) return mChildren;
+
+ mChildren = [[NSMutableArray alloc] init];
+
+ if (Accessible* acc = [mParent geckoAccessible].AsAccessible()) {
+ TableAccessible* table = acc->AsTable();
+ MOZ_ASSERT(table, "Got null table when fetching column children!");
+ uint32_t numRows = table->RowCount();
+
+ for (uint32_t j = 0; j < numRows; j++) {
+ Accessible* cell = table->CellAt(j, mIndex);
+ mozAccessible* nativeCell =
+ cell ? GetNativeFromGeckoAccessible(cell) : nil;
+ if ([nativeCell isAccessibilityElement]) {
+ [mChildren addObject:nativeCell];
+ }
+ }
+
+ } else if (ProxyAccessible* proxy = [mParent geckoAccessible].AsProxy()) {
+ uint32_t numRows = proxy->TableRowCount();
+
+ for (uint32_t j = 0; j < numRows; j++) {
+ ProxyAccessible* cell = proxy->TableCellAt(j, mIndex);
+ mozAccessible* nativeCell =
+ cell ? GetNativeFromGeckoAccessible(cell) : nil;
+ if ([nativeCell isAccessibilityElement]) {
+ [mChildren addObject:nativeCell];
+ }
+ }
+ }
+
+ return mChildren;
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateChildren];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (void)expire {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateChildren];
+
+ mParent = nil;
+
+ [super expire];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (BOOL)isExpired {
+ MOZ_ASSERT((mChildren == nil && mParent == nil) == mIsExpired);
+
+ return [super isExpired];
+}
+
+- (void)invalidateChildren {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // make room for new children
+ if (mChildren) {
+ [mChildren release];
+ mChildren = nil;
+ }
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+@implementation mozTablePartAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+- (NSString*)moxRole {
+ return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super moxRole];
+}
+
+- (BOOL)isLayoutTablePart {
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ while (acc) {
+ if (acc->Role() == roles::TREE_TABLE) {
+ return false;
+ }
+ if (acc->IsTable()) {
+ return acc->AsTable()->IsProbablyLayoutTable();
+ }
+ acc = acc->Parent();
+ }
+ return false;
+ }
+
+ if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ while (proxy) {
+ if (proxy->Role() == roles::TREE_TABLE) {
+ return false;
+ }
+ if (proxy->IsTable()) {
+ return proxy->TableIsProbablyForLayout();
+ }
+ proxy = proxy->Parent();
+ }
+ }
+
+ return false;
+}
+
+@end
+
+@implementation mozTableAccessible
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
+ [self invalidateColumns];
+ }
+
+ [super handleAccessibleEvent:eventType];
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [self invalidateColumns];
+ [super dealloc];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+- (NSNumber*)moxRowCount {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ return mGeckoAccessible.IsAccessible()
+ ? @(mGeckoAccessible.AsAccessible()->AsTable()->RowCount())
+ : @(mGeckoAccessible.AsProxy()->TableRowCount());
+}
+
+- (NSNumber*)moxColumnCount {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ return mGeckoAccessible.IsAccessible()
+ ? @(mGeckoAccessible.AsAccessible()->AsTable()->ColCount())
+ : @(mGeckoAccessible.AsProxy()->TableColumnCount());
+}
+
+- (NSArray*)moxRows {
+ // Create a new array with the list of table rows.
+ return [[self moxChildren]
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
+ mozAccessible* child,
+ NSDictionary* bindings) {
+ return [child isKindOfClass:[mozTableRowAccessible class]];
+ }]];
+}
+
+- (NSArray*)moxColumns {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mColContainers) {
+ return mColContainers;
+ }
+
+ mColContainers = [[NSMutableArray alloc] init];
+ uint32_t numCols = 0;
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ numCols = acc->AsTable()->ColCount();
+ } else {
+ numCols = mGeckoAccessible.AsProxy()->TableColumnCount();
+ }
+
+ for (uint32_t i = 0; i < numCols; i++) {
+ mozColumnContainer* container =
+ [[mozColumnContainer alloc] initWithIndex:i andParent:self];
+ [mColContainers addObject:container];
+ }
+
+ return mColContainers;
+}
+
+- (NSArray*)moxUnignoredChildren {
+ if (![self isLayoutTablePart]) {
+ return [[super moxUnignoredChildren]
+ arrayByAddingObjectsFromArray:[self moxColumns]];
+ }
+
+ return [super moxUnignoredChildren];
+}
+
+- (NSArray*)moxColumnHeaderUIElements {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ uint32_t numCols = 0;
+ TableAccessible* table = nullptr;
+
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ table = mGeckoAccessible.AsAccessible()->AsTable();
+ numCols = table->ColCount();
+ } else {
+ numCols = mGeckoAccessible.AsProxy()->TableColumnCount();
+ }
+
+ NSMutableArray* colHeaders =
+ [[NSMutableArray alloc] initWithCapacity:numCols];
+
+ for (uint32_t i = 0; i < numCols; i++) {
+ AccessibleOrProxy cell;
+ if (table) {
+ cell = table->CellAt(0, i);
+ } else {
+ cell = mGeckoAccessible.AsProxy()->TableCellAt(0, i);
+ }
+
+ if (!cell.IsNull() && cell.Role() == roles::COLUMNHEADER) {
+ mozAccessible* colHeader = GetNativeFromGeckoAccessible(cell);
+ [colHeaders addObject:colHeader];
+ }
+ }
+
+ return colHeaders;
+}
+
+- (id)moxCellForColumnAndRow:(NSArray*)columnAndRow {
+ if (columnAndRow == nil || [columnAndRow count] != 2) {
+ return nil;
+ }
+
+ uint32_t col = [[columnAndRow objectAtIndex:0] unsignedIntValue];
+ uint32_t row = [[columnAndRow objectAtIndex:1] unsignedIntValue];
+
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ AccessibleOrProxy cell;
+ if (mGeckoAccessible.IsAccessible()) {
+ cell = mGeckoAccessible.AsAccessible()->AsTable()->CellAt(row, col);
+ } else {
+ cell = mGeckoAccessible.AsProxy()->TableCellAt(row, col);
+ }
+
+ if (cell.IsNull()) {
+ return nil;
+ }
+
+ return GetNativeFromGeckoAccessible(cell);
+}
+
+- (void)invalidateColumns {
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+ if (mColContainers) {
+ [mColContainers release];
+ mColContainers = nil;
+ }
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@end
+
+@implementation mozTableRowAccessible
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ if (eventType == nsIAccessibleEvent::EVENT_REORDER) {
+ id parent = [self moxParent];
+ if ([parent isKindOfClass:[mozTableAccessible class]]) {
+ [parent invalidateColumns];
+ }
+ }
+
+ [super handleAccessibleEvent:eventType];
+}
+
+- (NSNumber*)moxIndex {
+ mozTableAccessible* parent = (mozTableAccessible*)[self moxParent];
+ return @([[parent moxRows] indexOfObjectIdenticalTo:self]);
+}
+
+@end
+
+@implementation mozTableCellAccessible
+
+- (NSValue*)moxRowIndexRange {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
+ return
+ [NSValue valueWithRange:NSMakeRange(cell->RowIdx(), cell->RowExtent())];
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ return [NSValue
+ valueWithRange:NSMakeRange(proxy->RowIdx(), proxy->RowExtent())];
+ }
+}
+
+- (NSValue*)moxColumnIndexRange {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
+ return
+ [NSValue valueWithRange:NSMakeRange(cell->ColIdx(), cell->ColExtent())];
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ return [NSValue
+ valueWithRange:NSMakeRange(proxy->ColIdx(), proxy->ColExtent())];
+ }
+}
+
+- (NSArray*)moxRowHeaderUIElements {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->RowHeaderCells(&headerCells);
+ return utils::ConvertToNSArray(headerCells);
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->RowHeaderCells(&headerCells);
+ return utils::ConvertToNSArray(headerCells);
+ }
+}
+
+- (NSArray*)moxColumnHeaderUIElements {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mGeckoAccessible.IsAccessible()) {
+ TableCellAccessible* cell = mGeckoAccessible.AsAccessible()->AsTableCell();
+ AutoTArray<Accessible*, 10> headerCells;
+ cell->ColHeaderCells(&headerCells);
+ return utils::ConvertToNSArray(headerCells);
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ nsTArray<ProxyAccessible*> headerCells;
+ proxy->ColHeaderCells(&headerCells);
+ return utils::ConvertToNSArray(headerCells);
+ }
+}
+
+@end
+
+@implementation mozOutlineAccessible
+
+- (NSArray*)moxRows {
+ // Create a new array with the list of outline rows. We
+ // use pivot here to do a deep traversal of all rows nested
+ // in this outline, not just those which are direct
+ // children, since that's what VO expects.
+ NSMutableArray* allRows = [[NSMutableArray alloc] init];
+ Pivot p = Pivot(mGeckoAccessible);
+ OutlineRule rule = OutlineRule();
+ AccessibleOrProxy firstChild = mGeckoAccessible.FirstChild();
+ AccessibleOrProxy match = p.Next(firstChild, rule, true);
+ while (!match.IsNull()) {
+ [allRows addObject:GetNativeFromGeckoAccessible(match)];
+ match = p.Next(match, rule);
+ }
+ return allRows;
+}
+
+- (NSArray*)moxColumns {
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ if (acc->IsContent() && acc->GetContent()->IsXULElement(nsGkAtoms::tree)) {
+ XULTreeAccessible* treeAcc = (XULTreeAccessible*)acc;
+ NSMutableArray* cols = [[NSMutableArray alloc] init];
+ // XUL trees store their columns in a group at the tree's first
+ // child. Here, we iterate over that group to get each column's
+ // native accessible and add it to our col array.
+ Accessible* treeColumns = treeAcc->GetChildAt(0);
+ if (treeColumns) {
+ uint32_t colCount = treeColumns->ChildCount();
+ for (uint32_t i = 0; i < colCount; i++) {
+ Accessible* treeColumnItem = treeColumns->GetChildAt(i);
+ [cols addObject:GetNativeFromGeckoAccessible(treeColumnItem)];
+ }
+ return cols;
+ }
+ }
+ }
+ // Webkit says we shouldn't expose any cols for aria-tree
+ // so we return an empty array here
+ return @[];
+}
+
+- (NSArray*)moxSelectedRows {
+ NSMutableArray* selectedRows = [[NSMutableArray alloc] init];
+ NSArray* allRows = [self moxRows];
+ for (mozAccessible* row in allRows) {
+ if ([row stateWithMask:states::SELECTED] != 0) {
+ [selectedRows addObject:row];
+ }
+ }
+
+ return selectedRows;
+}
+
+- (NSString*)moxOrientation {
+ return NSAccessibilityVerticalOrientationValue;
+}
+
+@end
+
+@implementation mozOutlineRowAccessible
+
+- (BOOL)isLayoutTablePart {
+ return NO;
+}
+
+- (NSNumber*)moxDisclosing {
+ return @([self stateWithMask:states::EXPANDED] != 0);
+}
+
+- (void)moxSetDisclosing:(NSNumber*)disclosing {
+ // VoiceOver requires this to be settable, but doesn't
+ // require it actually affect our disclosing state.
+ // We expose the attr as settable with this method
+ // but do nothing to actually set it.
+ return;
+}
+
+- (NSNumber*)moxExpanded {
+ return @([self stateWithMask:states::EXPANDED] != 0);
+}
+
+- (id)moxDisclosedByRow {
+ // According to webkit: this attr corresponds to the row
+ // that contains this row. It should be the same as the
+ // first parent that is a treeitem. If the parent is the tree
+ // itself, this should be nil. This is tricky for xul trees because
+ // all rows are direct children of the outline; they use
+ // relations to expose their heirarchy structure.
+
+ // first we check the relations to see if we're in a xul tree
+ // with weird row semantics
+ NSArray<mozAccessible*>* disclosingRows =
+ [self getRelationsByType:RelationType::NODE_CHILD_OF];
+ mozAccessible* disclosingRow = [disclosingRows firstObject];
+
+ if (disclosingRow) {
+ // if we find a row from our relation check,
+ // verify it isn't the outline itself and return
+ // appropriately
+ if ([[disclosingRow moxRole] isEqualToString:@"AXOutline"]) {
+ return nil;
+ }
+
+ return disclosingRow;
+ }
+
+ mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
+ // otherwise, its likely we're in an aria tree, so we can use
+ // these role and subrole checks
+ if ([[parent moxRole] isEqualToString:@"AXOutline"]) {
+ return nil;
+ }
+
+ if ([[parent moxSubrole] isEqualToString:@"AXOutlineRow"]) {
+ disclosingRow = parent;
+ }
+
+ return nil;
+}
+
+- (NSNumber*)moxDisclosureLevel {
+ GroupPos groupPos;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ groupPos = acc->GroupPosition();
+ } else if (ProxyAccessible* proxy = mGeckoAccessible.AsProxy()) {
+ groupPos = proxy->GroupPosition();
+ }
+ // mac expects 0-indexed levels, but groupPos.level is 1-indexed
+ // so we subtract 1 here for levels above 0
+ return groupPos.level > 0 ? @(groupPos.level - 1) : @(groupPos.level);
+}
+
+- (NSArray*)moxDisclosedRows {
+ // According to webkit: this attr corresponds to the rows
+ // that are considered inside this row. Again, this is weird for
+ // xul trees so we have to use relations first and then fall-back
+ // to the children filter for non-xul outlines.
+
+ // first we check the relations to see if we're in a xul tree
+ // with weird row semantics
+ if (NSArray* disclosedRows =
+ [self getRelationsByType:RelationType::NODE_PARENT_OF]) {
+ // if we find rows from our relation check, return them here
+ return disclosedRows;
+ }
+
+ // otherwise, filter our children for outline rows
+ return [[self moxChildren]
+ filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
+ mozAccessible* child,
+ NSDictionary* bindings) {
+ return [child isKindOfClass:[mozOutlineRowAccessible class]];
+ }]];
+}
+
+- (NSNumber*)moxIndex {
+ mozAccessible* parent = (mozAccessible*)[self moxUnignoredParent];
+ while (parent) {
+ if ([[parent moxRole] isEqualToString:@"AXOutline"]) {
+ break;
+ }
+ parent = (mozAccessible*)[parent moxUnignoredParent];
+ }
+
+ NSUInteger index =
+ [[(mozOutlineAccessible*)parent moxRows] indexOfObjectIdenticalTo:self];
+ return index == NSNotFound ? nil : @(index);
+}
+
+- (NSString*)moxLabel {
+ nsAutoString title;
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ acc->Name(title);
+ } else {
+ mGeckoAccessible.AsProxy()->Name(title);
+ }
+ // XXX: When parsing outlines built with ul/lu's, we
+ // include the bullet in this description even
+ // though webkit doesn't. Not all outlines are built with
+ // ul/lu's so we can't strip the first character here.
+
+ return nsCocoaUtils::ToNSString(title);
+}
+
+- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
+ [super stateChanged:state isEnabled:enabled];
+
+ if (state == states::EXPANDED) {
+ // If the EXPANDED state is updated, fire appropriate events on the
+ // outline row.
+ [self moxPostNotification:(enabled
+ ? NSAccessibilityRowExpandedNotification
+ : NSAccessibilityRowCollapsedNotification)];
+ }
+}
+
+@end
diff --git a/accessible/mac/mozTextAccessible.h b/accessible/mac/mozTextAccessible.h
new file mode 100644
index 0000000000..55d0ac7a06
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.h
@@ -0,0 +1,113 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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/. */
+
+#import "mozAccessible.h"
+
+@interface mozTextAccessible : mozAccessible
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (id)moxValue;
+
+// override
+- (id)moxRequired;
+
+// override
+- (NSNumber*)moxInvalid;
+
+// override
+- (NSNumber*)moxInsertionPointLineNumber;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSString*)moxSubrole;
+
+// override
+- (NSNumber*)moxNumberOfCharacters;
+
+// override
+- (NSString*)moxSelectedText;
+
+// override
+- (NSValue*)moxSelectedTextRange;
+
+// override
+- (NSValue*)moxVisibleCharacterRange;
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (void)moxSetValue:(id)value;
+
+// override
+- (void)moxSetSelectedText:(NSString*)text;
+
+// override
+- (void)moxSetSelectedTextRange:(NSValue*)range;
+
+// override
+- (void)moxSetVisibleCharacterRange:(NSValue*)range;
+
+// override
+- (NSString*)moxStringForRange:(NSValue*)range;
+
+// override
+- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range;
+
+// override
+- (NSValue*)moxRangeForLine:(NSNumber*)line;
+
+// override
+- (NSNumber*)moxLineForIndex:(NSNumber*)index;
+
+// override
+- (NSValue*)moxBoundsForRange:(NSValue*)range;
+
+#pragma mark - mozAccessible
+
+// override
+- (void)handleAccessibleTextChangeEvent:(NSString*)change
+ inserted:(BOOL)isInserted
+ inContainer:
+ (const mozilla::a11y::AccessibleOrProxy&)
+ container
+ at:(int32_t)start;
+
+// override
+- (void)handleAccessibleEvent:(uint32_t)eventType;
+
+@end
+
+@interface mozTextLeafAccessible : mozAccessible
+
+// override
+- (BOOL)moxBlockSelector:(SEL)selector;
+
+// override
+- (NSString*)moxValue;
+
+// override
+- (NSString*)moxTitle;
+
+// override
+- (NSString*)moxLabel;
+
+// override
+- (NSString*)moxStringForRange:(NSValue*)range;
+
+// override
+- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range;
+
+// override
+- (NSValue*)moxBoundsForRange:(NSValue*)range;
+
+@end
diff --git a/accessible/mac/mozTextAccessible.mm b/accessible/mac/mozTextAccessible.mm
new file mode 100644
index 0000000000..12d654f67a
--- /dev/null
+++ b/accessible/mac/mozTextAccessible.mm
@@ -0,0 +1,448 @@
+/* clang-format off */
+/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* clang-format on */
+/* 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "mozilla/a11y/PDocAccessible.h"
+#include "nsCocoaUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "nsObjCExceptions.h"
+#include "TextLeafAccessible.h"
+
+#import "mozTextAccessible.h"
+#import "GeckoTextMarker.h"
+#import "MOXTextMarkerDelegate.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+inline bool ToNSRange(id aValue, NSRange* aRange) {
+ MOZ_ASSERT(aRange, "aRange is nil");
+
+ if ([aValue isKindOfClass:[NSValue class]] &&
+ strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) {
+ *aRange = [aValue rangeValue];
+ return true;
+ }
+
+ return false;
+}
+
+inline NSString* ToNSString(id aValue) {
+ if ([aValue isKindOfClass:[NSString class]]) {
+ return aValue;
+ }
+
+ return nil;
+}
+
+@interface mozTextAccessible ()
+- (long)textLength;
+- (BOOL)isReadOnly;
+- (NSString*)text;
+- (GeckoTextMarkerRange)selection;
+- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range;
+@end
+
+@implementation mozTextAccessible
+
+- (NSString*)moxTitle {
+ return @"";
+}
+
+- (id)moxValue {
+ // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText
+ // object's AXSelectedText attribute. See bug 674612 for details.
+ // Also if there is no selected text, we return the full text.
+ // See bug 369710 for details.
+ if ([[self moxRole] isEqualToString:NSAccessibilityStaticTextRole]) {
+ NSString* selectedText = [self moxSelectedText];
+ return (selectedText && [selectedText length]) ? selectedText : [self text];
+ }
+
+ return [self text];
+}
+
+- (id)moxRequired {
+ return @([self stateWithMask:states::REQUIRED] != 0);
+}
+
+- (NSString*)moxInvalid {
+ if ([self stateWithMask:states::INVALID] != 0) {
+ // If the attribute exists, it has one of four values: true, false,
+ // grammar, or spelling. We query the attribute value here in order
+ // to find the correct string to return.
+ if (Accessible* acc = mGeckoAccessible.AsAccessible()) {
+ HyperTextAccessible* text = acc->AsHyperText();
+ if (!text || !text->IsTextRole()) {
+ // we can't get the attribute, but we should still respect the
+ // invalid state flag
+ return @"true";
+ }
+ nsAutoString invalidStr;
+ nsCOMPtr<nsIPersistentProperties> attributes =
+ text->DefaultTextAttributes();
+ nsAccUtils::GetAccAttr(attributes, nsGkAtoms::invalid, invalidStr);
+ if (invalidStr.IsEmpty()) {
+ // if the attribute had no value, we should still respect the
+ // invalid state flag.
+ return @"true";
+ }
+ return nsCocoaUtils::ToNSString(invalidStr);
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ // Similar to the acc case above, we iterate through our attributes
+ // to find the value for `invalid`.
+ AutoTArray<Attribute, 10> attrs;
+ proxy->DefaultTextAttributes(&attrs);
+ for (size_t i = 0; i < attrs.Length(); i++) {
+ if (attrs.ElementAt(i).Name() == "invalid") {
+ nsString invalidStr = attrs.ElementAt(i).Value();
+ if (invalidStr.IsEmpty()) {
+ break;
+ }
+ return nsCocoaUtils::ToNSString(invalidStr);
+ }
+ }
+ // if we iterated through our attributes and didn't find `invalid`,
+ // or if the invalid attribute had no value, we should still respect
+ // the invalid flag and return true.
+ return @"true";
+ }
+ }
+ // If the flag is not set, we return false.
+ return @"false";
+}
+
+- (NSNumber*)moxInsertionPointLineNumber {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ int32_t lineNumber = -1;
+ if (mGeckoAccessible.IsAccessible()) {
+ if (HyperTextAccessible* textAcc =
+ mGeckoAccessible.AsAccessible()->AsHyperText()) {
+ lineNumber = textAcc->CaretLineNumber() - 1;
+ }
+ } else {
+ lineNumber = mGeckoAccessible.AsProxy()->CaretLineNumber() - 1;
+ }
+
+ return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil;
+}
+
+- (NSString*)moxRole {
+ if ([self ARIARole] == nsGkAtoms::textbox ||
+ [self stateWithMask:states::MULTI_LINE]) {
+ return NSAccessibilityTextAreaRole;
+ }
+
+ return [super moxRole];
+}
+
+- (NSString*)moxSubrole {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ if (mRole == roles::PASSWORD_TEXT) {
+ return NSAccessibilitySecureTextFieldSubrole;
+ }
+
+ if (mRole == roles::ENTRY) {
+ Accessible* acc = mGeckoAccessible.AsAccessible();
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ if ((acc && acc->IsSearchbox()) || (proxy && proxy->IsSearchbox())) {
+ return @"AXSearchField";
+ }
+ }
+
+ return nil;
+}
+
+- (NSNumber*)moxNumberOfCharacters {
+ return @([self textLength]);
+}
+
+- (NSString*)moxSelectedText {
+ GeckoTextMarkerRange selection = [self selection];
+ if (!selection.IsValid()) {
+ return nil;
+ }
+
+ return selection.Text();
+}
+
+- (NSValue*)moxSelectedTextRange {
+ GeckoTextMarkerRange selection = [self selection];
+ if (!selection.IsValid()) {
+ return nil;
+ }
+
+ GeckoTextMarkerRange fromStartToSelection(
+ GeckoTextMarker(mGeckoAccessible, 0), selection.mStart);
+
+ return [NSValue valueWithRange:NSMakeRange(fromStartToSelection.Length(),
+ selection.Length())];
+}
+
+- (NSValue*)moxVisibleCharacterRange {
+ // XXX this won't work with Textarea and such as we actually don't give
+ // the visible character range.
+ return [NSValue valueWithRange:NSMakeRange(0, [self textLength])];
+}
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ if (selector == @selector(moxSetValue:) && [self isReadOnly]) {
+ return YES;
+ }
+
+ return [super moxBlockSelector:selector];
+}
+
+- (void)moxSetValue:(id)value {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ nsString text;
+ nsCocoaUtils::GetStringForNSString(value, text);
+ if (mGeckoAccessible.IsAccessible()) {
+ if (HyperTextAccessible* textAcc =
+ mGeckoAccessible.AsAccessible()->AsHyperText()) {
+ textAcc->ReplaceText(text);
+ }
+ } else {
+ mGeckoAccessible.AsProxy()->ReplaceText(text);
+ }
+}
+
+- (void)moxSetSelectedText:(NSString*)selectedText {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSString* stringValue = ToNSString(selectedText);
+ if (!stringValue) {
+ return;
+ }
+
+ int32_t start = 0, end = 0;
+ nsString text;
+ if (mGeckoAccessible.IsAccessible()) {
+ if (HyperTextAccessible* textAcc =
+ mGeckoAccessible.AsAccessible()->AsHyperText()) {
+ textAcc->SelectionBoundsAt(0, &start, &end);
+ textAcc->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ textAcc->InsertText(text, start);
+ }
+ } else {
+ ProxyAccessible* proxy = mGeckoAccessible.AsProxy();
+ nsString data;
+ proxy->SelectionBoundsAt(0, data, &start, &end);
+ proxy->DeleteText(start, end - start);
+ nsCocoaUtils::GetStringForNSString(stringValue, text);
+ proxy->InsertText(text, start);
+ }
+}
+
+- (void)moxSetSelectedTextRange:(NSValue*)selectedTextRange {
+ GeckoTextMarkerRange markerRange =
+ [self textMarkerRangeFromRange:selectedTextRange];
+
+ markerRange.Select();
+}
+
+- (void)moxSetVisibleCharacterRange:(NSValue*)visibleCharacterRange {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSRange range;
+ if (!ToNSRange(visibleCharacterRange, &range)) {
+ return;
+ }
+
+ if (mGeckoAccessible.IsAccessible()) {
+ if (HyperTextAccessible* textAcc =
+ mGeckoAccessible.AsAccessible()->AsHyperText()) {
+ textAcc->ScrollSubstringTo(range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ }
+ } else {
+ mGeckoAccessible.AsProxy()->ScrollSubstringTo(
+ range.location, range.location + range.length,
+ nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE);
+ }
+}
+
+- (NSString*)moxStringForRange:(NSValue*)range {
+ GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range];
+
+ if (!markerRange.IsValid()) {
+ return nil;
+ }
+
+ return markerRange.Text();
+}
+
+- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range {
+ return [[[NSAttributedString alloc]
+ initWithString:[self moxStringForRange:range]] autorelease];
+}
+
+- (NSValue*)moxRangeForLine:(NSNumber*)line {
+ // XXX: actually get the integer value for the line #
+ return [NSValue valueWithRange:NSMakeRange(0, [self textLength])];
+}
+
+- (NSNumber*)moxLineForIndex:(NSNumber*)index {
+ // XXX: actually return the line #
+ return @0;
+}
+
+- (NSValue*)moxBoundsForRange:(NSValue*)range {
+ GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range];
+
+ if (!markerRange.IsValid()) {
+ return nil;
+ }
+
+ return markerRange.Bounds();
+}
+
+#pragma mark - mozAccessible
+
+- (void)handleAccessibleTextChangeEvent:(NSString*)change
+ inserted:(BOOL)isInserted
+ inContainer:(const AccessibleOrProxy&)container
+ at:(int32_t)start {
+ GeckoTextMarker startMarker(container, start);
+ NSDictionary* userInfo = @{
+ @"AXTextChangeElement" : self,
+ @"AXTextStateChangeType" : @(AXTextStateChangeTypeEdit),
+ @"AXTextChangeValues" : @[ @{
+ @"AXTextChangeValue" : (change ? change : @""),
+ @"AXTextChangeValueStartMarker" : startMarker.CreateAXTextMarker(),
+ @"AXTextEditType" : isInserted ? @(AXTextEditTypeTyping)
+ : @(AXTextEditTypeDelete)
+ } ]
+ };
+
+ mozAccessible* webArea = [self topWebArea];
+ [webArea moxPostNotification:NSAccessibilityValueChangedNotification
+ withUserInfo:userInfo];
+ [self moxPostNotification:NSAccessibilityValueChangedNotification
+ withUserInfo:userInfo];
+
+ [self moxPostNotification:NSAccessibilityValueChangedNotification];
+}
+
+- (void)handleAccessibleEvent:(uint32_t)eventType {
+ switch (eventType) {
+ default:
+ [super handleAccessibleEvent:eventType];
+ break;
+ }
+}
+
+#pragma mark -
+
+- (long)textLength {
+ return [[self text] length];
+}
+
+- (BOOL)isReadOnly {
+ return [self stateWithMask:states::EDITABLE] == 0;
+}
+
+- (NSString*)text {
+ // A password text field returns an empty value
+ if (mRole == roles::PASSWORD_TEXT) {
+ return @"";
+ }
+
+ id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate];
+ return [delegate
+ moxStringForTextMarkerRange:[delegate
+ moxTextMarkerRangeForUIElement:self]];
+}
+
+- (GeckoTextMarkerRange)selection {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate];
+ GeckoTextMarkerRange selection =
+ [static_cast<MOXTextMarkerDelegate*>(delegate) selection];
+
+ if (!selection.IsValid() || !selection.Crop(mGeckoAccessible)) {
+ // The selection is not in this accessible. Return invalid range.
+ return GeckoTextMarkerRange();
+ }
+
+ return selection;
+}
+
+- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range {
+ NSRange r = [range rangeValue];
+
+ GeckoTextMarker startMarker =
+ GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location);
+
+ GeckoTextMarker endMarker =
+ GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location + r.length);
+
+ return GeckoTextMarkerRange(startMarker, endMarker);
+}
+
+@end
+
+@implementation mozTextLeafAccessible
+
+- (BOOL)moxBlockSelector:(SEL)selector {
+ if (selector == @selector(moxChildren) || selector == @selector
+ (moxTitleUIElement)) {
+ return YES;
+ }
+
+ return [super moxBlockSelector:selector];
+}
+
+- (NSString*)moxValue {
+ return [super moxTitle];
+}
+
+- (NSString*)moxTitle {
+ return nil;
+}
+
+- (NSString*)moxLabel {
+ return nil;
+}
+
+- (NSString*)moxStringForRange:(NSValue*)range {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSRange r = [range rangeValue];
+ GeckoTextMarkerRange textMarkerRange(mGeckoAccessible);
+ textMarkerRange.mStart.mOffset += r.location;
+ textMarkerRange.mEnd.mOffset =
+ textMarkerRange.mStart.mOffset + r.location + r.length;
+
+ return textMarkerRange.Text();
+}
+
+- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range {
+ return [[[NSAttributedString alloc]
+ initWithString:[self moxStringForRange:range]] autorelease];
+}
+
+- (NSValue*)moxBoundsForRange:(NSValue*)range {
+ MOZ_ASSERT(!mGeckoAccessible.IsNull());
+
+ NSRange r = [range rangeValue];
+ GeckoTextMarkerRange textMarkerRange(mGeckoAccessible);
+
+ textMarkerRange.mStart.mOffset += r.location;
+ textMarkerRange.mEnd.mOffset = textMarkerRange.mStart.mOffset + r.length;
+
+ return textMarkerRange.Bounds();
+}
+
+@end