/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CssAltContent.h" #include "DocAccessible-inl.h" #include "mozilla/a11y/DocManager.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/Element.h" #include "nsIContent.h" #include "nsIFrame.h" #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" namespace mozilla::a11y { CssAltContent::CssAltContent(nsIContent* aContent) { nsIFrame* frame = aContent->GetPrimaryFrame(); if (!frame) { return; } if (!frame->IsReplaced()) { return; } // Check if this is for a pseudo-element. if (aContent->IsInNativeAnonymousSubtree()) { nsIContent* parent = aContent->GetParent(); if (parent && (parent->IsGeneratedContentContainerForBefore() || parent->IsGeneratedContentContainerForAfter() || parent->IsGeneratedContentContainerForMarker())) { mPseudoElement = parent->AsElement(); // We need the frame from the pseudo-element to get the content style. frame = parent->GetPrimaryFrame(); if (!frame) { return; } // We need the real element to get any attributes. mRealElement = parent->GetParentElement(); if (!mRealElement) { return; } } } if (!mRealElement) { if (aContent->IsElement()) { mRealElement = aContent->AsElement(); } else { return; } } mItems = frame->StyleContent()->AltContentItems(); } void CssAltContent::AppendToString(nsAString& aOut) { // There can be multiple alt text items. for (const auto& item : mItems) { if (item.IsString()) { aOut.Append(NS_ConvertUTF8toUTF16(item.AsString().AsString())); } else if (item.IsAttr()) { // This item gets its value from an attribute on the element or from // fallback text. MOZ_ASSERT(mRealElement); const auto& attr = item.AsAttr(); RefPtr name = attr.attribute.AsAtom(); int32_t nsId = kNameSpaceID_None; RefPtr ns = attr.namespace_url.AsAtom(); if (!ns->IsEmpty()) { nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( ns.forget(), nsId); if (NS_FAILED(rv)) { continue; } } if (mRealElement->IsHTMLElement() && mRealElement->OwnerDoc()->IsHTMLDocument()) { ToLowerCaseASCII(name); } nsAutoString val; if (!mRealElement->GetAttr(nsId, name, val)) { if (RefPtr fallback = attr.fallback.AsAtom()) { fallback->ToString(val); } } aOut.Append(val); } } } /* static */ bool CssAltContent::HandleAttributeChange(nsIContent* aContent, int32_t aNameSpaceID, nsAtom* aAttribute) { // Handle CSS content which replaces the content of aContent itself. if (CssAltContent(aContent).HandleAttributeChange(aNameSpaceID, aAttribute)) { return true; } // Handle any pseudo-elements with CSS alt content. for (dom::Element* pseudo : {nsLayoutUtils::GetBeforePseudo(aContent), nsLayoutUtils::GetAfterPseudo(aContent), nsLayoutUtils::GetMarkerPseudo(aContent)}) { // CssAltContent wants a child of a pseudo-element. nsIContent* child = pseudo ? pseudo->GetFirstChild() : nullptr; if (child && CssAltContent(child).HandleAttributeChange(aNameSpaceID, aAttribute)) { return true; } } return false; } bool CssAltContent::HandleAttributeChange(int32_t aNameSpaceID, nsAtom* aAttribute) { for (const auto& item : mItems) { if (!item.IsAttr()) { continue; } MOZ_ASSERT(mRealElement); const auto& attr = item.AsAttr(); RefPtr name = attr.attribute.AsAtom(); if (mRealElement->IsHTMLElement() && mRealElement->OwnerDoc()->IsHTMLDocument()) { ToLowerCaseASCII(name); } if (name != aAttribute) { continue; } int32_t nsId = kNameSpaceID_None; RefPtr ns = attr.namespace_url.AsAtom(); if (!ns->IsEmpty()) { nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( ns.forget(), nsId); if (NS_FAILED(rv)) { continue; } } if (nsId != aNameSpaceID) { continue; } // The CSS alt content references this attribute which has just changed. DocAccessible* docAcc = GetExistingDocAccessible(mRealElement->OwnerDoc()); MOZ_ASSERT(docAcc); if (mPseudoElement) { // For simplicity, we just recreate the pseudo-element subtree. If this // becomes a performance problem, we can probably do better. For images, // we can just fire a name change event. Text is a bit more complicated, // as we need to update the text leaf with the new alt text and fire the // appropriate text change events. Mixed content gets even messier. docAcc->RecreateAccessible(mPseudoElement); } else { // This is CSS content replacing an element's content. MOZ_ASSERT(mRealElement->GetPrimaryFrame()); MOZ_ASSERT(mRealElement->GetPrimaryFrame()->IsReplaced()); LocalAccessible* acc = docAcc->GetAccessible(mRealElement); MOZ_ASSERT(acc); docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, acc); } return true; } return false; } } // namespace mozilla::a11y