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