File: | root/firefox-clang/accessible/generic/LocalAccessible.cpp |
Warning: | line 3040, column 11 Although the value stored to 'selected' is used in the enclosing expression, the value is never actually read from 'selected' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 "AccEvent.h" |
7 | #include "LocalAccessible-inl.h" |
8 | |
9 | #include "EmbeddedObjCollector.h" |
10 | #include "AccGroupInfo.h" |
11 | #include "AccIterator.h" |
12 | #include "CachedTableAccessible.h" |
13 | #include "CssAltContent.h" |
14 | #include "DocAccessible-inl.h" |
15 | #include "mozilla/a11y/AccAttributes.h" |
16 | #include "mozilla/a11y/DocAccessibleChild.h" |
17 | #include "mozilla/a11y/Platform.h" |
18 | #include "mozilla/FocusModel.h" |
19 | #include "nsAccUtils.h" |
20 | #include "nsAccessibilityService.h" |
21 | #include "ApplicationAccessible.h" |
22 | #include "nsGenericHTMLElement.h" |
23 | #include "NotificationController.h" |
24 | #include "nsEventShell.h" |
25 | #include "nsTextEquivUtils.h" |
26 | #include "EventTree.h" |
27 | #include "OuterDocAccessible.h" |
28 | #include "Pivot.h" |
29 | #include "Relation.h" |
30 | #include "mozilla/a11y/Role.h" |
31 | #include "RootAccessible.h" |
32 | #include "States.h" |
33 | #include "TextLeafRange.h" |
34 | #include "TextRange.h" |
35 | #include "HTMLElementAccessibles.h" |
36 | #include "HTMLSelectAccessible.h" |
37 | #include "HTMLTableAccessible.h" |
38 | #include "ImageAccessible.h" |
39 | |
40 | #include "nsComputedDOMStyle.h" |
41 | #include "nsGkAtoms.h" |
42 | #include "nsIDOMXULButtonElement.h" |
43 | #include "nsIDOMXULSelectCntrlEl.h" |
44 | #include "nsIDOMXULSelectCntrlItemEl.h" |
45 | #include "nsINodeList.h" |
46 | |
47 | #include "mozilla/dom/Document.h" |
48 | #include "mozilla/dom/HTMLFormElement.h" |
49 | #include "mozilla/dom/HTMLAnchorElement.h" |
50 | #include "mozilla/gfx/Matrix.h" |
51 | #include "nsIContent.h" |
52 | #include "nsIFormControl.h" |
53 | |
54 | #include "nsDisplayList.h" |
55 | #include "nsLayoutUtils.h" |
56 | #include "nsPresContext.h" |
57 | #include "nsIFrame.h" |
58 | #include "nsTextFrame.h" |
59 | #include "nsView.h" |
60 | #include "nsIDocShellTreeItem.h" |
61 | #include "nsStyleStructInlines.h" |
62 | #include "nsFocusManager.h" |
63 | |
64 | #include "nsString.h" |
65 | #include "nsAtom.h" |
66 | #include "nsContainerFrame.h" |
67 | |
68 | #include "mozilla/Assertions.h" |
69 | #include "mozilla/BasicEvents.h" |
70 | #include "mozilla/ErrorResult.h" |
71 | #include "mozilla/FloatingPoint.h" |
72 | #include "mozilla/PerfStats.h" |
73 | #include "mozilla/PresShell.h" |
74 | #include "mozilla/ProfilerMarkers.h" |
75 | #include "mozilla/ScrollContainerFrame.h" |
76 | #include "mozilla/StaticPrefs_dom.h" |
77 | #include "mozilla/StaticPrefs_ui.h" |
78 | #include "mozilla/dom/Element.h" |
79 | #include "mozilla/dom/HTMLLabelElement.h" |
80 | #include "mozilla/dom/KeyboardEventBinding.h" |
81 | #include "mozilla/dom/TreeWalker.h" |
82 | #include "mozilla/dom/UserActivation.h" |
83 | #include "mozilla/dom/MutationEventBinding.h" |
84 | |
85 | using namespace mozilla; |
86 | using namespace mozilla::a11y; |
87 | |
88 | //////////////////////////////////////////////////////////////////////////////// |
89 | // LocalAccessible: nsISupports and cycle collection |
90 | |
91 | NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)LocalAccessible::cycleCollection LocalAccessible::_cycleCollectorGlobal ; |
92 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)void LocalAccessible::cycleCollection::Unlink(void* p) { LocalAccessible * tmp = DowncastCCParticipant<LocalAccessible>(p); |
93 | tmp->Shutdown(); |
94 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END(void)tmp; } |
95 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)nsresult LocalAccessible::cycleCollection::TraverseNative( void * p, nsCycleCollectionTraversalCallback& cb) { LocalAccessible * tmp = DowncastCCParticipant<LocalAccessible>(p); cb.DescribeRefCountedNode (tmp->mRefCnt.get(), "LocalAccessible"); |
96 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)ImplCycleCollectionTraverse(cb, tmp->mContent, "mContent", 0); ImplCycleCollectionTraverse(cb, tmp->mDoc, "mDoc", 0) ; |
97 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END(void)tmp; return NS_OK; } |
98 | |
99 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)nsresult LocalAccessible::QueryInterface(const nsIID& aIID , void** aInstancePtr) { do { if (!(aInstancePtr)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "QueryInterface requires a non-NULL destination!" , "aInstancePtr", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 99); MOZ_PretendNoReturn(); } } while (0); nsISupports* foundInterface ; if (TopThreeWordsEquals( aIID, (nsXPCOMCycleCollectionParticipant ::kIID), (nsCycleCollectionISupports::kIID)) && (LowWordEquals (aIID, (nsXPCOMCycleCollectionParticipant::kIID)) || LowWordEquals (aIID, (nsCycleCollectionISupports::kIID)))) { if (LowWordEquals (aIID, (nsXPCOMCycleCollectionParticipant::kIID))) { *aInstancePtr = LocalAccessible::cycleCollection::GetParticipant(); return NS_OK; } if (LowWordEquals(aIID, (nsCycleCollectionISupports ::kIID))) { *aInstancePtr = LocalAccessible::cycleCollection:: Upcast(this); return NS_OK; } foundInterface = nullptr; } else |
100 | NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)if (aIID.Equals(mozilla::detail::kImplementedIID<std::remove_reference_t <decltype(*this)>, LocalAccessible>)) { *aInstancePtr = do_AddRef(static_cast<LocalAccessible*>(this)).take( ); return NS_OK; } else |
101 | NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)if (aIID.Equals(mozilla::detail::kImplementedIID<std::remove_reference_t <decltype(*this)>, nsISupports>)) foundInterface = static_cast <nsISupports*>(static_cast<LocalAccessible*>(this )); else |
102 | NS_INTERFACE_MAP_ENDfoundInterface = 0; nsresult status; if (!foundInterface) { do { static_assert( mozilla::detail::AssertionConditionType< decltype(!aIID.Equals((nsISupports::kIID)))>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!aIID.Equals((nsISupports::kIID ))))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("!aIID.Equals((nsISupports::kIID))", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 102); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!aIID.Equals((nsISupports::kIID))" ")"); do { MOZ_CrashSequence(__null, 102); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); status = NS_NOINTERFACE ; } else { (foundInterface)->AddRef(); status = NS_OK; } * aInstancePtr = foundInterface; return status; } |
103 | |
104 | NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)MozExternalRefCountType LocalAccessible::AddRef(void) { static_assert (!std::is_destructible_v<LocalAccessible>, "Reference-counted class " "LocalAccessible" " should not have a public destructor. " "Make this class's destructor non-public" ); do { static_assert( mozilla::detail::AssertionConditionType <decltype(int32_t(mRefCnt) >= 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) >= 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) >= 0" " (" "illegal refcnt" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 104); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) >= 0" ") (" "illegal refcnt" ")"); do { MOZ_CrashSequence(__null, 104 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); _mOwningThread.AssertOwnership("LocalAccessible" " not thread-safe" ); nsISupports* base = LocalAccessible::cycleCollection::Upcast (this); nsrefcnt count = mRefCnt.incr(base); NS_LogAddRef((this ), (count), ("LocalAccessible"), (uint32_t)(sizeof(*this))); return count; } |
105 | NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())MozExternalRefCountType LocalAccessible::Release(void) { do { static_assert( mozilla::detail::AssertionConditionType<decltype (int32_t(mRefCnt) > 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(int32_t(mRefCnt) > 0))), 0 ))) { do { } while (false); MOZ_ReportAssertionFailure("int32_t(mRefCnt) > 0" " (" "dup release" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 105); AnnotateMozCrashReason("MOZ_ASSERT" "(" "int32_t(mRefCnt) > 0" ") (" "dup release" ")"); do { MOZ_CrashSequence(__null, 105 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); _mOwningThread.AssertOwnership("LocalAccessible" " not thread-safe" ); nsISupports* base = LocalAccessible::cycleCollection::Upcast (this); nsrefcnt count = mRefCnt.decr(base); if (count == 0) { NS_CycleCollectableHasRefCntZero(); } NS_LogRelease((this), ( count), ("LocalAccessible")); return count; } void LocalAccessible ::DeleteCycleCollectable(void) { LastRelease(); } |
106 | |
107 | LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc) |
108 | : mContent(aContent), |
109 | mDoc(aDoc), |
110 | mParent(nullptr), |
111 | mIndexInParent(-1), |
112 | mFirstLineStart(-1), |
113 | mStateFlags(0), |
114 | mContextFlags(0), |
115 | mReorderEventTarget(false), |
116 | mShowEventTarget(false), |
117 | mHideEventTarget(false), |
118 | mIndexOfEmbeddedChild(-1), |
119 | mGroupInfo(nullptr) {} |
120 | |
121 | LocalAccessible::~LocalAccessible() { |
122 | NS_ASSERTION(!mDoc, "LastRelease was never called!?!")do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "LastRelease was never called!?!" , "!mDoc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 122); MOZ_PretendNoReturn(); } } while (0); |
123 | } |
124 | |
125 | ENameValueFlag LocalAccessible::Name(nsString& aName) const { |
126 | aName.Truncate(); |
127 | |
128 | if (!HasOwnContent()) return eNameOK; |
129 | |
130 | ENameValueFlag nameFlag = ARIAName(aName); |
131 | if (!aName.IsEmpty()) return nameFlag; |
132 | |
133 | nameFlag = NativeName(aName); |
134 | if (!aName.IsEmpty()) return nameFlag; |
135 | |
136 | // In the end get the name from tooltip. |
137 | if (mContent->IsHTMLElement()) { |
138 | if (mContent->AsElement()->GetAttr(nsGkAtoms::title, aName)) { |
139 | aName.CompressWhitespace(); |
140 | return eNameFromTooltip; |
141 | } |
142 | } else if (mContent->IsXULElement()) { |
143 | if (mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aName)) { |
144 | aName.CompressWhitespace(); |
145 | return eNameFromTooltip; |
146 | } |
147 | } else if (mContent->IsSVGElement()) { |
148 | // If user agents need to choose among multiple 'desc' or 'title' |
149 | // elements for processing, the user agent shall choose the first one. |
150 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
151 | childElm = childElm->GetNextSibling()) { |
152 | if (childElm->IsSVGElement(nsGkAtoms::desc)) { |
153 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
154 | return eNameFromTooltip; |
155 | } |
156 | } |
157 | } |
158 | |
159 | if (auto cssAlt = CssAltContent(mContent)) { |
160 | cssAlt.AppendToString(aName); |
161 | return eNameOK; |
162 | } |
163 | |
164 | aName.SetIsVoid(true); |
165 | |
166 | return nameFlag; |
167 | } |
168 | |
169 | void LocalAccessible::Description(nsString& aDescription) const { |
170 | // There are 4 conditions that make an accessible have no accDescription: |
171 | // 1. it's a text node; or |
172 | // 2. It has no ARIA describedby or description property |
173 | // 3. it doesn't have an accName; or |
174 | // 4. its title attribute already equals to its accName nsAutoString name; |
175 | |
176 | if (!HasOwnContent() || mContent->IsText()) return; |
177 | |
178 | ARIADescription(aDescription); |
179 | |
180 | if (aDescription.IsEmpty()) { |
181 | NativeDescription(aDescription); |
182 | |
183 | if (aDescription.IsEmpty()) { |
184 | // Keep the Name() method logic. |
185 | if (mContent->IsHTMLElement()) { |
186 | mContent->AsElement()->GetAttr(nsGkAtoms::title, aDescription); |
187 | } else if (mContent->IsXULElement()) { |
188 | mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aDescription); |
189 | } else if (mContent->IsSVGElement()) { |
190 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
191 | childElm = childElm->GetNextSibling()) { |
192 | if (childElm->IsSVGElement(nsGkAtoms::desc)) { |
193 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, |
194 | &aDescription); |
195 | break; |
196 | } |
197 | } |
198 | } |
199 | } |
200 | } |
201 | |
202 | if (!aDescription.IsEmpty()) { |
203 | aDescription.CompressWhitespace(); |
204 | nsAutoString name; |
205 | Name(name); |
206 | // Don't expose a description if it is the same as the name. |
207 | if (aDescription.Equals(name)) aDescription.Truncate(); |
208 | } |
209 | } |
210 | |
211 | KeyBinding LocalAccessible::AccessKey() const { |
212 | if (!HasOwnContent()) return KeyBinding(); |
213 | |
214 | uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent); |
215 | if (!key && mContent->IsElement()) { |
216 | LocalAccessible* label = nullptr; |
217 | |
218 | // Copy access key from label node. |
219 | if (mContent->IsHTMLElement()) { |
220 | // Unless it is labeled via an ancestor <label>, in which case that would |
221 | // be redundant. |
222 | HTMLLabelIterator iter(Document(), this, |
223 | HTMLLabelIterator::eSkipAncestorLabel); |
224 | label = iter.Next(); |
225 | } |
226 | if (!label) { |
227 | XULLabelIterator iter(Document(), mContent); |
228 | label = iter.Next(); |
229 | } |
230 | |
231 | if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent()); |
232 | } |
233 | |
234 | if (!key) return KeyBinding(); |
235 | |
236 | // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1). |
237 | switch (StaticPrefs::ui_key_generalAccessKey()) { |
238 | case -1: |
239 | break; |
240 | case dom::KeyboardEvent_Binding::DOM_VK_SHIFT: |
241 | return KeyBinding(key, KeyBinding::kShift); |
242 | case dom::KeyboardEvent_Binding::DOM_VK_CONTROL: |
243 | return KeyBinding(key, KeyBinding::kControl); |
244 | case dom::KeyboardEvent_Binding::DOM_VK_ALT: |
245 | return KeyBinding(key, KeyBinding::kAlt); |
246 | case dom::KeyboardEvent_Binding::DOM_VK_META: |
247 | return KeyBinding(key, KeyBinding::kMeta); |
248 | default: |
249 | return KeyBinding(); |
250 | } |
251 | |
252 | // Determine the access modifier used in this context. |
253 | dom::Document* document = mContent->GetComposedDoc(); |
254 | if (!document) return KeyBinding(); |
255 | |
256 | nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell()); |
257 | if (!treeItem) return KeyBinding(); |
258 | |
259 | nsresult rv = NS_ERROR_FAILURE; |
260 | int32_t modifierMask = 0; |
261 | switch (treeItem->ItemType()) { |
262 | case nsIDocShellTreeItem::typeChrome: |
263 | modifierMask = StaticPrefs::ui_key_chromeAccess(); |
264 | rv = NS_OK; |
265 | break; |
266 | case nsIDocShellTreeItem::typeContent: |
267 | modifierMask = StaticPrefs::ui_key_contentAccess(); |
268 | rv = NS_OK; |
269 | break; |
270 | } |
271 | |
272 | return NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1))) ? KeyBinding(key, modifierMask) : KeyBinding(); |
273 | } |
274 | |
275 | KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); } |
276 | |
277 | uint64_t LocalAccessible::VisibilityState() const { |
278 | if (IPCAccessibilityActive()) { |
279 | // Visibility states must be calculated by RemoteAccessible, so there's no |
280 | // point calculating them here. |
281 | return 0; |
282 | } |
283 | nsIFrame* frame = GetFrame(); |
284 | if (!frame) { |
285 | // Element having display:contents is considered visible semantically, |
286 | // despite it doesn't have a visually visible box. |
287 | if (nsCoreUtils::IsDisplayContents(mContent)) { |
288 | return states::OFFSCREEN; |
289 | } |
290 | return states::INVISIBLE; |
291 | } |
292 | |
293 | if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE; |
294 | |
295 | // It's invisible if the presshell is hidden by a visibility:hidden element in |
296 | // an ancestor document. |
297 | if (frame->PresShell()->IsUnderHiddenEmbedderElement()) { |
298 | return states::INVISIBLE; |
299 | } |
300 | |
301 | // Offscreen state if the document's visibility state is not visible. |
302 | if (Document()->IsHidden()) return states::OFFSCREEN; |
303 | |
304 | // Walk the parent frame chain to see if the frame is in background tab or |
305 | // scrolled out. |
306 | nsIFrame* curFrame = frame; |
307 | do { |
308 | nsView* view = curFrame->GetView(); |
309 | if (view && view->GetVisibility() == ViewVisibility::Hide) { |
310 | return states::INVISIBLE; |
311 | } |
312 | |
313 | if (nsLayoutUtils::IsPopup(curFrame)) { |
314 | return 0; |
315 | } |
316 | |
317 | if (curFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) { |
318 | // Offscreen state for background tab content. |
319 | return states::OFFSCREEN; |
320 | } |
321 | |
322 | nsIFrame* parentFrame = curFrame->GetParent(); |
323 | // If contained by scrollable frame then check that at least 12 pixels |
324 | // around the object is visible, otherwise the object is offscreen. |
325 | const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12); |
326 | if (ScrollContainerFrame* scrollContainerFrame = |
327 | do_QueryFrame(parentFrame)) { |
328 | nsRect scrollPortRect = scrollContainerFrame->GetScrollPortRect(); |
329 | nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor( |
330 | frame, frame->GetRectRelativeToSelf(), parentFrame); |
331 | if (!scrollPortRect.Contains(frameRect)) { |
332 | scrollPortRect.Deflate(kMinPixels, kMinPixels); |
333 | if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN; |
334 | } |
335 | } |
336 | |
337 | if (!parentFrame) { |
338 | parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame); |
339 | // Even if we couldn't find the parent frame, it might mean we are in an |
340 | // out-of-process iframe, try to see if |frame| is scrolled out in an |
341 | // scrollable frame in a cross-process ancestor document. |
342 | if (!parentFrame && |
343 | nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess( |
344 | frame, kMinPixels)) { |
345 | return states::OFFSCREEN; |
346 | } |
347 | } |
348 | |
349 | curFrame = parentFrame; |
350 | } while (curFrame); |
351 | |
352 | // Zero area rects can occur in the first frame of a multi-frame text flow, |
353 | // in which case the rendered text is not empty and the frame should not be |
354 | // marked invisible. |
355 | // XXX Can we just remove this check? Why do we need to mark empty |
356 | // text invisible? |
357 | if (frame->IsTextFrame() && !frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && |
358 | frame->GetRect().IsEmpty()) { |
359 | nsIFrame::RenderedText text = frame->GetRenderedText( |
360 | 0, UINT32_MAX(4294967295U), nsIFrame::TextOffsetType::OffsetsInContentText, |
361 | nsIFrame::TrailingWhitespace::DontTrim); |
362 | if (text.mString.IsEmpty()) { |
363 | return states::INVISIBLE; |
364 | } |
365 | } |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | uint64_t LocalAccessible::NativeState() const { |
371 | uint64_t state = 0; |
372 | |
373 | if (!IsInDocument()) state |= states::STALE; |
374 | |
375 | if (HasOwnContent() && mContent->IsElement()) { |
376 | dom::ElementState elementState = mContent->AsElement()->State(); |
377 | |
378 | if (elementState.HasState(dom::ElementState::INVALID)) { |
379 | state |= states::INVALID; |
380 | } |
381 | |
382 | if (elementState.HasState(dom::ElementState::REQUIRED)) { |
383 | state |= states::REQUIRED; |
384 | } |
385 | |
386 | state |= NativeInteractiveState(); |
387 | } |
388 | |
389 | // Gather states::INVISIBLE and states::OFFSCREEN flags for this object. |
390 | state |= VisibilityState(); |
391 | |
392 | nsIFrame* frame = GetFrame(); |
393 | if (frame && frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { |
394 | state |= states::FLOATING; |
395 | } |
396 | |
397 | // Check if a XUL element has the popup attribute (an attached popup menu). |
398 | if (HasOwnContent() && mContent->IsXULElement() && |
399 | mContent->AsElement()->HasAttr(nsGkAtoms::popup)) { |
400 | state |= states::HASPOPUP; |
401 | } |
402 | |
403 | // Bypass the link states specialization for non links. |
404 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
405 | if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole || |
406 | roleMapEntry->role == roles::LINK) { |
407 | state |= NativeLinkState(); |
408 | } |
409 | |
410 | return state; |
411 | } |
412 | |
413 | uint64_t LocalAccessible::NativeInteractiveState() const { |
414 | if (!mContent->IsElement()) return 0; |
415 | |
416 | if (NativelyUnavailable()) return states::UNAVAILABLE; |
417 | |
418 | nsIFrame* frame = GetFrame(); |
419 | auto flags = IsFocusableFlags(0); |
420 | // If we're caching this remote document in the parent process, we |
421 | // need to cache focusability irrespective of visibility. Otherwise, |
422 | // if this document is invisible when it first loads, we'll cache that |
423 | // all descendants are unfocusable and this won't get updated when the |
424 | // document becomes visible. Even if we did get notified when the |
425 | // document becomes visible, it would be wasteful to walk the entire |
426 | // tree to figure out what is now focusable and push cache updates. |
427 | // Although ignoring visibility means IsFocusable will return true for |
428 | // visibility: hidden, etc., this isn't a problem because we don't include |
429 | // those hidden elements in the a11y tree anyway. |
430 | if (mDoc->IPCDoc()) { |
431 | flags |= IsFocusableFlags::IgnoreVisibility; |
432 | } |
433 | if (frame && frame->IsFocusable(flags)) { |
434 | return states::FOCUSABLE; |
435 | } |
436 | return 0; |
437 | } |
438 | |
439 | uint64_t LocalAccessible::NativeLinkState() const { return 0; } |
440 | |
441 | bool LocalAccessible::NativelyUnavailable() const { |
442 | if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled(); |
443 | |
444 | return mContent->IsElement() && mContent->AsElement()->AttrValueIs( |
445 | kNameSpaceID_None, nsGkAtoms::disabled, |
446 | nsGkAtoms::_true, eCaseMatters); |
447 | } |
448 | |
449 | Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY, |
450 | EWhichChildAtPoint aWhichChild) { |
451 | Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild); |
452 | if (aWhichChild != EWhichChildAtPoint::DirectChild && child && |
453 | child->IsOuterDoc()) { |
454 | child = child->ChildAtPoint(aX, aY, aWhichChild); |
455 | } |
456 | |
457 | return child; |
458 | } |
459 | |
460 | LocalAccessible* LocalAccessible::LocalChildAtPoint( |
461 | int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) { |
462 | // If we can't find the point in a child, we will return the fallback answer: |
463 | // we return |this| if the point is within it, otherwise nullptr. |
464 | LocalAccessible* fallbackAnswer = nullptr; |
465 | LayoutDeviceIntRect rect = Bounds(); |
466 | if (rect.Contains(aX, aY)) fallbackAnswer = this; |
467 | |
468 | if (nsAccUtils::MustPrune(this)) { // Do not dig any further |
469 | return fallbackAnswer; |
470 | } |
471 | |
472 | // Search an accessible at the given point starting from accessible document |
473 | // because containing block (see CSS2) for out of flow element (for example, |
474 | // absolutely positioned element) may be different from its DOM parent and |
475 | // therefore accessible for containing block may be different from accessible |
476 | // for DOM parent but GetFrameForPoint() should be called for containing block |
477 | // to get an out of flow element. |
478 | DocAccessible* accDocument = Document(); |
479 | NS_ENSURE_TRUE(accDocument, nullptr)do { if ((__builtin_expect(!!(!(accDocument)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "accDocument" ") failed" , nullptr, "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 479); return nullptr; } } while (false); |
480 | |
481 | nsIFrame* rootFrame = accDocument->GetFrame(); |
482 | NS_ENSURE_TRUE(rootFrame, nullptr)do { if ((__builtin_expect(!!(!(rootFrame)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "rootFrame" ") failed", nullptr , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 482); return nullptr; } } while (false); |
483 | |
484 | nsIFrame* startFrame = rootFrame; |
485 | |
486 | // Check whether the point is at popup content. |
487 | nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr); |
488 | NS_ENSURE_TRUE(rootWidget, nullptr)do { if ((__builtin_expect(!!(!(rootWidget)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "rootWidget" ") failed", nullptr, "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 488); return nullptr; } } while (false); |
489 | |
490 | LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds(); |
491 | |
492 | auto point = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y()); |
493 | |
494 | nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint( |
495 | accDocument->PresContext()->GetRootPresContext(), rootWidget, point); |
496 | if (popupFrame) { |
497 | // If 'this' accessible is not inside the popup then ignore the popup when |
498 | // searching an accessible at point. |
499 | DocAccessible* popupDoc = |
500 | GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc()); |
501 | LocalAccessible* popupAcc = |
502 | popupDoc->GetAccessibleOrContainer(popupFrame->GetContent()); |
503 | LocalAccessible* popupChild = this; |
504 | while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) { |
505 | popupChild = popupChild->LocalParent(); |
506 | } |
507 | |
508 | if (popupChild == popupAcc) startFrame = popupFrame; |
509 | } |
510 | |
511 | nsPresContext* presContext = startFrame->PresContext(); |
512 | nsRect screenRect = startFrame->GetScreenRectInAppUnits(); |
513 | nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(), |
514 | presContext->DevPixelsToAppUnits(aY) - screenRect.Y()); |
515 | |
516 | nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint( |
517 | RelativeTo{startFrame, ViewportType::Visual}, offset); |
518 | |
519 | nsIContent* content = nullptr; |
520 | if (!foundFrame || !(content = foundFrame->GetContent())) { |
521 | return fallbackAnswer; |
522 | } |
523 | |
524 | // Get accessible for the node with the point or the first accessible in |
525 | // the DOM parent chain. |
526 | DocAccessible* contentDocAcc = |
527 | GetAccService()->GetDocAccessible(content->OwnerDoc()); |
528 | |
529 | // contentDocAcc in some circumstances can be nullptr. See bug 729861 |
530 | NS_ASSERTION(contentDocAcc, "could not get the document accessible")do { if (!(contentDocAcc)) { NS_DebugBreak(NS_DEBUG_ASSERTION , "could not get the document accessible", "contentDocAcc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 530); MOZ_PretendNoReturn(); } } while (0); |
531 | if (!contentDocAcc) return fallbackAnswer; |
532 | |
533 | LocalAccessible* accessible = |
534 | contentDocAcc->GetAccessibleOrContainer(content); |
535 | if (!accessible) return fallbackAnswer; |
536 | |
537 | // Hurray! We have an accessible for the frame that layout gave us. |
538 | // Since DOM node of obtained accessible may be out of flow then we should |
539 | // ensure obtained accessible is a child of this accessible. |
540 | LocalAccessible* child = accessible; |
541 | while (child != this) { |
542 | LocalAccessible* parent = child->LocalParent(); |
543 | if (!parent) { |
544 | // Reached the top of the hierarchy. These bounds were inside an |
545 | // accessible that is not a descendant of this one. |
546 | return fallbackAnswer; |
547 | } |
548 | |
549 | // If we landed on a legitimate child of |this|, and we want the direct |
550 | // child, return it here. |
551 | if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) { |
552 | return child; |
553 | } |
554 | |
555 | child = parent; |
556 | } |
557 | |
558 | // Manually walk through accessible children and see if the are within this |
559 | // point. Skip offscreen or invisible accessibles. This takes care of cases |
560 | // where layout won't walk into things for us, such as image map areas and |
561 | // sub documents (XXX: subdocuments should be handled by methods of |
562 | // OuterDocAccessibles). |
563 | uint32_t childCount = accessible->ChildCount(); |
564 | if (childCount == 1 && accessible->IsOuterDoc() && |
565 | accessible->FirstChild()->IsRemote()) { |
566 | // No local children. |
567 | return accessible; |
568 | } |
569 | for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) { |
570 | LocalAccessible* child = accessible->LocalChildAt(childIdx); |
571 | |
572 | LayoutDeviceIntRect childRect = child->Bounds(); |
573 | if (childRect.Contains(aX, aY) && |
574 | (child->State() & states::INVISIBLE) == 0) { |
575 | if (aWhichChild == EWhichChildAtPoint::DeepestChild) { |
576 | return child->LocalChildAtPoint(aX, aY, |
577 | EWhichChildAtPoint::DeepestChild); |
578 | } |
579 | |
580 | return child; |
581 | } |
582 | } |
583 | |
584 | return accessible; |
585 | } |
586 | |
587 | nsIFrame* LocalAccessible::FindNearestAccessibleAncestorFrame() { |
588 | nsIFrame* frame = GetFrame(); |
589 | if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && |
590 | nsLayoutUtils::IsReallyFixedPos(frame)) { |
591 | return mDoc->PresShellPtr()->GetRootFrame(); |
592 | } |
593 | |
594 | if (IsDoc()) { |
595 | // We bound documents by their own frame, which is their PresShell's root |
596 | // frame. We cache the document offset elsewhere in BundleFieldsForCache |
597 | // using the nsGkAtoms::crossorigin attribute. |
598 | MOZ_ASSERT(frame, "DocAccessibles should always have a frame")do { static_assert( mozilla::detail::AssertionConditionType< decltype(frame)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(frame))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("frame" " (" "DocAccessibles should always have a frame" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 598); AnnotateMozCrashReason("MOZ_ASSERT" "(" "frame" ") (" "DocAccessibles should always have a frame" ")"); do { MOZ_CrashSequence (__null, 598); __attribute__((nomerge)) ::abort(); } while (false ); } } while (false); |
599 | return frame; |
600 | } |
601 | |
602 | // Iterate through accessible's ancestors to find one with a frame. |
603 | LocalAccessible* ancestor = mParent; |
604 | while (ancestor) { |
605 | if (nsIFrame* boundingFrame = ancestor->GetFrame()) { |
606 | return boundingFrame; |
607 | } |
608 | ancestor = ancestor->LocalParent(); |
609 | } |
610 | |
611 | MOZ_ASSERT_UNREACHABLE("No ancestor with frame?")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "No ancestor with frame?" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 611); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "No ancestor with frame?" ")"); do { MOZ_CrashSequence(__null, 611); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
612 | return nsLayoutUtils::GetContainingBlockForClientRect(frame); |
613 | } |
614 | |
615 | nsRect LocalAccessible::ParentRelativeBounds() { |
616 | nsIFrame* frame = GetFrame(); |
617 | if (frame && mContent) { |
618 | nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame(); |
619 | nsRect result = nsLayoutUtils::GetAllInFlowRectsUnion(frame, boundingFrame); |
620 | |
621 | if (result.IsEmpty()) { |
622 | // If we end up with a 0x0 rect from above (or one with negative |
623 | // height/width) we should try using the ink overflow rect instead. If we |
624 | // use this rect, our relative bounds will match the bounds of what |
625 | // appears visually. We do this because some web authors (icloud.com for |
626 | // example) employ things like 0x0 buttons with visual overflow. Without |
627 | // this, such frames aren't navigable by screen readers. |
628 | result = frame->InkOverflowRectRelativeToSelf(); |
629 | result.MoveBy(frame->GetOffsetTo(boundingFrame)); |
630 | } |
631 | |
632 | if (boundingFrame->GetRect().IsEmpty() || |
633 | nsLayoutUtils::GetNextContinuationOrIBSplitSibling(boundingFrame)) { |
634 | // Constructing a bounding box across a frame that has an IB split means |
635 | // the origin is likely be different from that of boundingFrame. |
636 | // Descendants will need their parent-relative bounds adjusted |
637 | // accordingly, since parent-relative bounds are constructed to the |
638 | // bounding box of the entire element and not each individual IB split |
639 | // frame. In the case that boundingFrame's rect is empty, |
640 | // GetAllInFlowRectsUnion might exclude its origin. For example, if |
641 | // boundingFrame is empty with an origin of (0, -840) but has a non-empty |
642 | // ib-split-sibling with (0, 0), the union rect will originate at (0, 0). |
643 | // This means the bounds returned for our parent Accessible might be |
644 | // offset from boundingFrame's rect. Since result is currently relative to |
645 | // boundingFrame's rect, we might need to adjust it to make it parent |
646 | // relative. |
647 | nsRect boundingUnion = |
648 | nsLayoutUtils::GetAllInFlowRectsUnion(boundingFrame, boundingFrame); |
649 | if (!boundingUnion.IsEmpty()) { |
650 | // The origin of boundingUnion is relative to boundingFrame, meaning |
651 | // when we call MoveBy on result with this value we're offsetting |
652 | // `result` by the distance boundingFrame's origin was moved to |
653 | // construct its bounding box. |
654 | result.MoveBy(-boundingUnion.TopLeft()); |
655 | } else { |
656 | // Since GetAllInFlowRectsUnion returned an empty rect on our parent |
657 | // Accessible, we would have used the ink overflow rect. However, |
658 | // GetAllInFlowRectsUnion calculates relative to the bounding frame's |
659 | // main rect, not its ink overflow rect. We need to adjust for the ink |
660 | // overflow offset to make our result parent relative. |
661 | nsRect boundingOverflow = |
662 | boundingFrame->InkOverflowRectRelativeToSelf(); |
663 | result.MoveBy(-boundingOverflow.TopLeft()); |
664 | } |
665 | } |
666 | |
667 | if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && |
668 | nsLayoutUtils::IsReallyFixedPos(frame)) { |
669 | // If we're dealing with a fixed position frame, we've already made it |
670 | // relative to the document which should have gotten rid of its scroll |
671 | // offset. |
672 | return result; |
673 | } |
674 | |
675 | if (ScrollContainerFrame* sf = |
676 | mParent == mDoc |
677 | ? mDoc->PresShellPtr()->GetRootScrollContainerFrame() |
678 | : boundingFrame->GetScrollTargetFrame()) { |
679 | // If boundingFrame has a scroll position, result is currently relative |
680 | // to that. Instead, we want result to remain the same regardless of |
681 | // scrolling. We then subtract the scroll position later when |
682 | // calculating absolute bounds. We do this because we don't want to push |
683 | // cache updates for the bounds of all descendants every time we scroll. |
684 | nsPoint scrollPos = sf->GetScrollPosition().ApplyResolution( |
685 | mDoc->PresShellPtr()->GetResolution()); |
686 | result.MoveBy(scrollPos.x, scrollPos.y); |
687 | } |
688 | |
689 | return result; |
690 | } |
691 | |
692 | return nsRect(); |
693 | } |
694 | |
695 | nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const { |
696 | nsIFrame* frame = GetFrame(); |
697 | if (frame && mContent) { |
698 | *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame); |
699 | nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion( |
700 | frame, *aBoundingFrame, |
701 | nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms); |
702 | |
703 | if (unionRect.IsEmpty()) { |
704 | // If we end up with a 0x0 rect from above (or one with negative |
705 | // height/width) we should try using the ink overflow rect instead. If we |
706 | // use this rect, our relative bounds will match the bounds of what |
707 | // appears visually. We do this because some web authors (icloud.com for |
708 | // example) employ things like 0x0 buttons with visual overflow. Without |
709 | // this, such frames aren't navigable by screen readers. |
710 | nsRect overflow = frame->InkOverflowRectRelativeToSelf(); |
711 | nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow); |
712 | return overflow; |
713 | } |
714 | |
715 | return unionRect; |
716 | } |
717 | |
718 | return nsRect(); |
719 | } |
720 | |
721 | nsRect LocalAccessible::BoundsInAppUnits() const { |
722 | nsIFrame* boundingFrame = nullptr; |
723 | nsRect unionRectTwips = RelativeBounds(&boundingFrame); |
724 | if (!boundingFrame) { |
725 | return nsRect(); |
726 | } |
727 | |
728 | PresShell* presShell = mDoc->PresContext()->PresShell(); |
729 | |
730 | // We need to inverse translate with the offset of the edge of the visual |
731 | // viewport from top edge of the layout viewport. |
732 | nsPoint viewportOffset = presShell->GetVisualViewportOffset() - |
733 | presShell->GetLayoutViewportOffset(); |
734 | unionRectTwips.MoveBy(-viewportOffset); |
735 | |
736 | // We need to take into account a non-1 resolution set on the presshell. |
737 | // This happens with async pinch zooming. Here we scale the bounds before |
738 | // adding the screen-relative offset. |
739 | unionRectTwips.ScaleRoundOut(presShell->GetResolution()); |
740 | // We have the union of the rectangle, now we need to put it in absolute |
741 | // screen coords. |
742 | nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits(); |
743 | unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y()); |
744 | |
745 | return unionRectTwips; |
746 | } |
747 | |
748 | LayoutDeviceIntRect LocalAccessible::Bounds() const { |
749 | return LayoutDeviceIntRect::FromAppUnitsToNearest( |
750 | BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel()); |
751 | } |
752 | |
753 | void LocalAccessible::SetSelected(bool aSelect) { |
754 | if (!HasOwnContent()) return; |
755 | |
756 | if (nsAccUtils::GetSelectableContainer(this, State()) && aSelect) { |
757 | TakeFocus(); |
758 | } |
759 | } |
760 | |
761 | void LocalAccessible::TakeSelection() { |
762 | LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State()); |
763 | if (select) { |
764 | if (select->State() & states::MULTISELECTABLE) select->UnselectAll(); |
765 | SetSelected(true); |
766 | } |
767 | } |
768 | |
769 | void LocalAccessible::TakeFocus() const { |
770 | nsIFrame* frame = GetFrame(); |
771 | if (!frame) return; |
772 | |
773 | nsIContent* focusContent = mContent; |
774 | |
775 | // If the accessible focus is managed by container widget then focus the |
776 | // widget and set the accessible as its current item. |
777 | if (!frame->IsFocusable()) { |
778 | LocalAccessible* widget = ContainerWidget(); |
779 | if (widget && widget->AreItemsOperable()) { |
780 | nsIContent* widgetElm = widget->GetContent(); |
781 | nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame(); |
782 | if (widgetFrame && widgetFrame->IsFocusable()) { |
783 | focusContent = widgetElm; |
784 | widget->SetCurrentItem(this); |
785 | } |
786 | } |
787 | } |
788 | |
789 | if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { |
790 | dom::AutoHandlingUserInputStatePusher inputStatePusher(true); |
791 | // XXXbz: Can we actually have a non-element content here? |
792 | RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent); |
793 | fm->SetFocus(element, 0); |
794 | } |
795 | } |
796 | |
797 | void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument, |
798 | nsIContent* aElm, |
799 | nsString& aName) { |
800 | LocalAccessible* label = nullptr; |
801 | XULLabelIterator iter(aDocument, aElm); |
802 | while ((label = iter.Next())) { |
803 | // Check if label's value attribute is used |
804 | label->Elm()->GetAttr(nsGkAtoms::value, aName); |
805 | if (aName.IsEmpty()) { |
806 | // If no value attribute, a non-empty label must contain |
807 | // children that define its text -- possibly using HTML |
808 | nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName); |
809 | } |
810 | } |
811 | aName.CompressWhitespace(); |
812 | } |
813 | |
814 | void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm, |
815 | nsString& aName) { |
816 | /** |
817 | * 3 main cases for XUL Controls to be labeled |
818 | * 1 - control contains label="foo" |
819 | * 2 - non-child label contains control="controlID" |
820 | * - label has either value="foo" or children |
821 | * 3 - name from subtree; e.g. a child label element |
822 | * Cases 1 and 2 are handled here. |
823 | * Case 3 is handled by GetNameFromSubtree called in NativeName. |
824 | * Once a label is found, the search is discontinued, so a control |
825 | * that has a label attribute as well as having a label external to |
826 | * the control that uses the control="controlID" syntax will use |
827 | * the label attribute for its Name. |
828 | */ |
829 | |
830 | // CASE #1 (via label attribute) -- great majority of the cases |
831 | // Only do this if this is not a select control element, which uses label |
832 | // attribute to indicate, which option is selected. |
833 | nsCOMPtr<nsIDOMXULSelectControlElement> select = |
834 | aElm->AsElement()->AsXULSelectControl(); |
835 | if (!select) { |
836 | aElm->AsElement()->GetAttr(nsGkAtoms::label, aName); |
837 | } |
838 | |
839 | // CASE #2 -- label as <label control="id" ... ></label> |
840 | if (aName.IsEmpty()) { |
841 | NameFromAssociatedXULLabel(aDocument, aElm, aName); |
842 | } |
843 | |
844 | aName.CompressWhitespace(); |
845 | } |
846 | |
847 | nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) { |
848 | NS_ENSURE_ARG_POINTER(aEvent)do { if ((__builtin_expect(!!(!(aEvent)), 0))) { NS_DebugBreak (NS_DEBUG_WARNING, "NS_ENSURE_TRUE(" "aEvent" ") failed", nullptr , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 848); return NS_ERROR_INVALID_POINTER; } } while (false); |
849 | |
850 | if (profiler_thread_is_being_profiled_for_markers()) { |
851 | nsAutoCString strEventType; |
852 | GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType); |
853 | nsAutoCString strMarker; |
854 | strMarker.AppendLiteral("A11y Event - "); |
855 | strMarker.Append(strEventType); |
856 | PROFILER_MARKER_UNTYPED(strMarker, A11Y)do { ; do { if (profiler_is_collecting_markers()) { ::profiler_add_marker_impl (strMarker, ::geckoprofiler::category::A11Y); } } while (false ); } while (false); |
857 | } |
858 | |
859 | if (IPCAccessibilityActive() && Document()) { |
860 | DocAccessibleChild* ipcDoc = mDoc->IPCDoc(); |
861 | // If ipcDoc is null, we can't fire the event to the client. We shouldn't |
862 | // have fired the event in the first place, since this makes events |
863 | // inconsistent for local and remote documents. To avoid this, don't call |
864 | // nsEventShell::FireEvent on a DocAccessible for which |
865 | // HasLoadState(eTreeConstructed) is false. |
866 | MOZ_ASSERT(ipcDoc)do { static_assert( mozilla::detail::AssertionConditionType< decltype(ipcDoc)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(ipcDoc))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("ipcDoc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 866); AnnotateMozCrashReason("MOZ_ASSERT" "(" "ipcDoc" ")") ; do { MOZ_CrashSequence(__null, 866); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
867 | if (ipcDoc) { |
868 | uint64_t id = aEvent->GetAccessible()->ID(); |
869 | |
870 | switch (aEvent->GetEventType()) { |
871 | case nsIAccessibleEvent::EVENT_SHOW: |
872 | ipcDoc->ShowEvent(downcast_accEvent(aEvent)); |
873 | break; |
874 | |
875 | case nsIAccessibleEvent::EVENT_HIDE: |
876 | ipcDoc->PushMutationEventData( |
877 | HideEventData{id, aEvent->IsFromUserInput()}); |
878 | break; |
879 | |
880 | case nsIAccessibleEvent::EVENT_INNER_REORDER: |
881 | case nsIAccessibleEvent::EVENT_REORDER: |
882 | if (IsTable()) { |
883 | SendCache(CacheDomain::Table, CacheUpdateType::Update, |
884 | /*aAppendEventData*/ true); |
885 | } |
886 | |
887 | #if defined(XP_WIN) |
888 | if (HasOwnContent() && mContent->IsMathMLElement()) { |
889 | // For any change in a MathML subtree, update the innerHTML cache on |
890 | // the root math element. |
891 | for (LocalAccessible* acc = this; acc; acc = acc->LocalParent()) { |
892 | if (acc->HasOwnContent() && |
893 | acc->mContent->IsMathMLElement(nsGkAtoms::math)) { |
894 | mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML); |
895 | } |
896 | } |
897 | } |
898 | #endif // defined(XP_WIN) |
899 | |
900 | // reorder events on the application acc aren't necessary to tell the |
901 | // parent about new top level documents. |
902 | if (!aEvent->GetAccessible()->IsApplication()) { |
903 | ipcDoc->PushMutationEventData( |
904 | ReorderEventData{id, aEvent->GetEventType()}); |
905 | } |
906 | break; |
907 | case nsIAccessibleEvent::EVENT_STATE_CHANGE: { |
908 | AccStateChangeEvent* event = downcast_accEvent(aEvent); |
909 | ipcDoc->SendStateChangeEvent(id, event->GetState(), |
910 | event->IsStateEnabled()); |
911 | break; |
912 | } |
913 | case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { |
914 | AccCaretMoveEvent* event = downcast_accEvent(aEvent); |
915 | ipcDoc->SendCaretMoveEvent( |
916 | id, event->GetCaretOffset(), event->IsSelectionCollapsed(), |
917 | event->IsAtEndOfLine(), event->GetGranularity(), |
918 | event->IsFromUserInput()); |
919 | break; |
920 | } |
921 | case nsIAccessibleEvent::EVENT_TEXT_INSERTED: |
922 | case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { |
923 | AccTextChangeEvent* event = downcast_accEvent(aEvent); |
924 | ipcDoc->PushMutationEventData(TextChangeEventData{ |
925 | id, event->ModifiedText(), event->GetStartOffset(), |
926 | event->GetLength(), event->IsTextInserted(), |
927 | event->IsFromUserInput()}); |
928 | break; |
929 | } |
930 | case nsIAccessibleEvent::EVENT_SELECTION: |
931 | case nsIAccessibleEvent::EVENT_SELECTION_ADD: |
932 | case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { |
933 | AccSelChangeEvent* selEvent = downcast_accEvent(aEvent); |
934 | ipcDoc->SendSelectionEvent(id, selEvent->Widget()->ID(), |
935 | aEvent->GetEventType()); |
936 | break; |
937 | } |
938 | case nsIAccessibleEvent::EVENT_FOCUS: |
939 | ipcDoc->SendFocusEvent(id); |
940 | break; |
941 | case nsIAccessibleEvent::EVENT_SCROLLING_END: |
942 | case nsIAccessibleEvent::EVENT_SCROLLING: { |
943 | AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent); |
944 | ipcDoc->SendScrollingEvent( |
945 | id, aEvent->GetEventType(), scrollingEvent->ScrollX(), |
946 | scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(), |
947 | scrollingEvent->MaxScrollY()); |
948 | break; |
949 | } |
950 | #if !defined(XP_WIN) |
951 | case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: { |
952 | AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent); |
953 | ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(), |
954 | announcementEvent->Priority()); |
955 | break; |
956 | } |
957 | #endif // !defined(XP_WIN) |
958 | case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: { |
959 | AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent); |
960 | AutoTArray<TextRange, 1> ranges; |
961 | textSelChangeEvent->SelectionRanges(&ranges); |
962 | nsTArray<TextRangeData> textRangeData(ranges.Length()); |
963 | for (size_t i = 0; i < ranges.Length(); i++) { |
964 | const TextRange& range = ranges.ElementAt(i); |
965 | LocalAccessible* start = range.StartContainer()->AsLocal(); |
966 | LocalAccessible* end = range.EndContainer()->AsLocal(); |
967 | textRangeData.AppendElement(TextRangeData(start->ID(), end->ID(), |
968 | range.StartOffset(), |
969 | range.EndOffset())); |
970 | } |
971 | ipcDoc->SendTextSelectionChangeEvent(id, textRangeData); |
972 | break; |
973 | } |
974 | case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE: |
975 | case nsIAccessibleEvent::EVENT_NAME_CHANGE: { |
976 | SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update); |
977 | ipcDoc->SendEvent(id, aEvent->GetEventType()); |
978 | break; |
979 | } |
980 | case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: |
981 | case nsIAccessibleEvent::EVENT_VALUE_CHANGE: { |
982 | SendCache(CacheDomain::Value, CacheUpdateType::Update); |
983 | ipcDoc->SendEvent(id, aEvent->GetEventType()); |
984 | break; |
985 | } |
986 | default: |
987 | ipcDoc->SendEvent(id, aEvent->GetEventType()); |
988 | } |
989 | } |
990 | } |
991 | |
992 | if (nsCoreUtils::AccEventObserversExist()) { |
993 | nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent)); |
994 | } |
995 | |
996 | if (IPCAccessibilityActive()) { |
997 | return NS_OK; |
998 | } |
999 | |
1000 | if (IsDefunct()) { |
1001 | // This could happen if there is an XPCOM observer, since script might run |
1002 | // which mutates the tree. |
1003 | return NS_OK; |
1004 | } |
1005 | |
1006 | LocalAccessible* target = aEvent->GetAccessible(); |
1007 | switch (aEvent->GetEventType()) { |
1008 | case nsIAccessibleEvent::EVENT_SHOW: { |
1009 | // Scope for PerfStats |
1010 | AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns)AutoProfilerTextMarker raiiObject1010( "a11y::PlatformShowHideEvent" , ::mozilla::baseprofiler::category::A11Y, {}, ""_ns); |
1011 | PerfStats::AutoMetricRecording< |
1012 | PerfStats::Metric::A11Y_PlatformShowHideEvent> |
1013 | autoRecording; |
1014 | // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK: |
1015 | // THIS CODE IS MEASURING TIMINGS. |
1016 | PlatformShowHideEvent(target, target->LocalParent(), true, |
1017 | aEvent->IsFromUserInput()); |
1018 | break; |
1019 | } |
1020 | case nsIAccessibleEvent::EVENT_HIDE: { |
1021 | // Scope for PerfStats |
1022 | AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns)AutoProfilerTextMarker raiiObject1022( "a11y::PlatformShowHideEvent" , ::mozilla::baseprofiler::category::A11Y, {}, ""_ns); |
1023 | PerfStats::AutoMetricRecording< |
1024 | PerfStats::Metric::A11Y_PlatformShowHideEvent> |
1025 | autoRecording; |
1026 | // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK: |
1027 | // THIS CODE IS MEASURING TIMINGS. |
1028 | PlatformShowHideEvent(target, target->LocalParent(), false, |
1029 | aEvent->IsFromUserInput()); |
1030 | break; |
1031 | } |
1032 | case nsIAccessibleEvent::EVENT_STATE_CHANGE: { |
1033 | AccStateChangeEvent* event = downcast_accEvent(aEvent); |
1034 | PlatformStateChangeEvent(target, event->GetState(), |
1035 | event->IsStateEnabled()); |
1036 | break; |
1037 | } |
1038 | case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { |
1039 | AccCaretMoveEvent* event = downcast_accEvent(aEvent); |
1040 | LayoutDeviceIntRect rect; |
1041 | // The caret rect is only used on Windows, so just pass an empty rect on |
1042 | // other platforms. |
1043 | // XXX We pass an empty rect on Windows as well because |
1044 | // AccessibleWrap::UpdateSystemCaretFor currently needs to call |
1045 | // HyperTextAccessible::GetCaretRect again to get the widget and there's |
1046 | // no point calling it twice. |
1047 | PlatformCaretMoveEvent( |
1048 | target, event->GetCaretOffset(), event->IsSelectionCollapsed(), |
1049 | event->GetGranularity(), rect, event->IsFromUserInput()); |
1050 | break; |
1051 | } |
1052 | case nsIAccessibleEvent::EVENT_TEXT_INSERTED: |
1053 | case nsIAccessibleEvent::EVENT_TEXT_REMOVED: { |
1054 | AccTextChangeEvent* event = downcast_accEvent(aEvent); |
1055 | const nsString& text = event->ModifiedText(); |
1056 | PlatformTextChangeEvent(target, text, event->GetStartOffset(), |
1057 | event->GetLength(), event->IsTextInserted(), |
1058 | event->IsFromUserInput()); |
1059 | break; |
1060 | } |
1061 | case nsIAccessibleEvent::EVENT_SELECTION: |
1062 | case nsIAccessibleEvent::EVENT_SELECTION_ADD: |
1063 | case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: { |
1064 | AccSelChangeEvent* selEvent = downcast_accEvent(aEvent); |
1065 | PlatformSelectionEvent(target, selEvent->Widget(), |
1066 | aEvent->GetEventType()); |
1067 | break; |
1068 | } |
1069 | case nsIAccessibleEvent::EVENT_FOCUS: { |
1070 | LayoutDeviceIntRect rect; |
1071 | // The caret rect is only used on Windows, so just pass an empty rect on |
1072 | // other platforms. |
1073 | #ifdef XP_WIN |
1074 | if (HyperTextAccessible* text = target->AsHyperText()) { |
1075 | nsIWidget* widget = nullptr; |
1076 | rect = text->GetCaretRect(&widget); |
1077 | } |
1078 | #endif |
1079 | PlatformFocusEvent(target, rect); |
1080 | break; |
1081 | } |
1082 | #if defined(ANDROID) |
1083 | case nsIAccessibleEvent::EVENT_SCROLLING_END: |
1084 | case nsIAccessibleEvent::EVENT_SCROLLING: { |
1085 | AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent); |
1086 | PlatformScrollingEvent( |
1087 | target, aEvent->GetEventType(), scrollingEvent->ScrollX(), |
1088 | scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(), |
1089 | scrollingEvent->MaxScrollY()); |
1090 | break; |
1091 | } |
1092 | case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: { |
1093 | AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent); |
1094 | PlatformAnnouncementEvent(target, announcementEvent->Announcement(), |
1095 | announcementEvent->Priority()); |
1096 | break; |
1097 | } |
1098 | #endif // defined(ANDROID) |
1099 | #if defined(MOZ_WIDGET_COCOA) |
1100 | case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: { |
1101 | AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent); |
1102 | AutoTArray<TextRange, 1> ranges; |
1103 | textSelChangeEvent->SelectionRanges(&ranges); |
1104 | PlatformTextSelectionChangeEvent(target, ranges); |
1105 | break; |
1106 | } |
1107 | #endif // defined(MOZ_WIDGET_COCOA) |
1108 | default: |
1109 | PlatformEvent(target, aEvent->GetEventType()); |
1110 | } |
1111 | |
1112 | return NS_OK; |
1113 | } |
1114 | |
1115 | already_AddRefed<AccAttributes> LocalAccessible::Attributes() { |
1116 | RefPtr<AccAttributes> attributes = NativeAttributes(); |
1117 | if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget(); |
1118 | |
1119 | // 'xml-roles' attribute coming from ARIA. |
1120 | nsString xmlRoles; |
1121 | if (nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::role, |
1122 | xmlRoles) && |
1123 | !xmlRoles.IsEmpty()) { |
1124 | attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles)); |
1125 | } else if (nsAtom* landmark = LandmarkRole()) { |
1126 | // 'xml-roles' attribute for landmark. |
1127 | attributes->SetAttribute(nsGkAtoms::xmlroles, landmark); |
1128 | } |
1129 | |
1130 | // Expose object attributes from ARIA attributes. |
1131 | aria::AttrIterator attribIter(mContent); |
1132 | while (attribIter.Next()) { |
1133 | if (attribIter.AttrName() == nsGkAtoms::aria_placeholder && |
1134 | attributes->HasAttribute(nsGkAtoms::placeholder)) { |
1135 | // If there is an HTML placeholder attribute exposed by |
1136 | // HTMLTextFieldAccessible::NativeAttributes, don't expose |
1137 | // aria-placeholder. |
1138 | continue; |
1139 | } |
1140 | attribIter.ExposeAttr(attributes); |
1141 | } |
1142 | |
1143 | // If there is no aria-live attribute then expose default value of 'live' |
1144 | // object attribute used for ARIA role of this accessible. |
1145 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1146 | if (roleMapEntry) { |
1147 | if (roleMapEntry->Is(nsGkAtoms::searchbox)) { |
1148 | attributes->SetAttribute(nsGkAtoms::textInputType, nsGkAtoms::search); |
1149 | } |
1150 | |
1151 | if (!attributes->HasAttribute(nsGkAtoms::aria_live)) { |
1152 | nsString live; |
1153 | if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) { |
1154 | attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live)); |
1155 | } |
1156 | } |
1157 | } |
1158 | |
1159 | return attributes.forget(); |
1160 | } |
1161 | |
1162 | already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() { |
1163 | RefPtr<AccAttributes> attributes = new AccAttributes(); |
1164 | |
1165 | // We support values, so expose the string value as well, via the valuetext |
1166 | // object attribute. We test for the value interface because we don't want |
1167 | // to expose traditional Value() information such as URL's on links and |
1168 | // documents, or text in an input. |
1169 | if (HasNumericValue()) { |
1170 | nsString valuetext; |
1171 | Value(valuetext); |
1172 | attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext)); |
1173 | } |
1174 | |
1175 | // Expose checkable object attribute if the accessible has checkable state |
1176 | if (State() & states::CHECKABLE) { |
1177 | attributes->SetAttribute(nsGkAtoms::checkable, true); |
1178 | } |
1179 | |
1180 | // Expose 'explicit-name' attribute. |
1181 | nsAutoString name; |
1182 | if (Name(name) != eNameFromSubtree && !name.IsVoid()) { |
1183 | attributes->SetAttribute(nsGkAtoms::explicit_name, true); |
1184 | } |
1185 | |
1186 | bool hierarchical = false; |
1187 | uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical); |
1188 | if (itemCount) { |
1189 | attributes->SetAttribute(nsGkAtoms::child_item_count, |
1190 | static_cast<int32_t>(itemCount)); |
1191 | } |
1192 | |
1193 | if (hierarchical) { |
1194 | attributes->SetAttribute(nsGkAtoms::tree, true); |
1195 | } |
1196 | |
1197 | // If the accessible doesn't have own content (such as list item bullet or |
1198 | // xul tree item) then don't calculate content based attributes. |
1199 | if (!HasOwnContent()) return attributes.forget(); |
1200 | |
1201 | // Get container-foo computed live region properties based on the closest |
1202 | // container with the live region attribute. Inner nodes override outer nodes |
1203 | // within the same document. The inner nodes can be used to override live |
1204 | // region behavior on more general outer nodes. |
1205 | nsAccUtils::SetLiveContainerAttributes(attributes, this); |
1206 | |
1207 | if (!mContent->IsElement()) return attributes.forget(); |
1208 | |
1209 | nsString id; |
1210 | if (nsCoreUtils::GetID(mContent, id)) { |
1211 | attributes->SetAttribute(nsGkAtoms::id, std::move(id)); |
1212 | } |
1213 | |
1214 | // Expose class because it may have useful microformat information. |
1215 | nsString _class; |
1216 | if (mContent->AsElement()->GetAttr(nsGkAtoms::_class, _class)) { |
1217 | attributes->SetAttribute(nsGkAtoms::_class, std::move(_class)); |
1218 | } |
1219 | |
1220 | // Expose tag. |
1221 | attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom()); |
1222 | |
1223 | if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) { |
1224 | // Expose draggable object attribute. |
1225 | if (htmlElement->Draggable()) { |
1226 | attributes->SetAttribute(nsGkAtoms::draggable, true); |
1227 | } |
1228 | nsString popover; |
1229 | htmlElement->GetPopover(popover); |
1230 | if (!popover.IsEmpty()) { |
1231 | attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popover)); |
1232 | } |
1233 | } |
1234 | |
1235 | // Don't calculate CSS-based object attributes when: |
1236 | // 1. There is no frame (e.g. the accessible is unattached from the tree). |
1237 | // 2. This is an image map area. CSS is irrelevant here. Furthermore, we won't |
1238 | // be able to get the computed style if the map is unslotted in a shadow host. |
1239 | nsIFrame* f = mContent->GetPrimaryFrame(); |
1240 | if (!f || mContent->IsHTMLElement(nsGkAtoms::area)) { |
1241 | return attributes.forget(); |
1242 | } |
1243 | |
1244 | // Expose 'display' attribute. |
1245 | if (RefPtr<nsAtom> display = DisplayStyle()) { |
1246 | attributes->SetAttribute(nsGkAtoms::display, display); |
1247 | } |
1248 | |
1249 | const ComputedStyle& style = *f->Style(); |
1250 | auto Atomize = [&](nsCSSPropertyID aId) -> RefPtr<nsAtom> { |
1251 | nsAutoCString value; |
1252 | style.GetComputedPropertyValue(aId, value); |
1253 | return NS_Atomize(value); |
1254 | }; |
1255 | |
1256 | // Expose 'text-align' attribute. |
1257 | attributes->SetAttribute(nsGkAtoms::textAlign, |
1258 | Atomize(eCSSProperty_text_align)); |
1259 | |
1260 | // Expose 'text-indent' attribute. |
1261 | attributes->SetAttribute(nsGkAtoms::textIndent, |
1262 | Atomize(eCSSProperty_text_indent)); |
1263 | |
1264 | auto GetMargin = [&](mozilla::Side aSide) -> CSSCoord { |
1265 | // This is here only to guarantee that we do the same as getComputedStyle |
1266 | // does, so that we don't hit precision errors in tests. |
1267 | const auto margin = |
1268 | f->StyleMargin()->GetMargin(aSide, f->StyleDisplay()->mPosition); |
1269 | if (margin->ConvertsToLength()) { |
1270 | return margin->AsLengthPercentage().ToLengthInCSSPixels(); |
1271 | } |
1272 | |
1273 | nscoord coordVal = f->GetUsedMargin().Side(aSide); |
1274 | return CSSPixel::FromAppUnits(coordVal); |
1275 | }; |
1276 | |
1277 | // Expose 'margin-left' attribute. |
1278 | attributes->SetAttribute(nsGkAtoms::marginLeft, GetMargin(eSideLeft)); |
1279 | |
1280 | // Expose 'margin-right' attribute. |
1281 | attributes->SetAttribute(nsGkAtoms::marginRight, GetMargin(eSideRight)); |
1282 | |
1283 | // Expose 'margin-top' attribute. |
1284 | attributes->SetAttribute(nsGkAtoms::marginTop, GetMargin(eSideTop)); |
1285 | |
1286 | // Expose 'margin-bottom' attribute. |
1287 | attributes->SetAttribute(nsGkAtoms::marginBottom, GetMargin(eSideBottom)); |
1288 | |
1289 | // Expose data-at-shortcutkeys attribute for web applications and virtual |
1290 | // cursors. Currently mostly used by JAWS. |
1291 | nsString atShortcutKeys; |
1292 | if (mContent->AsElement()->GetAttr( |
1293 | kNameSpaceID_None, nsGkAtoms::dataAtShortcutkeys, atShortcutKeys)) { |
1294 | attributes->SetAttribute(nsGkAtoms::dataAtShortcutkeys, |
1295 | std::move(atShortcutKeys)); |
1296 | } |
1297 | |
1298 | return attributes.forget(); |
1299 | } |
1300 | |
1301 | bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) { |
1302 | return aAttribute == nsGkAtoms::aria_disabled || |
1303 | // The HTML element disabled state gets handled in |
1304 | // DocAccessible::ElementStateChanged. This matches |
1305 | // LocalAccessible::NativelyUnavailable. |
1306 | (aAttribute == nsGkAtoms::disabled && !mContent->IsHTMLElement()) || |
1307 | aAttribute == nsGkAtoms::tabindex || |
1308 | aAttribute == nsGkAtoms::aria_required || |
1309 | aAttribute == nsGkAtoms::aria_invalid || |
1310 | aAttribute == nsGkAtoms::aria_expanded || |
1311 | aAttribute == nsGkAtoms::aria_checked || |
1312 | (aAttribute == nsGkAtoms::aria_pressed && IsButton()) || |
1313 | aAttribute == nsGkAtoms::aria_readonly || |
1314 | aAttribute == nsGkAtoms::aria_current || |
1315 | aAttribute == nsGkAtoms::aria_haspopup || |
1316 | aAttribute == nsGkAtoms::aria_busy || |
1317 | aAttribute == nsGkAtoms::aria_multiline || |
1318 | aAttribute == nsGkAtoms::aria_multiselectable || |
1319 | // We track this for focusable state update |
1320 | aAttribute == nsGkAtoms::contenteditable || |
1321 | aAttribute == nsGkAtoms::popovertarget; |
1322 | } |
1323 | |
1324 | void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID, |
1325 | nsAtom* aAttribute, int32_t aModType, |
1326 | const nsAttrValue* aOldValue, |
1327 | uint64_t aOldState) { |
1328 | // Fire accessible event after short timer, because we need to wait for |
1329 | // DOM attribute & resulting layout to actually change. Otherwise, |
1330 | // assistive technology will retrieve the wrong state/value/selection info. |
1331 | |
1332 | CssAltContent::HandleAttributeChange(mContent, aNameSpaceID, aAttribute); |
1333 | |
1334 | // XXX todo |
1335 | // We still need to handle special HTML cases here |
1336 | // For example, if an <img>'s usemap attribute is modified |
1337 | // Otherwise it may just be a state change, for example an object changing |
1338 | // its visibility |
1339 | // |
1340 | // XXX todo: report aria state changes for "undefined" literal value changes |
1341 | // filed as bug 472142 |
1342 | // |
1343 | // XXX todo: invalidate accessible when aria state changes affect exposed |
1344 | // role filed as bug 472143 |
1345 | |
1346 | if (AttributeChangesState(aAttribute)) { |
1347 | uint64_t currState = State(); |
1348 | uint64_t diffState = currState ^ aOldState; |
1349 | if (diffState) { |
1350 | for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) { |
1351 | if (diffState & state) { |
1352 | RefPtr<AccEvent> stateChangeEvent = |
1353 | new AccStateChangeEvent(this, state, (currState & state)); |
1354 | mDoc->FireDelayedEvent(stateChangeEvent); |
1355 | } |
1356 | } |
1357 | } |
1358 | } |
1359 | |
1360 | if (aAttribute == nsGkAtoms::_class) { |
1361 | mDoc->QueueCacheUpdate(this, CacheDomain::DOMNodeIDAndClass); |
1362 | return; |
1363 | } |
1364 | |
1365 | // When a details object has its open attribute changed |
1366 | // we should fire a state-change event on the accessible of |
1367 | // its main summary |
1368 | if (aAttribute == nsGkAtoms::open) { |
1369 | // FromDetails checks if the given accessible belongs to |
1370 | // a details frame and also locates the accessible of its |
1371 | // main summary. |
1372 | if (HTMLSummaryAccessible* summaryAccessible = |
1373 | HTMLSummaryAccessible::FromDetails(this)) { |
1374 | RefPtr<AccEvent> expandedChangeEvent = |
1375 | new AccStateChangeEvent(summaryAccessible, states::EXPANDED); |
1376 | mDoc->FireDelayedEvent(expandedChangeEvent); |
1377 | return; |
1378 | } |
1379 | } |
1380 | |
1381 | // Check for namespaced ARIA attribute |
1382 | if (aNameSpaceID == kNameSpaceID_None) { |
1383 | // Check for hyphenated aria-foo property? |
1384 | if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) { |
1385 | uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute); |
1386 | if (!(attrFlags & ATTR_BYPASSOBJ)) { |
1387 | mDoc->QueueCacheUpdate(this, CacheDomain::ARIA); |
1388 | // For aria attributes like drag and drop changes we fire a generic |
1389 | // attribute change event; at least until native API comes up with a |
1390 | // more meaningful event. |
1391 | RefPtr<AccEvent> event = |
1392 | new AccObjectAttrChangedEvent(this, aAttribute); |
1393 | mDoc->FireDelayedEvent(event); |
1394 | } |
1395 | } |
1396 | } |
1397 | |
1398 | dom::Element* elm = Elm(); |
1399 | |
1400 | if (HasNumericValue() && |
1401 | (aAttribute == nsGkAtoms::aria_valuemax || |
1402 | aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min || |
1403 | aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) { |
1404 | mDoc->QueueCacheUpdate(this, CacheDomain::Value); |
1405 | return; |
1406 | } |
1407 | |
1408 | // Fire text value change event whenever aria-valuetext is changed. |
1409 | if (aAttribute == nsGkAtoms::aria_valuetext) { |
1410 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this); |
1411 | return; |
1412 | } |
1413 | |
1414 | if (aAttribute == nsGkAtoms::aria_valuenow) { |
1415 | if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_valuetext) || |
1416 | nsAccUtils::ARIAAttrValueIs(elm, nsGkAtoms::aria_valuetext, |
1417 | nsGkAtoms::_empty, eCaseMatters)) { |
1418 | // Fire numeric value change event when aria-valuenow is changed and |
1419 | // aria-valuetext is empty |
1420 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this); |
1421 | } else { |
1422 | // We need to update the cache here since we won't get an event if |
1423 | // aria-valuenow is shadowed by aria-valuetext. |
1424 | mDoc->QueueCacheUpdate(this, CacheDomain::Value); |
1425 | } |
1426 | return; |
1427 | } |
1428 | |
1429 | if (aAttribute == nsGkAtoms::aria_owns) { |
1430 | mDoc->Controller()->ScheduleRelocation(this); |
1431 | } |
1432 | |
1433 | // Fire name change and description change events. |
1434 | if (aAttribute == nsGkAtoms::aria_label || aAttribute == nsGkAtoms::label) { |
1435 | // A valid aria-labelledby would take precedence over an aria-label or a xul |
1436 | // label attribute. So if that relation exists the name won't change. |
1437 | AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby); |
1438 | if (!iter.NextElem()) { |
1439 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); |
1440 | } |
1441 | return; |
1442 | } |
1443 | |
1444 | if (aAttribute == nsGkAtoms::aria_description) { |
1445 | // A valid aria-describedby would take precedence so an aria-description |
1446 | // change won't change the description. |
1447 | AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby); |
1448 | if (!iter.NextElem()) { |
1449 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, |
1450 | this); |
1451 | } |
1452 | return; |
1453 | } |
1454 | |
1455 | if (aAttribute == nsGkAtoms::aria_describedby) { |
1456 | mDoc->QueueCacheUpdate(this, CacheDomain::Relations); |
1457 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this); |
1458 | if (aModType == dom::MutationEvent_Binding::MODIFICATION || |
1459 | aModType == dom::MutationEvent_Binding::ADDITION) { |
1460 | // The subtrees of the new aria-describedby targets might be used to |
1461 | // compute the description for this. Therefore, we need to set |
1462 | // the eHasDescriptionDependent flag on all Accessibles in these subtrees. |
1463 | AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby); |
1464 | while (LocalAccessible* target = iter.Next()) { |
1465 | target->ModifySubtreeContextFlags(eHasDescriptionDependent, true); |
1466 | } |
1467 | } |
1468 | return; |
1469 | } |
1470 | |
1471 | if (aAttribute == nsGkAtoms::aria_labelledby) { |
1472 | // We only queue cache updates for explicit relations. Implicit, reverse |
1473 | // relations are handled in ApplyCache and stored in a map on the remote |
1474 | // document itself. |
1475 | mDoc->QueueCacheUpdate(this, CacheDomain::Relations); |
1476 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); |
1477 | if (aModType == dom::MutationEvent_Binding::MODIFICATION || |
1478 | aModType == dom::MutationEvent_Binding::ADDITION) { |
1479 | // The subtrees of the new aria-labelledby targets might be used to |
1480 | // compute the name for this. Therefore, we need to set |
1481 | // the eHasNameDependent flag on all Accessibles in these subtrees. |
1482 | AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby); |
1483 | while (LocalAccessible* target = iter.Next()) { |
1484 | target->ModifySubtreeContextFlags(eHasNameDependent, true); |
1485 | } |
1486 | } |
1487 | return; |
1488 | } |
1489 | |
1490 | if ((aAttribute == nsGkAtoms::aria_expanded || |
1491 | aAttribute == nsGkAtoms::href) && |
1492 | (aModType == dom::MutationEvent_Binding::ADDITION || |
1493 | aModType == dom::MutationEvent_Binding::REMOVAL)) { |
1494 | // The presence of aria-expanded adds an expand/collapse action. |
1495 | mDoc->QueueCacheUpdate(this, CacheDomain::Actions); |
1496 | } |
1497 | |
1498 | if (aAttribute == nsGkAtoms::href || aAttribute == nsGkAtoms::src) { |
1499 | mDoc->QueueCacheUpdate(this, CacheDomain::Value); |
1500 | } |
1501 | |
1502 | if (aAttribute == nsGkAtoms::aria_controls || |
1503 | aAttribute == nsGkAtoms::aria_flowto || |
1504 | aAttribute == nsGkAtoms::aria_details || |
1505 | aAttribute == nsGkAtoms::aria_errormessage) { |
1506 | mDoc->QueueCacheUpdate(this, CacheDomain::Relations); |
1507 | } |
1508 | |
1509 | if (aAttribute == nsGkAtoms::popovertarget) { |
1510 | mDoc->QueueCacheUpdate(this, CacheDomain::Relations); |
1511 | return; |
1512 | } |
1513 | |
1514 | if (aAttribute == nsGkAtoms::alt && |
1515 | !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label) && |
1516 | !elm->HasAttr(nsGkAtoms::aria_labelledby)) { |
1517 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); |
1518 | return; |
1519 | } |
1520 | |
1521 | if (aAttribute == nsGkAtoms::title) { |
1522 | nsAutoString name; |
1523 | ARIAName(name); |
1524 | if (name.IsEmpty()) { |
1525 | NativeName(name); |
1526 | if (name.IsEmpty()) { |
1527 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); |
1528 | return; |
1529 | } |
1530 | } |
1531 | |
1532 | if (!elm->HasAttr(nsGkAtoms::aria_describedby)) { |
1533 | mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, |
1534 | this); |
1535 | } |
1536 | |
1537 | return; |
1538 | } |
1539 | |
1540 | // ARIA or XUL selection |
1541 | if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) || |
1542 | aAttribute == nsGkAtoms::aria_selected) { |
1543 | LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State()); |
1544 | if (widget) { |
1545 | AccSelChangeEvent::SelChangeType selChangeType; |
1546 | if (aNameSpaceID != kNameSpaceID_None) { |
1547 | selChangeType = elm->AttrValueIs(aNameSpaceID, aAttribute, |
1548 | nsGkAtoms::_true, eCaseMatters) |
1549 | ? AccSelChangeEvent::eSelectionAdd |
1550 | : AccSelChangeEvent::eSelectionRemove; |
1551 | } else { |
1552 | selChangeType = nsAccUtils::ARIAAttrValueIs( |
1553 | elm, aAttribute, nsGkAtoms::_true, eCaseMatters) |
1554 | ? AccSelChangeEvent::eSelectionAdd |
1555 | : AccSelChangeEvent::eSelectionRemove; |
1556 | } |
1557 | |
1558 | RefPtr<AccEvent> event = |
1559 | new AccSelChangeEvent(widget, this, selChangeType); |
1560 | mDoc->FireDelayedEvent(event); |
1561 | if (aAttribute == nsGkAtoms::aria_selected) { |
1562 | mDoc->QueueCacheUpdate(this, CacheDomain::State); |
1563 | } |
1564 | } |
1565 | |
1566 | return; |
1567 | } |
1568 | |
1569 | if (aAttribute == nsGkAtoms::aria_level || |
1570 | aAttribute == nsGkAtoms::aria_setsize || |
1571 | aAttribute == nsGkAtoms::aria_posinset) { |
1572 | mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo); |
1573 | return; |
1574 | } |
1575 | |
1576 | if (aAttribute == nsGkAtoms::accesskey) { |
1577 | mDoc->QueueCacheUpdate(this, CacheDomain::Actions); |
1578 | } |
1579 | |
1580 | if (aAttribute == nsGkAtoms::name && |
1581 | (mContent && mContent->IsHTMLElement(nsGkAtoms::a))) { |
1582 | // If an anchor's name changed, it's possible a LINKS_TO relation |
1583 | // also changed. Push a cache update for Relations. |
1584 | mDoc->QueueCacheUpdate(this, CacheDomain::Relations); |
1585 | } |
1586 | } |
1587 | |
1588 | void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize, |
1589 | int32_t* aPosInSet) const { |
1590 | if (!mContent) { |
1591 | return; |
1592 | } |
1593 | |
1594 | if (aLevel) { |
1595 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel); |
1596 | } |
1597 | if (aSetSize) { |
1598 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize); |
1599 | } |
1600 | if (aPosInSet) { |
1601 | nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet); |
1602 | } |
1603 | } |
1604 | |
1605 | uint64_t LocalAccessible::ExplicitState() const { |
1606 | if (IsDefunct()) return states::DEFUNCT; |
1607 | |
1608 | uint64_t state = NativeState(); |
1609 | // Apply ARIA states to be sure accessible states will be overridden. |
1610 | ApplyARIAState(&state); |
1611 | |
1612 | if (!(state & states::UNAVAILABLE)) { |
1613 | // If the object is a current item of container widget then mark it as |
1614 | // ACTIVE. This allows screen reader virtual buffer modes to know which |
1615 | // descendant is the current one that would get focus if the user navigates |
1616 | // to the container widget. |
1617 | LocalAccessible* widget = ContainerWidget(); |
1618 | if (widget && widget->CurrentItem() == this) state |= states::ACTIVE; |
1619 | } |
1620 | |
1621 | return state; |
1622 | } |
1623 | |
1624 | uint64_t LocalAccessible::State() { |
1625 | uint64_t state = ExplicitState(); |
1626 | |
1627 | ApplyImplicitState(state); |
1628 | return state; |
1629 | } |
1630 | |
1631 | void LocalAccessible::ApplyARIAState(uint64_t* aState) const { |
1632 | if (!mContent->IsElement()) return; |
1633 | |
1634 | dom::Element* element = mContent->AsElement(); |
1635 | |
1636 | // Test for universal states first |
1637 | *aState |= aria::UniversalStatesFor(element); |
1638 | |
1639 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1640 | if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) { |
1641 | // This is a <td> inside a role="grid", so it gets an implicit role of |
1642 | // GRID_CELL in ARIATransformRole. However, because it's implicit, we |
1643 | // don't have a role map entry, and without that, we can't apply ARIA states |
1644 | // below. Therefore, we get the role map entry here. |
1645 | roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell); |
1646 | MOZ_ASSERT(roleMapEntry, "Should have role map entry for gridcell")do { static_assert( mozilla::detail::AssertionConditionType< decltype(roleMapEntry)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(roleMapEntry))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("roleMapEntry" " (" "Should have role map entry for gridcell" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 1646); AnnotateMozCrashReason("MOZ_ASSERT" "(" "roleMapEntry" ") (" "Should have role map entry for gridcell" ")"); do { MOZ_CrashSequence (__null, 1646); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
1647 | } |
1648 | if (roleMapEntry) { |
1649 | // We only force the readonly bit off if we have a real mapping for the aria |
1650 | // role. This preserves the ability for screen readers to use readonly |
1651 | // (primarily on the document) as the hint for creating a virtual buffer. |
1652 | if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY; |
1653 | |
1654 | if (mContent->HasID()) { |
1655 | // If has a role & ID and aria-activedescendant on the container, assume |
1656 | // focusable. |
1657 | const LocalAccessible* ancestor = this; |
1658 | while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) { |
1659 | dom::Element* el = ancestor->Elm(); |
1660 | if (el && el->HasAttr(nsGkAtoms::aria_activedescendant)) { |
1661 | *aState |= states::FOCUSABLE; |
1662 | break; |
1663 | } |
1664 | } |
1665 | } |
1666 | } |
1667 | |
1668 | if (!(*aState & states::FOCUSABLE)) { |
1669 | // Sometimes, we use aria-activedescendant targeting something which isn't |
1670 | // actually a descendant. This is technically a spec violation, but it's a |
1671 | // useful hack which makes certain things much easier. For example, we use |
1672 | // this for "fake focus" for multi select browser tabs and Quantumbar |
1673 | // autocomplete suggestions. |
1674 | // In these cases, the aria-activedescendant code above won't make the |
1675 | // active item focusable. It doesn't make sense for something to have |
1676 | // focus when it isn't focusable, so fix that here. |
1677 | if (FocusMgr()->IsActiveItem(this)) { |
1678 | *aState |= states::FOCUSABLE; |
1679 | } |
1680 | } |
1681 | |
1682 | // special case: A native button element whose role got transformed by ARIA to |
1683 | // a toggle button Also applies to togglable button menus, like in the Dev |
1684 | // Tools Web Console. |
1685 | if (IsButton() || IsMenuButton()) { |
1686 | aria::MapToState(aria::eARIAPressed, element, aState); |
1687 | } |
1688 | |
1689 | if (!roleMapEntry) return; |
1690 | |
1691 | *aState |= roleMapEntry->state; |
1692 | |
1693 | if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) && |
1694 | aria::MapToState(roleMapEntry->attributeMap2, element, aState) && |
1695 | aria::MapToState(roleMapEntry->attributeMap3, element, aState)) { |
1696 | aria::MapToState(roleMapEntry->attributeMap4, element, aState); |
1697 | } |
1698 | |
1699 | // ARIA gridcell inherits readonly state from the grid until it's overridden. |
1700 | if ((roleMapEntry->Is(nsGkAtoms::gridcell) || |
1701 | roleMapEntry->Is(nsGkAtoms::columnheader) || |
1702 | roleMapEntry->Is(nsGkAtoms::rowheader)) && |
1703 | // Don't recurse infinitely for an authoring error like |
1704 | // <table role="gridcell">. Without this check, we'd call TableFor(this) |
1705 | // below, which would return this. |
1706 | !IsTable() && |
1707 | !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) { |
1708 | if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) { |
1709 | uint64_t gridState = 0; |
1710 | grid->ApplyARIAState(&gridState); |
1711 | *aState |= gridState & states::READONLY; |
1712 | } |
1713 | } |
1714 | } |
1715 | |
1716 | void LocalAccessible::Value(nsString& aValue) const { |
1717 | if (HasNumericValue()) { |
1718 | // aria-valuenow is a number, and aria-valuetext is the optional text |
1719 | // equivalent. For the string value, we will try the optional text |
1720 | // equivalent first. |
1721 | if (!mContent->IsElement()) { |
1722 | return; |
1723 | } |
1724 | |
1725 | if (!nsAccUtils::GetARIAAttr(mContent->AsElement(), |
1726 | nsGkAtoms::aria_valuetext, aValue)) { |
1727 | if (!NativeHasNumericValue()) { |
1728 | double checkValue = CurValue(); |
1729 | if (!std::isnan(checkValue)) { |
1730 | aValue.AppendFloat(checkValue); |
1731 | } |
1732 | } |
1733 | } |
1734 | return; |
1735 | } |
1736 | |
1737 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1738 | if (!roleMapEntry) { |
1739 | return; |
1740 | } |
1741 | |
1742 | // Value of textbox is a textified subtree. |
1743 | if (roleMapEntry->Is(nsGkAtoms::textbox)) { |
1744 | nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue); |
1745 | return; |
1746 | } |
1747 | |
1748 | // Value of combobox is a text of current or selected item. |
1749 | if (roleMapEntry->Is(nsGkAtoms::combobox)) { |
1750 | LocalAccessible* option = CurrentItem(); |
1751 | if (!option) { |
1752 | uint32_t childCount = ChildCount(); |
1753 | for (uint32_t idx = 0; idx < childCount; idx++) { |
1754 | LocalAccessible* child = mChildren.ElementAt(idx); |
1755 | if (child->IsListControl()) { |
1756 | Accessible* acc = child->GetSelectedItem(0); |
1757 | option = acc ? acc->AsLocal() : nullptr; |
1758 | break; |
1759 | } |
1760 | } |
1761 | } |
1762 | |
1763 | // If there's a selected item, get the value from it. Otherwise, determine |
1764 | // the value from descendant elements. |
1765 | nsTextEquivUtils::GetTextEquivFromSubtree(option ? option : this, aValue); |
1766 | } |
1767 | } |
1768 | |
1769 | double LocalAccessible::MaxValue() const { |
1770 | double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemax); |
1771 | if (std::isnan(checkValue) && !NativeHasNumericValue()) { |
1772 | // aria-valuemax isn't present and this element doesn't natively provide a |
1773 | // maximum value. Use the ARIA default. |
1774 | const nsRoleMapEntry* roleMap = ARIARoleMap(); |
1775 | if (roleMap && roleMap->role == roles::SPINBUTTON) { |
1776 | return UnspecifiedNaN<double>(); |
1777 | } |
1778 | return 100; |
1779 | } |
1780 | return checkValue; |
1781 | } |
1782 | |
1783 | double LocalAccessible::MinValue() const { |
1784 | double checkValue = AttrNumericValue(nsGkAtoms::aria_valuemin); |
1785 | if (std::isnan(checkValue) && !NativeHasNumericValue()) { |
1786 | // aria-valuemin isn't present and this element doesn't natively provide a |
1787 | // minimum value. Use the ARIA default. |
1788 | const nsRoleMapEntry* roleMap = ARIARoleMap(); |
1789 | if (roleMap && roleMap->role == roles::SPINBUTTON) { |
1790 | return UnspecifiedNaN<double>(); |
1791 | } |
1792 | return 0; |
1793 | } |
1794 | return checkValue; |
1795 | } |
1796 | |
1797 | double LocalAccessible::Step() const { |
1798 | return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA. |
1799 | } |
1800 | |
1801 | double LocalAccessible::CurValue() const { |
1802 | double checkValue = AttrNumericValue(nsGkAtoms::aria_valuenow); |
1803 | if (std::isnan(checkValue) && !NativeHasNumericValue()) { |
1804 | // aria-valuenow isn't present and this element doesn't natively provide a |
1805 | // current value. Use the ARIA default. |
1806 | const nsRoleMapEntry* roleMap = ARIARoleMap(); |
1807 | if (roleMap && roleMap->role == roles::SPINBUTTON) { |
1808 | return UnspecifiedNaN<double>(); |
1809 | } |
1810 | double minValue = MinValue(); |
1811 | return minValue + ((MaxValue() - minValue) / 2); |
1812 | } |
1813 | |
1814 | return checkValue; |
1815 | } |
1816 | |
1817 | bool LocalAccessible::SetCurValue(double aValue) { return false; } |
1818 | |
1819 | role LocalAccessible::FindNextValidARIARole( |
1820 | std::initializer_list<nsStaticAtom*> aRolesToSkip) const { |
1821 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1822 | if (roleMapEntry && mContent && mContent->IsElement()) { |
1823 | dom::Element* elem = mContent->AsElement(); |
1824 | if (!nsAccUtils::ARIAAttrValueIs(elem, nsGkAtoms::role, |
1825 | roleMapEntry->roleAtom, eIgnoreCase)) { |
1826 | // Get the next valid token that isn't in the list of roles to skip. |
1827 | uint8_t roleMapIndex = |
1828 | aria::GetFirstValidRoleMapIndexExcluding(elem, aRolesToSkip); |
1829 | // If we don't find a valid token, fall back to the native role. |
1830 | if (roleMapIndex == aria::NO_ROLE_MAP_ENTRY_INDEX || |
1831 | roleMapIndex == aria::LANDMARK_ROLE_MAP_ENTRY_INDEX) { |
1832 | return NativeRole(); |
1833 | } |
1834 | const nsRoleMapEntry* fallbackRoleMapEntry = |
1835 | aria::GetRoleMapFromIndex(roleMapIndex); |
1836 | if (!fallbackRoleMapEntry) { |
1837 | return NativeRole(); |
1838 | } |
1839 | // Return the next valid role, but validate that first, too. |
1840 | return ARIATransformRole(fallbackRoleMapEntry->role); |
1841 | } |
1842 | } |
1843 | // Fall back to the native role. |
1844 | return NativeRole(); |
1845 | } |
1846 | |
1847 | role LocalAccessible::ARIATransformRole(role aRole) const { |
1848 | // Beginning with ARIA 1.1, user agents are expected to use the native host |
1849 | // language role of the element when the form or region roles are used without |
1850 | // a name. Says the spec, "the user agent MUST treat such elements as if no |
1851 | // role had been provided." |
1852 | // https://w3c.github.io/aria/#document-handling_author-errors_roles |
1853 | // |
1854 | // XXX: While the name computation algorithm can be non-trivial in the general |
1855 | // case, it should not be especially bad here: If the author hasn't used the |
1856 | // region role, this calculation won't occur. And the region role's name |
1857 | // calculation rule excludes name from content. That said, this use case is |
1858 | // another example of why we should consider caching the accessible name. See: |
1859 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1378235. |
1860 | if (aRole == roles::REGION || aRole == roles::FORM) { |
1861 | if (NameIsEmpty()) { |
1862 | // If we have a "form" or "region" role, but no accessible name, we need |
1863 | // to search for the next valid role. First, we search through the role |
1864 | // attribute value string - there might be a valid fallback there. Skip |
1865 | // all "form" or "region" attributes; we know they're not valid since |
1866 | // there's no accessible name. If we find a valid role that's not "form" |
1867 | // or "region", fall back to it (but run it through ARIATransformRole |
1868 | // first). Otherwise, fall back to the element's native role. |
1869 | return FindNextValidARIARole({nsGkAtoms::region, nsGkAtoms::form}); |
1870 | } |
1871 | return aRole; |
1872 | } |
1873 | |
1874 | // XXX: these unfortunate exceptions don't fit into the ARIA table. This is |
1875 | // where the accessible role depends on both the role and ARIA state. |
1876 | if (aRole == roles::PUSHBUTTON) { |
1877 | if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_pressed)) { |
1878 | // For simplicity, any existing pressed attribute except "" or "undefined" |
1879 | // indicates a toggle. |
1880 | return roles::TOGGLE_BUTTON; |
1881 | } |
1882 | |
1883 | if (mContent->IsElement() && |
1884 | nsAccUtils::ARIAAttrValueIs(mContent->AsElement(), |
1885 | nsGkAtoms::aria_haspopup, nsGkAtoms::_true, |
1886 | eCaseMatters)) { |
1887 | // For button with aria-haspopup="true". |
1888 | return roles::BUTTONMENU; |
1889 | } |
1890 | |
1891 | } else if (aRole == roles::LISTBOX) { |
1892 | // A listbox inside of a combobox needs a special role because of ATK |
1893 | // mapping to menu. |
1894 | if (mParent && mParent->IsCombobox()) { |
1895 | return roles::COMBOBOX_LIST; |
1896 | } |
1897 | |
1898 | } else if (aRole == roles::OPTION) { |
1899 | if (mParent && mParent->Role() == roles::COMBOBOX_LIST) { |
1900 | return roles::COMBOBOX_OPTION; |
1901 | } |
1902 | |
1903 | // Orphaned option outside the context of a listbox. |
1904 | const Accessible* listbox = FindAncestorIf([](const Accessible& aAcc) { |
1905 | const role accRole = aAcc.Role(); |
1906 | return accRole == roles::LISTBOX ? AncestorSearchOption::Found |
1907 | : accRole == roles::GROUPING ? AncestorSearchOption::Continue |
1908 | : AncestorSearchOption::NotFound; |
1909 | }); |
1910 | if (!listbox) { |
1911 | return NativeRole(); |
1912 | } |
1913 | } else if (aRole == roles::MENUITEM) { |
1914 | // Menuitem has a submenu. |
1915 | if (mContent->IsElement() && |
1916 | nsAccUtils::ARIAAttrValueIs(mContent->AsElement(), |
1917 | nsGkAtoms::aria_haspopup, nsGkAtoms::_true, |
1918 | eCaseMatters)) { |
1919 | return roles::PARENT_MENUITEM; |
1920 | } |
1921 | |
1922 | // Orphaned menuitem outside the context of a menu/menubar. |
1923 | const Accessible* menu = FindAncestorIf([](const Accessible& aAcc) { |
1924 | const role accRole = aAcc.Role(); |
1925 | return (accRole == roles::MENUBAR || accRole == roles::MENUPOPUP) |
1926 | ? AncestorSearchOption::Found |
1927 | : accRole == roles::GROUPING ? AncestorSearchOption::Continue |
1928 | : AncestorSearchOption::NotFound; |
1929 | }); |
1930 | if (!menu) { |
1931 | return NativeRole(); |
1932 | } |
1933 | } else if (aRole == roles::RADIO_MENU_ITEM || |
1934 | aRole == roles::CHECK_MENU_ITEM) { |
1935 | // Orphaned radio/checkbox menuitem outside the context of a menu/menubar. |
1936 | const Accessible* menu = FindAncestorIf([](const Accessible& aAcc) { |
1937 | const role accRole = aAcc.Role(); |
1938 | return (accRole == roles::MENUBAR || accRole == roles::MENUPOPUP) |
1939 | ? AncestorSearchOption::Found |
1940 | : accRole == roles::GROUPING ? AncestorSearchOption::Continue |
1941 | : AncestorSearchOption::NotFound; |
1942 | }); |
1943 | if (!menu) { |
1944 | return NativeRole(); |
1945 | } |
1946 | } else if (aRole == roles::CELL) { |
1947 | // A cell inside an ancestor table element that has a grid role needs a |
1948 | // gridcell role |
1949 | // (https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings). |
1950 | const LocalAccessible* table = nsAccUtils::TableFor(this); |
1951 | if (table && table->IsARIARole(nsGkAtoms::grid)) { |
1952 | return roles::GRID_CELL; |
1953 | } |
1954 | } else if (aRole == roles::ROW) { |
1955 | // Orphaned rows outside the context of a table. |
1956 | const LocalAccessible* table = nsAccUtils::TableFor(this); |
1957 | if (!table) { |
1958 | return NativeRole(); |
1959 | } |
1960 | } else if (aRole == roles::ROWGROUP) { |
1961 | // Orphaned rowgroups outside the context of a table. |
1962 | const Accessible* table = FindAncestorIf([](const Accessible& aAcc) { |
1963 | return aAcc.IsTable() ? AncestorSearchOption::Found |
1964 | : AncestorSearchOption::NotFound; |
1965 | }); |
1966 | if (!table) { |
1967 | return NativeRole(); |
1968 | } |
1969 | } else if (aRole == roles::GRID_CELL || aRole == roles::ROWHEADER || |
1970 | aRole == roles::COLUMNHEADER) { |
1971 | // Orphaned gridcell/rowheader/columnheader outside the context of a row. |
1972 | const Accessible* row = FindAncestorIf([](const Accessible& aAcc) { |
1973 | return aAcc.IsTableRow() ? AncestorSearchOption::Found |
1974 | : AncestorSearchOption::NotFound; |
1975 | }); |
1976 | if (!row) { |
1977 | return NativeRole(); |
1978 | } |
1979 | } else if (aRole == roles::LISTITEM) { |
1980 | // doc-biblioentry and doc-endnote should not be treated as listitems. |
1981 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
1982 | if (!roleMapEntry || (roleMapEntry->roleAtom != nsGkAtoms::docBiblioentry && |
1983 | roleMapEntry->roleAtom != nsGkAtoms::docEndnote)) { |
1984 | // Orphaned listitem outside the context of a list. |
1985 | const Accessible* list = FindAncestorIf([](const Accessible& aAcc) { |
1986 | return aAcc.IsList() ? AncestorSearchOption::Found |
1987 | : AncestorSearchOption::Continue; |
1988 | }); |
1989 | if (!list) { |
1990 | return NativeRole(); |
1991 | } |
1992 | } |
1993 | } else if (aRole == roles::PAGETAB) { |
1994 | // Orphaned tab outside the context of a tablist. |
1995 | const Accessible* tablist = FindAncestorIf([](const Accessible& aAcc) { |
1996 | return aAcc.Role() == roles::PAGETABLIST ? AncestorSearchOption::Found |
1997 | : AncestorSearchOption::NotFound; |
1998 | }); |
1999 | if (!tablist) { |
2000 | return NativeRole(); |
2001 | } |
2002 | } else if (aRole == roles::OUTLINEITEM) { |
2003 | // Orphaned treeitem outside the context of a tree. |
2004 | const Accessible* tree = FindAncestorIf([](const Accessible& aAcc) { |
2005 | return aAcc.Role() == roles::OUTLINE ? AncestorSearchOption::Found |
2006 | : AncestorSearchOption::Continue; |
2007 | }); |
2008 | if (!tree) { |
2009 | return NativeRole(); |
2010 | } |
2011 | } |
2012 | |
2013 | return aRole; |
2014 | } |
2015 | |
2016 | role LocalAccessible::GetMinimumRole(role aRole) const { |
2017 | if (aRole != roles::TEXT && aRole != roles::TEXT_CONTAINER && |
2018 | aRole != roles::SECTION) { |
2019 | // This isn't a generic role, so aRole is specific enough. |
2020 | return aRole; |
2021 | } |
2022 | dom::Element* el = Elm(); |
2023 | if (el && el->IsHTMLElement() && el->HasAttr(nsGkAtoms::popover)) { |
2024 | return roles::GROUPING; |
2025 | } |
2026 | return aRole; |
2027 | } |
2028 | |
2029 | role LocalAccessible::NativeRole() const { return roles::NOTHING; } |
2030 | |
2031 | uint8_t LocalAccessible::ActionCount() const { |
2032 | return HasPrimaryAction() || ActionAncestor() ? 1 : 0; |
2033 | } |
2034 | |
2035 | void LocalAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) { |
2036 | aName.Truncate(); |
2037 | |
2038 | if (aIndex != 0) return; |
2039 | |
2040 | uint32_t actionRule = GetActionRule(); |
2041 | |
2042 | switch (actionRule) { |
2043 | case eActivateAction: |
2044 | aName.AssignLiteral("activate"); |
2045 | return; |
2046 | |
2047 | case eClickAction: |
2048 | aName.AssignLiteral("click"); |
2049 | return; |
2050 | |
2051 | case ePressAction: |
2052 | aName.AssignLiteral("press"); |
2053 | return; |
2054 | |
2055 | case eCheckUncheckAction: { |
2056 | uint64_t state = State(); |
2057 | if (state & states::CHECKED) { |
2058 | aName.AssignLiteral("uncheck"); |
2059 | } else if (state & states::MIXED) { |
2060 | aName.AssignLiteral("cycle"); |
2061 | } else { |
2062 | aName.AssignLiteral("check"); |
2063 | } |
2064 | return; |
2065 | } |
2066 | |
2067 | case eJumpAction: |
2068 | aName.AssignLiteral("jump"); |
2069 | return; |
2070 | |
2071 | case eOpenCloseAction: |
2072 | if (State() & states::EXPANDED) { |
2073 | aName.AssignLiteral("close"); |
2074 | } else { |
2075 | aName.AssignLiteral("open"); |
2076 | } |
2077 | return; |
2078 | |
2079 | case eSelectAction: |
2080 | aName.AssignLiteral("select"); |
2081 | return; |
2082 | |
2083 | case eSwitchAction: |
2084 | aName.AssignLiteral("switch"); |
2085 | return; |
2086 | |
2087 | case eSortAction: |
2088 | aName.AssignLiteral("sort"); |
2089 | return; |
2090 | |
2091 | case eExpandAction: |
2092 | if (State() & states::EXPANDED) { |
2093 | aName.AssignLiteral("collapse"); |
2094 | } else { |
2095 | aName.AssignLiteral("expand"); |
2096 | } |
2097 | return; |
2098 | } |
2099 | |
2100 | if (ActionAncestor()) { |
2101 | aName.AssignLiteral("clickAncestor"); |
2102 | return; |
2103 | } |
2104 | } |
2105 | |
2106 | bool LocalAccessible::DoAction(uint8_t aIndex) const { |
2107 | if (aIndex != 0) return false; |
2108 | |
2109 | if (HasPrimaryAction() || ActionAncestor()) { |
2110 | DoCommand(); |
2111 | return true; |
2112 | } |
2113 | |
2114 | return false; |
2115 | } |
2116 | |
2117 | bool LocalAccessible::HasPrimaryAction() const { |
2118 | return GetActionRule() != eNoAction; |
2119 | } |
2120 | |
2121 | nsIContent* LocalAccessible::GetAtomicRegion() const { |
2122 | nsIContent* loopContent = mContent; |
2123 | nsAutoString atomic; |
2124 | while (loopContent && |
2125 | (!loopContent->IsElement() || |
2126 | !nsAccUtils::GetARIAAttr(loopContent->AsElement(), |
2127 | nsGkAtoms::aria_atomic, atomic))) { |
2128 | loopContent = loopContent->GetParent(); |
2129 | } |
2130 | |
2131 | return atomic.EqualsLiteral("true") ? loopContent : nullptr; |
2132 | } |
2133 | |
2134 | LocalAccessible* LocalAccessible::GetPopoverTargetDetailsRelation() const { |
2135 | dom::Element* targetEl = mContent->GetEffectivePopoverTargetElement(); |
2136 | if (!targetEl) { |
2137 | return nullptr; |
2138 | } |
2139 | LocalAccessible* targetAcc = mDoc->GetAccessible(targetEl); |
2140 | if (!targetAcc) { |
2141 | return nullptr; |
2142 | } |
2143 | // Even if the popovertarget is valid, there are a few cases where we must not |
2144 | // expose it via the details relation. |
2145 | if (const nsAttrValue* actionVal = |
2146 | Elm()->GetParsedAttr(nsGkAtoms::popovertargetaction)) { |
2147 | if (static_cast<PopoverTargetAction>(actionVal->GetEnumValue()) == |
2148 | PopoverTargetAction::Hide) { |
2149 | return nullptr; |
2150 | } |
2151 | } |
2152 | if (targetAcc->NextSibling() == this || targetAcc->PrevSibling() == this) { |
2153 | return nullptr; |
2154 | } |
2155 | return targetAcc; |
2156 | } |
2157 | |
2158 | Relation LocalAccessible::RelationByType(RelationType aType) const { |
2159 | if (!HasOwnContent()) return Relation(); |
2160 | |
2161 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
2162 | |
2163 | // Relationships are defined on the same content node that the role would be |
2164 | // defined on. |
2165 | switch (aType) { |
2166 | case RelationType::LABELLED_BY: { |
2167 | Relation rel(new AssociatedElementsIterator(mDoc, mContent, |
2168 | nsGkAtoms::aria_labelledby)); |
2169 | if (mContent->IsHTMLElement()) { |
2170 | rel.AppendIter(new HTMLLabelIterator(Document(), this)); |
2171 | } |
2172 | rel.AppendIter(new XULLabelIterator(Document(), mContent)); |
2173 | |
2174 | return rel; |
2175 | } |
2176 | |
2177 | case RelationType::LABEL_FOR: { |
2178 | Relation rel(new RelatedAccIterator(Document(), mContent, |
2179 | nsGkAtoms::aria_labelledby)); |
2180 | if (mContent->IsXULElement(nsGkAtoms::label)) { |
2181 | rel.AppendIter( |
2182 | new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control)); |
2183 | } |
2184 | |
2185 | return rel; |
2186 | } |
2187 | |
2188 | case RelationType::DESCRIBED_BY: { |
2189 | Relation rel(new AssociatedElementsIterator(mDoc, mContent, |
2190 | nsGkAtoms::aria_describedby)); |
2191 | if (mContent->IsXULElement()) { |
2192 | rel.AppendIter(new XULDescriptionIterator(Document(), mContent)); |
2193 | } |
2194 | |
2195 | return rel; |
2196 | } |
2197 | |
2198 | case RelationType::DESCRIPTION_FOR: { |
2199 | Relation rel(new RelatedAccIterator(Document(), mContent, |
2200 | nsGkAtoms::aria_describedby)); |
2201 | |
2202 | // This affectively adds an optional control attribute to xul:description, |
2203 | // which only affects accessibility, by allowing the description to be |
2204 | // tied to a control. |
2205 | if (mContent->IsXULElement(nsGkAtoms::description)) { |
2206 | rel.AppendIter( |
2207 | new AssociatedElementsIterator(mDoc, mContent, nsGkAtoms::control)); |
2208 | } |
2209 | |
2210 | return rel; |
2211 | } |
2212 | |
2213 | case RelationType::NODE_CHILD_OF: { |
2214 | Relation rel; |
2215 | // This is an ARIA tree or treegrid that doesn't use owns, so we need to |
2216 | // get the parent the hard way. |
2217 | if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || |
2218 | roleMapEntry->role == roles::LISTITEM || |
2219 | roleMapEntry->role == roles::ROW)) { |
2220 | AccGroupInfo* groupInfo = |
2221 | const_cast<LocalAccessible*>(this)->GetOrCreateGroupInfo(); |
2222 | if (groupInfo) { |
2223 | Accessible* parent = groupInfo->ConceptualParent(); |
2224 | if (parent) { |
2225 | MOZ_ASSERT(parent->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType< decltype(parent->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(parent->IsLocal()))), 0)) ) { do { } while (false); MOZ_ReportAssertionFailure("parent->IsLocal()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2225); AnnotateMozCrashReason("MOZ_ASSERT" "(" "parent->IsLocal()" ")"); do { MOZ_CrashSequence(__null, 2225); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2226 | rel.AppendTarget(parent->AsLocal()); |
2227 | } |
2228 | } |
2229 | } |
2230 | |
2231 | // If this is an OOP iframe document, we can't support NODE_CHILD_OF |
2232 | // here, since the iframe resides in a different process. This is fine |
2233 | // because the client will then request the parent instead, which will be |
2234 | // correctly handled by platform code. |
2235 | if (XRE_IsContentProcess() && IsRoot()) { |
2236 | dom::Document* doc = |
2237 | const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode(); |
2238 | dom::BrowsingContext* bc = doc->GetBrowsingContext(); |
2239 | MOZ_ASSERT(bc)do { static_assert( mozilla::detail::AssertionConditionType< decltype(bc)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(bc))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("bc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2239); AnnotateMozCrashReason("MOZ_ASSERT" "(" "bc" ")"); do { MOZ_CrashSequence(__null, 2239); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
2240 | if (!bc->Top()->IsInProcess()) { |
2241 | return rel; |
2242 | } |
2243 | } |
2244 | |
2245 | // If accessible is in its own Window, or is the root of a document, |
2246 | // then we should provide NODE_CHILD_OF relation so that MSAA clients |
2247 | // can easily get to true parent instead of getting to oleacc's |
2248 | // ROLE_WINDOW accessible which will prevent us from going up further |
2249 | // (because it is system generated and has no idea about the hierarchy |
2250 | // above it). |
2251 | nsIFrame* frame = GetFrame(); |
2252 | if (frame) { |
2253 | nsView* view = frame->GetView(); |
2254 | if (view) { |
2255 | ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame); |
2256 | if (scrollContainerFrame || view->GetWidget() || |
2257 | !frame->GetParent()) { |
2258 | rel.AppendTarget(LocalParent()); |
2259 | } |
2260 | } |
2261 | } |
2262 | |
2263 | return rel; |
2264 | } |
2265 | |
2266 | case RelationType::NODE_PARENT_OF: { |
2267 | // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees |
2268 | // also can be organized by groups. |
2269 | if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM || |
2270 | roleMapEntry->role == roles::LISTITEM || |
2271 | roleMapEntry->role == roles::ROW || |
2272 | roleMapEntry->role == roles::OUTLINE || |
2273 | roleMapEntry->role == roles::LIST || |
2274 | roleMapEntry->role == roles::TREE_TABLE)) { |
2275 | return Relation(new ItemIterator(this)); |
2276 | } |
2277 | |
2278 | return Relation(); |
2279 | } |
2280 | |
2281 | case RelationType::CONTROLLED_BY: { |
2282 | Relation rel(new RelatedAccIterator(Document(), mContent, |
2283 | nsGkAtoms::aria_controls)); |
2284 | |
2285 | RelatedAccIterator owners(Document(), mContent, nsGkAtoms::aria_owns); |
2286 | if (LocalAccessible* owner = owners.Next()) { |
2287 | if (nsAccUtils::IsEditableARIACombobox(owner)) { |
2288 | MOZ_ASSERT(!IsRelocated(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(!IsRelocated())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!IsRelocated()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!IsRelocated()" " (" "Child is not relocated to editable combobox" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2289); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()" ") (" "Child is not relocated to editable combobox" ")"); do { MOZ_CrashSequence(__null, 2289); __attribute__((nomerge)) :: abort(); } while (false); } } while (false) |
2289 | "Child is not relocated to editable combobox")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!IsRelocated())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!IsRelocated()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!IsRelocated()" " (" "Child is not relocated to editable combobox" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2289); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!IsRelocated()" ") (" "Child is not relocated to editable combobox" ")"); do { MOZ_CrashSequence(__null, 2289); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
2290 | rel.AppendTarget(owner); |
2291 | } |
2292 | } |
2293 | |
2294 | return rel; |
2295 | } |
2296 | case RelationType::CONTROLLER_FOR: { |
2297 | Relation rel(new AssociatedElementsIterator(mDoc, mContent, |
2298 | nsGkAtoms::aria_controls)); |
2299 | rel.AppendIter(new HTMLOutputIterator(Document(), mContent)); |
2300 | if (nsAccUtils::IsEditableARIACombobox(this)) { |
2301 | AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::aria_owns); |
2302 | while (Accessible* owned_child = iter.Next()) { |
2303 | MOZ_ASSERT(!owned_child->AsLocal()->IsRelocated())do { static_assert( mozilla::detail::AssertionConditionType< decltype(!owned_child->AsLocal()->IsRelocated())>::isValid , "invalid assertion condition"); if ((__builtin_expect(!!(!( !!(!owned_child->AsLocal()->IsRelocated()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!owned_child->AsLocal()->IsRelocated()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2303); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!owned_child->AsLocal()->IsRelocated()" ")"); do { MOZ_CrashSequence(__null, 2303); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2304 | rel.AppendTarget(owned_child->AsLocal()); |
2305 | } |
2306 | } |
2307 | return rel; |
2308 | } |
2309 | |
2310 | case RelationType::FLOWS_TO: |
2311 | return Relation(new AssociatedElementsIterator(mDoc, mContent, |
2312 | nsGkAtoms::aria_flowto)); |
2313 | |
2314 | case RelationType::FLOWS_FROM: |
2315 | return Relation( |
2316 | new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto)); |
2317 | |
2318 | case RelationType::MEMBER_OF: { |
2319 | if (Role() == roles::RADIOBUTTON) { |
2320 | /* If we see a radio button role here, we're dealing with an aria |
2321 | * radio button (because input=radio buttons are |
2322 | * HTMLRadioButtonAccessibles) */ |
2323 | Relation rel = Relation(); |
2324 | LocalAccessible* currParent = LocalParent(); |
2325 | while (currParent && currParent->Role() != roles::RADIO_GROUP) { |
2326 | currParent = currParent->LocalParent(); |
2327 | } |
2328 | |
2329 | if (currParent && currParent->Role() == roles::RADIO_GROUP) { |
2330 | /* If we found a radiogroup parent, search for all |
2331 | * roles::RADIOBUTTON children and add them to our relation. |
2332 | * This search will include the radio button this method |
2333 | * was called from, which is expected. */ |
2334 | Pivot p = Pivot(currParent); |
2335 | PivotRoleRule rule(roles::RADIOBUTTON); |
2336 | Accessible* match = p.Next(currParent, rule); |
2337 | while (match) { |
2338 | MOZ_ASSERT(match->IsLocal(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(match->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()" " (" "We shouldn't find any remote accs while building our " "relation!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2340); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()" ") (" "We shouldn't find any remote accs while building our " "relation!" ")"); do { MOZ_CrashSequence(__null, 2340); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false) |
2339 | "We shouldn't find any remote accs while building our "do { static_assert( mozilla::detail::AssertionConditionType< decltype(match->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()" " (" "We shouldn't find any remote accs while building our " "relation!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2340); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()" ") (" "We shouldn't find any remote accs while building our " "relation!" ")"); do { MOZ_CrashSequence(__null, 2340); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false) |
2340 | "relation!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(match->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(match->IsLocal()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("match->IsLocal()" " (" "We shouldn't find any remote accs while building our " "relation!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2340); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()" ") (" "We shouldn't find any remote accs while building our " "relation!" ")"); do { MOZ_CrashSequence(__null, 2340); __attribute__ ((nomerge)) ::abort(); } while (false); } } while (false); |
2341 | rel.AppendTarget(match->AsLocal()); |
2342 | match = p.Next(match, rule); |
2343 | } |
2344 | } |
2345 | |
2346 | /* By webkit's standard, aria radio buttons do not get grouped |
2347 | * if they lack a group parent, so we return an empty |
2348 | * relation here if the above check fails. */ |
2349 | |
2350 | return rel; |
2351 | } |
2352 | |
2353 | return Relation(mDoc, GetAtomicRegion()); |
2354 | } |
2355 | |
2356 | case RelationType::LINKS_TO: { |
2357 | Relation rel = Relation(); |
2358 | if (Role() == roles::LINK) { |
2359 | dom::HTMLAnchorElement* anchor = |
2360 | dom::HTMLAnchorElement::FromNode(mContent); |
2361 | if (!anchor) { |
2362 | return rel; |
2363 | } |
2364 | // If this node is an anchor element, query its hash to find the |
2365 | // target. |
2366 | nsAutoCString hash; |
2367 | anchor->GetHash(hash); |
2368 | if (hash.IsEmpty()) { |
2369 | return rel; |
2370 | } |
2371 | |
2372 | // GetHash returns an ID or name with a leading '#', trim it so we can |
2373 | // search the doc by ID or name alone. |
2374 | NS_ConvertUTF8toUTF16 hash16(Substring(hash, 1)); |
2375 | if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash16)) { |
2376 | rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm)); |
2377 | } else if (nsCOMPtr<nsINodeList> list = |
2378 | mContent->OwnerDoc()->GetElementsByName(hash16)) { |
2379 | // Loop through the named nodes looking for the first anchor |
2380 | uint32_t length = list->Length(); |
2381 | for (uint32_t i = 0; i < length; i++) { |
2382 | nsIContent* node = list->Item(i); |
2383 | if (node->IsHTMLElement(nsGkAtoms::a)) { |
2384 | rel.AppendTarget(mDoc->GetAccessibleOrContainer(node)); |
2385 | break; |
2386 | } |
2387 | } |
2388 | } |
2389 | } |
2390 | |
2391 | return rel; |
2392 | } |
2393 | |
2394 | case RelationType::SUBWINDOW_OF: |
2395 | case RelationType::EMBEDS: |
2396 | case RelationType::EMBEDDED_BY: |
2397 | case RelationType::POPUP_FOR: |
2398 | case RelationType::PARENT_WINDOW_OF: |
2399 | return Relation(); |
2400 | |
2401 | case RelationType::DEFAULT_BUTTON: { |
2402 | if (mContent->IsHTMLElement()) { |
2403 | // HTML form controls implements nsIFormControl interface. |
2404 | if (auto* control = nsIFormControl::FromNode(mContent)) { |
2405 | if (dom::HTMLFormElement* form = control->GetForm()) { |
2406 | return Relation(mDoc, form->GetDefaultSubmitElement()); |
2407 | } |
2408 | } |
2409 | } else { |
2410 | // In XUL, use first <button default="true" .../> in the document |
2411 | dom::Document* doc = mContent->OwnerDoc(); |
2412 | nsIContent* buttonEl = nullptr; |
2413 | if (doc->AllowXULXBL()) { |
2414 | nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons = |
2415 | doc->GetElementsByAttribute(u"default"_ns, u"true"_ns); |
2416 | if (possibleDefaultButtons) { |
2417 | uint32_t length = possibleDefaultButtons->Length(); |
2418 | // Check for button in list of default="true" elements |
2419 | for (uint32_t count = 0; count < length && !buttonEl; count++) { |
2420 | nsIContent* item = possibleDefaultButtons->Item(count); |
2421 | RefPtr<nsIDOMXULButtonElement> button = |
2422 | item->IsElement() ? item->AsElement()->AsXULButton() |
2423 | : nullptr; |
2424 | if (button) { |
2425 | buttonEl = item; |
2426 | } |
2427 | } |
2428 | } |
2429 | return Relation(mDoc, buttonEl); |
2430 | } |
2431 | } |
2432 | return Relation(); |
2433 | } |
2434 | |
2435 | case RelationType::CONTAINING_DOCUMENT: |
2436 | return Relation(mDoc); |
2437 | |
2438 | case RelationType::CONTAINING_TAB_PANE: { |
2439 | nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode()); |
2440 | if (docShell) { |
2441 | // Walk up the parent chain without crossing the boundary at which item |
2442 | // types change, preventing us from walking up out of tab content. |
2443 | nsCOMPtr<nsIDocShellTreeItem> root; |
2444 | docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root)); |
2445 | if (root) { |
2446 | // If the item type is typeContent, we assume we are in browser tab |
2447 | // content. Note, this includes content such as about:addons, |
2448 | // for consistency. |
2449 | if (root->ItemType() == nsIDocShellTreeItem::typeContent) { |
2450 | return Relation(nsAccUtils::GetDocAccessibleFor(root)); |
2451 | } |
2452 | } |
2453 | } |
2454 | return Relation(); |
2455 | } |
2456 | |
2457 | case RelationType::CONTAINING_APPLICATION: |
2458 | return Relation(ApplicationAcc()); |
2459 | |
2460 | case RelationType::DETAILS: { |
2461 | if (mContent->IsElement() && |
2462 | nsAccUtils::HasARIAAttr(mContent->AsElement(), |
2463 | nsGkAtoms::aria_details)) { |
2464 | return Relation(new AssociatedElementsIterator( |
2465 | mDoc, mContent, nsGkAtoms::aria_details)); |
2466 | } |
2467 | if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) { |
2468 | return Relation(target); |
2469 | } |
2470 | return Relation(); |
2471 | } |
2472 | |
2473 | case RelationType::DETAILS_FOR: { |
2474 | Relation rel( |
2475 | new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details)); |
2476 | RelatedAccIterator invokers(mDoc, mContent, nsGkAtoms::popovertarget); |
2477 | while (Accessible* invoker = invokers.Next()) { |
2478 | // We should only expose DETAILS_FOR if DETAILS was exposed on the |
2479 | // invoker. However, DETAILS exposure on popover invokers is |
2480 | // conditional. |
2481 | LocalAccessible* popoverTarget = |
2482 | invoker->AsLocal()->GetPopoverTargetDetailsRelation(); |
2483 | if (popoverTarget) { |
2484 | MOZ_ASSERT(popoverTarget == this)do { static_assert( mozilla::detail::AssertionConditionType< decltype(popoverTarget == this)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(popoverTarget == this))), 0) )) { do { } while (false); MOZ_ReportAssertionFailure("popoverTarget == this" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2484); AnnotateMozCrashReason("MOZ_ASSERT" "(" "popoverTarget == this" ")"); do { MOZ_CrashSequence(__null, 2484); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2485 | rel.AppendTarget(invoker); |
2486 | } |
2487 | } |
2488 | return rel; |
2489 | } |
2490 | |
2491 | case RelationType::ERRORMSG: |
2492 | return Relation(new AssociatedElementsIterator( |
2493 | mDoc, mContent, nsGkAtoms::aria_errormessage)); |
2494 | |
2495 | case RelationType::ERRORMSG_FOR: |
2496 | return Relation( |
2497 | new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage)); |
2498 | |
2499 | default: |
2500 | return Relation(); |
2501 | } |
2502 | } |
2503 | |
2504 | void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {} |
2505 | |
2506 | void LocalAccessible::DoCommand(uint32_t aActionIndex) const { |
2507 | NS_DispatchToMainThread(NS_NewRunnableFunction( |
2508 | "LocalAccessible::DispatchClickEvent", |
2509 | [aActionIndex, acc = RefPtr{this}]() MOZ_CAN_RUN_SCRIPT_BOUNDARY { |
2510 | acc->DispatchClickEvent(aActionIndex); |
2511 | })); |
2512 | } |
2513 | |
2514 | void LocalAccessible::DispatchClickEvent(uint32_t aActionIndex) const { |
2515 | if (IsDefunct()) return; |
2516 | MOZ_ASSERT(mContent)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mContent", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2516); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")" ); do { MOZ_CrashSequence(__null, 2516); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2517 | |
2518 | RefPtr<PresShell> presShell = mDoc->PresShellPtr(); |
2519 | |
2520 | // Scroll into view. |
2521 | presShell->ScrollContentIntoView(mContent, ScrollAxis(), ScrollAxis(), |
2522 | ScrollFlags::ScrollOverflowHidden); |
2523 | |
2524 | AutoWeakFrame frame = GetFrame(); |
2525 | if (!frame) { |
2526 | return; |
2527 | } |
2528 | |
2529 | // We use RelativeBounds rather than querying the frame directly because of |
2530 | // special cases like image map areas which don't have their own frame. |
2531 | // RelativeBounds overrides handle these special cases. |
2532 | nsIFrame* boundingFrame = nullptr; |
2533 | nsRect rect = RelativeBounds(&boundingFrame); |
2534 | MOZ_ASSERT(boundingFrame)do { static_assert( mozilla::detail::AssertionConditionType< decltype(boundingFrame)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(boundingFrame))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("boundingFrame", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2534); AnnotateMozCrashReason("MOZ_ASSERT" "(" "boundingFrame" ")"); do { MOZ_CrashSequence(__null, 2534); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2535 | |
2536 | // Compute x and y coordinates in dev pixels relative to the widget. |
2537 | nsPoint offsetToWidget; |
2538 | nsCOMPtr<nsIWidget> widget = boundingFrame->GetNearestWidget(offsetToWidget); |
2539 | if (!widget) { |
2540 | return; |
2541 | } |
2542 | |
2543 | rect += offsetToWidget; |
2544 | RefPtr<nsPresContext> presContext = presShell->GetPresContext(); |
2545 | int32_t x = presContext->AppUnitsToDevPixels(rect.x + rect.width / 2); |
2546 | int32_t y = presContext->AppUnitsToDevPixels(rect.y + rect.height / 2); |
2547 | |
2548 | // Simulate a touch interaction by dispatching touch events with mouse events. |
2549 | // Even though we calculated x and y using the bounding frame, we must use the |
2550 | // primary frame for the event. In most cases, these two frames will be |
2551 | // different, but they are the same for special cases such as image map areas |
2552 | // which don't have their own frame. |
2553 | nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, mContent, frame, presShell, |
2554 | widget); |
2555 | |
2556 | // This isn't needed once bug 1924790 is fixed. |
2557 | mContent->OwnerDoc()->NotifyUserGestureActivation(); |
2558 | |
2559 | nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, mContent, frame, presShell, |
2560 | widget); |
2561 | nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, mContent, frame, presShell, |
2562 | widget); |
2563 | nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, mContent, frame, presShell, |
2564 | widget); |
2565 | } |
2566 | |
2567 | void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX, |
2568 | int32_t aY) { |
2569 | nsIFrame* frame = GetFrame(); |
2570 | if (!frame) return; |
2571 | |
2572 | LayoutDeviceIntPoint coords = |
2573 | nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this); |
2574 | |
2575 | nsIFrame* parentFrame = frame; |
2576 | while ((parentFrame = parentFrame->GetParent())) { |
2577 | nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords); |
2578 | } |
2579 | } |
2580 | |
2581 | void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset, |
2582 | uint32_t aLength) { |
2583 | // Return text representation of non-text accessible within hypertext |
2584 | // accessible. Text accessible overrides this method to return enclosed text. |
2585 | if (aStartOffset != 0 || aLength == 0) return; |
2586 | |
2587 | MOZ_ASSERT(mParent,do { static_assert( mozilla::detail::AssertionConditionType< decltype(mParent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mParent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mParent" " (" "Called on accessible unbound from tree. Result can be wrong." ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2588); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong." ")"); do { MOZ_CrashSequence(__null, 2588); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
2588 | "Called on accessible unbound from tree. Result can be wrong.")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mParent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mParent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mParent" " (" "Called on accessible unbound from tree. Result can be wrong." ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2588); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") (" "Called on accessible unbound from tree. Result can be wrong." ")"); do { MOZ_CrashSequence(__null, 2588); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2589 | nsIFrame* frame = GetFrame(); |
2590 | // We handle something becoming display: none async, which means we won't have |
2591 | // a frame when we're queuing text removed events. Thus, it's important that |
2592 | // we produce text here even if there's no frame. Otherwise, we won't fire a |
2593 | // text removed event at all, which might leave client caches (e.g. NVDA |
2594 | // virtual buffers) with dead nodes. |
2595 | if (IsHTMLBr() || (frame && frame->IsBrFrame())) { |
2596 | aText += kForcedNewLineChar; |
2597 | } else if (mParent && nsAccUtils::MustPrune(mParent)) { |
2598 | // Expose the embedded object accessible as imaginary embedded object |
2599 | // character if its parent hypertext accessible doesn't expose children to |
2600 | // AT. |
2601 | aText += kImaginaryEmbeddedObjectChar; |
2602 | } else { |
2603 | aText += kEmbeddedObjectChar; |
2604 | } |
2605 | } |
2606 | |
2607 | void LocalAccessible::Shutdown() { |
2608 | // Mark the accessible as defunct, invalidate the child count and pointers to |
2609 | // other accessibles, also make sure none of its children point to this |
2610 | // parent |
2611 | mStateFlags |= eIsDefunct; |
2612 | |
2613 | int32_t childCount = mChildren.Length(); |
2614 | for (int32_t childIdx = 0; childIdx < childCount; childIdx++) { |
2615 | mChildren.ElementAt(childIdx)->UnbindFromParent(); |
2616 | } |
2617 | mChildren.Clear(); |
2618 | |
2619 | mEmbeddedObjCollector = nullptr; |
2620 | |
2621 | if (mParent) mParent->RemoveChild(this); |
2622 | |
2623 | mContent = nullptr; |
2624 | mDoc = nullptr; |
2625 | if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) { |
2626 | SelectionMgr()->ResetCaretOffset(); |
2627 | } |
2628 | } |
2629 | |
2630 | // LocalAccessible protected |
2631 | ENameValueFlag LocalAccessible::ARIAName(nsString& aName) const { |
2632 | // 'slot' elements should ignore aria-label and aria-labelledby. |
2633 | if (mContent->IsHTMLElement(nsGkAtoms::slot)) { |
2634 | return eNameOK; |
2635 | } |
2636 | // aria-labelledby now takes precedence over aria-label |
2637 | nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs( |
2638 | this, nsGkAtoms::aria_labelledby, aName); |
2639 | if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) { |
2640 | aName.CompressWhitespace(); |
2641 | } |
2642 | |
2643 | if (!aName.IsEmpty()) { |
2644 | return eNameFromRelations; |
2645 | } |
2646 | |
2647 | if (mContent->IsElement() && |
2648 | nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::aria_label, |
2649 | aName)) { |
2650 | aName.CompressWhitespace(); |
2651 | } |
2652 | |
2653 | return eNameOK; |
2654 | } |
2655 | |
2656 | // LocalAccessible protected |
2657 | void LocalAccessible::ARIADescription(nsString& aDescription) const { |
2658 | // aria-describedby takes precedence over aria-description |
2659 | nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs( |
2660 | this, nsGkAtoms::aria_describedby, aDescription); |
2661 | if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) { |
2662 | aDescription.CompressWhitespace(); |
2663 | } |
2664 | |
2665 | if (aDescription.IsEmpty() && mContent->IsElement() && |
2666 | nsAccUtils::GetARIAAttr(mContent->AsElement(), |
2667 | nsGkAtoms::aria_description, aDescription)) { |
2668 | aDescription.CompressWhitespace(); |
2669 | } |
2670 | } |
2671 | |
2672 | // LocalAccessible protected |
2673 | ENameValueFlag LocalAccessible::NativeName(nsString& aName) const { |
2674 | if (mContent->IsHTMLElement()) { |
2675 | LocalAccessible* label = nullptr; |
2676 | HTMLLabelIterator iter(Document(), this); |
2677 | while ((label = iter.Next())) { |
2678 | nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(), |
2679 | &aName); |
2680 | aName.CompressWhitespace(); |
2681 | } |
2682 | |
2683 | if (!aName.IsEmpty()) { |
2684 | return eNameFromRelations; |
2685 | } |
2686 | |
2687 | NameFromAssociatedXULLabel(mDoc, mContent, aName); |
2688 | if (!aName.IsEmpty()) { |
2689 | return eNameOK; |
2690 | } |
2691 | |
2692 | nsTextEquivUtils::GetNameFromSubtree(this, aName); |
2693 | return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
2694 | } |
2695 | |
2696 | if (mContent->IsXULElement()) { |
2697 | XULElmName(mDoc, mContent, aName); |
2698 | if (!aName.IsEmpty()) return eNameOK; |
2699 | |
2700 | nsTextEquivUtils::GetNameFromSubtree(this, aName); |
2701 | return aName.IsEmpty() ? eNameOK : eNameFromSubtree; |
2702 | } |
2703 | |
2704 | if (mContent->IsSVGElement()) { |
2705 | // If user agents need to choose among multiple 'desc' or 'title' |
2706 | // elements for processing, the user agent shall choose the first one. |
2707 | for (nsIContent* childElm = mContent->GetFirstChild(); childElm; |
2708 | childElm = childElm->GetNextSibling()) { |
2709 | if (childElm->IsSVGElement(nsGkAtoms::title)) { |
2710 | nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName); |
2711 | return eNameOK; |
2712 | } |
2713 | } |
2714 | } |
2715 | |
2716 | return eNameOK; |
2717 | } |
2718 | |
2719 | // LocalAccessible protected |
2720 | void LocalAccessible::NativeDescription(nsString& aDescription) const { |
2721 | bool isXUL = mContent->IsXULElement(); |
2722 | if (isXUL) { |
2723 | // Try XUL <description control="[id]">description text</description> |
2724 | XULDescriptionIterator iter(Document(), mContent); |
2725 | LocalAccessible* descr = nullptr; |
2726 | while ((descr = iter.Next())) { |
2727 | nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(), |
2728 | &aDescription); |
2729 | } |
2730 | } |
2731 | } |
2732 | |
2733 | // LocalAccessible protected |
2734 | void LocalAccessible::BindToParent(LocalAccessible* aParent, |
2735 | uint32_t aIndexInParent) { |
2736 | MOZ_ASSERT(aParent, "This method isn't used to set null parent")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aParent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aParent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aParent" " (" "This method isn't used to set null parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2736); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParent" ") (" "This method isn't used to set null parent" ")"); do { MOZ_CrashSequence (__null, 2736); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2737 | MOZ_ASSERT(!mParent, "The child was expected to be moved")do { static_assert( mozilla::detail::AssertionConditionType< decltype(!mParent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!mParent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!mParent" " (" "The child was expected to be moved" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2737); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mParent" ") (" "The child was expected to be moved" ")"); do { MOZ_CrashSequence (__null, 2737); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2738 | |
2739 | #ifdef A11Y_LOG1 |
2740 | if (mParent) { |
2741 | logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent", |
2742 | mParent, "new parent", aParent, "child", this, nullptr); |
2743 | } |
2744 | #endif |
2745 | |
2746 | mParent = aParent; |
2747 | mIndexInParent = aIndexInParent; |
2748 | |
2749 | if (mParent->HasNameDependent() || mParent->IsXULListItem() || |
2750 | RelationByType(RelationType::LABEL_FOR).Next() || |
2751 | nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) { |
2752 | mContextFlags |= eHasNameDependent; |
2753 | } else { |
2754 | mContextFlags &= ~eHasNameDependent; |
2755 | } |
2756 | if (mParent->HasDescriptionDependent() || |
2757 | RelationByType(RelationType::DESCRIPTION_FOR).Next()) { |
2758 | mContextFlags |= eHasDescriptionDependent; |
2759 | } else { |
2760 | mContextFlags &= ~eHasDescriptionDependent; |
2761 | } |
2762 | |
2763 | // Add name/description dependent flags for dependent content once |
2764 | // a name/description provider is added to doc. |
2765 | Relation rel = RelationByType(RelationType::LABELLED_BY); |
2766 | LocalAccessible* relTarget = nullptr; |
2767 | while ((relTarget = rel.LocalNext())) { |
2768 | if (!relTarget->HasNameDependent()) { |
2769 | relTarget->ModifySubtreeContextFlags(eHasNameDependent, true); |
2770 | } |
2771 | } |
2772 | |
2773 | rel = RelationByType(RelationType::DESCRIBED_BY); |
2774 | while ((relTarget = rel.LocalNext())) { |
2775 | if (!relTarget->HasDescriptionDependent()) { |
2776 | relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true); |
2777 | } |
2778 | } |
2779 | |
2780 | mContextFlags |= |
2781 | static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) & |
2782 | eInsideAlert; |
2783 | |
2784 | if (IsTableCell()) { |
2785 | CachedTableAccessible::Invalidate(this); |
2786 | } |
2787 | } |
2788 | |
2789 | // LocalAccessible protected |
2790 | void LocalAccessible::UnbindFromParent() { |
2791 | // We do this here to handle document shutdown and an Accessible being moved. |
2792 | // We do this for subtree removal in DocAccessible::UncacheChildrenInSubtree. |
2793 | if (IsTable() || IsTableCell()) { |
2794 | CachedTableAccessible::Invalidate(this); |
2795 | } |
2796 | |
2797 | mParent = nullptr; |
2798 | mIndexInParent = -1; |
2799 | mIndexOfEmbeddedChild = -1; |
2800 | |
2801 | delete mGroupInfo; |
2802 | mGroupInfo = nullptr; |
2803 | mContextFlags &= ~eHasNameDependent & ~eInsideAlert; |
2804 | } |
2805 | |
2806 | //////////////////////////////////////////////////////////////////////////////// |
2807 | // LocalAccessible public methods |
2808 | |
2809 | RootAccessible* LocalAccessible::RootAccessible() const { |
2810 | nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode()); |
2811 | NS_ASSERTION(docShell, "No docshell for mContent")do { if (!(docShell)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "No docshell for mContent" , "docShell", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2811); MOZ_PretendNoReturn(); } } while (0); |
2812 | if (!docShell) { |
2813 | return nullptr; |
2814 | } |
2815 | |
2816 | nsCOMPtr<nsIDocShellTreeItem> root; |
2817 | docShell->GetInProcessRootTreeItem(getter_AddRefs(root)); |
2818 | NS_ASSERTION(root, "No root content tree item")do { if (!(root)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "No root content tree item" , "root", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2818); MOZ_PretendNoReturn(); } } while (0); |
2819 | if (!root) { |
2820 | return nullptr; |
2821 | } |
2822 | |
2823 | DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root); |
2824 | return docAcc ? docAcc->AsRoot() : nullptr; |
2825 | } |
2826 | |
2827 | nsIFrame* LocalAccessible::GetFrame() const { |
2828 | return mContent ? mContent->GetPrimaryFrame() : nullptr; |
2829 | } |
2830 | |
2831 | nsINode* LocalAccessible::GetNode() const { return mContent; } |
2832 | |
2833 | dom::Element* LocalAccessible::Elm() const { |
2834 | return dom::Element::FromNodeOrNull(mContent); |
2835 | } |
2836 | |
2837 | void LocalAccessible::Language(nsAString& aLanguage) { |
2838 | aLanguage.Truncate(); |
2839 | |
2840 | if (!mDoc) return; |
2841 | |
2842 | nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage); |
2843 | if (aLanguage.IsEmpty()) { // Nothing found, so use document's language |
2844 | mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage, |
2845 | aLanguage); |
2846 | } |
2847 | } |
2848 | |
2849 | bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) { |
2850 | if (!aChild) return false; |
2851 | |
2852 | if (aIndex == mChildren.Length()) { |
2853 | // XXX(Bug 1631371) Check if this should use a fallible operation as it |
2854 | // pretended earlier. |
2855 | mChildren.AppendElement(aChild); |
2856 | } else { |
2857 | // XXX(Bug 1631371) Check if this should use a fallible operation as it |
2858 | // pretended earlier. |
2859 | mChildren.InsertElementAt(aIndex, aChild); |
2860 | |
2861 | MOZ_ASSERT(mStateFlags & eKidsMutating, "Illicit children change")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mStateFlags & eKidsMutating)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mStateFlags & eKidsMutating ))), 0))) { do { } while (false); MOZ_ReportAssertionFailure( "mStateFlags & eKidsMutating" " (" "Illicit children change" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2861); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mStateFlags & eKidsMutating" ") (" "Illicit children change" ")"); do { MOZ_CrashSequence (__null, 2861); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2862 | |
2863 | for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) { |
2864 | mChildren[idx]->mIndexInParent = idx; |
2865 | } |
2866 | } |
2867 | |
2868 | if (aChild->IsText()) { |
2869 | mStateFlags |= eHasTextKids; |
2870 | } |
2871 | |
2872 | aChild->BindToParent(this, aIndex); |
2873 | return true; |
2874 | } |
2875 | |
2876 | bool LocalAccessible::RemoveChild(LocalAccessible* aChild) { |
2877 | MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aChild))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild" " (" "No child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2877); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild" ") (" "No child was given" ")"); do { MOZ_CrashSequence(__null , 2877); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2878 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent, "No parent")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mParent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent" " (" "No parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2878); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent" ") (" "No parent" ")"); do { MOZ_CrashSequence(__null, 2878) ; __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2879 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this, "Wrong parent")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent == this)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mParent == this)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this" " (" "Wrong parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2879); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this" ") (" "Wrong parent" ")"); do { MOZ_CrashSequence(__null, 2879 ); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2880 | MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent != -1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aChild->mIndexInParent != -1" " (" "Unbound child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2881); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1" ") (" "Unbound child was given" ")"); do { MOZ_CrashSequence (__null, 2881); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2881 | "Unbound child was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent != -1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aChild->mIndexInParent != -1" " (" "Unbound child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2881); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1" ") (" "Unbound child was given" ")"); do { MOZ_CrashSequence (__null, 2881); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2882 | MOZ_DIAGNOSTIC_ASSERT((mStateFlags & eKidsMutating) || aChild->IsDefunct() ||do { static_assert( mozilla::detail::AssertionConditionType< decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct () || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating ) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" " (" "Illicit children change" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2884); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" ") (" "Illicit children change" ")"); do { MOZ_CrashSequence (__null, 2884); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2883 | aChild->IsDoc() || IsApplication(),do { static_assert( mozilla::detail::AssertionConditionType< decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct () || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating ) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" " (" "Illicit children change" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2884); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" ") (" "Illicit children change" ")"); do { MOZ_CrashSequence (__null, 2884); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2884 | "Illicit children change")do { static_assert( mozilla::detail::AssertionConditionType< decltype((mStateFlags & eKidsMutating) || aChild->IsDefunct () || aChild->IsDoc() || IsApplication())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!((mStateFlags & eKidsMutating ) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" " (" "Illicit children change" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2884); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()" ") (" "Illicit children change" ")"); do { MOZ_CrashSequence (__null, 2884); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2885 | |
2886 | int32_t index = static_cast<uint32_t>(aChild->mIndexInParent); |
2887 | if (mChildren.SafeElementAt(index) != aChild) { |
2888 | MOZ_ASSERT_UNREACHABLE("A wrong child index")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "A wrong child index" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2888); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "A wrong child index" ")"); do { MOZ_CrashSequence (__null, 2888); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2889 | index = mChildren.IndexOf(aChild); |
2890 | if (index == -1) { |
2891 | MOZ_ASSERT_UNREACHABLE("No child was found")do { static_assert( mozilla::detail::AssertionConditionType< decltype(false)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(false))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("false" " (" "MOZ_ASSERT_UNREACHABLE: " "No child was found" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2891); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") (" "MOZ_ASSERT_UNREACHABLE: " "No child was found" ")"); do { MOZ_CrashSequence (__null, 2891); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2892 | return false; |
2893 | } |
2894 | } |
2895 | |
2896 | aChild->UnbindFromParent(); |
2897 | mChildren.RemoveElementAt(index); |
2898 | |
2899 | for (uint32_t idx = index; idx < mChildren.Length(); idx++) { |
2900 | mChildren[idx]->mIndexInParent = idx; |
2901 | } |
2902 | |
2903 | return true; |
2904 | } |
2905 | |
2906 | void LocalAccessible::RelocateChild(uint32_t aNewIndex, |
2907 | LocalAccessible* aChild) { |
2908 | MOZ_DIAGNOSTIC_ASSERT(aChild, "No child was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild)>::isValid, "invalid assertion condition") ; if ((__builtin_expect(!!(!(!!(aChild))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild" " (" "No child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2908); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild" ") (" "No child was given" ")"); do { MOZ_CrashSequence(__null , 2908); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2909 | MOZ_DIAGNOSTIC_ASSERT(aChild->mParent == this,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent == this)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mParent == this)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this" " (" "A child from different subtree was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2910); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this" ") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence (__null, 2910); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2910 | "A child from different subtree was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent == this)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mParent == this)) ), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent == this" " (" "A child from different subtree was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2910); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this" ") (" "A child from different subtree was given" ")"); do { MOZ_CrashSequence (__null, 2910); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2911 | MOZ_DIAGNOSTIC_ASSERT(aChild->mIndexInParent != -1,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent != -1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aChild->mIndexInParent != -1" " (" "Unbound child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2912); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1" ") (" "Unbound child was given" ")"); do { MOZ_CrashSequence (__null, 2912); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2912 | "Unbound child was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mIndexInParent != -1)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->mIndexInParent != -1))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aChild->mIndexInParent != -1" " (" "Unbound child was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2912); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1" ") (" "Unbound child was given" ")"); do { MOZ_CrashSequence (__null, 2912); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2913 | MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent ) == aChild)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt (aChild->mIndexInParent) == aChild))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" " (" "Wrong index in parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2915); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" ") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null , 2915); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
2914 | aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild,do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent ) == aChild)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt (aChild->mIndexInParent) == aChild))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" " (" "Wrong index in parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2915); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" ") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null , 2915); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
2915 | "Wrong index in parent")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->mParent->LocalChildAt(aChild->mIndexInParent ) == aChild)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(aChild->mParent->LocalChildAt (aChild->mIndexInParent) == aChild))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" " (" "Wrong index in parent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2915); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild" ") (" "Wrong index in parent" ")"); do { MOZ_CrashSequence(__null , 2915); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2916 | MOZ_DIAGNOSTIC_ASSERT(do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<uint32_t>(aChild->mIndexInParent ) != aNewIndex)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild ->mIndexInParent) != aNewIndex))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" " (" "No move, same index" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2918); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" ") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null , 2918); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
2917 | static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex,do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<uint32_t>(aChild->mIndexInParent ) != aNewIndex)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild ->mIndexInParent) != aNewIndex))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" " (" "No move, same index" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2918); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" ") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null , 2918); __attribute__((nomerge)) ::abort(); } while (false); } } while (false) |
2918 | "No move, same index")do { static_assert( mozilla::detail::AssertionConditionType< decltype(static_cast<uint32_t>(aChild->mIndexInParent ) != aNewIndex)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(static_cast<uint32_t>(aChild ->mIndexInParent) != aNewIndex))), 0))) { do { } while (false ); MOZ_ReportAssertionFailure("static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" " (" "No move, same index" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2918); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex" ") (" "No move, same index" ")"); do { MOZ_CrashSequence(__null , 2918); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
2919 | MOZ_DIAGNOSTIC_ASSERT(aNewIndex <= mChildren.Length(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(aNewIndex <= mChildren.Length())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aNewIndex <= mChildren.Length ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aNewIndex <= mChildren.Length()" " (" "Wrong new index was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2920); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()" ") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence (__null, 2920); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false) |
2920 | "Wrong new index was given")do { static_assert( mozilla::detail::AssertionConditionType< decltype(aNewIndex <= mChildren.Length())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aNewIndex <= mChildren.Length ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("aNewIndex <= mChildren.Length()" " (" "Wrong new index was given" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2920); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()" ") (" "Wrong new index was given" ")"); do { MOZ_CrashSequence (__null, 2920); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
2921 | |
2922 | RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false); |
2923 | if (mDoc->Controller()->QueueMutationEvent(hideEvent)) { |
2924 | aChild->SetHideEventTarget(true); |
2925 | } |
2926 | |
2927 | mEmbeddedObjCollector = nullptr; |
2928 | mChildren.RemoveElementAt(aChild->mIndexInParent); |
2929 | |
2930 | uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent; |
2931 | |
2932 | // If the child is moved after its current position. |
2933 | if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) { |
2934 | startIdx = aChild->mIndexInParent; |
2935 | if (aNewIndex == mChildren.Length() + 1) { |
2936 | // The child is moved to the end. |
2937 | mChildren.AppendElement(aChild); |
2938 | endIdx = mChildren.Length() - 1; |
2939 | } else { |
2940 | mChildren.InsertElementAt(aNewIndex - 1, aChild); |
2941 | endIdx = aNewIndex; |
2942 | } |
2943 | } else { |
2944 | // The child is moved prior its current position. |
2945 | mChildren.InsertElementAt(aNewIndex, aChild); |
2946 | } |
2947 | |
2948 | for (uint32_t idx = startIdx; idx <= endIdx; idx++) { |
2949 | mChildren[idx]->mIndexInParent = idx; |
2950 | mChildren[idx]->mIndexOfEmbeddedChild = -1; |
2951 | } |
2952 | |
2953 | for (uint32_t idx = 0; idx < mChildren.Length(); idx++) { |
2954 | mChildren[idx]->mStateFlags |= eGroupInfoDirty; |
2955 | } |
2956 | |
2957 | RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild); |
2958 | DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent); |
2959 | MOZ_ASSERT(added)do { static_assert( mozilla::detail::AssertionConditionType< decltype(added)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(added))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("added", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2959); AnnotateMozCrashReason("MOZ_ASSERT" "(" "added" ")") ; do { MOZ_CrashSequence(__null, 2959); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
2960 | aChild->SetShowEventTarget(true); |
2961 | } |
2962 | |
2963 | LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const { |
2964 | LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr); |
2965 | if (!child) return nullptr; |
2966 | |
2967 | #ifdef DEBUG1 |
2968 | LocalAccessible* realParent = child->mParent; |
2969 | NS_ASSERTION(!realParent || realParent == this,do { if (!(!realParent || realParent == this)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Two accessibles have the same first child accessible!" , "!realParent || realParent == this", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2970); MOZ_PretendNoReturn(); } } while (0) |
2970 | "Two accessibles have the same first child accessible!")do { if (!(!realParent || realParent == this)) { NS_DebugBreak (NS_DEBUG_ASSERTION, "Two accessibles have the same first child accessible!" , "!realParent || realParent == this", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 2970); MOZ_PretendNoReturn(); } } while (0); |
2971 | #endif |
2972 | |
2973 | return child; |
2974 | } |
2975 | |
2976 | uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); } |
2977 | |
2978 | int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; } |
2979 | |
2980 | uint32_t LocalAccessible::EmbeddedChildCount() { |
2981 | if (mStateFlags & eHasTextKids) { |
2982 | if (!mEmbeddedObjCollector) { |
2983 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
2984 | } |
2985 | return mEmbeddedObjCollector->Count(); |
2986 | } |
2987 | |
2988 | return ChildCount(); |
2989 | } |
2990 | |
2991 | Accessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) { |
2992 | if (mStateFlags & eHasTextKids) { |
2993 | if (!mEmbeddedObjCollector) { |
2994 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
2995 | } |
2996 | return mEmbeddedObjCollector.get() |
2997 | ? mEmbeddedObjCollector->GetAccessibleAt(aIndex) |
2998 | : nullptr; |
2999 | } |
3000 | |
3001 | return ChildAt(aIndex); |
3002 | } |
3003 | |
3004 | int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) { |
3005 | MOZ_ASSERT(aChild->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType< decltype(aChild->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(aChild->IsLocal()))), 0)) ) { do { } while (false); MOZ_ReportAssertionFailure("aChild->IsLocal()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3005); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aChild->IsLocal()" ")"); do { MOZ_CrashSequence(__null, 3005); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3006 | if (mStateFlags & eHasTextKids) { |
3007 | if (!mEmbeddedObjCollector) { |
3008 | mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this)); |
3009 | } |
3010 | return mEmbeddedObjCollector.get() |
3011 | ? mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal()) |
3012 | : -1; |
3013 | } |
3014 | |
3015 | return GetIndexOf(aChild->AsLocal()); |
3016 | } |
3017 | |
3018 | //////////////////////////////////////////////////////////////////////////////// |
3019 | // HyperLinkAccessible methods |
3020 | |
3021 | bool LocalAccessible::IsLink() const { |
3022 | // Every embedded accessible within hypertext accessible implements |
3023 | // hyperlink interface. |
3024 | return mParent && mParent->IsHyperText() && !IsText(); |
3025 | } |
3026 | |
3027 | //////////////////////////////////////////////////////////////////////////////// |
3028 | // SelectAccessible |
3029 | |
3030 | void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) { |
3031 | AccIterator iter(this, filters::GetSelected); |
3032 | LocalAccessible* selected = nullptr; |
3033 | while ((selected = iter.Next())) aItems->AppendElement(selected); |
3034 | } |
3035 | |
3036 | uint32_t LocalAccessible::SelectedItemCount() { |
3037 | uint32_t count = 0; |
3038 | AccIterator iter(this, filters::GetSelected); |
3039 | LocalAccessible* selected = nullptr; |
3040 | while ((selected = iter.Next())) ++count; |
Although the value stored to 'selected' is used in the enclosing expression, the value is never actually read from 'selected' | |
3041 | |
3042 | return count; |
3043 | } |
3044 | |
3045 | Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) { |
3046 | AccIterator iter(this, filters::GetSelected); |
3047 | LocalAccessible* selected = nullptr; |
3048 | |
3049 | uint32_t index = 0; |
3050 | while ((selected = iter.Next()) && index < aIndex) index++; |
3051 | |
3052 | return selected; |
3053 | } |
3054 | |
3055 | bool LocalAccessible::IsItemSelected(uint32_t aIndex) { |
3056 | uint32_t index = 0; |
3057 | AccIterator iter(this, filters::GetSelectable); |
3058 | LocalAccessible* selected = nullptr; |
3059 | while ((selected = iter.Next()) && index < aIndex) index++; |
3060 | |
3061 | return selected && selected->State() & states::SELECTED; |
3062 | } |
3063 | |
3064 | bool LocalAccessible::AddItemToSelection(uint32_t aIndex) { |
3065 | uint32_t index = 0; |
3066 | AccIterator iter(this, filters::GetSelectable); |
3067 | LocalAccessible* selected = nullptr; |
3068 | while ((selected = iter.Next()) && index < aIndex) index++; |
3069 | |
3070 | if (selected) selected->SetSelected(true); |
3071 | |
3072 | return static_cast<bool>(selected); |
3073 | } |
3074 | |
3075 | bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) { |
3076 | uint32_t index = 0; |
3077 | AccIterator iter(this, filters::GetSelectable); |
3078 | LocalAccessible* selected = nullptr; |
3079 | while ((selected = iter.Next()) && index < aIndex) index++; |
3080 | |
3081 | if (selected) selected->SetSelected(false); |
3082 | |
3083 | return static_cast<bool>(selected); |
3084 | } |
3085 | |
3086 | bool LocalAccessible::SelectAll() { |
3087 | bool success = false; |
3088 | LocalAccessible* selectable = nullptr; |
3089 | |
3090 | AccIterator iter(this, filters::GetSelectable); |
3091 | while ((selectable = iter.Next())) { |
3092 | success = true; |
3093 | selectable->SetSelected(true); |
3094 | } |
3095 | return success; |
3096 | } |
3097 | |
3098 | bool LocalAccessible::UnselectAll() { |
3099 | bool success = false; |
3100 | LocalAccessible* selected = nullptr; |
3101 | |
3102 | AccIterator iter(this, filters::GetSelected); |
3103 | while ((selected = iter.Next())) { |
3104 | success = true; |
3105 | selected->SetSelected(false); |
3106 | } |
3107 | return success; |
3108 | } |
3109 | |
3110 | //////////////////////////////////////////////////////////////////////////////// |
3111 | // Widgets |
3112 | |
3113 | bool LocalAccessible::IsWidget() const { return false; } |
3114 | |
3115 | bool LocalAccessible::IsActiveWidget() const { |
3116 | if (FocusMgr()->HasDOMFocus(mContent)) return true; |
3117 | |
3118 | // If text entry of combobox widget has a focus then the combobox widget is |
3119 | // active. |
3120 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
3121 | if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) { |
3122 | uint32_t childCount = ChildCount(); |
3123 | for (uint32_t idx = 0; idx < childCount; idx++) { |
3124 | LocalAccessible* child = mChildren.ElementAt(idx); |
3125 | if (child->Role() == roles::ENTRY) { |
3126 | return FocusMgr()->HasDOMFocus(child->GetContent()); |
3127 | } |
3128 | } |
3129 | } |
3130 | |
3131 | return false; |
3132 | } |
3133 | |
3134 | bool LocalAccessible::AreItemsOperable() const { |
3135 | return HasOwnContent() && mContent->IsElement() && |
3136 | mContent->AsElement()->HasAttr(nsGkAtoms::aria_activedescendant); |
3137 | } |
3138 | |
3139 | LocalAccessible* LocalAccessible::CurrentItem() const { |
3140 | // Check for aria-activedescendant, which changes which element has focus. |
3141 | // For activedescendant, the ARIA spec does not require that the user agent |
3142 | // checks whether pointed node is actually a DOM descendant of the element |
3143 | // with the aria-activedescendant attribute. |
3144 | if (HasOwnContent() && mContent->IsElement()) { |
3145 | if (dom::Element* activeDescendantElm = |
3146 | nsCoreUtils::GetAriaActiveDescendantElement( |
3147 | mContent->AsElement())) { |
3148 | if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) { |
3149 | // Don't want a cyclical descendant relationship. That would be bad. |
3150 | return nullptr; |
3151 | } |
3152 | |
3153 | DocAccessible* document = Document(); |
3154 | if (document) return document->GetAccessible(activeDescendantElm); |
3155 | } |
3156 | } |
3157 | return nullptr; |
3158 | } |
3159 | |
3160 | void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {} |
3161 | |
3162 | LocalAccessible* LocalAccessible::ContainerWidget() const { |
3163 | if (HasARIARole() && mContent->HasID()) { |
3164 | for (LocalAccessible* parent = LocalParent(); parent; |
3165 | parent = parent->LocalParent()) { |
3166 | nsIContent* parentContent = parent->GetContent(); |
3167 | if (parentContent && parentContent->IsElement() && |
3168 | nsCoreUtils::GetAriaActiveDescendantElement( |
3169 | parentContent->AsElement())) { |
3170 | return parent; |
3171 | } |
3172 | |
3173 | // Don't cross DOM document boundaries. |
3174 | if (parent->IsDoc()) break; |
3175 | } |
3176 | } |
3177 | return nullptr; |
3178 | } |
3179 | |
3180 | bool LocalAccessible::IsActiveDescendantId(LocalAccessible** aWidget) const { |
3181 | if (!HasOwnContent() || !mContent->HasID()) { |
3182 | return false; |
3183 | } |
3184 | |
3185 | dom::DocumentOrShadowRoot* docOrShadowRoot = |
3186 | mContent->GetUncomposedDocOrConnectedShadowRoot(); |
3187 | if (!docOrShadowRoot) { |
3188 | return false; |
3189 | } |
3190 | |
3191 | nsAutoCString selector; |
3192 | selector.AppendPrintf( |
3193 | "[aria-activedescendant=\"%s\"]", |
3194 | NS_ConvertUTF16toUTF8(mContent->GetID()->GetUTF16String()).get()); |
3195 | IgnoredErrorResult er; |
3196 | |
3197 | dom::Element* widgetElm = |
3198 | docOrShadowRoot->AsNode().QuerySelector(selector, er); |
3199 | |
3200 | if (!widgetElm || er.Failed()) { |
3201 | return false; |
3202 | } |
3203 | |
3204 | if (widgetElm->IsInclusiveDescendantOf(mContent)) { |
3205 | // Don't want a cyclical descendant relationship. That would be bad. |
3206 | return false; |
3207 | } |
3208 | |
3209 | LocalAccessible* widget = mDoc->GetAccessible(widgetElm); |
3210 | |
3211 | if (aWidget) { |
3212 | *aWidget = widget; |
3213 | } |
3214 | |
3215 | return !!widget; |
3216 | } |
3217 | |
3218 | void LocalAccessible::Announce(const nsAString& aAnnouncement, |
3219 | uint16_t aPriority) { |
3220 | RefPtr<AccAnnouncementEvent> event = |
3221 | new AccAnnouncementEvent(this, aAnnouncement, aPriority); |
3222 | nsEventShell::FireEvent(event); |
3223 | } |
3224 | |
3225 | //////////////////////////////////////////////////////////////////////////////// |
3226 | // LocalAccessible protected methods |
3227 | |
3228 | void LocalAccessible::LastRelease() { |
3229 | // First cleanup if needed... |
3230 | if (mDoc) { |
3231 | Shutdown(); |
3232 | NS_ASSERTION(!mDoc,do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "A Shutdown() impl forgot to call its parent's Shutdown?" , "!mDoc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3233); MOZ_PretendNoReturn(); } } while (0) |
3233 | "A Shutdown() impl forgot to call its parent's Shutdown?")do { if (!(!mDoc)) { NS_DebugBreak(NS_DEBUG_ASSERTION, "A Shutdown() impl forgot to call its parent's Shutdown?" , "!mDoc", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3233); MOZ_PretendNoReturn(); } } while (0); |
3234 | } |
3235 | // ... then die. |
3236 | delete this; |
3237 | } |
3238 | |
3239 | LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset, |
3240 | nsresult* aError) const { |
3241 | if (!mParent || mIndexInParent == -1) { |
3242 | if (aError) *aError = NS_ERROR_UNEXPECTED; |
3243 | |
3244 | return nullptr; |
3245 | } |
3246 | |
3247 | if (aError && |
3248 | mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) { |
3249 | *aError = NS_OK; // fail peacefully |
3250 | return nullptr; |
3251 | } |
3252 | |
3253 | LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset); |
3254 | if (aError && !child) *aError = NS_ERROR_UNEXPECTED; |
3255 | |
3256 | return child; |
3257 | } |
3258 | |
3259 | void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags, |
3260 | bool aAdd) { |
3261 | Pivot pivot(this); |
3262 | LocalAccInSameDocRule rule; |
3263 | for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) { |
3264 | MOZ_ASSERT(anchor->IsLocal())do { static_assert( mozilla::detail::AssertionConditionType< decltype(anchor->IsLocal())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(anchor->IsLocal()))), 0)) ) { do { } while (false); MOZ_ReportAssertionFailure("anchor->IsLocal()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3264); AnnotateMozCrashReason("MOZ_ASSERT" "(" "anchor->IsLocal()" ")"); do { MOZ_CrashSequence(__null, 3264); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3265 | LocalAccessible* acc = anchor->AsLocal(); |
3266 | if (aAdd) { |
3267 | acc->mContextFlags |= aContextFlags; |
3268 | } else { |
3269 | acc->mContextFlags &= ~aContextFlags; |
3270 | } |
3271 | } |
3272 | } |
3273 | |
3274 | double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const { |
3275 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
3276 | if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) { |
3277 | return UnspecifiedNaN<double>(); |
3278 | } |
3279 | |
3280 | nsAutoString attrValue; |
3281 | if (!mContent->IsElement() || |
3282 | !nsAccUtils::GetARIAAttr(mContent->AsElement(), aAttr, attrValue)) { |
3283 | return UnspecifiedNaN<double>(); |
3284 | } |
3285 | |
3286 | nsresult error = NS_OK; |
3287 | double value = attrValue.ToDouble(&error); |
3288 | return NS_FAILED(error)((bool)(__builtin_expect(!!(NS_FAILED_impl(error)), 0))) ? UnspecifiedNaN<double>() : value; |
3289 | } |
3290 | |
3291 | uint32_t LocalAccessible::GetActionRule() const { |
3292 | if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) { |
3293 | return eNoAction; |
3294 | } |
3295 | |
3296 | // Return "click" action on elements that have an attached popup menu. |
3297 | if (mContent->IsXULElement()) { |
3298 | if (mContent->AsElement()->HasAttr(nsGkAtoms::popup)) { |
3299 | return eClickAction; |
3300 | } |
3301 | } |
3302 | |
3303 | // Has registered 'click' event handler. |
3304 | bool isOnclick = nsCoreUtils::HasClickListener(mContent); |
3305 | |
3306 | if (isOnclick) return eClickAction; |
3307 | |
3308 | // Get an action based on ARIA role. |
3309 | const nsRoleMapEntry* roleMapEntry = ARIARoleMap(); |
3310 | if (roleMapEntry && roleMapEntry->actionRule != eNoAction) { |
3311 | return roleMapEntry->actionRule; |
3312 | } |
3313 | |
3314 | // Get an action based on ARIA attribute. |
3315 | if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) { |
3316 | return eExpandAction; |
3317 | } |
3318 | |
3319 | return eNoAction; |
3320 | } |
3321 | |
3322 | AccGroupInfo* LocalAccessible::GetGroupInfo() const { |
3323 | if (mGroupInfo && !(mStateFlags & eGroupInfoDirty)) { |
3324 | return mGroupInfo; |
3325 | } |
3326 | |
3327 | return nullptr; |
3328 | } |
3329 | |
3330 | AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() { |
3331 | if (mGroupInfo) { |
3332 | if (mStateFlags & eGroupInfoDirty) { |
3333 | mGroupInfo->Update(); |
3334 | mStateFlags &= ~eGroupInfoDirty; |
3335 | } |
3336 | |
3337 | return mGroupInfo; |
3338 | } |
3339 | |
3340 | mGroupInfo = AccGroupInfo::CreateGroupInfo(this); |
3341 | mStateFlags &= ~eGroupInfoDirty; |
3342 | return mGroupInfo; |
3343 | } |
3344 | |
3345 | void LocalAccessible::SendCache(uint64_t aCacheDomain, |
3346 | CacheUpdateType aUpdateType, |
3347 | bool aAppendEventData) { |
3348 | PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_SendCache> |
3349 | autoRecording; |
3350 | // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS. |
3351 | |
3352 | if (!IPCAccessibilityActive() || !Document()) { |
3353 | return; |
3354 | } |
3355 | |
3356 | // Only send cache updates for domains that are active. |
3357 | const uint64_t domainsToSend = |
3358 | nsAccessibilityService::GetActiveCacheDomains() & aCacheDomain; |
3359 | |
3360 | // Avoid sending cache updates if we have no domains to update. |
3361 | if (domainsToSend == CacheDomain::None) { |
3362 | return; |
3363 | } |
3364 | |
3365 | DocAccessibleChild* ipcDoc = mDoc->IPCDoc(); |
3366 | if (!ipcDoc) { |
3367 | // This means DocAccessible::DoInitialUpdate hasn't been called yet, which |
3368 | // means the a11y tree hasn't been built yet. Therefore, this should only |
3369 | // be possible if this is a DocAccessible. |
3370 | MOZ_ASSERT(IsDoc(), "Called on a non-DocAccessible but IPCDoc is null")do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsDoc())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(IsDoc()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("IsDoc()" " (" "Called on a non-DocAccessible but IPCDoc is null" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3370); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsDoc()" ") (" "Called on a non-DocAccessible but IPCDoc is null" ")"); do { MOZ_CrashSequence(__null, 3370); __attribute__((nomerge)) :: abort(); } while (false); } } while (false); |
3371 | return; |
3372 | } |
3373 | |
3374 | RefPtr<AccAttributes> fields = |
3375 | BundleFieldsForCache(domainsToSend, aUpdateType); |
3376 | if (!fields->Count()) { |
3377 | return; |
3378 | } |
3379 | nsTArray<CacheData> data; |
3380 | data.AppendElement(CacheData(ID(), fields)); |
3381 | if (aAppendEventData) { |
3382 | ipcDoc->PushMutationEventData( |
3383 | CacheEventData{std::move(aUpdateType), std::move(data)}); |
3384 | } else { |
3385 | ipcDoc->SendCache(aUpdateType, data); |
3386 | } |
3387 | |
3388 | if (profiler_thread_is_being_profiled_for_markers()) { |
3389 | nsAutoCString updateTypeStr; |
3390 | if (aUpdateType == CacheUpdateType::Initial) { |
3391 | updateTypeStr = "Initial"; |
3392 | } else if (aUpdateType == CacheUpdateType::Update) { |
3393 | updateTypeStr = "Update"; |
3394 | } else { |
3395 | updateTypeStr = "Other"; |
3396 | } |
3397 | PROFILER_MARKER_TEXT("LocalAccessible::SendCache", A11Y, {}, updateTypeStr)do { ; do { if (profiler_is_collecting_markers()) { ::profiler_add_marker_impl ("LocalAccessible::SendCache", ::geckoprofiler::category::A11Y , {}, ::geckoprofiler::markers::TextMarker{}, updateTypeStr); } } while (false); } while (false); |
3398 | } |
3399 | } |
3400 | |
3401 | already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache( |
3402 | uint64_t aCacheDomain, CacheUpdateType aUpdateType, |
3403 | uint64_t aInitialDomains) { |
3404 | MOZ_ASSERT((~aCacheDomain & aInitialDomains) == CacheDomain::None,do { static_assert( mozilla::detail::AssertionConditionType< decltype((~aCacheDomain & aInitialDomains) == CacheDomain ::None)>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!((~aCacheDomain & aInitialDomains) == CacheDomain ::None))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("(~aCacheDomain & aInitialDomains) == CacheDomain::None" " (" "Initial domain pushes without domains requested!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3405); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None" ") (" "Initial domain pushes without domains requested!" ")" ); do { MOZ_CrashSequence(__null, 3405); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
3405 | "Initial domain pushes without domains requested!")do { static_assert( mozilla::detail::AssertionConditionType< decltype((~aCacheDomain & aInitialDomains) == CacheDomain ::None)>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!((~aCacheDomain & aInitialDomains) == CacheDomain ::None))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("(~aCacheDomain & aInitialDomains) == CacheDomain::None" " (" "Initial domain pushes without domains requested!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3405); AnnotateMozCrashReason("MOZ_ASSERT" "(" "(~aCacheDomain & aInitialDomains) == CacheDomain::None" ") (" "Initial domain pushes without domains requested!" ")" ); do { MOZ_CrashSequence(__null, 3405); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3406 | RefPtr<AccAttributes> fields = new AccAttributes(); |
3407 | |
3408 | if (aUpdateType == CacheUpdateType::Initial) { |
3409 | aInitialDomains = CacheDomain::All; |
3410 | } |
3411 | // Pass a single cache domain in to query whether this is the initial push for |
3412 | // this domain. |
3413 | auto IsInitialPush = [aInitialDomains](uint64_t aCacheDomain) { |
3414 | return (aCacheDomain & aInitialDomains) == aCacheDomain; |
3415 | }; |
3416 | auto IsUpdatePush = [aInitialDomains](uint64_t aCacheDomain) { |
3417 | return (aCacheDomain & aInitialDomains) == CacheDomain::None; |
3418 | }; |
3419 | |
3420 | // Caching name for text leaf Accessibles is redundant, since their name is |
3421 | // always their text. Text gets handled below. |
3422 | if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) { |
3423 | nsString name; |
3424 | int32_t nameFlag = Name(name); |
3425 | if (nameFlag != eNameOK) { |
3426 | fields->SetAttribute(CacheKey::NameValueFlag, nameFlag); |
3427 | } else if (IsUpdatePush(CacheDomain::NameAndDescription)) { |
3428 | fields->SetAttribute(CacheKey::NameValueFlag, DeleteEntry()); |
3429 | } |
3430 | |
3431 | if (IsTextField()) { |
3432 | MOZ_ASSERT(mContent)do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mContent", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3432); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")" ); do { MOZ_CrashSequence(__null, 3432); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3433 | nsString placeholder; |
3434 | // Only cache the placeholder separately if it isn't used as the name. |
3435 | if (Elm()->GetAttr(nsGkAtoms::placeholder, placeholder) && |
3436 | name != placeholder) { |
3437 | fields->SetAttribute(CacheKey::HTMLPlaceholder, std::move(placeholder)); |
3438 | } else if (IsUpdatePush(CacheDomain::NameAndDescription)) { |
3439 | fields->SetAttribute(CacheKey::HTMLPlaceholder, DeleteEntry()); |
3440 | } |
3441 | } |
3442 | |
3443 | if (!name.IsEmpty()) { |
3444 | fields->SetAttribute(CacheKey::Name, std::move(name)); |
3445 | } else if (IsUpdatePush(CacheDomain::NameAndDescription)) { |
3446 | fields->SetAttribute(CacheKey::Name, DeleteEntry()); |
3447 | } |
3448 | |
3449 | nsString description; |
3450 | Description(description); |
3451 | if (!description.IsEmpty()) { |
3452 | fields->SetAttribute(CacheKey::Description, std::move(description)); |
3453 | } else if (IsUpdatePush(CacheDomain::NameAndDescription)) { |
3454 | fields->SetAttribute(CacheKey::Description, DeleteEntry()); |
3455 | } |
3456 | } |
3457 | |
3458 | if (aCacheDomain & CacheDomain::Value) { |
3459 | // We cache the text value in 3 cases: |
3460 | // 1. Accessible is an HTML input type that holds a number. |
3461 | // 2. Accessible has a numeric value and an aria-valuetext. |
3462 | // 3. Accessible is an HTML input type that holds text. |
3463 | // 4. Accessible is a link, in which case value is the target URL. |
3464 | // ... for all other cases we divine the value remotely. |
3465 | bool cacheValueText = false; |
3466 | if (HasNumericValue()) { |
3467 | fields->SetAttribute(CacheKey::NumericValue, CurValue()); |
3468 | fields->SetAttribute(CacheKey::MaxValue, MaxValue()); |
3469 | fields->SetAttribute(CacheKey::MinValue, MinValue()); |
3470 | fields->SetAttribute(CacheKey::Step, Step()); |
3471 | cacheValueText = NativeHasNumericValue() || |
3472 | (mContent->IsElement() && |
3473 | nsAccUtils::HasARIAAttr(mContent->AsElement(), |
3474 | nsGkAtoms::aria_valuetext)); |
3475 | } else { |
3476 | cacheValueText = IsTextField() || IsHTMLLink(); |
3477 | } |
3478 | |
3479 | if (cacheValueText) { |
3480 | nsString value; |
3481 | Value(value); |
3482 | if (!value.IsEmpty()) { |
3483 | fields->SetAttribute(CacheKey::TextValue, std::move(value)); |
3484 | } else if (IsUpdatePush(CacheDomain::Value)) { |
3485 | fields->SetAttribute(CacheKey::TextValue, DeleteEntry()); |
3486 | } |
3487 | } |
3488 | |
3489 | if (IsImage()) { |
3490 | // Cache the src of images. This is used by some clients to help remediate |
3491 | // inaccessible images. |
3492 | MOZ_ASSERT(mContent, "Image must have mContent")do { static_assert( mozilla::detail::AssertionConditionType< decltype(mContent)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(mContent))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("mContent" " (" "Image must have mContent" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3492); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ") (" "Image must have mContent" ")"); do { MOZ_CrashSequence(__null , 3492); __attribute__((nomerge)) ::abort(); } while (false); } } while (false); |
3493 | nsString src; |
3494 | mContent->AsElement()->GetAttr(nsGkAtoms::src, src); |
3495 | if (!src.IsEmpty()) { |
3496 | fields->SetAttribute(CacheKey::SrcURL, std::move(src)); |
3497 | } else if (IsUpdatePush(CacheDomain::Value)) { |
3498 | fields->SetAttribute(CacheKey::SrcURL, DeleteEntry()); |
3499 | } |
3500 | } |
3501 | |
3502 | if (TagName() == nsGkAtoms::meter) { |
3503 | // We should only cache value region for HTML meter elements. A meter |
3504 | // should always have a value region, so this attribute should never |
3505 | // be empty (i.e. there is no DeleteEntry() clause here). |
3506 | HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this); |
3507 | fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion()); |
3508 | } |
3509 | } |
3510 | |
3511 | if (aCacheDomain & CacheDomain::Viewport && IsDoc()) { |
3512 | // Construct the viewport cache for this document. This cache domain will |
3513 | // only be requested after we finish painting. |
3514 | DocAccessible* doc = AsDoc(); |
3515 | PresShell* presShell = doc->PresShellPtr(); |
3516 | |
3517 | if (nsIFrame* rootFrame = presShell->GetRootFrame()) { |
3518 | nsTArray<nsIFrame*> frames; |
3519 | ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame(); |
3520 | nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect(); |
3521 | |
3522 | nsLayoutUtils::GetFramesForArea( |
3523 | RelativeTo{rootFrame}, scrollPort, frames, |
3524 | {{// We don't add the ::OnlyVisible option here, because |
3525 | // it means we always consider frames with pointer-events: none. |
3526 | // See usage of HitTestIsForVisibility in nsDisplayList::HitTest. |
3527 | // This flag ensures the display lists are built, even if |
3528 | // the page hasn't finished loading. |
3529 | nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression, |
3530 | // Each doc should have its own viewport cache, so we can |
3531 | // ignore cross-doc content as an optimization. |
3532 | nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc}}); |
3533 | |
3534 | nsTHashSet<LocalAccessible*> inViewAccs; |
3535 | nsTArray<uint64_t> viewportCache(frames.Length()); |
3536 | // Layout considers table rows fully occluded by their containing cells. |
3537 | // This means they don't have their own display list items, and they won't |
3538 | // show up in the list returned from GetFramesForArea. To prevent table |
3539 | // rows from appearing offscreen, we manually add any rows for which we |
3540 | // have on-screen cells. |
3541 | LocalAccessible* prevParentRow = nullptr; |
3542 | for (nsIFrame* frame : frames) { |
3543 | if (frame->IsInlineFrame() && !frame->IsPrimaryFrame()) { |
3544 | // This is a line other than the first line in an inline element. Even |
3545 | // though there are multiple frames for this element (one per line), |
3546 | // there is only a single Accessible with bounds encompassing all the |
3547 | // frames. We don't have any additional information about the |
3548 | // individual continuation frames in our cache. Thus, we don't want |
3549 | // this Accessible to appear before leaves on other lines which are |
3550 | // later in the `frames` array. Otherwise, when hit testing, this |
3551 | // Accessible will match instead of those leaves. We will add this |
3552 | // Accessible when we get to its primary frame later. |
3553 | continue; |
3554 | } |
3555 | nsIContent* content = frame->GetContent(); |
3556 | if (!content) { |
3557 | continue; |
3558 | } |
3559 | |
3560 | LocalAccessible* acc = doc->GetAccessible(content); |
3561 | // The document should always be present at the end of the list, so |
3562 | // including it is unnecessary and wasteful. We skip the document here |
3563 | // and handle it as a fallback when hit testing. |
3564 | if (!acc || acc == mDoc) { |
3565 | continue; |
3566 | } |
3567 | |
3568 | if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) { |
3569 | acc = acc->LocalParent(); |
3570 | } |
3571 | if (acc->IsTableCell()) { |
3572 | LocalAccessible* parent = acc->LocalParent(); |
3573 | if (parent && parent->IsTableRow() && parent != prevParentRow) { |
3574 | // If we've entered a new row since the last cell we saw, add the |
3575 | // previous parent row to our viewport cache here to maintain |
3576 | // hittesting order. Keep track of the current parent row. |
3577 | if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) { |
3578 | viewportCache.AppendElement(prevParentRow->ID()); |
3579 | } |
3580 | prevParentRow = parent; |
3581 | } |
3582 | } else if (acc->IsTable()) { |
3583 | // If we've encountered a table, we know we've already |
3584 | // handled all of this table's content (because we're traversing |
3585 | // in hittesting order). Add our table's final row to the viewport |
3586 | // cache before adding the table itself. Reset our marker for the next |
3587 | // table. |
3588 | if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) { |
3589 | viewportCache.AppendElement(prevParentRow->ID()); |
3590 | } |
3591 | prevParentRow = nullptr; |
3592 | } else if (acc->IsImageMap()) { |
3593 | // Layout doesn't walk image maps, so we do that |
3594 | // manually here. We do this before adding the map itself |
3595 | // so the children come earlier in the hittesting order. |
3596 | for (uint32_t i = 0; i < acc->ChildCount(); i++) { |
3597 | LocalAccessible* child = acc->LocalChildAt(i); |
3598 | MOZ_ASSERT(child)do { static_assert( mozilla::detail::AssertionConditionType< decltype(child)>::isValid, "invalid assertion condition"); if ((__builtin_expect(!!(!(!!(child))), 0))) { do { } while ( false); MOZ_ReportAssertionFailure("child", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3598); AnnotateMozCrashReason("MOZ_ASSERT" "(" "child" ")") ; do { MOZ_CrashSequence(__null, 3598); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3599 | if (inViewAccs.EnsureInserted(child)) { |
3600 | MOZ_ASSERT(!child->IsDoc())do { static_assert( mozilla::detail::AssertionConditionType< decltype(!child->IsDoc())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!child->IsDoc()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!child->IsDoc()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3600); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!child->IsDoc()" ")"); do { MOZ_CrashSequence(__null, 3600); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3601 | viewportCache.AppendElement(child->ID()); |
3602 | } |
3603 | } |
3604 | } else if (acc->IsHTMLCombobox()) { |
3605 | // Layout doesn't consider combobox lists (or their |
3606 | // currently selected items) to be onscreen, but we do. |
3607 | // Add those things manually here. |
3608 | HTMLComboboxAccessible* combobox = |
3609 | static_cast<HTMLComboboxAccessible*>(acc); |
3610 | HTMLComboboxListAccessible* list = combobox->List(); |
3611 | LocalAccessible* currItem = combobox->SelectedOption(); |
3612 | // Preserve hittesting order by adding the item, then |
3613 | // the list, and finally the combobox itself. |
3614 | if (currItem && inViewAccs.EnsureInserted(currItem)) { |
3615 | viewportCache.AppendElement(currItem->ID()); |
3616 | } |
3617 | if (list && inViewAccs.EnsureInserted(list)) { |
3618 | viewportCache.AppendElement(list->ID()); |
3619 | } |
3620 | } |
3621 | |
3622 | if (inViewAccs.EnsureInserted(acc)) { |
3623 | MOZ_ASSERT(!acc->IsDoc())do { static_assert( mozilla::detail::AssertionConditionType< decltype(!acc->IsDoc())>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(!acc->IsDoc()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("!acc->IsDoc()" , "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3623); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!acc->IsDoc()" ")"); do { MOZ_CrashSequence(__null, 3623); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3624 | viewportCache.AppendElement(acc->ID()); |
3625 | } |
3626 | } |
3627 | |
3628 | // Always send the viewport cache, even if we have no accessibles |
3629 | // in it. We don't want to send a delete entry because the viewport |
3630 | // cache _does_ exist, it is simply representing an empty screen. |
3631 | fields->SetAttribute(CacheKey::Viewport, std::move(viewportCache)); |
3632 | } |
3633 | } |
3634 | |
3635 | if (aCacheDomain & CacheDomain::APZ && IsDoc()) { |
3636 | PresShell* presShell = AsDoc()->PresShellPtr(); |
3637 | MOZ_ASSERT(presShell, "Can't get APZ factor for null presShell")do { static_assert( mozilla::detail::AssertionConditionType< decltype(presShell)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(presShell))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("presShell" " (" "Can't get APZ factor for null presShell" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3637); AnnotateMozCrashReason("MOZ_ASSERT" "(" "presShell" ") (" "Can't get APZ factor for null presShell" ")"); do { MOZ_CrashSequence (__null, 3637); __attribute__((nomerge)) ::abort(); } while ( false); } } while (false); |
3638 | nsPoint viewportOffset = |
3639 | presShell->GetVisualViewportOffsetRelativeToLayoutViewport(); |
3640 | if (viewportOffset.x || viewportOffset.y) { |
3641 | nsTArray<int32_t> offsetArray(2); |
3642 | offsetArray.AppendElement(viewportOffset.x); |
3643 | offsetArray.AppendElement(viewportOffset.y); |
3644 | fields->SetAttribute(CacheKey::VisualViewportOffset, |
3645 | std::move(offsetArray)); |
3646 | } else if (IsUpdatePush(CacheDomain::APZ)) { |
3647 | fields->SetAttribute(CacheKey::VisualViewportOffset, DeleteEntry()); |
3648 | } |
3649 | } |
3650 | |
3651 | bool boundsChanged = false; |
3652 | nsIFrame* frame = GetFrame(); |
3653 | if (aCacheDomain & CacheDomain::Bounds) { |
3654 | nsRect newBoundsRect = ParentRelativeBounds(); |
3655 | |
3656 | // 1. Layout might notify us of a possible bounds change when the bounds |
3657 | // haven't really changed. Therefore, we cache the last bounds we sent |
3658 | // and don't send an update if they haven't changed. |
3659 | // 2. For an initial cache push, we ignore 1) and always send the bounds. |
3660 | // This handles the case where this LocalAccessible was moved (but not |
3661 | // re-created). In that case, we will have cached bounds, but we currently |
3662 | // do an initial cache push. |
3663 | MOZ_ASSERT(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome(),do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" " (" "Incremental cache push but mBounds is not set!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3664); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" ") (" "Incremental cache push but mBounds is not set!" ")"); do { MOZ_CrashSequence(__null, 3664); __attribute__((nomerge )) ::abort(); } while (false); } } while (false) |
3664 | "Incremental cache push but mBounds is not set!")do { static_assert( mozilla::detail::AssertionConditionType< decltype(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome ())>::isValid, "invalid assertion condition"); if ((__builtin_expect (!!(!(!!(IsInitialPush(CacheDomain::Bounds) || mBounds.isSome ()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure ("IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" " (" "Incremental cache push but mBounds is not set!" ")", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 3664); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsInitialPush(CacheDomain::Bounds) || mBounds.isSome()" ") (" "Incremental cache push but mBounds is not set!" ")"); do { MOZ_CrashSequence(__null, 3664); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
3665 | |
3666 | if (OuterDocAccessible* doc = AsOuterDoc()) { |
3667 | if (nsIFrame* docFrame = doc->GetFrame()) { |
3668 | const nsMargin& newOffset = docFrame->GetUsedBorderAndPadding(); |
3669 | Maybe<nsMargin> currOffset = doc->GetCrossDocOffset(); |
3670 | if (!currOffset || *currOffset != newOffset) { |
3671 | // OOP iframe docs can't compute their position within their |
3672 | // cross-proc parent, so we have to manually cache that offset |
3673 | // on the parent (outer doc) itself. For simplicity and consistency, |
3674 | // we do this here for both OOP and in-process iframes. For in-process |
3675 | // iframes, this also avoids the need to push a cache update for the |
3676 | // embedded document when the iframe changes its padding, gets |
3677 | // re-created, etc. Similar to bounds, we maintain a local cache and a |
3678 | // remote cache to avoid sending redundant updates. |
3679 | doc->SetCrossDocOffset(newOffset); |
3680 | nsTArray<int32_t> offsetArray(2); |
3681 | offsetArray.AppendElement(newOffset.Side(eSideLeft)); // X offset |
3682 | offsetArray.AppendElement(newOffset.Side(eSideTop)); // Y offset |
3683 | fields->SetAttribute(CacheKey::CrossDocOffset, |
3684 | std::move(offsetArray)); |
3685 | } |
3686 | } |
3687 | } |
3688 | |
3689 | // mBounds should never be Nothing, but sometimes it is (see Bug 1922691). |
3690 | // We null check mBounds here to avoid a crash while we figure out how this |
3691 | // can happen. |
3692 | boundsChanged = IsInitialPush(CacheDomain::Bounds) || !mBounds || |
3693 | !newBoundsRect.IsEqualEdges(mBounds.value()); |
3694 | if (boundsChanged) { |
3695 | mBounds = Some(newBoundsRect); |
3696 | |
3697 | nsTArray<int32_t> boundsArray(4); |
3698 | |
3699 | boundsArray.AppendElement(newBoundsRect.x); |
3700 | boundsArray.AppendElement(newBoundsRect.y); |
3701 | boundsArray.AppendElement(newBoundsRect.width); |
3702 | boundsArray.AppendElement(newBoundsRect.height); |
3703 | |
3704 | fields->SetAttribute(CacheKey::ParentRelativeBounds, |
3705 | std::move(boundsArray)); |
3706 | } |
3707 | |
3708 | if (frame && frame->ScrollableOverflowRect().IsEmpty()) { |
3709 | fields->SetAttribute(CacheKey::IsClipped, true); |
3710 | } else if (IsUpdatePush(CacheDomain::Bounds)) { |
3711 | fields->SetAttribute(CacheKey::IsClipped, DeleteEntry()); |
3712 | } |
3713 | } |
3714 | |
3715 | if (aCacheDomain & CacheDomain::Text) { |
3716 | if (!HasChildren()) { |
3717 | // We only cache text and line offsets on leaf Accessibles. |
3718 | // Only text Accessibles can have actual text. |
3719 | if (IsText()) { |
3720 | nsString text; |
3721 | AppendTextTo(text); |
3722 | fields->SetAttribute(CacheKey::Text, std::move(text)); |
3723 | TextLeafPoint point(this, 0); |
3724 | RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc( |
3725 | /* aIncludeDefaults */ false); |
3726 | fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs)); |
3727 | } |
3728 | } |
3729 | if (HyperTextAccessible* ht = AsHyperText()) { |
3730 | RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes(); |
3731 | fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs)); |
3732 | } else if (!IsText()) { |
3733 | // Language is normally cached in text attributes, but Accessibles that |
3734 | // aren't HyperText or Text (e.g. <img>, <input type="radio">) don't have |
3735 | // text attributes. The Text domain isn't a great fit, but the kinds of |
3736 | // clients (e.g. screen readers) that care about language are likely to |
3737 | // care about text as well. |
3738 | nsString language; |
3739 | Language(language); |
3740 | if (!language.IsEmpty()) { |
3741 | fields->SetAttribute(CacheKey::Language, std::move(language)); |
3742 | } else if (IsUpdatePush(CacheDomain::Text)) { |
3743 | fields->SetAttribute(CacheKey::Language, DeleteEntry()); |
3744 | } |
3745 | } |
3746 | } |
3747 | |
3748 | // If text changes, we must also update text offset attributes. |
3749 | if (aCacheDomain & (CacheDomain::TextOffsetAttributes | CacheDomain::Text) && |
3750 | IsTextLeaf()) { |
3751 | auto offsetAttrs = TextLeafPoint::GetTextOffsetAttributes(this); |
3752 | if (!offsetAttrs.IsEmpty()) { |
3753 | fields->SetAttribute(CacheKey::TextOffsetAttributes, |
3754 | std::move(offsetAttrs)); |
3755 | } else if (IsUpdatePush(CacheDomain::TextOffsetAttributes) || |
3756 | IsUpdatePush(CacheDomain::Text)) { |
3757 | fields->SetAttribute(CacheKey::TextOffsetAttributes, DeleteEntry()); |
3758 | } |
3759 | } |
3760 | |
3761 | if (aCacheDomain & (CacheDomain::TextBounds) && !HasChildren()) { |
3762 | // We cache line start offsets for both text and non-text leaf Accessibles |
3763 | // because non-text leaf Accessibles can still start a line. |
3764 | TextLeafPoint lineStart = |
3765 | TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc( |
3766 | /* aIncludeOrigin */ true); |
3767 | int32_t lineStartOffset = lineStart ? lineStart.mOffset : -1; |
3768 | // We push line starts and text bounds in two cases: |
3769 | // 1. TextBounds is pushed initially. |
3770 | // 2. CacheDomain::Bounds was requested (indicating that the frame was |
3771 | // reflowed) but the bounds didn't actually change. This can happen when |
3772 | // the spanned text is non-rectangular. For example, an Accessible might |
3773 | // cover two characters on one line and a single character on another line. |
3774 | // An insertion in a previous text node might cause it to shift such that it |
3775 | // now covers a single character on the first line and two characters on the |
3776 | // second line. Its bounding rect will be the same both before and after the |
3777 | // insertion. In this case, we use the first line start to determine whether |
3778 | // there was a change. This should be safe because the text didn't change in |
3779 | // this Accessible, so if the first line start doesn't shift, none of them |
3780 | // should shift. |
3781 | if (IsInitialPush(CacheDomain::TextBounds) || |
3782 | aCacheDomain & CacheDomain::Text || boundsChanged || |
3783 | mFirstLineStart != lineStartOffset) { |
3784 | mFirstLineStart = lineStartOffset; |
3785 | nsTArray<int32_t> lineStarts; |
3786 | for (; lineStart; |
3787 | lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) { |
3788 | lineStarts.AppendElement(lineStart.mOffset); |
3789 | } |
3790 | if (!lineStarts.IsEmpty()) { |
3791 | fields->SetAttribute(CacheKey::TextLineStarts, std::move(lineStarts)); |
3792 | } else if (IsUpdatePush(CacheDomain::TextBounds)) { |
3793 | fields->SetAttribute(CacheKey::TextLineStarts, DeleteEntry()); |
3794 | } |
3795 | |
3796 | if (frame && frame->IsTextFrame()) { |
3797 | if (nsTextFrame* currTextFrame = do_QueryFrame(frame)) { |
3798 | nsTArray<int32_t> charData(nsAccUtils::TextLength(this) * |
3799 | kNumbersInRect); |
3800 | // Continuation offsets are calculated relative to the primary frame. |
3801 | // However, the acc's bounds are calculated using |
3802 | // GetAllInFlowRectsUnion. For wrapped text which starts part way |
3803 | // through a line, this might mean the top left of the acc is |
3804 | // different to the top left of the primary frame. This also happens |
3805 | // when the primary frame is empty (e.g. a blank line at the start of |
3806 | // pre-formatted text), since the union rect will exclude the origin |
3807 | // in that case. Calculate the offset from the acc's rect to the |
3808 | // primary frame's rect. |
3809 | nsRect accOffset = |
3810 | nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame); |
3811 | while (currTextFrame) { |
3812 | nsPoint contOffset = currTextFrame->GetOffsetTo(frame); |
3813 | contOffset -= accOffset.TopLeft(); |
3814 | int32_t length = currTextFrame->GetContentLength(); |
3815 | nsTArray<nsRect> charBounds(length); |
3816 | currTextFrame->GetCharacterRectsInRange( |
3817 | currTextFrame->GetContentOffset(), length, charBounds); |
3818 | for (nsRect& charRect : charBounds) { |
3819 | if (charRect.width == 0 && |
3820 | !currTextFrame->StyleText()->WhiteSpaceIsSignificant()) { |
3821 | // GetCharacterRectsInRange gives us one rect per content |
3822 | // offset. However, TextLeafAccessibles use rendered offsets; |
3823 | // e.g. they might exclude some content white space. If we get |
3824 | // a 0 width rect and it's white space, skip this rect, since |
3825 | // this character isn't in the rendered text. We do have |
3826 | // a way to convert between content and rendered offsets, but |
3827 | // doing this for every character is expensive. |
3828 | const char16_t contentChar = mContent->GetText()->CharAt( |
3829 | charData.Length() / kNumbersInRect); |
3830 | if (contentChar == u' ' || contentChar == u'\t' || |
3831 | contentChar == u'\n') { |
3832 | continue; |
3833 | } |
3834 | } |
3835 | // We expect each char rect to be relative to the text leaf |
3836 | // acc this text lives in. Unfortunately, GetCharacterRectsInRange |
3837 | // returns rects relative to their continuation. Add the |
3838 | // continuation's relative position here to make our final |
3839 | // rect relative to the text leaf acc. |
3840 | charRect.MoveBy(contOffset); |
3841 | charData.AppendElement(charRect.x); |
3842 | charData.AppendElement(charRect.y); |
3843 | charData.AppendElement(charRect.width); |
3844 | charData.AppendElement(charRect.height); |
3845 | } |
3846 | currTextFrame = currTextFrame->GetNextContinuation(); |
3847 | } |
3848 | if (charData.Length()) { |
3849 | fields->SetAttribute(CacheKey::TextBounds, std::move(charData)); |
3850 | } |
3851 | } |
3852 | } |
3853 | } |
3854 | } |
3855 | |
3856 | if (aCacheDomain & CacheDomain::TransformMatrix) { |
3857 | bool transformed = false; |
3858 | if (frame && frame->IsTransformed()) { |
3859 | // This matrix is only valid when applied to CSSPixel points/rects |
3860 | // in the coordinate space of `frame`. |
3861 | gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix( |
3862 | frame, nsPoint(0, 0), AppUnitsPerCSSPixel(), |
3863 | nsDisplayTransform::INCLUDE_PERSPECTIVE); |
3864 | // We might get back the identity matrix. This can happen if there is no |
3865 | // actual transform. For example, if an element has |
3866 | // will-change: transform, nsIFrame::IsTransformed will return true, but |
3867 | // this doesn't necessarily mean there is a transform right now. |
3868 | // Applying the identity matrix is effectively a no-op, so there's no |
3869 | // point caching it. |
3870 | transformed = !mtx.IsIdentity(); |
3871 | if (transformed) { |
3872 | UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx); |
3873 | fields->SetAttribute(CacheKey::TransformMatrix, std::move(ptr)); |
3874 | } |
3875 | } |
3876 | if (!transformed && IsUpdatePush(CacheDomain::TransformMatrix)) { |
3877 | // Otherwise, if we're bundling a transform update but this |
3878 | // frame isn't transformed (or doesn't exist), we need |
3879 | // to send a DeleteEntry() to remove any |
3880 | // transform that was previously cached for this frame. |
3881 | fields->SetAttribute(CacheKey::TransformMatrix, DeleteEntry()); |
3882 | } |
3883 | } |
3884 | |
3885 | if (aCacheDomain & CacheDomain::ScrollPosition && frame) { |
3886 | const auto [scrollPosition, scrollRange] = mDoc->ComputeScrollData(this); |
3887 | if (scrollRange.width || scrollRange.height) { |
3888 | // If the scroll range is 0 by 0, this acc is not scrollable. We |
3889 | // can't simply check scrollPosition != 0, since it's valid for scrollable |
3890 | // frames to have a (0, 0) position. We also can't check IsEmpty or |
3891 | // ZeroArea because frames with only one scrollable dimension will return |
3892 | // a height/width of zero for the non-scrollable dimension, yielding zero |
3893 | // area even if the width/height for the scrollable dimension is nonzero. |
3894 | // We also cache (0, 0) for accs with overflow:auto or overflow:scroll, |
3895 | // even if the content is not currently large enough to be scrollable |
3896 | // right now -- these accs have a non-zero scroll range. |
3897 | nsTArray<int32_t> positionArr(2); |
3898 | positionArr.AppendElement(scrollPosition.x); |
3899 | positionArr.AppendElement(scrollPosition.y); |
3900 | fields->SetAttribute(CacheKey::ScrollPosition, std::move(positionArr)); |
3901 | } else if (IsUpdatePush(CacheDomain::ScrollPosition)) { |
3902 | fields->SetAttribute(CacheKey::ScrollPosition, DeleteEntry()); |
3903 | } |
3904 | } |
3905 | |
3906 | if (aCacheDomain & CacheDomain::DOMNodeIDAndClass && mContent) { |
3907 | nsAtom* id = mContent->GetID(); |
3908 | if (id) { |
3909 | fields->SetAttribute(CacheKey::DOMNodeID, id); |
3910 | } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) { |
3911 | fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry()); |
3912 | } |
3913 | nsString className; |
3914 | DOMNodeClass(className); |
3915 | if (!className.IsEmpty()) { |
3916 | fields->SetAttribute(CacheKey::DOMNodeClass, std::move(className)); |
3917 | } else if (IsUpdatePush(CacheDomain::DOMNodeIDAndClass)) { |
3918 | fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry()); |
3919 | } |
3920 | } |
3921 | |
3922 | // State is only included in the initial push. Thereafter, cached state is |
3923 | // updated via events. |
3924 | if (aCacheDomain & CacheDomain::State) { |
3925 | if (IsInitialPush(CacheDomain::State)) { |
3926 | // Most states are updated using state change events, so we only send |
3927 | // these for the initial cache push. |
3928 | uint64_t state = ExplicitState(); |
3929 | // Exclude states which must be calculated by RemoteAccessible. |
3930 | state &= ~kRemoteCalculatedStates; |
3931 | fields->SetAttribute(CacheKey::State, state); |
3932 | } |
3933 | // If aria-selected isn't specified, there may be no SELECTED state. |
3934 | // However, aria-selected can be implicit in some cases when an item is |
3935 | // focused. We don't want to do this if aria-selected is explicitly |
3936 | // set to "false", so we need to differentiate between false and unset. |
3937 | if (auto ariaSelected = ARIASelected()) { |
3938 | fields->SetAttribute(CacheKey::ARIASelected, *ariaSelected); |
3939 | } else if (IsUpdatePush(CacheDomain::State)) { |
3940 | fields->SetAttribute(CacheKey::ARIASelected, DeleteEntry()); // Unset. |
3941 | } |
3942 | } |
3943 | |
3944 | if (aCacheDomain & CacheDomain::GroupInfo && mContent) { |
3945 | for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize, |
3946 | nsGkAtoms::aria_posinset}) { |
3947 | int32_t value = 0; |
3948 | if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) { |
3949 | fields->SetAttribute(attr, value); |
3950 | } else if (IsUpdatePush(CacheDomain::GroupInfo)) { |
3951 | fields->SetAttribute(attr, DeleteEntry()); |
3952 | } |
3953 | } |
3954 | } |
3955 | |
3956 | if (aCacheDomain & CacheDomain::Actions) { |
3957 | if (HasPrimaryAction()) { |
3958 | // Here we cache the primary action. |
3959 | nsAutoString actionName; |
3960 | ActionNameAt(0, actionName); |
3961 | RefPtr<nsAtom> actionAtom = NS_Atomize(actionName); |
3962 | fields->SetAttribute(CacheKey::PrimaryAction, actionAtom); |
3963 | } else if (IsUpdatePush(CacheDomain::Actions)) { |
3964 | fields->SetAttribute(CacheKey::PrimaryAction, DeleteEntry()); |
3965 | } |
3966 | |
3967 | if (ImageAccessible* imgAcc = AsImage()) { |
3968 | // Here we cache the showlongdesc action. |
3969 | if (imgAcc->HasLongDesc()) { |
3970 | fields->SetAttribute(CacheKey::HasLongdesc, true); |
3971 | } else if (IsUpdatePush(CacheDomain::Actions)) { |
3972 | fields->SetAttribute(CacheKey::HasLongdesc, DeleteEntry()); |
3973 | } |
3974 | } |
3975 | |
3976 | KeyBinding accessKey = AccessKey(); |
3977 | if (!accessKey.IsEmpty()) { |
3978 | fields->SetAttribute(CacheKey::AccessKey, accessKey.Serialize()); |
3979 | } else if (IsUpdatePush(CacheDomain::Actions)) { |
3980 | fields->SetAttribute(CacheKey::AccessKey, DeleteEntry()); |
3981 | } |
3982 | } |
3983 | |
3984 | if (aCacheDomain & CacheDomain::Style) { |
3985 | if (RefPtr<nsAtom> display = DisplayStyle()) { |
3986 | fields->SetAttribute(CacheKey::CSSDisplay, display); |
3987 | } |
3988 | |
3989 | float opacity = Opacity(); |
3990 | if (opacity != 1.0f) { |
3991 | fields->SetAttribute(CacheKey::Opacity, opacity); |
3992 | } else if (IsUpdatePush(CacheDomain::Style)) { |
3993 | fields->SetAttribute(CacheKey::Opacity, DeleteEntry()); |
3994 | } |
3995 | |
3996 | if (frame && |
3997 | frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed && |
3998 | nsLayoutUtils::IsReallyFixedPos(frame)) { |
3999 | fields->SetAttribute(CacheKey::CssPosition, nsGkAtoms::fixed); |
4000 | } else if (IsUpdatePush(CacheDomain::Style)) { |
4001 | fields->SetAttribute(CacheKey::CssPosition, DeleteEntry()); |
4002 | } |
4003 | |
4004 | if (frame) { |
4005 | nsAutoCString overflow; |
4006 | frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow); |
4007 | RefPtr<nsAtom> overflowAtom = NS_Atomize(overflow); |
4008 | if (overflowAtom == nsGkAtoms::hidden) { |
4009 | fields->SetAttribute(CacheKey::CSSOverflow, nsGkAtoms::hidden); |
4010 | } else if (IsUpdatePush(CacheDomain::Style)) { |
4011 | fields->SetAttribute(CacheKey::CSSOverflow, DeleteEntry()); |
4012 | } |
4013 | } |
4014 | } |
4015 | |
4016 | if (aCacheDomain & CacheDomain::Table) { |
4017 | if (auto* table = HTMLTableAccessible::GetFrom(this)) { |
4018 | if (table->IsProbablyLayoutTable()) { |
4019 | fields->SetAttribute(CacheKey::TableLayoutGuess, true); |
4020 | } else if (IsUpdatePush(CacheDomain::Table)) { |
4021 | fields->SetAttribute(CacheKey::TableLayoutGuess, DeleteEntry()); |
4022 | } |
4023 | } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) { |
4024 | // For HTML table cells, we must use the HTMLTableCellAccessible |
4025 | // GetRow/ColExtent methods rather than using the DOM attributes directly. |
4026 | // This is because of things like rowspan="0" which depend on knowing |
4027 | // about thead, tbody, etc., which is info we don't have in the a11y tree. |
4028 | int32_t value = static_cast<int32_t>(cell->RowExtent()); |
4029 | MOZ_ASSERT(value > 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(value > 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(value > 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("value > 0", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 4029); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0" ")"); do { MOZ_CrashSequence(__null, 4029); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
4030 | if (value > 1) { |
4031 | fields->SetAttribute(CacheKey::RowSpan, value); |
4032 | } else if (IsUpdatePush(CacheDomain::Table)) { |
4033 | fields->SetAttribute(CacheKey::RowSpan, DeleteEntry()); |
4034 | } |
4035 | value = static_cast<int32_t>(cell->ColExtent()); |
4036 | MOZ_ASSERT(value > 0)do { static_assert( mozilla::detail::AssertionConditionType< decltype(value > 0)>::isValid, "invalid assertion condition" ); if ((__builtin_expect(!!(!(!!(value > 0))), 0))) { do { } while (false); MOZ_ReportAssertionFailure("value > 0", "/root/firefox-clang/accessible/generic/LocalAccessible.cpp" , 4036); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0" ")"); do { MOZ_CrashSequence(__null, 4036); __attribute__((nomerge )) ::abort(); } while (false); } } while (false); |
4037 | if (value > 1) { |
4038 | fields->SetAttribute(CacheKey::ColSpan, value); |
4039 | } else if (IsUpdatePush(CacheDomain::Table)) { |
4040 | fields->SetAttribute(CacheKey::ColSpan, DeleteEntry()); |
4041 | } |
4042 | if (mContent->AsElement()->HasAttr(nsGkAtoms::headers)) { |
4043 | nsTArray<uint64_t> headers; |
4044 | AssociatedElementsIterator iter(mDoc, mContent, nsGkAtoms::headers); |
4045 | while (LocalAccessible* cell = iter.Next()) { |
4046 | if (cell->IsTableCell()) { |
4047 | headers.AppendElement(cell->ID()); |
4048 | } |
4049 | } |
4050 | fields->SetAttribute(CacheKey::CellHeaders, std::move(headers)); |
4051 | } else if (IsUpdatePush(CacheDomain::Table)) { |
4052 | fields->SetAttribute(CacheKey::CellHeaders, DeleteEntry()); |
4053 | } |
4054 | } |
4055 | } |
4056 | |
4057 | if (aCacheDomain & CacheDomain::ARIA && mContent && mContent->IsElement()) { |
4058 | // We use a nested AccAttributes to make cache updates simpler. Rather than |
4059 | // managing individual removals, we just replace or remove the entire set of |
4060 | // ARIA attributes. |
4061 | RefPtr<AccAttributes> ariaAttrs; |
4062 | aria::AttrIterator attrIt(mContent); |
4063 | while (attrIt.Next()) { |
4064 | if (!ariaAttrs) { |
4065 | ariaAttrs = new AccAttributes(); |
4066 | } |
4067 | attrIt.ExposeAttr(ariaAttrs); |
4068 | } |
4069 | if (ariaAttrs) { |
4070 | fields->SetAttribute(CacheKey::ARIAAttributes, std::move(ariaAttrs)); |
4071 | } else if (IsUpdatePush(CacheDomain::ARIA)) { |
4072 | fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry()); |
4073 | } |
4074 | } |
4075 | |
4076 | if (aCacheDomain & CacheDomain::Relations && mContent) { |
4077 | if (IsHTMLRadioButton() || |
4078 | (mContent->IsElement() && |
4079 | mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) { |
4080 | // HTML radio buttons with the same name should be grouped |
4081 | // and returned together when their MEMBER_OF relation is |
4082 | // requested. Computing LINKS_TO also requires we cache `name` on |
4083 | // anchor elements. |
4084 | nsString name; |
4085 | mContent->AsElement()->GetAttr(nsGkAtoms::name, name); |
4086 | if (!name.IsEmpty()) { |
4087 | fields->SetAttribute(CacheKey::DOMName, std::move(name)); |
4088 | } else if (IsUpdatePush(CacheDomain::Relations)) { |
4089 | // It's possible we used to have a name and it's since been |
4090 | // removed. Send a delete entry. |
4091 | fields->SetAttribute(CacheKey::DOMName, DeleteEntry()); |
4092 | } |
4093 | } |
4094 | |
4095 | for (auto const& data : kRelationTypeAtoms) { |
4096 | nsTArray<uint64_t> ids; |
4097 | nsStaticAtom* const relAtom = data.mAtom; |
4098 | |
4099 | Relation rel; |
4100 | if (data.mType == RelationType::LABEL_FOR) { |
4101 | // Labels are a special case -- we need to validate that the target of |
4102 | // their `for` attribute is in fact labelable. DOM checks this when we |
4103 | // call GetControl(). If a label contains an element we will return it |
4104 | // here. |
4105 | if (dom::HTMLLabelElement* labelEl = |
4106 | dom::HTMLLabelElement::FromNode(mContent)) { |
4107 | rel.AppendTarget(mDoc, labelEl->GetControl()); |
4108 | } |
4109 | } else if (data.mType == RelationType::DETAILS || |
4110 | data.mType == RelationType::CONTROLLER_FOR) { |
4111 | // We need to use RelationByType for details because it might include |
4112 | // popovertarget. Nothing exposes an implicit reverse details |
4113 | // relation, so using RelationByType here is fine. |
4114 | // |
4115 | // We need to use RelationByType for controls because it might include |
4116 | // failed aria-owned relocations or it may be an output element. |
4117 | // Nothing exposes an implicit reverse controls relation, so using |
4118 | // RelationByType here is fine. |
4119 | rel = RelationByType(data.mType); |
4120 | } else { |
4121 | // We use an AssociatedElementsIterator here instead of calling |
4122 | // RelationByType directly because we only want to cache explicit |
4123 | // relations. Implicit relations (e.g. LABEL_FOR exposed on the target |
4124 | // of aria-labelledby) will be computed and stored separately in the |
4125 | // parent process. |
4126 | rel.AppendIter(new AssociatedElementsIterator(mDoc, mContent, relAtom)); |
4127 | } |
4128 | |
4129 | while (LocalAccessible* acc = rel.LocalNext()) { |
4130 | ids.AppendElement(acc->ID()); |
4131 | } |
4132 | if (ids.Length()) { |
4133 | fields->SetAttribute(relAtom, std::move(ids)); |
4134 | } else if (IsUpdatePush(CacheDomain::Relations)) { |
4135 | fields->SetAttribute(relAtom, DeleteEntry()); |
4136 | } |
4137 | } |
4138 | } |
4139 | |
4140 | #if defined(XP_WIN) |
4141 | if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() && |
4142 | mContent->IsMathMLElement(nsGkAtoms::math)) { |
4143 | nsString innerHTML; |
4144 | mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors()); |
4145 | fields->SetAttribute(CacheKey::InnerHTML, std::move(innerHTML)); |
4146 | } |
4147 | #endif // defined(XP_WIN) |
4148 | |
4149 | if (aUpdateType == CacheUpdateType::Initial) { |
4150 | // Add fields which never change and thus only need to be included in the |
4151 | // initial cache push. |
4152 | if (mContent && mContent->IsElement()) { |
4153 | fields->SetAttribute(CacheKey::TagName, mContent->NodeInfo()->NameAtom()); |
4154 | |
4155 | dom::Element* el = mContent->AsElement(); |
4156 | if (IsTextField() || IsDateTimeField()) { |
4157 | // Cache text input types. Accessible is recreated if this changes, |
4158 | // so it is considered immutable. |
4159 | if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) { |
4160 | RefPtr<nsAtom> inputType = attr->GetAsAtom(); |
4161 | if (inputType) { |
4162 | fields->SetAttribute(CacheKey::InputType, inputType); |
4163 | } |
4164 | } |
4165 | } |
4166 | |
4167 | // Changing the role attribute currently re-creates the Accessible, so |
4168 | // it's immutable in the cache. |
4169 | if (const nsRoleMapEntry* roleMap = ARIARoleMap()) { |
4170 | // Most of the time, the role attribute is a single, known role. We |
4171 | // already send the map index, so we don't need to double up. |
4172 | if (!nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::role, roleMap->roleAtom, |
4173 | eIgnoreCase)) { |
4174 | // Multiple roles or unknown roles are rare, so just send them as a |
4175 | // string. |
4176 | nsAutoString role; |
4177 | nsAccUtils::GetARIAAttr(el, nsGkAtoms::role, role); |
4178 | fields->SetAttribute(CacheKey::ARIARole, std::move(role)); |
4179 | } |
4180 | } |
4181 | |
4182 | if (auto* htmlEl = nsGenericHTMLElement::FromNode(mContent)) { |
4183 | // Changing popover recreates the Accessible, so it's immutable in the |
4184 | // cache. |
4185 | nsAutoString popover; |
4186 | htmlEl->GetPopover(popover); |
4187 | if (!popover.IsEmpty()) { |
4188 | fields->SetAttribute(CacheKey::PopupType, |
4189 | RefPtr{NS_Atomize(popover)}); |
4190 | } |
4191 | } |
4192 | } |
4193 | |
4194 | if (frame) { |
4195 | // Note our frame's current computed style so we can track style changes |
4196 | // later on. |
4197 | mOldComputedStyle = frame->Style(); |
4198 | if (frame->IsTransformed()) { |
4199 | mStateFlags |= eOldFrameHasValidTransformStyle; |
4200 | } else { |
4201 | mStateFlags &= ~eOldFrameHasValidTransformStyle; |
4202 | } |
4203 | } |
4204 | |
4205 | if (IsDoc()) { |
4206 | if (PresShell* presShell = AsDoc()->PresShellPtr()) { |
4207 | // Send the initial resolution of the document. When this changes, we |
4208 | // will ne notified via nsAS::NotifyOfResolutionChange |
4209 | float resolution = presShell->GetResolution(); |
4210 | fields->SetAttribute(CacheKey::Resolution, resolution); |
4211 | int32_t appUnitsPerDevPixel = |
4212 | presShell->GetPresContext()->AppUnitsPerDevPixel(); |
4213 | fields->SetAttribute(CacheKey::AppUnitsPerDevPixel, |
4214 | appUnitsPerDevPixel); |
4215 | } |
4216 | |
4217 | nsString mimeType; |
4218 | AsDoc()->MimeType(mimeType); |
4219 | fields->SetAttribute(CacheKey::MimeType, std::move(mimeType)); |
4220 | } |
4221 | } |
4222 | |
4223 | if ((aCacheDomain & (CacheDomain::Text | CacheDomain::ScrollPosition | |
4224 | CacheDomain::APZ) || |
4225 | boundsChanged) && |
4226 | mDoc) { |
4227 | mDoc->SetViewportCacheDirty(true); |
4228 | } |
4229 | |
4230 | return fields.forget(); |
4231 | } |
4232 | |
4233 | void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() { |
4234 | // mOldComputedStyle might be null if the initial cache hasn't been sent yet. |
4235 | // In that case, there is nothing to do here. |
4236 | if (!IPCAccessibilityActive() || !mOldComputedStyle) { |
4237 | return; |
4238 | } |
4239 | |
4240 | if (nsIFrame* frame = GetFrame()) { |
4241 | const ComputedStyle* newStyle = frame->Style(); |
4242 | |
4243 | const auto overflowProps = |
4244 | nsCSSPropertyIDSet({eCSSProperty_overflow_x, eCSSProperty_overflow_y}); |
4245 | |
4246 | for (nsCSSPropertyID overflowProp : overflowProps) { |
4247 | nsAutoCString oldOverflow, newOverflow; |
4248 | mOldComputedStyle->GetComputedPropertyValue(overflowProp, oldOverflow); |
4249 | newStyle->GetComputedPropertyValue(overflowProp, newOverflow); |
4250 | |
4251 | if (oldOverflow != newOverflow) { |
4252 | if (oldOverflow.Equals("hidden"_ns) || |
4253 | newOverflow.Equals("hidden"_ns)) { |
4254 | mDoc->QueueCacheUpdate(this, CacheDomain::Style); |
4255 | } |
4256 | if (oldOverflow.Equals("auto"_ns) || newOverflow.Equals("auto"_ns) || |
4257 | oldOverflow.Equals("scroll"_ns) || |
4258 | newOverflow.Equals("scroll"_ns)) { |
4259 | // We cache a (0,0) scroll position for frames that have overflow |
4260 | // styling which means they _could_ become scrollable, even if the |
4261 | // content within them doesn't currently scroll. |
4262 | mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition); |
4263 | } |
4264 | } else { |
4265 | ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame); |
4266 | if (!scrollContainerFrame && (newOverflow.Equals("auto"_ns) || |
4267 | newOverflow.Equals("scroll"_ns))) { |
4268 | // A document's body element can lose its scroll frame if the root |
4269 | // element (eg. <html>) is restyled to overflow scroll/auto. In that |
4270 | // case we will not get any useful notifications for the body element |
4271 | // except for a reframe to a non-scrolling frame. |
4272 | mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition); |
4273 | } |
4274 | } |
4275 | } |
4276 | |
4277 | nsAutoCString oldDisplay, newDisplay; |
4278 | mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display, |
4279 | oldDisplay); |
4280 | newStyle->GetComputedPropertyValue(eCSSProperty_display, newDisplay); |
4281 | |
4282 | nsAutoCString oldOpacity, newOpacity; |
4283 | mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_opacity, |
4284 | oldOpacity); |
4285 | newStyle->GetComputedPropertyValue(eCSSProperty_opacity, newOpacity); |
4286 | |
4287 | if (oldDisplay != newDisplay || oldOpacity != newOpacity) { |
4288 | // CacheDomain::Style covers both display and opacity, so if |
4289 | // either property has changed, send an update for the entire domain. |
4290 | mDoc->QueueCacheUpdate(this, CacheDomain::Style); |
4291 | } |
4292 | |
4293 | nsAutoCString oldPosition, newPosition; |
4294 | mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_position, |
4295 | oldPosition); |
4296 | newStyle->GetComputedPropertyValue(eCSSProperty_position, newPosition); |
4297 | |
4298 | if (oldPosition != newPosition) { |
4299 | RefPtr<nsAtom> oldAtom = NS_Atomize(oldPosition); |
4300 | RefPtr<nsAtom> newAtom = NS_Atomize(newPosition); |
4301 | if (oldAtom == nsGkAtoms::fixed || newAtom == nsGkAtoms::fixed) { |
4302 | mDoc->QueueCacheUpdate(this, CacheDomain::Style); |
4303 | } |
4304 | } |
4305 | |
4306 | bool newHasValidTransformStyle = |
4307 | newStyle->StyleDisplay()->HasTransform(frame); |
4308 | bool oldHasValidTransformStyle = |
4309 | (mStateFlags & eOldFrameHasValidTransformStyle) != 0; |
4310 | |
4311 | // We should send a transform update if we're adding or |
4312 | // removing transform styling altogether. |
4313 | bool sendTransformUpdate = |
4314 | newHasValidTransformStyle || oldHasValidTransformStyle; |
4315 | |
4316 | if (newHasValidTransformStyle && oldHasValidTransformStyle) { |
4317 | // If we continue to have transform styling, verify |
4318 | // our transform has actually changed. |
4319 | nsChangeHint transformHint = |
4320 | newStyle->StyleDisplay()->CalcTransformPropertyDifference( |
4321 | *mOldComputedStyle->StyleDisplay()); |
4322 | // If this hint exists, it implies we found a property difference |
4323 | sendTransformUpdate = !!transformHint; |
4324 | } |
4325 | |
4326 | if (sendTransformUpdate) { |
4327 | // If our transform matrix has changed, it's possible our |
4328 | // viewport cache has also changed. |
4329 | mDoc->SetViewportCacheDirty(true); |
4330 | // Queuing a cache update for the TransformMatrix domain doesn't |
4331 | // necessarily mean we'll send the matrix itself, we may |
4332 | // send a DeleteEntry() instead. See BundleFieldsForCache for |
4333 | // more information. |
4334 | mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix); |
4335 | } |
4336 | |
4337 | mOldComputedStyle = newStyle; |
4338 | if (newHasValidTransformStyle) { |
4339 | mStateFlags |= eOldFrameHasValidTransformStyle; |
4340 | } else { |
4341 | mStateFlags &= ~eOldFrameHasValidTransformStyle; |
4342 | } |
4343 | } |
4344 | } |
4345 | |
4346 | nsAtom* LocalAccessible::TagName() const { |
4347 | return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom() |
4348 | : nullptr; |
4349 | } |
4350 | |
4351 | already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const { |
4352 | dom::Element* elm = Elm(); |
4353 | if (!elm) { |
4354 | return nullptr; |
4355 | } |
4356 | if (elm->IsHTMLElement(nsGkAtoms::area)) { |
4357 | // This is an image map area. CSS is irrelevant here. |
4358 | return nullptr; |
4359 | } |
4360 | static const dom::Element::AttrValuesArray presentationRoles[] = { |
4361 | nsGkAtoms::none, nsGkAtoms::presentation, nullptr}; |
4362 | if (nsAccUtils::FindARIAAttrValueIn(elm, nsGkAtoms::role, presentationRoles, |
4363 | eIgnoreCase) != AttrArray::ATTR_MISSING && |
4364 | IsGeneric()) { |
4365 | // This Accessible has been marked presentational, but we forced a generic |
4366 | // Accessible for some reason; e.g. CSS transform. Don't expose display in |
4367 | // this case, as the author might be explicitly trying to avoid said |
4368 | // exposure. |
4369 | return nullptr; |
4370 | } |
4371 | RefPtr<const ComputedStyle> style = |
4372 | nsComputedDOMStyle::GetComputedStyleNoFlush(elm); |
4373 | if (!style) { |
4374 | // The element is not styled, maybe not in the flat tree? |
4375 | return nullptr; |
4376 | } |
4377 | nsAutoCString value; |
4378 | style->GetComputedPropertyValue(eCSSProperty_display, value); |
4379 | return NS_Atomize(value); |
4380 | } |
4381 | |
4382 | float LocalAccessible::Opacity() const { |
4383 | if (nsIFrame* frame = GetFrame()) { |
4384 | return frame->StyleEffects()->mOpacity; |
4385 | } |
4386 | |
4387 | return 1.0f; |
4388 | } |
4389 | |
4390 | void LocalAccessible::DOMNodeID(nsString& aID) const { |
4391 | aID.Truncate(); |
4392 | if (mContent) { |
4393 | if (nsAtom* id = mContent->GetID()) { |
4394 | id->ToString(aID); |
4395 | } |
4396 | } |
4397 | } |
4398 | |
4399 | void LocalAccessible::DOMNodeClass(nsString& aClass) const { |
4400 | aClass.Truncate(); |
4401 | if (auto* el = dom::Element::FromNodeOrNull(mContent)) { |
4402 | el->GetClassName(aClass); |
4403 | } |
4404 | } |
4405 | |
4406 | void LocalAccessible::LiveRegionAttributes(nsAString* aLive, |
4407 | nsAString* aRelevant, |
4408 | Maybe<bool>* aAtomic, |
4409 | nsAString* aBusy) const { |
4410 | dom::Element* el = Elm(); |
4411 | if (!el) { |
4412 | return; |
4413 | } |
4414 | if (aLive) { |
4415 | nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_live, *aLive); |
4416 | } |
4417 | if (aRelevant) { |
4418 | nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_relevant, *aRelevant); |
4419 | } |
4420 | if (aAtomic) { |
4421 | // XXX We ignore aria-atomic="false", but this probably doesn't conform to |
4422 | // the spec. |
4423 | if (nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_atomic, |
4424 | nsGkAtoms::_true, eCaseMatters)) { |
4425 | *aAtomic = Some(true); |
4426 | } |
4427 | } |
4428 | if (aBusy) { |
4429 | nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_busy, *aBusy); |
4430 | } |
4431 | } |
4432 | |
4433 | Maybe<bool> LocalAccessible::ARIASelected() const { |
4434 | if (dom::Element* el = Elm()) { |
4435 | nsStaticAtom* atom = |
4436 | nsAccUtils::NormalizeARIAToken(el, nsGkAtoms::aria_selected); |
4437 | if (atom == nsGkAtoms::_true) { |
4438 | return Some(true); |
4439 | } |
4440 | if (atom == nsGkAtoms::_false) { |
4441 | return Some(false); |
4442 | } |
4443 | } |
4444 | return Nothing(); |
4445 | } |
4446 | |
4447 | void LocalAccessible::StaticAsserts() const { |
4448 | static_assert( |
4449 | eLastStateFlag <= (1 << kStateFlagsBits) - 1, |
4450 | "LocalAccessible::mStateFlags was oversized by eLastStateFlag!"); |
4451 | static_assert( |
4452 | eLastContextFlag <= (1 << kContextFlagsBits) - 1, |
4453 | "LocalAccessible::mContextFlags was oversized by eLastContextFlag!"); |
4454 | } |
4455 | |
4456 | TableAccessible* LocalAccessible::AsTable() { |
4457 | if (IsTable() && !mContent->IsXULElement()) { |
4458 | return CachedTableAccessible::GetFrom(this); |
4459 | } |
4460 | return nullptr; |
4461 | } |
4462 | |
4463 | TableCellAccessible* LocalAccessible::AsTableCell() { |
4464 | if (IsTableCell() && !mContent->IsXULElement()) { |
4465 | return CachedTableCellAccessible::GetFrom(this); |
4466 | } |
4467 | return nullptr; |
4468 | } |
4469 | |
4470 | Maybe<int32_t> LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const { |
4471 | if (mContent) { |
4472 | int32_t val; |
4473 | if (nsCoreUtils::GetUIntAttr(mContent, aAttrName, &val)) { |
4474 | return Some(val); |
4475 | } |
4476 | // XXX Handle attributes that allow -1; e.g. aria-row/colcount. |
4477 | } |
4478 | return Nothing(); |
4479 | } |
4480 | |
4481 | bool LocalAccessible::GetStringARIAAttr(nsAtom* aAttrName, |
4482 | nsAString& aAttrValue) const { |
4483 | if (dom::Element* elm = Elm()) { |
4484 | return nsAccUtils::GetARIAAttr(elm, aAttrName, aAttrValue); |
4485 | } |
4486 | return false; |
4487 | } |