diff options
Diffstat (limited to 'accessible/ios')
-rw-r--r-- | accessible/ios/.clang-format | 11 | ||||
-rw-r--r-- | accessible/ios/AccessibleWrap.h | 47 | ||||
-rw-r--r-- | accessible/ios/AccessibleWrap.mm | 49 | ||||
-rw-r--r-- | accessible/ios/ApplicationAccessibleWrap.h | 20 | ||||
-rw-r--r-- | accessible/ios/DocAccessibleWrap.h | 23 | ||||
-rw-r--r-- | accessible/ios/MUIAccessible.h | 72 | ||||
-rw-r--r-- | accessible/ios/MUIAccessible.mm | 497 | ||||
-rw-r--r-- | accessible/ios/MUIRootAccessible.h | 29 | ||||
-rw-r--r-- | accessible/ios/MUIRootAccessible.mm | 45 | ||||
-rw-r--r-- | accessible/ios/MUIRootAccessibleProtocol.h | 51 | ||||
-rw-r--r-- | accessible/ios/Platform.mm | 57 | ||||
-rw-r--r-- | accessible/ios/RootAccessibleWrap.h | 42 | ||||
-rw-r--r-- | accessible/ios/RootAccessibleWrap.mm | 50 | ||||
-rw-r--r-- | accessible/ios/moz.build | 30 |
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") |