diff options
Diffstat (limited to 'accessible')
40 files changed, 4062 insertions, 0 deletions
diff --git a/accessible/base/moz.build b/accessible/base/moz.build index 54627ca50c..ea9b67aee9 100644 --- a/accessible/base/moz.build +++ b/accessible/base/moz.build @@ -96,6 +96,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': '/accessible/windows/ia2', '/accessible/windows/msaa', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build index 720d9bf01b..6855daf909 100644 --- a/accessible/generic/moz.build +++ b/accessible/generic/moz.build @@ -52,6 +52,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': '/accessible/windows/ia2', '/accessible/windows/msaa', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/html/moz.build b/accessible/html/moz.build index a18c4e59b1..e486f10456 100644 --- a/accessible/html/moz.build +++ b/accessible/html/moz.build @@ -32,6 +32,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': '/accessible/windows/ia2', '/accessible/windows/msaa', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build index 91fd1fa4d3..cb852de271 100644 --- a/accessible/ipc/moz.build +++ b/accessible/ipc/moz.build @@ -19,6 +19,10 @@ else: LOCAL_INCLUDES += [ '/accessible/atk', ] + elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/ipc/other/moz.build b/accessible/ipc/other/moz.build index 489520cef6..50f96de040 100644 --- a/accessible/ipc/other/moz.build +++ b/accessible/ipc/other/moz.build @@ -28,6 +28,10 @@ if CONFIG['ACCESSIBILITY']: LOCAL_INCLUDES += [ '/accessible/atk', ] + elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/mac/ARIAGridAccessibleWrap.h b/accessible/mac/ARIAGridAccessibleWrap.h new file mode 100644 index 0000000000..5d397e915c --- /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..6c746ff0dc --- /dev/null +++ b/accessible/mac/AccessibleWrap.h @@ -0,0 +1,103 @@ +/* -*- 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/. */ + +/* 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 "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 bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override; + virtual bool RemoveChild(Accessible* aAccessible) override; + + virtual nsresult HandleAccEvent(AccEvent* aEvent) override; + +protected: + + /** + * Return true if the parent doesn't have children to expose to AT. + */ + bool AncestorIsFlat(); + + /** + * 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; +}; + +#if defined(__OBJC__) + void FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType); +#else + void FireNativeEvent(id aNativeAcc, uint32_t aEventType); +#endif + +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..65f2e1db42 --- /dev/null +++ b/accessible/mac/AccessibleWrap.mm @@ -0,0 +1,256 @@ +/* -*- 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 "DocAccessible.h" +#include "nsObjCExceptions.h" + +#include "Accessible-inl.h" +#include "nsAccUtils.h" +#include "Role.h" + +#import "mozAccessible.h" +#import "mozActionElements.h" +#import "mozHTMLAccessible.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) +{ +} + +AccessibleWrap::~AccessibleWrap() +{ +} + +mozAccessible* +AccessibleWrap::GetNativeObject() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) { + uintptr_t accWrap = reinterpret_cast<uintptr_t>(this); + mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap]; + } + + 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]; + + 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); + + if (IPCAccessibilityActive()) { + return NS_OK; + } + + uint32_t eventType = aEvent->GetEventType(); + + // ignore everything but focus-changed, value-changed, caret, selection + // and document load complete events for now. + if (eventType != nsIAccessibleEvent::EVENT_FOCUS && + eventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE && + eventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE && + eventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED && + eventType != nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED && + eventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE) + return NS_OK; + + Accessible* accessible = aEvent->GetAccessible(); + NS_ENSURE_STATE(accessible); + + mozAccessible *nativeAcc = nil; + accessible->GetNativeInterface((void**)&nativeAcc); + if (!nativeAcc) + return NS_ERROR_FAILURE; + + FireNativeEvent(nativeAcc, eventType); + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +bool +AccessibleWrap::InsertChildAt(uint32_t aIdx, Accessible* aAccessible) +{ + bool inserted = Accessible::InsertChildAt(aIdx, aAccessible); + if (inserted && mNativeObject) + [mNativeObject appendChild:aAccessible]; + + return inserted; +} + +bool +AccessibleWrap::RemoveChild(Accessible* aAccessible) +{ + bool removed = Accessible::RemoveChild(aAccessible); + + if (removed && mNativeObject) + [mNativeObject invalidateChildren]; + + return removed; +} + +//////////////////////////////////////////////////////////////////////////////// +// AccessibleWrap protected + +bool +AccessibleWrap::AncestorIsFlat() +{ + // 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. + // + // 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. + + Accessible* parent = Parent(); + while (parent) { + if (nsAccUtils::MustPrune(parent)) + return true; + + parent = parent->Parent(); + } + // no parent was flat + return false; +} + +void +a11y::FireNativeEvent(mozAccessible* aNativeAcc, uint32_t aEventType) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + switch (aEventType) { + case nsIAccessibleEvent::EVENT_FOCUS: + [aNativeAcc didReceiveFocus]; + break; + case nsIAccessibleEvent::EVENT_VALUE_CHANGE: + case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: + [aNativeAcc valueDidChange]; + break; + case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: + case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: + [aNativeAcc selectedTextDidChange]; + break; + case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: + [aNativeAcc documentLoadComplete]; + break; + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +Class +a11y::GetTypeFromRole(roles::Role aRole) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + switch (aRole) { + case roles::COMBOBOX: + case roles::PUSHBUTTON: + case roles::SPLITBUTTON: + case roles::TOGGLE_BUTTON: + { + return [mozButtonAccessible class]; + } + + case roles::PAGETAB: + return [mozButtonAccessible class]; + + case roles::CHECKBUTTON: + return [mozCheckboxAccessible class]; + + case roles::HEADING: + return [mozHeadingAccessible class]; + + case roles::PAGETABLIST: + return [mozTabsAccessible class]; + + case roles::ENTRY: + case roles::STATICTEXT: + case roles::CAPTION: + case roles::ACCEL_LABEL: + case roles::PASSWORD_TEXT: + // normal textfield (static or editable) + return [mozTextAccessible class]; + + case roles::TEXT_LEAF: + return [mozTextLeafAccessible class]; + + case roles::LINK: + return [mozLinkAccessible 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..9343c29ddc --- /dev/null +++ b/accessible/mac/ApplicationAccessibleWrap.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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..3e80a0d33c --- /dev/null +++ b/accessible/mac/DocAccessibleWrap.h @@ -0,0 +1,25 @@ +/* -*- 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_DocAccessibleWrap_h__ +#define mozilla_a11y_DocAccessibleWrap_h__ + +#include "DocAccessible.h" + +namespace mozilla { +namespace a11y { + +class DocAccessibleWrap : public DocAccessible +{ +public: + DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell); + virtual ~DocAccessibleWrap(); + +}; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/mac/DocAccessibleWrap.mm b/accessible/mac/DocAccessibleWrap.mm new file mode 100644 index 0000000000..8a513f485a --- /dev/null +++ b/accessible/mac/DocAccessibleWrap.mm @@ -0,0 +1,21 @@ +/* -*- 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 "DocAccessibleWrap.h" + +#import "mozAccessible.h" + +using namespace mozilla::a11y; + +DocAccessibleWrap:: + DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) : + DocAccessible(aDocument, aPresShell) +{ +} + +DocAccessibleWrap::~DocAccessibleWrap() +{ +} + diff --git a/accessible/mac/HTMLTableAccessibleWrap.h b/accessible/mac/HTMLTableAccessibleWrap.h new file mode 100644 index 0000000000..4f158e241d --- /dev/null +++ b/accessible/mac/HTMLTableAccessibleWrap.h @@ -0,0 +1,24 @@ +/* -*- 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..fb335ef0f7 --- /dev/null +++ b/accessible/mac/HyperTextAccessibleWrap.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_HyperTextAccessibleWrap_h__ +#define mozilla_a11y_HyperTextAccessibleWrap_h__ + +#include "HyperTextAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class HyperTextAccessible HyperTextAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif + diff --git a/accessible/mac/ImageAccessibleWrap.h b/accessible/mac/ImageAccessibleWrap.h new file mode 100644 index 0000000000..069efb6511 --- /dev/null +++ b/accessible/mac/ImageAccessibleWrap.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_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/MacUtils.h b/accessible/mac/MacUtils.h new file mode 100644 index 0000000000..f88a27ee58 --- /dev/null +++ b/accessible/mac/MacUtils.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef _MacUtils_H_ +#define _MacUtils_H_ + +@class NSString; +class nsString; + +namespace mozilla { +namespace a11y { +namespace utils { + +/** + * Get a localized string from the string bundle. + * Return nil if not found. + */ +NSString* LocalizedString(const nsString& aString); + +} +} +} + +#endif diff --git a/accessible/mac/MacUtils.mm b/accessible/mac/MacUtils.mm new file mode 100644 index 0000000000..2ce03fe966 --- /dev/null +++ b/accessible/mac/MacUtils.mm @@ -0,0 +1,32 @@ +/* -*- 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 "MacUtils.h" + +#include "Accessible.h" + +#include "nsCocoaUtils.h" + +namespace mozilla { +namespace a11y { +namespace utils { + +/** + * 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); +} + +} +} +} diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm new file mode 100644 index 0000000000..a104bf904c --- /dev/null +++ b/accessible/mac/Platform.mm @@ -0,0 +1,174 @@ +/* -*- 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 <Cocoa/Cocoa.h> + +#include "Platform.h" +#include "ProxyAccessible.h" +#include "DocAccessibleParent.h" +#include "mozTableAccessible.h" + +#include "nsAppShell.h" + +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) +{ + // 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 + type = GetTypeFromRole(aProxy->Role()); + + uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY; + mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap]; + aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper)); + + mozAccessible* nativeParent = nullptr; + if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) { + // If proxy is top level, the parent we need to invalidate the children of + // will be a non-remote accessible. + Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser(); + if (outerDoc) { + nativeParent = GetNativeFromGeckoAccessible(outerDoc); + } + } else { + // Non-top level proxies need proxy parents' children invalidated. + ProxyAccessible* parent = aProxy->Parent(); + nativeParent = GetNativeFromProxy(parent); + NS_ASSERTION(parent, "a non-top-level proxy is missing a parent?"); + } + + if (nativeParent) { + [nativeParent invalidateChildren]; + } +} + +void +ProxyDestroyed(ProxyAccessible* aProxy) +{ + mozAccessible* nativeParent = nil; + if (aProxy->IsDoc() && aProxy->AsDoc()->IsTopLevel()) { + // Invalidate native parent in parent process's children on proxy destruction + Accessible* outerDoc = aProxy->OuterDocOfRemoteBrowser(); + if (outerDoc) { + nativeParent = GetNativeFromGeckoAccessible(outerDoc); + } + } else { + if (!aProxy->Document()->IsShutdown()) { + // Only do if the document has not been shut down, else parent will return + // garbage since we don't shut down children from top down. + ProxyAccessible* parent = aProxy->Parent(); + // Invalidate proxy parent's children. + if (parent) { + nativeParent = GetNativeFromProxy(parent); + } + } + } + + mozAccessible* wrapper = GetNativeFromProxy(aProxy); + [wrapper expire]; + [wrapper release]; + aProxy->SetWrapper(0); + + if (nativeParent) { + [nativeParent invalidateChildren]; + } +} + +void +ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType) +{ + // ignore everything but focus-changed, value-changed, caret and selection + // events for now. + 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_TEXT_SELECTION_CHANGED) + return; + + mozAccessible* wrapper = GetNativeFromProxy(aProxy); + if (wrapper) + FireNativeEvent(wrapper, aEventType); +} + +void +ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool) +{ + // mac doesn't care about state change events +} + +void +ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset) +{ + mozAccessible* wrapper = GetNativeFromProxy(aTarget); + if (wrapper) + [wrapper selectedTextDidChange]; +} + +void +ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t, + bool, bool) +{ +} + +void +ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool) +{ +} + +void +ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t) +{ +} +} // 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); + + return [super accessibilitySetValue:value forAttribute:attribute]; +} + +@end + diff --git a/accessible/mac/RootAccessibleWrap.h b/accessible/mac/RootAccessibleWrap.h new file mode 100644 index 0000000000..aa53e06ac0 --- /dev/null +++ b/accessible/mac/RootAccessibleWrap.h @@ -0,0 +1,34 @@ +/* -*- 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 { +namespace a11y { + +class RootAccessibleWrap : public RootAccessible +{ +public: + RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* 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..037545cce2 --- /dev/null +++ b/accessible/mac/RootAccessibleWrap.mm @@ -0,0 +1,53 @@ +/* -*- 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 "RootAccessibleWrap.h" + +#include "mozDocAccessible.h" + +#include "nsCOMPtr.h" +#include "nsObjCExceptions.h" +#include "nsIFrame.h" +#include "nsView.h" +#include "nsIWidget.h" + +using namespace mozilla::a11y; + +RootAccessibleWrap:: + RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* 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); + NS_ASSERTION (*aOutView, + "Couldn't get the native NSView parent we need to connect the accessibility hierarchy!"); + } + } + } +} diff --git a/accessible/mac/TextLeafAccessibleWrap.h b/accessible/mac/TextLeafAccessibleWrap.h new file mode 100644 index 0000000000..d07b9defec --- /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..f7dc6cc547 --- /dev/null +++ b/accessible/mac/XULListboxAccessibleWrap.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_XULListboxAccessibleWrap_h__ +#define mozilla_a11y_XULListboxAccessibleWrap_h__ + +#include "XULListboxAccessible.h" + +namespace mozilla { +namespace a11y { + +typedef class XULListboxAccessible XULListboxAccessibleWrap; +typedef class XULListCellAccessible XULListCellAccessibleWrap; + +} // namespace a11y +} // namespace mozilla + +#endif diff --git a/accessible/mac/XULMenuAccessibleWrap.h b/accessible/mac/XULMenuAccessibleWrap.h new file mode 100644 index 0000000000..6efcf007eb --- /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..b3631e9adb --- /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..8d2e7b391f --- /dev/null +++ b/accessible/mac/moz.build @@ -0,0 +1,44 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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', +] + +SOURCES += [ + 'AccessibleWrap.mm', + 'DocAccessibleWrap.mm', + 'MacUtils.mm', + 'mozAccessible.mm', + 'mozActionElements.mm', + 'mozDocAccessible.mm', + 'mozHTMLAccessible.mm', + 'mozTableAccessible.mm', + 'mozTextAccessible.mm', + 'Platform.mm', + 'RootAccessibleWrap.mm', +] + +LOCAL_INCLUDES += [ + '/accessible/base', + '/accessible/generic', + '/accessible/html', + '/accessible/ipc', + '/accessible/ipc/other', + '/accessible/xul', + '/layout/generic', + '/layout/xul', + '/widget', + '/widget/cocoa', +] + +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..6d7db3fe98 --- /dev/null +++ b/accessible/mac/mozAccessible.h @@ -0,0 +1,181 @@ +/* -*- 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/. */ + +#include "AccessibleWrap.h" +#include "ProxyAccessible.h" + +#import <Cocoa/Cocoa.h> + +#import "mozAccessibleProtocol.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 id <mozAccessible> +GetObjectOrRepresentedView(id <mozAccessible> aObject) +{ + return [aObject hasRepresentedView] ? [aObject representedView] : aObject; +} + +inline mozAccessible* +GetNativeFromGeckoAccessible(Accessible* aAccessible) +{ + mozAccessible* native = nil; + aAccessible->GetNativeInterface((void**)&native); + return native; +} + +inline mozAccessible* +GetNativeFromProxy(const ProxyAccessible* aProxy) +{ + return reinterpret_cast<mozAccessible*>(aProxy->GetWrapper()); +} + +} // a11y +} // mozilla + +// This is OR'd with the Accessible owner to indicate the wrap-ee is a proxy. +static const uintptr_t IS_PROXY = 1; + +@interface mozAccessible : NSObject <mozAccessible> +{ + /** + * Weak reference; it owns us. + */ + uintptr_t mGeckoAccessible; + + /** + * Strong ref to array of children + */ + NSMutableArray* mChildren; + + /** + * Weak reference to the parent + */ + mozAccessible* mParent; + + /** + * The role of our gecko accessible. + */ + mozilla::a11y::role mRole; +} + +// return the Accessible for this mozAccessible if it exists. +- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible; + +// return the ProxyAccessible for this mozAccessible if it exists. +- (mozilla::a11y::ProxyAccessible*)getProxyAccessible; + +// inits with the gecko owner. +- (id)initWithAccessible:(uintptr_t)aGeckoObj; + +// our accessible parent (AXParent) +- (id <mozAccessible>)parent; + +// a lazy cache of our accessible children (AXChildren). updated +- (NSArray*)children; + +// returns the size of this accessible. +- (NSValue*)size; + +// returns the position, in cocoa coordinates. +- (NSValue*)position; + +// can be overridden to report another role name. +- (NSString*)role; + +// a subrole is a more specialized variant of the role. for example, +// the role might be "textfield", while the subrole is "password textfield". +- (NSString*)subrole; + +// Return the role description, as there are a few exceptions. +- (NSString*)roleDescription; + +// returns the native window we're inside. +- (NSWindow*)window; + +// the value of this element. +- (id)value; + +// name that is associated with this accessible (for buttons, etc) +- (NSString*)title; + +// the accessible description (help text) of this particular instance. +- (NSString*)help; + +- (BOOL)isEnabled; + +// information about focus. +- (BOOL)isFocused; +- (BOOL)canBeFocused; + +// returns NO if for some reason we were unable to focus the element. +- (BOOL)focus; + +// notifications sent out to listening accessible providers. +- (void)didReceiveFocus; +- (void)valueDidChange; +- (void)selectedTextDidChange; +- (void)documentLoadComplete; + +// internal method to retrieve a child at a given index. +- (id)childAt:(uint32_t)i; + +#pragma mark - + +// invalidates and removes all our children from our cached array. +- (void)invalidateChildren; + +/** + * Append a child if they are already cached. + */ +- (void)appendChild:(mozilla::a11y::Accessible*)aAccessible; + +// 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; +- (BOOL)isExpired; + +#ifdef DEBUG +- (void)printHierarchy; +- (void)printHierarchyWithLevel:(unsigned)numSpaces; + +- (void)sanityCheckChildren; +- (void)sanityCheckChildren:(NSArray*)theChildren; +#endif + +// ---- NSAccessibility methods ---- // + +// whether to skip this element when traversing the accessibility +// hierarchy. +- (BOOL)accessibilityIsIgnored; + +// called by third-parties to determine the deepest child element under the mouse +- (id)accessibilityHitTest:(NSPoint)point; + +// returns the deepest unignored focused accessible element +- (id)accessibilityFocusedUIElement; + +// a mozAccessible needs to at least provide links to its parent and +// children. +- (NSArray*)accessibilityAttributeNames; + +// value for the specified attribute +- (id)accessibilityAttributeValue:(NSString*)attribute; + +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute; +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute; + +@end + diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm new file mode 100644 index 0000000000..a02779ef25 --- /dev/null +++ b/accessible/mac/mozAccessible.mm @@ -0,0 +1,1197 @@ +/* -*- 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 "mozAccessible.h" + +#import "MacUtils.h" +#import "mozView.h" + +#include "Accessible-inl.h" +#include "nsAccUtils.h" +#include "nsIAccessibleRelation.h" +#include "nsIAccessibleEditableText.h" +#include "nsIPersistentProperties2.h" +#include "Relation.h" +#include "Role.h" +#include "RootAccessible.h" +#include "TableAccessible.h" +#include "TableCellAccessible.h" +#include "mozilla/a11y/PDocAccessible.h" +#include "OuterDocAccessible.h" + +#include "mozilla/Services.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; + +#define NSAccessibilityMathRootRadicandAttribute @"AXMathRootRadicand" +#define NSAccessibilityMathRootIndexAttribute @"AXMathRootIndex" +#define NSAccessibilityMathFractionNumeratorAttribute @"AXMathFractionNumerator" +#define NSAccessibilityMathFractionDenominatorAttribute @"AXMathFractionDenominator" +#define NSAccessibilityMathBaseAttribute @"AXMathBase" +#define NSAccessibilityMathSubscriptAttribute @"AXMathSubscript" +#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript" +#define NSAccessibilityMathUnderAttribute @"AXMathUnder" +#define NSAccessibilityMathOverAttribute @"AXMathOver" +#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness" +// XXX WebKit also defines the following attributes. +// See bugs 1176970 and 1176983. +// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen" +// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose" +// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts" +// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts" + +#pragma mark - + +@implementation mozAccessible + +- (id)initWithAccessible:(uintptr_t)aGeckoAccessible +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ((self = [super init])) { + mGeckoAccessible = aGeckoAccessible; + if (aGeckoAccessible & IS_PROXY) + mRole = [self getProxyAccessible]->Role(); + else + mRole = [self getGeckoAccessible]->Role(); + } + + return self; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void)dealloc +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [mChildren release]; + [super dealloc]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible +{ + // Check if mGeckoAccessible points at a proxy + if (mGeckoAccessible & IS_PROXY) + return nil; + + return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible); +} + +- (mozilla::a11y::ProxyAccessible*)getProxyAccessible +{ + // Check if mGeckoAccessible points at a proxy + if (!(mGeckoAccessible & IS_PROXY)) + return nil; + + return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY); +} + +#pragma mark - + +- (BOOL)accessibilityIsIgnored +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + // unknown (either unimplemented, or irrelevant) elements are marked as ignored + // as well as expired elements. + + bool noRole = [[self role] isEqualToString:NSAccessibilityUnknownRole]; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + return (noRole && !(accWrap->InteractiveState() & states::FOCUSABLE)); + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return (noRole && !(proxy->State() & states::FOCUSABLE)); + + return true; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +- (NSArray*)additionalAccessibilityAttributeNames +{ + NSMutableArray* additional = [NSMutableArray array]; + switch (mRole) { + case roles::MATHML_ROOT: + [additional addObject:NSAccessibilityMathRootIndexAttribute]; + [additional addObject:NSAccessibilityMathRootRadicandAttribute]; + break; + case roles::MATHML_SQUARE_ROOT: + [additional addObject:NSAccessibilityMathRootRadicandAttribute]; + break; + case roles::MATHML_FRACTION: + [additional addObject:NSAccessibilityMathFractionNumeratorAttribute]; + [additional addObject:NSAccessibilityMathFractionDenominatorAttribute]; + [additional addObject:NSAccessibilityMathLineThicknessAttribute]; + break; + case roles::MATHML_SUB: + case roles::MATHML_SUP: + case roles::MATHML_SUB_SUP: + [additional addObject:NSAccessibilityMathBaseAttribute]; + [additional addObject:NSAccessibilityMathSubscriptAttribute]; + [additional addObject:NSAccessibilityMathSuperscriptAttribute]; + break; + case roles::MATHML_UNDER: + case roles::MATHML_OVER: + case roles::MATHML_UNDER_OVER: + [additional addObject:NSAccessibilityMathBaseAttribute]; + [additional addObject:NSAccessibilityMathUnderAttribute]; + [additional addObject:NSAccessibilityMathOverAttribute]; + break; + // XXX bug 1176983 + // roles::MATHML_MULTISCRIPTS should also have the following attributes: + // - NSAccessibilityMathPrescriptsAttribute + // - NSAccessibilityMathPostscriptsAttribute + // XXX bug 1176970 + // roles::MATHML_FENCED should also have the following attributes: + // - NSAccessibilityMathFencedOpenAttribute + // - NSAccessibilityMathFencedCloseAttribute + default: + break; + } + + return additional; +} + +- (NSArray*)accessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + // if we're expired, we don't support any attributes. + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) + return [NSArray array]; + + static NSArray* generalAttributes = nil; + + if (!generalAttributes) { + // standard attributes that are shared and supported by all generic elements. + generalAttributes = [[NSArray alloc] initWithObjects: NSAccessibilityChildrenAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityRoleAttribute, + NSAccessibilityTitleAttribute, + NSAccessibilityValueAttribute, + NSAccessibilitySubroleAttribute, + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityPositionAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilitySizeAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityFocusedAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityTitleUIElementAttribute, + NSAccessibilityTopLevelUIElementAttribute, +#if DEBUG + @"AXMozDescription", +#endif + nil]; + } + + NSArray* objectAttributes = generalAttributes; + + NSArray* additionalAttributes = [self additionalAccessibilityAttributeNames]; + if ([additionalAttributes count]) + objectAttributes = [objectAttributes arrayByAddingObjectsFromArray:additionalAttributes]; + + return objectAttributes; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)childAt:(uint32_t)i +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + Accessible* child = accWrap->GetChildAt(i); + return child ? GetNativeFromGeckoAccessible(child) : nil; + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + ProxyAccessible* child = proxy->ChildAt(i); + return child ? GetNativeFromProxy(child) : nil; + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) + return nil; + +#if DEBUG + if ([attribute isEqualToString:@"AXMozDescription"]) + return [NSString stringWithFormat:@"role = %u native = %@", mRole, [self class]]; +#endif + + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) + return [self children]; + if ([attribute isEqualToString:NSAccessibilityParentAttribute]) + return [self parent]; + +#ifdef DEBUG_hakan + NSLog (@"(%@ responding to attr %@)", self, attribute); +#endif + + if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) + return [self role]; + if ([attribute isEqualToString:NSAccessibilityPositionAttribute]) + return [self position]; + if ([attribute isEqualToString:NSAccessibilitySubroleAttribute]) + return [self subrole]; + if ([attribute isEqualToString:NSAccessibilityEnabledAttribute]) + return [NSNumber numberWithBool:[self isEnabled]]; + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) + return [self value]; + if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) + return [self roleDescription]; + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) + return [NSNumber numberWithBool:[self isFocused]]; + if ([attribute isEqualToString:NSAccessibilitySizeAttribute]) + return [self size]; + if ([attribute isEqualToString:NSAccessibilityWindowAttribute]) + return [self window]; + if ([attribute isEqualToString:NSAccessibilityTopLevelUIElementAttribute]) + return [self window]; + if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) + return [self title]; + if ([attribute isEqualToString:NSAccessibilityTitleUIElementAttribute]) { + if (accWrap) { + Relation rel = accWrap->RelationByType(RelationType::LABELLED_BY); + Accessible* tempAcc = rel.Next(); + return tempAcc ? GetNativeFromGeckoAccessible(tempAcc) : nil; + } + nsTArray<ProxyAccessible*> rel = proxy->RelationByType(RelationType::LABELLED_BY); + ProxyAccessible* tempProxy = rel.SafeElementAt(0); + return tempProxy ? GetNativeFromProxy(tempProxy) : nil; + } + if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) + return [self help]; + + switch (mRole) { + case roles::MATHML_ROOT: + if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathRootIndexAttribute]) + return [self childAt:1]; + break; + case roles::MATHML_SQUARE_ROOT: + if ([attribute isEqualToString:NSAccessibilityMathRootRadicandAttribute]) + return [self childAt:0]; + break; + case roles::MATHML_FRACTION: + if ([attribute isEqualToString:NSAccessibilityMathFractionNumeratorAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute]) + return [self childAt:1]; + if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) { + // 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. + nsAutoString thickness; + if (accWrap) { + nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes(); + nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness); + } else { + AutoTArray<Attribute, 10> attrs; + proxy->Attributes(&attrs); + for (size_t i = 0 ; i < attrs.Length() ; i++) { + if (attrs.ElementAt(i).Name() == "thickness") { + thickness = attrs.ElementAt(i).Value(); + break; + } + } + } + double value = 1.0; + if (!thickness.IsEmpty()) + value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(), + nullptr); + return [NSNumber numberWithInteger:(value ? 1 : 0)]; + } + break; + case roles::MATHML_SUB: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) + return [self childAt:1]; +#ifdef DEBUG + if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) + return nil; +#endif + break; + case roles::MATHML_SUP: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; +#ifdef DEBUG + if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) + return nil; +#endif + if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) + return [self childAt:1]; + break; + case roles::MATHML_SUB_SUP: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathSubscriptAttribute]) + return [self childAt:1]; + if ([attribute isEqualToString:NSAccessibilityMathSuperscriptAttribute]) + return [self childAt:2]; + break; + case roles::MATHML_UNDER: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) + return [self childAt:1]; +#ifdef DEBUG + if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) + return nil; +#endif + break; + case roles::MATHML_OVER: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; +#ifdef DEBUG + if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) + return nil; +#endif + if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) + return [self childAt:1]; + break; + case roles::MATHML_UNDER_OVER: + if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute]) + return [self childAt:0]; + if ([attribute isEqualToString:NSAccessibilityMathUnderAttribute]) + return [self childAt:1]; + if ([attribute isEqualToString:NSAccessibilityMathOverAttribute]) + return [self childAt:2]; + break; + // XXX bug 1176983 + // roles::MATHML_MULTISCRIPTS should also have the following attributes: + // - NSAccessibilityMathPrescriptsAttribute + // - NSAccessibilityMathPostscriptsAttribute + // XXX bug 1176970 + // roles::MATHML_FENCED should also have the following attributes: + // - NSAccessibilityMathFencedOpenAttribute + // - NSAccessibilityMathFencedCloseAttribute + default: + break; + } + +#ifdef DEBUG + NSLog (@"!!! %@ can't respond to attribute %@", self, attribute); +#endif + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) + return [self canBeFocused]; + + return NO; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + +#ifdef DEBUG_hakan + NSLog (@"[%@] %@='%@'", self, attribute, value); +#endif + + // we only support focusing elements so far. + if ([attribute isEqualToString:NSAccessibilityFocusedAttribute] && [value boolValue]) + [self focus]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) + return nil; + + // 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)); + + mozAccessible* nativeChild = nil; + if (accWrap) { + Accessible* child = accWrap->ChildAtPoint(geckoPoint.x, geckoPoint.y, + Accessible::eDeepestChild); + if (child) + nativeChild = GetNativeFromGeckoAccessible(child); + } else if (proxy) { + ProxyAccessible* child = proxy->ChildAtPoint(geckoPoint.x, geckoPoint.y, + Accessible::eDeepestChild); + if (child) + nativeChild = GetNativeFromProxy(child); + } + + if (nativeChild) + return nativeChild; + + // if we didn't find anything, return ourself or child view. + return GetObjectOrRepresentedView(self); +} + +- (NSArray*)accessibilityActionNames +{ + return nil; +} + +- (NSString*)accessibilityActionDescription:(NSString*)action +{ + // by default we return whatever the MacOS API know about. + // if you have custom actions, override. + return NSAccessibilityActionDescription(action); +} + +- (void)accessibilityPerformAction:(NSString*)action +{ +} + +- (id)accessibilityFocusedUIElement +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) + return nil; + + mozAccessible* focusedChild = nil; + if (accWrap) { + Accessible* focusedGeckoChild = accWrap->FocusedChild(); + if (focusedGeckoChild) + focusedChild = GetNativeFromGeckoAccessible(focusedGeckoChild); + } else if (proxy) { + ProxyAccessible* focusedGeckoChild = proxy->FocusedChild(); + if (focusedGeckoChild) + focusedChild = GetNativeFromProxy(focusedGeckoChild); + } + + if (focusedChild) + return GetObjectOrRepresentedView(focusedChild); + + // return ourself if we can't get a native focused child. + return GetObjectOrRepresentedView(self); +} + +#pragma mark - + +- (id <mozAccessible>)parent +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + id nativeParent = nil; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + Accessible* accessibleParent = accWrap->Parent(); + if (accessibleParent) + nativeParent = GetNativeFromGeckoAccessible(accessibleParent); + if (nativeParent) + return GetObjectOrRepresentedView(nativeParent); + + // Return native of root accessible if we have no direct parent + nativeParent = GetNativeFromGeckoAccessible(accWrap->RootAccessible()); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if (ProxyAccessible* proxyParent = proxy->Parent()) { + nativeParent = GetNativeFromProxy(proxyParent); + } + + if (nativeParent) + return GetObjectOrRepresentedView(nativeParent); + + Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); + nativeParent = outerDoc ? + GetNativeFromGeckoAccessible(outerDoc) : nil; + } else { + return nil; + } + + NSAssert1 (nativeParent, @"!!! we can't find a parent for %@", self); + + return GetObjectOrRepresentedView(nativeParent); + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL)hasRepresentedView +{ + return NO; +} + +- (id)representedView +{ + return nil; +} + +- (BOOL)isRoot +{ + return NO; +} + +// gets our native children lazily. +// returns nil when there are no children. +- (NSArray*)children +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if (mChildren) + return mChildren; + + // get the array of children. + mChildren = [[NSMutableArray alloc] init]; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + if (accWrap) { + uint32_t childCount = accWrap->ChildCount(); + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { + mozAccessible* nativeChild = GetNativeFromGeckoAccessible(accWrap->GetChildAt(childIdx)); + if (nativeChild) + [mChildren addObject:nativeChild]; + } + + // children from child if this is an outerdoc + OuterDocAccessible* docOwner = accWrap->AsOuterDoc(); + if (docOwner) { + if (ProxyAccessible* proxyDoc = docOwner->RemoteChildDoc()) { + mozAccessible* nativeRemoteChild = GetNativeFromProxy(proxyDoc); + [mChildren insertObject:nativeRemoteChild atIndex:0]; + NSAssert1 (nativeRemoteChild, @"%@ found a child remote doc missing a native\n", self); + } + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + uint32_t childCount = proxy->ChildrenCount(); + for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { + mozAccessible* nativeChild = GetNativeFromProxy(proxy->ChildAt(childIdx)); + if (nativeChild) + [mChildren addObject:nativeChild]; + } + + } + + return mChildren; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSValue*)position +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + nsIntRect rect; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + rect = accWrap->Bounds(); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + rect = proxy->Bounds(); + else + return nil; + + 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]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSValue*)size +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + nsIntRect rect; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + rect = accWrap->Bounds(); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + rect = proxy->Bounds(); + else + return nil; + + CGFloat scaleFactor = + nsCocoaUtils::GetBackingScaleFactor([[NSScreen screens] objectAtIndex:0]); + return [NSValue valueWithSize:NSMakeSize(static_cast<CGFloat>(rect.width) / scaleFactor, + static_cast<CGFloat>(rect.height) / scaleFactor)]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSString*)role +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + if (accWrap) { + #ifdef DEBUG_A11Y + NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap), + "Does not support Text when it should"); + #endif + } else if (![self getProxyAccessible]) { + return nil; + } + +#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \ + case roles::geckoRole: \ + return macRole; + + switch (mRole) { +#include "RoleMap.h" + default: + NS_NOTREACHED("Unknown role."); + return NSAccessibilityUnknownRole; + } + +#undef ROLE +} + +- (NSString*)subrole +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + + // Deal with landmarks first + nsIAtom* landmark = nullptr; + if (accWrap) + landmark = accWrap->LandmarkRole(); + else if (proxy) + landmark = proxy->LandmarkRole(); + + if (landmark) { + if (landmark == nsGkAtoms::application) + return @"AXLandmarkApplication"; + if (landmark == nsGkAtoms::banner) + return @"AXLandmarkBanner"; + if (landmark == nsGkAtoms::complementary) + return @"AXLandmarkComplementary"; + if (landmark == nsGkAtoms::contentinfo) + return @"AXLandmarkContentInfo"; + if (landmark == nsGkAtoms::form) + return @"AXLandmarkForm"; + if (landmark == nsGkAtoms::main) + return @"AXLandmarkMain"; + if (landmark == nsGkAtoms::navigation) + return @"AXLandmarkNavigation"; + if (landmark == nsGkAtoms::search) + return @"AXLandmarkSearch"; + if (landmark == nsGkAtoms::searchbox) + return @"AXSearchField"; + } + + // Now, deal with widget roles + nsIAtom* roleAtom = nullptr; + if (accWrap && accWrap->HasARIARole()) { + const nsRoleMapEntry* roleMap = accWrap->ARIARoleMap(); + roleAtom = *roleMap->roleAtom; + } + if (proxy) + roleAtom = proxy->ARIARoleAtom(); + + if (roleAtom) { + if (roleAtom == nsGkAtoms::alert) + return @"AXApplicationAlert"; + if (roleAtom == nsGkAtoms::alertdialog) + return @"AXApplicationAlertDialog"; + if (roleAtom == nsGkAtoms::article) + return @"AXDocumentArticle"; + if (roleAtom == nsGkAtoms::dialog) + return @"AXApplicationDialog"; + if (roleAtom == nsGkAtoms::document) + return @"AXDocument"; + if (roleAtom == nsGkAtoms::log_) + return @"AXApplicationLog"; + if (roleAtom == nsGkAtoms::math) + return @"AXDocumentMath"; + if (roleAtom == nsGkAtoms::note_) + return @"AXDocumentNote"; + if (roleAtom == nsGkAtoms::region) + return @"AXDocumentRegion"; + if (roleAtom == nsGkAtoms::status) + return @"AXApplicationStatus"; + if (roleAtom == nsGkAtoms::tabpanel) + return @"AXTabPanel"; + if (roleAtom == nsGkAtoms::timer) + return @"AXApplicationTimer"; + if (roleAtom == nsGkAtoms::tooltip) + return @"AXUserInterfaceTooltip"; + } + + switch (mRole) { + case roles::LIST: + return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole; + + case roles::ENTRY: + if ((accWrap && accWrap->IsSearchbox()) || + (proxy && proxy->IsSearchbox())) + return @"AXSearchField"; + break; + + case roles::DEFINITION_LIST: + return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole; + + case roles::TERM: + return @"AXTerm"; + + case roles::DEFINITION: + return @"AXDefinition"; + + case roles::MATHML_MATH: + return @"AXDocumentMath"; + + case roles::MATHML_FRACTION: + return @"AXMathFraction"; + + case roles::MATHML_FENCED: + // XXX bug 1176970 + // This should be AXMathFence, but doing so without implementing the + // whole fence interface seems to make VoiceOver crash, so we present it + // as a row for now. + return @"AXMathRow"; + + case roles::MATHML_SUB: + case roles::MATHML_SUP: + case roles::MATHML_SUB_SUP: + return @"AXMathSubscriptSuperscript"; + + case roles::MATHML_ROW: + case roles::MATHML_STYLE: + case roles::MATHML_ERROR: + return @"AXMathRow"; + + case roles::MATHML_UNDER: + case roles::MATHML_OVER: + case roles::MATHML_UNDER_OVER: + return @"AXMathUnderOver"; + + case roles::MATHML_SQUARE_ROOT: + return @"AXMathSquareRoot"; + + case roles::MATHML_ROOT: + return @"AXMathRoot"; + + case roles::MATHML_TEXT: + return @"AXMathText"; + + case roles::MATHML_NUMBER: + return @"AXMathNumber"; + + case roles::MATHML_IDENTIFIER: + return @"AXMathIdentifier"; + + case roles::MATHML_TABLE: + return @"AXMathTable"; + + case roles::MATHML_TABLE_ROW: + return @"AXMathTableRow"; + + case roles::MATHML_CELL: + return @"AXMathTableCell"; + + // XXX: NSAccessibility also uses subroles AXMathSeparatorOperator and + // AXMathFenceOperator. We should use the NS_MATHML_OPERATOR_FENCE and + // NS_MATHML_OPERATOR_SEPARATOR bits of nsOperatorFlags, but currently they + // are only available from the MathML layout code. Hence we just fallback + // to subrole AXMathOperator for now. + // XXX bug 1175747 WebKit also creates anonymous operators for <mfenced> + // which have subroles AXMathSeparatorOperator and AXMathFenceOperator. + case roles::MATHML_OPERATOR: + return @"AXMathOperator"; + + case roles::MATHML_MULTISCRIPTS: + return @"AXMathMultiscript"; + + case roles::SWITCH: + return @"AXSwitch"; + + case roles::ALERT: + return @"AXApplicationAlert"; + + case roles::SEPARATOR: + return @"AXContentSeparator"; + + case roles::PROPERTYPAGE: + return @"AXTabPanel"; + + case roles::DETAILS: + return @"AXDetails"; + + case roles::SUMMARY: + return @"AXSummary"; + + default: + break; + } + + return nil; +} + +struct RoleDescrMap +{ + NSString* role; + const nsString description; +}; + +static const RoleDescrMap sRoleDescrMap[] = { + { @"AXApplicationAlert", NS_LITERAL_STRING("alert") }, + { @"AXApplicationAlertDialog", NS_LITERAL_STRING("alertDialog") }, + { @"AXApplicationLog", NS_LITERAL_STRING("log") }, + { @"AXApplicationStatus", NS_LITERAL_STRING("status") }, + { @"AXApplicationTimer", NS_LITERAL_STRING("timer") }, + { @"AXContentSeparator", NS_LITERAL_STRING("separator") }, + { @"AXDefinition", NS_LITERAL_STRING("definition") }, + { @"AXDocument", NS_LITERAL_STRING("document") }, + { @"AXDocumentArticle", NS_LITERAL_STRING("article") }, + { @"AXDocumentMath", NS_LITERAL_STRING("math") }, + { @"AXDocumentNote", NS_LITERAL_STRING("note") }, + { @"AXDocumentRegion", NS_LITERAL_STRING("region") }, + { @"AXLandmarkApplication", NS_LITERAL_STRING("application") }, + { @"AXLandmarkBanner", NS_LITERAL_STRING("banner") }, + { @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") }, + { @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") }, + { @"AXLandmarkMain", NS_LITERAL_STRING("main") }, + { @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") }, + { @"AXLandmarkSearch", NS_LITERAL_STRING("search") }, + { @"AXSearchField", NS_LITERAL_STRING("searchTextField") }, + { @"AXTabPanel", NS_LITERAL_STRING("tabPanel") }, + { @"AXTerm", NS_LITERAL_STRING("term") }, + { @"AXUserInterfaceTooltip", NS_LITERAL_STRING("tooltip") } +}; + +struct RoleDescrComparator +{ + const NSString* mRole; + explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {} + int operator()(const RoleDescrMap& aEntry) const { + return [mRole compare:aEntry.role]; + } +}; + +- (NSString*)roleDescription +{ + if (mRole == roles::DOCUMENT) + return utils::LocalizedString(NS_LITERAL_STRING("htmlContent")); + + NSString* subrole = [self subrole]; + + if (subrole) { + size_t idx = 0; + if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap), + RoleDescrComparator(subrole), &idx)) { + return utils::LocalizedString(sRoleDescrMap[idx].description); + } + } + + return NSAccessibilityRoleDescription([self role], subrole); +} + +- (NSString*)title +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + nsAutoString title; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + accWrap->Name(title); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + proxy->Name(title); + + return nsCocoaUtils::ToNSString(title); + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)value +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + nsAutoString value; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + accWrap->Value(value); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + proxy->Value(value); + + return nsCocoaUtils::ToNSString(value); + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void)valueDidChange +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + +#ifdef DEBUG_hakan + NSLog(@"%@'s value changed!", self); +#endif + // sending out a notification is expensive, so we don't do it other than for really important objects, + // like mozTextAccessible. + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)selectedTextDidChange +{ + // Do nothing. mozTextAccessible will. +} + +- (void)documentLoadComplete +{ + id realSelf = GetObjectOrRepresentedView(self); + NSAccessibilityPostNotification(realSelf, NSAccessibilityFocusedUIElementChangedNotification); + NSAccessibilityPostNotification(realSelf, @"AXLoadComplete"); + NSAccessibilityPostNotification(realSelf, @"AXLayoutComplete"); +} + +- (NSString*)help +{ + 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 (AccessibleWrap* accWrap = [self getGeckoAccessible]) + accWrap->Description(helpText); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + proxy->Description(helpText); + + return nsCocoaUtils::ToNSString(helpText); + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +// objc-style description (from NSObject); not to be confused with the accessible description above. +- (NSString*)description +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [NSString stringWithFormat:@"(%p) %@", self, [self role]]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL)isFocused +{ + return FocusMgr()->IsFocused([self getGeckoAccessible]); +} + +- (BOOL)canBeFocused +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + return accWrap->InteractiveState() & states::FOCUSABLE; + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return proxy->State() & states::FOCUSABLE; + + return false; +} + +- (BOOL)focus +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + accWrap->TakeFocus(); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + proxy->TakeFocus(); + else + return NO; + + return YES; +} + +- (BOOL)isEnabled +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + return ((accWrap->InteractiveState() & states::UNAVAILABLE) == 0); + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return ((proxy->State() & states::UNAVAILABLE) == 0); + + return false; +} + +// The root accessible calls this when the focused node was +// changed to us. +- (void)didReceiveFocus +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + +#ifdef DEBUG_hakan + NSLog (@"%@ received focus!", self); +#endif + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), + NSAccessibilityFocusedUIElementChangedNotification); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (NSWindow*)window +{ + 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 (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + docAcc = accWrap->Document(); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + Accessible* outerDoc = proxy->OuterDocOfRemoteBrowser(); + if (outerDoc) + docAcc = outerDoc->Document(); + } + + if (docAcc) + nativeWindow = static_cast<NSWindow*>(docAcc->GetNativeWindow()); + + NSAssert1(nativeWindow, @"Could not get native window for %@", self); + return nativeWindow; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void)invalidateChildren +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // make room for new children + [mChildren release]; + mChildren = nil; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)appendChild:(Accessible*)aAccessible +{ + // if mChildren is nil, then we don't even need to bother + if (!mChildren) + return; + + mozAccessible *curNative = GetNativeFromGeckoAccessible(aAccessible); + if (curNative) + [mChildren addObject:curNative]; +} + +- (void)expire +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [self invalidateChildren]; + + mGeckoAccessible = 0; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (BOOL)isExpired +{ + return ![self getGeckoAccessible] && ![self getProxyAccessible]; +} + +#pragma mark - +#pragma mark Debug methods +#pragma mark - + +#ifdef DEBUG + +// will check that our children actually reference us as their +// parent. +- (void)sanityCheckChildren:(NSArray *)children +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSEnumerator *iter = [children objectEnumerator]; + mozAccessible *curObj = nil; + + NSLog(@"sanity checking %@", self); + + while ((curObj = [iter nextObject])) { + id realSelf = GetObjectOrRepresentedView(self); + NSLog(@"checking %@", realSelf); + NSAssert2([curObj parent] == realSelf, + @"!!! %@ not returning %@ as AXParent, even though it is a AXChild of it!", curObj, realSelf); + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)sanityCheckChildren +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [self sanityCheckChildren:[self children]]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)printHierarchy +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [self printHierarchyWithLevel:0]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)printHierarchyWithLevel:(unsigned)level +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSAssert(![self isExpired], @"!!! trying to print hierarchy of expired object!"); + + // print this node + NSMutableString *indent = [NSMutableString stringWithCapacity:level]; + unsigned i=0; + for (;i<level;i++) + [indent appendString:@" "]; + + NSLog (@"%@(#%i) %@", indent, level, self); + + // use |children| method to make sure our children are lazily fetched first. + NSArray *children = [self children]; + if (!children) + return; + + [self sanityCheckChildren]; + + NSEnumerator *iter = [children objectEnumerator]; + mozAccessible *object = nil; + + while (iter && (object = [iter nextObject])) + // print every child node's subtree, increasing the indenting + // by two for every level. + [object printHierarchyWithLevel:(level+1)]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +#endif /* DEBUG */ + +@end diff --git a/accessible/mac/mozAccessibleProtocol.h b/accessible/mac/mozAccessibleProtocol.h new file mode 100644 index 0000000000..5f67b1dcf2 --- /dev/null +++ b/accessible/mac/mozAccessibleProtocol.h @@ -0,0 +1,69 @@ +/* -*- 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 <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; + +#ifdef DEBUG +// debug utility that will print the native accessibility tree, starting +// at this node. +- (void)printHierarchy; +#endif + +/*** general ***/ + +// returns the accessible at the specified point. +- (id)accessibilityHitTest:(NSPoint)point; + +// whether this element is flagged as ignored. +- (BOOL)accessibilityIsIgnored; + +// 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..a325921eb8 --- /dev/null +++ b/accessible/mac/mozActionElements.h @@ -0,0 +1,37 @@ +/* -*- 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 <Cocoa/Cocoa.h> +#import "mozAccessible.h" + +/* Simple subclasses for things like checkboxes, buttons, etc. */ + +@interface mozButtonAccessible : mozAccessible + { + } +- (BOOL)hasPopup; +- (void)click; +- (BOOL)isTab; +@end + +@interface mozCheckboxAccessible : mozButtonAccessible +// returns one of the constants defined in CheckboxValue +- (int)isChecked; +@end + +/* Class for tabs - not individual tabs */ +@interface mozTabsAccessible : mozAccessible +{ + NSMutableArray* mTabs; +} +-(id)tabs; +@end + +/** + * Accessible for a PANE + */ +@interface mozPaneAccessible : mozAccessible + +@end diff --git a/accessible/mac/mozActionElements.mm b/accessible/mac/mozActionElements.mm new file mode 100644 index 0000000000..5decd6cccc --- /dev/null +++ b/accessible/mac/mozActionElements.mm @@ -0,0 +1,340 @@ +/* -*- 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 "mozActionElements.h" + +#import "MacUtils.h" +#include "Accessible-inl.h" +#include "DocAccessible.h" +#include "XULTabAccessible.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 + +- (NSArray*)accessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + static NSArray *attributes = nil; + if (!attributes) { + attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required + NSAccessibilityRoleAttribute, // required + NSAccessibilityRoleDescriptionAttribute, + NSAccessibilityPositionAttribute, // required + NSAccessibilitySizeAttribute, // required + NSAccessibilityWindowAttribute, // required + NSAccessibilityPositionAttribute, // required + NSAccessibilityTopLevelUIElementAttribute, // required + NSAccessibilityHelpAttribute, + NSAccessibilityEnabledAttribute, // required + NSAccessibilityFocusedAttribute, // required + NSAccessibilityTitleAttribute, // required + NSAccessibilityChildrenAttribute, + NSAccessibilityDescriptionAttribute, +#if DEBUG + @"AXMozDescription", +#endif + nil]; + } + return attributes; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + if ([self hasPopup]) + return [self children]; + return nil; + } + + if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { + if ([self isTab]) + return utils::LocalizedString(NS_LITERAL_STRING("tab")); + + return NSAccessibilityRoleDescription([self role], nil); + } + + return [super accessibilityAttributeValue:attribute]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL)accessibilityIsIgnored +{ + return ![self getGeckoAccessible] && ![self getProxyAccessible]; +} + +- (NSArray*)accessibilityActionNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([self isEnabled]) { + if ([self hasPopup]) + return [NSArray arrayWithObjects:NSAccessibilityPressAction, + NSAccessibilityShowMenuAction, + nil]; + return [NSArray arrayWithObject:NSAccessibilityPressAction]; + } + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSString*)accessibilityActionDescription:(NSString*)action +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([action isEqualToString:NSAccessibilityPressAction]) { + if ([self isTab]) + return utils::LocalizedString(NS_LITERAL_STRING("switch")); + + return @"press button"; // XXX: localize this later? + } + + if ([self hasPopup]) { + if ([action isEqualToString:NSAccessibilityShowMenuAction]) + return @"show menu"; + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void)accessibilityPerformAction:(NSString*)action +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if ([self isEnabled] && [action isEqualToString:NSAccessibilityPressAction]) { + // TODO: this should bring up the menu, but currently doesn't. + // once msaa and atk have merged better, they will implement + // the action needed to show the menu. + [self click]; + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)click +{ + // both buttons and checkboxes have only one action. we should really stop using arbitrary + // arrays with actions, and define constants for these actions. + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + accWrap->DoAction(0); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + proxy->DoAction(0); +} + +- (BOOL)isTab +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + return accWrap->Role() == roles::PAGETAB; + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return proxy->Role() == roles::PAGETAB; + + return false; +} + +- (BOOL)hasPopup +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + return accWrap->NativeState() & states::HASPOPUP; + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return proxy->NativeState() & states::HASPOPUP; + + return false; +} + +@end + +@implementation mozCheckboxAccessible + +- (NSString*)accessibilityActionDescription:(NSString*)action +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([action isEqualToString:NSAccessibilityPressAction]) { + if ([self isChecked] != kUnchecked) + return @"uncheck checkbox"; // XXX: localize this later? + + return @"check checkbox"; // XXX: localize this later? + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (int)isChecked +{ + uint64_t state = 0; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) + state = accWrap->NativeState(); + else if (ProxyAccessible* proxy = [self getProxyAccessible]) + state = proxy->NativeState(); + + // check if we're checked or in a mixed state + if (state & states::CHECKED) { + return (state & states::MIXED) ? kMixed : kChecked; + } + + return kUnchecked; +} + +- (id)value +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [NSNumber numberWithInt:[self isChecked]]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +@end + +@implementation mozTabsAccessible + +- (void)dealloc +{ + [mTabs release]; + + [super dealloc]; +} + +- (NSArray*)accessibilityAttributeNames +{ + // standard attributes that are shared and supported by root accessible (AXMain) elements. + static NSMutableArray* attributes = nil; + + if (!attributes) { + attributes = [[super accessibilityAttributeNames] mutableCopy]; + [attributes addObject:NSAccessibilityContentsAttribute]; + [attributes addObject:NSAccessibilityTabsAttribute]; + } + + return attributes; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + if ([attribute isEqualToString:NSAccessibilityContentsAttribute]) + return [super children]; + if ([attribute isEqualToString:NSAccessibilityTabsAttribute]) + return [self tabs]; + + return [super accessibilityAttributeValue:attribute]; +} + +/** + * Returns the selected tab (the mozAccessible) + */ +- (id)value +{ + mozAccessible* nativeAcc = nil; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + if (Accessible* accTab = accWrap->GetSelectedItem(0)) { + accTab->GetNativeInterface((void**)&nativeAcc); + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if (ProxyAccessible* proxyTab = proxy->GetSelectedItem(0)) { + nativeAcc = GetNativeFromProxy(proxyTab); + } + } + + return nativeAcc; +} + +/** + * Return the mozAccessibles that are the tabs. + */ +- (id)tabs +{ + if (mTabs) + return mTabs; + + NSArray* children = [self children]; + NSEnumerator* enumerator = [children objectEnumerator]; + mTabs = [[NSMutableArray alloc] init]; + + id obj; + while ((obj = [enumerator nextObject])) + if ([obj isTab]) + [mTabs addObject:obj]; + + return mTabs; +} + +- (void)invalidateChildren +{ + [super invalidateChildren]; + + [mTabs release]; + mTabs = nil; +} + +@end + +@implementation mozPaneAccessible + +- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) + return 0; + + // By default this calls -[[mozAccessible children] count]. + // Since we don't cache mChildren. This is faster. + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + if (accWrap) + return accWrap->ChildCount() ? 1 : 0; + + return proxy->ChildrenCount() ? 1 : 0; + } + + return [super accessibilityArrayAttributeCount:attribute]; +} + +- (NSArray*)children +{ + if (![self getGeckoAccessible]) + return nil; + + nsDeckFrame* deckFrame = do_QueryFrame([self getGeckoAccessible]->GetFrame()); + nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr; + + Accessible* selectedAcc = nullptr; + if (selectedFrame) { + nsINode* node = selectedFrame->GetContent(); + selectedAcc = [self getGeckoAccessible]->Document()->GetAccessible(node); + } + + if (selectedAcc) { + mozAccessible *curNative = GetNativeFromGeckoAccessible(selectedAcc); + if (curNative) + return [NSArray arrayWithObjects:GetObjectOrRepresentedView(curNative), nil]; + } + + return nil; +} + +@end diff --git a/accessible/mac/mozDocAccessible.h b/accessible/mac/mozDocAccessible.h new file mode 100644 index 0000000000..c381773110 --- /dev/null +++ b/accessible/mac/mozDocAccessible.h @@ -0,0 +1,31 @@ +/* -*- 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 <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 +} +@end diff --git a/accessible/mac/mozDocAccessible.mm b/accessible/mac/mozDocAccessible.mm new file mode 100644 index 0000000000..4bae81f01c --- /dev/null +++ b/accessible/mac/mozDocAccessible.mm @@ -0,0 +1,111 @@ +/* -*- 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/. */ + +#include "RootAccessibleWrap.h" + +#import "mozDocAccessible.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 + +- (NSArray*)accessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + // if we're expired, we don't support any attributes. + if (![self getGeckoAccessible]) + return [NSArray array]; + + // standard attributes that are shared and supported by root accessible (AXMain) elements. + static NSMutableArray* attributes = nil; + + if (!attributes) { + attributes = [[super accessibilityAttributeNames] mutableCopy]; + [attributes addObject:NSAccessibilityMainAttribute]; + [attributes addObject:NSAccessibilityMinimizedAttribute]; + } + + return attributes; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([attribute isEqualToString:NSAccessibilityMainAttribute]) + return [NSNumber numberWithBool:[[self window] isMainWindow]]; + if ([attribute isEqualToString:NSAccessibilityMinimizedAttribute]) + return [NSNumber numberWithBool:[[self window] isMiniaturized]]; + + return [super accessibilityAttributeValue:attribute]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + + +// return the AXParent that our parallell NSView tells us about. +- (id)parent +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if (!mParallelView) + mParallelView = (id<mozView, mozAccessible>)[self representedView]; + + if (mParallelView) + return [mParallelView accessibilityAttributeValue:NSAccessibilityParentAttribute]; + + NSAssert(mParallelView, @"we're a root accessible w/o native view?"); + return [super parent]; + + 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; + + if (mParallelView) + return (id)mParallelView; + + mParallelView = getNativeViewFromRootAccessible ([self getGeckoAccessible]); + + NSAssert(mParallelView, @"can't return root accessible's native parallel view."); + return mParallelView; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL)isRoot +{ + return YES; +} + +@end diff --git a/accessible/mac/mozHTMLAccessible.h b/accessible/mac/mozHTMLAccessible.h new file mode 100644 index 0000000000..c70a3c2a25 --- /dev/null +++ b/accessible/mac/mozHTMLAccessible.h @@ -0,0 +1,16 @@ +/* -*- 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 mozHeadingAccessible : mozAccessible + +@end + +@interface mozLinkAccessible : mozAccessible + +@end diff --git a/accessible/mac/mozHTMLAccessible.mm b/accessible/mac/mozHTMLAccessible.mm new file mode 100644 index 0000000000..2079a4aa6b --- /dev/null +++ b/accessible/mac/mozHTMLAccessible.mm @@ -0,0 +1,141 @@ +/* -*- 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 "mozHTMLAccessible.h" + +#import "Accessible-inl.h" +#import "HyperTextAccessible.h" + +#import "nsCocoaUtils.h" + +using namespace mozilla::a11y; + +@implementation mozHeadingAccessible + +- (NSString*)title +{ + nsAutoString title; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + mozilla::ErrorResult rv; + // XXX use the flattening API when there are available + // see bug 768298 + accWrap->GetContent()->GetTextContent(title, rv); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + proxy->Title(title); + } + + return nsCocoaUtils::ToNSString(title); +} + +- (id)value +{ + uint32_t level = 0; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + level = accWrap->GetLevelInternal(); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + level = proxy->GetLevelInternal(); + } + + return [NSNumber numberWithInt:level]; +} + +@end + +@interface mozLinkAccessible () +-(NSURL*)url; +@end + +@implementation mozLinkAccessible + +- (NSArray*)accessibilityAttributeNames +{ + // if we're expired, we don't support any attributes. + if (![self getGeckoAccessible] && ![self getProxyAccessible]) + return [NSArray array]; + + static NSMutableArray* attributes = nil; + + if (!attributes) { + attributes = [[super accessibilityAttributeNames] mutableCopy]; + [attributes addObject:NSAccessibilityURLAttribute]; + } + + return attributes; +} + +- (id)accessibilityAttributeValue:(NSString *)attribute +{ + if ([attribute isEqualToString:NSAccessibilityURLAttribute]) + return [self url]; + + return [super accessibilityAttributeValue:attribute]; +} + +- (NSArray*)accessibilityActionNames +{ + // if we're expired, we don't support any attributes. + if (![self getGeckoAccessible] && ![self getProxyAccessible]) + return [NSArray array]; + + static NSArray* actionNames = nil; + + if (!actionNames) { + actionNames = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction, + nil]; + } + + return actionNames; +} + +- (void)accessibilityPerformAction:(NSString*)action +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + if (!accWrap && !proxy) { + return; + } + + if ([action isEqualToString:NSAccessibilityPressAction]) { + if (accWrap) { + accWrap->DoAction(0); + } else if (proxy) { + proxy->DoAction(0); + } + return; + } + + [super accessibilityPerformAction:action]; + +} + +- (NSString*)customDescription +{ + return @""; +} + +- (NSString*)value +{ + return @""; +} + +- (NSURL*)url +{ + nsAutoString value; + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + accWrap->Value(value); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + proxy->Value(value); + } + + NSString* urlString = value.IsEmpty() ? nil : nsCocoaUtils::ToNSString(value); + if (!urlString) + return nil; + + return [NSURL URLWithString:urlString]; +} + +@end diff --git a/accessible/mac/mozTableAccessible.h b/accessible/mac/mozTableAccessible.h new file mode 100644 index 0000000000..435b5adc57 --- /dev/null +++ b/accessible/mac/mozTableAccessible.h @@ -0,0 +1,28 @@ +/* -*- 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 mozTablePartAccessible : mozAccessible +- (BOOL)isLayoutTablePart; +- (NSString*)role; +@end + +@interface mozTableAccessible : mozTablePartAccessible +- (NSArray*)additionalAccessibilityAttributeNames; +- (id)accessibilityAttributeValue:(NSString*)attribute; +@end + +@interface mozTableRowAccessible : mozTablePartAccessible +- (NSArray*)additionalAccessibilityAttributeNames; +- (id)accessibilityAttributeValue:(NSString*)attribute; +@end + +@interface mozTableCellAccessible : mozTablePartAccessible +- (NSArray*)additionalAccessibilityAttributeNames; +- (id)accessibilityAttributeValue:(NSString*)attribute; +@end diff --git a/accessible/mac/mozTableAccessible.mm b/accessible/mac/mozTableAccessible.mm new file mode 100644 index 0000000000..6ad157b9f0 --- /dev/null +++ b/accessible/mac/mozTableAccessible.mm @@ -0,0 +1,281 @@ +/* -*- 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 "Accessible-inl.h" +#import "mozTableAccessible.h" +#import "TableAccessible.h" +#import "TableCellAccessible.h" +#import "nsCocoaUtils.h" + +using namespace mozilla::a11y; + +// convert an array of Gecko accessibles to an NSArray of native accessibles +static inline NSMutableArray* +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 +static inline NSMutableArray* +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 = GetNativeFromProxy(curAccessible); + if (curNative) + [nativeArray addObject:GetObjectOrRepresentedView(curNative)]; + } + + return nativeArray; +} + +@implementation mozTablePartAccessible +- (BOOL)isLayoutTablePart; +{ + if (Accessible* accWrap = [self getGeckoAccessible]) { + while (accWrap) { + if (accWrap->IsTable()) { + return accWrap->AsTable()->IsProbablyLayoutTable(); + } + accWrap = accWrap->Parent(); + } + return false; + } + + if (ProxyAccessible* proxy = [self getProxyAccessible]) { + while (proxy) { + if (proxy->IsTable()) { + return proxy->TableIsProbablyForLayout(); + } + proxy = proxy->Parent(); + } + } + + return false; +} + +- (NSString*)role +{ + return [self isLayoutTablePart] ? NSAccessibilityGroupRole : [super role]; +} +@end + +@implementation mozTableAccessible +- (NSArray*)additionalAccessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames]; + if ([self isLayoutTablePart]) { + return additionalAttributes; + } + + static NSArray* tableAttrs = nil; + if (!tableAttrs) { + NSMutableArray* tempArray = [NSMutableArray new]; + [tempArray addObject:NSAccessibilityRowCountAttribute]; + [tempArray addObject:NSAccessibilityColumnCountAttribute]; + [tempArray addObject:NSAccessibilityRowsAttribute]; + tableAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + return [additionalAttributes arrayByAddingObjectsFromArray:tableAttrs]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + TableAccessible* table = accWrap->AsTable(); + if ([attribute isEqualToString:NSAccessibilityRowCountAttribute]) + return @(table->RowCount()); + if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute]) + return @(table->ColCount()); + if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) { + // Create a new array with the list of table rows. + NSMutableArray* nativeArray = [[NSMutableArray alloc] init]; + uint32_t totalCount = accWrap->ChildCount(); + for (uint32_t i = 0; i < totalCount; i++) { + if (accWrap->GetChildAt(i)->IsTableRow()) { + mozAccessible* curNative = + GetNativeFromGeckoAccessible(accWrap->GetChildAt(i)); + if (curNative) + [nativeArray addObject:GetObjectOrRepresentedView(curNative)]; + } + } + return nativeArray; + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if ([attribute isEqualToString:NSAccessibilityRowCountAttribute]) + return @(proxy->TableRowCount()); + if ([attribute isEqualToString:NSAccessibilityColumnCountAttribute]) + return @(proxy->TableColumnCount()); + if ([attribute isEqualToString:NSAccessibilityRowsAttribute]) { + // Create a new array with the list of table rows. + NSMutableArray* nativeArray = [[NSMutableArray alloc] init]; + uint32_t totalCount = proxy->ChildrenCount(); + for (uint32_t i = 0; i < totalCount; i++) { + if (proxy->ChildAt(i)->IsTableRow()) { + mozAccessible* curNative = + GetNativeFromProxy(proxy->ChildAt(i)); + if (curNative) + [nativeArray addObject:GetObjectOrRepresentedView(curNative)]; + } + } + return nativeArray; + } + } + + return [super accessibilityAttributeValue:attribute]; +} +@end + +@implementation mozTableRowAccessible +- (NSArray*)additionalAccessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames]; + if ([self isLayoutTablePart]) { + return additionalAttributes; + } + + static NSArray* tableRowAttrs = nil; + if (!tableRowAttrs) { + NSMutableArray* tempArray = [NSMutableArray new]; + [tempArray addObject:NSAccessibilityIndexAttribute]; + tableRowAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + return [additionalAttributes arrayByAddingObjectsFromArray:tableRowAttrs]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) { + // Count the number of rows before that one to obtain the row index. + uint32_t index = 0; + Accessible* parent = accWrap->Parent(); + if (parent) { + for (int32_t i = accWrap->IndexInParent() - 1; i >= 0; i--) { + if (parent->GetChildAt(i)->IsTableRow()) { + index++; + } + } + } + return [NSNumber numberWithUnsignedInteger:index]; + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if ([attribute isEqualToString:NSAccessibilityIndexAttribute]) { + // Count the number of rows before that one to obtain the row index. + uint32_t index = 0; + ProxyAccessible* parent = proxy->Parent(); + if (parent) { + for (int32_t i = proxy->IndexInParent() - 1; i >= 0; i--) { + if (parent->ChildAt(i)->IsTableRow()) { + index++; + } + } + } + return [NSNumber numberWithUnsignedInteger:index]; + } + } + + return [super accessibilityAttributeValue:attribute]; +} +@end + +@implementation mozTableCellAccessible +- (NSArray*)additionalAccessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSArray* additionalAttributes = [super additionalAccessibilityAttributeNames]; + if ([self isLayoutTablePart]) { + return additionalAttributes; + } + + static NSArray* tableCellAttrs = nil; + if (!tableCellAttrs) { + NSMutableArray* tempArray = [NSMutableArray new]; + [tempArray addObject:NSAccessibilityRowIndexRangeAttribute]; + [tempArray addObject:NSAccessibilityColumnIndexRangeAttribute]; + [tempArray addObject:NSAccessibilityRowHeaderUIElementsAttribute]; + [tempArray addObject:NSAccessibilityColumnHeaderUIElementsAttribute]; + tableCellAttrs = [[NSArray alloc] initWithArray:tempArray]; + [tempArray release]; + } + + return [additionalAttributes arrayByAddingObjectsFromArray:tableCellAttrs]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + TableCellAccessible* cell = accWrap->AsTableCell(); + if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute]) + return [NSValue valueWithRange:NSMakeRange(cell->RowIdx(), + cell->RowExtent())]; + if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) + return [NSValue valueWithRange:NSMakeRange(cell->ColIdx(), + cell->ColExtent())]; + if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) { + AutoTArray<Accessible*, 10> headerCells; + cell->RowHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) { + AutoTArray<Accessible*, 10> headerCells; + cell->ColHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if ([attribute isEqualToString:NSAccessibilityRowIndexRangeAttribute]) + return [NSValue valueWithRange:NSMakeRange(proxy->RowIdx(), + proxy->RowExtent())]; + if ([attribute isEqualToString:NSAccessibilityColumnIndexRangeAttribute]) + return [NSValue valueWithRange:NSMakeRange(proxy->ColIdx(), + proxy->ColExtent())]; + if ([attribute isEqualToString:NSAccessibilityRowHeaderUIElementsAttribute]) { + nsTArray<ProxyAccessible*> headerCells; + proxy->RowHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + if ([attribute isEqualToString:NSAccessibilityColumnHeaderUIElementsAttribute]) { + nsTArray<ProxyAccessible*> headerCells; + proxy->ColHeaderCells(&headerCells); + return ConvertToNSArray(headerCells); + } + } + + return [super accessibilityAttributeValue:attribute]; +} +@end diff --git a/accessible/mac/mozTextAccessible.h b/accessible/mac/mozTextAccessible.h new file mode 100644 index 0000000000..8bc23ae8d5 --- /dev/null +++ b/accessible/mac/mozTextAccessible.h @@ -0,0 +1,17 @@ +/* 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" + +#import "HyperTextAccessible.h" + +@interface mozTextAccessible : mozAccessible +{ +} +@end + +@interface mozTextLeafAccessible : mozAccessible +{ +} +@end diff --git a/accessible/mac/mozTextAccessible.mm b/accessible/mac/mozTextAccessible.mm new file mode 100644 index 0000000000..1f433b802e --- /dev/null +++ b/accessible/mac/mozTextAccessible.mm @@ -0,0 +1,627 @@ +/* -*- 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/. */ + +#include "Accessible-inl.h" +#include "HyperTextAccessible-inl.h" +#include "TextLeafAccessible.h" + +#include "nsCocoaUtils.h" +#include "nsObjCExceptions.h" + +#import "mozTextAccessible.h" + +using namespace mozilla; +using namespace mozilla::a11y; + +inline bool +ToNSRange(id aValue, NSRange* aRange) +{ + NS_PRECONDITION(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 () +- (NSString*)subrole; +- (NSString*)selectedText; +- (NSValue*)selectedTextRange; +- (NSValue*)visibleCharacterRange; +- (long)textLength; +- (BOOL)isReadOnly; +- (NSNumber*)caretLineNumber; +- (void)setText:(NSString*)newText; +- (NSString*)text; +- (NSString*)stringFromRange:(NSRange*)range; +@end + +@implementation mozTextAccessible + +- (BOOL)accessibilityIsIgnored +{ + return ![self getGeckoAccessible] && ![self getProxyAccessible]; +} + +- (NSArray*)accessibilityAttributeNames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + static NSMutableArray* supportedAttributes = nil; + if (!supportedAttributes) { + // text-specific attributes to supplement the standard one + supportedAttributes = [[NSMutableArray alloc] initWithObjects: + NSAccessibilitySelectedTextAttribute, // required + NSAccessibilitySelectedTextRangeAttribute, // required + NSAccessibilityNumberOfCharactersAttribute, // required + NSAccessibilityVisibleCharacterRangeAttribute, // required + NSAccessibilityInsertionPointLineNumberAttribute, + @"AXRequired", + @"AXInvalid", + nil + ]; + [supportedAttributes addObjectsFromArray:[super accessibilityAttributeNames]]; + } + return supportedAttributes; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) + return [NSNumber numberWithInt:[self textLength]]; + + if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) + return [self caretLineNumber]; + + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) + return [self selectedTextRange]; + + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) + return [self selectedText]; + + if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) + return @""; + + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { + // 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 role] isEqualToString:NSAccessibilityStaticTextRole]) { + NSString* selectedText = [self selectedText]; + return (selectedText && [selectedText length]) ? selectedText : [self text]; + } + + return [self text]; + } + + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + if ([attribute isEqualToString:@"AXRequired"]) { + return [NSNumber numberWithBool:!!(accWrap->State() & states::REQUIRED)]; + } + + if ([attribute isEqualToString:@"AXInvalid"]) { + return [NSNumber numberWithBool:!!(accWrap->State() & states::INVALID)]; + } + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + if ([attribute isEqualToString:@"AXRequired"]) { + return [NSNumber numberWithBool:!!(proxy->State() & states::REQUIRED)]; + } + + if ([attribute isEqualToString:@"AXInvalid"]) { + return [NSNumber numberWithBool:!!(proxy->State() & states::INVALID)]; + } + } + + if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) + return [self visibleCharacterRange]; + + // let mozAccessible handle all other attributes + return [super accessibilityAttributeValue:attribute]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSArray*)accessibilityParameterizedAttributeNames +{ + static NSArray* supportedParametrizedAttributes = nil; + // text specific parametrized attributes + if (!supportedParametrizedAttributes) { + supportedParametrizedAttributes = [[NSArray alloc] initWithObjects: + NSAccessibilityStringForRangeParameterizedAttribute, + NSAccessibilityLineForIndexParameterizedAttribute, + NSAccessibilityRangeForLineParameterizedAttribute, + NSAccessibilityAttributedStringForRangeParameterizedAttribute, + NSAccessibilityBoundsForRangeParameterizedAttribute, +#if DEBUG + NSAccessibilityRangeForPositionParameterizedAttribute, + NSAccessibilityRangeForIndexParameterizedAttribute, + NSAccessibilityRTFForRangeParameterizedAttribute, + NSAccessibilityStyleRangeForIndexParameterizedAttribute, +#endif + nil + ]; + } + return supportedParametrizedAttributes; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return nil; + + if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) { + NSRange range; + if (!ToNSRange(parameter, &range)) { +#if DEBUG + NSLog(@"%@: range not set", attribute); +#endif + return @""; + } + + return [self stringFromRange:&range]; + } + + if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) { + // XXX: actually get the integer value for the line # + return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; + } + + if ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute]) { + NSRange range; + if (!ToNSRange(parameter, &range)) { +#if DEBUG + NSLog(@"%@: range not set", attribute); +#endif + return @""; + } + + return [[[NSAttributedString alloc] initWithString:[self stringFromRange:&range]] autorelease]; + } + + if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) { + // XXX: actually return the line # + return [NSNumber numberWithInt:0]; + } + + if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) { + NSRange range; + if (!ToNSRange(parameter, &range)) { +#if DEBUG + NSLog(@"%@:no range", attribute); +#endif + return nil; + } + + int32_t start = range.location; + int32_t end = start + range.length; + DesktopIntRect bounds; + if (textAcc) { + bounds = + DesktopIntRect::FromUnknownRect(textAcc->TextBounds(start, end)); + } else if (proxy) { + bounds = + DesktopIntRect::FromUnknownRect(proxy->TextBounds(start, end)); + } + + return [NSValue valueWithRect:nsCocoaUtils::GeckoRectToCocoaRect(bounds)]; + } + +#if DEBUG + NSLog(@"unhandled attribute:%@ forParameter:%@", attribute, parameter); +#endif + + return nil; +} + +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) + return ![self isReadOnly]; + + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] || + [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] || + [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) + return YES; + + return [super accessibilityIsAttributeSettable:attribute]; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return; + + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { + [self setText:ToNSString(value)]; + + return; + } + + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { + NSString* stringValue = ToNSString(value); + if (!stringValue) + return; + + int32_t start = 0, end = 0; + nsString text; + if (textAcc) { + textAcc->SelectionBoundsAt(0, &start, &end); + textAcc->DeleteText(start, end - start); + nsCocoaUtils::GetStringForNSString(stringValue, text); + textAcc->InsertText(text, start); + } else if (proxy) { + nsString data; + proxy->SelectionBoundsAt(0, data, &start, &end); + proxy->DeleteText(start, end - start); + nsCocoaUtils::GetStringForNSString(stringValue, text); + proxy->InsertText(text, start); + } + } + + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { + NSRange range; + if (!ToNSRange(value, &range)) + return; + + if (textAcc) { + textAcc->SetSelectionBoundsAt(0, range.location, + range.location + range.length); + } else if (proxy) { + proxy->SetSelectionBoundsAt(0, range.location, + range.location + range.length); + } + return; + } + + if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { + NSRange range; + if (!ToNSRange(value, &range)) + return; + + if (textAcc) { + textAcc->ScrollSubstringTo(range.location, range.location + range.length, + nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); + } else if (proxy) { + proxy->ScrollSubstringTo(range.location, range.location + range.length, + nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); + } + return; + } + + [super accessibilitySetValue:value forAttribute:attribute]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (NSString*)subrole +{ + if(mRole == roles::PASSWORD_TEXT) + return NSAccessibilitySecureTextFieldSubrole; + + return nil; +} + +#pragma mark - + +- (BOOL)isReadOnly +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) + return YES; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (textAcc) + return (accWrap->State() & states::READONLY) == 0; + + if (ProxyAccessible* proxy = [self getProxyAccessible]) + return (proxy->State() & states::READONLY) == 0; + + return NO; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +- (NSNumber*)caretLineNumber +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + + int32_t lineNumber = -1; + if (textAcc) { + lineNumber = textAcc->CaretLineNumber() - 1; + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + lineNumber = proxy->CaretLineNumber() - 1; + } + + return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; +} + +- (void)setText:(NSString*)aNewString +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + + nsString text; + nsCocoaUtils::GetStringForNSString(aNewString, text); + if (textAcc) { + textAcc->ReplaceText(text); + } else if (ProxyAccessible* proxy = [self getProxyAccessible]) { + proxy->ReplaceText(text); + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (NSString*)text +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return nil; + + // A password text field returns an empty value + if (mRole == roles::PASSWORD_TEXT) + return @""; + + nsAutoString text; + if (textAcc) { + textAcc->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text); + } else if (proxy) { + proxy->TextSubstring(0, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, text); + } + + return nsCocoaUtils::ToNSString(text); +} + +- (long)textLength +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return 0; + + return textAcc ? textAcc->CharacterCount() : proxy->CharacterCount(); + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); +} + +- (long)selectedTextLength +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return 0; + + int32_t start = 0, end = 0; + if (textAcc) { + textAcc->SelectionBoundsAt(0, &start, &end); + } else if (proxy) { + nsString data; + proxy->SelectionBoundsAt(0, data, &start, &end); + } + return (end - start); + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); +} + +- (NSString*)selectedText +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return nil; + + int32_t start = 0, end = 0; + nsAutoString selText; + if (textAcc) { + textAcc->SelectionBoundsAt(0, &start, &end); + if (start != end) { + textAcc->TextSubstring(start, end, selText); + } + } else if (proxy) { + proxy->SelectionBoundsAt(0, selText, &start, &end); + } + + return nsCocoaUtils::ToNSString(selText); + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSValue*)selectedTextRange +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + + int32_t start = 0; + int32_t end = 0; + int32_t count = 0; + if (textAcc) { + count = textAcc->SelectionCount(); + if (count) { + textAcc->SelectionBoundsAt(0, &start, &end); + return [NSValue valueWithRange:NSMakeRange(start, end - start)]; + } + + start = textAcc->CaretOffset(); + return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)]; + } + + if (proxy) { + count = proxy->SelectionCount(); + if (count) { + nsString data; + proxy->SelectionBoundsAt(0, data, &start, &end); + return [NSValue valueWithRange:NSMakeRange(start, end - start)]; + } + + start = proxy->CaretOffset(); + return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)]; + } + + return [NSValue valueWithRange:NSMakeRange(0, 0)]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (NSValue*)visibleCharacterRange +{ + // XXX this won't work with Textarea and such as we actually don't give + // the visible character range. + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return 0; + + return [NSValue valueWithRange: + NSMakeRange(0, textAcc ? + textAcc->CharacterCount() : proxy->CharacterCount())]; +} + +- (void)valueDidChange +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), + NSAccessibilityValueChangedNotification); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void)selectedTextDidChange +{ + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), + NSAccessibilitySelectedTextChangedNotification); +} + +- (NSString*)stringFromRange:(NSRange*)range +{ + NS_PRECONDITION(range, "no range"); + + AccessibleWrap* accWrap = [self getGeckoAccessible]; + ProxyAccessible* proxy = [self getProxyAccessible]; + HyperTextAccessible* textAcc = accWrap? accWrap->AsHyperText() : nullptr; + if (!textAcc && !proxy) + return nil; + + nsAutoString text; + if (textAcc) { + textAcc->TextSubstring(range->location, + range->location + range->length, text); + } else if (proxy) { + proxy->TextSubstring(range->location, + range->location + range->length, text); + } + + return nsCocoaUtils::ToNSString(text); +} + +@end + +@implementation mozTextLeafAccessible + +- (NSArray*)accessibilityAttributeNames +{ + static NSMutableArray* supportedAttributes = nil; + if (!supportedAttributes) { + supportedAttributes = [[super accessibilityAttributeNames] mutableCopy]; + [supportedAttributes removeObject:NSAccessibilityChildrenAttribute]; + } + + return supportedAttributes; +} + +- (id)accessibilityAttributeValue:(NSString*)attribute +{ + if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) + return @""; + + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) + return [self text]; + + return [super accessibilityAttributeValue:attribute]; +} + +- (NSString*)text +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + return nsCocoaUtils::ToNSString(accWrap->AsTextLeaf()->Text()); + } + + if (ProxyAccessible* proxy = [self getProxyAccessible]) { + nsString text; + proxy->Text(&text); + return nsCocoaUtils::ToNSString(text); + } + + return nil; +} + +- (long)textLength +{ + if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { + return accWrap->AsTextLeaf()->Text().Length(); + } + + if (ProxyAccessible* proxy = [self getProxyAccessible]) { + nsString text; + proxy->Text(&text); + return text.Length(); + } + + return 0; +} + +@end diff --git a/accessible/moz.build b/accessible/moz.build index edfd88f504..aa1cccb982 100644 --- a/accessible/moz.build +++ b/accessible/moz.build @@ -9,6 +9,8 @@ if 'gtk' in toolkit: DIRS += ['atk'] elif toolkit == 'windows': DIRS += ['windows'] +elif toolkit == 'cocoa': + DIRS += ['mac'] else: DIRS += ['other'] diff --git a/accessible/xpcom/moz.build b/accessible/xpcom/moz.build index 5d1ad16884..d43efd06d0 100644 --- a/accessible/xpcom/moz.build +++ b/accessible/xpcom/moz.build @@ -42,6 +42,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': LOCAL_INCLUDES += [ '/accessible/windows/msaa', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', diff --git a/accessible/xul/moz.build b/accessible/xul/moz.build index b04c35dc40..54182c0975 100644 --- a/accessible/xul/moz.build +++ b/accessible/xul/moz.build @@ -37,6 +37,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': '/accessible/windows/ia2', '/accessible/windows/msaa', ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + LOCAL_INCLUDES += [ + '/accessible/mac', + ] else: LOCAL_INCLUDES += [ '/accessible/other', |