Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

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