CssAltContent.cpp (6461B)
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 "CssAltContent.h" 7 #include "DocAccessible-inl.h" 8 #include "mozilla/a11y/DocManager.h" 9 #include "mozilla/dom/Document.h" 10 #include "mozilla/dom/Element.h" 11 #include "nsCoreUtils.h" 12 #include "nsIContent.h" 13 #include "nsIFrame.h" 14 #include "nsLayoutUtils.h" 15 #include "nsNameSpaceManager.h" 16 17 namespace mozilla::a11y { 18 19 CssAltContent::CssAltContent(nsIContent* aContent) { 20 if (!aContent) { 21 return; 22 } 23 24 nsIFrame* frame = aContent->GetPrimaryFrame(); 25 if (!frame) { 26 return; 27 } 28 // Check if this is for a pseudo-element. 29 if (nsCoreUtils::IsPseudoElement(aContent)) { 30 // If there are children, we want to expose the alt text on those instead, 31 // so ignore it for the pseudo-element itself. 32 if (aContent->HasChildren()) { 33 return; 34 } 35 // No children only happens when there is alt text with an empty content 36 // string; e.g. content: "" / "alt" 37 // In this case, we need to expose the alt text on the pseudo-element 38 // itself. 39 mPseudoElement = aContent->AsElement(); 40 } else if (aContent->IsInNativeAnonymousSubtree()) { 41 if (!frame->IsReplaced()) { 42 return; 43 } 44 dom::Element* parent = aContent->GetParentElement(); 45 if (parent && nsCoreUtils::IsPseudoElement(parent)) { 46 // aContent is a child of a pseudo-element. 47 mPseudoElement = parent; 48 // We need the frame from the pseudo-element to get the content style. 49 frame = parent->GetPrimaryFrame(); 50 if (!frame) { 51 return; 52 } 53 } 54 } 55 if (mPseudoElement) { 56 // We need the real element to get any attributes. 57 mRealElement = mPseudoElement->GetParentElement(); 58 if (!mRealElement) { 59 return; 60 } 61 } 62 if (!mRealElement) { 63 // This isn't for a pseudo-element. It might be an element which has its 64 // content replaced using CSS content. 65 if (aContent->IsElement()) { 66 if (!frame->IsReplaced()) { 67 return; 68 } 69 mRealElement = aContent->AsElement(); 70 } else { 71 return; 72 } 73 } 74 mItems = frame->StyleContent()->AltContentItems(); 75 } 76 77 void CssAltContent::AppendToString(nsAString& aOut) { 78 // There can be multiple alt text items. 79 for (const auto& item : mItems) { 80 if (item.IsString()) { 81 aOut.Append(NS_ConvertUTF8toUTF16(item.AsString().AsString())); 82 } else if (item.IsAttr()) { 83 // This item gets its value from an attribute on the element or from 84 // fallback text. 85 MOZ_ASSERT(mRealElement); 86 const auto& attr = item.AsAttr(); 87 RefPtr<nsAtom> name = attr.attribute.AsAtom(); 88 int32_t nsId = kNameSpaceID_None; 89 RefPtr<nsAtom> ns = attr.namespace_url.AsAtom(); 90 if (!ns->IsEmpty()) { 91 nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( 92 ns.forget(), nsId); 93 if (NS_FAILED(rv)) { 94 continue; 95 } 96 } 97 if (mRealElement->IsHTMLElement() && 98 mRealElement->OwnerDoc()->IsHTMLDocument()) { 99 ToLowerCaseASCII(name); 100 } 101 nsAutoString val; 102 if (!mRealElement->GetAttr(nsId, name, val)) { 103 if (RefPtr<nsAtom> fallback = attr.fallback.AsAtom()) { 104 fallback->ToString(val); 105 } 106 } 107 aOut.Append(val); 108 } 109 } 110 } 111 112 /* static */ 113 bool CssAltContent::HandleAttributeChange(nsIContent* aContent, 114 int32_t aNameSpaceID, 115 nsAtom* aAttribute) { 116 // Handle CSS content which replaces the content of aContent itself. 117 if (CssAltContent(aContent).HandleAttributeChange(aNameSpaceID, aAttribute)) { 118 return true; 119 } 120 // Handle any pseudo-elements with CSS alt content. 121 for (dom::Element* pseudo : {nsLayoutUtils::GetBeforePseudo(aContent), 122 nsLayoutUtils::GetAfterPseudo(aContent), 123 nsLayoutUtils::GetMarkerPseudo(aContent)}) { 124 // CssAltContent wants a child of a pseudo-element if there is one. 125 nsIContent* content = pseudo ? pseudo->GetFirstChild() : nullptr; 126 if (!content) { 127 content = pseudo; 128 } 129 if (content && CssAltContent(content).HandleAttributeChange(aNameSpaceID, 130 aAttribute)) { 131 return true; 132 } 133 } 134 return false; 135 } 136 137 bool CssAltContent::HandleAttributeChange(int32_t aNameSpaceID, 138 nsAtom* aAttribute) { 139 for (const auto& item : mItems) { 140 if (!item.IsAttr()) { 141 continue; 142 } 143 MOZ_ASSERT(mRealElement); 144 const auto& attr = item.AsAttr(); 145 RefPtr<nsAtom> name = attr.attribute.AsAtom(); 146 if (mRealElement->IsHTMLElement() && 147 mRealElement->OwnerDoc()->IsHTMLDocument()) { 148 ToLowerCaseASCII(name); 149 } 150 if (name != aAttribute) { 151 continue; 152 } 153 int32_t nsId = kNameSpaceID_None; 154 RefPtr<nsAtom> ns = attr.namespace_url.AsAtom(); 155 if (!ns->IsEmpty()) { 156 nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( 157 ns.forget(), nsId); 158 if (NS_FAILED(rv)) { 159 continue; 160 } 161 } 162 if (nsId != aNameSpaceID) { 163 continue; 164 } 165 // The CSS alt content references this attribute which has just changed. 166 DocAccessible* docAcc = GetExistingDocAccessible(mRealElement->OwnerDoc()); 167 MOZ_ASSERT(docAcc); 168 if (mPseudoElement) { 169 // For simplicity, we just recreate the pseudo-element subtree. If this 170 // becomes a performance problem, we can probably do better. For images, 171 // we can just fire a name change event. Text is a bit more complicated, 172 // as we need to update the text leaf with the new alt text and fire the 173 // appropriate text change events. Mixed content gets even messier. 174 docAcc->RecreateAccessible(mPseudoElement); 175 } else { 176 // This is CSS content replacing an element's content. 177 MOZ_ASSERT(mRealElement->GetPrimaryFrame()); 178 MOZ_ASSERT(mRealElement->GetPrimaryFrame()->IsReplaced()); 179 LocalAccessible* acc = docAcc->GetAccessible(mRealElement); 180 MOZ_ASSERT(acc); 181 docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, acc); 182 } 183 return true; 184 } 185 return false; 186 } 187 188 } // namespace mozilla::a11y