/src/mozilla-central/accessible/generic/Accessible.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "Accessible-inl.h" |
7 | | |
8 | | #include "nsIXBLAccessible.h" |
9 | | |
10 | | #include "EmbeddedObjCollector.h" |
11 | | #include "AccGroupInfo.h" |
12 | | #include "AccIterator.h" |
13 | | #include "nsAccUtils.h" |
14 | | #include "nsAccessibilityService.h" |
15 | | #include "ApplicationAccessible.h" |
16 | | #include "nsAccessiblePivot.h" |
17 | | #include "nsGenericHTMLElement.h" |
18 | | #include "NotificationController.h" |
19 | | #include "nsEventShell.h" |
20 | | #include "nsTextEquivUtils.h" |
21 | | #include "DocAccessibleChild.h" |
22 | | #include "EventTree.h" |
23 | | #include "GeckoProfiler.h" |
24 | | #include "Relation.h" |
25 | | #include "Role.h" |
26 | | #include "RootAccessible.h" |
27 | | #include "States.h" |
28 | | #include "StyleInfo.h" |
29 | | #include "TableAccessible.h" |
30 | | #include "TableCellAccessible.h" |
31 | | #include "TreeWalker.h" |
32 | | #include "XULDocument.h" |
33 | | |
34 | | #include "nsIDOMXULButtonElement.h" |
35 | | #include "nsIDOMXULSelectCntrlEl.h" |
36 | | #include "nsIDOMXULSelectCntrlItemEl.h" |
37 | | #include "nsINodeList.h" |
38 | | #include "nsPIDOMWindow.h" |
39 | | |
40 | | #include "nsIDocument.h" |
41 | | #include "nsIContent.h" |
42 | | #include "nsIForm.h" |
43 | | #include "nsIFormControl.h" |
44 | | |
45 | | #include "nsDeckFrame.h" |
46 | | #include "nsLayoutUtils.h" |
47 | | #include "nsIPresShell.h" |
48 | | #include "nsIStringBundle.h" |
49 | | #include "nsPresContext.h" |
50 | | #include "nsIFrame.h" |
51 | | #include "nsView.h" |
52 | | #include "nsIDocShellTreeItem.h" |
53 | | #include "nsIScrollableFrame.h" |
54 | | #include "nsFocusManager.h" |
55 | | |
56 | | #include "nsString.h" |
57 | | #include "nsUnicharUtils.h" |
58 | | #include "nsReadableUtils.h" |
59 | | #include "prdtoa.h" |
60 | | #include "nsAtom.h" |
61 | | #include "nsIURI.h" |
62 | | #include "nsArrayUtils.h" |
63 | | #include "nsIMutableArray.h" |
64 | | #include "nsIObserverService.h" |
65 | | #include "nsIServiceManager.h" |
66 | | #include "nsWhitespaceTokenizer.h" |
67 | | #include "nsAttrName.h" |
68 | | #include "nsPersistentProperties.h" |
69 | | |
70 | | #include "mozilla/Assertions.h" |
71 | | #include "mozilla/BasicEvents.h" |
72 | | #include "mozilla/ErrorResult.h" |
73 | | #include "mozilla/EventStateManager.h" |
74 | | #include "mozilla/EventStates.h" |
75 | | #include "mozilla/FloatingPoint.h" |
76 | | #include "mozilla/MouseEvents.h" |
77 | | #include "mozilla/Unused.h" |
78 | | #include "mozilla/Preferences.h" |
79 | | #include "mozilla/dom/CanvasRenderingContext2D.h" |
80 | | #include "mozilla/dom/Element.h" |
81 | | #include "mozilla/dom/HTMLCanvasElement.h" |
82 | | #include "mozilla/dom/HTMLBodyElement.h" |
83 | | #include "mozilla/dom/KeyboardEventBinding.h" |
84 | | #include "mozilla/dom/TreeWalker.h" |
85 | | |
86 | | using namespace mozilla; |
87 | | using namespace mozilla::a11y; |
88 | | |
89 | | |
90 | | //////////////////////////////////////////////////////////////////////////////// |
91 | | // Accessible: nsISupports and cycle collection |
92 | | |
93 | | NS_IMPL_CYCLE_COLLECTION_CLASS(Accessible) |
94 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Accessible) |
95 | 0 | tmp->Shutdown(); |
96 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
97 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Accessible) |
98 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc) |
99 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
100 | | |
101 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible) |
102 | 0 | NS_INTERFACE_MAP_ENTRY_CONCRETE(Accessible) |
103 | 0 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, Accessible) |
104 | 0 | NS_INTERFACE_MAP_END |
105 | | |
106 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(Accessible) |
107 | | NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(Accessible, LastRelease()) |
108 | | |
109 | | Accessible::Accessible(nsIContent* aContent, DocAccessible* aDoc) : |
110 | | mContent(aContent), mDoc(aDoc), |
111 | | mParent(nullptr), mIndexInParent(-1), |
112 | | mRoleMapEntryIndex(aria::NO_ROLE_MAP_ENTRY_INDEX), |
113 | | mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0), |
114 | | mReorderEventTarget(false), mShowEventTarget(false), mHideEventTarget(false) |
115 | 0 | { |
116 | 0 | mBits.groupInfo = nullptr; |
117 | 0 | mInt.mIndexOfEmbeddedChild = -1; |
118 | 0 |
|
119 | 0 | // Assign an ID to this Accessible for use in UniqueID(). |
120 | 0 | recordreplay::RegisterThing(this); |
121 | 0 | } |
122 | | |
123 | | Accessible::~Accessible() |
124 | 0 | { |
125 | 0 | NS_ASSERTION(!mDoc, "LastRelease was never called!?!"); |
126 | 0 |
|
127 | 0 | recordreplay::UnregisterThing(this); |
128 | 0 | } |
129 | | |
130 | | ENameValueFlag |
131 | | Accessible::Name(nsString& aName) const |
132 | 0 | { |
133 | 0 | aName.Truncate(); |
134 | 0 |
|
135 | 0 | if (!HasOwnContent()) |
136 | 0 | return eNameOK; |
137 | 0 | |
138 | 0 | ARIAName(aName); |
139 | 0 | if (!aName.IsEmpty()) |
140 | 0 | return eNameOK; |
141 | 0 | |
142 | 0 | nsCOMPtr<nsIXBLAccessible> xblAccessible(do_QueryInterface(mContent)); |
143 | 0 | if (xblAccessible) { |
144 | 0 | xblAccessible->GetAccessibleName(aName); |
145 | 0 | if (!aName.IsEmpty()) |
146 | 0 | return eNameOK; |
147 | 0 | } |
148 | 0 | |
149 | 0 | ENameValueFlag nameFlag = NativeName(aName); |
150 | 0 | if (!aName.IsEmpty()) |
151 | 0 | return nameFlag; |
152 | 0 | |
153 | 0 | // In the end get the name from tooltip. |
154 | 0 | if (mContent->IsHTMLElement()) { |
155 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) { |
156 | 0 | aName.CompressWhitespace(); |
157 | 0 | return eNameFromTooltip; |
158 | 0 | } |
159 | 0 | } else if (mContent->IsXULElement()) { |
160 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) { |
161 | 0 | aName.CompressWhitespace(); |
162 | 0 | return eNameFromTooltip; |
163 | 0 | } |
164 | 0 | } else if (mContent->IsSVGElement()) { |
165 | 0 | // If user agents need to choose among multiple ‘desc’ or ‘title’ elements |
166 | 0 | // for processing, the user agent shall choose the first one. |
167 | 0 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
168 | 0 | childElm = childElm->GetNextSibling()) { |
169 | 0 | if (childElm->IsSVGElement(nsGkAtoms::desc)) { |
170 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
171 | 0 | return eNameFromTooltip; |
172 | 0 | } |
173 | 0 | } |
174 | 0 | } |
175 | 0 |
|
176 | 0 | if (nameFlag != eNoNameOnPurpose) |
177 | 0 | aName.SetIsVoid(true); |
178 | 0 |
|
179 | 0 | return nameFlag; |
180 | 0 | } |
181 | | |
182 | | void |
183 | | Accessible::Description(nsString& aDescription) |
184 | 0 | { |
185 | 0 | // There are 4 conditions that make an accessible have no accDescription: |
186 | 0 | // 1. it's a text node; or |
187 | 0 | // 2. It has no DHTML describedby property |
188 | 0 | // 3. it doesn't have an accName; or |
189 | 0 | // 4. its title attribute already equals to its accName nsAutoString name; |
190 | 0 |
|
191 | 0 | if (!HasOwnContent() || mContent->IsText()) |
192 | 0 | return; |
193 | 0 | |
194 | 0 | nsTextEquivUtils:: |
195 | 0 | GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby, |
196 | 0 | aDescription); |
197 | 0 |
|
198 | 0 | if (aDescription.IsEmpty()) { |
199 | 0 | NativeDescription(aDescription); |
200 | 0 |
|
201 | 0 | if (aDescription.IsEmpty()) { |
202 | 0 | // Keep the Name() method logic. |
203 | 0 | if (mContent->IsHTMLElement()) { |
204 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aDescription); |
205 | 0 | } else if (mContent->IsXULElement()) { |
206 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aDescription); |
207 | 0 | } else if (mContent->IsSVGElement()) { |
208 | 0 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
209 | 0 | childElm = childElm->GetNextSibling()) { |
210 | 0 | if (childElm->IsSVGElement(nsGkAtoms::desc)) { |
211 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, |
212 | 0 | &aDescription); |
213 | 0 | break; |
214 | 0 | } |
215 | 0 | } |
216 | 0 | } |
217 | 0 | } |
218 | 0 | } |
219 | 0 |
|
220 | 0 | if (!aDescription.IsEmpty()) { |
221 | 0 | aDescription.CompressWhitespace(); |
222 | 0 | nsAutoString name; |
223 | 0 | Name(name); |
224 | 0 | // Don't expose a description if it is the same as the name. |
225 | 0 | if (aDescription.Equals(name)) |
226 | 0 | aDescription.Truncate(); |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | KeyBinding |
231 | | Accessible::AccessKey() const |
232 | 0 | { |
233 | 0 | if (!HasOwnContent()) |
234 | 0 | return KeyBinding(); |
235 | 0 | |
236 | 0 | uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent); |
237 | 0 | if (!key && mContent->IsElement()) { |
238 | 0 | Accessible* label = nullptr; |
239 | 0 |
|
240 | 0 | // Copy access key from label node. |
241 | 0 | if (mContent->IsHTMLElement()) { |
242 | 0 | // Unless it is labeled via an ancestor <label>, in which case that would |
243 | 0 | // be redundant. |
244 | 0 | HTMLLabelIterator iter(Document(), this, |
245 | 0 | HTMLLabelIterator::eSkipAncestorLabel); |
246 | 0 | label = iter.Next(); |
247 | 0 |
|
248 | 0 | } else if (mContent->IsXULElement()) { |
249 | 0 | XULLabelIterator iter(Document(), mContent); |
250 | 0 | label = iter.Next(); |
251 | 0 | } |
252 | 0 |
|
253 | 0 | if (label) |
254 | 0 | key = nsCoreUtils::GetAccessKeyFor(label->GetContent()); |
255 | 0 | } |
256 | 0 |
|
257 | 0 | if (!key) |
258 | 0 | return KeyBinding(); |
259 | 0 | |
260 | 0 | // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1). |
261 | 0 | switch (Preferences::GetInt("ui.key.generalAccessKey", -1)) { |
262 | 0 | case -1: |
263 | 0 | break; |
264 | 0 | case dom::KeyboardEvent_Binding::DOM_VK_SHIFT: |
265 | 0 | return KeyBinding(key, KeyBinding::kShift); |
266 | 0 | case dom::KeyboardEvent_Binding::DOM_VK_CONTROL: |
267 | 0 | return KeyBinding(key, KeyBinding::kControl); |
268 | 0 | case dom::KeyboardEvent_Binding::DOM_VK_ALT: |
269 | 0 | return KeyBinding(key, KeyBinding::kAlt); |
270 | 0 | case dom::KeyboardEvent_Binding::DOM_VK_META: |
271 | 0 | return KeyBinding(key, KeyBinding::kMeta); |
272 | 0 | default: |
273 | 0 | return KeyBinding(); |
274 | 0 | } |
275 | 0 | |
276 | 0 | // Determine the access modifier used in this context. |
277 | 0 | nsIDocument* document = mContent->GetUncomposedDoc(); |
278 | 0 | if (!document) |
279 | 0 | return KeyBinding(); |
280 | 0 | |
281 | 0 | nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell()); |
282 | 0 | if (!treeItem) |
283 | 0 | return KeyBinding(); |
284 | 0 | |
285 | 0 | nsresult rv = NS_ERROR_FAILURE; |
286 | 0 | int32_t modifierMask = 0; |
287 | 0 | switch (treeItem->ItemType()) { |
288 | 0 | case nsIDocShellTreeItem::typeChrome: |
289 | 0 | rv = Preferences::GetInt("ui.key.chromeAccess", &modifierMask); |
290 | 0 | break; |
291 | 0 | case nsIDocShellTreeItem::typeContent: |
292 | 0 | rv = Preferences::GetInt("ui.key.contentAccess", &modifierMask); |
293 | 0 | break; |
294 | 0 | } |
295 | 0 | |
296 | 0 | return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding(); |
297 | 0 | } |
298 | | |
299 | | KeyBinding |
300 | | Accessible::KeyboardShortcut() const |
301 | 0 | { |
302 | 0 | return KeyBinding(); |
303 | 0 | } |
304 | | |
305 | | void |
306 | | Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut) |
307 | 0 | { |
308 | 0 | nsCOMPtr<nsIStringBundleService> stringBundleService = |
309 | 0 | services::GetStringBundleService(); |
310 | 0 | if (!stringBundleService) |
311 | 0 | return; |
312 | 0 | |
313 | 0 | nsCOMPtr<nsIStringBundle> stringBundle; |
314 | 0 | stringBundleService->CreateBundle( |
315 | 0 | "chrome://global-platform/locale/accessible.properties", |
316 | 0 | getter_AddRefs(stringBundle)); |
317 | 0 | if (!stringBundle) |
318 | 0 | return; |
319 | 0 | |
320 | 0 | nsAutoString xsValue; |
321 | 0 | nsresult rv = |
322 | 0 | stringBundle->GetStringFromName(NS_ConvertUTF16toUTF8(aKey).get(), xsValue); |
323 | 0 | if (NS_SUCCEEDED(rv)) |
324 | 0 | aStringOut.Assign(xsValue); |
325 | 0 | } |
326 | | |
327 | | uint64_t |
328 | | Accessible::VisibilityState() const |
329 | 0 | { |
330 | 0 | nsIFrame* frame = GetFrame(); |
331 | 0 | if (!frame) { |
332 | 0 | // Element having display:contents is considered visible semantically, |
333 | 0 | // despite it doesn't have a visually visible box. |
334 | 0 | if (mContent->IsElement() && mContent->AsElement()->IsDisplayContents()) { |
335 | 0 | return states::OFFSCREEN; |
336 | 0 | } |
337 | 0 | return states::INVISIBLE; |
338 | 0 | } |
339 | 0 | |
340 | 0 | // Walk the parent frame chain to see if there's invisible parent or the frame |
341 | 0 | // is in background tab. |
342 | 0 | if (!frame->StyleVisibility()->IsVisible()) |
343 | 0 | return states::INVISIBLE; |
344 | 0 | |
345 | 0 | // Offscreen state if the document's visibility state is not visible. |
346 | 0 | if (Document()->IsHidden()) |
347 | 0 | return states::OFFSCREEN; |
348 | 0 | |
349 | 0 | nsIFrame* curFrame = frame; |
350 | 0 | do { |
351 | 0 | nsView* view = curFrame->GetView(); |
352 | 0 | if (view && view->GetVisibility() == nsViewVisibility_kHide) |
353 | 0 | return states::INVISIBLE; |
354 | 0 | |
355 | 0 | if (nsLayoutUtils::IsPopup(curFrame)) |
356 | 0 | return 0; |
357 | 0 | |
358 | 0 | // Offscreen state for background tab content and invisible for not selected |
359 | 0 | // deck panel. |
360 | 0 | nsIFrame* parentFrame = curFrame->GetParent(); |
361 | 0 | nsDeckFrame* deckFrame = do_QueryFrame(parentFrame); |
362 | 0 | if (deckFrame && deckFrame->GetSelectedBox() != curFrame) { |
363 | 0 | if (deckFrame->GetContent()->IsXULElement(nsGkAtoms::tabpanels)) |
364 | 0 | return states::OFFSCREEN; |
365 | 0 | |
366 | 0 | MOZ_ASSERT_UNREACHABLE("Children of not selected deck panel are not accessible."); |
367 | 0 | return states::INVISIBLE; |
368 | 0 | } |
369 | 0 | |
370 | 0 | // If contained by scrollable frame then check that at least 12 pixels |
371 | 0 | // around the object is visible, otherwise the object is offscreen. |
372 | 0 | nsIScrollableFrame* scrollableFrame = do_QueryFrame(parentFrame); |
373 | 0 | if (scrollableFrame) { |
374 | 0 | nsRect scrollPortRect = scrollableFrame->GetScrollPortRect(); |
375 | 0 | nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor( |
376 | 0 | frame, frame->GetRectRelativeToSelf(), parentFrame); |
377 | 0 | if (!scrollPortRect.Contains(frameRect)) { |
378 | 0 | const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12); |
379 | 0 | scrollPortRect.Deflate(kMinPixels, kMinPixels); |
380 | 0 | if (!scrollPortRect.Intersects(frameRect)) |
381 | 0 | return states::OFFSCREEN; |
382 | 0 | } |
383 | 0 | } |
384 | 0 | |
385 | 0 | if (!parentFrame) { |
386 | 0 | parentFrame = nsLayoutUtils::GetCrossDocParentFrame(curFrame); |
387 | 0 | if (parentFrame && !parentFrame->StyleVisibility()->IsVisible()) |
388 | 0 | return states::INVISIBLE; |
389 | 0 | } |
390 | 0 | |
391 | 0 | curFrame = parentFrame; |
392 | 0 | } while (curFrame); |
393 | 0 |
|
394 | 0 | // Zero area rects can occur in the first frame of a multi-frame text flow, |
395 | 0 | // in which case the rendered text is not empty and the frame should not be |
396 | 0 | // marked invisible. |
397 | 0 | // XXX Can we just remove this check? Why do we need to mark empty |
398 | 0 | // text invisible? |
399 | 0 | if (frame->IsTextFrame() && |
400 | 0 | !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && |
401 | 0 | frame->GetRect().IsEmpty()) { |
402 | 0 | nsIFrame::RenderedText text = frame->GetRenderedText(0, |
403 | 0 | UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT, |
404 | 0 | nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE); |
405 | 0 | if (text.mString.IsEmpty()) { |
406 | 0 | return states::INVISIBLE; |
407 | 0 | } |
408 | 0 | } |
409 | 0 | |
410 | 0 | return 0; |
411 | 0 | } |
412 | | |
413 | | uint64_t |
414 | | Accessible::NativeState() const |
415 | 0 | { |
416 | 0 | uint64_t state = 0; |
417 | 0 |
|
418 | 0 | if (!IsInDocument()) |
419 | 0 | state |= states::STALE; |
420 | 0 |
|
421 | 0 | if (HasOwnContent() && mContent->IsElement()) { |
422 | 0 | EventStates elementState = mContent->AsElement()->State(); |
423 | 0 |
|
424 | 0 | if (elementState.HasState(NS_EVENT_STATE_INVALID)) |
425 | 0 | state |= states::INVALID; |
426 | 0 |
|
427 | 0 | if (elementState.HasState(NS_EVENT_STATE_REQUIRED)) |
428 | 0 | state |= states::REQUIRED; |
429 | 0 |
|
430 | 0 | state |= NativeInteractiveState(); |
431 | 0 | if (FocusMgr()->IsFocused(this)) |
432 | 0 | state |= states::FOCUSED; |
433 | 0 | } |
434 | 0 |
|
435 | 0 | // Gather states::INVISIBLE and states::OFFSCREEN flags for this object. |
436 | 0 | state |= VisibilityState(); |
437 | 0 |
|
438 | 0 | nsIFrame *frame = GetFrame(); |
439 | 0 | if (frame) { |
440 | 0 | if (frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) |
441 | 0 | state |= states::FLOATING; |
442 | 0 |
|
443 | 0 | // XXX we should look at layout for non XUL box frames, but need to decide |
444 | 0 | // how that interacts with ARIA. |
445 | 0 | if (HasOwnContent() && mContent->IsXULElement() && frame->IsXULBoxFrame()) { |
446 | 0 | const nsStyleXUL* xulStyle = frame->StyleXUL(); |
447 | 0 | if (xulStyle && frame->IsXULBoxFrame()) { |
448 | 0 | // In XUL all boxes are either vertical or horizontal |
449 | 0 | if (xulStyle->mBoxOrient == StyleBoxOrient::Vertical) |
450 | 0 | state |= states::VERTICAL; |
451 | 0 | else |
452 | 0 | state |= states::HORIZONTAL; |
453 | 0 | } |
454 | 0 | } |
455 | 0 | } |
456 | 0 |
|
457 | 0 | // Check if a XUL element has the popup attribute (an attached popup menu). |
458 | 0 | if (HasOwnContent() && mContent->IsXULElement() && |
459 | 0 | mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) |
460 | 0 | state |= states::HASPOPUP; |
461 | 0 |
|
462 | 0 | // Bypass the link states specialization for non links. |
463 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
464 | 0 | if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole || |
465 | 0 | roleMapEntry->role == roles::LINK) |
466 | 0 | state |= NativeLinkState(); |
467 | 0 |
|
468 | 0 | return state; |
469 | 0 | } |
470 | | |
471 | | uint64_t |
472 | | Accessible::NativeInteractiveState() const |
473 | 0 | { |
474 | 0 | if (!mContent->IsElement()) |
475 | 0 | return 0; |
476 | 0 | |
477 | 0 | if (NativelyUnavailable()) |
478 | 0 | return states::UNAVAILABLE; |
479 | 0 | |
480 | 0 | nsIFrame* frame = GetFrame(); |
481 | 0 | if (frame && frame->IsFocusable()) |
482 | 0 | return states::FOCUSABLE; |
483 | 0 | |
484 | 0 | return 0; |
485 | 0 | } |
486 | | |
487 | | uint64_t |
488 | | Accessible::NativeLinkState() const |
489 | 0 | { |
490 | 0 | return 0; |
491 | 0 | } |
492 | | |
493 | | bool |
494 | | Accessible::NativelyUnavailable() const |
495 | 0 | { |
496 | 0 | if (mContent->IsHTMLElement()) |
497 | 0 | return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED); |
498 | 0 |
|
499 | 0 | return mContent->IsElement() && |
500 | 0 | mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled, |
501 | 0 | nsGkAtoms::_true, eCaseMatters); |
502 | 0 | } |
503 | | |
504 | | Accessible* |
505 | | Accessible::FocusedChild() |
506 | 0 | { |
507 | 0 | Accessible* focus = FocusMgr()->FocusedAccessible(); |
508 | 0 | if (focus && (focus == this || focus->Parent() == this)) |
509 | 0 | return focus; |
510 | 0 | |
511 | 0 | return nullptr; |
512 | 0 | } |
513 | | |
514 | | Accessible* |
515 | | Accessible::ChildAtPoint(int32_t aX, int32_t aY, |
516 | | EWhichChildAtPoint aWhichChild) |
517 | 0 | { |
518 | 0 | // If we can't find the point in a child, we will return the fallback answer: |
519 | 0 | // we return |this| if the point is within it, otherwise nullptr. |
520 | 0 | Accessible* fallbackAnswer = nullptr; |
521 | 0 | nsIntRect rect = Bounds(); |
522 | 0 | if (rect.Contains(aX, aY)) |
523 | 0 | fallbackAnswer = this; |
524 | 0 |
|
525 | 0 | if (nsAccUtils::MustPrune(this)) // Do not dig any further |
526 | 0 | return fallbackAnswer; |
527 | 0 | |
528 | 0 | // Search an accessible at the given point starting from accessible document |
529 | 0 | // because containing block (see CSS2) for out of flow element (for example, |
530 | 0 | // absolutely positioned element) may be different from its DOM parent and |
531 | 0 | // therefore accessible for containing block may be different from accessible |
532 | 0 | // for DOM parent but GetFrameForPoint() should be called for containing block |
533 | 0 | // to get an out of flow element. |
534 | 0 | DocAccessible* accDocument = Document(); |
535 | 0 | NS_ENSURE_TRUE(accDocument, nullptr); |
536 | 0 |
|
537 | 0 | nsIFrame* rootFrame = accDocument->GetFrame(); |
538 | 0 | NS_ENSURE_TRUE(rootFrame, nullptr); |
539 | 0 |
|
540 | 0 | nsIFrame* startFrame = rootFrame; |
541 | 0 |
|
542 | 0 | // Check whether the point is at popup content. |
543 | 0 | nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr); |
544 | 0 | NS_ENSURE_TRUE(rootWidget, nullptr); |
545 | 0 |
|
546 | 0 | LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds(); |
547 | 0 |
|
548 | 0 | WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget, |
549 | 0 | WidgetMouseEvent::eSynthesized); |
550 | 0 | dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y()); |
551 | 0 |
|
552 | 0 | nsIFrame* popupFrame = nsLayoutUtils:: |
553 | 0 | GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(), |
554 | 0 | &dummyEvent); |
555 | 0 | if (popupFrame) { |
556 | 0 | // If 'this' accessible is not inside the popup then ignore the popup when |
557 | 0 | // searching an accessible at point. |
558 | 0 | DocAccessible* popupDoc = |
559 | 0 | GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc()); |
560 | 0 | Accessible* popupAcc = |
561 | 0 | popupDoc->GetAccessibleOrContainer(popupFrame->GetContent()); |
562 | 0 | Accessible* popupChild = this; |
563 | 0 | while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) |
564 | 0 | popupChild = popupChild->Parent(); |
565 | 0 |
|
566 | 0 | if (popupChild == popupAcc) |
567 | 0 | startFrame = popupFrame; |
568 | 0 | } |
569 | 0 |
|
570 | 0 | nsPresContext* presContext = startFrame->PresContext(); |
571 | 0 | nsRect screenRect = startFrame->GetScreenRectInAppUnits(); |
572 | 0 | nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(), |
573 | 0 | presContext->DevPixelsToAppUnits(aY) - screenRect.Y()); |
574 | 0 |
|
575 | 0 | // We need to take into account a non-1 resolution set on the presshell. |
576 | 0 | // This happens in mobile platforms with async pinch zooming. |
577 | 0 | offset = offset.RemoveResolution(presContext->PresShell()->GetResolution()); |
578 | 0 |
|
579 | 0 | nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(startFrame, offset); |
580 | 0 |
|
581 | 0 | nsIContent* content = nullptr; |
582 | 0 | if (!foundFrame || !(content = foundFrame->GetContent())) |
583 | 0 | return fallbackAnswer; |
584 | 0 | |
585 | 0 | // Get accessible for the node with the point or the first accessible in |
586 | 0 | // the DOM parent chain. |
587 | 0 | DocAccessible* contentDocAcc = GetAccService()-> |
588 | 0 | GetDocAccessible(content->OwnerDoc()); |
589 | 0 |
|
590 | 0 | // contentDocAcc in some circumstances can be nullptr. See bug 729861 |
591 | 0 | NS_ASSERTION(contentDocAcc, "could not get the document accessible"); |
592 | 0 | if (!contentDocAcc) |
593 | 0 | return fallbackAnswer; |
594 | 0 | |
595 | 0 | Accessible* accessible = contentDocAcc->GetAccessibleOrContainer(content); |
596 | 0 | if (!accessible) |
597 | 0 | return fallbackAnswer; |
598 | 0 | |
599 | 0 | // Hurray! We have an accessible for the frame that layout gave us. |
600 | 0 | // Since DOM node of obtained accessible may be out of flow then we should |
601 | 0 | // ensure obtained accessible is a child of this accessible. |
602 | 0 | Accessible* child = accessible; |
603 | 0 | while (child != this) { |
604 | 0 | Accessible* parent = child->Parent(); |
605 | 0 | if (!parent) { |
606 | 0 | // Reached the top of the hierarchy. These bounds were inside an |
607 | 0 | // accessible that is not a descendant of this one. |
608 | 0 | return fallbackAnswer; |
609 | 0 | } |
610 | 0 | |
611 | 0 | // If we landed on a legitimate child of |this|, and we want the direct |
612 | 0 | // child, return it here. |
613 | 0 | if (parent == this && aWhichChild == eDirectChild) |
614 | 0 | return child; |
615 | 0 | |
616 | 0 | child = parent; |
617 | 0 | } |
618 | 0 |
|
619 | 0 | // Manually walk through accessible children and see if the are within this |
620 | 0 | // point. Skip offscreen or invisible accessibles. This takes care of cases |
621 | 0 | // where layout won't walk into things for us, such as image map areas and |
622 | 0 | // sub documents (XXX: subdocuments should be handled by methods of |
623 | 0 | // OuterDocAccessibles). |
624 | 0 | uint32_t childCount = accessible->ChildCount(); |
625 | 0 | for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
626 | 0 | Accessible* child = accessible->GetChildAt(childIdx); |
627 | 0 |
|
628 | 0 | nsIntRect childRect = child->Bounds(); |
629 | 0 | if (childRect.Contains(aX, aY) && |
630 | 0 | (child->State() & states::INVISIBLE) == 0) { |
631 | 0 |
|
632 | 0 | if (aWhichChild == eDeepestChild) |
633 | 0 | return child->ChildAtPoint(aX, aY, eDeepestChild); |
634 | 0 | |
635 | 0 | return child; |
636 | 0 | } |
637 | 0 | } |
638 | 0 |
|
639 | 0 | return accessible; |
640 | 0 | } |
641 | | |
642 | | nsRect |
643 | | Accessible::RelativeBounds(nsIFrame** aBoundingFrame) const |
644 | 0 | { |
645 | 0 | nsIFrame* frame = GetFrame(); |
646 | 0 | if (frame && mContent) { |
647 | 0 | bool* pHasHitRegionRect = static_cast<bool*>(mContent->GetProperty(nsGkAtoms::hitregion)); |
648 | 0 | MOZ_ASSERT(pHasHitRegionRect == nullptr || |
649 | 0 | *pHasHitRegionRect, "hitregion property is always null or true"); |
650 | 0 | bool hasHitRegionRect = pHasHitRegionRect != nullptr && *pHasHitRegionRect; |
651 | 0 |
|
652 | 0 | if (hasHitRegionRect && mContent->IsElement()) { |
653 | 0 | // This is for canvas fallback content |
654 | 0 | // Find a canvas frame the found hit region is relative to. |
655 | 0 | nsIFrame* canvasFrame = frame->GetParent(); |
656 | 0 | if (canvasFrame) { |
657 | 0 | canvasFrame = nsLayoutUtils::GetClosestFrameOfType( |
658 | 0 | canvasFrame, LayoutFrameType::HTMLCanvas); |
659 | 0 | } |
660 | 0 |
|
661 | 0 | // make the canvas the bounding frame |
662 | 0 | if (canvasFrame) { |
663 | 0 | *aBoundingFrame = canvasFrame; |
664 | 0 | dom::HTMLCanvasElement *canvas = |
665 | 0 | dom::HTMLCanvasElement::FromNode(canvasFrame->GetContent()); |
666 | 0 |
|
667 | 0 | // get the bounding rect of the hit region |
668 | 0 | nsRect bounds; |
669 | 0 | if (canvas && canvas->CountContexts() && |
670 | 0 | canvas->GetContextAtIndex(0)->GetHitRegionRect(mContent->AsElement(), bounds)) { |
671 | 0 | return bounds; |
672 | 0 | } |
673 | 0 | } |
674 | 0 | } |
675 | 0 | |
676 | 0 | *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); |
677 | 0 | return nsLayoutUtils:: |
678 | 0 | GetAllInFlowRectsUnion(frame, *aBoundingFrame, |
679 | 0 | nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS); |
680 | 0 | } |
681 | 0 | |
682 | 0 | return nsRect(); |
683 | 0 | } |
684 | | |
685 | | nsRect |
686 | | Accessible::BoundsInAppUnits() const |
687 | 0 | { |
688 | 0 | nsIFrame* boundingFrame = nullptr; |
689 | 0 | nsRect unionRectTwips = RelativeBounds(&boundingFrame); |
690 | 0 | if (!boundingFrame) { |
691 | 0 | return nsRect(); |
692 | 0 | } |
693 | 0 | |
694 | 0 | // We need to take into account a non-1 resolution set on the presshell. |
695 | 0 | // This happens in mobile platforms with async pinch zooming. Here we |
696 | 0 | // scale the bounds before adding the screen-relative offset. |
697 | 0 | unionRectTwips.ScaleRoundOut(mDoc->PresContext()->PresShell()->GetResolution()); |
698 | 0 | // We have the union of the rectangle, now we need to put it in absolute |
699 | 0 | // screen coords. |
700 | 0 | nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits(); |
701 | 0 | unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y()); |
702 | 0 |
|
703 | 0 | return unionRectTwips; |
704 | 0 | } |
705 | | |
706 | | nsIntRect |
707 | | Accessible::Bounds() const |
708 | 0 | { |
709 | 0 | return BoundsInAppUnits().ToNearestPixels(mDoc->PresContext()->AppUnitsPerDevPixel()); |
710 | 0 | } |
711 | | |
712 | | nsIntRect |
713 | | Accessible::BoundsInCSSPixels() const |
714 | 0 | { |
715 | 0 | return BoundsInAppUnits().ToNearestPixels(AppUnitsPerCSSPixel()); |
716 | 0 | } |
717 | | |
718 | | void |
719 | | Accessible::SetSelected(bool aSelect) |
720 | 0 | { |
721 | 0 | if (!HasOwnContent()) |
722 | 0 | return; |
723 | 0 | |
724 | 0 | Accessible* select = nsAccUtils::GetSelectableContainer(this, State()); |
725 | 0 | if (select) { |
726 | 0 | if (select->State() & states::MULTISELECTABLE) { |
727 | 0 | if (mContent->IsElement() && ARIARoleMap()) { |
728 | 0 | if (aSelect) { |
729 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, |
730 | 0 | nsGkAtoms::aria_selected, |
731 | 0 | NS_LITERAL_STRING("true"), true); |
732 | 0 | } else { |
733 | 0 | mContent->AsElement()->UnsetAttr(kNameSpaceID_None, |
734 | 0 | nsGkAtoms::aria_selected, true); |
735 | 0 | } |
736 | 0 | } |
737 | 0 | return; |
738 | 0 | } |
739 | 0 |
|
740 | 0 | if (aSelect) |
741 | 0 | TakeFocus(); |
742 | 0 | } |
743 | 0 | } |
744 | | |
745 | | void |
746 | | Accessible::TakeSelection() |
747 | 0 | { |
748 | 0 | Accessible* select = nsAccUtils::GetSelectableContainer(this, State()); |
749 | 0 | if (select) { |
750 | 0 | if (select->State() & states::MULTISELECTABLE) |
751 | 0 | select->UnselectAll(); |
752 | 0 | SetSelected(true); |
753 | 0 | } |
754 | 0 | } |
755 | | |
756 | | void |
757 | | Accessible::TakeFocus() const |
758 | 0 | { |
759 | 0 | nsIFrame* frame = GetFrame(); |
760 | 0 | if (!frame) |
761 | 0 | return; |
762 | 0 | |
763 | 0 | nsIContent* focusContent = mContent; |
764 | 0 |
|
765 | 0 | // If the accessible focus is managed by container widget then focus the |
766 | 0 | // widget and set the accessible as its current item. |
767 | 0 | if (!frame->IsFocusable()) { |
768 | 0 | Accessible* widget = ContainerWidget(); |
769 | 0 | if (widget && widget->AreItemsOperable()) { |
770 | 0 | nsIContent* widgetElm = widget->GetContent(); |
771 | 0 | nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame(); |
772 | 0 | if (widgetFrame && widgetFrame->IsFocusable()) { |
773 | 0 | focusContent = widgetElm; |
774 | 0 | widget->SetCurrentItem(this); |
775 | 0 | } |
776 | 0 | } |
777 | 0 | } |
778 | 0 |
|
779 | 0 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
780 | 0 | if (fm) { |
781 | 0 | AutoHandlingUserInputStatePusher inputStatePusher(true, nullptr, focusContent->OwnerDoc()); |
782 | 0 | // XXXbz: Can we actually have a non-element content here? |
783 | 0 | RefPtr<Element> element = |
784 | 0 | focusContent->IsElement() ? focusContent->AsElement() : nullptr; |
785 | 0 | fm->SetFocus(element, 0); |
786 | 0 | } |
787 | 0 | } |
788 | | |
789 | | void |
790 | | Accessible::XULElmName(DocAccessible* aDocument, |
791 | | nsIContent* aElm, nsString& aName) |
792 | 0 | { |
793 | 0 | /** |
794 | 0 | * 3 main cases for XUL Controls to be labeled |
795 | 0 | * 1 - control contains label="foo" |
796 | 0 | * 2 - control has, as a child, a label element |
797 | 0 | * - label has either value="foo" or children |
798 | 0 | * 3 - non-child label contains control="controlID" |
799 | 0 | * - label has either value="foo" or children |
800 | 0 | * Once a label is found, the search is discontinued, so a control |
801 | 0 | * that has a label child as well as having a label external to |
802 | 0 | * the control that uses the control="controlID" syntax will use |
803 | 0 | * the child label for its Name. |
804 | 0 | */ |
805 | 0 |
|
806 | 0 | // CASE #1 (via label attribute) -- great majority of the cases |
807 | 0 | nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm); |
808 | 0 | if (itemEl) { |
809 | 0 | itemEl->GetLabel(aName); |
810 | 0 | } else { |
811 | 0 | // Use @label if this is not a select control element, which uses label |
812 | 0 | // attribute to indicate, which option is selected. |
813 | 0 | nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm); |
814 | 0 | if (!select) { |
815 | 0 | aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName); |
816 | 0 | } |
817 | 0 | } |
818 | 0 |
|
819 | 0 | // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label> |
820 | 0 | if (aName.IsEmpty()) { |
821 | 0 | Accessible* label = nullptr; |
822 | 0 | XULLabelIterator iter(aDocument, aElm); |
823 | 0 | while ((label = iter.Next())) { |
824 | 0 | // Check if label's value attribute is used |
825 | 0 | label->Elm()->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aName); |
826 | 0 | if (aName.IsEmpty()) { |
827 | 0 | // If no value attribute, a non-empty label must contain |
828 | 0 | // children that define its text -- possibly using HTML |
829 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName); |
830 | 0 | } |
831 | 0 | } |
832 | 0 | } |
833 | 0 |
|
834 | 0 | aName.CompressWhitespace(); |
835 | 0 | if (!aName.IsEmpty()) |
836 | 0 | return; |
837 | 0 | |
838 | 0 | // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem> |
839 | 0 | nsIContent *bindingParent = aElm->GetBindingParent(); |
840 | 0 | nsIContent* parent = |
841 | 0 | bindingParent? bindingParent->GetParent() : aElm->GetParent(); |
842 | 0 | nsAutoString ancestorTitle; |
843 | 0 | while (parent) { |
844 | 0 | if (parent->IsXULElement(nsGkAtoms::toolbaritem) && |
845 | 0 | parent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::title, ancestorTitle)) { |
846 | 0 | // Before returning this, check if the element itself has a tooltip: |
847 | 0 | if (aElm->IsElement() && |
848 | 0 | aElm->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext, aName)) { |
849 | 0 | aName.CompressWhitespace(); |
850 | 0 | return; |
851 | 0 | } |
852 | 0 | |
853 | 0 | aName.Assign(ancestorTitle); |
854 | 0 | aName.CompressWhitespace(); |
855 | 0 | return; |
856 | 0 | } |
857 | 0 | parent = parent->GetParent(); |
858 | 0 | } |
859 | 0 | } |
860 | | |
861 | | nsresult |
862 | | Accessible::HandleAccEvent(AccEvent* aEvent) |
863 | 0 | { |
864 | 0 | NS_ENSURE_ARG_POINTER(aEvent); |
865 | 0 |
|
866 | 0 | #ifdef MOZ_GECKO_PROFILER |
867 | 0 | if (profiler_is_active()) { |
868 | 0 | nsAutoCString strEventType; |
869 | 0 | GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType); |
870 | 0 | nsAutoCString strMarker; |
871 | 0 | strMarker.AppendLiteral("A11y Event - "); |
872 | 0 | strMarker.Append(strEventType); |
873 | 0 | profiler_add_marker(strMarker.get()); |
874 | 0 | } |
875 | 0 | #endif |
876 | 0 |
|
877 | 0 | if (IPCAccessibilityActive() && Document()) { |
878 | 0 | DocAccessibleChild* ipcDoc = mDoc->IPCDoc(); |
879 | 0 | MOZ_ASSERT(ipcDoc); |
880 | 0 | if (ipcDoc) { |
881 | 0 | uint64_t id = aEvent->GetAccessible()->IsDoc() ? 0 : |
882 | 0 | reinterpret_cast<uintptr_t>(aEvent->GetAccessible()->UniqueID()); |
883 | 0 |
|
884 | 0 | switch(aEvent->GetEventType()) { |
885 | 0 | case nsIAccessibleEvent::EVENT_SHOW: |
886 | 0 | ipcDoc->ShowEvent(downcast_accEvent(aEvent)); |
887 | 0 | break; |
888 | 0 |
|
889 | 0 | case nsIAccessibleEvent::EVENT_HIDE: |
890 | 0 | ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput()); |
891 | 0 | break; |
892 | 0 |
|
893 | 0 | case nsIAccessibleEvent::EVENT_REORDER: |
894 | 0 | // reorder events on the application acc aren't necessary to tell the parent |
895 | 0 | // about new top level documents. |
896 | 0 | if (!aEvent->GetAccessible()->IsApplication()) |
897 | 0 | ipcDoc->SendEvent(id, aEvent->GetEventType()); |
898 | 0 | break; |
899 | 0 | case nsIAccessibleEvent::EVENT_STATE_CHANGE: { |
900 | 0 | AccStateChangeEvent* event = downcast_accEvent(aEvent); |
901 | 0 | ipcDoc->SendStateChangeEvent(id, event->GetState(), |
902 | 0 | event->IsStateEnabled()); |
903 | 0 | break; |
904 | 0 | } |
905 | 0 | case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { |
906 | 0 | AccCaretMoveEvent* event = downcast_accEvent(aEvent); |
907 | 0 | ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset()); |
908 | 0 | break; |
909 | 0 | } |
910 | 0 | case nsIAccessibleEvent::EVENT_TEXT_INSERTED: |
911 | 0 | case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { |
912 | 0 | AccTextChangeEvent* event = downcast_accEvent(aEvent); |
913 | 0 | ipcDoc->SendTextChangeEvent(id, event->ModifiedText(), |
914 | 0 | event->GetStartOffset(), |
915 | 0 | event->GetLength(), |
916 | 0 | event->IsTextInserted(), |
917 | 0 | event->IsFromUserInput()); |
918 | 0 | break; |
919 | 0 | } |
920 | 0 | case nsIAccessibleEvent::EVENT_SELECTION: |
921 | 0 | case nsIAccessibleEvent::EVENT_SELECTION_ADD: |
922 | 0 | case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { |
923 | 0 | AccSelChangeEvent* selEvent = downcast_accEvent(aEvent); |
924 | 0 | uint64_t widgetID = selEvent->Widget()->IsDoc() ? 0 : |
925 | 0 | reinterpret_cast<uintptr_t>(selEvent->Widget()->UniqueID()); |
926 | 0 | ipcDoc->SendSelectionEvent(id, widgetID, aEvent->GetEventType()); |
927 | 0 | break; |
928 | 0 | } |
929 | 0 | case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: { |
930 | 0 | AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent); |
931 | 0 | Accessible* position = vcEvent->NewAccessible(); |
932 | 0 | Accessible* oldPosition = vcEvent->OldAccessible(); |
933 | 0 | ipcDoc->SendVirtualCursorChangeEvent(id, |
934 | 0 | oldPosition ? reinterpret_cast<uintptr_t>(oldPosition->UniqueID()) : 0, |
935 | 0 | vcEvent->OldStartOffset(), vcEvent->OldEndOffset(), |
936 | 0 | position ? reinterpret_cast<uintptr_t>(position->UniqueID()) : 0, |
937 | 0 | vcEvent->NewStartOffset(), vcEvent->NewEndOffset(), |
938 | 0 | vcEvent->Reason(), vcEvent->BoundaryType(), |
939 | 0 | vcEvent->IsFromUserInput()); |
940 | 0 | break; |
941 | 0 | } |
942 | | #if defined(XP_WIN) |
943 | | case nsIAccessibleEvent::EVENT_FOCUS: { |
944 | | ipcDoc->SendFocusEvent(id); |
945 | | break; |
946 | | } |
947 | | #endif |
948 | 0 | case nsIAccessibleEvent::EVENT_SCROLLING_END: |
949 | 0 | case nsIAccessibleEvent::EVENT_SCROLLING: { |
950 | 0 | AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent); |
951 | 0 | ipcDoc->SendScrollingEvent(id, aEvent->GetEventType(), |
952 | 0 | scrollingEvent->ScrollX(), scrollingEvent->ScrollY(), |
953 | 0 | scrollingEvent->MaxScrollX(), scrollingEvent->MaxScrollY()); |
954 | 0 | break; |
955 | 0 | } |
956 | 0 | default: |
957 | 0 | ipcDoc->SendEvent(id, aEvent->GetEventType()); |
958 | 0 | } |
959 | 0 | } |
960 | 0 | } |
961 | 0 |
|
962 | 0 | if (nsCoreUtils::AccEventObserversExist()) { |
963 | 0 | nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent)); |
964 | 0 | } |
965 | 0 |
|
966 | 0 | return NS_OK; |
967 | 0 | } |
968 | | |
969 | | already_AddRefed<nsIPersistentProperties> |
970 | | Accessible::Attributes() |
971 | 0 | { |
972 | 0 | nsCOMPtr<nsIPersistentProperties> attributes = NativeAttributes(); |
973 | 0 | if (!HasOwnContent() || !mContent->IsElement()) |
974 | 0 | return attributes.forget(); |
975 | 0 | |
976 | 0 | // 'xml-roles' attribute for landmark. |
977 | 0 | nsAtom* landmark = LandmarkRole(); |
978 | 0 | if (landmark) { |
979 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, landmark); |
980 | 0 |
|
981 | 0 | } else { |
982 | 0 | // 'xml-roles' attribute coming from ARIA. |
983 | 0 | nsAutoString xmlRoles; |
984 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles)) |
985 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles); |
986 | 0 | } |
987 | 0 |
|
988 | 0 | // Expose object attributes from ARIA attributes. |
989 | 0 | nsAutoString unused; |
990 | 0 | aria::AttrIterator attribIter(mContent); |
991 | 0 | nsAutoString name, value; |
992 | 0 | while(attribIter.Next(name, value)) |
993 | 0 | attributes->SetStringProperty(NS_ConvertUTF16toUTF8(name), value, unused); |
994 | 0 |
|
995 | 0 | // If there is no aria-live attribute then expose default value of 'live' |
996 | 0 | // object attribute used for ARIA role of this accessible. |
997 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
998 | 0 | if (roleMapEntry) { |
999 | 0 | if (roleMapEntry->Is(nsGkAtoms::searchbox)) { |
1000 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, |
1001 | 0 | NS_LITERAL_STRING("search")); |
1002 | 0 | } |
1003 | 0 |
|
1004 | 0 | nsAutoString live; |
1005 | 0 | nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live); |
1006 | 0 | if (live.IsEmpty()) { |
1007 | 0 | if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) |
1008 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::live, live); |
1009 | 0 | } |
1010 | 0 | } |
1011 | 0 |
|
1012 | 0 | return attributes.forget(); |
1013 | 0 | } |
1014 | | |
1015 | | already_AddRefed<nsIPersistentProperties> |
1016 | | Accessible::NativeAttributes() |
1017 | 0 | { |
1018 | 0 | RefPtr<nsPersistentProperties> attributes = new nsPersistentProperties(); |
1019 | 0 |
|
1020 | 0 | nsAutoString unused; |
1021 | 0 |
|
1022 | 0 | // We support values, so expose the string value as well, via the valuetext |
1023 | 0 | // object attribute. We test for the value interface because we don't want |
1024 | 0 | // to expose traditional Value() information such as URL's on links and |
1025 | 0 | // documents, or text in an input. |
1026 | 0 | if (HasNumericValue()) { |
1027 | 0 | nsAutoString valuetext; |
1028 | 0 | Value(valuetext); |
1029 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("valuetext"), valuetext, |
1030 | 0 | unused); |
1031 | 0 | } |
1032 | 0 |
|
1033 | 0 | // Expose checkable object attribute if the accessible has checkable state |
1034 | 0 | if (State() & states::CHECKABLE) { |
1035 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::checkable, |
1036 | 0 | NS_LITERAL_STRING("true")); |
1037 | 0 | } |
1038 | 0 |
|
1039 | 0 | // Expose 'explicit-name' attribute. |
1040 | 0 | nsAutoString name; |
1041 | 0 | if (Name(name) != eNameFromSubtree && !name.IsVoid()) { |
1042 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("explicit-name"), |
1043 | 0 | NS_LITERAL_STRING("true"), unused); |
1044 | 0 | } |
1045 | 0 |
|
1046 | 0 | // Group attributes (level/setsize/posinset) |
1047 | 0 | GroupPos groupPos = GroupPosition(); |
1048 | 0 | nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, |
1049 | 0 | groupPos.setSize, groupPos.posInSet); |
1050 | 0 |
|
1051 | 0 | bool hierarchical = false; |
1052 | 0 | uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical); |
1053 | 0 | if (itemCount) { |
1054 | 0 | nsAutoString itemCountStr; |
1055 | 0 | itemCountStr.AppendInt(itemCount); |
1056 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("child-item-count"), |
1057 | 0 | itemCountStr, unused); |
1058 | 0 | } |
1059 | 0 |
|
1060 | 0 | if (hierarchical) { |
1061 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("hierarchical"), |
1062 | 0 | NS_LITERAL_STRING("true"), unused); |
1063 | 0 | } |
1064 | 0 |
|
1065 | 0 | // If the accessible doesn't have own content (such as list item bullet or |
1066 | 0 | // xul tree item) then don't calculate content based attributes. |
1067 | 0 | if (!HasOwnContent()) |
1068 | 0 | return attributes.forget(); |
1069 | 0 | |
1070 | 0 | nsEventShell::GetEventAttributes(GetNode(), attributes); |
1071 | 0 |
|
1072 | 0 | // Get container-foo computed live region properties based on the closest |
1073 | 0 | // container with the live region attribute. Inner nodes override outer nodes |
1074 | 0 | // within the same document. The inner nodes can be used to override live |
1075 | 0 | // region behavior on more general outer nodes. However, nodes in outer |
1076 | 0 | // documents override nodes in inner documents: outer doc author may want to |
1077 | 0 | // override properties on a widget they used in an iframe. |
1078 | 0 | nsIContent* startContent = mContent; |
1079 | 0 | while (startContent) { |
1080 | 0 | nsIDocument* doc = startContent->GetComposedDoc(); |
1081 | 0 | if (!doc) |
1082 | 0 | break; |
1083 | 0 | |
1084 | 0 | nsAccUtils::SetLiveContainerAttributes(attributes, startContent, |
1085 | 0 | doc->GetRootElement()); |
1086 | 0 |
|
1087 | 0 | // Allow ARIA live region markup from outer documents to override |
1088 | 0 | nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell(); |
1089 | 0 | if (!docShellTreeItem) |
1090 | 0 | break; |
1091 | 0 | |
1092 | 0 | nsCOMPtr<nsIDocShellTreeItem> sameTypeParent; |
1093 | 0 | docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent)); |
1094 | 0 | if (!sameTypeParent || sameTypeParent == docShellTreeItem) |
1095 | 0 | break; |
1096 | 0 | |
1097 | 0 | nsIDocument* parentDoc = doc->GetParentDocument(); |
1098 | 0 | if (!parentDoc) |
1099 | 0 | break; |
1100 | 0 | |
1101 | 0 | startContent = parentDoc->FindContentForSubDocument(doc); |
1102 | 0 | } |
1103 | 0 |
|
1104 | 0 | if (!mContent->IsElement()) |
1105 | 0 | return attributes.forget(); |
1106 | 0 | |
1107 | 0 | nsAutoString id; |
1108 | 0 | if (nsCoreUtils::GetID(mContent, id)) |
1109 | 0 | attributes->SetStringProperty(NS_LITERAL_CSTRING("id"), id, unused); |
1110 | 0 |
|
1111 | 0 | // Expose class because it may have useful microformat information. |
1112 | 0 | nsAutoString _class; |
1113 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, _class)) |
1114 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::_class, _class); |
1115 | 0 |
|
1116 | 0 | // Expose tag. |
1117 | 0 | nsAutoString tagName; |
1118 | 0 | mContent->NodeInfo()->GetName(tagName); |
1119 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::tag, tagName); |
1120 | 0 |
|
1121 | 0 | // Expose draggable object attribute. |
1122 | 0 | if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) { |
1123 | 0 | if (htmlElement->Draggable()) { |
1124 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::draggable, |
1125 | 0 | NS_LITERAL_STRING("true")); |
1126 | 0 | } |
1127 | 0 | } |
1128 | 0 |
|
1129 | 0 | // Don't calculate CSS-based object attributes when no frame (i.e. |
1130 | 0 | // the accessible is unattached from the tree). |
1131 | 0 | if (!mContent->GetPrimaryFrame()) |
1132 | 0 | return attributes.forget(); |
1133 | 0 | |
1134 | 0 | // CSS style based object attributes. |
1135 | 0 | nsAutoString value; |
1136 | 0 | StyleInfo styleInfo(mContent->AsElement()); |
1137 | 0 |
|
1138 | 0 | // Expose 'display' attribute. |
1139 | 0 | styleInfo.Display(value); |
1140 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::display, value); |
1141 | 0 |
|
1142 | 0 | // Expose 'text-align' attribute. |
1143 | 0 | styleInfo.TextAlign(value); |
1144 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textAlign, value); |
1145 | 0 |
|
1146 | 0 | // Expose 'text-indent' attribute. |
1147 | 0 | styleInfo.TextIndent(value); |
1148 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textIndent, value); |
1149 | 0 |
|
1150 | 0 | // Expose 'margin-left' attribute. |
1151 | 0 | styleInfo.MarginLeft(value); |
1152 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginLeft, value); |
1153 | 0 |
|
1154 | 0 | // Expose 'margin-right' attribute. |
1155 | 0 | styleInfo.MarginRight(value); |
1156 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginRight, value); |
1157 | 0 |
|
1158 | 0 | // Expose 'margin-top' attribute. |
1159 | 0 | styleInfo.MarginTop(value); |
1160 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginTop, value); |
1161 | 0 |
|
1162 | 0 | // Expose 'margin-bottom' attribute. |
1163 | 0 | styleInfo.MarginBottom(value); |
1164 | 0 | nsAccUtils::SetAccAttr(attributes, nsGkAtoms::marginBottom, value); |
1165 | 0 |
|
1166 | 0 | return attributes.forget(); |
1167 | 0 | } |
1168 | | |
1169 | | GroupPos |
1170 | | Accessible::GroupPosition() |
1171 | 0 | { |
1172 | 0 | GroupPos groupPos; |
1173 | 0 | if (!HasOwnContent()) |
1174 | 0 | return groupPos; |
1175 | 0 | |
1176 | 0 | // Get group position from ARIA attributes. |
1177 | 0 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, &groupPos.level); |
1178 | 0 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, &groupPos.setSize); |
1179 | 0 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, &groupPos.posInSet); |
1180 | 0 |
|
1181 | 0 | // If ARIA is missed and the accessible is visible then calculate group |
1182 | 0 | // position from hierarchy. |
1183 | 0 | if (State() & states::INVISIBLE) |
1184 | 0 | return groupPos; |
1185 | 0 | |
1186 | 0 | // Calculate group level if ARIA is missed. |
1187 | 0 | if (groupPos.level == 0) { |
1188 | 0 | int32_t level = GetLevelInternal(); |
1189 | 0 | if (level != 0) |
1190 | 0 | groupPos.level = level; |
1191 | 0 | } |
1192 | 0 |
|
1193 | 0 | // Calculate position in group and group size if ARIA is missed. |
1194 | 0 | if (groupPos.posInSet == 0 || groupPos.setSize == 0) { |
1195 | 0 | int32_t posInSet = 0, setSize = 0; |
1196 | 0 | GetPositionAndSizeInternal(&posInSet, &setSize); |
1197 | 0 | if (posInSet != 0 && setSize != 0) { |
1198 | 0 | if (groupPos.posInSet == 0) |
1199 | 0 | groupPos.posInSet = posInSet; |
1200 | 0 |
|
1201 | 0 | if (groupPos.setSize == 0) |
1202 | 0 | groupPos.setSize = setSize; |
1203 | 0 | } |
1204 | 0 | } |
1205 | 0 |
|
1206 | 0 | return groupPos; |
1207 | 0 | } |
1208 | | |
1209 | | uint64_t |
1210 | | Accessible::State() |
1211 | 0 | { |
1212 | 0 | if (IsDefunct()) |
1213 | 0 | return states::DEFUNCT; |
1214 | 0 | |
1215 | 0 | uint64_t state = NativeState(); |
1216 | 0 | // Apply ARIA states to be sure accessible states will be overridden. |
1217 | 0 | ApplyARIAState(&state); |
1218 | 0 |
|
1219 | 0 | // If this is an ARIA item of the selectable widget and if it's focused and |
1220 | 0 | // not marked unselected explicitly (i.e. aria-selected="false") then expose |
1221 | 0 | // it as selected to make ARIA widget authors life easier. |
1222 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1223 | 0 | if (roleMapEntry && !(state & states::SELECTED) && |
1224 | 0 | (!mContent->IsElement() || |
1225 | 0 | !mContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
1226 | 0 | nsGkAtoms::aria_selected, |
1227 | 0 | nsGkAtoms::_false, eCaseMatters))) { |
1228 | 0 | // Special case for tabs: focused tab or focus inside related tab panel |
1229 | 0 | // implies selected state. |
1230 | 0 | if (roleMapEntry->role == roles::PAGETAB) { |
1231 | 0 | if (state & states::FOCUSED) { |
1232 | 0 | state |= states::SELECTED; |
1233 | 0 | } else { |
1234 | 0 | // If focus is in a child of the tab panel surely the tab is selected! |
1235 | 0 | Relation rel = RelationByType(RelationType::LABEL_FOR); |
1236 | 0 | Accessible* relTarget = nullptr; |
1237 | 0 | while ((relTarget = rel.Next())) { |
1238 | 0 | if (relTarget->Role() == roles::PROPERTYPAGE && |
1239 | 0 | FocusMgr()->IsFocusWithin(relTarget)) |
1240 | 0 | state |= states::SELECTED; |
1241 | 0 | } |
1242 | 0 | } |
1243 | 0 | } else if (state & states::FOCUSED) { |
1244 | 0 | Accessible* container = nsAccUtils::GetSelectableContainer(this, state); |
1245 | 0 | if (container && |
1246 | 0 | !nsAccUtils::HasDefinedARIAToken(container->GetContent(), |
1247 | 0 | nsGkAtoms::aria_multiselectable)) { |
1248 | 0 | state |= states::SELECTED; |
1249 | 0 | } |
1250 | 0 | } |
1251 | 0 | } |
1252 | 0 |
|
1253 | 0 | const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED; |
1254 | 0 | if ((state & kExpandCollapseStates) == kExpandCollapseStates) { |
1255 | 0 | // Cannot be both expanded and collapsed -- this happens in ARIA expanded |
1256 | 0 | // combobox because of limitation of ARIAMap. |
1257 | 0 | // XXX: Perhaps we will be able to make this less hacky if we support |
1258 | 0 | // extended states in ARIAMap, e.g. derive COLLAPSED from |
1259 | 0 | // EXPANDABLE && !EXPANDED. |
1260 | 0 | state &= ~states::COLLAPSED; |
1261 | 0 | } |
1262 | 0 |
|
1263 | 0 | if (!(state & states::UNAVAILABLE)) { |
1264 | 0 | state |= states::ENABLED | states::SENSITIVE; |
1265 | 0 |
|
1266 | 0 | // If the object is a current item of container widget then mark it as |
1267 | 0 | // ACTIVE. This allows screen reader virtual buffer modes to know which |
1268 | 0 | // descendant is the current one that would get focus if the user navigates |
1269 | 0 | // to the container widget. |
1270 | 0 | Accessible* widget = ContainerWidget(); |
1271 | 0 | if (widget && widget->CurrentItem() == this) |
1272 | 0 | state |= states::ACTIVE; |
1273 | 0 | } |
1274 | 0 |
|
1275 | 0 | if ((state & states::COLLAPSED) || (state & states::EXPANDED)) |
1276 | 0 | state |= states::EXPANDABLE; |
1277 | 0 |
|
1278 | 0 | // For some reasons DOM node may have not a frame. We tract such accessibles |
1279 | 0 | // as invisible. |
1280 | 0 | nsIFrame *frame = GetFrame(); |
1281 | 0 | if (!frame) |
1282 | 0 | return state; |
1283 | 0 | |
1284 | 0 | if (frame->StyleEffects()->mOpacity == 1.0f && |
1285 | 0 | !(state & states::INVISIBLE)) { |
1286 | 0 | state |= states::OPAQUE1; |
1287 | 0 | } |
1288 | 0 |
|
1289 | 0 | return state; |
1290 | 0 | } |
1291 | | |
1292 | | void |
1293 | | Accessible::ApplyARIAState(uint64_t* aState) const |
1294 | 0 | { |
1295 | 0 | if (!mContent->IsElement()) |
1296 | 0 | return; |
1297 | 0 | |
1298 | 0 | dom::Element* element = mContent->AsElement(); |
1299 | 0 |
|
1300 | 0 | // Test for universal states first |
1301 | 0 | *aState |= aria::UniversalStatesFor(element); |
1302 | 0 |
|
1303 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1304 | 0 | if (roleMapEntry) { |
1305 | 0 |
|
1306 | 0 | // We only force the readonly bit off if we have a real mapping for the aria |
1307 | 0 | // role. This preserves the ability for screen readers to use readonly |
1308 | 0 | // (primarily on the document) as the hint for creating a virtual buffer. |
1309 | 0 | if (roleMapEntry->role != roles::NOTHING) |
1310 | 0 | *aState &= ~states::READONLY; |
1311 | 0 |
|
1312 | 0 | if (mContent->HasID()) { |
1313 | 0 | // If has a role & ID and aria-activedescendant on the container, assume |
1314 | 0 | // focusable. |
1315 | 0 | const Accessible* ancestor = this; |
1316 | 0 | while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { |
1317 | 0 | dom::Element* el = ancestor->Elm(); |
1318 | 0 | if (el && |
1319 | 0 | el->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant)) { |
1320 | 0 | *aState |= states::FOCUSABLE; |
1321 | 0 | break; |
1322 | 0 | } |
1323 | 0 | } |
1324 | 0 | } |
1325 | 0 | } |
1326 | 0 |
|
1327 | 0 | if (*aState & states::FOCUSABLE) { |
1328 | 0 | // Propogate aria-disabled from ancestors down to any focusable descendant. |
1329 | 0 | const Accessible* ancestor = this; |
1330 | 0 | while ((ancestor = ancestor->Parent()) && !ancestor->IsDoc()) { |
1331 | 0 | dom::Element* el = ancestor->Elm(); |
1332 | 0 | if (el && el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled, |
1333 | 0 | nsGkAtoms::_true, eCaseMatters)) { |
1334 | 0 | *aState |= states::UNAVAILABLE; |
1335 | 0 | break; |
1336 | 0 | } |
1337 | 0 | } |
1338 | 0 | } |
1339 | 0 |
|
1340 | 0 | // special case: A native button element whose role got transformed by ARIA to a toggle button |
1341 | 0 | // Also applies to togglable button menus, like in the Dev Tools Web Console. |
1342 | 0 | if (IsButton() || IsMenuButton()) |
1343 | 0 | aria::MapToState(aria::eARIAPressed, element, aState); |
1344 | 0 |
|
1345 | 0 | if (!roleMapEntry) |
1346 | 0 | return; |
1347 | 0 | |
1348 | 0 | *aState |= roleMapEntry->state; |
1349 | 0 |
|
1350 | 0 | if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) && |
1351 | 0 | aria::MapToState(roleMapEntry->attributeMap2, element, aState) && |
1352 | 0 | aria::MapToState(roleMapEntry->attributeMap3, element, aState)) |
1353 | 0 | aria::MapToState(roleMapEntry->attributeMap4, element, aState); |
1354 | 0 |
|
1355 | 0 | // ARIA gridcell inherits editable/readonly states from the grid until it's |
1356 | 0 | // overridden. |
1357 | 0 | if ((roleMapEntry->Is(nsGkAtoms::gridcell) || |
1358 | 0 | roleMapEntry->Is(nsGkAtoms::columnheader) || |
1359 | 0 | roleMapEntry->Is(nsGkAtoms::rowheader)) && |
1360 | 0 | !(*aState & (states::READONLY | states::EDITABLE))) { |
1361 | 0 | const TableCellAccessible* cell = AsTableCell(); |
1362 | 0 | if (cell) { |
1363 | 0 | TableAccessible* table = cell->Table(); |
1364 | 0 | if (table) { |
1365 | 0 | Accessible* grid = table->AsAccessible(); |
1366 | 0 | uint64_t gridState = 0; |
1367 | 0 | grid->ApplyARIAState(&gridState); |
1368 | 0 | *aState |= (gridState & (states::READONLY | states::EDITABLE)); |
1369 | 0 | } |
1370 | 0 | } |
1371 | 0 | } |
1372 | 0 | } |
1373 | | |
1374 | | void |
1375 | | Accessible::Value(nsString& aValue) const |
1376 | 0 | { |
1377 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1378 | 0 |
|
1379 | 0 | if ((roleMapEntry && roleMapEntry->valueRule != eNoValue) || |
1380 | 0 | // Bug 1475376: aria-valuetext should also be supported for implicit ARIA |
1381 | 0 | // roles; e.g. <input type="range">. |
1382 | 0 | HasNumericValue()) { |
1383 | 0 | // aria-valuenow is a number, and aria-valuetext is the optional text |
1384 | 0 | // equivalent. For the string value, we will try the optional text |
1385 | 0 | // equivalent first. |
1386 | 0 | if (!mContent->IsElement()) { |
1387 | 0 | return; |
1388 | 0 | } |
1389 | 0 | |
1390 | 0 | if (!mContent->AsElement()->GetAttr(kNameSpaceID_None, |
1391 | 0 | nsGkAtoms::aria_valuetext, aValue)) { |
1392 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, |
1393 | 0 | aValue); |
1394 | 0 | } |
1395 | 0 | return; |
1396 | 0 | } |
1397 | 0 |
|
1398 | 0 | if (!roleMapEntry) { |
1399 | 0 | return; |
1400 | 0 | } |
1401 | 0 | |
1402 | 0 | // Value of textbox is a textified subtree. |
1403 | 0 | if (roleMapEntry->Is(nsGkAtoms::textbox)) { |
1404 | 0 | nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); |
1405 | 0 | return; |
1406 | 0 | } |
1407 | 0 | |
1408 | 0 | // Value of combobox is a text of current or selected item. |
1409 | 0 | if (roleMapEntry->Is(nsGkAtoms::combobox)) { |
1410 | 0 | Accessible* option = CurrentItem(); |
1411 | 0 | if (!option) { |
1412 | 0 | uint32_t childCount = ChildCount(); |
1413 | 0 | for (uint32_t idx = 0; idx < childCount; idx++) { |
1414 | 0 | Accessible* child = mChildren.ElementAt(idx); |
1415 | 0 | if (child->IsListControl()) { |
1416 | 0 | option = child->GetSelectedItem(0); |
1417 | 0 | break; |
1418 | 0 | } |
1419 | 0 | } |
1420 | 0 | } |
1421 | 0 |
|
1422 | 0 | if (option) |
1423 | 0 | nsTextEquivUtils::GetTextEquivFromSubtree(option, aValue); |
1424 | 0 | } |
1425 | 0 | } |
1426 | | |
1427 | | double |
1428 | | Accessible::MaxValue() const |
1429 | 0 | { |
1430 | 0 | return AttrNumericValue(nsGkAtoms::aria_valuemax); |
1431 | 0 | } |
1432 | | |
1433 | | double |
1434 | | Accessible::MinValue() const |
1435 | 0 | { |
1436 | 0 | return AttrNumericValue(nsGkAtoms::aria_valuemin); |
1437 | 0 | } |
1438 | | |
1439 | | double |
1440 | | Accessible::Step() const |
1441 | 0 | { |
1442 | 0 | return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA. |
1443 | 0 | } |
1444 | | |
1445 | | double |
1446 | | Accessible::CurValue() const |
1447 | 0 | { |
1448 | 0 | return AttrNumericValue(nsGkAtoms::aria_valuenow); |
1449 | 0 | } |
1450 | | |
1451 | | bool |
1452 | | Accessible::SetCurValue(double aValue) |
1453 | 0 | { |
1454 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1455 | 0 | if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) |
1456 | 0 | return false; |
1457 | 0 | |
1458 | 0 | const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE; |
1459 | 0 | if (State() & kValueCannotChange) |
1460 | 0 | return false; |
1461 | 0 | |
1462 | 0 | double checkValue = MinValue(); |
1463 | 0 | if (!IsNaN(checkValue) && aValue < checkValue) |
1464 | 0 | return false; |
1465 | 0 | |
1466 | 0 | checkValue = MaxValue(); |
1467 | 0 | if (!IsNaN(checkValue) && aValue > checkValue) |
1468 | 0 | return false; |
1469 | 0 | |
1470 | 0 | nsAutoString strValue; |
1471 | 0 | strValue.AppendFloat(aValue); |
1472 | 0 |
|
1473 | 0 | if (!mContent->IsElement()) |
1474 | 0 | return true; |
1475 | 0 | |
1476 | 0 | return NS_SUCCEEDED( |
1477 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow, |
1478 | 0 | strValue, true)); |
1479 | 0 | } |
1480 | | |
1481 | | role |
1482 | | Accessible::ARIATransformRole(role aRole) const |
1483 | 0 | { |
1484 | 0 | // Beginning with ARIA 1.1, user agents are expected to use the native host |
1485 | 0 | // language role of the element when the region role is used without a name. |
1486 | 0 | // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-region |
1487 | 0 | // |
1488 | 0 | // XXX: While the name computation algorithm can be non-trivial in the general |
1489 | 0 | // case, it should not be especially bad here: If the author hasn't used the |
1490 | 0 | // region role, this calculation won't occur. And the region role's name |
1491 | 0 | // calculation rule excludes name from content. That said, this use case is |
1492 | 0 | // another example of why we should consider caching the accessible name. See: |
1493 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235. |
1494 | 0 | if (aRole == roles::REGION) { |
1495 | 0 | nsAutoString name; |
1496 | 0 | Name(name); |
1497 | 0 | return name.IsEmpty() ? NativeRole() : aRole; |
1498 | 0 | } |
1499 | 0 |
|
1500 | 0 | // XXX: these unfortunate exceptions don't fit into the ARIA table. This is |
1501 | 0 | // where the accessible role depends on both the role and ARIA state. |
1502 | 0 | if (aRole == roles::PUSHBUTTON) { |
1503 | 0 | if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) { |
1504 | 0 | // For simplicity, any existing pressed attribute except "" or "undefined" |
1505 | 0 | // indicates a toggle. |
1506 | 0 | return roles::TOGGLE_BUTTON; |
1507 | 0 | } |
1508 | 0 | |
1509 | 0 | if (mContent->IsElement() && |
1510 | 0 | mContent->AsElement()->AttrValueIs(kNameSpaceID_None, |
1511 | 0 | nsGkAtoms::aria_haspopup, |
1512 | 0 | nsGkAtoms::_true, |
1513 | 0 | eCaseMatters)) { |
1514 | 0 | // For button with aria-haspopup="true". |
1515 | 0 | return roles::BUTTONMENU; |
1516 | 0 | } |
1517 | 0 | |
1518 | 0 | } else if (aRole == roles::LISTBOX) { |
1519 | 0 | // A listbox inside of a combobox needs a special role because of ATK |
1520 | 0 | // mapping to menu. |
1521 | 0 | if (mParent && mParent->IsCombobox()) { |
1522 | 0 | return roles::COMBOBOX_LIST; |
1523 | 0 | } else { |
1524 | 0 | // Listbox is owned by a combobox |
1525 | 0 | Relation rel = RelationByType(RelationType::NODE_CHILD_OF); |
1526 | 0 | Accessible* targetAcc = nullptr; |
1527 | 0 | while ((targetAcc = rel.Next())) |
1528 | 0 | if (targetAcc->IsCombobox()) |
1529 | 0 | return roles::COMBOBOX_LIST; |
1530 | 0 | } |
1531 | 0 |
|
1532 | 0 | } else if (aRole == roles::OPTION) { |
1533 | 0 | if (mParent && mParent->Role() == roles::COMBOBOX_LIST) |
1534 | 0 | return roles::COMBOBOX_OPTION; |
1535 | 0 | |
1536 | 0 | } else if (aRole == roles::MENUITEM) { |
1537 | 0 | // Menuitem has a submenu. |
1538 | 0 | if (mContent->IsElement() && |
1539 | 0 | mContent->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_haspopup, |
1540 | 0 | nsGkAtoms::_true, eCaseMatters)) { |
1541 | 0 | return roles::PARENT_MENUITEM; |
1542 | 0 | } |
1543 | 0 | } |
1544 | 0 | |
1545 | 0 | return aRole; |
1546 | 0 | } |
1547 | | |
1548 | | nsAtom* |
1549 | | Accessible::LandmarkRole() const |
1550 | 0 | { |
1551 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1552 | 0 | return roleMapEntry && roleMapEntry->IsOfType(eLandmark) ? |
1553 | 0 | *(roleMapEntry->roleAtom) : nullptr; |
1554 | 0 | } |
1555 | | |
1556 | | role |
1557 | | Accessible::NativeRole() const |
1558 | 0 | { |
1559 | 0 | return roles::NOTHING; |
1560 | 0 | } |
1561 | | |
1562 | | uint8_t |
1563 | | Accessible::ActionCount() const |
1564 | 0 | { |
1565 | 0 | return GetActionRule() == eNoAction ? 0 : 1; |
1566 | 0 | } |
1567 | | |
1568 | | void |
1569 | | Accessible::ActionNameAt(uint8_t aIndex, nsAString& aName) |
1570 | 0 | { |
1571 | 0 | aName.Truncate(); |
1572 | 0 |
|
1573 | 0 | if (aIndex != 0) |
1574 | 0 | return; |
1575 | 0 | |
1576 | 0 | uint32_t actionRule = GetActionRule(); |
1577 | 0 |
|
1578 | 0 | switch (actionRule) { |
1579 | 0 | case eActivateAction: |
1580 | 0 | aName.AssignLiteral("activate"); |
1581 | 0 | return; |
1582 | 0 |
|
1583 | 0 | case eClickAction: |
1584 | 0 | aName.AssignLiteral("click"); |
1585 | 0 | return; |
1586 | 0 |
|
1587 | 0 | case ePressAction: |
1588 | 0 | aName.AssignLiteral("press"); |
1589 | 0 | return; |
1590 | 0 |
|
1591 | 0 | case eCheckUncheckAction: |
1592 | 0 | { |
1593 | 0 | uint64_t state = State(); |
1594 | 0 | if (state & states::CHECKED) |
1595 | 0 | aName.AssignLiteral("uncheck"); |
1596 | 0 | else if (state & states::MIXED) |
1597 | 0 | aName.AssignLiteral("cycle"); |
1598 | 0 | else |
1599 | 0 | aName.AssignLiteral("check"); |
1600 | 0 | return; |
1601 | 0 | } |
1602 | 0 |
|
1603 | 0 | case eJumpAction: |
1604 | 0 | aName.AssignLiteral("jump"); |
1605 | 0 | return; |
1606 | 0 |
|
1607 | 0 | case eOpenCloseAction: |
1608 | 0 | if (State() & states::COLLAPSED) |
1609 | 0 | aName.AssignLiteral("open"); |
1610 | 0 | else |
1611 | 0 | aName.AssignLiteral("close"); |
1612 | 0 | return; |
1613 | 0 |
|
1614 | 0 | case eSelectAction: |
1615 | 0 | aName.AssignLiteral("select"); |
1616 | 0 | return; |
1617 | 0 |
|
1618 | 0 | case eSwitchAction: |
1619 | 0 | aName.AssignLiteral("switch"); |
1620 | 0 | return; |
1621 | 0 |
|
1622 | 0 | case eSortAction: |
1623 | 0 | aName.AssignLiteral("sort"); |
1624 | 0 | return; |
1625 | 0 |
|
1626 | 0 | case eExpandAction: |
1627 | 0 | if (State() & states::COLLAPSED) |
1628 | 0 | aName.AssignLiteral("expand"); |
1629 | 0 | else |
1630 | 0 | aName.AssignLiteral("collapse"); |
1631 | 0 | return; |
1632 | 0 | } |
1633 | 0 | } |
1634 | | |
1635 | | bool |
1636 | | Accessible::DoAction(uint8_t aIndex) const |
1637 | 0 | { |
1638 | 0 | if (aIndex != 0) |
1639 | 0 | return false; |
1640 | 0 | |
1641 | 0 | if (GetActionRule() != eNoAction) { |
1642 | 0 | DoCommand(); |
1643 | 0 | return true; |
1644 | 0 | } |
1645 | 0 | |
1646 | 0 | return false; |
1647 | 0 | } |
1648 | | |
1649 | | nsIContent* |
1650 | | Accessible::GetAtomicRegion() const |
1651 | 0 | { |
1652 | 0 | nsIContent *loopContent = mContent; |
1653 | 0 | nsAutoString atomic; |
1654 | 0 | while (loopContent && |
1655 | 0 | (!loopContent->IsElement() || |
1656 | 0 | !loopContent->AsElement()->GetAttr(kNameSpaceID_None, |
1657 | 0 | nsGkAtoms::aria_atomic, atomic))) |
1658 | 0 | loopContent = loopContent->GetParent(); |
1659 | 0 |
|
1660 | 0 | return atomic.EqualsLiteral("true") ? loopContent : nullptr; |
1661 | 0 | } |
1662 | | |
1663 | | Relation |
1664 | | Accessible::RelationByType(RelationType aType) const |
1665 | 0 | { |
1666 | 0 | if (!HasOwnContent()) |
1667 | 0 | return Relation(); |
1668 | 0 | |
1669 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1670 | 0 |
|
1671 | 0 | // Relationships are defined on the same content node that the role would be |
1672 | 0 | // defined on. |
1673 | 0 | switch (aType) { |
1674 | 0 | case RelationType::LABELLED_BY: { |
1675 | 0 | Relation rel(new IDRefsIterator(mDoc, mContent, |
1676 | 0 | nsGkAtoms::aria_labelledby)); |
1677 | 0 | if (mContent->IsHTMLElement()) { |
1678 | 0 | rel.AppendIter(new HTMLLabelIterator(Document(), this)); |
1679 | 0 | } else if (mContent->IsXULElement()) { |
1680 | 0 | rel.AppendIter(new XULLabelIterator(Document(), mContent)); |
1681 | 0 | } |
1682 | 0 |
|
1683 | 0 | return rel; |
1684 | 0 | } |
1685 | 0 |
|
1686 | 0 | case RelationType::LABEL_FOR: { |
1687 | 0 | Relation rel(new RelatedAccIterator(Document(), mContent, |
1688 | 0 | nsGkAtoms::aria_labelledby)); |
1689 | 0 | if (mContent->IsXULElement(nsGkAtoms::label)) |
1690 | 0 | rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control)); |
1691 | 0 |
|
1692 | 0 | return rel; |
1693 | 0 | } |
1694 | 0 |
|
1695 | 0 | case RelationType::DESCRIBED_BY: { |
1696 | 0 | Relation rel(new IDRefsIterator(mDoc, mContent, |
1697 | 0 | nsGkAtoms::aria_describedby)); |
1698 | 0 | if (mContent->IsXULElement()) |
1699 | 0 | rel.AppendIter(new XULDescriptionIterator(Document(), mContent)); |
1700 | 0 |
|
1701 | 0 | return rel; |
1702 | 0 | } |
1703 | 0 |
|
1704 | 0 | case RelationType::DESCRIPTION_FOR: { |
1705 | 0 | Relation rel(new RelatedAccIterator(Document(), mContent, |
1706 | 0 | nsGkAtoms::aria_describedby)); |
1707 | 0 |
|
1708 | 0 | // This affectively adds an optional control attribute to xul:description, |
1709 | 0 | // which only affects accessibility, by allowing the description to be |
1710 | 0 | // tied to a control. |
1711 | 0 | if (mContent->IsXULElement(nsGkAtoms::description)) |
1712 | 0 | rel.AppendIter(new IDRefsIterator(mDoc, mContent, |
1713 | 0 | nsGkAtoms::control)); |
1714 | 0 |
|
1715 | 0 | return rel; |
1716 | 0 | } |
1717 | 0 |
|
1718 | 0 | case RelationType::NODE_CHILD_OF: { |
1719 | 0 | Relation rel; |
1720 | 0 | // This is an ARIA tree or treegrid that doesn't use owns, so we need to |
1721 | 0 | // get the parent the hard way. |
1722 | 0 | if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || |
1723 | 0 | roleMapEntry->role == roles::LISTITEM || |
1724 | 0 | roleMapEntry->role == roles::ROW)) { |
1725 | 0 | rel.AppendTarget(GetGroupInfo()->ConceptualParent()); |
1726 | 0 | } |
1727 | 0 |
|
1728 | 0 | // If accessible is in its own Window, or is the root of a document, |
1729 | 0 | // then we should provide NODE_CHILD_OF relation so that MSAA clients |
1730 | 0 | // can easily get to true parent instead of getting to oleacc's |
1731 | 0 | // ROLE_WINDOW accessible which will prevent us from going up further |
1732 | 0 | // (because it is system generated and has no idea about the hierarchy |
1733 | 0 | // above it). |
1734 | 0 | nsIFrame *frame = GetFrame(); |
1735 | 0 | if (frame) { |
1736 | 0 | nsView *view = frame->GetView(); |
1737 | 0 | if (view) { |
1738 | 0 | nsIScrollableFrame *scrollFrame = do_QueryFrame(frame); |
1739 | 0 | if (scrollFrame || view->GetWidget() || !frame->GetParent()) |
1740 | 0 | rel.AppendTarget(Parent()); |
1741 | 0 | } |
1742 | 0 | } |
1743 | 0 |
|
1744 | 0 | return rel; |
1745 | 0 | } |
1746 | 0 |
|
1747 | 0 | case RelationType::NODE_PARENT_OF: { |
1748 | 0 | // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees |
1749 | 0 | // also can be organized by groups. |
1750 | 0 | if (roleMapEntry && |
1751 | 0 | (roleMapEntry->role == roles::OUTLINEITEM || |
1752 | 0 | roleMapEntry->role == roles::LISTITEM || |
1753 | 0 | roleMapEntry->role == roles::ROW || |
1754 | 0 | roleMapEntry->role == roles::OUTLINE || |
1755 | 0 | roleMapEntry->role == roles::LIST || |
1756 | 0 | roleMapEntry->role == roles::TREE_TABLE)) { |
1757 | 0 | return Relation(new ItemIterator(this)); |
1758 | 0 | } |
1759 | 0 | |
1760 | 0 | return Relation(); |
1761 | 0 | } |
1762 | 0 |
|
1763 | 0 | case RelationType::CONTROLLED_BY: |
1764 | 0 | return Relation(new RelatedAccIterator(Document(), mContent, |
1765 | 0 | nsGkAtoms::aria_controls)); |
1766 | 0 |
|
1767 | 0 | case RelationType::CONTROLLER_FOR: { |
1768 | 0 | Relation rel(new IDRefsIterator(mDoc, mContent, |
1769 | 0 | nsGkAtoms::aria_controls)); |
1770 | 0 | rel.AppendIter(new HTMLOutputIterator(Document(), mContent)); |
1771 | 0 | return rel; |
1772 | 0 | } |
1773 | 0 |
|
1774 | 0 | case RelationType::FLOWS_TO: |
1775 | 0 | return Relation(new IDRefsIterator(mDoc, mContent, |
1776 | 0 | nsGkAtoms::aria_flowto)); |
1777 | 0 |
|
1778 | 0 | case RelationType::FLOWS_FROM: |
1779 | 0 | return Relation(new RelatedAccIterator(Document(), mContent, |
1780 | 0 | nsGkAtoms::aria_flowto)); |
1781 | 0 |
|
1782 | 0 | case RelationType::MEMBER_OF: |
1783 | 0 | return Relation(mDoc, GetAtomicRegion()); |
1784 | 0 |
|
1785 | 0 | case RelationType::SUBWINDOW_OF: |
1786 | 0 | case RelationType::EMBEDS: |
1787 | 0 | case RelationType::EMBEDDED_BY: |
1788 | 0 | case RelationType::POPUP_FOR: |
1789 | 0 | case RelationType::PARENT_WINDOW_OF: |
1790 | 0 | return Relation(); |
1791 | 0 |
|
1792 | 0 | case RelationType::DEFAULT_BUTTON: { |
1793 | 0 | if (mContent->IsHTMLElement()) { |
1794 | 0 | // HTML form controls implements nsIFormControl interface. |
1795 | 0 | nsCOMPtr<nsIFormControl> control(do_QueryInterface(mContent)); |
1796 | 0 | if (control) { |
1797 | 0 | nsCOMPtr<nsIForm> form(do_QueryInterface(control->GetFormElement())); |
1798 | 0 | if (form) { |
1799 | 0 | nsCOMPtr<nsIContent> formContent = |
1800 | 0 | do_QueryInterface(form->GetDefaultSubmitElement()); |
1801 | 0 | return Relation(mDoc, formContent); |
1802 | 0 | } |
1803 | 0 | } |
1804 | 0 | } else { |
1805 | 0 | // In XUL, use first <button default="true" .../> in the document |
1806 | 0 | nsIDocument* doc = mContent->OwnerDoc(); |
1807 | 0 | nsCOMPtr<nsIDOMXULButtonElement> buttonEl; |
1808 | 0 | if (doc->IsXULDocument()) { |
1809 | 0 | dom::XULDocument* xulDoc = doc->AsXULDocument(); |
1810 | 0 | nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons = |
1811 | 0 | xulDoc->GetElementsByAttribute(NS_LITERAL_STRING("default"), |
1812 | 0 | NS_LITERAL_STRING("true")); |
1813 | 0 | if (possibleDefaultButtons) { |
1814 | 0 | uint32_t length = possibleDefaultButtons->Length(); |
1815 | 0 | // Check for button in list of default="true" elements |
1816 | 0 | for (uint32_t count = 0; count < length && !buttonEl; count ++) { |
1817 | 0 | buttonEl = do_QueryInterface(possibleDefaultButtons->Item(count)); |
1818 | 0 | } |
1819 | 0 | } |
1820 | 0 | if (!buttonEl) { // Check for anonymous accept button in <dialog> |
1821 | 0 | dom::Element* rootElm = mContent->OwnerDoc()->GetRootElement(); |
1822 | 0 | if (rootElm) { |
1823 | 0 | nsIContent* possibleButtonEl = rootElm->OwnerDoc()-> |
1824 | 0 | GetAnonymousElementByAttribute(rootElm, nsGkAtoms::_default, |
1825 | 0 | NS_LITERAL_STRING("true")); |
1826 | 0 | buttonEl = do_QueryInterface(possibleButtonEl); |
1827 | 0 | } |
1828 | 0 | } |
1829 | 0 | nsCOMPtr<nsIContent> relatedContent(do_QueryInterface(buttonEl)); |
1830 | 0 | return Relation(mDoc, relatedContent); |
1831 | 0 | } |
1832 | 0 | } |
1833 | 0 | return Relation(); |
1834 | 0 | } |
1835 | 0 |
|
1836 | 0 | case RelationType::CONTAINING_DOCUMENT: |
1837 | 0 | return Relation(mDoc); |
1838 | 0 |
|
1839 | 0 | case RelationType::CONTAINING_TAB_PANE: { |
1840 | 0 | nsCOMPtr<nsIDocShell> docShell = |
1841 | 0 | nsCoreUtils::GetDocShellFor(GetNode()); |
1842 | 0 | if (docShell) { |
1843 | 0 | // Walk up the parent chain without crossing the boundary at which item |
1844 | 0 | // types change, preventing us from walking up out of tab content. |
1845 | 0 | nsCOMPtr<nsIDocShellTreeItem> root; |
1846 | 0 | docShell->GetSameTypeRootTreeItem(getter_AddRefs(root)); |
1847 | 0 | if (root) { |
1848 | 0 | // If the item type is typeContent, we assume we are in browser tab |
1849 | 0 | // content. Note, this includes content such as about:addons, |
1850 | 0 | // for consistency. |
1851 | 0 | if (root->ItemType() == nsIDocShellTreeItem::typeContent) { |
1852 | 0 | return Relation(nsAccUtils::GetDocAccessibleFor(root)); |
1853 | 0 | } |
1854 | 0 | } |
1855 | 0 | } |
1856 | 0 | return Relation(); |
1857 | 0 | } |
1858 | 0 |
|
1859 | 0 | case RelationType::CONTAINING_APPLICATION: |
1860 | 0 | return Relation(ApplicationAcc()); |
1861 | 0 |
|
1862 | 0 | case RelationType::DETAILS: |
1863 | 0 | return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details)); |
1864 | 0 |
|
1865 | 0 | case RelationType::DETAILS_FOR: |
1866 | 0 | return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details)); |
1867 | 0 |
|
1868 | 0 | case RelationType::ERRORMSG: |
1869 | 0 | return Relation(new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage)); |
1870 | 0 |
|
1871 | 0 | case RelationType::ERRORMSG_FOR: |
1872 | 0 | return Relation(new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage)); |
1873 | 0 |
|
1874 | 0 | default: |
1875 | 0 | return Relation(); |
1876 | 0 | } |
1877 | 0 | } |
1878 | | |
1879 | | void |
1880 | | Accessible::GetNativeInterface(void** aNativeAccessible) |
1881 | 0 | { |
1882 | 0 | } |
1883 | | |
1884 | | void |
1885 | | Accessible::DoCommand(nsIContent* aContent, uint32_t aActionIndex) const |
1886 | | { |
1887 | | class Runnable final : public mozilla::Runnable |
1888 | | { |
1889 | | public: |
1890 | | Runnable(const Accessible* aAcc, nsIContent* aContent, uint32_t aIdx) |
1891 | | : mozilla::Runnable("Runnable") |
1892 | | , mAcc(aAcc) |
1893 | | , mContent(aContent) |
1894 | | , mIdx(aIdx) |
1895 | 0 | { |
1896 | 0 | } |
1897 | | |
1898 | | NS_IMETHOD Run() override |
1899 | 0 | { |
1900 | 0 | if (mAcc) |
1901 | 0 | mAcc->DispatchClickEvent(mContent, mIdx); |
1902 | 0 |
|
1903 | 0 | return NS_OK; |
1904 | 0 | } |
1905 | | |
1906 | | void Revoke() |
1907 | 0 | { |
1908 | 0 | mAcc = nullptr; |
1909 | 0 | mContent = nullptr; |
1910 | 0 | } |
1911 | | |
1912 | | private: |
1913 | | RefPtr<const Accessible> mAcc; |
1914 | | nsCOMPtr<nsIContent> mContent; |
1915 | | uint32_t mIdx; |
1916 | | }; |
1917 | | |
1918 | | nsIContent* content = aContent ? aContent : mContent.get(); |
1919 | | nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex); |
1920 | | NS_DispatchToMainThread(runnable); |
1921 | | } |
1922 | | |
1923 | | void |
1924 | | Accessible::DispatchClickEvent(nsIContent *aContent, uint32_t aActionIndex) const |
1925 | 0 | { |
1926 | 0 | if (IsDefunct()) |
1927 | 0 | return; |
1928 | 0 | |
1929 | 0 | nsCOMPtr<nsIPresShell> presShell = mDoc->PresShell(); |
1930 | 0 |
|
1931 | 0 | // Scroll into view. |
1932 | 0 | presShell->ScrollContentIntoView(aContent, |
1933 | 0 | nsIPresShell::ScrollAxis(), |
1934 | 0 | nsIPresShell::ScrollAxis(), |
1935 | 0 | nsIPresShell::SCROLL_OVERFLOW_HIDDEN); |
1936 | 0 |
|
1937 | 0 | AutoWeakFrame frame = aContent->GetPrimaryFrame(); |
1938 | 0 | if (!frame) |
1939 | 0 | return; |
1940 | 0 | |
1941 | 0 | // Compute x and y coordinates. |
1942 | 0 | nsPoint point; |
1943 | 0 | nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point); |
1944 | 0 | if (!widget) |
1945 | 0 | return; |
1946 | 0 | |
1947 | 0 | nsSize size = frame->GetSize(); |
1948 | 0 |
|
1949 | 0 | RefPtr<nsPresContext> presContext = presShell->GetPresContext(); |
1950 | 0 | int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2); |
1951 | 0 | int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2); |
1952 | 0 |
|
1953 | 0 | // Simulate a touch interaction by dispatching touch events with mouse events. |
1954 | 0 | nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame, |
1955 | 0 | presShell, widget); |
1956 | 0 | nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame, |
1957 | 0 | presShell, widget); |
1958 | 0 | nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame, |
1959 | 0 | presShell, widget); |
1960 | 0 | nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame, |
1961 | 0 | presShell, widget); |
1962 | 0 | } |
1963 | | |
1964 | | void |
1965 | | Accessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, int32_t aY) |
1966 | 0 | { |
1967 | 0 | nsIFrame* frame = GetFrame(); |
1968 | 0 | if (!frame) |
1969 | 0 | return; |
1970 | 0 | |
1971 | 0 | nsIntPoint coords = |
1972 | 0 | nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this); |
1973 | 0 |
|
1974 | 0 | nsIFrame* parentFrame = frame; |
1975 | 0 | while ((parentFrame = parentFrame->GetParent())) |
1976 | 0 | nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); |
1977 | 0 | } |
1978 | | |
1979 | | void |
1980 | | Accessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, |
1981 | | uint32_t aLength) |
1982 | 0 | { |
1983 | 0 | // Return text representation of non-text accessible within hypertext |
1984 | 0 | // accessible. Text accessible overrides this method to return enclosed text. |
1985 | 0 | if (aStartOffset != 0 || aLength == 0) |
1986 | 0 | return; |
1987 | 0 | |
1988 | 0 | nsIFrame *frame = GetFrame(); |
1989 | 0 | if (!frame) { |
1990 | 0 | if (mContent->IsElement() && mContent->AsElement()->IsDisplayContents()) { |
1991 | 0 | aText += kEmbeddedObjectChar; |
1992 | 0 | } |
1993 | 0 | return; |
1994 | 0 | } |
1995 | 0 |
|
1996 | 0 | MOZ_ASSERT(mParent, |
1997 | 0 | "Called on accessible unbound from tree. Result can be wrong."); |
1998 | 0 |
|
1999 | 0 | if (frame->IsBrFrame()) { |
2000 | 0 | aText += kForcedNewLineChar; |
2001 | 0 | } else if (mParent && nsAccUtils::MustPrune(mParent)) { |
2002 | 0 | // Expose the embedded object accessible as imaginary embedded object |
2003 | 0 | // character if its parent hypertext accessible doesn't expose children to |
2004 | 0 | // AT. |
2005 | 0 | aText += kImaginaryEmbeddedObjectChar; |
2006 | 0 | } else { |
2007 | 0 | aText += kEmbeddedObjectChar; |
2008 | 0 | } |
2009 | 0 | } |
2010 | | |
2011 | | void |
2012 | | Accessible::Shutdown() |
2013 | 0 | { |
2014 | 0 | // Mark the accessible as defunct, invalidate the child count and pointers to |
2015 | 0 | // other accessibles, also make sure none of its children point to this parent |
2016 | 0 | mStateFlags |= eIsDefunct; |
2017 | 0 |
|
2018 | 0 | int32_t childCount = mChildren.Length(); |
2019 | 0 | for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { |
2020 | 0 | mChildren.ElementAt(childIdx)->UnbindFromParent(); |
2021 | 0 | } |
2022 | 0 | mChildren.Clear(); |
2023 | 0 |
|
2024 | 0 | mEmbeddedObjCollector = nullptr; |
2025 | 0 |
|
2026 | 0 | if (mParent) |
2027 | 0 | mParent->RemoveChild(this); |
2028 | 0 |
|
2029 | 0 | mContent = nullptr; |
2030 | 0 | mDoc = nullptr; |
2031 | 0 | if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) |
2032 | 0 | SelectionMgr()->ResetCaretOffset(); |
2033 | 0 | } |
2034 | | |
2035 | | // Accessible protected |
2036 | | void |
2037 | | Accessible::ARIAName(nsString& aName) const |
2038 | 0 | { |
2039 | 0 | // aria-labelledby now takes precedence over aria-label |
2040 | 0 | nsresult rv = nsTextEquivUtils:: |
2041 | 0 | GetTextEquivFromIDRefs(this, nsGkAtoms::aria_labelledby, aName); |
2042 | 0 | if (NS_SUCCEEDED(rv)) { |
2043 | 0 | aName.CompressWhitespace(); |
2044 | 0 | } |
2045 | 0 |
|
2046 | 0 | if (aName.IsEmpty() && |
2047 | 0 | mContent->IsElement() && |
2048 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_label, |
2049 | 0 | aName)) { |
2050 | 0 | aName.CompressWhitespace(); |
2051 | 0 | } |
2052 | 0 | } |
2053 | | |
2054 | | // Accessible protected |
2055 | | ENameValueFlag |
2056 | | Accessible::NativeName(nsString& aName) const |
2057 | 0 | { |
2058 | 0 | if (mContent->IsHTMLElement()) { |
2059 | 0 | Accessible* label = nullptr; |
2060 | 0 | HTMLLabelIterator iter(Document(), this); |
2061 | 0 | while ((label = iter.Next())) { |
2062 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(), |
2063 | 0 | &aName); |
2064 | 0 | aName.CompressWhitespace(); |
2065 | 0 | } |
2066 | 0 |
|
2067 | 0 | if (!aName.IsEmpty()) |
2068 | 0 | return eNameOK; |
2069 | 0 | |
2070 | 0 | nsTextEquivUtils::GetNameFromSubtree(this, aName); |
2071 | 0 | return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
2072 | 0 | } |
2073 | 0 |
|
2074 | 0 | if (mContent->IsXULElement()) { |
2075 | 0 | XULElmName(mDoc, mContent, aName); |
2076 | 0 | if (!aName.IsEmpty()) |
2077 | 0 | return eNameOK; |
2078 | 0 | |
2079 | 0 | nsTextEquivUtils::GetNameFromSubtree(this, aName); |
2080 | 0 | return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
2081 | 0 | } |
2082 | 0 |
|
2083 | 0 | if (mContent->IsSVGElement()) { |
2084 | 0 | // If user agents need to choose among multiple ‘desc’ or ‘title’ elements |
2085 | 0 | // for processing, the user agent shall choose the first one. |
2086 | 0 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
2087 | 0 | childElm = childElm->GetNextSibling()) { |
2088 | 0 | if (childElm->IsSVGElement(nsGkAtoms::title)) { |
2089 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
2090 | 0 | return eNameOK; |
2091 | 0 | } |
2092 | 0 | } |
2093 | 0 | } |
2094 | 0 |
|
2095 | 0 | return eNameOK; |
2096 | 0 | } |
2097 | | |
2098 | | // Accessible protected |
2099 | | void |
2100 | | Accessible::NativeDescription(nsString& aDescription) |
2101 | 0 | { |
2102 | 0 | bool isXUL = mContent->IsXULElement(); |
2103 | 0 | if (isXUL) { |
2104 | 0 | // Try XUL <description control="[id]">description text</description> |
2105 | 0 | XULDescriptionIterator iter(Document(), mContent); |
2106 | 0 | Accessible* descr = nullptr; |
2107 | 0 | while ((descr = iter.Next())) { |
2108 | 0 | nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(), |
2109 | 0 | &aDescription); |
2110 | 0 | } |
2111 | 0 | } |
2112 | 0 | } |
2113 | | |
2114 | | // Accessible protected |
2115 | | void |
2116 | | Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) |
2117 | 0 | { |
2118 | 0 | MOZ_ASSERT(aParent, "This method isn't used to set null parent"); |
2119 | 0 | MOZ_ASSERT(!mParent, "The child was expected to be moved"); |
2120 | 0 |
|
2121 | 0 | #ifdef A11Y_LOG |
2122 | 0 | if (mParent) { |
2123 | 0 | logging::TreeInfo("BindToParent: stealing accessible", 0, |
2124 | 0 | "old parent", mParent, |
2125 | 0 | "new parent", aParent, |
2126 | 0 | "child", this, nullptr); |
2127 | 0 | } |
2128 | 0 | #endif |
2129 | 0 |
|
2130 | 0 | mParent = aParent; |
2131 | 0 | mIndexInParent = aIndexInParent; |
2132 | 0 |
|
2133 | 0 | // Note: this is currently only used for richlistitems and their children. |
2134 | 0 | if (mParent->HasNameDependentParent() || mParent->IsXULListItem()) |
2135 | 0 | mContextFlags |= eHasNameDependentParent; |
2136 | 0 | else |
2137 | 0 | mContextFlags &= ~eHasNameDependentParent; |
2138 | 0 |
|
2139 | 0 | mContextFlags |= |
2140 | 0 | static_cast<uint32_t>((mParent->IsAlert() || |
2141 | 0 | mParent->IsInsideAlert())) & eInsideAlert; |
2142 | 0 | } |
2143 | | |
2144 | | // Accessible protected |
2145 | | void |
2146 | | Accessible::UnbindFromParent() |
2147 | 0 | { |
2148 | 0 | mParent = nullptr; |
2149 | 0 | mIndexInParent = -1; |
2150 | 0 | mInt.mIndexOfEmbeddedChild = -1; |
2151 | 0 | if (IsProxy()) |
2152 | 0 | MOZ_CRASH("this should never be called on proxy wrappers"); |
2153 | 0 |
|
2154 | 0 | delete mBits.groupInfo; |
2155 | 0 | mBits.groupInfo = nullptr; |
2156 | 0 | mContextFlags &= ~eHasNameDependentParent & ~eInsideAlert; |
2157 | 0 | } |
2158 | | |
2159 | | //////////////////////////////////////////////////////////////////////////////// |
2160 | | // Accessible public methods |
2161 | | |
2162 | | RootAccessible* |
2163 | | Accessible::RootAccessible() const |
2164 | 0 | { |
2165 | 0 | nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode()); |
2166 | 0 | NS_ASSERTION(docShell, "No docshell for mContent"); |
2167 | 0 | if (!docShell) { |
2168 | 0 | return nullptr; |
2169 | 0 | } |
2170 | 0 | |
2171 | 0 | nsCOMPtr<nsIDocShellTreeItem> root; |
2172 | 0 | docShell->GetRootTreeItem(getter_AddRefs(root)); |
2173 | 0 | NS_ASSERTION(root, "No root content tree item"); |
2174 | 0 | if (!root) { |
2175 | 0 | return nullptr; |
2176 | 0 | } |
2177 | 0 | |
2178 | 0 | DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root); |
2179 | 0 | return docAcc ? docAcc->AsRoot() : nullptr; |
2180 | 0 | } |
2181 | | |
2182 | | nsIFrame* |
2183 | | Accessible::GetFrame() const |
2184 | 0 | { |
2185 | 0 | return mContent ? mContent->GetPrimaryFrame() : nullptr; |
2186 | 0 | } |
2187 | | |
2188 | | nsINode* |
2189 | | Accessible::GetNode() const |
2190 | 0 | { |
2191 | 0 | return mContent; |
2192 | 0 | } |
2193 | | |
2194 | | void |
2195 | | Accessible::Language(nsAString& aLanguage) |
2196 | 0 | { |
2197 | 0 | aLanguage.Truncate(); |
2198 | 0 |
|
2199 | 0 | if (!mDoc) |
2200 | 0 | return; |
2201 | 0 | |
2202 | 0 | nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage); |
2203 | 0 | if (aLanguage.IsEmpty()) { // Nothing found, so use document's language |
2204 | 0 | mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage, |
2205 | 0 | aLanguage); |
2206 | 0 | } |
2207 | 0 | } |
2208 | | |
2209 | | bool |
2210 | | Accessible::InsertChildAt(uint32_t aIndex, Accessible* aChild) |
2211 | 0 | { |
2212 | 0 | if (!aChild) |
2213 | 0 | return false; |
2214 | 0 | |
2215 | 0 | if (aIndex == mChildren.Length()) { |
2216 | 0 | if (!mChildren.AppendElement(aChild)) |
2217 | 0 | return false; |
2218 | 0 | |
2219 | 0 | } else { |
2220 | 0 | if (!mChildren.InsertElementAt(aIndex, aChild)) |
2221 | 0 | return false; |
2222 | 0 | |
2223 | 0 | MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change"); |
2224 | 0 |
|
2225 | 0 | for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) { |
2226 | 0 | mChildren[idx]->mIndexInParent = idx; |
2227 | 0 | } |
2228 | 0 | } |
2229 | 0 |
|
2230 | 0 | if (aChild->IsText()) { |
2231 | 0 | mStateFlags |= eHasTextKids; |
2232 | 0 | } |
2233 | 0 |
|
2234 | 0 | aChild->BindToParent(this, aIndex); |
2235 | 0 | return true; |
2236 | 0 | } |
2237 | | |
2238 | | bool |
2239 | | Accessible::RemoveChild(Accessible* aChild) |
2240 | 0 | { |
2241 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given"); |
2242 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent"); |
2243 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent"); |
2244 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given"); |
2245 | 0 | MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() || |
2246 | 0 | aChild->IsDoc() || IsApplication(), |
2247 | 0 | "Illicit children change"); |
2248 | 0 |
|
2249 | 0 | int32_t index = static_cast<uint32_t>(aChild->mIndexInParent); |
2250 | 0 | if (mChildren.SafeElementAt(index) != aChild) { |
2251 | 0 | MOZ_ASSERT_UNREACHABLE("A wrong child index"); |
2252 | 0 | index = mChildren.IndexOf(aChild); |
2253 | 0 | if (index == -1) { |
2254 | 0 | MOZ_ASSERT_UNREACHABLE("No child was found"); |
2255 | 0 | return false; |
2256 | 0 | } |
2257 | 0 | } |
2258 | 0 |
|
2259 | 0 | aChild->UnbindFromParent(); |
2260 | 0 | mChildren.RemoveElementAt(index); |
2261 | 0 |
|
2262 | 0 | for (uint32_t idx = index; idx < mChildren.Length(); idx++) { |
2263 | 0 | mChildren[idx]->mIndexInParent = idx; |
2264 | 0 | } |
2265 | 0 |
|
2266 | 0 | return true; |
2267 | 0 | } |
2268 | | |
2269 | | void |
2270 | | Accessible::MoveChild(uint32_t aNewIndex, Accessible* aChild) |
2271 | 0 | { |
2272 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given"); |
2273 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "A child from different subtree was given"); |
2274 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1, "Unbound child was given"); |
2275 | 0 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent->GetChildAt(aChild->mIndexInParent) == aChild, "Wrong index in parent"); |
2276 | 0 | MOZ_DIAGNOSTIC_ASSERT(static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex, |
2277 | 0 | "No move, same index"); |
2278 | 0 | MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(), "Wrong new index was given"); |
2279 | 0 |
|
2280 | 0 | RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false); |
2281 | 0 | if (mDoc->Controller()->QueueMutationEvent(hideEvent)) { |
2282 | 0 | aChild->SetHideEventTarget(true); |
2283 | 0 | } |
2284 | 0 |
|
2285 | 0 | mEmbeddedObjCollector = nullptr; |
2286 | 0 | mChildren.RemoveElementAt(aChild->mIndexInParent); |
2287 | 0 |
|
2288 | 0 | uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent; |
2289 | 0 |
|
2290 | 0 | // If the child is moved after its current position. |
2291 | 0 | if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) { |
2292 | 0 | startIdx = aChild->mIndexInParent; |
2293 | 0 | if (aNewIndex == mChildren.Length() + 1) { |
2294 | 0 | // The child is moved to the end. |
2295 | 0 | mChildren.AppendElement(aChild); |
2296 | 0 | endIdx = mChildren.Length() - 1; |
2297 | 0 | } |
2298 | 0 | else { |
2299 | 0 | mChildren.InsertElementAt(aNewIndex - 1, aChild); |
2300 | 0 | endIdx = aNewIndex; |
2301 | 0 | } |
2302 | 0 | } |
2303 | 0 | else { |
2304 | 0 | // The child is moved prior its current position. |
2305 | 0 | mChildren.InsertElementAt(aNewIndex, aChild); |
2306 | 0 | } |
2307 | 0 |
|
2308 | 0 | for (uint32_t idx = startIdx; idx <= endIdx; idx++) { |
2309 | 0 | mChildren[idx]->mIndexInParent = idx; |
2310 | 0 | mChildren[idx]->mStateFlags |= eGroupInfoDirty; |
2311 | 0 | mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1; |
2312 | 0 | } |
2313 | 0 |
|
2314 | 0 | RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild); |
2315 | 0 | DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent); |
2316 | 0 | MOZ_ASSERT(added); |
2317 | 0 | aChild->SetShowEventTarget(true); |
2318 | 0 | } |
2319 | | |
2320 | | Accessible* |
2321 | | Accessible::GetChildAt(uint32_t aIndex) const |
2322 | 0 | { |
2323 | 0 | Accessible* child = mChildren.SafeElementAt(aIndex, nullptr); |
2324 | 0 | if (!child) |
2325 | 0 | return nullptr; |
2326 | 0 | |
2327 | | #ifdef DEBUG |
2328 | | Accessible* realParent = child->mParent; |
2329 | | NS_ASSERTION(!realParent || realParent == this, |
2330 | | "Two accessibles have the same first child accessible!"); |
2331 | | #endif |
2332 | | |
2333 | 0 | return child; |
2334 | 0 | } |
2335 | | |
2336 | | uint32_t |
2337 | | Accessible::ChildCount() const |
2338 | 0 | { |
2339 | 0 | return mChildren.Length(); |
2340 | 0 | } |
2341 | | |
2342 | | int32_t |
2343 | | Accessible::IndexInParent() const |
2344 | 0 | { |
2345 | 0 | return mIndexInParent; |
2346 | 0 | } |
2347 | | |
2348 | | uint32_t |
2349 | | Accessible::EmbeddedChildCount() |
2350 | 0 | { |
2351 | 0 | if (mStateFlags & eHasTextKids) { |
2352 | 0 | if (!mEmbeddedObjCollector) |
2353 | 0 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
2354 | 0 | return mEmbeddedObjCollector->Count(); |
2355 | 0 | } |
2356 | 0 |
|
2357 | 0 | return ChildCount(); |
2358 | 0 | } |
2359 | | |
2360 | | Accessible* |
2361 | | Accessible::GetEmbeddedChildAt(uint32_t aIndex) |
2362 | 0 | { |
2363 | 0 | if (mStateFlags & eHasTextKids) { |
2364 | 0 | if (!mEmbeddedObjCollector) |
2365 | 0 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
2366 | 0 | return mEmbeddedObjCollector.get() ? |
2367 | 0 | mEmbeddedObjCollector->GetAccessibleAt(aIndex) : nullptr; |
2368 | 0 | } |
2369 | 0 |
|
2370 | 0 | return GetChildAt(aIndex); |
2371 | 0 | } |
2372 | | |
2373 | | int32_t |
2374 | | Accessible::GetIndexOfEmbeddedChild(Accessible* aChild) |
2375 | 0 | { |
2376 | 0 | if (mStateFlags & eHasTextKids) { |
2377 | 0 | if (!mEmbeddedObjCollector) |
2378 | 0 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
2379 | 0 | return mEmbeddedObjCollector.get() ? |
2380 | 0 | mEmbeddedObjCollector->GetIndexAt(aChild) : -1; |
2381 | 0 | } |
2382 | 0 |
|
2383 | 0 | return GetIndexOf(aChild); |
2384 | 0 | } |
2385 | | |
2386 | | //////////////////////////////////////////////////////////////////////////////// |
2387 | | // HyperLinkAccessible methods |
2388 | | |
2389 | | bool |
2390 | | Accessible::IsLink() const |
2391 | 0 | { |
2392 | 0 | // Every embedded accessible within hypertext accessible implements |
2393 | 0 | // hyperlink interface. |
2394 | 0 | return mParent && mParent->IsHyperText() && !IsText(); |
2395 | 0 | } |
2396 | | |
2397 | | uint32_t |
2398 | | Accessible::StartOffset() |
2399 | 0 | { |
2400 | 0 | MOZ_ASSERT(IsLink(), "StartOffset is called not on hyper link!"); |
2401 | 0 |
|
2402 | 0 | HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr; |
2403 | 0 | return hyperText ? hyperText->GetChildOffset(this) : 0; |
2404 | 0 | } |
2405 | | |
2406 | | uint32_t |
2407 | | Accessible::EndOffset() |
2408 | 0 | { |
2409 | 0 | MOZ_ASSERT(IsLink(), "EndOffset is called on not hyper link!"); |
2410 | 0 |
|
2411 | 0 | HyperTextAccessible* hyperText = mParent ? mParent->AsHyperText() : nullptr; |
2412 | 0 | return hyperText ? (hyperText->GetChildOffset(this) + 1) : 0; |
2413 | 0 | } |
2414 | | |
2415 | | uint32_t |
2416 | | Accessible::AnchorCount() |
2417 | 0 | { |
2418 | 0 | MOZ_ASSERT(IsLink(), "AnchorCount is called on not hyper link!"); |
2419 | 0 | return 1; |
2420 | 0 | } |
2421 | | |
2422 | | Accessible* |
2423 | | Accessible::AnchorAt(uint32_t aAnchorIndex) |
2424 | 0 | { |
2425 | 0 | MOZ_ASSERT(IsLink(), "GetAnchor is called on not hyper link!"); |
2426 | 0 | return aAnchorIndex == 0 ? this : nullptr; |
2427 | 0 | } |
2428 | | |
2429 | | already_AddRefed<nsIURI> |
2430 | | Accessible::AnchorURIAt(uint32_t aAnchorIndex) const |
2431 | 0 | { |
2432 | 0 | MOZ_ASSERT(IsLink(), "AnchorURIAt is called on not hyper link!"); |
2433 | 0 | return nullptr; |
2434 | 0 | } |
2435 | | |
2436 | | void |
2437 | | Accessible::ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset, |
2438 | | bool aIsBefore) const |
2439 | 0 | { |
2440 | 0 | if (IsHyperText()) { |
2441 | 0 | *aContainer = const_cast<Accessible*>(this)->AsHyperText(); |
2442 | 0 | *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount(); |
2443 | 0 | return; |
2444 | 0 | } |
2445 | 0 |
|
2446 | 0 | const Accessible* child = nullptr; |
2447 | 0 | const Accessible* parent = this; |
2448 | 0 | do { |
2449 | 0 | child = parent; |
2450 | 0 | parent = parent->Parent(); |
2451 | 0 | } while (parent && !parent->IsHyperText()); |
2452 | 0 |
|
2453 | 0 | if (parent) { |
2454 | 0 | *aContainer = const_cast<Accessible*>(parent)->AsHyperText(); |
2455 | 0 | *aOffset = (*aContainer)->GetChildOffset( |
2456 | 0 | child->IndexInParent() + static_cast<int32_t>(!aIsBefore)); |
2457 | 0 | } |
2458 | 0 | } |
2459 | | |
2460 | | |
2461 | | //////////////////////////////////////////////////////////////////////////////// |
2462 | | // SelectAccessible |
2463 | | |
2464 | | void |
2465 | | Accessible::SelectedItems(nsTArray<Accessible*>* aItems) |
2466 | 0 | { |
2467 | 0 | AccIterator iter(this, filters::GetSelected); |
2468 | 0 | Accessible* selected = nullptr; |
2469 | 0 | while ((selected = iter.Next())) |
2470 | 0 | aItems->AppendElement(selected); |
2471 | 0 | } |
2472 | | |
2473 | | uint32_t |
2474 | | Accessible::SelectedItemCount() |
2475 | 0 | { |
2476 | 0 | uint32_t count = 0; |
2477 | 0 | AccIterator iter(this, filters::GetSelected); |
2478 | 0 | Accessible* selected = nullptr; |
2479 | 0 | while ((selected = iter.Next())) |
2480 | 0 | ++count; |
2481 | 0 |
|
2482 | 0 | return count; |
2483 | 0 | } |
2484 | | |
2485 | | Accessible* |
2486 | | Accessible::GetSelectedItem(uint32_t aIndex) |
2487 | 0 | { |
2488 | 0 | AccIterator iter(this, filters::GetSelected); |
2489 | 0 | Accessible* selected = nullptr; |
2490 | 0 |
|
2491 | 0 | uint32_t index = 0; |
2492 | 0 | while ((selected = iter.Next()) && index < aIndex) |
2493 | 0 | index++; |
2494 | 0 |
|
2495 | 0 | return selected; |
2496 | 0 | } |
2497 | | |
2498 | | bool |
2499 | | Accessible::IsItemSelected(uint32_t aIndex) |
2500 | 0 | { |
2501 | 0 | uint32_t index = 0; |
2502 | 0 | AccIterator iter(this, filters::GetSelectable); |
2503 | 0 | Accessible* selected = nullptr; |
2504 | 0 | while ((selected = iter.Next()) && index < aIndex) |
2505 | 0 | index++; |
2506 | 0 |
|
2507 | 0 | return selected && |
2508 | 0 | selected->State() & states::SELECTED; |
2509 | 0 | } |
2510 | | |
2511 | | bool |
2512 | | Accessible::AddItemToSelection(uint32_t aIndex) |
2513 | 0 | { |
2514 | 0 | uint32_t index = 0; |
2515 | 0 | AccIterator iter(this, filters::GetSelectable); |
2516 | 0 | Accessible* selected = nullptr; |
2517 | 0 | while ((selected = iter.Next()) && index < aIndex) |
2518 | 0 | index++; |
2519 | 0 |
|
2520 | 0 | if (selected) |
2521 | 0 | selected->SetSelected(true); |
2522 | 0 |
|
2523 | 0 | return static_cast<bool>(selected); |
2524 | 0 | } |
2525 | | |
2526 | | bool |
2527 | | Accessible::RemoveItemFromSelection(uint32_t aIndex) |
2528 | 0 | { |
2529 | 0 | uint32_t index = 0; |
2530 | 0 | AccIterator iter(this, filters::GetSelectable); |
2531 | 0 | Accessible* selected = nullptr; |
2532 | 0 | while ((selected = iter.Next()) && index < aIndex) |
2533 | 0 | index++; |
2534 | 0 |
|
2535 | 0 | if (selected) |
2536 | 0 | selected->SetSelected(false); |
2537 | 0 |
|
2538 | 0 | return static_cast<bool>(selected); |
2539 | 0 | } |
2540 | | |
2541 | | bool |
2542 | | Accessible::SelectAll() |
2543 | 0 | { |
2544 | 0 | bool success = false; |
2545 | 0 | Accessible* selectable = nullptr; |
2546 | 0 |
|
2547 | 0 | AccIterator iter(this, filters::GetSelectable); |
2548 | 0 | while((selectable = iter.Next())) { |
2549 | 0 | success = true; |
2550 | 0 | selectable->SetSelected(true); |
2551 | 0 | } |
2552 | 0 | return success; |
2553 | 0 | } |
2554 | | |
2555 | | bool |
2556 | | Accessible::UnselectAll() |
2557 | 0 | { |
2558 | 0 | bool success = false; |
2559 | 0 | Accessible* selected = nullptr; |
2560 | 0 |
|
2561 | 0 | AccIterator iter(this, filters::GetSelected); |
2562 | 0 | while ((selected = iter.Next())) { |
2563 | 0 | success = true; |
2564 | 0 | selected->SetSelected(false); |
2565 | 0 | } |
2566 | 0 | return success; |
2567 | 0 | } |
2568 | | |
2569 | | //////////////////////////////////////////////////////////////////////////////// |
2570 | | // Widgets |
2571 | | |
2572 | | bool |
2573 | | Accessible::IsWidget() const |
2574 | 0 | { |
2575 | 0 | return false; |
2576 | 0 | } |
2577 | | |
2578 | | bool |
2579 | | Accessible::IsActiveWidget() const |
2580 | 0 | { |
2581 | 0 | if (FocusMgr()->HasDOMFocus(mContent)) |
2582 | 0 | return true; |
2583 | 0 | |
2584 | 0 | // If text entry of combobox widget has a focus then the combobox widget is |
2585 | 0 | // active. |
2586 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
2587 | 0 | if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) { |
2588 | 0 | uint32_t childCount = ChildCount(); |
2589 | 0 | for (uint32_t idx = 0; idx < childCount; idx++) { |
2590 | 0 | Accessible* child = mChildren.ElementAt(idx); |
2591 | 0 | if (child->Role() == roles::ENTRY) |
2592 | 0 | return FocusMgr()->HasDOMFocus(child->GetContent()); |
2593 | 0 | } |
2594 | 0 | } |
2595 | 0 |
|
2596 | 0 | return false; |
2597 | 0 | } |
2598 | | |
2599 | | bool |
2600 | | Accessible::AreItemsOperable() const |
2601 | 0 | { |
2602 | 0 | return HasOwnContent() && |
2603 | 0 | mContent->IsElement() && |
2604 | 0 | mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_activedescendant); |
2605 | 0 | } |
2606 | | |
2607 | | Accessible* |
2608 | | Accessible::CurrentItem() const |
2609 | 0 | { |
2610 | 0 | // Check for aria-activedescendant, which changes which element has focus. |
2611 | 0 | // For activedescendant, the ARIA spec does not require that the user agent |
2612 | 0 | // checks whether pointed node is actually a DOM descendant of the element |
2613 | 0 | // with the aria-activedescendant attribute. |
2614 | 0 | nsAutoString id; |
2615 | 0 | if (HasOwnContent() && |
2616 | 0 | mContent->IsElement() && |
2617 | 0 | mContent->AsElement()->GetAttr(kNameSpaceID_None, |
2618 | 0 | nsGkAtoms::aria_activedescendant, id)) { |
2619 | 0 | nsIDocument* DOMDoc = mContent->OwnerDoc(); |
2620 | 0 | dom::Element* activeDescendantElm = DOMDoc->GetElementById(id); |
2621 | 0 | if (activeDescendantElm) { |
2622 | 0 | DocAccessible* document = Document(); |
2623 | 0 | if (document) |
2624 | 0 | return document->GetAccessible(activeDescendantElm); |
2625 | 0 | } |
2626 | 0 | } |
2627 | 0 | return nullptr; |
2628 | 0 | } |
2629 | | |
2630 | | void |
2631 | | Accessible::SetCurrentItem(const Accessible* aItem) |
2632 | 0 | { |
2633 | 0 | nsAtom* id = aItem->GetContent()->GetID(); |
2634 | 0 | if (id) { |
2635 | 0 | nsAutoString idStr; |
2636 | 0 | id->ToString(idStr); |
2637 | 0 | mContent->AsElement()->SetAttr(kNameSpaceID_None, |
2638 | 0 | nsGkAtoms::aria_activedescendant, |
2639 | 0 | idStr, |
2640 | 0 | true); |
2641 | 0 | } |
2642 | 0 | } |
2643 | | |
2644 | | Accessible* |
2645 | | Accessible::ContainerWidget() const |
2646 | 0 | { |
2647 | 0 | if (HasARIARole() && mContent->HasID()) { |
2648 | 0 | for (Accessible* parent = Parent(); parent; parent = parent->Parent()) { |
2649 | 0 | nsIContent* parentContent = parent->GetContent(); |
2650 | 0 | if (parentContent && |
2651 | 0 | parentContent->IsElement() && |
2652 | 0 | parentContent->AsElement()->HasAttr(kNameSpaceID_None, |
2653 | 0 | nsGkAtoms::aria_activedescendant)) { |
2654 | 0 | return parent; |
2655 | 0 | } |
2656 | 0 | |
2657 | 0 | // Don't cross DOM document boundaries. |
2658 | 0 | if (parent->IsDoc()) |
2659 | 0 | break; |
2660 | 0 | } |
2661 | 0 | } |
2662 | 0 | return nullptr; |
2663 | 0 | } |
2664 | | |
2665 | | //////////////////////////////////////////////////////////////////////////////// |
2666 | | // Accessible protected methods |
2667 | | |
2668 | | void |
2669 | | Accessible::LastRelease() |
2670 | 0 | { |
2671 | 0 | // First cleanup if needed... |
2672 | 0 | if (mDoc) { |
2673 | 0 | Shutdown(); |
2674 | 0 | NS_ASSERTION(!mDoc, |
2675 | 0 | "A Shutdown() impl forgot to call its parent's Shutdown?"); |
2676 | 0 | } |
2677 | 0 | // ... then die. |
2678 | 0 | delete this; |
2679 | 0 | } |
2680 | | |
2681 | | Accessible* |
2682 | | Accessible::GetSiblingAtOffset(int32_t aOffset, nsresult* aError) const |
2683 | 0 | { |
2684 | 0 | if (!mParent || mIndexInParent == -1) { |
2685 | 0 | if (aError) |
2686 | 0 | *aError = NS_ERROR_UNEXPECTED; |
2687 | 0 |
|
2688 | 0 | return nullptr; |
2689 | 0 | } |
2690 | 0 |
|
2691 | 0 | if (aError && |
2692 | 0 | mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) { |
2693 | 0 | *aError = NS_OK; // fail peacefully |
2694 | 0 | return nullptr; |
2695 | 0 | } |
2696 | 0 | |
2697 | 0 | Accessible* child = mParent->GetChildAt(mIndexInParent + aOffset); |
2698 | 0 | if (aError && !child) |
2699 | 0 | *aError = NS_ERROR_UNEXPECTED; |
2700 | 0 |
|
2701 | 0 | return child; |
2702 | 0 | } |
2703 | | |
2704 | | double |
2705 | | Accessible::AttrNumericValue(nsAtom* aAttr) const |
2706 | 0 | { |
2707 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
2708 | 0 | if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) |
2709 | 0 | return UnspecifiedNaN<double>(); |
2710 | 0 | |
2711 | 0 | nsAutoString attrValue; |
2712 | 0 | if (!mContent->IsElement() || |
2713 | 0 | !mContent->AsElement()->GetAttr(kNameSpaceID_None, aAttr, attrValue)) |
2714 | 0 | return UnspecifiedNaN<double>(); |
2715 | 0 | |
2716 | 0 | nsresult error = NS_OK; |
2717 | 0 | double value = attrValue.ToDouble(&error); |
2718 | 0 | return NS_FAILED(error) ? UnspecifiedNaN<double>() : value; |
2719 | 0 | } |
2720 | | |
2721 | | uint32_t |
2722 | | Accessible::GetActionRule() const |
2723 | 0 | { |
2724 | 0 | if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) |
2725 | 0 | return eNoAction; |
2726 | 0 | |
2727 | 0 | // Return "click" action on elements that have an attached popup menu. |
2728 | 0 | if (mContent->IsXULElement()) |
2729 | 0 | if (mContent->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::popup)) |
2730 | 0 | return eClickAction; |
2731 | 0 | |
2732 | 0 | // Has registered 'click' event handler. |
2733 | 0 | bool isOnclick = nsCoreUtils::HasClickListener(mContent); |
2734 | 0 |
|
2735 | 0 | if (isOnclick) |
2736 | 0 | return eClickAction; |
2737 | 0 | |
2738 | 0 | // Get an action based on ARIA role. |
2739 | 0 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
2740 | 0 | if (roleMapEntry && |
2741 | 0 | roleMapEntry->actionRule != eNoAction) |
2742 | 0 | return roleMapEntry->actionRule; |
2743 | 0 | |
2744 | 0 | // Get an action based on ARIA attribute. |
2745 | 0 | if (nsAccUtils::HasDefinedARIAToken(mContent, |
2746 | 0 | nsGkAtoms::aria_expanded)) |
2747 | 0 | return eExpandAction; |
2748 | 0 | |
2749 | 0 | return eNoAction; |
2750 | 0 | } |
2751 | | |
2752 | | AccGroupInfo* |
2753 | | Accessible::GetGroupInfo() const |
2754 | 0 | { |
2755 | 0 | if (IsProxy()) |
2756 | 0 | MOZ_CRASH("This should never be called on proxy wrappers"); |
2757 | 0 |
|
2758 | 0 | if (mBits.groupInfo){ |
2759 | 0 | if (HasDirtyGroupInfo()) { |
2760 | 0 | mBits.groupInfo->Update(); |
2761 | 0 | mStateFlags &= ~eGroupInfoDirty; |
2762 | 0 | } |
2763 | 0 |
|
2764 | 0 | return mBits.groupInfo; |
2765 | 0 | } |
2766 | 0 |
|
2767 | 0 | mBits.groupInfo = AccGroupInfo::CreateGroupInfo(this); |
2768 | 0 | return mBits.groupInfo; |
2769 | 0 | } |
2770 | | |
2771 | | void |
2772 | | Accessible::GetPositionAndSizeInternal(int32_t *aPosInSet, int32_t *aSetSize) |
2773 | 0 | { |
2774 | 0 | AccGroupInfo* groupInfo = GetGroupInfo(); |
2775 | 0 | if (groupInfo) { |
2776 | 0 | *aPosInSet = groupInfo->PosInSet(); |
2777 | 0 | *aSetSize = groupInfo->SetSize(); |
2778 | 0 | } |
2779 | 0 | } |
2780 | | |
2781 | | int32_t |
2782 | | Accessible::GetLevelInternal() |
2783 | 0 | { |
2784 | 0 | int32_t level = nsAccUtils::GetDefaultLevel(this); |
2785 | 0 |
|
2786 | 0 | if (!IsBoundToParent()) |
2787 | 0 | return level; |
2788 | 0 | |
2789 | 0 | roles::Role role = Role(); |
2790 | 0 | if (role == roles::OUTLINEITEM) { |
2791 | 0 | // Always expose 'level' attribute for 'outlineitem' accessible. The number |
2792 | 0 | // of nested 'grouping' accessibles containing 'outlineitem' accessible is |
2793 | 0 | // its level. |
2794 | 0 | level = 1; |
2795 | 0 |
|
2796 | 0 | Accessible* parent = this; |
2797 | 0 | while ((parent = parent->Parent())) { |
2798 | 0 | roles::Role parentRole = parent->Role(); |
2799 | 0 |
|
2800 | 0 | if (parentRole == roles::OUTLINE) |
2801 | 0 | break; |
2802 | 0 | if (parentRole == roles::GROUPING) |
2803 | 0 | ++ level; |
2804 | 0 |
|
2805 | 0 | } |
2806 | 0 |
|
2807 | 0 | } else if (role == roles::LISTITEM) { |
2808 | 0 | // Expose 'level' attribute on nested lists. We support two hierarchies: |
2809 | 0 | // a) list -> listitem -> list -> listitem (nested list is a last child |
2810 | 0 | // of listitem of the parent list); |
2811 | 0 | // b) list -> listitem -> group -> listitem (nested listitems are contained |
2812 | 0 | // by group that is a last child of the parent listitem). |
2813 | 0 |
|
2814 | 0 | // Calculate 'level' attribute based on number of parent listitems. |
2815 | 0 | level = 0; |
2816 | 0 | Accessible* parent = this; |
2817 | 0 | while ((parent = parent->Parent())) { |
2818 | 0 | roles::Role parentRole = parent->Role(); |
2819 | 0 |
|
2820 | 0 | if (parentRole == roles::LISTITEM) |
2821 | 0 | ++ level; |
2822 | 0 | else if (parentRole != roles::LIST && parentRole != roles::GROUPING) |
2823 | 0 | break; |
2824 | 0 | } |
2825 | 0 |
|
2826 | 0 | if (level == 0) { |
2827 | 0 | // If this listitem is on top of nested lists then expose 'level' |
2828 | 0 | // attribute. |
2829 | 0 | parent = Parent(); |
2830 | 0 | uint32_t siblingCount = parent->ChildCount(); |
2831 | 0 | for (uint32_t siblingIdx = 0; siblingIdx < siblingCount; siblingIdx++) { |
2832 | 0 | Accessible* sibling = parent->GetChildAt(siblingIdx); |
2833 | 0 |
|
2834 | 0 | Accessible* siblingChild = sibling->LastChild(); |
2835 | 0 | if (siblingChild) { |
2836 | 0 | roles::Role lastChildRole = siblingChild->Role(); |
2837 | 0 | if (lastChildRole == roles::LIST || lastChildRole == roles::GROUPING) |
2838 | 0 | return 1; |
2839 | 0 | } |
2840 | 0 | } |
2841 | 0 | } else { |
2842 | 0 | ++ level; // level is 1-index based |
2843 | 0 | } |
2844 | 0 | } |
2845 | 0 |
|
2846 | 0 | return level; |
2847 | 0 | } |
2848 | | |
2849 | | void |
2850 | | Accessible::StaticAsserts() const |
2851 | 0 | { |
2852 | 0 | static_assert(eLastStateFlag <= (1 << kStateFlagsBits) - 1, |
2853 | 0 | "Accessible::mStateFlags was oversized by eLastStateFlag!"); |
2854 | 0 | static_assert(eLastAccType <= (1 << kTypeBits) - 1, |
2855 | 0 | "Accessible::mType was oversized by eLastAccType!"); |
2856 | 0 | static_assert(eLastContextFlag <= (1 << kContextFlagsBits) - 1, |
2857 | 0 | "Accessible::mContextFlags was oversized by eLastContextFlag!"); |
2858 | 0 | static_assert(eLastAccGenericType <= (1 << kGenericTypesBits) - 1, |
2859 | 0 | "Accessible::mGenericType was oversized by eLastAccGenericType!"); |
2860 | 0 | } |
2861 | | |
2862 | | //////////////////////////////////////////////////////////////////////////////// |
2863 | | // KeyBinding class |
2864 | | |
2865 | | // static |
2866 | | uint32_t |
2867 | | KeyBinding::AccelModifier() |
2868 | 0 | { |
2869 | 0 | switch (WidgetInputEvent::AccelModifier()) { |
2870 | 0 | case MODIFIER_ALT: |
2871 | 0 | return kAlt; |
2872 | 0 | case MODIFIER_CONTROL: |
2873 | 0 | return kControl; |
2874 | 0 | case MODIFIER_META: |
2875 | 0 | return kMeta; |
2876 | 0 | case MODIFIER_OS: |
2877 | 0 | return kOS; |
2878 | 0 | default: |
2879 | 0 | MOZ_CRASH("Handle the new result of WidgetInputEvent::AccelModifier()"); |
2880 | 0 | return 0; |
2881 | 0 | } |
2882 | 0 | } |
2883 | | |
2884 | | void |
2885 | | KeyBinding::ToPlatformFormat(nsAString& aValue) const |
2886 | 0 | { |
2887 | 0 | nsCOMPtr<nsIStringBundle> keyStringBundle; |
2888 | 0 | nsCOMPtr<nsIStringBundleService> stringBundleService = |
2889 | 0 | mozilla::services::GetStringBundleService(); |
2890 | 0 | if (stringBundleService) |
2891 | 0 | stringBundleService->CreateBundle( |
2892 | 0 | "chrome://global-platform/locale/platformKeys.properties", |
2893 | 0 | getter_AddRefs(keyStringBundle)); |
2894 | 0 |
|
2895 | 0 | if (!keyStringBundle) |
2896 | 0 | return; |
2897 | 0 | |
2898 | 0 | nsAutoString separator; |
2899 | 0 | keyStringBundle->GetStringFromName("MODIFIER_SEPARATOR", separator); |
2900 | 0 |
|
2901 | 0 | nsAutoString modifierName; |
2902 | 0 | if (mModifierMask & kControl) { |
2903 | 0 | keyStringBundle->GetStringFromName("VK_CONTROL", modifierName); |
2904 | 0 |
|
2905 | 0 | aValue.Append(modifierName); |
2906 | 0 | aValue.Append(separator); |
2907 | 0 | } |
2908 | 0 |
|
2909 | 0 | if (mModifierMask & kAlt) { |
2910 | 0 | keyStringBundle->GetStringFromName("VK_ALT", modifierName); |
2911 | 0 |
|
2912 | 0 | aValue.Append(modifierName); |
2913 | 0 | aValue.Append(separator); |
2914 | 0 | } |
2915 | 0 |
|
2916 | 0 | if (mModifierMask & kShift) { |
2917 | 0 | keyStringBundle->GetStringFromName("VK_SHIFT", modifierName); |
2918 | 0 |
|
2919 | 0 | aValue.Append(modifierName); |
2920 | 0 | aValue.Append(separator); |
2921 | 0 | } |
2922 | 0 |
|
2923 | 0 | if (mModifierMask & kMeta) { |
2924 | 0 | keyStringBundle->GetStringFromName("VK_META", modifierName); |
2925 | 0 |
|
2926 | 0 | aValue.Append(modifierName); |
2927 | 0 | aValue.Append(separator); |
2928 | 0 | } |
2929 | 0 |
|
2930 | 0 | aValue.Append(mKey); |
2931 | 0 | } |
2932 | | |
2933 | | void |
2934 | | KeyBinding::ToAtkFormat(nsAString& aValue) const |
2935 | 0 | { |
2936 | 0 | nsAutoString modifierName; |
2937 | 0 | if (mModifierMask & kControl) |
2938 | 0 | aValue.AppendLiteral("<Control>"); |
2939 | 0 |
|
2940 | 0 | if (mModifierMask & kAlt) |
2941 | 0 | aValue.AppendLiteral("<Alt>"); |
2942 | 0 |
|
2943 | 0 | if (mModifierMask & kShift) |
2944 | 0 | aValue.AppendLiteral("<Shift>"); |
2945 | 0 |
|
2946 | 0 | if (mModifierMask & kMeta) |
2947 | 0 | aValue.AppendLiteral("<Meta>"); |
2948 | 0 |
|
2949 | 0 | aValue.Append(mKey); |
2950 | 0 | } |