summaryrefslogtreecommitdiffstats
path: root/accessible/ios
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/ios')
-rw-r--r--accessible/ios/.clang-format11
-rw-r--r--accessible/ios/AccessibleWrap.h47
-rw-r--r--accessible/ios/AccessibleWrap.mm49
-rw-r--r--accessible/ios/ApplicationAccessibleWrap.h20
-rw-r--r--accessible/ios/DocAccessibleWrap.h23
-rw-r--r--accessible/ios/MUIAccessible.h72
-rw-r--r--accessible/ios/MUIAccessible.mm497
-rw-r--r--accessible/ios/MUIRootAccessible.h29
-rw-r--r--accessible/ios/MUIRootAccessible.mm45
-rw-r--r--accessible/ios/MUIRootAccessibleProtocol.h51
-rw-r--r--accessible/ios/Platform.mm57
-rw-r--r--accessible/ios/RootAccessibleWrap.h42
-rw-r--r--accessible/ios/RootAccessibleWrap.mm50
-rw-r--r--accessible/ios/moz.build30
14 files changed, 1023 insertions, 0 deletions
diff --git a/accessible/ios/.clang-format b/accessible/ios/.clang-format
new file mode 100644
index 0000000000..269bce4d0f
--- /dev/null
+++ b/accessible/ios/.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/ios/AccessibleWrap.h b/accessible/ios/AccessibleWrap.h
new file mode 100644
index 0000000000..e5a55a4d2f
--- /dev/null
+++ b/accessible/ios/AccessibleWrap.h
@@ -0,0 +1,47 @@
+/* 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 https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_AccessibleWrap_h_
+#define mozilla_a11y_AccessibleWrap_h_
+
+#include <objc/objc.h>
+
+#include "nsCOMPtr.h"
+#include "LocalAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public LocalAccessible {
+ public: // construction, destruction
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap() = default;
+
+ virtual void Shutdown() override;
+
+ /**
+ * Get the native Obj-C object (MUIAccessible).
+ */
+ virtual void GetNativeInterface(void** aOutAccessible) override;
+
+ protected:
+ id GetNativeObject();
+
+ private:
+ id mNativeObject;
+
+ bool mNativeInited;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/AccessibleWrap.mm b/accessible/ios/AccessibleWrap.mm
new file mode 100644
index 0000000000..576e854c60
--- /dev/null
+++ b/accessible/ios/AccessibleWrap.mm
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccessibleWrap.h"
+#include "LocalAccessible-inl.h"
+
+#import "MUIAccessible.h"
+#import "MUIRootAccessible.h"
+
+using namespace mozilla::a11y;
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : LocalAccessible(aContent, aDoc),
+ mNativeObject(nil),
+ mNativeInited(false) {}
+
+void AccessibleWrap::Shutdown() {
+ // this ensures 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;
+ }
+
+ LocalAccessible::Shutdown();
+}
+
+id AccessibleWrap::GetNativeObject() {
+ if (!mNativeInited && !IsDefunct()) {
+ Class type = IsRoot() ? [MUIRootAccessible class] : [MUIAccessible class];
+ mNativeObject = [[type alloc] initWithAccessible:this];
+ }
+
+ mNativeInited = true;
+
+ return mNativeObject;
+}
+
+void AccessibleWrap::GetNativeInterface(void** aOutInterface) {
+ *aOutInterface = static_cast<void*>(GetNativeObject());
+}
diff --git a/accessible/ios/ApplicationAccessibleWrap.h b/accessible/ios/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..fd5ced8eb2
--- /dev/null
+++ b/accessible/ios/ApplicationAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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 {
+
+using ApplicationAccessibleWrap = ApplicationAccessible;
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/DocAccessibleWrap.h b/accessible/ios/DocAccessibleWrap.h
new file mode 100644
index 0000000000..e14dfd4394
--- /dev/null
+++ b/accessible/ios/DocAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- 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 https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef DocAccessible DocAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/MUIAccessible.h b/accessible/ios/MUIAccessible.h
new file mode 100644
index 0000000000..725b06c345
--- /dev/null
+++ b/accessible/ios/MUIAccessible.h
@@ -0,0 +1,72 @@
+/* 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 _MUIAccessible_H_
+#define _MUIAccessible_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIAccessibility.h>
+
+#include "AccessibleWrap.h"
+#include "RemoteAccessible.h"
+
+@class MUIAccessible;
+
+namespace mozilla {
+namespace a11y {
+
+inline MUIAccessible* _Nullable GetNativeFromGeckoAccessible(
+ mozilla::a11y::Accessible* _Nullable aAcc) {
+ if (!aAcc) {
+ return nil;
+ }
+ if (LocalAccessible* localAcc = aAcc->AsLocal()) {
+ MUIAccessible* native = nil;
+ localAcc->GetNativeInterface((void**)&native);
+ return native;
+ }
+
+ RemoteAccessible* remoteAcc = aAcc->AsRemote();
+ return reinterpret_cast<MUIAccessible*>(remoteAcc->GetWrapper());
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+@interface MUIAccessible : NSObject {
+ mozilla::a11y::Accessible* mGeckoAccessible;
+}
+
+// inits with the given accessible
+- (nonnull id)initWithAccessible:(nonnull mozilla::a11y::Accessible*)aAcc;
+
+// allows for gecko accessible access outside of the class
+- (mozilla::a11y::Accessible* _Nullable)geckoAccessible;
+
+- (void)expire;
+
+// override
+- (void)dealloc;
+
+// UIAccessibility
+- (BOOL)isAccessibilityElement;
+- (nullable NSString*)accessibilityLabel;
+- (nullable NSString*)accessibilityHint;
+- (CGRect)accessibilityFrame;
+- (nullable NSString*)accessibilityValue;
+- (uint64_t)accessibilityTraits;
+
+// UIAccessibilityContainer
+- (NSInteger)accessibilityElementCount;
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index;
+- (NSInteger)indexOfAccessibilityElement:(nonnull id)element;
+- (nullable NSArray*)accessibilityElements;
+- (UIAccessibilityContainerType)accessibilityContainerType;
+
+@end
+
+#endif
diff --git a/accessible/ios/MUIAccessible.mm b/accessible/ios/MUIAccessible.mm
new file mode 100644
index 0000000000..46c4712e2e
--- /dev/null
+++ b/accessible/ios/MUIAccessible.mm
@@ -0,0 +1,497 @@
+/* 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 "MUIAccessible.h"
+
+#include "nsString.h"
+#include "RootAccessibleWrap.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+#ifdef A11Y_LOG
+# define DEBUG_HINTS
+#endif
+
+#ifdef DEBUG_HINTS
+static NSString* ToNSString(const nsACString& aCString) {
+ if (aCString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [[[NSString alloc] initWithBytes:aCString.BeginReading()
+ length:aCString.Length()
+ encoding:NSUTF8StringEncoding] autorelease];
+}
+#endif
+
+static NSString* ToNSString(const nsAString& aString) {
+ if (aString.IsEmpty()) {
+ return [NSString string];
+ }
+ return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
+ aString.BeginReading())
+ length:aString.Length()];
+}
+
+// These rules offer conditions for whether a gecko accessible
+// should be considered a UIKit accessibility element. Each role is mapped to a
+// rule.
+enum class IsAccessibilityElementRule {
+ // Always yes
+ Yes,
+ // Always no
+ No,
+ // If the accessible has no children. For example an empty header
+ // which is labeled.
+ IfChildless,
+ // If the accessible has no children and it is named and focusable.
+ IfChildlessWithNameAndFocusable,
+ // If this accessible isn't a child of an accessibility element. For example,
+ // a text leaf child of a button.
+ IfParentIsntElementWithName,
+ // If this accessible has multiple leafs that should functionally be
+ // united, for example a link with span elements.
+ IfBrokenUp,
+};
+
+class Trait {
+ public:
+ static const uint64_t None = 0;
+ static const uint64_t Button = ((uint64_t)0x1) << 0;
+ static const uint64_t Link = ((uint64_t)0x1) << 1;
+ static const uint64_t Image = ((uint64_t)0x1) << 2;
+ static const uint64_t Selected = ((uint64_t)0x1) << 3;
+ static const uint64_t PlaysSound = ((uint64_t)0x1) << 4;
+ static const uint64_t KeyboardKey = ((uint64_t)0x1) << 5;
+ static const uint64_t StaticText = ((uint64_t)0x1) << 6;
+ static const uint64_t SummaryElement = ((uint64_t)0x1) << 7;
+ static const uint64_t NotEnabled = ((uint64_t)0x1) << 8;
+ static const uint64_t UpdatesFrequently = ((uint64_t)0x1) << 9;
+ static const uint64_t SearchField = ((uint64_t)0x1) << 10;
+ static const uint64_t StartsMediaSession = ((uint64_t)0x1) << 11;
+ static const uint64_t Adjustable = ((uint64_t)0x1) << 12;
+ static const uint64_t AllowsDirectInteraction = ((uint64_t)0x1) << 13;
+ static const uint64_t CausesPageTurn = ((uint64_t)0x1) << 14;
+ static const uint64_t TabBar = ((uint64_t)0x1) << 15;
+ static const uint64_t Header = ((uint64_t)0x1) << 16;
+ static const uint64_t WebContent = ((uint64_t)0x1) << 17;
+ static const uint64_t TextEntry = ((uint64_t)0x1) << 18;
+ static const uint64_t PickerElement = ((uint64_t)0x1) << 19;
+ static const uint64_t RadioButton = ((uint64_t)0x1) << 20;
+ static const uint64_t IsEditing = ((uint64_t)0x1) << 21;
+ static const uint64_t LaunchIcon = ((uint64_t)0x1) << 22;
+ static const uint64_t StatusBarElement = ((uint64_t)0x1) << 23;
+ static const uint64_t SecureTextField = ((uint64_t)0x1) << 24;
+ static const uint64_t Inactive = ((uint64_t)0x1) << 25;
+ static const uint64_t Footer = ((uint64_t)0x1) << 26;
+ static const uint64_t BackButton = ((uint64_t)0x1) << 27;
+ static const uint64_t TabButton = ((uint64_t)0x1) << 28;
+ static const uint64_t AutoCorrectCandidate = ((uint64_t)0x1) << 29;
+ static const uint64_t DeleteKey = ((uint64_t)0x1) << 30;
+ static const uint64_t SelectionDismissesItem = ((uint64_t)0x1) << 31;
+ static const uint64_t Visited = ((uint64_t)0x1) << 32;
+ static const uint64_t Scrollable = ((uint64_t)0x1) << 33;
+ static const uint64_t Spacer = ((uint64_t)0x1) << 34;
+ static const uint64_t TableIndex = ((uint64_t)0x1) << 35;
+ static const uint64_t Map = ((uint64_t)0x1) << 36;
+ static const uint64_t TextOperationsAvailable = ((uint64_t)0x1) << 37;
+ static const uint64_t Draggable = ((uint64_t)0x1) << 38;
+ static const uint64_t GesturePracticeRegion = ((uint64_t)0x1) << 39;
+ static const uint64_t PopupButton = ((uint64_t)0x1) << 40;
+ static const uint64_t AllowsNativeSliding = ((uint64_t)0x1) << 41;
+ static const uint64_t MathEquation = ((uint64_t)0x1) << 42;
+ static const uint64_t ContainedByTable = ((uint64_t)0x1) << 43;
+ static const uint64_t ContainedByList = ((uint64_t)0x1) << 44;
+ static const uint64_t TouchContainer = ((uint64_t)0x1) << 45;
+ static const uint64_t SupportsZoom = ((uint64_t)0x1) << 46;
+ static const uint64_t TextArea = ((uint64_t)0x1) << 47;
+ static const uint64_t BookContent = ((uint64_t)0x1) << 48;
+ static const uint64_t ContainedByLandmark = ((uint64_t)0x1) << 49;
+ static const uint64_t FolderIcon = ((uint64_t)0x1) << 50;
+ static const uint64_t ReadOnly = ((uint64_t)0x1) << 51;
+ static const uint64_t MenuItem = ((uint64_t)0x1) << 52;
+ static const uint64_t Toggle = ((uint64_t)0x1) << 53;
+ static const uint64_t IgnoreItemChooser = ((uint64_t)0x1) << 54;
+ static const uint64_t SupportsTrackingDetail = ((uint64_t)0x1) << 55;
+ static const uint64_t Alert = ((uint64_t)0x1) << 56;
+ static const uint64_t ContainedByFieldset = ((uint64_t)0x1) << 57;
+ static const uint64_t AllowsLayoutChangeInStatusBar = ((uint64_t)0x1) << 58;
+};
+
+#pragma mark -
+
+@interface NSObject (AccessibilityPrivate)
+- (void)_accessibilityUnregister;
+@end
+
+@implementation MUIAccessible
+
+- (id)initWithAccessible:(Accessible*)aAcc {
+ MOZ_ASSERT(aAcc, "Cannot init MUIAccessible with null");
+ if ((self = [super init])) {
+ mGeckoAccessible = aAcc;
+ }
+
+ return self;
+}
+
+- (mozilla::a11y::Accessible*)geckoAccessible {
+ return mGeckoAccessible;
+}
+
+- (void)expire {
+ mGeckoAccessible = nullptr;
+ if ([self respondsToSelector:@selector(_accessibilityUnregister)]) {
+ [self _accessibilityUnregister];
+ }
+}
+
+- (void)dealloc {
+ [super dealloc];
+}
+
+static bool isAccessibilityElementInternal(Accessible* aAccessible) {
+ MOZ_ASSERT(aAccessible);
+ IsAccessibilityElementRule rule = IsAccessibilityElementRule::No;
+
+#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
+ msaaRole, ia2Role, androidClass, iosIsElement, nameRule) \
+ case roles::_geckoRole: \
+ rule = iosIsElement; \
+ break;
+ switch (aAccessible->Role()) {
+#include "RoleMap.h"
+ }
+
+ switch (rule) {
+ case IsAccessibilityElementRule::Yes:
+ return true;
+ case IsAccessibilityElementRule::No:
+ return false;
+ case IsAccessibilityElementRule::IfChildless:
+ return aAccessible->ChildCount() == 0;
+ case IsAccessibilityElementRule::IfParentIsntElementWithName: {
+ nsAutoString name;
+ aAccessible->Name(name);
+ name.CompressWhitespace();
+ if (name.IsEmpty()) {
+ return false;
+ }
+
+ if (isAccessibilityElementInternal(aAccessible->Parent())) {
+ // This is a text leaf that needs to be pruned from a button or the
+ // likes. It should also be ignored in the event of its parent being a
+ // pruned link.
+ return false;
+ }
+
+ return true;
+ }
+ case IsAccessibilityElementRule::IfChildlessWithNameAndFocusable:
+ if (aAccessible->ChildCount() == 0 &&
+ (aAccessible->State() & states::FOCUSABLE)) {
+ nsAutoString name;
+ aAccessible->Name(name);
+ name.CompressWhitespace();
+ return !name.IsEmpty();
+ }
+ return false;
+ case IsAccessibilityElementRule::IfBrokenUp: {
+ uint32_t childCount = aAccessible->ChildCount();
+ if (childCount == 1) {
+ // If this is a single child container just use the text leaf and its
+ // traits will be inherited.
+ return false;
+ }
+
+ for (uint32_t idx = 0; idx < childCount; idx++) {
+ Accessible* child = aAccessible->ChildAt(idx);
+ role accRole = child->Role();
+ if (accRole != roles::STATICTEXT && accRole != roles::TEXT_LEAF &&
+ accRole != roles::GRAPHIC) {
+ // If this container contains anything but text leafs and images
+ // ignore this accessible. Its descendants will inherit the
+ // container's traits.
+ return false;
+ }
+ }
+
+ return true;
+ }
+ default:
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Unhandled IsAccessibilityElementRule");
+
+ return false;
+}
+
+- (BOOL)isAccessibilityElement {
+ if (!mGeckoAccessible) {
+ return NO;
+ }
+
+ return isAccessibilityElementInternal(mGeckoAccessible) ? YES : NO;
+}
+
+- (NSString*)accessibilityLabel {
+ if (!mGeckoAccessible) {
+ return @"";
+ }
+
+ nsAutoString name;
+ mGeckoAccessible->Name(name);
+
+ return ToNSString(name);
+}
+
+- (NSString*)accessibilityHint {
+ if (!mGeckoAccessible) {
+ return @"";
+ }
+
+#ifdef DEBUG_HINTS
+ // Just put in a debug description as the label so we get a clue about which
+ // accessible ends up where.
+ nsAutoCString desc;
+ mGeckoAccessible->DebugDescription(desc);
+ return ToNSString(desc);
+#else
+ return @"";
+#endif
+}
+
+- (CGRect)accessibilityFrame {
+ RootAccessibleWrap* rootAcc = static_cast<RootAccessibleWrap*>(
+ mGeckoAccessible->IsLocal()
+ ? mGeckoAccessible->AsLocal()->RootAccessible()
+ : mGeckoAccessible->AsRemote()
+ ->OuterDocOfRemoteBrowser()
+ ->RootAccessible());
+
+ if (!rootAcc) {
+ return CGRectMake(0, 0, 0, 0);
+ }
+
+ LayoutDeviceIntRect rect = mGeckoAccessible->Bounds();
+ return rootAcc->DevPixelsRectToUIKit(rect);
+}
+
+- (NSString*)accessibilityValue {
+ if (!mGeckoAccessible) {
+ return nil;
+ }
+
+ uint64_t state = mGeckoAccessible->State();
+ if (state & states::LINKED) {
+ // Value returns the URL. We don't want to expose that as the value on iOS.
+ return nil;
+ }
+
+ if (state & states::CHECKABLE) {
+ if (state & states::CHECKED) {
+ return @"1";
+ }
+ if (state & states::MIXED) {
+ return @"2";
+ }
+ return @"0";
+ }
+
+ if (mGeckoAccessible->IsPassword()) {
+ // Accessible::Value returns an empty string. On iOS, we need to return the
+ // masked password so that AT knows how many characters are in the password.
+ Accessible* leaf = mGeckoAccessible->FirstChild();
+ if (!leaf) {
+ return nil;
+ }
+ nsAutoString masked;
+ leaf->AppendTextTo(masked);
+ return ToNSString(masked);
+ }
+
+ // If there is a heading ancestor, self has the header trait, so value should
+ // be the heading level.
+ for (Accessible* acc = mGeckoAccessible; acc; acc = acc->Parent()) {
+ if (acc->Role() == roles::HEADING) {
+ return [NSString stringWithFormat:@"%d", acc->GroupPosition().level];
+ }
+ }
+
+ nsAutoString value;
+ mGeckoAccessible->Value(value);
+ return ToNSString(value);
+}
+
+static uint64_t GetAccessibilityTraits(Accessible* aAccessible) {
+ uint64_t state = aAccessible->State();
+ uint64_t traits = Trait::WebContent;
+ switch (aAccessible->Role()) {
+ case roles::LINK:
+ traits |= Trait::Link;
+ break;
+ case roles::GRAPHIC:
+ traits |= Trait::Image;
+ break;
+ case roles::PAGETAB:
+ traits |= Trait::TabButton;
+ break;
+ case roles::PUSHBUTTON:
+ case roles::SUMMARY:
+ case roles::COMBOBOX:
+ case roles::BUTTONMENU:
+ case roles::TOGGLE_BUTTON:
+ case roles::CHECKBUTTON:
+ case roles::SWITCH:
+ traits |= Trait::Button;
+ break;
+ case roles::RADIOBUTTON:
+ traits |= Trait::RadioButton;
+ break;
+ case roles::HEADING:
+ traits |= Trait::Header;
+ break;
+ case roles::STATICTEXT:
+ case roles::TEXT_LEAF:
+ traits |= Trait::StaticText;
+ break;
+ case roles::SLIDER:
+ case roles::SPINBUTTON:
+ traits |= Trait::Adjustable;
+ break;
+ case roles::MENUITEM:
+ case roles::PARENT_MENUITEM:
+ case roles::CHECK_MENU_ITEM:
+ case roles::RADIO_MENU_ITEM:
+ traits |= Trait::MenuItem;
+ break;
+ case roles::PASSWORD_TEXT:
+ traits |= Trait::SecureTextField;
+ break;
+ default:
+ break;
+ }
+
+ if ((traits & Trait::Link) && (state & states::TRAVERSED)) {
+ traits |= Trait::Visited;
+ }
+
+ if ((traits & Trait::Button) && (state & states::HASPOPUP)) {
+ traits |= Trait::PopupButton;
+ }
+
+ if (state & states::SELECTED) {
+ traits |= Trait::Selected;
+ }
+
+ if (state & states::CHECKABLE) {
+ traits |= Trait::Toggle;
+ }
+
+ if (!(state & states::ENABLED)) {
+ traits |= Trait::NotEnabled;
+ }
+
+ if (state & states::EDITABLE) {
+ traits |= Trait::TextEntry;
+ if (state & states::FOCUSED) {
+ // XXX: Also add "has text cursor" trait
+ traits |= Trait::IsEditing | Trait::TextOperationsAvailable;
+ }
+
+ if (aAccessible->IsSearchbox()) {
+ traits |= Trait::SearchField;
+ }
+
+ if (state & states::MULTI_LINE) {
+ traits |= Trait::TextArea;
+ }
+ }
+
+ return traits;
+}
+
+- (uint64_t)accessibilityTraits {
+ if (!mGeckoAccessible) {
+ return Trait::None;
+ }
+
+ uint64_t traits = GetAccessibilityTraits(mGeckoAccessible);
+
+ for (Accessible* parent = mGeckoAccessible->Parent(); parent;
+ parent = parent->Parent()) {
+ traits |= GetAccessibilityTraits(parent);
+ }
+
+ return traits;
+}
+
+- (NSInteger)accessibilityElementCount {
+ return mGeckoAccessible ? mGeckoAccessible->ChildCount() : 0;
+}
+
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index {
+ if (!mGeckoAccessible) {
+ return nil;
+ }
+
+ Accessible* child = mGeckoAccessible->ChildAt(index);
+ return GetNativeFromGeckoAccessible(child);
+}
+
+- (NSInteger)indexOfAccessibilityElement:(id)element {
+ Accessible* acc = [(MUIAccessible*)element geckoAccessible];
+ if (!acc || mGeckoAccessible != acc->Parent()) {
+ return -1;
+ }
+
+ return acc->IndexInParent();
+}
+
+- (NSArray* _Nullable)accessibilityElements {
+ NSMutableArray* children = [[[NSMutableArray alloc] init] autorelease];
+ uint32_t childCount = mGeckoAccessible->ChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ if (MUIAccessible* child =
+ GetNativeFromGeckoAccessible(mGeckoAccessible->ChildAt(i))) {
+ [children addObject:child];
+ }
+ }
+
+ return children;
+}
+
+- (UIAccessibilityContainerType)accessibilityContainerType {
+ return UIAccessibilityContainerTypeNone;
+}
+
+- (NSRange)_accessibilitySelectedTextRange {
+ if (!mGeckoAccessible || !mGeckoAccessible->IsHyperText()) {
+ return NSMakeRange(NSNotFound, 0);
+ }
+ // XXX This will only work in simple plain text boxes. It will break horribly
+ // if there are any embedded objects. Also, it only supports caret, not
+ // selection.
+ int32_t caret = mGeckoAccessible->AsHyperTextBase()->CaretOffset();
+ if (caret != -1) {
+ return NSMakeRange(caret, 0);
+ }
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)_accessibilitySetSelectedTextRange:(NSRange)range {
+ if (!mGeckoAccessible || !mGeckoAccessible->IsHyperText()) {
+ return;
+ }
+ // XXX This will only work in simple plain text boxes. It will break horribly
+ // if there are any embedded objects. Also, it only supports caret, not
+ // selection.
+ mGeckoAccessible->AsHyperTextBase()->SetCaretOffset(range.location);
+}
+
+@end
diff --git a/accessible/ios/MUIRootAccessible.h b/accessible/ios/MUIRootAccessible.h
new file mode 100644
index 0000000000..e3ce3b9c35
--- /dev/null
+++ b/accessible/ios/MUIRootAccessible.h
@@ -0,0 +1,29 @@
+/* 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 "MUIAccessible.h"
+
+// our protocol that we implement (so uikit widgets can talk to us)
+#import "mozilla/a11y/MUIRootAccessibleProtocol.h"
+
+/*
+ The root accessible. It acts as a delegate to the UIKit child view.
+*/
+@interface MUIRootAccessible : MUIAccessible <MUIRootAccessibleProtocol> {
+ id<MUIRootAccessibleProtocol> mParallelView; // weak ref
+}
+
+// override
+- (id)initWithAccessible:(mozilla::a11y::Accessible*)aAcc;
+
+// override
+- (BOOL)hasRepresentedView;
+
+// override
+- (id)representedView;
+
+@end
diff --git a/accessible/ios/MUIRootAccessible.mm b/accessible/ios/MUIRootAccessible.mm
new file mode 100644
index 0000000000..35c87272e9
--- /dev/null
+++ b/accessible/ios/MUIRootAccessible.mm
@@ -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/. */
+
+#include "RootAccessibleWrap.h"
+
+#import "MUIRootAccessible.h"
+#import <UIKit/UIScreen.h>
+
+using namespace mozilla::a11y;
+
+static id<MUIRootAccessibleProtocol> getNativeViewFromRootAccessible(
+ LocalAccessible* aAccessible) {
+ RootAccessibleWrap* root =
+ static_cast<RootAccessibleWrap*>(aAccessible->AsRoot());
+ id<MUIRootAccessibleProtocol> nativeView = nil;
+ root->GetNativeWidget((void**)&nativeView);
+ return nativeView;
+}
+
+#pragma mark -
+
+@implementation MUIRootAccessible
+
+- (id)initWithAccessible:(mozilla::a11y::Accessible*)aAcc {
+ MOZ_ASSERT(!aAcc->IsRemote(), "MUIRootAccessible is never remote");
+
+ mParallelView = getNativeViewFromRootAccessible(aAcc->AsLocal());
+
+ return [super initWithAccessible:aAcc];
+}
+
+- (BOOL)hasRepresentedView {
+ return YES;
+}
+
+// this will return our parallel UIView.
+- (id)representedView {
+ return mParallelView;
+}
+
+@end
diff --git a/accessible/ios/MUIRootAccessibleProtocol.h b/accessible/ios/MUIRootAccessibleProtocol.h
new file mode 100644
index 0000000000..23451021ac
--- /dev/null
+++ b/accessible/ios/MUIRootAccessibleProtocol.h
@@ -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/. */
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIAccessibility.h>
+
+/* This protocol's primary use is so widget/cocoa can talk back to us
+ properly.
+
+ ChildView owns the topmost MUIRootAccessible, 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 MUIRootAccessibleProtocol <NSObject>
+
+- (BOOL)hasRepresentedView;
+
+- (nullable id)representedView;
+
+// UIAccessibility
+
+- (BOOL)isAccessibilityElement;
+
+- (nullable NSString*)accessibilityLabel;
+
+- (CGRect)accessibilityFrame;
+
+- (nullable NSString*)accessibilityValue;
+
+- (uint64_t)accessibilityTraits;
+
+// UIAccessibilityContainer
+
+- (NSInteger)accessibilityElementCount;
+
+- (nullable id)accessibilityElementAtIndex:(NSInteger)index;
+
+- (NSInteger)indexOfAccessibilityElement:(nonnull id)element;
+
+- (nullable NSArray*)accessibilityElements;
+
+- (UIAccessibilityContainerType)accessibilityContainerType;
+
+@end
diff --git a/accessible/ios/Platform.mm b/accessible/ios/Platform.mm
new file mode 100644
index 0000000000..b4342ec555
--- /dev/null
+++ b/accessible/ios/Platform.mm
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Platform.h"
+#include "RemoteAccessible.h"
+#include "DocAccessibleParent.h"
+
+#import "MUIAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+bool ShouldA11yBeEnabled() {
+ // XXX: Figure out proper a11y activation strategies in iOS.
+ return true;
+}
+
+void PlatformInit() {}
+
+void PlatformShutdown() {}
+
+void ProxyCreated(RemoteAccessible* aProxy) {
+ MUIAccessible* mozWrapper = [[MUIAccessible alloc] initWithAccessible:aProxy];
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
+}
+
+void ProxyDestroyed(RemoteAccessible* aProxy) {
+ MUIAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
+ [wrapper expire];
+ [wrapper release];
+ aProxy->SetWrapper(0);
+}
+
+void PlatformEvent(Accessible*, uint32_t) {}
+
+void PlatformStateChangeEvent(Accessible*, uint64_t, bool) {}
+
+void PlatformFocusEvent(Accessible* aTarget,
+ const LayoutDeviceIntRect& aCaretRect) {}
+
+void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset,
+ bool aIsSelectionCollapsed, int32_t aGranularity,
+ const LayoutDeviceIntRect& aCaretRect,
+ bool aFromUser) {}
+
+void PlatformTextChangeEvent(Accessible*, const nsAString&, int32_t, uint32_t,
+ bool, bool) {}
+
+void PlatformShowHideEvent(Accessible*, Accessible*, bool, bool) {}
+
+void PlatformSelectionEvent(Accessible*, Accessible*, uint32_t) {}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ios/RootAccessibleWrap.h b/accessible/ios/RootAccessibleWrap.h
new file mode 100644
index 0000000000..2353d5e791
--- /dev/null
+++ b/accessible/ios/RootAccessibleWrap.h
@@ -0,0 +1,42 @@
+/* -*- 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 https://firefox-source-docs.mozilla.org/accessible/index.html
+ */
+
+#ifndef mozilla_a11y_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+struct CGRect;
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+/**
+ * iOS specific functionality for the node at a root of the accessibility
+ * tree: see the RootAccessible superclass for further details.
+ */
+class RootAccessibleWrap : public RootAccessible {
+ public:
+ RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+ virtual ~RootAccessibleWrap() = default;
+
+ // Lets our native accessible get in touch with the
+ // native cocoa view that is our accessible parent.
+ void GetNativeWidget(void** aOutView);
+
+ CGRect DevPixelsRectToUIKit(const LayoutDeviceIntRect& aRect);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ios/RootAccessibleWrap.mm b/accessible/ios/RootAccessibleWrap.mm
new file mode 100644
index 0000000000..1d2a404161
--- /dev/null
+++ b/accessible/ios/RootAccessibleWrap.mm
@@ -0,0 +1,50 @@
+/* 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 "MUIRootAccessible.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) {}
+
+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_DIAGNOSTIC_ASSERT(*aOutView, "Couldn't get the native UIView!");
+ }
+ }
+ }
+}
+
+CGRect RootAccessibleWrap::DevPixelsRectToUIKit(
+ const LayoutDeviceIntRect& aRect) {
+ UIView* nativeWidget = nil;
+ GetNativeWidget((void**)&nativeWidget);
+ CGRect rootFrame = [nativeWidget accessibilityFrame];
+ CGFloat scale = [nativeWidget contentScaleFactor];
+ return CGRectMake(((CGFloat)aRect.x / scale) + rootFrame.origin.x,
+ ((CGFloat)aRect.y / scale) + rootFrame.origin.y,
+ (CGFloat)aRect.width / scale,
+ (CGFloat)aRect.height / scale);
+}
diff --git a/accessible/ios/moz.build b/accessible/ios/moz.build
new file mode 100644
index 0000000000..fd94c6cc97
--- /dev/null
+++ b/accessible/ios/moz.build
@@ -0,0 +1,30 @@
+# -*- 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.mozilla.a11y += [
+ "AccessibleWrap.h",
+ "MUIRootAccessibleProtocol.h",
+]
+
+SOURCES += [
+ "AccessibleWrap.mm",
+ "MUIAccessible.mm",
+ "MUIRootAccessible.mm",
+ "Platform.mm",
+ "RootAccessibleWrap.mm",
+]
+
+LOCAL_INCLUDES += [
+ "/accessible/base",
+ "/accessible/generic",
+ "/accessible/html",
+ "/accessible/ipc",
+ "/accessible/xul",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")