/* -*- 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 "TextAttrs.h" #include "AccAttributes.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" #include "StyleInfo.h" #include "gfxTextRun.h" #include "nsFontMetrics.h" #include "nsLayoutUtils.h" #include "nsContainerFrame.h" #include "HyperTextAccessible.h" #include "mozilla/AppUnits.h" using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // TextAttrsMgr //////////////////////////////////////////////////////////////////////////////// void TextAttrsMgr::GetAttributes(AccAttributes* aAttributes, uint32_t* aStartOffset, uint32_t* aEndOffset) { // 1. Hyper text accessible must be specified always. // 2. Offset accessible must be specified in // the case of text attributes. Result hyper text offsets are optional if you // just want the attributes for a single text Accessible. // 3. Offset accessible and result hyper text offsets must not be specified // but include default text attributes flag and attributes list must be // specified in the case of default text attributes. MOZ_ASSERT( mHyperTextAcc && ((mOffsetAcc && mOffsetAccIdx != -1) || (!mOffsetAcc && mOffsetAccIdx == -1 && !aStartOffset && !aEndOffset && mIncludeDefAttrs && aAttributes)), "Wrong usage of TextAttrsMgr!"); // Embedded objects are combined into own range with empty attributes set. if (mOffsetAcc && !mOffsetAcc->IsText()) { if (!aStartOffset) { return; } for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) { LocalAccessible* currAcc = mHyperTextAcc->LocalChildAt(childIdx); if (currAcc->IsText()) break; (*aStartOffset)--; } uint32_t childCount = mHyperTextAcc->ChildCount(); for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childCount; childIdx++) { LocalAccessible* currAcc = mHyperTextAcc->LocalChildAt(childIdx); if (currAcc->IsText()) break; (*aEndOffset)++; } return; } // Get the content and frame of the accessible. In the case of document // accessible it's role content and root frame. nsIContent* hyperTextElm = mHyperTextAcc->GetContent(); if (!hyperTextElm) { return; // XXX: we don't support text attrs on document with no body } nsIFrame* rootFrame = mHyperTextAcc->GetFrame(); if (!rootFrame) { return; } nsIContent *offsetNode = nullptr, *offsetElm = nullptr; nsIFrame* frame = nullptr; if (mOffsetAcc) { offsetNode = mOffsetAcc->GetContent(); offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode); MOZ_ASSERT(offsetElm, "No element for offset accessible!"); if (!offsetElm) return; frame = offsetElm->GetPrimaryFrame(); } // "language" text attribute LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode); // "aria-invalid" text attribute InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode); // "background-color" text attribute BGColorTextAttr bgColorTextAttr(rootFrame, frame); // "color" text attribute ColorTextAttr colorTextAttr(rootFrame, frame); // "font-family" text attribute FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame); // "font-size" text attribute FontSizeTextAttr fontSizeTextAttr(rootFrame, frame); // "font-style" text attribute FontStyleTextAttr fontStyleTextAttr(rootFrame, frame); // "font-weight" text attribute FontWeightTextAttr fontWeightTextAttr(rootFrame, frame); // "auto-generated" text attribute AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc); // "text-underline(line-through)-style(color)" text attributes TextDecorTextAttr textDecorTextAttr(rootFrame, frame); // "text-position" text attribute TextPosTextAttr textPosTextAttr(rootFrame, frame, hyperTextElm, offsetNode); TextAttr* attrArray[] = { &langTextAttr, &invalidTextAttr, &bgColorTextAttr, &colorTextAttr, &fontFamilyTextAttr, &fontSizeTextAttr, &fontStyleTextAttr, &fontWeightTextAttr, &autoGenTextAttr, &textDecorTextAttr, &textPosTextAttr}; // Expose text attributes if applicable. if (aAttributes) { for (uint32_t idx = 0; idx < ArrayLength(attrArray); idx++) { attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs); } } // Expose text attributes range where they are applied if applicable. if (aStartOffset) { GetRange(attrArray, ArrayLength(attrArray), aStartOffset, aEndOffset); } } void TextAttrsMgr::GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen, uint32_t* aStartOffset, uint32_t* aEndOffset) { // Navigate backward from anchor accessible to find start offset. for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) { LocalAccessible* currAcc = mHyperTextAcc->LocalChildAt(childIdx); // Stop on embedded accessible since embedded accessibles are combined into // own range. if (!currAcc->IsText()) break; MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()), "Text accessible has to have an associated DOM element"); bool offsetFound = false; for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) { TextAttr* textAttr = aAttrArray[attrIdx]; if (!textAttr->Equal(currAcc)) { offsetFound = true; break; } } if (offsetFound) break; *(aStartOffset) -= nsAccUtils::TextLength(currAcc); } // Navigate forward from anchor accessible to find end offset. uint32_t childLen = mHyperTextAcc->ChildCount(); for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childLen; childIdx++) { LocalAccessible* currAcc = mHyperTextAcc->LocalChildAt(childIdx); if (!currAcc->IsText()) break; MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()), "Text accessible has to have an associated DOM element"); bool offsetFound = false; for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) { TextAttr* textAttr = aAttrArray[attrIdx]; // Alter the end offset when text attribute changes its value and stop // the search. if (!textAttr->Equal(currAcc)) { offsetFound = true; break; } } if (offsetFound) break; (*aEndOffset) += nsAccUtils::TextLength(currAcc); } } //////////////////////////////////////////////////////////////////////////////// // LangTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::LangTextAttr::LangTextAttr(HyperTextAccessible* aRoot, nsIContent* aRootElm, nsIContent* aElm) : TTextAttr(!aElm), mRootContent(aRootElm) { aRoot->Language(mRootNativeValue); mIsRootDefined = !mRootNativeValue.IsEmpty(); if (aElm) { nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue); mIsDefined = !mNativeValue.IsEmpty(); } } TextAttrsMgr::LangTextAttr::~LangTextAttr() {} bool TextAttrsMgr::LangTextAttr::GetValueFor(LocalAccessible* aAccessible, nsString* aValue) { nsCoreUtils::GetLanguageFor(aAccessible->GetContent(), mRootContent, *aValue); return !aValue->IsEmpty(); } void TextAttrsMgr::LangTextAttr::ExposeValue(AccAttributes* aAttributes, const nsString& aValue) { RefPtr lang = NS_Atomize(aValue); aAttributes->SetAttribute(nsGkAtoms::language, lang); } //////////////////////////////////////////////////////////////////////////////// // InvalidTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::InvalidTextAttr::InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm) : TTextAttr(!aElm), mRootElm(aRootElm) { mIsRootDefined = GetValue(mRootElm, &mRootNativeValue); if (aElm) mIsDefined = GetValue(aElm, &mNativeValue); } bool TextAttrsMgr::InvalidTextAttr::GetValueFor(LocalAccessible* aAccessible, uint32_t* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); return elm ? GetValue(elm, aValue) : false; } void TextAttrsMgr::InvalidTextAttr::ExposeValue(AccAttributes* aAttributes, const uint32_t& aValue) { switch (aValue) { case eFalse: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::_false); break; case eGrammar: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::grammar); break; case eSpelling: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::spelling); break; case eTrue: aAttributes->SetAttribute(nsGkAtoms::invalid, nsGkAtoms::_true); break; } } bool TextAttrsMgr::InvalidTextAttr::GetValue(nsIContent* aElm, uint32_t* aValue) { nsIContent* elm = aElm; do { if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) { static dom::Element::AttrValuesArray tokens[] = { nsGkAtoms::_false, nsGkAtoms::grammar, nsGkAtoms::spelling, nullptr}; int32_t idx = nsAccUtils::FindARIAAttrValueIn( elm->AsElement(), nsGkAtoms::aria_invalid, tokens, eCaseMatters); switch (idx) { case 0: *aValue = eFalse; return true; case 1: *aValue = eGrammar; return true; case 2: *aValue = eSpelling; return true; default: *aValue = eTrue; return true; } } } while ((elm = elm->GetParent()) && elm != mRootElm); return false; } //////////////////////////////////////////////////////////////////////////////// // BGColorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::BGColorTextAttr::BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame), mRootFrame(aRootFrame) { mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue); if (aFrame) mIsDefined = GetColor(aFrame, &mNativeValue); } bool TextAttrsMgr::BGColorTextAttr::GetValueFor(LocalAccessible* aAccessible, nscolor* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { return GetColor(frame, aValue); } } return false; } void TextAttrsMgr::BGColorTextAttr::ExposeValue(AccAttributes* aAttributes, const nscolor& aValue) { aAttributes->SetAttribute(nsGkAtoms::backgroundColor, Color{aValue}); } bool TextAttrsMgr::BGColorTextAttr::GetColor(nsIFrame* aFrame, nscolor* aColor) { nscolor backgroundColor = aFrame->StyleBackground()->BackgroundColor(aFrame); if (NS_GET_A(backgroundColor) > 0) { *aColor = backgroundColor; return true; } nsContainerFrame* parentFrame = aFrame->GetParent(); if (!parentFrame) { *aColor = aFrame->PresContext()->DefaultBackgroundColor(); return true; } // Each frame of parents chain for the initially passed 'aFrame' has // transparent background color. So background color isn't changed from // 'mRootFrame' to initially passed 'aFrame'. if (parentFrame == mRootFrame) return false; return GetColor(parentFrame, aColor); } //////////////////////////////////////////////////////////////////////////////// // ColorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::ColorTextAttr::ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = aRootFrame->StyleText()->mColor.ToColor(); mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleText()->mColor.ToColor(); mIsDefined = true; } } bool TextAttrsMgr::ColorTextAttr::GetValueFor(LocalAccessible* aAccessible, nscolor* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { if (nsIFrame* frame = elm->GetPrimaryFrame()) { *aValue = frame->StyleText()->mColor.ToColor(); return true; } } return false; } void TextAttrsMgr::ColorTextAttr::ExposeValue(AccAttributes* aAttributes, const nscolor& aValue) { aAttributes->SetAttribute(nsGkAtoms::color, Color{aValue}); } //////////////////////////////////////////////////////////////////////////////// // FontFamilyTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue); if (aFrame) mIsDefined = GetFontFamily(aFrame, mNativeValue); } bool TextAttrsMgr::FontFamilyTextAttr::GetValueFor(LocalAccessible* aAccessible, nsString* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { return GetFontFamily(frame, *aValue); } } return false; } void TextAttrsMgr::FontFamilyTextAttr::ExposeValue(AccAttributes* aAttributes, const nsString& aValue) { RefPtr family = NS_Atomize(aValue); aAttributes->SetAttribute(nsGkAtoms::font_family, family); } bool TextAttrsMgr::FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame, nsString& aFamily) { RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f); gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); RefPtr font = fontGroup->GetFirstValidFont(); gfxFontEntry* fontEntry = font->GetFontEntry(); aFamily.Append(NS_ConvertUTF8toUTF16(fontEntry->FamilyName())); return true; } //////////////////////////////////////////////////////////////////////////////// // FontSizeTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontSizeTextAttr::FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mDC = aRootFrame->PresContext()->DeviceContext(); mRootNativeValue = aRootFrame->StyleFont()->mSize.ToAppUnits(); mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleFont()->mSize.ToAppUnits(); mIsDefined = true; } } bool TextAttrsMgr::FontSizeTextAttr::GetValueFor(LocalAccessible* aAccessible, nscoord* aValue) { nsIContent* el = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (el) { nsIFrame* frame = el->GetPrimaryFrame(); if (frame) { *aValue = frame->StyleFont()->mSize.ToAppUnits(); return true; } } return false; } void TextAttrsMgr::FontSizeTextAttr::ExposeValue(AccAttributes* aAttributes, const nscoord& aValue) { // Convert from nscoord to pt. // // Note: according to IA2, "The conversion doesn't have to be exact. // The intent is to give the user a feel for the size of the text." // // ATK does not specify a unit and will likely follow IA2 here. // // XXX todo: consider sharing this code with layout module? (bug 474621) float px = NSAppUnitsToFloatPixels(aValue, mozilla::AppUnitsPerCSSPixel()); // Each pt is 4/3 of a CSS pixel. FontSize fontSize{NS_lround(px * 3 / 4)}; aAttributes->SetAttribute(nsGkAtoms::font_size, fontSize); } //////////////////////////////////////////////////////////////////////////////// // FontStyleTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontStyleTextAttr::FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = aRootFrame->StyleFont()->mFont.style; mIsRootDefined = true; if (aFrame) { mNativeValue = aFrame->StyleFont()->mFont.style; mIsDefined = true; } } bool TextAttrsMgr::FontStyleTextAttr::GetValueFor(LocalAccessible* aAccessible, FontSlantStyle* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { *aValue = frame->StyleFont()->mFont.style; return true; } } return false; } void TextAttrsMgr::FontStyleTextAttr::ExposeValue( AccAttributes* aAttributes, const FontSlantStyle& aValue) { if (aValue.IsNormal()) { aAttributes->SetAttribute(nsGkAtoms::font_style, nsGkAtoms::normal); } else if (aValue.IsItalic()) { RefPtr atom = NS_Atomize("italic"); aAttributes->SetAttribute(nsGkAtoms::font_style, atom); } else { nsAutoCString s; aValue.ToString(s); nsString wide; CopyUTF8toUTF16(s, wide); aAttributes->SetAttribute(nsGkAtoms::font_style, std::move(wide)); } } //////////////////////////////////////////////////////////////////////////////// // FontWeightTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::FontWeightTextAttr::FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = GetFontWeight(aRootFrame); mIsRootDefined = true; if (aFrame) { mNativeValue = GetFontWeight(aFrame); mIsDefined = true; } } bool TextAttrsMgr::FontWeightTextAttr::GetValueFor(LocalAccessible* aAccessible, FontWeight* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { *aValue = GetFontWeight(frame); return true; } } return false; } void TextAttrsMgr::FontWeightTextAttr::ExposeValue(AccAttributes* aAttributes, const FontWeight& aValue) { int value = aValue.ToIntRounded(); aAttributes->SetAttribute(nsGkAtoms::fontWeight, value); } FontWeight TextAttrsMgr::FontWeightTextAttr::GetFontWeight(nsIFrame* aFrame) { // nsFont::width isn't suitable here because it's necessary to expose real // value of font weight (used font might not have some font weight values). RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f); gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); RefPtr font = fontGroup->GetFirstValidFont(); // When there doesn't exist a bold font in the family and so the rendering of // a non-bold font face is changed so that the user sees what looks like a // bold font, i.e. synthetic bolding is used. (Simply returns false on any // platforms that don't use the multi-strike synthetic bolding.) if (font->ApplySyntheticBold()) { return FontWeight::BOLD; } // On Windows, font->GetStyle()->weight will give the same weight as // fontEntry->Weight(), the weight of the first font in the font group, // which may not be the weight of the font face used to render the // characters. On Mac, font->GetStyle()->weight will just give the same // number as getComputedStyle(). fontEntry->Weight() will give the weight // range supported by the font face used, so we clamp the weight that was // requested by style to what is actually supported by the font. gfxFontEntry* fontEntry = font->GetFontEntry(); return fontEntry->Weight().Clamp(font->GetStyle()->weight); } //////////////////////////////////////////////////////////////////////////////// // AutoGeneratedTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::AutoGeneratedTextAttr::AutoGeneratedTextAttr( HyperTextAccessible* aHyperTextAcc, LocalAccessible* aAccessible) : TTextAttr(!aAccessible) { mRootNativeValue = false; mIsRootDefined = false; if (aAccessible) { mIsDefined = mNativeValue = ((aAccessible->NativeRole() == roles::STATICTEXT) || (aAccessible->NativeRole() == roles::LISTITEM_MARKER)); } } bool TextAttrsMgr::AutoGeneratedTextAttr::GetValueFor( LocalAccessible* aAccessible, bool* aValue) { return *aValue = (aAccessible->NativeRole() == roles::STATICTEXT); } void TextAttrsMgr::AutoGeneratedTextAttr::ExposeValue( AccAttributes* aAttributes, const bool& aValue) { aAttributes->SetAttribute(nsGkAtoms::auto_generated, aValue); } //////////////////////////////////////////////////////////////////////////////// // TextDecorTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::TextDecorValue::TextDecorValue(nsIFrame* aFrame) { const nsStyleTextReset* textReset = aFrame->StyleTextReset(); mStyle = textReset->mTextDecorationStyle; mColor = textReset->mTextDecorationColor.CalcColor(aFrame); mLine = textReset->mTextDecorationLine & (StyleTextDecorationLine::UNDERLINE | StyleTextDecorationLine::LINE_THROUGH); } TextAttrsMgr::TextDecorTextAttr::TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) : TTextAttr(!aFrame) { mRootNativeValue = TextDecorValue(aRootFrame); mIsRootDefined = mRootNativeValue.IsDefined(); if (aFrame) { mNativeValue = TextDecorValue(aFrame); mIsDefined = mNativeValue.IsDefined(); } } bool TextAttrsMgr::TextDecorTextAttr::GetValueFor(LocalAccessible* aAccessible, TextDecorValue* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { *aValue = TextDecorValue(frame); return aValue->IsDefined(); } } return false; } void TextAttrsMgr::TextDecorTextAttr::ExposeValue( AccAttributes* aAttributes, const TextDecorValue& aValue) { if (aValue.IsUnderline()) { RefPtr underlineStyle = StyleInfo::TextDecorationStyleToAtom(aValue.Style()); aAttributes->SetAttribute(nsGkAtoms::textUnderlineStyle, underlineStyle); aAttributes->SetAttribute(nsGkAtoms::textUnderlineColor, Color{aValue.Color()}); return; } if (aValue.IsLineThrough()) { RefPtr lineThroughStyle = StyleInfo::TextDecorationStyleToAtom(aValue.Style()); aAttributes->SetAttribute(nsGkAtoms::textLineThroughStyle, lineThroughStyle); aAttributes->SetAttribute(nsGkAtoms::textLineThroughColor, Color{aValue.Color()}); } } //////////////////////////////////////////////////////////////////////////////// // TextPosTextAttr //////////////////////////////////////////////////////////////////////////////// TextAttrsMgr::TextPosTextAttr::TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame, nsIContent* aRootElm, nsIContent* aElm) : TTextAttr>(!aFrame && !aElm), mRootElm(aRootElm) { // Get the text-position values for the roots and children. // If we find an ARIA text-position value on a DOM element - searching up // from the supplied root DOM element - use the associated frame as the root // frame. This ensures that we're using the proper root frame for comparison. nsIFrame* ariaFrame = nullptr; Maybe rootAria = GetAriaTextPosValue(aRootElm, ariaFrame); if (rootAria && ariaFrame) { aRootFrame = ariaFrame; } Maybe rootLayout = GetLayoutTextPosValue(aRootFrame); Maybe childLayout; Maybe childAria; if (aFrame) { childLayout = GetLayoutTextPosValue(aFrame); } if (aElm) { childAria = GetAriaTextPosValue(aElm); } // Aria values take precedence over layout values. mIsRootDefined = rootAria || rootLayout; mRootNativeValue = rootAria ? rootAria : rootLayout; mIsDefined = childAria || childLayout; mNativeValue = childAria ? childAria : childLayout; // If there's no child text-position information from ARIA, and the child // layout info is equivalent to the root layout info (i.e., it's inherited), // then we should prefer the root information. if (!childAria && childLayout == rootLayout) { mIsDefined = false; } } bool TextAttrsMgr::TextPosTextAttr::GetValueFor(LocalAccessible* aAccessible, Maybe* aValue) { nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent()); if (elm) { nsIFrame* frame = elm->GetPrimaryFrame(); if (frame) { Maybe layoutValue = GetLayoutTextPosValue(frame); Maybe ariaValue = GetAriaTextPosValue(elm); *aValue = ariaValue ? ariaValue : layoutValue; return aValue->isSome(); } } return false; } void TextAttrsMgr::TextPosTextAttr::ExposeValue( AccAttributes* aAttributes, const Maybe& aValue) { if (aValue.isNothing()) { return; } RefPtr atom = nullptr; switch (*aValue) { case eTextPosBaseline: atom = nsGkAtoms::baseline; break; case eTextPosSub: atom = nsGkAtoms::sub; break; case eTextPosSuper: atom = NS_Atomize("super"); break; } if (atom) { aAttributes->SetAttribute(nsGkAtoms::textPosition, atom); } } Maybe TextAttrsMgr::TextPosTextAttr::GetAriaTextPosValue(nsIContent* aElm) const { nsIFrame* ariaFrame = nullptr; return GetAriaTextPosValue(aElm, ariaFrame); } Maybe TextAttrsMgr::TextPosTextAttr::GetAriaTextPosValue(nsIContent* aElm, nsIFrame*& ariaFrame) const { // Search for the superscript and subscript roles that imply text-position. const nsIContent* elm = aElm; do { if (elm->IsElement()) { const mozilla::dom::Element* domElm = elm->AsElement(); static const dom::Element::AttrValuesArray tokens[] = { nsGkAtoms::subscript, nsGkAtoms::superscript, nullptr}; const int32_t valueIdx = domElm->FindAttrValueIn( kNameSpaceID_None, nsGkAtoms::role, tokens, eCaseMatters); ariaFrame = domElm->GetPrimaryFrame(); if (valueIdx == 0) { return Some(eTextPosSub); } if (valueIdx == 1) { return Some(eTextPosSuper); } } } while ((elm = elm->GetParent()) && elm != mRootElm); ariaFrame = nullptr; return Nothing{}; } Maybe TextAttrsMgr::TextPosTextAttr::GetLayoutTextPosValue(nsIFrame* aFrame) const { const auto& verticalAlign = aFrame->StyleDisplay()->mVerticalAlign; if (verticalAlign.IsKeyword()) { switch (verticalAlign.AsKeyword()) { case StyleVerticalAlignKeyword::Baseline: return Some(eTextPosBaseline); case StyleVerticalAlignKeyword::Sub: return Some(eTextPosSub); case StyleVerticalAlignKeyword::Super: return Some(eTextPosSuper); // No good guess for the rest, so do not expose value of text-position // attribute. default: return Nothing{}; } } const auto& length = verticalAlign.AsLength(); if (length.ConvertsToPercentage()) { const float percentValue = length.ToPercentage(); return percentValue > 0 ? Some(eTextPosSuper) : (percentValue < 0 ? Some(eTextPosSub) : Some(eTextPosBaseline)); } if (length.ConvertsToLength()) { const nscoord coordValue = length.ToLength(); return coordValue > 0 ? Some(eTextPosSuper) : (coordValue < 0 ? Some(eTextPosSub) : Some(eTextPosBaseline)); } if (const nsIContent* content = aFrame->GetContent()) { if (content->IsHTMLElement(nsGkAtoms::sup)) return Some(eTextPosSuper); if (content->IsHTMLElement(nsGkAtoms::sub)) return Some(eTextPosSub); } return Nothing{}; }