tor-browser

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

MathMLElement.cpp (26994B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MathMLElement.h"
      8 
      9 #include "mozilla/EventListenerManager.h"
     10 #include "mozilla/FocusModel.h"
     11 #include "mozilla/StaticPrefs_mathml.h"
     12 #include "mozilla/TextUtils.h"
     13 #include "mozilla/dom/BindContext.h"
     14 #include "mozilla/dom/Document.h"
     15 #include "nsAttrValueOrString.h"
     16 #include "nsCSSValue.h"
     17 #include "nsContentUtils.h"
     18 #include "nsGkAtoms.h"
     19 #include "nsIContentInlines.h"
     20 #include "nsIScriptError.h"
     21 #include "nsITableCellLayout.h"  // for MAX_COLSPAN / MAX_ROWSPAN
     22 #include "nsIURI.h"
     23 #include "nsPresContext.h"
     24 #include "nsStyleConsts.h"
     25 
     26 // used for parsing CSS units
     27 #include "mozilla/EventDispatcher.h"
     28 #include "mozilla/MappedDeclarationsBuilder.h"
     29 #include "mozilla/dom/MathMLElementBinding.h"
     30 #include "mozilla/dom/SVGLength.h"
     31 
     32 using namespace mozilla;
     33 using namespace mozilla::dom;
     34 
     35 //----------------------------------------------------------------------
     36 // nsISupports methods:
     37 
     38 NS_IMPL_ISUPPORTS_INHERITED(MathMLElement, MathMLElementBase, Link)
     39 
     40 static nsresult ReportLengthParseError(const nsString& aValue,
     41                                       Document* aDocument) {
     42  AutoTArray<nsString, 1> arg = {aValue};
     43  return nsContentUtils::ReportToConsole(
     44      nsIScriptError::errorFlag, "MathML"_ns, aDocument,
     45      nsContentUtils::eMATHML_PROPERTIES, "LengthParsingError", arg);
     46 }
     47 
     48 static nsresult ReportParseErrorNoTag(const nsString& aValue, nsAtom* aAtom,
     49                                      Document& aDocument) {
     50  AutoTArray<nsString, 2> argv = {aValue, nsDependentAtomString(aAtom)};
     51  return nsContentUtils::ReportToConsole(
     52      nsIScriptError::errorFlag, "MathML"_ns, &aDocument,
     53      nsContentUtils::eMATHML_PROPERTIES, "AttributeParsingErrorNoTag", argv);
     54 }
     55 
     56 MathMLElement::MathMLElement(
     57    already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
     58    : MathMLElementBase(std::move(aNodeInfo)), Link(this) {}
     59 
     60 MathMLElement::MathMLElement(
     61    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     62    : MathMLElementBase(std::move(aNodeInfo)), Link(this) {}
     63 
     64 nsresult MathMLElement::BindToTree(BindContext& aContext, nsINode& aParent) {
     65  nsresult rv = MathMLElementBase::BindToTree(aContext, aParent);
     66  NS_ENSURE_SUCCESS(rv, rv);
     67 
     68  Link::BindToTree(aContext);
     69 
     70  // Set the bit in the document for telemetry.
     71  if (Document* doc = aContext.GetComposedDoc()) {
     72    doc->SetUseCounter(eUseCounter_custom_MathMLUsed);
     73  }
     74 
     75  return rv;
     76 }
     77 
     78 void MathMLElement::UnbindFromTree(UnbindContext& aContext) {
     79  MathMLElementBase::UnbindFromTree(aContext);
     80  // Without removing the link state we risk a dangling pointer in the
     81  // mStyledLinks hashtable
     82  Link::UnbindFromTree();
     83 }
     84 
     85 bool MathMLElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
     86                                   const nsAString& aValue,
     87                                   nsIPrincipal* aMaybeScriptedPrincipal,
     88                                   nsAttrValue& aResult) {
     89  MOZ_ASSERT(IsMathMLElement());
     90 
     91  if (aNamespaceID == kNameSpaceID_None) {
     92    if (aAttribute == nsGkAtoms::mathcolor ||
     93        aAttribute == nsGkAtoms::mathbackground) {
     94      return aResult.ParseColor(aValue);
     95    }
     96    if (aAttribute == nsGkAtoms::tabindex) {
     97      return aResult.ParseIntValue(aValue);
     98    }
     99    if (mNodeInfo->Equals(nsGkAtoms::mtd)) {
    100      if (aAttribute == nsGkAtoms::columnspan) {
    101        aResult.ParseClampedNonNegativeInt(aValue, 1, 1, MAX_COLSPAN);
    102        return true;
    103      }
    104      if (aAttribute == nsGkAtoms::rowspan) {
    105        aResult.ParseClampedNonNegativeInt(aValue, 1, 0, MAX_ROWSPAN);
    106        return true;
    107      }
    108    }
    109  }
    110 
    111  return MathMLElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
    112                                           aMaybeScriptedPrincipal, aResult);
    113 }
    114 
    115 // https://mathml-refresh.github.io/mathml-core/#global-attributes
    116 static Element::MappedAttributeEntry sGlobalAttributes[] = {
    117    {nsGkAtoms::dir},
    118    {nsGkAtoms::mathbackground},
    119    {nsGkAtoms::mathcolor},
    120    {nsGkAtoms::mathsize},
    121    {nsGkAtoms::scriptlevel},
    122    {nsGkAtoms::displaystyle},
    123    {nullptr}};
    124 
    125 bool MathMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
    126  MOZ_ASSERT(IsMathMLElement());
    127 
    128  static const MappedAttributeEntry* const globalMap[] = {sGlobalAttributes};
    129 
    130  return FindAttributeDependence(aAttribute, globalMap) ||
    131         ((!StaticPrefs::mathml_legacy_mathvariant_attribute_disabled() ||
    132           mNodeInfo->Equals(nsGkAtoms::mi)) &&
    133          aAttribute == nsGkAtoms::mathvariant) ||
    134         (mNodeInfo->Equals(nsGkAtoms::mtable) &&
    135          aAttribute == nsGkAtoms::width);
    136 }
    137 
    138 nsMapRuleToAttributesFunc MathMLElement::GetAttributeMappingFunction() const {
    139  if (mNodeInfo->Equals(nsGkAtoms::mtable)) {
    140    return &MapMTableAttributesInto;
    141  }
    142  if (StaticPrefs::mathml_legacy_mathvariant_attribute_disabled() &&
    143      mNodeInfo->Equals(nsGkAtoms::mi)) {
    144    return &MapMiAttributesInto;
    145  }
    146  return &MapGlobalMathMLAttributesInto;
    147 }
    148 
    149 /* static */
    150 bool MathMLElement::ParseNamedSpaceValue(const nsString& aString,
    151                                         nsCSSValue& aCSSValue, uint32_t aFlags,
    152                                         const Document& aDocument) {
    153  if (StaticPrefs::mathml_mathspace_names_disabled()) {
    154    return false;
    155  }
    156  int32_t i = 0;
    157  // See if it is one of the 'namedspace' (ranging -7/18em, -6/18, ... 7/18em)
    158  if (aString.EqualsLiteral("veryverythinmathspace")) {
    159    i = 1;
    160  } else if (aString.EqualsLiteral("verythinmathspace")) {
    161    i = 2;
    162  } else if (aString.EqualsLiteral("thinmathspace")) {
    163    i = 3;
    164  } else if (aString.EqualsLiteral("mediummathspace")) {
    165    i = 4;
    166  } else if (aString.EqualsLiteral("thickmathspace")) {
    167    i = 5;
    168  } else if (aString.EqualsLiteral("verythickmathspace")) {
    169    i = 6;
    170  } else if (aString.EqualsLiteral("veryverythickmathspace")) {
    171    i = 7;
    172  } else if (aFlags & PARSE_ALLOW_NEGATIVE) {
    173    if (aString.EqualsLiteral("negativeveryverythinmathspace")) {
    174      i = -1;
    175    } else if (aString.EqualsLiteral("negativeverythinmathspace")) {
    176      i = -2;
    177    } else if (aString.EqualsLiteral("negativethinmathspace")) {
    178      i = -3;
    179    } else if (aString.EqualsLiteral("negativemediummathspace")) {
    180      i = -4;
    181    } else if (aString.EqualsLiteral("negativethickmathspace")) {
    182      i = -5;
    183    } else if (aString.EqualsLiteral("negativeverythickmathspace")) {
    184      i = -6;
    185    } else if (aString.EqualsLiteral("negativeveryverythickmathspace")) {
    186      i = -7;
    187    }
    188  }
    189  if (0 != i) {
    190    AutoTArray<nsString, 1> params;
    191    params.AppendElement(aString);
    192    aDocument.WarnOnceAbout(
    193        dom::DeprecatedOperations::eMathML_DeprecatedMathSpaceValue2, false,
    194        params);
    195    aCSSValue.SetFloatValue(float(i) / float(18), eCSSUnit_EM);
    196    return true;
    197  }
    198 
    199  return false;
    200 }
    201 
    202 // The REC says:
    203 //
    204 // "Most presentation elements have attributes that accept values representing
    205 // lengths to be used for size, spacing or similar properties. The syntax of a
    206 // length is specified as
    207 //
    208 // number | number unit | namedspace
    209 //
    210 // There should be no space between the number and the unit of a length."
    211 //
    212 // "A trailing '%' represents a percent of the default value. The default
    213 // value, or how it is obtained, is listed in the table of attributes for each
    214 // element. [...] A number without a unit is intepreted as a multiple of the
    215 // default value."
    216 //
    217 // "The possible units in MathML are:
    218 //
    219 // Unit Description
    220 // em   an em (font-relative unit traditionally used for horizontal lengths)
    221 // ex   an ex (font-relative unit traditionally used for vertical lengths)
    222 // px   pixels, or size of a pixel in the current display
    223 // in   inches (1 inch = 2.54 centimeters)
    224 // cm   centimeters
    225 // mm   millimeters
    226 // pt   points (1 point = 1/72 inch)
    227 // pc   picas (1 pica = 12 points)
    228 // %    percentage of default value"
    229 //
    230 // The numbers are defined that way:
    231 // - unsigned-number: "a string of decimal digits with up to one decimal point
    232 //   (U+002E), representing a non-negative terminating decimal number (a type of
    233 //   rational number)"
    234 // - number: "an optional prefix of '-' (U+002D), followed by an unsigned
    235 //   number, representing a terminating decimal number (a type of rational
    236 //   number)"
    237 //
    238 /* static */
    239 // XXXfredw: Deprecate legacy MathML syntax and use the CSS parser instead.
    240 // See https://github.com/mathml-refresh/mathml/issues/63
    241 bool MathMLElement::ParseNumericValue(const nsString& aString,
    242                                      nsCSSValue& aCSSValue, uint32_t aFlags,
    243                                      Document* aDocument) {
    244  nsAutoString str(aString);
    245  str.CompressWhitespace();  // aString is const in this code...
    246 
    247  int32_t stringLength = str.Length();
    248  if (!stringLength) {
    249    if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    250      ReportLengthParseError(aString, aDocument);
    251    }
    252    return false;
    253  }
    254 
    255  if (aDocument && ParseNamedSpaceValue(str, aCSSValue, aFlags, *aDocument)) {
    256    return true;
    257  }
    258 
    259  nsAutoString number, unit;
    260 
    261  // see if the negative sign is there
    262  int32_t i = 0;
    263  char16_t c = str[0];
    264  if (c == '-') {
    265    number.Append(c);
    266    i++;
    267  }
    268 
    269  // Gather up characters that make up the number
    270  bool gotDot = false;
    271  for (; i < stringLength; i++) {
    272    c = str[i];
    273    if (gotDot && c == '.') {
    274      if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    275        ReportLengthParseError(aString, aDocument);
    276      }
    277      return false;  // two dots encountered
    278    } else if (c == '.')
    279      gotDot = true;
    280    else if (!IsAsciiDigit(c)) {
    281      str.Right(unit, stringLength - i);
    282      // some authors leave blanks before the unit, but that shouldn't
    283      // be allowed, so don't CompressWhitespace on 'unit'.
    284      break;
    285    }
    286    number.Append(c);
    287  }
    288  if (gotDot && str[i - 1] == '.') {
    289    if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    290      ReportLengthParseError(aString, aDocument);
    291    }
    292    return false;  // Number ending with a dot.
    293  }
    294 
    295  // Convert number to floating point
    296  nsresult errorCode;
    297  float floatValue = number.ToFloat(&errorCode);
    298  if (NS_FAILED(errorCode)) {
    299    if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    300      ReportLengthParseError(aString, aDocument);
    301    }
    302    return false;
    303  }
    304  if (floatValue < 0 && !(aFlags & PARSE_ALLOW_NEGATIVE)) {
    305    if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    306      ReportLengthParseError(aString, aDocument);
    307    }
    308    return false;
    309  }
    310 
    311  nsCSSUnit cssUnit;
    312  if (unit.IsEmpty()) {
    313    // We are supposed to have a unit, but there isn't one.
    314    // If the value is 0 we can just call it "pixels" otherwise
    315    // this is illegal.
    316    if (floatValue != 0.0) {
    317      if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    318        ReportLengthParseError(aString, aDocument);
    319      }
    320      return false;
    321    }
    322    cssUnit = eCSSUnit_Pixel;
    323  } else if (unit.EqualsLiteral("%")) {
    324    aCSSValue.SetPercentValue(floatValue / 100.0f);
    325    return true;
    326  } else {
    327    uint8_t unitType = SVGLength::GetUnitTypeForString(unit);
    328    if (unitType ==
    329        SVGLength_Binding::SVG_LENGTHTYPE_UNKNOWN) {  // unexpected unit
    330      if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
    331        ReportLengthParseError(aString, aDocument);
    332      }
    333      return false;
    334    }
    335    cssUnit = SVGLength::SpecifiedUnitTypeToCSSUnit(unitType);
    336  }
    337 
    338  aCSSValue.SetFloatValue(floatValue, cssUnit);
    339  return true;
    340 }
    341 
    342 void MathMLElement::MapMTableAttributesInto(
    343    MappedDeclarationsBuilder& aBuilder) {
    344  // width
    345  //
    346  // "Specifies the desired width of the entire table and is intended for
    347  // visual user agents. When the value is a percentage value, the value is
    348  // relative to the horizontal space a MathML renderer has available for the
    349  // math element. When the value is "auto", the MathML renderer should
    350  // calculate the table width from its contents using whatever layout
    351  // algorithm it chooses. "
    352  //
    353  // values: "auto" | length
    354  // default: auto
    355  //
    356  if (!aBuilder.PropertyIsSet(eCSSProperty_width)) {
    357    const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::width);
    358    nsCSSValue width;
    359    // This does not handle auto and unitless values
    360    if (value && (value->Type() == nsAttrValue::eString ||
    361                  value->Type() == nsAttrValue::eAtom)) {
    362      nsString str(nsAttrValueOrString(value).String());
    363      ParseNumericValue(str, width, 0, &aBuilder.Document());
    364      if (width.GetUnit() == eCSSUnit_Percent) {
    365        aBuilder.SetPercentValue(eCSSProperty_width, width.GetPercentValue());
    366      } else if (width.GetUnit() != eCSSUnit_Null) {
    367        aBuilder.SetLengthValue(eCSSProperty_width, width);
    368      }
    369    }
    370  }
    371  MapGlobalMathMLAttributesInto(aBuilder);
    372 }
    373 
    374 void MathMLElement::MapMiAttributesInto(MappedDeclarationsBuilder& aBuilder) {
    375  // mathvariant
    376  // https://w3c.github.io/mathml-core/#dfn-mathvariant
    377  if (!aBuilder.PropertyIsSet(eCSSProperty_text_transform)) {
    378    const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::mathvariant);
    379    if (value && (value->Type() == nsAttrValue::eString ||
    380                  value->Type() == nsAttrValue::eAtom)) {
    381      nsString str(nsAttrValueOrString(value).String());
    382      str.CompressWhitespace();
    383      if (str.LowerCaseEqualsASCII("normal")) {
    384        aBuilder.SetKeywordValue(eCSSProperty_text_transform,
    385                                 StyleTextTransform::NONE._0);
    386      }
    387    }
    388  }
    389  MapGlobalMathMLAttributesInto(aBuilder);
    390 }
    391 
    392 // Helper consteval function, similar in spirit to memmem(3).
    393 // It is only meant to be used at compile-time and uses a naive algorithm for
    394 // maintainability. The impact on compile time should be negligible given the
    395 // input size, and there's no runtime cost.
    396 template <uint8_t N, uint8_t M>
    397 static constexpr uint8_t cmemmemi(const char (&needle)[N],
    398                                  const char (&haystack)[M]) {
    399  static_assert(M > N, "needle larger than haystack");
    400  for (uint8_t i = 0; i < M - N; ++i) {
    401    for (uint8_t j = 0; j < N; ++j) {
    402      if (needle[j] != haystack[i + j]) {
    403        break;
    404      }
    405      if (needle[j] == '\0') {
    406        return i;
    407      }
    408    }
    409  }
    410  // Trigger an illegal access in the parent array at compile time.
    411  return std::numeric_limits<uint8_t>::max();
    412 }
    413 
    414 void MathMLElement::MapGlobalMathMLAttributesInto(
    415    MappedDeclarationsBuilder& aBuilder) {
    416  // scriptlevel
    417  // https://w3c.github.io/mathml-core/#dfn-scriptlevel
    418  const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::scriptlevel);
    419  if (value &&
    420      (value->Type() == nsAttrValue::eString ||
    421       value->Type() == nsAttrValue::eAtom) &&
    422      !aBuilder.PropertyIsSet(eCSSProperty_math_depth)) {
    423    nsString str(nsAttrValueOrString(value).String());
    424    // FIXME: Should we remove whitespace trimming?
    425    // See https://github.com/w3c/mathml/issues/122
    426    str.CompressWhitespace();
    427    if (str.Length() > 0) {
    428      nsresult errorCode;
    429      int32_t intValue = str.ToInteger(&errorCode);
    430      bool reportParseError = true;
    431      if (NS_SUCCEEDED(errorCode)) {
    432        char16_t ch = str.CharAt(0);
    433        bool isRelativeScriptLevel = (ch == '+' || ch == '-');
    434        // ToInteger is not very strict, check this is really <unsigned>.
    435        reportParseError = false;
    436        for (uint32_t i = isRelativeScriptLevel ? 1 : 0; i < str.Length();
    437             i++) {
    438          if (!IsAsciiDigit(str.CharAt(i))) {
    439            reportParseError = true;
    440            break;
    441          }
    442        }
    443        if (!reportParseError) {
    444          aBuilder.SetMathDepthValue(intValue, isRelativeScriptLevel);
    445        }
    446      }
    447      if (reportParseError) {
    448        ReportParseErrorNoTag(str, nsGkAtoms::scriptlevel, aBuilder.Document());
    449      }
    450    }
    451  }
    452 
    453  // mathsize
    454  // https://w3c.github.io/mathml-core/#dfn-mathsize
    455  value = aBuilder.GetAttr(nsGkAtoms::mathsize);
    456  if (value &&
    457      (value->Type() == nsAttrValue::eString ||
    458       value->Type() == nsAttrValue::eAtom) &&
    459      !aBuilder.PropertyIsSet(eCSSProperty_font_size)) {
    460    nsString str(nsAttrValueOrString(value).String());
    461    nsCSSValue fontSize;
    462    ParseNumericValue(str, fontSize, 0, nullptr);
    463    if (fontSize.GetUnit() == eCSSUnit_Percent) {
    464      aBuilder.SetPercentValue(eCSSProperty_font_size,
    465                               fontSize.GetPercentValue());
    466    } else if (fontSize.GetUnit() != eCSSUnit_Null) {
    467      aBuilder.SetLengthValue(eCSSProperty_font_size, fontSize);
    468    }
    469  }
    470 
    471  if (!StaticPrefs::mathml_legacy_mathvariant_attribute_disabled()) {
    472    // mathvariant
    473    //
    474    // "Specifies the logical class of the token. Note that this class is more
    475    // than styling, it typically conveys semantic intent;"
    476    //
    477    // values: "normal" | "bold" | "italic" | "bold-italic" | "double-struck" |
    478    // "bold-fraktur" | "script" | "bold-script" | "fraktur" | "sans-serif" |
    479    // "bold-sans-serif" | "sans-serif-italic" | "sans-serif-bold-italic" |
    480    // "monospace" | "initial" | "tailed" | "looped" | "stretched"
    481    // default: normal (except on <mi>)
    482    //
    483    value = aBuilder.GetAttr(nsGkAtoms::mathvariant);
    484    if (value &&
    485        (value->Type() == nsAttrValue::eString ||
    486         value->Type() == nsAttrValue::eAtom) &&
    487        !aBuilder.PropertyIsSet(eCSSProperty__moz_math_variant)) {
    488      nsString str(nsAttrValueOrString(value).String());
    489      str.CompressWhitespace();
    490 
    491      // Instead of a big table that holds all sizes, store a compressed version
    492      // with offset, taking advantage of common suffixes.
    493      //
    494      // naive approach:
    495      //     sizeof(char_table)
    496      //   = |size| x |max-length|
    497      //   = 19 x 23
    498      //   = 437
    499      //
    500      // offset approach:
    501      //     sizeof(offset_table) + sizeof(compressed_table)
    502      //   = |size| x |sizeof(uint8_t)| + 151
    503      //   = 19 x 1 + 151
    504      //   = 170
    505 
    506      static constexpr const char compressed_sizes[] =
    507          "normal\0"
    508          "bold\0"
    509          "bold-script\0"
    510          "double-struck\0"
    511          "bold-fraktur\0"
    512          "bold-sans-serif\0"
    513          "sans-serif-italic\0"
    514          "sans-serif-bold-italic\0"
    515          "monospace\0"
    516          "initial\0"
    517          "tailed\0"
    518          "looped\0"
    519          "stretched\0";
    520 
    521      static constexpr uint8_t value_indices[] = {
    522          cmemmemi("normal", compressed_sizes),
    523          cmemmemi("bold", compressed_sizes),
    524          cmemmemi("italic", compressed_sizes),
    525          cmemmemi("bold-italic", compressed_sizes),
    526          cmemmemi("script", compressed_sizes),
    527          cmemmemi("bold-script", compressed_sizes),
    528          cmemmemi("fraktur", compressed_sizes),
    529          cmemmemi("double-struck", compressed_sizes),
    530          cmemmemi("bold-fraktur", compressed_sizes),
    531          cmemmemi("sans-serif", compressed_sizes),
    532          cmemmemi("bold-sans-serif", compressed_sizes),
    533          cmemmemi("sans-serif-italic", compressed_sizes),
    534          cmemmemi("sans-serif-bold-italic", compressed_sizes),
    535          cmemmemi("monospace", compressed_sizes),
    536          cmemmemi("initial", compressed_sizes),
    537          cmemmemi("tailed", compressed_sizes),
    538          cmemmemi("looped", compressed_sizes),
    539          cmemmemi("stretched", compressed_sizes),
    540      };
    541 
    542      for (size_t i = 0; i < std::size(value_indices); ++i) {
    543        if (str.LowerCaseEqualsASCII(&compressed_sizes[value_indices[i]])) {
    544          // Convert the index to an enum. We skip the "none" style thus the
    545          // + 1.
    546          StyleMathVariant value = (StyleMathVariant)(i + 1);
    547          if (value != StyleMathVariant::Normal) {
    548            // Warn about deprecated mathvariant attribute values. Strictly
    549            // speaking, we should also warn about mathvariant="normal" if the
    550            // element is not an <mi>. However this would require exposing the
    551            // tag name via aBuilder. Moreover, this use case is actually to
    552            // revert the effect of a non-normal mathvariant value on an
    553            // ancestor element, which should consequently have already
    554            // triggered a warning.
    555            AutoTArray<nsString, 1> params;
    556            params.AppendElement(str);
    557            aBuilder.Document().WarnOnceAbout(
    558                dom::DeprecatedOperations::eMathML_DeprecatedMathVariant, false,
    559                params);
    560          }
    561          aBuilder.SetKeywordValue(eCSSProperty__moz_math_variant, value);
    562          break;
    563        }
    564      }
    565    }
    566  }
    567 
    568  // mathbackground
    569  // https://w3c.github.io/mathml-core/#dfn-mathbackground
    570  value = aBuilder.GetAttr(nsGkAtoms::mathbackground);
    571  if (value) {
    572    nscolor color;
    573    if (value->GetColorValue(color)) {
    574      aBuilder.SetColorValueIfUnset(eCSSProperty_background_color, color);
    575    }
    576  }
    577 
    578  // mathcolor
    579  // https://w3c.github.io/mathml-core/#dfn-mathcolor
    580  value = aBuilder.GetAttr(nsGkAtoms::mathcolor);
    581  nscolor color;
    582  if (value && value->GetColorValue(color)) {
    583    aBuilder.SetColorValueIfUnset(eCSSProperty_color, color);
    584  }
    585 
    586  // dir
    587  // https://w3c.github.io/mathml-core/#dfn-dir
    588  value = aBuilder.GetAttr(nsGkAtoms::dir);
    589  if (value &&
    590      (value->Type() == nsAttrValue::eString ||
    591       value->Type() == nsAttrValue::eAtom) &&
    592      !aBuilder.PropertyIsSet(eCSSProperty_direction)) {
    593    nsString str(nsAttrValueOrString(value).String());
    594    static const char dirs[][4] = {"ltr", "rtl"};
    595    static const StyleDirection dirValues[std::size(dirs)] = {
    596        StyleDirection::Ltr, StyleDirection::Rtl};
    597    for (uint32_t i = 0; i < std::size(dirs); ++i) {
    598      if (str.LowerCaseEqualsASCII(dirs[i])) {
    599        aBuilder.SetKeywordValue(eCSSProperty_direction, dirValues[i]);
    600        break;
    601      }
    602    }
    603  }
    604 
    605  // displaystyle
    606  // https://mathml-refresh.github.io/mathml-core/#dfn-displaystyle
    607  value = aBuilder.GetAttr(nsGkAtoms::displaystyle);
    608  if (value &&
    609      (value->Type() == nsAttrValue::eString ||
    610       value->Type() == nsAttrValue::eAtom) &&
    611      !aBuilder.PropertyIsSet(eCSSProperty_math_style)) {
    612    nsString str(nsAttrValueOrString(value).String());
    613    static const char displaystyles[][6] = {"false", "true"};
    614    static const StyleMathStyle mathStyle[std::size(displaystyles)] = {
    615        StyleMathStyle::Compact, StyleMathStyle::Normal};
    616    for (uint32_t i = 0; i < std::size(displaystyles); ++i) {
    617      if (str.LowerCaseEqualsASCII(displaystyles[i])) {
    618        aBuilder.SetKeywordValue(eCSSProperty_math_style, mathStyle[i]);
    619        break;
    620      }
    621    }
    622  }
    623 }
    624 
    625 void MathMLElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
    626  Element::GetEventTargetParent(aVisitor);
    627 
    628  GetEventTargetParentForLinks(aVisitor);
    629 }
    630 
    631 nsresult MathMLElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
    632  return PostHandleEventForLinks(aVisitor);
    633 }
    634 
    635 NS_IMPL_ELEMENT_CLONE(MathMLElement)
    636 
    637 void MathMLElement::SetIncrementScriptLevel(bool aIncrementScriptLevel,
    638                                            bool aNotify) {
    639  NS_ASSERTION(aNotify, "We always notify!");
    640  if (aIncrementScriptLevel) {
    641    AddStates(ElementState::INCREMENT_SCRIPT_LEVEL);
    642  } else {
    643    RemoveStates(ElementState::INCREMENT_SCRIPT_LEVEL);
    644  }
    645 }
    646 
    647 int32_t MathMLElement::TabIndexDefault() { return IsLink() ? 0 : -1; }
    648 
    649 // XXX Bug 1586011: Share logic with other element classes.
    650 Focusable MathMLElement::IsFocusableWithoutStyle(IsFocusableFlags) {
    651  if (!IsInComposedDoc() || IsInDesignMode()) {
    652    // In designMode documents we only allow focusing the document.
    653    return {};
    654  }
    655 
    656  int32_t tabIndex = TabIndex();
    657  if (!IsLink()) {
    658    // If a tabindex is specified at all we're focusable
    659    if (GetTabIndexAttrValue().isSome()) {
    660      return {true, tabIndex};
    661    }
    662    return {};
    663  }
    664 
    665  if (!OwnerDoc()->LinkHandlingEnabled()) {
    666    return {};
    667  }
    668 
    669  // Links that are in an editable region should never be focusable, even if
    670  // they are in a contenteditable="false" region.
    671  if (nsContentUtils::IsNodeInEditableRegion(this)) {
    672    return {};
    673  }
    674 
    675  if (!FocusModel::IsTabFocusable(TabFocusableType::Links)) {
    676    tabIndex = -1;
    677  }
    678 
    679  return {true, tabIndex};
    680 }
    681 
    682 already_AddRefed<nsIURI> MathMLElement::GetHrefURI() const {
    683  // MathML href
    684  // The REC says: "When user agents encounter MathML elements with both href
    685  // and xlink:href attributes, the href attribute should take precedence."
    686  const nsAttrValue* href = mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None);
    687  if (!href) {
    688    return nullptr;
    689  }
    690  // Get absolute URI
    691  nsAutoString hrefStr;
    692  href->ToString(hrefStr);
    693  nsCOMPtr<nsIURI> hrefURI;
    694  nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(hrefURI), hrefStr,
    695                                            OwnerDoc(), GetBaseURI());
    696  return hrefURI.forget();
    697 }
    698 
    699 bool MathMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
    700  // The intent is to align MathML event attributes on HTML5, so the flag
    701  // EventNameType_HTML is used here.
    702  return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
    703 }
    704 
    705 void MathMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
    706                                  const nsAttrValue* aValue, bool aNotify) {
    707  if (aNamespaceID == kNameSpaceID_None) {
    708    if (!aValue && IsEventAttributeName(aName)) {
    709      if (EventListenerManager* manager = GetExistingListenerManager()) {
    710        manager->RemoveEventHandler(GetEventNameForAttr(aName));
    711      }
    712    }
    713  }
    714 
    715  return MathMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
    716 }
    717 
    718 void MathMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
    719                                 const nsAttrValue* aValue,
    720                                 const nsAttrValue* aOldValue,
    721                                 nsIPrincipal* aSubjectPrincipal,
    722                                 bool aNotify) {
    723  // It is important that this be done after the attribute is set/unset.
    724  // We will need the updated attribute value because notifying the document
    725  // that content states have changed will call IntrinsicState, which will try
    726  // to get updated information about the visitedness from Link.
    727  if (aName == nsGkAtoms::href && aNameSpaceID == kNameSpaceID_None) {
    728    Link::ResetLinkState(aNotify, aValue || Link::ElementHasHref());
    729  }
    730 
    731  if (aNameSpaceID == kNameSpaceID_None) {
    732    if (IsEventAttributeName(aName) && aValue) {
    733      MOZ_ASSERT(aValue->Type() == nsAttrValue::eString ||
    734                     aValue->Type() == nsAttrValue::eAtom,
    735                 "Expected string or atom value for script body");
    736      SetEventHandler(GetEventNameForAttr(aName),
    737                      nsAttrValueOrString(aValue).String());
    738    }
    739  }
    740 
    741  return MathMLElementBase::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue,
    742                                         aSubjectPrincipal, aNotify);
    743 }
    744 
    745 JSObject* MathMLElement::WrapNode(JSContext* aCx,
    746                                  JS::Handle<JSObject*> aGivenProto) {
    747  return MathMLElement_Binding::Wrap(aCx, this, aGivenProto);
    748 }