Bug Summary

File:var/lib/jenkins/workspace/firefox-scan-build/accessible/generic/LocalAccessible.cpp
Warning:line 3029, column 11
Although the value stored to 'selected' is used in the enclosing expression, the value is never actually read from 'selected'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name Unified_cpp_accessible_generic0.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -analyzer-config-compatibility-mode=true -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -ffp-contract=off -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/accessible/generic -fcoverage-compilation-dir=/var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/accessible/generic -resource-dir /usr/lib/llvm-18/lib/clang/18 -include /var/lib/jenkins/workspace/firefox-scan-build/config/gcc_hidden.h -include /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/mozilla-config.h -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/stl_wrappers -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/system_wrappers -U _FORTIFY_SOURCE -D _FORTIFY_SOURCE=2 -D DEBUG=1 -D MOZ_HAS_MOZGLUE -D MOZILLA_INTERNAL_API -D IMPL_LIBXUL -D MOZ_SUPPORT_LEAKCHECKING -D STATIC_EXPORTABLE_JS_API -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/generic -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/accessible/generic -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/base -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/html -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/xpcom -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/xul -I /var/lib/jenkins/workspace/firefox-scan-build/dom/base -I /var/lib/jenkins/workspace/firefox-scan-build/dom/xul -I /var/lib/jenkins/workspace/firefox-scan-build/layout/generic -I /var/lib/jenkins/workspace/firefox-scan-build/layout/xul -I /var/lib/jenkins/workspace/firefox-scan-build/accessible/atk -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/ipc/ipdl/_ipdlheaders -I /var/lib/jenkins/workspace/firefox-scan-build/ipc/chromium/src -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nspr -I /var/lib/jenkins/workspace/firefox-scan-build/obj-x86_64-pc-linux-gnu/dist/include/nss -D MOZILLA_CLIENT -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -Wno-error=tautological-type-limit-compare -Wno-invalid-offsetof -Wno-range-loop-analysis -Wno-deprecated-anon-enum-enum-conversion -Wno-deprecated-enum-enum-conversion -Wno-deprecated-this-capture -Wno-inline-new-delete -Wno-error=deprecated-declarations -Wno-error=array-bounds -Wno-error=free-nonheap-object -Wno-error=atomic-alignment -Wno-error=deprecated-builtins -Wno-psabi -Wno-error=builtin-macro-redefined -Wno-vla-cxx-extension -Wno-unknown-warning-option -fdeprecated-macro -ferror-limit 19 -stack-protector 2 -fstack-clash-protection -ftrivial-auto-var-init=pattern -fno-rtti -fgnuc-version=4.2.1 -fno-aligned-allocation -vectorize-loops -vectorize-slp -analyzer-checker optin.performance.Padding -analyzer-output=html -analyzer-config stable-report-filename=true -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2024-07-27-022226-2793976-1 -x c++ Unified_cpp_accessible_generic0.cpp
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
83using namespace mozilla;
84using namespace mozilla::a11y;
85
86////////////////////////////////////////////////////////////////////////////////
87// LocalAccessible: nsISupports and cycle collection
88
89NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)LocalAccessible::cycleCollection LocalAccessible::_cycleCollectorGlobal
;
90NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)void LocalAccessible::cycleCollection::Unlink(void* p) { LocalAccessible
* tmp = DowncastCCParticipant<LocalAccessible>(p);
91 tmp->Shutdown();
92NS_IMPL_CYCLE_COLLECTION_UNLINK_END(void)tmp; }
93NS_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)
;
95NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END(void)tmp; return NS_OK; }
96
97NS_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
100NS_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
102NS_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; }
103NS_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
105LocalAccessible::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
119LocalAccessible::~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
123ENameValueFlag 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
167void 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
209KeyBinding 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
273KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }
274
275uint64_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
368uint64_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
411uint64_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
437uint64_t LocalAccessible::NativeLinkState() const { return 0; }
438
439bool 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
447Accessible* 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
458LocalAccessible* 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
585nsIFrame* 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
613nsRect 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
693nsRect 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
719nsRect 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
746LayoutDeviceIntRect LocalAccessible::Bounds() const {
747 return LayoutDeviceIntRect::FromAppUnitsToNearest(
748 BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel());
749}
750
751void LocalAccessible::SetSelected(bool aSelect) {
752 if (!HasOwnContent()) return;
753
754 if (nsAccUtils::GetSelectableContainer(this, State()) && aSelect) {
755 TakeFocus();
756 }
757}
758
759void 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
767void 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
795void 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
812void 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
845nsresult 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
1094already_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
1141already_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
1284bool 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
1306void 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 IDRefsIterator 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 IDRefsIterator 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 IDRefsIterator 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 IDRefsIterator 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
1576void 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
1593uint64_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
1629void 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
1725void 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
1778double 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
1792double 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
1806double LocalAccessible::Step() const {
1807 return UnspecifiedNaN<double>(); // no mimimum increment (step) in ARIA.
1808}
1809
1810double 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
1826bool LocalAccessible::SetCurValue(double aValue) { return false; }
1827
1828role 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
1856role 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
2025role 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
2038role LocalAccessible::NativeRole() const { return roles::NOTHING; }
2039
2040uint8_t LocalAccessible::ActionCount() const {
2041 return HasPrimaryAction() || ActionAncestor() ? 1 : 0;
2042}
2043
2044void 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
2115bool 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
2126bool LocalAccessible::HasPrimaryAction() const {
2127 return GetActionRule() != eNoAction;
2128}
2129
2130nsIContent* 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
2143LocalAccessible* 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
2167Relation 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(
2177 new IDRefsIterator(mDoc, mContent, 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(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
2191 }
2192
2193 return rel;
2194 }
2195
2196 case RelationType::DESCRIBED_BY: {
2197 Relation rel(
2198 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_describedby));
2199 if (mContent->IsXULElement()) {
2200 rel.AppendIter(new XULDescriptionIterator(Document(), mContent));
2201 }
2202
2203 return rel;
2204 }
2205
2206 case RelationType::DESCRIPTION_FOR: {
2207 Relation rel(new RelatedAccIterator(Document(), mContent,
2208 nsGkAtoms::aria_describedby));
2209
2210 // This affectively adds an optional control attribute to xul:description,
2211 // which only affects accessibility, by allowing the description to be
2212 // tied to a control.
2213 if (mContent->IsXULElement(nsGkAtoms::description)) {
2214 rel.AppendIter(new IDRefsIterator(mDoc, mContent, nsGkAtoms::control));
2215 }
2216
2217 return rel;
2218 }
2219
2220 case RelationType::NODE_CHILD_OF: {
2221 Relation rel;
2222 // This is an ARIA tree or treegrid that doesn't use owns, so we need to
2223 // get the parent the hard way.
2224 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2225 roleMapEntry->role == roles::LISTITEM ||
2226 roleMapEntry->role == roles::ROW)) {
2227 AccGroupInfo* groupInfo =
2228 const_cast<LocalAccessible*>(this)->GetOrCreateGroupInfo();
2229 if (groupInfo) {
2230 Accessible* parent = groupInfo->ConceptualParent();
2231 if (parent) {
2232 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"
, 2232); AnnotateMozCrashReason("MOZ_ASSERT" "(" "parent->IsLocal()"
")"); do { *((volatile int*)__null) = 2232; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2233 rel.AppendTarget(parent->AsLocal());
2234 }
2235 }
2236 }
2237
2238 // If this is an OOP iframe document, we can't support NODE_CHILD_OF
2239 // here, since the iframe resides in a different process. This is fine
2240 // because the client will then request the parent instead, which will be
2241 // correctly handled by platform code.
2242 if (XRE_IsContentProcess() && IsRoot()) {
2243 dom::Document* doc =
2244 const_cast<LocalAccessible*>(this)->AsDoc()->DocumentNode();
2245 dom::BrowsingContext* bc = doc->GetBrowsingContext();
2246 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"
, 2246); AnnotateMozCrashReason("MOZ_ASSERT" "(" "bc" ")"); do
{ *((volatile int*)__null) = 2246; __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
2247 if (!bc->Top()->IsInProcess()) {
2248 return rel;
2249 }
2250 }
2251
2252 // If accessible is in its own Window, or is the root of a document,
2253 // then we should provide NODE_CHILD_OF relation so that MSAA clients
2254 // can easily get to true parent instead of getting to oleacc's
2255 // ROLE_WINDOW accessible which will prevent us from going up further
2256 // (because it is system generated and has no idea about the hierarchy
2257 // above it).
2258 nsIFrame* frame = GetFrame();
2259 if (frame) {
2260 nsView* view = frame->GetView();
2261 if (view) {
2262 ScrollContainerFrame* scrollContainerFrame = do_QueryFrame(frame);
2263 if (scrollContainerFrame || view->GetWidget() ||
2264 !frame->GetParent()) {
2265 rel.AppendTarget(LocalParent());
2266 }
2267 }
2268 }
2269
2270 return rel;
2271 }
2272
2273 case RelationType::NODE_PARENT_OF: {
2274 // ARIA tree or treegrid can do the hierarchy by @aria-level, ARIA trees
2275 // also can be organized by groups.
2276 if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
2277 roleMapEntry->role == roles::LISTITEM ||
2278 roleMapEntry->role == roles::ROW ||
2279 roleMapEntry->role == roles::OUTLINE ||
2280 roleMapEntry->role == roles::LIST ||
2281 roleMapEntry->role == roles::TREE_TABLE)) {
2282 return Relation(new ItemIterator(this));
2283 }
2284
2285 return Relation();
2286 }
2287
2288 case RelationType::CONTROLLED_BY:
2289 return Relation(new RelatedAccIterator(Document(), mContent,
2290 nsGkAtoms::aria_controls));
2291
2292 case RelationType::CONTROLLER_FOR: {
2293 Relation rel(
2294 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_controls));
2295 rel.AppendIter(new HTMLOutputIterator(Document(), mContent));
2296 return rel;
2297 }
2298
2299 case RelationType::FLOWS_TO:
2300 return Relation(
2301 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_flowto));
2302
2303 case RelationType::FLOWS_FROM:
2304 return Relation(
2305 new RelatedAccIterator(Document(), mContent, nsGkAtoms::aria_flowto));
2306
2307 case RelationType::MEMBER_OF: {
2308 if (Role() == roles::RADIOBUTTON) {
2309 /* If we see a radio button role here, we're dealing with an aria
2310 * radio button (because input=radio buttons are
2311 * HTMLRadioButtonAccessibles) */
2312 Relation rel = Relation();
2313 LocalAccessible* currParent = LocalParent();
2314 while (currParent && currParent->Role() != roles::RADIO_GROUP) {
2315 currParent = currParent->LocalParent();
2316 }
2317
2318 if (currParent && currParent->Role() == roles::RADIO_GROUP) {
2319 /* If we found a radiogroup parent, search for all
2320 * roles::RADIOBUTTON children and add them to our relation.
2321 * This search will include the radio button this method
2322 * was called from, which is expected. */
2323 Pivot p = Pivot(currParent);
2324 PivotRoleRule rule(roles::RADIOBUTTON);
2325 Accessible* match = p.Next(currParent, rule);
2326 while (match) {
2327 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"
, 2329); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { *((volatile int*)__null) = 2329; __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2328 "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"
, 2329); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { *((volatile int*)__null) = 2329; __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
2329 "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"
, 2329); AnnotateMozCrashReason("MOZ_ASSERT" "(" "match->IsLocal()"
") (" "We shouldn't find any remote accs while building our "
"relation!" ")"); do { *((volatile int*)__null) = 2329; __attribute__
((nomerge)) ::abort(); } while (false); } } while (false)
;
2330 rel.AppendTarget(match->AsLocal());
2331 match = p.Next(match, rule);
2332 }
2333 }
2334
2335 /* By webkit's standard, aria radio buttons do not get grouped
2336 * if they lack a group parent, so we return an empty
2337 * relation here if the above check fails. */
2338
2339 return rel;
2340 }
2341
2342 return Relation(mDoc, GetAtomicRegion());
2343 }
2344
2345 case RelationType::LINKS_TO: {
2346 Relation rel = Relation();
2347 if (Role() == roles::LINK) {
2348 dom::HTMLAnchorElement* anchor =
2349 dom::HTMLAnchorElement::FromNode(mContent);
2350 if (!anchor) {
2351 return rel;
2352 }
2353 // If this node is an anchor element, query its hash to find the
2354 // target.
2355 nsAutoCString hash;
2356 anchor->GetHash(hash);
2357 if (hash.IsEmpty()) {
2358 return rel;
2359 }
2360
2361 // GetHash returns an ID or name with a leading '#', trim it so we can
2362 // search the doc by ID or name alone.
2363 NS_ConvertUTF8toUTF16 hash16(Substring(hash, 1));
2364 if (dom::Element* elm = mContent->OwnerDoc()->GetElementById(hash16)) {
2365 rel.AppendTarget(mDoc->GetAccessibleOrContainer(elm));
2366 } else if (nsCOMPtr<nsINodeList> list =
2367 mContent->OwnerDoc()->GetElementsByName(hash16)) {
2368 // Loop through the named nodes looking for the first anchor
2369 uint32_t length = list->Length();
2370 for (uint32_t i = 0; i < length; i++) {
2371 nsIContent* node = list->Item(i);
2372 if (node->IsHTMLElement(nsGkAtoms::a)) {
2373 rel.AppendTarget(mDoc->GetAccessibleOrContainer(node));
2374 break;
2375 }
2376 }
2377 }
2378 }
2379
2380 return rel;
2381 }
2382
2383 case RelationType::SUBWINDOW_OF:
2384 case RelationType::EMBEDS:
2385 case RelationType::EMBEDDED_BY:
2386 case RelationType::POPUP_FOR:
2387 case RelationType::PARENT_WINDOW_OF:
2388 return Relation();
2389
2390 case RelationType::DEFAULT_BUTTON: {
2391 if (mContent->IsHTMLElement()) {
2392 // HTML form controls implements nsIFormControl interface.
2393 if (auto* control = nsIFormControl::FromNode(mContent)) {
2394 if (dom::HTMLFormElement* form = control->GetForm()) {
2395 return Relation(mDoc, form->GetDefaultSubmitElement());
2396 }
2397 }
2398 } else {
2399 // In XUL, use first <button default="true" .../> in the document
2400 dom::Document* doc = mContent->OwnerDoc();
2401 nsIContent* buttonEl = nullptr;
2402 if (doc->AllowXULXBL()) {
2403 nsCOMPtr<nsIHTMLCollection> possibleDefaultButtons =
2404 doc->GetElementsByAttribute(u"default"_ns, u"true"_ns);
2405 if (possibleDefaultButtons) {
2406 uint32_t length = possibleDefaultButtons->Length();
2407 // Check for button in list of default="true" elements
2408 for (uint32_t count = 0; count < length && !buttonEl; count++) {
2409 nsIContent* item = possibleDefaultButtons->Item(count);
2410 RefPtr<nsIDOMXULButtonElement> button =
2411 item->IsElement() ? item->AsElement()->AsXULButton()
2412 : nullptr;
2413 if (button) {
2414 buttonEl = item;
2415 }
2416 }
2417 }
2418 return Relation(mDoc, buttonEl);
2419 }
2420 }
2421 return Relation();
2422 }
2423
2424 case RelationType::CONTAINING_DOCUMENT:
2425 return Relation(mDoc);
2426
2427 case RelationType::CONTAINING_TAB_PANE: {
2428 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2429 if (docShell) {
2430 // Walk up the parent chain without crossing the boundary at which item
2431 // types change, preventing us from walking up out of tab content.
2432 nsCOMPtr<nsIDocShellTreeItem> root;
2433 docShell->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
2434 if (root) {
2435 // If the item type is typeContent, we assume we are in browser tab
2436 // content. Note, this includes content such as about:addons,
2437 // for consistency.
2438 if (root->ItemType() == nsIDocShellTreeItem::typeContent) {
2439 return Relation(nsAccUtils::GetDocAccessibleFor(root));
2440 }
2441 }
2442 }
2443 return Relation();
2444 }
2445
2446 case RelationType::CONTAINING_APPLICATION:
2447 return Relation(ApplicationAcc());
2448
2449 case RelationType::DETAILS: {
2450 if (mContent->IsElement() &&
2451 mContent->AsElement()->HasAttr(nsGkAtoms::aria_details)) {
2452 return Relation(
2453 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_details));
2454 }
2455 if (LocalAccessible* target = GetPopoverTargetDetailsRelation()) {
2456 return Relation(target);
2457 }
2458 return Relation();
2459 }
2460
2461 case RelationType::DETAILS_FOR: {
2462 Relation rel(
2463 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_details));
2464 RelatedAccIterator invokers(mDoc, mContent, nsGkAtoms::popovertarget);
2465 while (Accessible* invoker = invokers.Next()) {
2466 // We should only expose DETAILS_FOR if DETAILS was exposed on the
2467 // invoker. However, DETAILS exposure on popover invokers is
2468 // conditional.
2469 LocalAccessible* popoverTarget =
2470 invoker->AsLocal()->GetPopoverTargetDetailsRelation();
2471 if (popoverTarget) {
2472 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"
, 2472); AnnotateMozCrashReason("MOZ_ASSERT" "(" "popoverTarget == this"
")"); do { *((volatile int*)__null) = 2472; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2473 rel.AppendTarget(invoker);
2474 }
2475 }
2476 return rel;
2477 }
2478
2479 case RelationType::ERRORMSG:
2480 return Relation(
2481 new IDRefsIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2482
2483 case RelationType::ERRORMSG_FOR:
2484 return Relation(
2485 new RelatedAccIterator(mDoc, mContent, nsGkAtoms::aria_errormessage));
2486
2487 default:
2488 return Relation();
2489 }
2490}
2491
2492void LocalAccessible::GetNativeInterface(void** aNativeAccessible) {}
2493
2494void LocalAccessible::DoCommand(nsIContent* aContent,
2495 uint32_t aActionIndex) const {
2496 class Runnable final : public mozilla::Runnable {
2497 public:
2498 Runnable(const LocalAccessible* aAcc, nsIContent* aContent, uint32_t aIdx)
2499 : mozilla::Runnable("Runnable"),
2500 mAcc(aAcc),
2501 mContent(aContent),
2502 mIdx(aIdx) {}
2503
2504 // XXX Cannot mark as MOZ_CAN_RUN_SCRIPT because the base class change
2505 // requires too big changes across a lot of modules.
2506 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODvirtual nsresult Run() override {
2507 if (mAcc) {
2508 MOZ_KnownLive(mAcc)(mAcc)->DispatchClickEvent(MOZ_KnownLive(mContent)(mContent), mIdx);
2509 }
2510 return NS_OK;
2511 }
2512
2513 void Revoke() {
2514 mAcc = nullptr;
2515 mContent = nullptr;
2516 }
2517
2518 private:
2519 RefPtr<const LocalAccessible> mAcc;
2520 nsCOMPtr<nsIContent> mContent;
2521 uint32_t mIdx;
2522 };
2523
2524 nsIContent* content = aContent ? aContent : mContent.get();
2525 nsCOMPtr<nsIRunnable> runnable = new Runnable(this, content, aActionIndex);
2526 NS_DispatchToMainThread(runnable);
2527}
2528
2529void LocalAccessible::DispatchClickEvent(nsIContent* aContent,
2530 uint32_t aActionIndex) const {
2531 if (IsDefunct()) return;
2532
2533 RefPtr<PresShell> presShell = mDoc->PresShellPtr();
2534
2535 // Scroll into view.
2536 presShell->ScrollContentIntoView(aContent, ScrollAxis(), ScrollAxis(),
2537 ScrollFlags::ScrollOverflowHidden);
2538
2539 AutoWeakFrame frame = aContent->GetPrimaryFrame();
2540 if (!frame) return;
2541
2542 // Compute x and y coordinates.
2543 nsPoint point;
2544 nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
2545 if (!widget) return;
2546
2547 nsSize size = frame->GetSize();
2548
2549 RefPtr<nsPresContext> presContext = presShell->GetPresContext();
2550 int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
2551 int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
2552
2553 // Simulate a touch interaction by dispatching touch events with mouse events.
2554 nsCoreUtils::DispatchTouchEvent(eTouchStart, x, y, aContent, frame, presShell,
2555 widget);
2556 nsCoreUtils::DispatchMouseEvent(eMouseDown, x, y, aContent, frame, presShell,
2557 widget);
2558 nsCoreUtils::DispatchTouchEvent(eTouchEnd, x, y, aContent, frame, presShell,
2559 widget);
2560 nsCoreUtils::DispatchMouseEvent(eMouseUp, x, y, aContent, frame, presShell,
2561 widget);
2562}
2563
2564void LocalAccessible::ScrollToPoint(uint32_t aCoordinateType, int32_t aX,
2565 int32_t aY) {
2566 nsIFrame* frame = GetFrame();
2567 if (!frame) return;
2568
2569 LayoutDeviceIntPoint coords =
2570 nsAccUtils::ConvertToScreenCoords(aX, aY, aCoordinateType, this);
2571
2572 nsIFrame* parentFrame = frame;
2573 while ((parentFrame = parentFrame->GetParent())) {
2574 nsCoreUtils::ScrollFrameToPoint(parentFrame, frame, coords);
2575 }
2576}
2577
2578void LocalAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
2579 uint32_t aLength) {
2580 // Return text representation of non-text accessible within hypertext
2581 // accessible. Text accessible overrides this method to return enclosed text.
2582 if (aStartOffset != 0 || aLength == 0) return;
2583
2584 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"
, 2585); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") ("
"Called on accessible unbound from tree. Result can be wrong."
")"); do { *((volatile int*)__null) = 2585; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
2585 "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"
, 2585); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mParent" ") ("
"Called on accessible unbound from tree. Result can be wrong."
")"); do { *((volatile int*)__null) = 2585; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2586 nsIFrame* frame = GetFrame();
2587 // We handle something becoming display: none async, which means we won't have
2588 // a frame when we're queuing text removed events. Thus, it's important that
2589 // we produce text here even if there's no frame. Otherwise, we won't fire a
2590 // text removed event at all, which might leave client caches (e.g. NVDA
2591 // virtual buffers) with dead nodes.
2592 if (IsHTMLBr() || (frame && frame->IsBrFrame())) {
2593 aText += kForcedNewLineChar;
2594 } else if (mParent && nsAccUtils::MustPrune(mParent)) {
2595 // Expose the embedded object accessible as imaginary embedded object
2596 // character if its parent hypertext accessible doesn't expose children to
2597 // AT.
2598 aText += kImaginaryEmbeddedObjectChar;
2599 } else {
2600 aText += kEmbeddedObjectChar;
2601 }
2602}
2603
2604void LocalAccessible::Shutdown() {
2605 // Mark the accessible as defunct, invalidate the child count and pointers to
2606 // other accessibles, also make sure none of its children point to this
2607 // parent
2608 mStateFlags |= eIsDefunct;
2609
2610 int32_t childCount = mChildren.Length();
2611 for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
2612 mChildren.ElementAt(childIdx)->UnbindFromParent();
2613 }
2614 mChildren.Clear();
2615
2616 mEmbeddedObjCollector = nullptr;
2617
2618 if (mParent) mParent->RemoveChild(this);
2619
2620 mContent = nullptr;
2621 mDoc = nullptr;
2622 if (SelectionMgr() && SelectionMgr()->AccessibleWithCaret(nullptr) == this) {
2623 SelectionMgr()->ResetCaretOffset();
2624 }
2625}
2626
2627// LocalAccessible protected
2628void LocalAccessible::ARIAName(nsString& aName) const {
2629 // 'slot' elements should ignore aria-label and aria-labelledby.
2630 if (mContent->IsHTMLElement(nsGkAtoms::slot)) {
2631 return;
2632 }
2633 // aria-labelledby now takes precedence over aria-label
2634 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2635 this, nsGkAtoms::aria_labelledby, aName);
2636 if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) {
2637 aName.CompressWhitespace();
2638 }
2639
2640 if (aName.IsEmpty() && mContent->IsElement() &&
2641 nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::aria_label,
2642 aName)) {
2643 aName.CompressWhitespace();
2644 }
2645}
2646
2647// LocalAccessible protected
2648void LocalAccessible::ARIADescription(nsString& aDescription) const {
2649 // aria-describedby takes precedence over aria-description
2650 nsresult rv = nsTextEquivUtils::GetTextEquivFromIDRefs(
2651 this, nsGkAtoms::aria_describedby, aDescription);
2652 if (NS_SUCCEEDED(rv)((bool)(__builtin_expect(!!(!NS_FAILED_impl(rv)), 1)))) {
2653 aDescription.CompressWhitespace();
2654 }
2655
2656 if (aDescription.IsEmpty() && mContent->IsElement() &&
2657 nsAccUtils::GetARIAAttr(mContent->AsElement(),
2658 nsGkAtoms::aria_description, aDescription)) {
2659 aDescription.CompressWhitespace();
2660 }
2661}
2662
2663// LocalAccessible protected
2664ENameValueFlag LocalAccessible::NativeName(nsString& aName) const {
2665 if (mContent->IsHTMLElement()) {
2666 LocalAccessible* label = nullptr;
2667 HTMLLabelIterator iter(Document(), this);
2668 while ((label = iter.Next())) {
2669 nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
2670 &aName);
2671 aName.CompressWhitespace();
2672 }
2673
2674 if (!aName.IsEmpty()) return eNameOK;
2675
2676 NameFromAssociatedXULLabel(mDoc, mContent, aName);
2677 if (!aName.IsEmpty()) {
2678 return eNameOK;
2679 }
2680
2681 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2682 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2683 }
2684
2685 if (mContent->IsXULElement()) {
2686 XULElmName(mDoc, mContent, aName);
2687 if (!aName.IsEmpty()) return eNameOK;
2688
2689 nsTextEquivUtils::GetNameFromSubtree(this, aName);
2690 return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
2691 }
2692
2693 if (mContent->IsSVGElement()) {
2694 // If user agents need to choose among multiple 'desc' or 'title'
2695 // elements for processing, the user agent shall choose the first one.
2696 for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
2697 childElm = childElm->GetNextSibling()) {
2698 if (childElm->IsSVGElement(nsGkAtoms::title)) {
2699 nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
2700 return eNameOK;
2701 }
2702 }
2703 }
2704
2705 return eNameOK;
2706}
2707
2708// LocalAccessible protected
2709void LocalAccessible::NativeDescription(nsString& aDescription) const {
2710 bool isXUL = mContent->IsXULElement();
2711 if (isXUL) {
2712 // Try XUL <description control="[id]">description text</description>
2713 XULDescriptionIterator iter(Document(), mContent);
2714 LocalAccessible* descr = nullptr;
2715 while ((descr = iter.Next())) {
2716 nsTextEquivUtils::AppendTextEquivFromContent(this, descr->GetContent(),
2717 &aDescription);
2718 }
2719 }
2720}
2721
2722// LocalAccessible protected
2723void LocalAccessible::BindToParent(LocalAccessible* aParent,
2724 uint32_t aIndexInParent) {
2725 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"
, 2725); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aParent" ") ("
"This method isn't used to set null parent" ")"); do { *((volatile
int*)__null) = 2725; __attribute__((nomerge)) ::abort(); } while
(false); } } while (false)
;
2726 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"
, 2726); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!mParent" ") ("
"The child was expected to be moved" ")"); do { *((volatile int
*)__null) = 2726; __attribute__((nomerge)) ::abort(); } while
(false); } } while (false)
;
2727
2728#ifdef A11Y_LOG1
2729 if (mParent) {
2730 logging::TreeInfo("BindToParent: stealing accessible", 0, "old parent",
2731 mParent, "new parent", aParent, "child", this, nullptr);
2732 }
2733#endif
2734
2735 mParent = aParent;
2736 mIndexInParent = aIndexInParent;
2737
2738 if (mParent->HasNameDependent() || mParent->IsXULListItem() ||
2739 RelationByType(RelationType::LABEL_FOR).Next() ||
2740 nsTextEquivUtils::HasNameRule(mParent, eNameFromSubtreeRule)) {
2741 mContextFlags |= eHasNameDependent;
2742 } else {
2743 mContextFlags &= ~eHasNameDependent;
2744 }
2745 if (mParent->HasDescriptionDependent() ||
2746 RelationByType(RelationType::DESCRIPTION_FOR).Next()) {
2747 mContextFlags |= eHasDescriptionDependent;
2748 } else {
2749 mContextFlags &= ~eHasDescriptionDependent;
2750 }
2751
2752 // Add name/description dependent flags for dependent content once
2753 // a name/description provider is added to doc.
2754 Relation rel = RelationByType(RelationType::LABELLED_BY);
2755 LocalAccessible* relTarget = nullptr;
2756 while ((relTarget = rel.LocalNext())) {
2757 if (!relTarget->HasNameDependent()) {
2758 relTarget->ModifySubtreeContextFlags(eHasNameDependent, true);
2759 }
2760 }
2761
2762 rel = RelationByType(RelationType::DESCRIBED_BY);
2763 while ((relTarget = rel.LocalNext())) {
2764 if (!relTarget->HasDescriptionDependent()) {
2765 relTarget->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
2766 }
2767 }
2768
2769 mContextFlags |=
2770 static_cast<uint32_t>((mParent->IsAlert() || mParent->IsInsideAlert())) &
2771 eInsideAlert;
2772
2773 if (IsTableCell()) {
2774 CachedTableAccessible::Invalidate(this);
2775 }
2776}
2777
2778// LocalAccessible protected
2779void LocalAccessible::UnbindFromParent() {
2780 // We do this here to handle document shutdown and an Accessible being moved.
2781 // We do this for subtree removal in DocAccessible::UncacheChildrenInSubtree.
2782 if (IsTable() || IsTableCell()) {
2783 CachedTableAccessible::Invalidate(this);
2784 }
2785
2786 mParent = nullptr;
2787 mIndexInParent = -1;
2788 mIndexOfEmbeddedChild = -1;
2789
2790 delete mGroupInfo;
2791 mGroupInfo = nullptr;
2792 mContextFlags &= ~eHasNameDependent & ~eInsideAlert;
2793}
2794
2795////////////////////////////////////////////////////////////////////////////////
2796// LocalAccessible public methods
2797
2798RootAccessible* LocalAccessible::RootAccessible() const {
2799 nsCOMPtr<nsIDocShell> docShell = nsCoreUtils::GetDocShellFor(GetNode());
2800 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"
, 2800); MOZ_PretendNoReturn(); } } while (0)
;
2801 if (!docShell) {
2802 return nullptr;
2803 }
2804
2805 nsCOMPtr<nsIDocShellTreeItem> root;
2806 docShell->GetInProcessRootTreeItem(getter_AddRefs(root));
2807 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"
, 2807); MOZ_PretendNoReturn(); } } while (0)
;
2808 if (!root) {
2809 return nullptr;
2810 }
2811
2812 DocAccessible* docAcc = nsAccUtils::GetDocAccessibleFor(root);
2813 return docAcc ? docAcc->AsRoot() : nullptr;
2814}
2815
2816nsIFrame* LocalAccessible::GetFrame() const {
2817 return mContent ? mContent->GetPrimaryFrame() : nullptr;
2818}
2819
2820nsINode* LocalAccessible::GetNode() const { return mContent; }
2821
2822dom::Element* LocalAccessible::Elm() const {
2823 return dom::Element::FromNodeOrNull(mContent);
2824}
2825
2826void LocalAccessible::Language(nsAString& aLanguage) {
2827 aLanguage.Truncate();
2828
2829 if (!mDoc) return;
2830
2831 nsCoreUtils::GetLanguageFor(mContent, nullptr, aLanguage);
2832 if (aLanguage.IsEmpty()) { // Nothing found, so use document's language
2833 mDoc->DocumentNode()->GetHeaderData(nsGkAtoms::headerContentLanguage,
2834 aLanguage);
2835 }
2836}
2837
2838bool LocalAccessible::InsertChildAt(uint32_t aIndex, LocalAccessible* aChild) {
2839 if (!aChild) return false;
2840
2841 if (aIndex == mChildren.Length()) {
2842 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2843 // pretended earlier.
2844 mChildren.AppendElement(aChild);
2845 } else {
2846 // XXX(Bug 1631371) Check if this should use a fallible operation as it
2847 // pretended earlier.
2848 mChildren.InsertElementAt(aIndex, aChild);
2849
2850 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"
, 2850); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mStateFlags & eKidsMutating"
") (" "Illicit children change" ")"); do { *((volatile int*)
__null) = 2850; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2851
2852 for (uint32_t idx = aIndex + 1; idx < mChildren.Length(); idx++) {
2853 mChildren[idx]->mIndexInParent = idx;
2854 }
2855 }
2856
2857 if (aChild->IsText()) {
2858 mStateFlags |= eHasTextKids;
2859 }
2860
2861 aChild->BindToParent(this, aIndex);
2862 return true;
2863}
2864
2865bool LocalAccessible::RemoveChild(LocalAccessible* aChild) {
2866 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"
, 2866); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { *((volatile int*)__null
) = 2866; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
;
2867 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"
, 2867); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent"
") (" "No parent" ")"); do { *((volatile int*)__null) = 2867
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
2868 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"
, 2868); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "Wrong parent" ")"); do { *((volatile int*)__null) = 2868
; __attribute__((nomerge)) ::abort(); } while (false); } } while
(false)
;
2869 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"
, 2870); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { *((volatile int*)
__null) = 2870; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
2870 "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"
, 2870); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { *((volatile int*)
__null) = 2870; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2871 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"
, 2873); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { *((volatile int*)
__null) = 2873; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
2872 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"
, 2873); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { *((volatile int*)
__null) = 2873; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
2873 "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"
, 2873); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "(mStateFlags & eKidsMutating) || aChild->IsDefunct() || aChild->IsDoc() || IsApplication()"
") (" "Illicit children change" ")"); do { *((volatile int*)
__null) = 2873; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2874
2875 int32_t index = static_cast<uint32_t>(aChild->mIndexInParent);
2876 if (mChildren.SafeElementAt(index) != aChild) {
2877 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"
, 2877); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "A wrong child index" ")"); do { *
((volatile int*)__null) = 2877; __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
;
2878 index = mChildren.IndexOf(aChild);
2879 if (index == -1) {
2880 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"
, 2880); AnnotateMozCrashReason("MOZ_ASSERT" "(" "false" ") ("
"MOZ_ASSERT_UNREACHABLE: " "No child was found" ")"); do { *
((volatile int*)__null) = 2880; __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
;
2881 return false;
2882 }
2883 }
2884
2885 aChild->UnbindFromParent();
2886 mChildren.RemoveElementAt(index);
2887
2888 for (uint32_t idx = index; idx < mChildren.Length(); idx++) {
2889 mChildren[idx]->mIndexInParent = idx;
2890 }
2891
2892 return true;
2893}
2894
2895void LocalAccessible::RelocateChild(uint32_t aNewIndex,
2896 LocalAccessible* aChild) {
2897 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"
, 2897); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild"
") (" "No child was given" ")"); do { *((volatile int*)__null
) = 2897; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
;
2898 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"
, 2899); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { *
((volatile int*)__null) = 2899; __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
2899 "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"
, 2899); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent == this"
") (" "A child from different subtree was given" ")"); do { *
((volatile int*)__null) = 2899; __attribute__((nomerge)) ::abort
(); } while (false); } } while (false)
;
2900 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"
, 2901); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { *((volatile int*)
__null) = 2901; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
2901 "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"
, 2901); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mIndexInParent != -1"
") (" "Unbound child was given" ")"); do { *((volatile int*)
__null) = 2901; __attribute__((nomerge)) ::abort(); } while (
false); } } while (false)
;
2902 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"
, 2904); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { *((volatile int*)__null
) = 2904; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
2903 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"
, 2904); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { *((volatile int*)__null
) = 2904; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
2904 "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"
, 2904); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aChild->mParent->LocalChildAt(aChild->mIndexInParent) == aChild"
") (" "Wrong index in parent" ")"); do { *((volatile int*)__null
) = 2904; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
;
2905 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"
, 2907); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { *((volatile int*)__null
) = 2907; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
2906 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"
, 2907); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { *((volatile int*)__null
) = 2907; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
2907 "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"
, 2907); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "static_cast<uint32_t>(aChild->mIndexInParent) != aNewIndex"
") (" "No move, same index" ")"); do { *((volatile int*)__null
) = 2907; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
;
2908 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"
, 2909); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { *((volatile int
*)__null) = 2909; __attribute__((nomerge)) ::abort(); } while
(false); } } while (false)
2909 "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"
, 2909); AnnotateMozCrashReason("MOZ_DIAGNOSTIC_ASSERT" "(" "aNewIndex <= mChildren.Length()"
") (" "Wrong new index was given" ")"); do { *((volatile int
*)__null) = 2909; __attribute__((nomerge)) ::abort(); } while
(false); } } while (false)
;
2910
2911 RefPtr<AccHideEvent> hideEvent = new AccHideEvent(aChild, false);
2912 if (mDoc->Controller()->QueueMutationEvent(hideEvent)) {
2913 aChild->SetHideEventTarget(true);
2914 }
2915
2916 mEmbeddedObjCollector = nullptr;
2917 mChildren.RemoveElementAt(aChild->mIndexInParent);
2918
2919 uint32_t startIdx = aNewIndex, endIdx = aChild->mIndexInParent;
2920
2921 // If the child is moved after its current position.
2922 if (static_cast<uint32_t>(aChild->mIndexInParent) < aNewIndex) {
2923 startIdx = aChild->mIndexInParent;
2924 if (aNewIndex == mChildren.Length() + 1) {
2925 // The child is moved to the end.
2926 mChildren.AppendElement(aChild);
2927 endIdx = mChildren.Length() - 1;
2928 } else {
2929 mChildren.InsertElementAt(aNewIndex - 1, aChild);
2930 endIdx = aNewIndex;
2931 }
2932 } else {
2933 // The child is moved prior its current position.
2934 mChildren.InsertElementAt(aNewIndex, aChild);
2935 }
2936
2937 for (uint32_t idx = startIdx; idx <= endIdx; idx++) {
2938 mChildren[idx]->mIndexInParent = idx;
2939 mChildren[idx]->mIndexOfEmbeddedChild = -1;
2940 }
2941
2942 for (uint32_t idx = 0; idx < mChildren.Length(); idx++) {
2943 mChildren[idx]->mStateFlags |= eGroupInfoDirty;
2944 }
2945
2946 RefPtr<AccShowEvent> showEvent = new AccShowEvent(aChild);
2947 DebugOnly<bool> added = mDoc->Controller()->QueueMutationEvent(showEvent);
2948 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"
, 2948); AnnotateMozCrashReason("MOZ_ASSERT" "(" "added" ")")
; do { *((volatile int*)__null) = 2948; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2949 aChild->SetShowEventTarget(true);
2950}
2951
2952LocalAccessible* LocalAccessible::LocalChildAt(uint32_t aIndex) const {
2953 LocalAccessible* child = mChildren.SafeElementAt(aIndex, nullptr);
2954 if (!child) return nullptr;
2955
2956#ifdef DEBUG1
2957 LocalAccessible* realParent = child->mParent;
2958 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"
, 2959); MOZ_PretendNoReturn(); } } while (0)
2959 "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"
, 2959); MOZ_PretendNoReturn(); } } while (0)
;
2960#endif
2961
2962 return child;
2963}
2964
2965uint32_t LocalAccessible::ChildCount() const { return mChildren.Length(); }
2966
2967int32_t LocalAccessible::IndexInParent() const { return mIndexInParent; }
2968
2969uint32_t LocalAccessible::EmbeddedChildCount() {
2970 if (mStateFlags & eHasTextKids) {
2971 if (!mEmbeddedObjCollector) {
2972 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2973 }
2974 return mEmbeddedObjCollector->Count();
2975 }
2976
2977 return ChildCount();
2978}
2979
2980Accessible* LocalAccessible::EmbeddedChildAt(uint32_t aIndex) {
2981 if (mStateFlags & eHasTextKids) {
2982 if (!mEmbeddedObjCollector) {
2983 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2984 }
2985 return mEmbeddedObjCollector.get()
2986 ? mEmbeddedObjCollector->GetAccessibleAt(aIndex)
2987 : nullptr;
2988 }
2989
2990 return ChildAt(aIndex);
2991}
2992
2993int32_t LocalAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
2994 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"
, 2994); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aChild->IsLocal()"
")"); do { *((volatile int*)__null) = 2994; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
2995 if (mStateFlags & eHasTextKids) {
2996 if (!mEmbeddedObjCollector) {
2997 mEmbeddedObjCollector.reset(new EmbeddedObjCollector(this));
2998 }
2999 return mEmbeddedObjCollector.get()
3000 ? mEmbeddedObjCollector->GetIndexAt(aChild->AsLocal())
3001 : -1;
3002 }
3003
3004 return GetIndexOf(aChild->AsLocal());
3005}
3006
3007////////////////////////////////////////////////////////////////////////////////
3008// HyperLinkAccessible methods
3009
3010bool LocalAccessible::IsLink() const {
3011 // Every embedded accessible within hypertext accessible implements
3012 // hyperlink interface.
3013 return mParent && mParent->IsHyperText() && !IsText();
3014}
3015
3016////////////////////////////////////////////////////////////////////////////////
3017// SelectAccessible
3018
3019void LocalAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
3020 AccIterator iter(this, filters::GetSelected);
3021 LocalAccessible* selected = nullptr;
3022 while ((selected = iter.Next())) aItems->AppendElement(selected);
3023}
3024
3025uint32_t LocalAccessible::SelectedItemCount() {
3026 uint32_t count = 0;
3027 AccIterator iter(this, filters::GetSelected);
3028 LocalAccessible* selected = nullptr;
3029 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'
3030
3031 return count;
3032}
3033
3034Accessible* LocalAccessible::GetSelectedItem(uint32_t aIndex) {
3035 AccIterator iter(this, filters::GetSelected);
3036 LocalAccessible* selected = nullptr;
3037
3038 uint32_t index = 0;
3039 while ((selected = iter.Next()) && index < aIndex) index++;
3040
3041 return selected;
3042}
3043
3044bool LocalAccessible::IsItemSelected(uint32_t aIndex) {
3045 uint32_t index = 0;
3046 AccIterator iter(this, filters::GetSelectable);
3047 LocalAccessible* selected = nullptr;
3048 while ((selected = iter.Next()) && index < aIndex) index++;
3049
3050 return selected && selected->State() & states::SELECTED;
3051}
3052
3053bool LocalAccessible::AddItemToSelection(uint32_t aIndex) {
3054 uint32_t index = 0;
3055 AccIterator iter(this, filters::GetSelectable);
3056 LocalAccessible* selected = nullptr;
3057 while ((selected = iter.Next()) && index < aIndex) index++;
3058
3059 if (selected) selected->SetSelected(true);
3060
3061 return static_cast<bool>(selected);
3062}
3063
3064bool LocalAccessible::RemoveItemFromSelection(uint32_t aIndex) {
3065 uint32_t index = 0;
3066 AccIterator iter(this, filters::GetSelectable);
3067 LocalAccessible* selected = nullptr;
3068 while ((selected = iter.Next()) && index < aIndex) index++;
3069
3070 if (selected) selected->SetSelected(false);
3071
3072 return static_cast<bool>(selected);
3073}
3074
3075bool LocalAccessible::SelectAll() {
3076 bool success = false;
3077 LocalAccessible* selectable = nullptr;
3078
3079 AccIterator iter(this, filters::GetSelectable);
3080 while ((selectable = iter.Next())) {
3081 success = true;
3082 selectable->SetSelected(true);
3083 }
3084 return success;
3085}
3086
3087bool LocalAccessible::UnselectAll() {
3088 bool success = false;
3089 LocalAccessible* selected = nullptr;
3090
3091 AccIterator iter(this, filters::GetSelected);
3092 while ((selected = iter.Next())) {
3093 success = true;
3094 selected->SetSelected(false);
3095 }
3096 return success;
3097}
3098
3099////////////////////////////////////////////////////////////////////////////////
3100// Widgets
3101
3102bool LocalAccessible::IsWidget() const { return false; }
3103
3104bool LocalAccessible::IsActiveWidget() const {
3105 if (FocusMgr()->HasDOMFocus(mContent)) return true;
3106
3107 // If text entry of combobox widget has a focus then the combobox widget is
3108 // active.
3109 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3110 if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::combobox)) {
3111 uint32_t childCount = ChildCount();
3112 for (uint32_t idx = 0; idx < childCount; idx++) {
3113 LocalAccessible* child = mChildren.ElementAt(idx);
3114 if (child->Role() == roles::ENTRY) {
3115 return FocusMgr()->HasDOMFocus(child->GetContent());
3116 }
3117 }
3118 }
3119
3120 return false;
3121}
3122
3123bool LocalAccessible::AreItemsOperable() const {
3124 return HasOwnContent() && mContent->IsElement() &&
3125 mContent->AsElement()->HasAttr(nsGkAtoms::aria_activedescendant);
3126}
3127
3128LocalAccessible* LocalAccessible::CurrentItem() const {
3129 // Check for aria-activedescendant, which changes which element has focus.
3130 // For activedescendant, the ARIA spec does not require that the user agent
3131 // checks whether pointed node is actually a DOM descendant of the element
3132 // with the aria-activedescendant attribute.
3133 if (HasOwnContent() && mContent->IsElement()) {
3134 if (dom::Element* activeDescendantElm =
3135 nsCoreUtils::GetAriaActiveDescendantElement(
3136 mContent->AsElement())) {
3137 if (mContent->IsInclusiveDescendantOf(activeDescendantElm)) {
3138 // Don't want a cyclical descendant relationship. That would be bad.
3139 return nullptr;
3140 }
3141
3142 DocAccessible* document = Document();
3143 if (document) return document->GetAccessible(activeDescendantElm);
3144 }
3145 }
3146 return nullptr;
3147}
3148
3149void LocalAccessible::SetCurrentItem(const LocalAccessible* aItem) {}
3150
3151LocalAccessible* LocalAccessible::ContainerWidget() const {
3152 if (HasARIARole() && mContent->HasID()) {
3153 for (LocalAccessible* parent = LocalParent(); parent;
3154 parent = parent->LocalParent()) {
3155 nsIContent* parentContent = parent->GetContent();
3156 if (parentContent && parentContent->IsElement() &&
3157 nsCoreUtils::GetAriaActiveDescendantElement(
3158 parentContent->AsElement())) {
3159 return parent;
3160 }
3161
3162 // Don't cross DOM document boundaries.
3163 if (parent->IsDoc()) break;
3164 }
3165 }
3166 return nullptr;
3167}
3168
3169bool LocalAccessible::IsActiveDescendantId(LocalAccessible** aWidget) const {
3170 if (!HasOwnContent() || !mContent->HasID()) {
3171 return false;
3172 }
3173
3174 dom::DocumentOrShadowRoot* docOrShadowRoot =
3175 mContent->GetUncomposedDocOrConnectedShadowRoot();
3176 if (!docOrShadowRoot) {
3177 return false;
3178 }
3179
3180 nsAutoCString selector;
3181 selector.AppendPrintf(
3182 "[aria-activedescendant=\"%s\"]",
3183 NS_ConvertUTF16toUTF8(mContent->GetID()->GetUTF16String()).get());
3184 IgnoredErrorResult er;
3185
3186 dom::Element* widgetElm =
3187 docOrShadowRoot->AsNode().QuerySelector(selector, er);
3188
3189 if (!widgetElm || er.Failed()) {
3190 return false;
3191 }
3192
3193 if (widgetElm->IsInclusiveDescendantOf(mContent)) {
3194 // Don't want a cyclical descendant relationship. That would be bad.
3195 return false;
3196 }
3197
3198 LocalAccessible* widget = mDoc->GetAccessible(widgetElm);
3199
3200 if (aWidget) {
3201 *aWidget = widget;
3202 }
3203
3204 return !!widget;
3205}
3206
3207void LocalAccessible::Announce(const nsAString& aAnnouncement,
3208 uint16_t aPriority) {
3209 RefPtr<AccAnnouncementEvent> event =
3210 new AccAnnouncementEvent(this, aAnnouncement, aPriority);
3211 nsEventShell::FireEvent(event);
3212}
3213
3214////////////////////////////////////////////////////////////////////////////////
3215// LocalAccessible protected methods
3216
3217void LocalAccessible::LastRelease() {
3218 // First cleanup if needed...
3219 if (mDoc) {
3220 Shutdown();
3221 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"
, 3222); MOZ_PretendNoReturn(); } } while (0)
3222 "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"
, 3222); MOZ_PretendNoReturn(); } } while (0)
;
3223 }
3224 // ... then die.
3225 delete this;
3226}
3227
3228LocalAccessible* LocalAccessible::GetSiblingAtOffset(int32_t aOffset,
3229 nsresult* aError) const {
3230 if (!mParent || mIndexInParent == -1) {
3231 if (aError) *aError = NS_ERROR_UNEXPECTED;
3232
3233 return nullptr;
3234 }
3235
3236 if (aError &&
3237 mIndexInParent + aOffset >= static_cast<int32_t>(mParent->ChildCount())) {
3238 *aError = NS_OK; // fail peacefully
3239 return nullptr;
3240 }
3241
3242 LocalAccessible* child = mParent->LocalChildAt(mIndexInParent + aOffset);
3243 if (aError && !child) *aError = NS_ERROR_UNEXPECTED;
3244
3245 return child;
3246}
3247
3248void LocalAccessible::ModifySubtreeContextFlags(uint32_t aContextFlags,
3249 bool aAdd) {
3250 Pivot pivot(this);
3251 LocalAccInSameDocRule rule;
3252 for (Accessible* anchor = this; anchor; anchor = pivot.Next(anchor, rule)) {
3253 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"
, 3253); AnnotateMozCrashReason("MOZ_ASSERT" "(" "anchor->IsLocal()"
")"); do { *((volatile int*)__null) = 3253; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3254 LocalAccessible* acc = anchor->AsLocal();
3255 if (aAdd) {
3256 acc->mContextFlags |= aContextFlags;
3257 } else {
3258 acc->mContextFlags &= ~aContextFlags;
3259 }
3260 }
3261}
3262
3263double LocalAccessible::AttrNumericValue(nsAtom* aAttr) const {
3264 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3265 if (!roleMapEntry || roleMapEntry->valueRule == eNoValue) {
3266 return UnspecifiedNaN<double>();
3267 }
3268
3269 nsAutoString attrValue;
3270 if (!mContent->IsElement() ||
3271 !nsAccUtils::GetARIAAttr(mContent->AsElement(), aAttr, attrValue)) {
3272 return UnspecifiedNaN<double>();
3273 }
3274
3275 nsresult error = NS_OK;
3276 double value = attrValue.ToDouble(&error);
3277 return NS_FAILED(error)((bool)(__builtin_expect(!!(NS_FAILED_impl(error)), 0))) ? UnspecifiedNaN<double>() : value;
3278}
3279
3280uint32_t LocalAccessible::GetActionRule() const {
3281 if (!HasOwnContent() || (InteractiveState() & states::UNAVAILABLE)) {
3282 return eNoAction;
3283 }
3284
3285 // Return "click" action on elements that have an attached popup menu.
3286 if (mContent->IsXULElement()) {
3287 if (mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
3288 return eClickAction;
3289 }
3290 }
3291
3292 // Has registered 'click' event handler.
3293 bool isOnclick = nsCoreUtils::HasClickListener(mContent);
3294
3295 if (isOnclick) return eClickAction;
3296
3297 // Get an action based on ARIA role.
3298 const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
3299 if (roleMapEntry && roleMapEntry->actionRule != eNoAction) {
3300 return roleMapEntry->actionRule;
3301 }
3302
3303 // Get an action based on ARIA attribute.
3304 if (nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_expanded)) {
3305 return eExpandAction;
3306 }
3307
3308 return eNoAction;
3309}
3310
3311AccGroupInfo* LocalAccessible::GetGroupInfo() const {
3312 if (mGroupInfo && !(mStateFlags & eGroupInfoDirty)) {
3313 return mGroupInfo;
3314 }
3315
3316 return nullptr;
3317}
3318
3319AccGroupInfo* LocalAccessible::GetOrCreateGroupInfo() {
3320 if (mGroupInfo) {
3321 if (mStateFlags & eGroupInfoDirty) {
3322 mGroupInfo->Update();
3323 mStateFlags &= ~eGroupInfoDirty;
3324 }
3325
3326 return mGroupInfo;
3327 }
3328
3329 mGroupInfo = AccGroupInfo::CreateGroupInfo(this);
3330 mStateFlags &= ~eGroupInfoDirty;
3331 return mGroupInfo;
3332}
3333
3334void LocalAccessible::SendCache(uint64_t aCacheDomain,
3335 CacheUpdateType aUpdateType) {
3336 if (!IPCAccessibilityActive() || !Document()) {
3337 return;
3338 }
3339
3340 DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
3341 if (!ipcDoc) {
3342 // This means DocAccessible::DoInitialUpdate hasn't been called yet, which
3343 // means the a11y tree hasn't been built yet. Therefore, this should only
3344 // be possible if this is a DocAccessible.
3345 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"
, 3345); AnnotateMozCrashReason("MOZ_ASSERT" "(" "IsDoc()" ") ("
"Called on a non-DocAccessible but IPCDoc is null" ")"); do {
*((volatile int*)__null) = 3345; __attribute__((nomerge)) ::
abort(); } while (false); } } while (false)
;
3346 return;
3347 }
3348
3349 RefPtr<AccAttributes> fields =
3350 BundleFieldsForCache(aCacheDomain, aUpdateType);
3351 if (!fields->Count()) {
3352 return;
3353 }
3354 nsTArray<CacheData> data;
3355 data.AppendElement(CacheData(ID(), fields));
3356 ipcDoc->SendCache(aUpdateType, data);
3357
3358 if (profiler_thread_is_being_profiled_for_markers()) {
3359 nsAutoCString updateTypeStr;
3360 if (aUpdateType == CacheUpdateType::Initial) {
3361 updateTypeStr = "Initial";
3362 } else if (aUpdateType == CacheUpdateType::Update) {
3363 updateTypeStr = "Update";
3364 } else {
3365 updateTypeStr = "Other";
3366 }
3367 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)
;
3368 }
3369}
3370
3371already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
3372 uint64_t aCacheDomain, CacheUpdateType aUpdateType) {
3373 RefPtr<AccAttributes> fields = new AccAttributes();
3374
3375 // Caching name for text leaf Accessibles is redundant, since their name is
3376 // always their text. Text gets handled below.
3377 if (aCacheDomain & CacheDomain::NameAndDescription && !IsText()) {
3378 nsString name;
3379 int32_t nameFlag = Name(name);
3380 if (nameFlag != eNameOK) {
3381 fields->SetAttribute(CacheKey::NameValueFlag, nameFlag);
3382 } else if (aUpdateType == CacheUpdateType::Update) {
3383 fields->SetAttribute(CacheKey::NameValueFlag, DeleteEntry());
3384 }
3385
3386 if (IsTextField()) {
3387 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"
, 3387); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ")"
); do { *((volatile int*)__null) = 3387; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3388 nsString placeholder;
3389 // Only cache the placeholder separately if it isn't used as the name.
3390 if (Elm()->GetAttr(nsGkAtoms::placeholder, placeholder) &&
3391 name != placeholder) {
3392 fields->SetAttribute(CacheKey::HTMLPlaceholder, std::move(placeholder));
3393 } else if (aUpdateType == CacheUpdateType::Update) {
3394 fields->SetAttribute(CacheKey::HTMLPlaceholder, DeleteEntry());
3395 }
3396 }
3397
3398 if (!name.IsEmpty()) {
3399 fields->SetAttribute(CacheKey::Name, std::move(name));
3400 } else if (aUpdateType == CacheUpdateType::Update) {
3401 fields->SetAttribute(CacheKey::Name, DeleteEntry());
3402 }
3403
3404 nsString description;
3405 Description(description);
3406 if (!description.IsEmpty()) {
3407 fields->SetAttribute(CacheKey::Description, std::move(description));
3408 } else if (aUpdateType == CacheUpdateType::Update) {
3409 fields->SetAttribute(CacheKey::Description, DeleteEntry());
3410 }
3411 }
3412
3413 if (aCacheDomain & CacheDomain::Value) {
3414 // We cache the text value in 3 cases:
3415 // 1. Accessible is an HTML input type that holds a number.
3416 // 2. Accessible has a numeric value and an aria-valuetext.
3417 // 3. Accessible is an HTML input type that holds text.
3418 // 4. Accessible is a link, in which case value is the target URL.
3419 // ... for all other cases we divine the value remotely.
3420 bool cacheValueText = false;
3421 if (HasNumericValue()) {
3422 fields->SetAttribute(CacheKey::NumericValue, CurValue());
3423 fields->SetAttribute(CacheKey::MaxValue, MaxValue());
3424 fields->SetAttribute(CacheKey::MinValue, MinValue());
3425 fields->SetAttribute(CacheKey::Step, Step());
3426 cacheValueText = NativeHasNumericValue() ||
3427 (mContent->IsElement() &&
3428 nsAccUtils::HasARIAAttr(mContent->AsElement(),
3429 nsGkAtoms::aria_valuetext));
3430 } else {
3431 cacheValueText = IsTextField() || IsHTMLLink();
3432 }
3433
3434 if (cacheValueText) {
3435 nsString value;
3436 Value(value);
3437 if (!value.IsEmpty()) {
3438 fields->SetAttribute(CacheKey::TextValue, std::move(value));
3439 } else if (aUpdateType == CacheUpdateType::Update) {
3440 fields->SetAttribute(CacheKey::TextValue, DeleteEntry());
3441 }
3442 }
3443
3444 if (IsImage()) {
3445 // Cache the src of images. This is used by some clients to help remediate
3446 // inaccessible images.
3447 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"
, 3447); AnnotateMozCrashReason("MOZ_ASSERT" "(" "mContent" ") ("
"Image must have mContent" ")"); do { *((volatile int*)__null
) = 3447; __attribute__((nomerge)) ::abort(); } while (false)
; } } while (false)
;
3448 nsString src;
3449 mContent->AsElement()->GetAttr(nsGkAtoms::src, src);
3450 if (!src.IsEmpty()) {
3451 fields->SetAttribute(CacheKey::SrcURL, std::move(src));
3452 } else if (aUpdateType == CacheUpdateType::Update) {
3453 fields->SetAttribute(CacheKey::SrcURL, DeleteEntry());
3454 }
3455 }
3456
3457 if (TagName() == nsGkAtoms::meter) {
3458 // We should only cache value region for HTML meter elements. A meter
3459 // should always have a value region, so this attribute should never
3460 // be empty (i.e. there is no DeleteEntry() clause here).
3461 HTMLMeterAccessible* meter = static_cast<HTMLMeterAccessible*>(this);
3462 fields->SetAttribute(CacheKey::ValueRegion, meter->ValueRegion());
3463 }
3464 }
3465
3466 if (aCacheDomain & CacheDomain::Viewport && IsDoc()) {
3467 // Construct the viewport cache for this document. This cache domain will
3468 // only be requested after we finish painting.
3469 DocAccessible* doc = AsDoc();
3470 PresShell* presShell = doc->PresShellPtr();
3471
3472 if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
3473 nsTArray<nsIFrame*> frames;
3474 ScrollContainerFrame* sf = presShell->GetRootScrollContainerFrame();
3475 nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
3476
3477 nsLayoutUtils::GetFramesForArea(
3478 RelativeTo{rootFrame}, scrollPort, frames,
3479 {{// We don't add the ::OnlyVisible option here, because
3480 // it means we always consider frames with pointer-events: none.
3481 // See usage of HitTestIsForVisibility in nsDisplayList::HitTest.
3482 // This flag ensures the display lists are built, even if
3483 // the page hasn't finished loading.
3484 nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
3485 // Each doc should have its own viewport cache, so we can
3486 // ignore cross-doc content as an optimization.
3487 nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc}});
3488
3489 nsTHashSet<LocalAccessible*> inViewAccs;
3490 nsTArray<uint64_t> viewportCache(frames.Length());
3491 // Layout considers table rows fully occluded by their containing cells.
3492 // This means they don't have their own display list items, and they won't
3493 // show up in the list returned from GetFramesForArea. To prevent table
3494 // rows from appearing offscreen, we manually add any rows for which we
3495 // have on-screen cells.
3496 LocalAccessible* prevParentRow = nullptr;
3497 for (nsIFrame* frame : frames) {
3498 nsIContent* content = frame->GetContent();
3499 if (!content) {
3500 continue;
3501 }
3502
3503 LocalAccessible* acc = doc->GetAccessible(content);
3504 // The document should always be present at the end of the list, so
3505 // including it is unnecessary and wasteful. We skip the document here
3506 // and handle it as a fallback when hit testing.
3507 if (!acc || acc == mDoc) {
3508 continue;
3509 }
3510
3511 if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
3512 acc = acc->LocalParent();
3513 }
3514 if (acc->IsTableCell()) {
3515 LocalAccessible* parent = acc->LocalParent();
3516 if (parent && parent->IsTableRow() && parent != prevParentRow) {
3517 // If we've entered a new row since the last cell we saw, add the
3518 // previous parent row to our viewport cache here to maintain
3519 // hittesting order. Keep track of the current parent row.
3520 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3521 viewportCache.AppendElement(prevParentRow->ID());
3522 }
3523 prevParentRow = parent;
3524 }
3525 } else if (acc->IsTable()) {
3526 // If we've encountered a table, we know we've already
3527 // handled all of this table's content (because we're traversing
3528 // in hittesting order). Add our table's final row to the viewport
3529 // cache before adding the table itself. Reset our marker for the next
3530 // table.
3531 if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
3532 viewportCache.AppendElement(prevParentRow->ID());
3533 }
3534 prevParentRow = nullptr;
3535 } else if (acc->IsImageMap()) {
3536 // Layout doesn't walk image maps, so we do that
3537 // manually here. We do this before adding the map itself
3538 // so the children come earlier in the hittesting order.
3539 for (uint32_t i = 0; i < acc->ChildCount(); i++) {
3540 LocalAccessible* child = acc->LocalChildAt(i);
3541 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"
, 3541); AnnotateMozCrashReason("MOZ_ASSERT" "(" "child" ")")
; do { *((volatile int*)__null) = 3541; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3542 if (inViewAccs.EnsureInserted(child)) {
3543 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"
, 3543); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!child->IsDoc()"
")"); do { *((volatile int*)__null) = 3543; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3544 viewportCache.AppendElement(child->ID());
3545 }
3546 }
3547 } else if (acc->IsHTMLCombobox()) {
3548 // Layout doesn't consider combobox lists (or their
3549 // currently selected items) to be onscreen, but we do.
3550 // Add those things manually here.
3551 HTMLComboboxAccessible* combobox =
3552 static_cast<HTMLComboboxAccessible*>(acc);
3553 HTMLComboboxListAccessible* list = combobox->List();
3554 LocalAccessible* currItem = combobox->SelectedOption();
3555 // Preserve hittesting order by adding the item, then
3556 // the list, and finally the combobox itself.
3557 if (currItem && inViewAccs.EnsureInserted(currItem)) {
3558 viewportCache.AppendElement(currItem->ID());
3559 }
3560 if (list && inViewAccs.EnsureInserted(list)) {
3561 viewportCache.AppendElement(list->ID());
3562 }
3563 }
3564
3565 if (inViewAccs.EnsureInserted(acc)) {
3566 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"
, 3566); AnnotateMozCrashReason("MOZ_ASSERT" "(" "!acc->IsDoc()"
")"); do { *((volatile int*)__null) = 3566; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3567 viewportCache.AppendElement(acc->ID());
3568 }
3569 }
3570
3571 if (viewportCache.Length()) {
3572 fields->SetAttribute(CacheKey::Viewport, std::move(viewportCache));
3573 }
3574 }
3575 }
3576
3577 bool boundsChanged = false;
3578 nsIFrame* frame = GetFrame();
3579 if (aCacheDomain & CacheDomain::Bounds) {
3580 nsRect newBoundsRect = ParentRelativeBounds();
3581
3582 // 1. Layout might notify us of a possible bounds change when the bounds
3583 // haven't really changed. Therefore, we cache the last bounds we sent
3584 // and don't send an update if they haven't changed.
3585 // 2. For an initial cache push, we ignore 1) and always send the bounds.
3586 // This handles the case where this LocalAccessible was moved (but not
3587 // re-created). In that case, we will have cached bounds, but we currently
3588 // do an initial cache push.
3589 MOZ_ASSERT(aUpdateType == CacheUpdateType::Initial || mBounds.isSome(),do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aUpdateType == CacheUpdateType::Initial || mBounds.isSome
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aUpdateType == CacheUpdateType::Initial || mBounds.isSome
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aUpdateType == CacheUpdateType::Initial || mBounds.isSome()"
" (" "Incremental cache push but mBounds is not set!" ")", "/var/lib/jenkins/workspace/firefox-scan-build/accessible/generic/LocalAccessible.cpp"
, 3590); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aUpdateType == CacheUpdateType::Initial || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { *((volatile int*)__null) = 3590; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
3590 "Incremental cache push but mBounds is not set!")do { static_assert( mozilla::detail::AssertionConditionType<
decltype(aUpdateType == CacheUpdateType::Initial || mBounds.isSome
())>::isValid, "invalid assertion condition"); if ((__builtin_expect
(!!(!(!!(aUpdateType == CacheUpdateType::Initial || mBounds.isSome
()))), 0))) { do { } while (false); MOZ_ReportAssertionFailure
("aUpdateType == CacheUpdateType::Initial || mBounds.isSome()"
" (" "Incremental cache push but mBounds is not set!" ")", "/var/lib/jenkins/workspace/firefox-scan-build/accessible/generic/LocalAccessible.cpp"
, 3590); AnnotateMozCrashReason("MOZ_ASSERT" "(" "aUpdateType == CacheUpdateType::Initial || mBounds.isSome()"
") (" "Incremental cache push but mBounds is not set!" ")");
do { *((volatile int*)__null) = 3590; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3591
3592 if (OuterDocAccessible* doc = AsOuterDoc()) {
3593 if (nsIFrame* docFrame = doc->GetFrame()) {
3594 const nsMargin& newOffset = docFrame->GetUsedBorderAndPadding();
3595 Maybe<nsMargin> currOffset = doc->GetCrossDocOffset();
3596 if (!currOffset || *currOffset != newOffset) {
3597 // OOP iframe docs can't compute their position within their
3598 // cross-proc parent, so we have to manually cache that offset
3599 // on the parent (outer doc) itself. For simplicity and consistency,
3600 // we do this here for both OOP and in-process iframes. For in-process
3601 // iframes, this also avoids the need to push a cache update for the
3602 // embedded document when the iframe changes its padding, gets
3603 // re-created, etc. Similar to bounds, we maintain a local cache and a
3604 // remote cache to avoid sending redundant updates.
3605 doc->SetCrossDocOffset(newOffset);
3606 nsTArray<int32_t> offsetArray(2);
3607 offsetArray.AppendElement(newOffset.Side(eSideLeft)); // X offset
3608 offsetArray.AppendElement(newOffset.Side(eSideTop)); // Y offset
3609 fields->SetAttribute(CacheKey::CrossDocOffset,
3610 std::move(offsetArray));
3611 }
3612 }
3613 }
3614
3615 boundsChanged = aUpdateType == CacheUpdateType::Initial ||
3616 !newBoundsRect.IsEqualEdges(mBounds.value());
3617 if (boundsChanged) {
3618 mBounds = Some(newBoundsRect);
3619
3620 nsTArray<int32_t> boundsArray(4);
3621
3622 boundsArray.AppendElement(newBoundsRect.x);
3623 boundsArray.AppendElement(newBoundsRect.y);
3624 boundsArray.AppendElement(newBoundsRect.width);
3625 boundsArray.AppendElement(newBoundsRect.height);
3626
3627 fields->SetAttribute(CacheKey::ParentRelativeBounds,
3628 std::move(boundsArray));
3629 }
3630
3631 if (frame && frame->ScrollableOverflowRect().IsEmpty()) {
3632 fields->SetAttribute(CacheKey::IsClipped, true);
3633 } else if (aUpdateType != CacheUpdateType::Initial) {
3634 fields->SetAttribute(CacheKey::IsClipped, DeleteEntry());
3635 }
3636 }
3637
3638 if (aCacheDomain & CacheDomain::Text) {
3639 if (!HasChildren()) {
3640 // We only cache text and line offsets on leaf Accessibles.
3641 // Only text Accessibles can have actual text.
3642 if (IsText()) {
3643 nsString text;
3644 AppendTextTo(text);
3645 fields->SetAttribute(CacheKey::Text, std::move(text));
3646 TextLeafPoint point(this, 0);
3647 RefPtr<AccAttributes> attrs = point.GetTextAttributesLocalAcc(
3648 /* aIncludeDefaults */ false);
3649 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3650 }
3651 }
3652 if (HyperTextAccessible* ht = AsHyperText()) {
3653 RefPtr<AccAttributes> attrs = ht->DefaultTextAttributes();
3654 fields->SetAttribute(CacheKey::TextAttributes, std::move(attrs));
3655 } else if (!IsText()) {
3656 // Language is normally cached in text attributes, but Accessibles that
3657 // aren't HyperText or Text (e.g. <img>, <input type="radio">) don't have
3658 // text attributes. The Text domain isn't a great fit, but the kinds of
3659 // clients (e.g. screen readers) that care about language are likely to
3660 // care about text as well.
3661 nsString language;
3662 Language(language);
3663 if (!language.IsEmpty()) {
3664 fields->SetAttribute(CacheKey::Language, std::move(language));
3665 } else if (aUpdateType == CacheUpdateType::Update) {
3666 fields->SetAttribute(CacheKey::Language, DeleteEntry());
3667 }
3668 }
3669 }
3670
3671 // If text changes, we must also update spelling errors.
3672 if (aCacheDomain & (CacheDomain::Spelling | CacheDomain::Text) &&
3673 IsTextLeaf()) {
3674 auto spellingErrors = TextLeafPoint::GetSpellingErrorOffsets(this);
3675 if (!spellingErrors.IsEmpty()) {
3676 fields->SetAttribute(CacheKey::SpellingErrors, std::move(spellingErrors));
3677 } else if (aUpdateType == CacheUpdateType::Update) {
3678 fields->SetAttribute(CacheKey::SpellingErrors, DeleteEntry());
3679 }
3680 }
3681
3682 if (aCacheDomain & (CacheDomain::Text | CacheDomain::Bounds) &&
3683 !HasChildren()) {
3684 // We cache line start offsets for both text and non-text leaf Accessibles
3685 // because non-text leaf Accessibles can still start a line.
3686 TextLeafPoint lineStart =
3687 TextLeafPoint(this, 0).FindNextLineStartSameLocalAcc(
3688 /* aIncludeOrigin */ true);
3689 int32_t lineStartOffset = lineStart ? lineStart.mOffset : -1;
3690 // We push line starts and text bounds in two cases:
3691 // 1. Text or bounds changed, which means it's very likely that line starts
3692 // and text bounds changed too.
3693 // 2. CacheDomain::Bounds was requested (indicating that the frame was
3694 // reflowed) but the bounds didn't actually change. This can happen when
3695 // the spanned text is non-rectangular. For example, an Accessible might
3696 // cover two characters on one line and a single character on another line.
3697 // An insertion in a previous text node might cause it to shift such that it
3698 // now covers a single character on the first line and two characters on the
3699 // second line. Its bounding rect will be the same both before and after the
3700 // insertion. In this case, we use the first line start to determine whether
3701 // there was a change. This should be safe because the text didn't change in
3702 // this Accessible, so if the first line start doesn't shift, none of them
3703 // should shift.
3704 if (aCacheDomain & CacheDomain::Text || boundsChanged ||
3705 mFirstLineStart != lineStartOffset) {
3706 mFirstLineStart = lineStartOffset;
3707 nsTArray<int32_t> lineStarts;
3708 for (; lineStart;
3709 lineStart = lineStart.FindNextLineStartSameLocalAcc(false)) {
3710 lineStarts.AppendElement(lineStart.mOffset);
3711 }
3712 if (!lineStarts.IsEmpty()) {
3713 fields->SetAttribute(CacheKey::TextLineStarts, std::move(lineStarts));
3714 } else if (aUpdateType == CacheUpdateType::Update) {
3715 fields->SetAttribute(CacheKey::TextLineStarts, DeleteEntry());
3716 }
3717
3718 if (frame && frame->IsTextFrame()) {
3719 if (nsTextFrame* currTextFrame = do_QueryFrame(frame)) {
3720 nsTArray<int32_t> charData(nsAccUtils::TextLength(this) *
3721 kNumbersInRect);
3722 // Continuation offsets are calculated relative to the primary frame.
3723 // However, the acc's bounds are calculated using
3724 // GetAllInFlowRectsUnion. For wrapped text which starts part way
3725 // through a line, this might mean the top left of the acc is
3726 // different to the top left of the primary frame. This also happens
3727 // when the primary frame is empty (e.g. a blank line at the start of
3728 // pre-formatted text), since the union rect will exclude the origin
3729 // in that case. Calculate the offset from the acc's rect to the
3730 // primary frame's rect.
3731 nsRect accOffset =
3732 nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame);
3733 while (currTextFrame) {
3734 nsPoint contOffset = currTextFrame->GetOffsetTo(frame);
3735 contOffset -= accOffset.TopLeft();
3736 int32_t length = currTextFrame->GetContentLength();
3737 nsTArray<nsRect> charBounds(length);
3738 currTextFrame->GetCharacterRectsInRange(
3739 currTextFrame->GetContentOffset(), length, charBounds);
3740 for (nsRect& charRect : charBounds) {
3741 if (charRect.width == 0 &&
3742 !currTextFrame->StyleText()->WhiteSpaceIsSignificant()) {
3743 // GetCharacterRectsInRange gives us one rect per content
3744 // offset. However, TextLeafAccessibles use rendered offsets;
3745 // e.g. they might exclude some content white space. If we get
3746 // a 0 width rect and it's white space, skip this rect, since
3747 // this character isn't in the rendered text. We do have
3748 // a way to convert between content and rendered offsets, but
3749 // doing this for every character is expensive.
3750 const char16_t contentChar = mContent->GetText()->CharAt(
3751 charData.Length() / kNumbersInRect);
3752 if (contentChar == u' ' || contentChar == u'\t' ||
3753 contentChar == u'\n') {
3754 continue;
3755 }
3756 }
3757 // We expect each char rect to be relative to the text leaf
3758 // acc this text lives in. Unfortunately, GetCharacterRectsInRange
3759 // returns rects relative to their continuation. Add the
3760 // continuation's relative position here to make our final
3761 // rect relative to the text leaf acc.
3762 charRect.MoveBy(contOffset);
3763 charData.AppendElement(charRect.x);
3764 charData.AppendElement(charRect.y);
3765 charData.AppendElement(charRect.width);
3766 charData.AppendElement(charRect.height);
3767 }
3768 currTextFrame = currTextFrame->GetNextContinuation();
3769 }
3770 if (charData.Length()) {
3771 fields->SetAttribute(CacheKey::TextBounds, std::move(charData));
3772 }
3773 }
3774 }
3775 }
3776 }
3777
3778 if (aCacheDomain & CacheDomain::TransformMatrix) {
3779 bool transformed = false;
3780 if (frame && frame->IsTransformed()) {
3781 // This matrix is only valid when applied to CSSPixel points/rects
3782 // in the coordinate space of `frame`.
3783 gfx::Matrix4x4 mtx = nsDisplayTransform::GetResultingTransformMatrix(
3784 frame, nsPoint(0, 0), AppUnitsPerCSSPixel(),
3785 nsDisplayTransform::INCLUDE_PERSPECTIVE |
3786 nsDisplayTransform::OFFSET_BY_ORIGIN);
3787 // We might get back the identity matrix. This can happen if there is no
3788 // actual transform. For example, if an element has
3789 // will-change: transform, nsIFrame::IsTransformed will return true, but
3790 // this doesn't necessarily mean there is a transform right now.
3791 // Applying the identity matrix is effectively a no-op, so there's no
3792 // point caching it.
3793 transformed = !mtx.IsIdentity();
3794 if (transformed) {
3795 UniquePtr<gfx::Matrix4x4> ptr = MakeUnique<gfx::Matrix4x4>(mtx);
3796 fields->SetAttribute(CacheKey::TransformMatrix, std::move(ptr));
3797 }
3798 }
3799 if (!transformed && aUpdateType == CacheUpdateType::Update) {
3800 // Otherwise, if we're bundling a transform update but this
3801 // frame isn't transformed (or doesn't exist), we need
3802 // to send a DeleteEntry() to remove any
3803 // transform that was previously cached for this frame.
3804 fields->SetAttribute(CacheKey::TransformMatrix, DeleteEntry());
3805 }
3806 }
3807
3808 if (aCacheDomain & CacheDomain::ScrollPosition && frame) {
3809 const auto [scrollPosition, scrollRange] = mDoc->ComputeScrollData(this);
3810 if (scrollRange.width || scrollRange.height) {
3811 // If the scroll range is 0 by 0, this acc is not scrollable. We
3812 // can't simply check scrollPosition != 0, since it's valid for scrollable
3813 // frames to have a (0, 0) position. We also can't check IsEmpty or
3814 // ZeroArea because frames with only one scrollable dimension will return
3815 // a height/width of zero for the non-scrollable dimension, yielding zero
3816 // area even if the width/height for the scrollable dimension is nonzero.
3817 // We also cache (0, 0) for accs with overflow:auto or overflow:scroll,
3818 // even if the content is not currently large enough to be scrollable
3819 // right now -- these accs have a non-zero scroll range.
3820 nsTArray<int32_t> positionArr(2);
3821 positionArr.AppendElement(scrollPosition.x);
3822 positionArr.AppendElement(scrollPosition.y);
3823 fields->SetAttribute(CacheKey::ScrollPosition, std::move(positionArr));
3824 } else if (aUpdateType == CacheUpdateType::Update) {
3825 fields->SetAttribute(CacheKey::ScrollPosition, DeleteEntry());
3826 }
3827 }
3828
3829 if (aCacheDomain & CacheDomain::DOMNodeIDAndClass && mContent) {
3830 nsAtom* id = mContent->GetID();
3831 if (id) {
3832 fields->SetAttribute(CacheKey::DOMNodeID, id);
3833 } else if (aUpdateType == CacheUpdateType::Update) {
3834 fields->SetAttribute(CacheKey::DOMNodeID, DeleteEntry());
3835 }
3836 nsString className;
3837 DOMNodeClass(className);
3838 if (!className.IsEmpty()) {
3839 fields->SetAttribute(CacheKey::DOMNodeClass, std::move(className));
3840 } else if (aUpdateType == CacheUpdateType::Update) {
3841 fields->SetAttribute(CacheKey::DOMNodeClass, DeleteEntry());
3842 }
3843 }
3844
3845 // State is only included in the initial push. Thereafter, cached state is
3846 // updated via events.
3847 if (aCacheDomain & CacheDomain::State) {
3848 if (aUpdateType == CacheUpdateType::Initial) {
3849 // Most states are updated using state change events, so we only send
3850 // these for the initial cache push.
3851 uint64_t state = State();
3852 // Exclude states which must be calculated by RemoteAccessible.
3853 state &= ~kRemoteCalculatedStates;
3854 fields->SetAttribute(CacheKey::State, state);
3855 }
3856 // If aria-selected isn't specified, there may be no SELECTED state.
3857 // However, aria-selected can be implicit in some cases when an item is
3858 // focused. We don't want to do this if aria-selected is explicitly
3859 // set to "false", so we need to differentiate between false and unset.
3860 if (auto ariaSelected = ARIASelected()) {
3861 fields->SetAttribute(CacheKey::ARIASelected, *ariaSelected);
3862 } else if (aUpdateType == CacheUpdateType::Update) {
3863 fields->SetAttribute(CacheKey::ARIASelected, DeleteEntry()); // Unset.
3864 }
3865 }
3866
3867 if (aCacheDomain & CacheDomain::GroupInfo && mContent) {
3868 for (nsAtom* attr : {nsGkAtoms::aria_level, nsGkAtoms::aria_setsize,
3869 nsGkAtoms::aria_posinset}) {
3870 int32_t value = 0;
3871 if (nsCoreUtils::GetUIntAttr(mContent, attr, &value)) {
3872 fields->SetAttribute(attr, value);
3873 } else if (aUpdateType == CacheUpdateType::Update) {
3874 fields->SetAttribute(attr, DeleteEntry());
3875 }
3876 }
3877 }
3878
3879 if (aCacheDomain & CacheDomain::Actions) {
3880 if (HasPrimaryAction()) {
3881 // Here we cache the primary action.
3882 nsAutoString actionName;
3883 ActionNameAt(0, actionName);
3884 RefPtr<nsAtom> actionAtom = NS_Atomize(actionName);
3885 fields->SetAttribute(CacheKey::PrimaryAction, actionAtom);
3886 } else if (aUpdateType == CacheUpdateType::Update) {
3887 fields->SetAttribute(CacheKey::PrimaryAction, DeleteEntry());
3888 }
3889
3890 if (ImageAccessible* imgAcc = AsImage()) {
3891 // Here we cache the showlongdesc action.
3892 if (imgAcc->HasLongDesc()) {
3893 fields->SetAttribute(CacheKey::HasLongdesc, true);
3894 } else if (aUpdateType == CacheUpdateType::Update) {
3895 fields->SetAttribute(CacheKey::HasLongdesc, DeleteEntry());
3896 }
3897 }
3898
3899 KeyBinding accessKey = AccessKey();
3900 if (!accessKey.IsEmpty()) {
3901 fields->SetAttribute(CacheKey::AccessKey, accessKey.Serialize());
3902 } else if (aUpdateType == CacheUpdateType::Update) {
3903 fields->SetAttribute(CacheKey::AccessKey, DeleteEntry());
3904 }
3905 }
3906
3907 if (aCacheDomain & CacheDomain::Style) {
3908 if (RefPtr<nsAtom> display = DisplayStyle()) {
3909 fields->SetAttribute(CacheKey::CSSDisplay, display);
3910 }
3911
3912 float opacity = Opacity();
3913 if (opacity != 1.0f) {
3914 fields->SetAttribute(CacheKey::Opacity, opacity);
3915 } else if (aUpdateType == CacheUpdateType::Update) {
3916 fields->SetAttribute(CacheKey::Opacity, DeleteEntry());
3917 }
3918
3919 if (frame &&
3920 frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
3921 nsLayoutUtils::IsReallyFixedPos(frame)) {
3922 fields->SetAttribute(CacheKey::CssPosition, nsGkAtoms::fixed);
3923 } else if (aUpdateType != CacheUpdateType::Initial) {
3924 fields->SetAttribute(CacheKey::CssPosition, DeleteEntry());
3925 }
3926
3927 if (frame) {
3928 nsAutoCString overflow;
3929 frame->Style()->GetComputedPropertyValue(eCSSProperty_overflow, overflow);
3930 RefPtr<nsAtom> overflowAtom = NS_Atomize(overflow);
3931 if (overflowAtom == nsGkAtoms::hidden) {
3932 fields->SetAttribute(CacheKey::CSSOverflow, nsGkAtoms::hidden);
3933 } else if (aUpdateType != CacheUpdateType::Initial) {
3934 fields->SetAttribute(CacheKey::CSSOverflow, DeleteEntry());
3935 }
3936 }
3937 }
3938
3939 if (aCacheDomain & CacheDomain::Table) {
3940 if (auto* table = HTMLTableAccessible::GetFrom(this)) {
3941 if (table->IsProbablyLayoutTable()) {
3942 fields->SetAttribute(CacheKey::TableLayoutGuess, true);
3943 } else if (aUpdateType == CacheUpdateType::Update) {
3944 fields->SetAttribute(CacheKey::TableLayoutGuess, DeleteEntry());
3945 }
3946 } else if (auto* cell = HTMLTableCellAccessible::GetFrom(this)) {
3947 // For HTML table cells, we must use the HTMLTableCellAccessible
3948 // GetRow/ColExtent methods rather than using the DOM attributes directly.
3949 // This is because of things like rowspan="0" which depend on knowing
3950 // about thead, tbody, etc., which is info we don't have in the a11y tree.
3951 int32_t value = static_cast<int32_t>(cell->RowExtent());
3952 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"
, 3952); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { *((volatile int*)__null) = 3952; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3953 if (value > 1) {
3954 fields->SetAttribute(CacheKey::RowSpan, value);
3955 } else if (aUpdateType == CacheUpdateType::Update) {
3956 fields->SetAttribute(CacheKey::RowSpan, DeleteEntry());
3957 }
3958 value = static_cast<int32_t>(cell->ColExtent());
3959 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"
, 3959); AnnotateMozCrashReason("MOZ_ASSERT" "(" "value > 0"
")"); do { *((volatile int*)__null) = 3959; __attribute__((nomerge
)) ::abort(); } while (false); } } while (false)
;
3960 if (value > 1) {
3961 fields->SetAttribute(CacheKey::ColSpan, value);
3962 } else if (aUpdateType == CacheUpdateType::Update) {
3963 fields->SetAttribute(CacheKey::ColSpan, DeleteEntry());
3964 }
3965 if (mContent->AsElement()->HasAttr(nsGkAtoms::headers)) {
3966 nsTArray<uint64_t> headers;
3967 IDRefsIterator iter(mDoc, mContent, nsGkAtoms::headers);
3968 while (LocalAccessible* cell = iter.Next()) {
3969 if (cell->IsTableCell()) {
3970 headers.AppendElement(cell->ID());
3971 }
3972 }
3973 fields->SetAttribute(CacheKey::CellHeaders, std::move(headers));
3974 } else if (aUpdateType == CacheUpdateType::Update) {
3975 fields->SetAttribute(CacheKey::CellHeaders, DeleteEntry());
3976 }
3977 }
3978 }
3979
3980 if (aCacheDomain & CacheDomain::ARIA && mContent && mContent->IsElement()) {
3981 // We use a nested AccAttributes to make cache updates simpler. Rather than
3982 // managing individual removals, we just replace or remove the entire set of
3983 // ARIA attributes.
3984 RefPtr<AccAttributes> ariaAttrs;
3985 aria::AttrIterator attrIt(mContent);
3986 while (attrIt.Next()) {
3987 if (!ariaAttrs) {
3988 ariaAttrs = new AccAttributes();
3989 }
3990 attrIt.ExposeAttr(ariaAttrs);
3991 }
3992 if (ariaAttrs) {
3993 fields->SetAttribute(CacheKey::ARIAAttributes, std::move(ariaAttrs));
3994 } else if (aUpdateType == CacheUpdateType::Update) {
3995 fields->SetAttribute(CacheKey::ARIAAttributes, DeleteEntry());
3996 }
3997 }
3998
3999 if (aCacheDomain & CacheDomain::Relations && mContent) {
4000 if (IsHTMLRadioButton() ||
4001 (mContent->IsElement() &&
4002 mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) {
4003 // HTML radio buttons with the same name should be grouped
4004 // and returned together when their MEMBER_OF relation is
4005 // requested. Computing LINKS_TO also requires we cache `name` on
4006 // anchor elements.
4007 nsString name;
4008 mContent->AsElement()->GetAttr(nsGkAtoms::name, name);
4009 if (!name.IsEmpty()) {
4010 fields->SetAttribute(CacheKey::DOMName, std::move(name));
4011 } else if (aUpdateType != CacheUpdateType::Initial) {
4012 // It's possible we used to have a name and it's since been
4013 // removed. Send a delete entry.
4014 fields->SetAttribute(CacheKey::DOMName, DeleteEntry());
4015 }
4016 }
4017
4018 for (auto const& data : kRelationTypeAtoms) {
4019 nsTArray<uint64_t> ids;
4020 nsStaticAtom* const relAtom = data.mAtom;
4021
4022 Relation rel;
4023 if (data.mType == RelationType::LABEL_FOR) {
4024 // Labels are a special case -- we need to validate that the target of
4025 // their `for` attribute is in fact labelable. DOM checks this when we
4026 // call GetControl(). If a label contains an element we will return it
4027 // here.
4028 if (dom::HTMLLabelElement* labelEl =
4029 dom::HTMLLabelElement::FromNode(mContent)) {
4030 rel.AppendTarget(mDoc, labelEl->GetControl());
4031 }
4032 } else if (data.mType == RelationType::DETAILS) {
4033 // We need to use RelationByType for details because it might include
4034 // popovertarget. Nothing exposes an implicit reverse details
4035 // relation, so using RelationByType here is fine.
4036 rel = RelationByType(RelationType::DETAILS);
4037 } else {
4038 // We use an IDRefsIterator here instead of calling RelationByType
4039 // directly because we only want to cache explicit relations. Implicit
4040 // relations (e.g. LABEL_FOR exposed on the target of aria-labelledby)
4041 // will be computed and stored separately in the parent process.
4042 rel.AppendIter(new IDRefsIterator(mDoc, mContent, relAtom));
4043 }
4044
4045 while (LocalAccessible* acc = rel.LocalNext()) {
4046 ids.AppendElement(acc->ID());
4047 }
4048 if (ids.Length()) {
4049 fields->SetAttribute(relAtom, std::move(ids));
4050 } else if (aUpdateType == CacheUpdateType::Update) {
4051 fields->SetAttribute(relAtom, DeleteEntry());
4052 }
4053 }
4054 }
4055
4056#if defined(XP_WIN)
4057 if (aCacheDomain & CacheDomain::InnerHTML && HasOwnContent() &&
4058 mContent->IsMathMLElement(nsGkAtoms::math)) {
4059 nsString innerHTML;
4060 mContent->AsElement()->GetInnerHTML(innerHTML, IgnoreErrors());
4061 fields->SetAttribute(CacheKey::InnerHTML, std::move(innerHTML));
4062 }
4063#endif // defined(XP_WIN)
4064
4065 if (aUpdateType == CacheUpdateType::Initial) {
4066 // Add fields which never change and thus only need to be included in the
4067 // initial cache push.
4068 if (mContent && mContent->IsElement()) {
4069 fields->SetAttribute(CacheKey::TagName, mContent->NodeInfo()->NameAtom());
4070
4071 dom::Element* el = mContent->AsElement();
4072 if (IsTextField() || IsDateTimeField()) {
4073 // Cache text input types. Accessible is recreated if this changes,
4074 // so it is considered immutable.
4075 if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
4076 RefPtr<nsAtom> inputType = attr->GetAsAtom();
4077 if (inputType) {
4078 fields->SetAttribute(CacheKey::InputType, inputType);
4079 }
4080 }
4081 }
4082
4083 // Changing the role attribute currently re-creates the Accessible, so
4084 // it's immutable in the cache.
4085 if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
4086 // Most of the time, the role attribute is a single, known role. We
4087 // already send the map index, so we don't need to double up.
4088 if (!nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::role, roleMap->roleAtom,
4089 eIgnoreCase)) {
4090 // Multiple roles or unknown roles are rare, so just send them as a
4091 // string.
4092 nsAutoString role;
4093 nsAccUtils::GetARIAAttr(el, nsGkAtoms::role, role);
4094 fields->SetAttribute(CacheKey::ARIARole, std::move(role));
4095 }
4096 }
4097
4098 if (auto* htmlEl = nsGenericHTMLElement::FromNode(mContent)) {
4099 // Changing popover recreates the Accessible, so it's immutable in the
4100 // cache.
4101 nsAutoString popover;
4102 htmlEl->GetPopover(popover);
4103 if (!popover.IsEmpty()) {
4104 fields->SetAttribute(CacheKey::PopupType,
4105 RefPtr{NS_Atomize(popover)});
4106 }
4107 }
4108 }
4109
4110 if (frame) {
4111 // Note our frame's current computed style so we can track style changes
4112 // later on.
4113 mOldComputedStyle = frame->Style();
4114 if (frame->IsTransformed()) {
4115 mStateFlags |= eOldFrameHasValidTransformStyle;
4116 } else {
4117 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4118 }
4119 }
4120
4121 if (IsDoc()) {
4122 if (PresShell* presShell = AsDoc()->PresShellPtr()) {
4123 // Send the initial resolution of the document. When this changes, we
4124 // will ne notified via nsAS::NotifyOfResolutionChange
4125 float resolution = presShell->GetResolution();
4126 fields->SetAttribute(CacheKey::Resolution, resolution);
4127 int32_t appUnitsPerDevPixel =
4128 presShell->GetPresContext()->AppUnitsPerDevPixel();
4129 fields->SetAttribute(CacheKey::AppUnitsPerDevPixel,
4130 appUnitsPerDevPixel);
4131 }
4132
4133 nsString mimeType;
4134 AsDoc()->MimeType(mimeType);
4135 fields->SetAttribute(CacheKey::MimeType, std::move(mimeType));
4136 }
4137 }
4138
4139 if ((aCacheDomain & (CacheDomain::Text | CacheDomain::ScrollPosition) ||
4140 boundsChanged) &&
4141 mDoc) {
4142 mDoc->SetViewportCacheDirty(true);
4143 }
4144
4145 return fields.forget();
4146}
4147
4148void LocalAccessible::MaybeQueueCacheUpdateForStyleChanges() {
4149 // mOldComputedStyle might be null if the initial cache hasn't been sent yet.
4150 // In that case, there is nothing to do here.
4151 if (!IPCAccessibilityActive() || !mOldComputedStyle) {
4152 return;
4153 }
4154
4155 if (nsIFrame* frame = GetFrame()) {
4156 const ComputedStyle* newStyle = frame->Style();
4157
4158 nsAutoCString oldOverflow, newOverflow;
4159 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_overflow,
4160 oldOverflow);
4161 newStyle->GetComputedPropertyValue(eCSSProperty_overflow, newOverflow);
4162
4163 if (oldOverflow != newOverflow) {
4164 if (oldOverflow.Equals("hidden"_ns) || newOverflow.Equals("hidden"_ns)) {
4165 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4166 }
4167 if (oldOverflow.Equals("auto"_ns) || newOverflow.Equals("auto"_ns) ||
4168 oldOverflow.Equals("scroll"_ns) || newOverflow.Equals("scroll"_ns)) {
4169 // We cache a (0,0) scroll position for frames that have overflow
4170 // styling which means they _could_ become scrollable, even if the
4171 // content within them doesn't currently scroll.
4172 mDoc->QueueCacheUpdate(this, CacheDomain::ScrollPosition);
4173 }
4174 }
4175
4176 nsAutoCString oldDisplay, newDisplay;
4177 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_display,
4178 oldDisplay);
4179 newStyle->GetComputedPropertyValue(eCSSProperty_display, newDisplay);
4180
4181 nsAutoCString oldOpacity, newOpacity;
4182 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_opacity,
4183 oldOpacity);
4184 newStyle->GetComputedPropertyValue(eCSSProperty_opacity, newOpacity);
4185
4186 if (oldDisplay != newDisplay || oldOpacity != newOpacity) {
4187 // CacheDomain::Style covers both display and opacity, so if
4188 // either property has changed, send an update for the entire domain.
4189 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4190 }
4191
4192 nsAutoCString oldPosition, newPosition;
4193 mOldComputedStyle->GetComputedPropertyValue(eCSSProperty_position,
4194 oldPosition);
4195 newStyle->GetComputedPropertyValue(eCSSProperty_position, newPosition);
4196
4197 if (oldPosition != newPosition) {
4198 RefPtr<nsAtom> oldAtom = NS_Atomize(oldPosition);
4199 RefPtr<nsAtom> newAtom = NS_Atomize(newPosition);
4200 if (oldAtom == nsGkAtoms::fixed || newAtom == nsGkAtoms::fixed) {
4201 mDoc->QueueCacheUpdate(this, CacheDomain::Style);
4202 }
4203 }
4204
4205 bool newHasValidTransformStyle =
4206 newStyle->StyleDisplay()->HasTransform(frame);
4207 bool oldHasValidTransformStyle =
4208 (mStateFlags & eOldFrameHasValidTransformStyle) != 0;
4209
4210 // We should send a transform update if we're adding or
4211 // removing transform styling altogether.
4212 bool sendTransformUpdate =
4213 newHasValidTransformStyle || oldHasValidTransformStyle;
4214
4215 if (newHasValidTransformStyle && oldHasValidTransformStyle) {
4216 // If we continue to have transform styling, verify
4217 // our transform has actually changed.
4218 nsChangeHint transformHint =
4219 newStyle->StyleDisplay()->CalcTransformPropertyDifference(
4220 *mOldComputedStyle->StyleDisplay());
4221 // If this hint exists, it implies we found a property difference
4222 sendTransformUpdate = !!transformHint;
4223 }
4224
4225 if (sendTransformUpdate) {
4226 // If our transform matrix has changed, it's possible our
4227 // viewport cache has also changed.
4228 mDoc->SetViewportCacheDirty(true);
4229 // Queuing a cache update for the TransformMatrix domain doesn't
4230 // necessarily mean we'll send the matrix itself, we may
4231 // send a DeleteEntry() instead. See BundleFieldsForCache for
4232 // more information.
4233 mDoc->QueueCacheUpdate(this, CacheDomain::TransformMatrix);
4234 }
4235
4236 mOldComputedStyle = newStyle;
4237 if (newHasValidTransformStyle) {
4238 mStateFlags |= eOldFrameHasValidTransformStyle;
4239 } else {
4240 mStateFlags &= ~eOldFrameHasValidTransformStyle;
4241 }
4242 }
4243}
4244
4245nsAtom* LocalAccessible::TagName() const {
4246 return mContent && mContent->IsElement() ? mContent->NodeInfo()->NameAtom()
4247 : nullptr;
4248}
4249
4250already_AddRefed<nsAtom> LocalAccessible::InputType() const {
4251 if (!IsTextField() && !IsDateTimeField()) {
4252 return nullptr;
4253 }
4254
4255 dom::Element* el = mContent->AsElement();
4256 if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
4257 RefPtr<nsAtom> inputType = attr->GetAsAtom();
4258 return inputType.forget();
4259 }
4260
4261 return nullptr;
4262}
4263
4264already_AddRefed<nsAtom> LocalAccessible::DisplayStyle() const {
4265 dom::Element* elm = Elm();
4266 if (!elm) {
4267 return nullptr;
4268 }
4269 if (elm->IsHTMLElement(nsGkAtoms::area)) {
4270 // This is an image map area. CSS is irrelevant here.
4271 return nullptr;
4272 }
4273 static const dom::Element::AttrValuesArray presentationRoles[] = {
4274 nsGkAtoms::none, nsGkAtoms::presentation, nullptr};
4275 if (nsAccUtils::FindARIAAttrValueIn(elm, nsGkAtoms::role, presentationRoles,
4276 eIgnoreCase) != AttrArray::ATTR_MISSING &&
4277 IsGeneric()) {
4278 // This Accessible has been marked presentational, but we forced a generic
4279 // Accessible for some reason; e.g. CSS transform. Don't expose display in
4280 // this case, as the author might be explicitly trying to avoid said
4281 // exposure.
4282 return nullptr;
4283 }
4284 RefPtr<const ComputedStyle> style =
4285 nsComputedDOMStyle::GetComputedStyleNoFlush(elm);
4286 if (!style) {
4287 // The element is not styled, maybe not in the flat tree?
4288 return nullptr;
4289 }
4290 nsAutoCString value;
4291 style->GetComputedPropertyValue(eCSSProperty_display, value);
4292 return NS_Atomize(value);
4293}
4294
4295float LocalAccessible::Opacity() const {
4296 if (nsIFrame* frame = GetFrame()) {
4297 return frame->StyleEffects()->mOpacity;
4298 }
4299
4300 return 1.0f;
4301}
4302
4303void LocalAccessible::DOMNodeID(nsString& aID) const {
4304 aID.Truncate();
4305 if (mContent) {
4306 if (nsAtom* id = mContent->GetID()) {
4307 id->ToString(aID);
4308 }
4309 }
4310}
4311
4312void LocalAccessible::DOMNodeClass(nsString& aClass) const {
4313 aClass.Truncate();
4314 if (auto* el = dom::Element::FromNodeOrNull(mContent)) {
4315 el->GetClassName(aClass);
4316 }
4317}
4318
4319void LocalAccessible::LiveRegionAttributes(nsAString* aLive,
4320 nsAString* aRelevant,
4321 Maybe<bool>* aAtomic,
4322 nsAString* aBusy) const {
4323 dom::Element* el = Elm();
4324 if (!el) {
4325 return;
4326 }
4327 if (aLive) {
4328 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_live, *aLive);
4329 }
4330 if (aRelevant) {
4331 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_relevant, *aRelevant);
4332 }
4333 if (aAtomic) {
4334 // XXX We ignore aria-atomic="false", but this probably doesn't conform to
4335 // the spec.
4336 if (nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_atomic,
4337 nsGkAtoms::_true, eCaseMatters)) {
4338 *aAtomic = Some(true);
4339 }
4340 }
4341 if (aBusy) {
4342 nsAccUtils::GetARIAAttr(el, nsGkAtoms::aria_busy, *aBusy);
4343 }
4344}
4345
4346Maybe<bool> LocalAccessible::ARIASelected() const {
4347 if (dom::Element* el = Elm()) {
4348 nsStaticAtom* atom =
4349 nsAccUtils::NormalizeARIAToken(el, nsGkAtoms::aria_selected);
4350 if (atom == nsGkAtoms::_true) {
4351 return Some(true);
4352 }
4353 if (atom == nsGkAtoms::_false) {
4354 return Some(false);
4355 }
4356 }
4357 return Nothing();
4358}
4359
4360void LocalAccessible::StaticAsserts() const {
4361 static_assert(
4362 eLastStateFlag <= (1 << kStateFlagsBits) - 1,
4363 "LocalAccessible::mStateFlags was oversized by eLastStateFlag!");
4364 static_assert(
4365 eLastContextFlag <= (1 << kContextFlagsBits) - 1,
4366 "LocalAccessible::mContextFlags was oversized by eLastContextFlag!");
4367}
4368
4369TableAccessible* LocalAccessible::AsTable() {
4370 if (IsTable() && !mContent->IsXULElement()) {
4371 return CachedTableAccessible::GetFrom(this);
4372 }
4373 return nullptr;
4374}
4375
4376TableCellAccessible* LocalAccessible::AsTableCell() {
4377 if (IsTableCell() && !mContent->IsXULElement()) {
4378 return CachedTableCellAccessible::GetFrom(this);
4379 }
4380 return nullptr;
4381}
4382
4383Maybe<int32_t> LocalAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
4384 if (mContent) {
4385 int32_t val;
4386 if (nsCoreUtils::GetUIntAttr(mContent, aAttrName, &val)) {
4387 return Some(val);
4388 }
4389 // XXX Handle attributes that allow -1; e.g. aria-row/colcount.
4390 }
4391 return Nothing();
4392}