Bug Summary

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

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