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