tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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