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 }