Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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) {
MOZ_ASSERT(
// 1. Hyper text accessible and attributes list must always be specified.
mHyperTextAcc && aAttributes &&
(
// 2. If text attributes for a child of a container are being
// requested, the offset accessible must be specified and it must
// be text.
(mOffsetAcc && mOffsetAcc->IsText()) ||
// 3. If only default text attributes for a container are being
// requested, the offset accessible must not be specified, but the
// include default text attributes flag must be specified.
(!mOffsetAcc && mIncludeDefAttrs)),
"Wrong usage of TextAttrsMgr!");
// 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.
for (TextAttr* attr : attrArray) {
attr->Expose(aAttributes, mIncludeDefAttrs);
}
}
////////////////////////////////////////////////////////////////////////////////
// LangTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::LangTextAttr::LangTextAttr(HyperTextAccessible* aRoot,
nsIContent* aRootElm, nsIContent* aElm)
: TTextAttr<nsString>(!aElm), mRootContent(aRootElm) {
aRoot->Language(mRootNativeValue);
mIsRootDefined = !mRootNativeValue.IsEmpty();
if (aElm) {
nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue);
mIsDefined = !mNativeValue.IsEmpty();
}
}
TextAttrsMgr::LangTextAttr::~LangTextAttr() {}
void TextAttrsMgr::LangTextAttr::ExposeValue(AccAttributes* aAttributes,
const nsString& aValue) {
RefPtr<nsAtom> lang = NS_Atomize(aValue);
aAttributes->SetAttribute(nsGkAtoms::language, lang);
}
////////////////////////////////////////////////////////////////////////////////
// InvalidTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::InvalidTextAttr::InvalidTextAttr(nsIContent* aRootElm,
nsIContent* aElm)
: TTextAttr<uint32_t>(!aElm), mRootElm(aRootElm) {
mIsRootDefined = GetValue(mRootElm, &mRootNativeValue);
if (aElm) mIsDefined = GetValue(aElm, &mNativeValue);
}
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<nscolor>(!aFrame), mRootFrame(aRootFrame) {
mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
if (aFrame) mIsDefined = GetColor(aFrame, &mNativeValue);
}
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<nscolor>(!aFrame) {
mRootNativeValue = aRootFrame->StyleText()->mColor.ToColor();
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleText()->mColor.ToColor();
mIsDefined = true;
}
}
void TextAttrsMgr::ColorTextAttr::ExposeValue(AccAttributes* aAttributes,
const nscolor& aValue) {
aAttributes->SetAttribute(nsGkAtoms::color, Color{aValue});
}
////////////////////////////////////////////////////////////////////////////////
// FontFamilyTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontFamilyTextAttr::FontFamilyTextAttr(nsIFrame* aRootFrame,
nsIFrame* aFrame)
: TTextAttr<nsString>(!aFrame) {
mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
if (aFrame) mIsDefined = GetFontFamily(aFrame, mNativeValue);
}
void TextAttrsMgr::FontFamilyTextAttr::ExposeValue(AccAttributes* aAttributes,
const nsString& aValue) {
RefPtr<nsAtom> family = NS_Atomize(aValue);
aAttributes->SetAttribute(nsGkAtoms::font_family, family);
}
bool TextAttrsMgr::FontFamilyTextAttr::GetFontFamily(nsIFrame* aFrame,
nsString& aFamily) {
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
RefPtr<gfxFont> font = fontGroup->GetFirstValidFont();
gfxFontEntry* fontEntry = font->GetFontEntry();
aFamily.Append(NS_ConvertUTF8toUTF16(fontEntry->FamilyName()));
return true;
}
////////////////////////////////////////////////////////////////////////////////
// FontSizeTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontSizeTextAttr::FontSizeTextAttr(nsIFrame* aRootFrame,
nsIFrame* aFrame)
: TTextAttr<nscoord>(!aFrame) {
mDC = aRootFrame->PresContext()->DeviceContext();
mRootNativeValue = aRootFrame->StyleFont()->mSize.ToAppUnits();
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleFont()->mSize.ToAppUnits();
mIsDefined = true;
}
}
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.
//
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<FontSlantStyle>(!aFrame) {
mRootNativeValue = aRootFrame->StyleFont()->mFont.style;
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleFont()->mFont.style;
mIsDefined = true;
}
}
void TextAttrsMgr::FontStyleTextAttr::ExposeValue(
AccAttributes* aAttributes, const FontSlantStyle& aValue) {
if (aValue.IsNormal()) {
aAttributes->SetAttribute(nsGkAtoms::font_style, nsGkAtoms::normal);
} else if (aValue.IsItalic()) {
RefPtr<nsAtom> 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<FontWeight>(!aFrame) {
mRootNativeValue = GetFontWeight(aRootFrame);
mIsRootDefined = true;
if (aFrame) {
mNativeValue = GetFontWeight(aFrame);
mIsDefined = true;
}
}
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<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
RefPtr<gfxFont> 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<bool>(!aAccessible) {
mRootNativeValue = false;
mIsRootDefined = false;
if (aAccessible) {
mIsDefined = mNativeValue =
((aAccessible->NativeRole() == roles::STATICTEXT) ||
(aAccessible->NativeRole() == roles::LISTITEM_MARKER));
}
}
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<TextDecorValue>(!aFrame) {
mRootNativeValue = TextDecorValue(aRootFrame);
mIsRootDefined = mRootNativeValue.IsDefined();
if (aFrame) {
mNativeValue = TextDecorValue(aFrame);
mIsDefined = mNativeValue.IsDefined();
}
}
void TextAttrsMgr::TextDecorTextAttr::ExposeValue(
AccAttributes* aAttributes, const TextDecorValue& aValue) {
if (aValue.IsUnderline()) {
RefPtr<nsAtom> underlineStyle =
StyleInfo::TextDecorationStyleToAtom(aValue.Style());
aAttributes->SetAttribute(nsGkAtoms::textUnderlineStyle, underlineStyle);
aAttributes->SetAttribute(nsGkAtoms::textUnderlineColor,
Color{aValue.Color()});
return;
}
if (aValue.IsLineThrough()) {
RefPtr<nsAtom> 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<Maybe<TextPosValue>>(!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<TextPosValue> rootAria = GetAriaTextPosValue(aRootElm, ariaFrame);
if (rootAria && ariaFrame) {
aRootFrame = ariaFrame;
}
Maybe<TextPosValue> rootLayout = GetLayoutTextPosValue(aRootFrame);
Maybe<TextPosValue> childLayout;
Maybe<TextPosValue> 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;
}
}
void TextAttrsMgr::TextPosTextAttr::ExposeValue(
AccAttributes* aAttributes, const Maybe<TextPosValue>& aValue) {
if (aValue.isNothing()) {
return;
}
RefPtr<nsAtom> 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::TextPosValue>
TextAttrsMgr::TextPosTextAttr::GetAriaTextPosValue(nsIContent* aElm) const {
nsIFrame* ariaFrame = nullptr;
return GetAriaTextPosValue(aElm, ariaFrame);
}
Maybe<TextAttrsMgr::TextPosValue>
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::TextPosValue>
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{};
}