2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "DocAccessibleWrap.h"
9 #include "nsObjCExceptions.h"
10 #include "nsCocoaUtils.h"
12 #include "LocalAccessible-inl.h"
13 #include "nsAccUtils.h"
15 #include "TextRange.h"
16 #include "gfxPlatform.h"
18 #import "MOXLandmarkAccessibles.h"
19 #import "MOXMathAccessibles.h"
20 #import "MOXTextMarkerDelegate.h"
21 #import "MOXWebAreaAccessible.h"
22 #import "mozAccessible.h"
23 #import "mozActionElements.h"
24 #import "mozHTMLAccessible.h"
25 #import "mozSelectableElements.h"
26 #import "mozTableAccessible.h"
27 #import "mozTextAccessible.h"
29 using namespace mozilla;
30 using namespace mozilla::a11y;
32 AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
33 : LocalAccessible(aContent, aDoc),
35 mNativeInited(false) {
36 if (aContent && aContent->IsElement() && aDoc) {
37 // Check if this accessible is a live region and queue it
38 // it for dispatching an event after it has been inserted.
39 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
40 static const dom::Element::AttrValuesArray sLiveRegionValues[] = {
41 nsGkAtoms::OFF, nsGkAtoms::polite, nsGkAtoms::assertive, nullptr};
42 int32_t attrValue = aContent->AsElement()->FindAttrValueIn(
43 kNameSpaceID_None, nsGkAtoms::aria_live, sLiveRegionValues,
46 // aria-live is "off", do nothing.
47 } else if (attrValue > 0) {
48 // aria-live attribute is polite or assertive. It's live!
49 doc->QueueNewLiveRegion(this);
50 } else if (const nsRoleMapEntry* roleMap =
51 aria::GetRoleMap(aContent->AsElement())) {
52 // aria role defines it as a live region. It's live!
53 if (roleMap->liveAttRule == ePoliteLiveAttr ||
54 roleMap->liveAttRule == eAssertiveLiveAttr) {
55 doc->QueueNewLiveRegion(this);
57 } else if (nsStaticAtom* value = GetAccService()->MarkupAttribute(
58 aContent, nsGkAtoms::aria_live)) {
59 // HTML element defines it as a live region. It's live!
60 if (value == nsGkAtoms::polite || value == nsGkAtoms::assertive) {
61 doc->QueueNewLiveRegion(this);
67 AccessibleWrap::~AccessibleWrap() {}
69 mozAccessible* AccessibleWrap::GetNativeObject() {
70 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
72 if (!mNativeInited && !mNativeObject) {
73 // We don't creat OSX accessibles for xul tooltips, defunct accessibles,
74 // <br> (whitespace) elements, or pruned children.
76 // To maintain a scripting environment where the XPCOM accessible hierarchy
77 // look the same on all platforms, we still let the C++ objects be created
79 if (!IsXULTooltip() && !IsDefunct() && Role() != roles::WHITESPACE) {
80 mNativeObject = [[GetNativeType() alloc] initWithAccessible:this];
88 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
91 void AccessibleWrap::GetNativeInterface(void** aOutInterface) {
92 *aOutInterface = static_cast<void*>(GetNativeObject());
95 // overridden in subclasses to create the right kind of object. by default we
96 // create a generic 'mozAccessible' node.
97 Class AccessibleWrap::GetNativeType() {
98 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
100 if (IsXULTabpanels()) {
101 return [mozPaneAccessible class];
105 return [mozTableAccessible class];
109 return [mozTableRowAccessible class];
113 return [mozTableCellAccessible class];
117 return [MOXWebAreaAccessible class];
120 return GetTypeFromRole(Role());
122 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
125 // this method is very important. it is fired when an accessible object "dies".
126 // after this point the object might still be around (because some 3rd party
127 // still has a ref to it), but it is in fact 'dead'.
128 void AccessibleWrap::Shutdown() {
129 // this ensure we will not try to re-create the native object.
130 mNativeInited = true;
132 // we really intend to access the member directly.
134 [mNativeObject expire];
135 [mNativeObject release];
139 LocalAccessible::Shutdown();
142 nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
143 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
145 nsresult rv = LocalAccessible::HandleAccEvent(aEvent);
146 NS_ENSURE_SUCCESS(rv, rv);
149 // The accessible can become defunct after their events are handled.
153 uint32_t eventType = aEvent->GetEventType();
155 if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
156 DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(Document());
157 doc->ProcessNewLiveRegions();
160 if (IPCAccessibilityActive()) {
164 LocalAccessible* eventTarget = nullptr;
167 case nsIAccessibleEvent::EVENT_SELECTION:
168 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
169 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
170 AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
171 // The "widget" is the selected widget's container. In OSX
172 // it is the target of the selection changed event.
173 eventTarget = selEvent->Widget();
176 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
177 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
178 LocalAccessible* acc = aEvent->GetAccessible();
179 // If there is a text input ancestor, use it as the event source.
180 while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
181 acc = acc->LocalParent();
183 eventTarget = acc ? acc : aEvent->GetAccessible();
187 eventTarget = aEvent->GetAccessible();
191 mozAccessible* nativeAcc = nil;
192 eventTarget->GetNativeInterface((void**)&nativeAcc);
194 return NS_ERROR_FAILURE;
198 case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
199 AccStateChangeEvent* event = downcast_accEvent(aEvent);
200 [nativeAcc stateChanged:event->GetState()
201 isEnabled:event->IsStateEnabled()];
205 case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
206 MOXTextMarkerDelegate* delegate =
207 [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
208 AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
209 AutoTArray<TextRange, 1> ranges;
210 event->SelectionRanges(&ranges);
212 if (ranges.Length()) {
213 // Cache selection in delegate.
214 [delegate setSelectionFrom:ranges[0].StartContainer()
215 at:ranges[0].StartOffset()
216 to:ranges[0].EndContainer()
217 at:ranges[0].EndOffset()];
220 [nativeAcc handleAccessibleEvent:eventType];
224 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
225 AccCaretMoveEvent* event = downcast_accEvent(aEvent);
226 int32_t caretOffset = event->GetCaretOffset();
227 MOXTextMarkerDelegate* delegate =
228 [MOXTextMarkerDelegate getOrCreateForDoc:aEvent->Document()];
229 [delegate setCaretOffset:eventTarget
231 moveGranularity:event->GetGranularity()];
232 if (event->IsSelectionCollapsed()) {
233 // If the selection is collapsed, invalidate our text selection cache.
234 [delegate setSelectionFrom:eventTarget
240 if (mozTextAccessible* textAcc = static_cast<mozTextAccessible*>(
241 [nativeAcc moxEditableAncestor])) {
243 handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
246 handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
251 case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
252 case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
253 AccTextChangeEvent* tcEvent = downcast_accEvent(aEvent);
254 [nativeAcc handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(
255 tcEvent->ModifiedText())
256 inserted:tcEvent->IsTextInserted()
257 inContainer:aEvent->GetAccessible()
258 at:tcEvent->GetStartOffset()];
262 case nsIAccessibleEvent::EVENT_ALERT:
263 case nsIAccessibleEvent::EVENT_FOCUS:
264 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
265 case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE:
266 case nsIAccessibleEvent::EVENT_MENUPOPUP_START:
267 case nsIAccessibleEvent::EVENT_MENUPOPUP_END:
268 case nsIAccessibleEvent::EVENT_REORDER:
269 case nsIAccessibleEvent::EVENT_SELECTION:
270 case nsIAccessibleEvent::EVENT_SELECTION_ADD:
271 case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
272 case nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED:
273 case nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED:
274 case nsIAccessibleEvent::EVENT_NAME_CHANGE:
275 case nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED:
276 case nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED:
277 [nativeAcc handleAccessibleEvent:eventType];
286 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
289 bool AccessibleWrap::ApplyPostFilter(const EWhichPostFilter& aSearchKey,
290 const nsString& aSearchText) {
291 // We currently only support the eContainsText post filter.
292 MOZ_ASSERT(aSearchKey == EWhichPostFilter::eContainsText,
293 "Only search text supported");
296 return name.Find(aSearchText, true) != kNotFound;
299 ////////////////////////////////////////////////////////////////////////////////
300 // AccessibleWrap protected
302 Class a11y::GetTypeFromRole(roles::Role aRole) {
303 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
306 case roles::COMBOBOX:
307 return [mozPopupButtonAccessible class];
309 case roles::PUSHBUTTON:
310 return [mozButtonAccessible class];
313 return [mozTabAccessible class];
315 case roles::CHECKBUTTON:
316 case roles::TOGGLE_BUTTON:
318 return [mozCheckboxAccessible class];
320 case roles::RADIOBUTTON:
321 return [mozRadioButtonAccessible class];
323 case roles::SPINBUTTON:
325 return [mozIncrementableAccessible class];
328 return [mozHeadingAccessible class];
330 case roles::PAGETABLIST:
331 return [mozTabGroupAccessible class];
335 case roles::ACCEL_LABEL:
336 case roles::EDITCOMBOBOX:
337 case roles::PASSWORD_TEXT:
338 // normal textfield (static or editable)
339 return [mozTextAccessible class];
341 case roles::TEXT_LEAF:
342 case roles::STATICTEXT:
343 return [mozTextLeafAccessible class];
345 case roles::LANDMARK:
346 return [MOXLandmarkAccessible class];
349 return [mozLinkAccessible class];
352 return [mozListboxAccessible class];
354 case roles::LISTITEM:
355 return [MOXListItemAccessible class];
357 case roles::OPTION: {
358 return [mozOptionAccessible class];
361 case roles::RICH_OPTION: {
362 return [mozSelectableChildAccessible class];
365 case roles::COMBOBOX_LIST:
367 case roles::MENUPOPUP: {
368 return [mozMenuAccessible class];
371 case roles::COMBOBOX_OPTION:
372 case roles::PARENT_MENUITEM:
373 case roles::MENUITEM: {
374 return [mozMenuItemAccessible class];
377 case roles::MATHML_ROOT:
378 return [MOXMathRootAccessible class];
380 case roles::MATHML_SQUARE_ROOT:
381 return [MOXMathSquareRootAccessible class];
383 case roles::MATHML_FRACTION:
384 return [MOXMathFractionAccessible class];
386 case roles::MATHML_SUB:
387 case roles::MATHML_SUP:
388 case roles::MATHML_SUB_SUP:
389 return [MOXMathSubSupAccessible class];
391 case roles::MATHML_UNDER:
392 case roles::MATHML_OVER:
393 case roles::MATHML_UNDER_OVER:
394 return [MOXMathUnderOverAccessible class];
397 case roles::TREE_TABLE:
398 return [mozOutlineAccessible class];
400 case roles::OUTLINEITEM:
401 return [mozOutlineRowAccessible class];
404 return [mozAccessible class];
409 NS_OBJC_END_TRY_BLOCK_RETURN(nil);