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 "base/compiler_specific.h"
10 #include "mozilla/dom/BindContext.h"
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/EventListenerManager.h"
13 #include "mozilla/FontPropertyTypes.h"
14 #include "mozilla/StaticPrefs_mathml.h"
15 #include "mozilla/TextUtils.h"
16 #include "nsGkAtoms.h"
17 #include "nsIContentInlines.h"
18 #include "nsITableCellLayout.h" // for MAX_COLSPAN / MAX_ROWSPAN
19 #include "nsCSSValue.h"
20 #include "nsMappedAttributes.h"
21 #include "nsStyleConsts.h"
22 #include "mozilla/dom/Document.h"
23 #include "nsPresContext.h"
24 #include "mozAutoDocUpdate.h"
25 #include "nsIScriptError.h"
26 #include "nsContentUtils.h"
27 #include "nsIURI.h"
28
29 #include "mozilla/EventDispatcher.h"
30 #include "mozilla/EventStates.h"
31 #include "mozilla/MappedDeclarations.h"
32 #include "mozilla/dom/MathMLElementBinding.h"
33
34 using namespace mozilla;
35 using namespace mozilla::dom;
36
37 //----------------------------------------------------------------------
38 // nsISupports methods:
39
NS_IMPL_ISUPPORTS_INHERITED(MathMLElement,MathMLElementBase,Link)40 NS_IMPL_ISUPPORTS_INHERITED(MathMLElement, MathMLElementBase, Link)
41
42 static nsresult ReportLengthParseError(const nsString& aValue,
43 Document* aDocument) {
44 AutoTArray<nsString, 1> arg = {aValue};
45 return nsContentUtils::ReportToConsole(
46 nsIScriptError::errorFlag, "MathML"_ns, aDocument,
47 nsContentUtils::eMATHML_PROPERTIES, "LengthParsingError", arg);
48 }
49
ReportParseErrorNoTag(const nsString & aValue,nsAtom * aAtom,Document * aDocument)50 static nsresult ReportParseErrorNoTag(const nsString& aValue, nsAtom* aAtom,
51 Document* aDocument) {
52 AutoTArray<nsString, 2> argv = {aValue, nsDependentAtomString(aAtom)};
53 return nsContentUtils::ReportToConsole(
54 nsIScriptError::errorFlag, "MathML"_ns, aDocument,
55 nsContentUtils::eMATHML_PROPERTIES, "AttributeParsingErrorNoTag", argv);
56 }
57
MathMLElement(already_AddRefed<mozilla::dom::NodeInfo> & aNodeInfo)58 MathMLElement::MathMLElement(
59 already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
60 : MathMLElementBase(std::move(aNodeInfo)),
61 ALLOW_THIS_IN_INITIALIZER_LIST(Link(this)),
62 mIncrementScriptLevel(false) {}
63
MathMLElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)64 MathMLElement::MathMLElement(
65 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
66 : MathMLElementBase(std::move(aNodeInfo)),
67 ALLOW_THIS_IN_INITIALIZER_LIST(Link(this)),
68 mIncrementScriptLevel(false) {}
69
BindToTree(BindContext & aContext,nsINode & aParent)70 nsresult MathMLElement::BindToTree(BindContext& aContext, nsINode& aParent) {
71 Link::ResetLinkState(false, Link::ElementHasHref());
72
73 nsresult rv = MathMLElementBase::BindToTree(aContext, aParent);
74 NS_ENSURE_SUCCESS(rv, rv);
75
76 // FIXME(emilio): Probably should be composed, this uses all the other link
77 // infrastructure.
78 if (Document* doc = aContext.GetUncomposedDoc()) {
79 doc->RegisterPendingLinkUpdate(this);
80 }
81
82 // Set the bit in the document for telemetry.
83 if (Document* doc = aContext.GetComposedDoc()) {
84 doc->SetMathMLEnabled();
85 }
86
87 return rv;
88 }
89
UnbindFromTree(bool aNullParent)90 void MathMLElement::UnbindFromTree(bool aNullParent) {
91 // Without removing the link state we risk a dangling pointer
92 // in the mStyledLinks hashtable
93 Link::ResetLinkState(false, Link::ElementHasHref());
94
95 MathMLElementBase::UnbindFromTree(aNullParent);
96 }
97
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)98 bool MathMLElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
99 const nsAString& aValue,
100 nsIPrincipal* aMaybeScriptedPrincipal,
101 nsAttrValue& aResult) {
102 MOZ_ASSERT(IsMathMLElement());
103
104 if (aNamespaceID == kNameSpaceID_None) {
105 if (aAttribute == nsGkAtoms::color || aAttribute == nsGkAtoms::mathcolor_ ||
106 aAttribute == nsGkAtoms::background ||
107 aAttribute == nsGkAtoms::mathbackground_) {
108 return aResult.ParseColor(aValue);
109 }
110 if (aAttribute == nsGkAtoms::tabindex) {
111 return aResult.ParseIntValue(aValue);
112 }
113 if (mNodeInfo->Equals(nsGkAtoms::mtd_)) {
114 if (aAttribute == nsGkAtoms::columnspan_) {
115 aResult.ParseClampedNonNegativeInt(aValue, 1, 1, MAX_COLSPAN);
116 return true;
117 }
118 if (aAttribute == nsGkAtoms::rowspan) {
119 aResult.ParseClampedNonNegativeInt(aValue, 1, 0, MAX_ROWSPAN);
120 return true;
121 }
122 }
123 }
124
125 return MathMLElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue,
126 aMaybeScriptedPrincipal, aResult);
127 }
128
129 // https://mathml-refresh.github.io/mathml-core/#global-attributes
130 static Element::MappedAttributeEntry sGlobalAttributes[] = {
131 {nsGkAtoms::dir}, {nsGkAtoms::mathbackground_},
132 {nsGkAtoms::mathcolor_}, {nsGkAtoms::mathsize_},
133 {nsGkAtoms::mathvariant_}, {nsGkAtoms::scriptlevel_},
134 {nsGkAtoms::displaystyle_}, {nullptr}};
135
136 static Element::MappedAttributeEntry sDeprecatedStyleAttributes[] = {
137 {nsGkAtoms::background},
138 {nsGkAtoms::color},
139 {nsGkAtoms::fontfamily_},
140 {nsGkAtoms::fontsize_},
141 {nsGkAtoms::fontstyle_},
142 {nsGkAtoms::fontweight_},
143 {nullptr}};
144
IsAttributeMapped(const nsAtom * aAttribute) const145 bool MathMLElement::IsAttributeMapped(const nsAtom* aAttribute) const {
146 MOZ_ASSERT(IsMathMLElement());
147
148 static const MappedAttributeEntry* const globalMap[] = {sGlobalAttributes};
149 static const MappedAttributeEntry* const styleMap[] = {
150 sDeprecatedStyleAttributes};
151
152 return FindAttributeDependence(aAttribute, globalMap) ||
153 (!StaticPrefs::mathml_deprecated_style_attributes_disabled() &&
154 FindAttributeDependence(aAttribute, styleMap)) ||
155 (!StaticPrefs::mathml_scriptminsize_attribute_disabled() &&
156 aAttribute == nsGkAtoms::scriptminsize_) ||
157 (!StaticPrefs::mathml_scriptsizemultiplier_attribute_disabled() &&
158 aAttribute == nsGkAtoms::scriptsizemultiplier_) ||
159 (mNodeInfo->Equals(nsGkAtoms::mtable_) &&
160 aAttribute == nsGkAtoms::width);
161 }
162
GetAttributeMappingFunction() const163 nsMapRuleToAttributesFunc MathMLElement::GetAttributeMappingFunction() const {
164 // It doesn't really matter what our tag is here, because only attributes
165 // that satisfy IsAttributeMapped will be stored in the mapped attributes
166 // list and available to the mapping function
167 return &MapMathMLAttributesInto;
168 }
169
170 /* static */
ParseNamedSpaceValue(const nsString & aString,nsCSSValue & aCSSValue,uint32_t aFlags,const Document & aDocument)171 bool MathMLElement::ParseNamedSpaceValue(const nsString& aString,
172 nsCSSValue& aCSSValue, uint32_t aFlags,
173 const Document& aDocument) {
174 if (StaticPrefs::mathml_mathspace_names_disabled()) {
175 return false;
176 }
177 int32_t i = 0;
178 // See if it is one of the 'namedspace' (ranging -7/18em, -6/18, ... 7/18em)
179 if (aString.EqualsLiteral("veryverythinmathspace")) {
180 i = 1;
181 } else if (aString.EqualsLiteral("verythinmathspace")) {
182 i = 2;
183 } else if (aString.EqualsLiteral("thinmathspace")) {
184 i = 3;
185 } else if (aString.EqualsLiteral("mediummathspace")) {
186 i = 4;
187 } else if (aString.EqualsLiteral("thickmathspace")) {
188 i = 5;
189 } else if (aString.EqualsLiteral("verythickmathspace")) {
190 i = 6;
191 } else if (aString.EqualsLiteral("veryverythickmathspace")) {
192 i = 7;
193 } else if (aFlags & PARSE_ALLOW_NEGATIVE) {
194 if (aString.EqualsLiteral("negativeveryverythinmathspace")) {
195 i = -1;
196 } else if (aString.EqualsLiteral("negativeverythinmathspace")) {
197 i = -2;
198 } else if (aString.EqualsLiteral("negativethinmathspace")) {
199 i = -3;
200 } else if (aString.EqualsLiteral("negativemediummathspace")) {
201 i = -4;
202 } else if (aString.EqualsLiteral("negativethickmathspace")) {
203 i = -5;
204 } else if (aString.EqualsLiteral("negativeverythickmathspace")) {
205 i = -6;
206 } else if (aString.EqualsLiteral("negativeveryverythickmathspace")) {
207 i = -7;
208 }
209 }
210 if (0 != i) {
211 aDocument.WarnOnceAbout(
212 dom::DeprecatedOperations::eMathML_DeprecatedMathSpaceValue);
213 aCSSValue.SetFloatValue(float(i) / float(18), eCSSUnit_EM);
214 return true;
215 }
216
217 return false;
218 }
219
220 // The REC says:
221 //
222 // "Most presentation elements have attributes that accept values representing
223 // lengths to be used for size, spacing or similar properties. The syntax of a
224 // length is specified as
225 //
226 // number | number unit | namedspace
227 //
228 // There should be no space between the number and the unit of a length."
229 //
230 // "A trailing '%' represents a percent of the default value. The default
231 // value, or how it is obtained, is listed in the table of attributes for each
232 // element. [...] A number without a unit is intepreted as a multiple of the
233 // default value."
234 //
235 // "The possible units in MathML are:
236 //
237 // Unit Description
238 // em an em (font-relative unit traditionally used for horizontal lengths)
239 // ex an ex (font-relative unit traditionally used for vertical lengths)
240 // px pixels, or size of a pixel in the current display
241 // in inches (1 inch = 2.54 centimeters)
242 // cm centimeters
243 // mm millimeters
244 // pt points (1 point = 1/72 inch)
245 // pc picas (1 pica = 12 points)
246 // % percentage of default value"
247 //
248 // The numbers are defined that way:
249 // - unsigned-number: "a string of decimal digits with up to one decimal point
250 // (U+002E), representing a non-negative terminating decimal number (a type of
251 // rational number)"
252 // - number: "an optional prefix of '-' (U+002D), followed by an unsigned
253 // number, representing a terminating decimal number (a type of rational
254 // number)"
255 //
256 /* static */
257 // XXXfredw: Deprecate legacy MathML syntax and use the CSS parser instead.
258 // See https://github.com/mathml-refresh/mathml/issues/63
ParseNumericValue(const nsString & aString,nsCSSValue & aCSSValue,uint32_t aFlags,Document * aDocument)259 bool MathMLElement::ParseNumericValue(const nsString& aString,
260 nsCSSValue& aCSSValue, uint32_t aFlags,
261 Document* aDocument) {
262 nsAutoString str(aString);
263 str.CompressWhitespace(); // aString is const in this code...
264
265 int32_t stringLength = str.Length();
266 if (!stringLength) {
267 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
268 ReportLengthParseError(aString, aDocument);
269 }
270 return false;
271 }
272
273 if (aDocument && ParseNamedSpaceValue(str, aCSSValue, aFlags, *aDocument)) {
274 return true;
275 }
276
277 nsAutoString number, unit;
278
279 // see if the negative sign is there
280 int32_t i = 0;
281 char16_t c = str[0];
282 if (c == '-') {
283 number.Append(c);
284 i++;
285 }
286
287 // Gather up characters that make up the number
288 bool gotDot = false;
289 for (; i < stringLength; i++) {
290 c = str[i];
291 if (gotDot && c == '.') {
292 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
293 ReportLengthParseError(aString, aDocument);
294 }
295 return false; // two dots encountered
296 } else if (c == '.')
297 gotDot = true;
298 else if (!IsAsciiDigit(c)) {
299 str.Right(unit, stringLength - i);
300 // some authors leave blanks before the unit, but that shouldn't
301 // be allowed, so don't CompressWhitespace on 'unit'.
302 break;
303 }
304 number.Append(c);
305 }
306 if (gotDot && str[i - 1] == '.') {
307 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
308 ReportLengthParseError(aString, aDocument);
309 }
310 return false; // Number ending with a dot.
311 }
312
313 // Convert number to floating point
314 nsresult errorCode;
315 float floatValue = number.ToFloat(&errorCode);
316 if (NS_FAILED(errorCode)) {
317 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
318 ReportLengthParseError(aString, aDocument);
319 }
320 return false;
321 }
322 if (floatValue < 0 && !(aFlags & PARSE_ALLOW_NEGATIVE)) {
323 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
324 ReportLengthParseError(aString, aDocument);
325 }
326 return false;
327 }
328
329 nsCSSUnit cssUnit;
330 if (unit.IsEmpty()) {
331 // We are supposed to have a unit, but there isn't one.
332 // If the value is 0 we can just call it "pixels" otherwise
333 // this is illegal.
334 if (floatValue != 0.0) {
335 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
336 ReportLengthParseError(aString, aDocument);
337 }
338 return false;
339 }
340 cssUnit = eCSSUnit_Pixel;
341 } else if (unit.EqualsLiteral("%")) {
342 aCSSValue.SetPercentValue(floatValue / 100.0f);
343 return true;
344 } else if (unit.LowerCaseEqualsLiteral("em"))
345 cssUnit = eCSSUnit_EM;
346 else if (unit.LowerCaseEqualsLiteral("ex"))
347 cssUnit = eCSSUnit_XHeight;
348 else if (unit.LowerCaseEqualsLiteral("px"))
349 cssUnit = eCSSUnit_Pixel;
350 else if (unit.LowerCaseEqualsLiteral("in"))
351 cssUnit = eCSSUnit_Inch;
352 else if (unit.LowerCaseEqualsLiteral("cm"))
353 cssUnit = eCSSUnit_Centimeter;
354 else if (unit.LowerCaseEqualsLiteral("mm"))
355 cssUnit = eCSSUnit_Millimeter;
356 else if (unit.LowerCaseEqualsLiteral("pt"))
357 cssUnit = eCSSUnit_Point;
358 else if (unit.LowerCaseEqualsLiteral("pc"))
359 cssUnit = eCSSUnit_Pica;
360 else if (unit.LowerCaseEqualsLiteral("q"))
361 cssUnit = eCSSUnit_Quarter;
362 else { // unexpected unit
363 if (!(aFlags & PARSE_SUPPRESS_WARNINGS)) {
364 ReportLengthParseError(aString, aDocument);
365 }
366 return false;
367 }
368
369 aCSSValue.SetFloatValue(floatValue, cssUnit);
370 return true;
371 }
372
MapMathMLAttributesInto(const nsMappedAttributes * aAttributes,MappedDeclarations & aDecls)373 void MathMLElement::MapMathMLAttributesInto(
374 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
375 // scriptsizemultiplier
376 //
377 // "Specifies the multiplier to be used to adjust font size due to changes
378 // in scriptlevel.
379 //
380 // values: number
381 // default: 0.71
382 //
383 const nsAttrValue* value =
384 aAttributes->GetAttr(nsGkAtoms::scriptsizemultiplier_);
385 if (value && value->Type() == nsAttrValue::eString &&
386 !aDecls.PropertyIsSet(eCSSProperty__moz_script_size_multiplier)) {
387 aDecls.Document()->WarnOnceAbout(
388 dom::DeprecatedOperations::
389 eMathML_DeprecatedScriptsizemultiplierAttribute);
390 auto str = value->GetStringValue();
391 str.CompressWhitespace();
392 // MathML numbers can't have leading '+'
393 if (str.Length() > 0 && str.CharAt(0) != '+') {
394 nsresult errorCode;
395 float floatValue = str.ToFloat(&errorCode);
396 // Negative scriptsizemultipliers are not parsed
397 if (NS_SUCCEEDED(errorCode) && floatValue >= 0.0f) {
398 aDecls.SetNumberValue(eCSSProperty__moz_script_size_multiplier,
399 floatValue);
400 } else {
401 ReportParseErrorNoTag(str, nsGkAtoms::scriptsizemultiplier_,
402 aDecls.Document());
403 }
404 }
405 }
406
407 // scriptminsize
408 //
409 // "Specifies the minimum font size allowed due to changes in scriptlevel.
410 // Note that this does not limit the font size due to changes to mathsize."
411 //
412 // values: length
413 // default: 8pt
414 //
415 // We don't allow negative values.
416 // Unitless and percent values give a multiple of the default value.
417 //
418 value = aAttributes->GetAttr(nsGkAtoms::scriptminsize_);
419 if (value && value->Type() == nsAttrValue::eString &&
420 !aDecls.PropertyIsSet(eCSSProperty__moz_script_min_size)) {
421 aDecls.Document()->WarnOnceAbout(
422 dom::DeprecatedOperations::eMathML_DeprecatedScriptminsizeAttribute);
423 nsCSSValue scriptMinSize;
424 ParseNumericValue(value->GetStringValue(), scriptMinSize,
425 PARSE_ALLOW_UNITLESS | CONVERT_UNITLESS_TO_PERCENT,
426 aDecls.Document());
427
428 if (scriptMinSize.GetUnit() == eCSSUnit_Percent) {
429 scriptMinSize.SetFloatValue(8.0 * scriptMinSize.GetPercentValue(),
430 eCSSUnit_Point);
431 }
432 if (scriptMinSize.GetUnit() != eCSSUnit_Null) {
433 aDecls.SetLengthValue(eCSSProperty__moz_script_min_size, scriptMinSize);
434 }
435 }
436
437 // scriptlevel
438 //
439 // "Changes the scriptlevel in effect for the children. When the value is
440 // given without a sign, it sets scriptlevel to the specified value; when a
441 // sign is given, it increments ("+") or decrements ("-") the current
442 // value. (Note that large decrements can result in negative values of
443 // scriptlevel, but these values are considered legal.)"
444 //
445 // values: ( "+" | "-" )? unsigned-integer
446 // default: inherited
447 //
448 value = aAttributes->GetAttr(nsGkAtoms::scriptlevel_);
449 if (value && value->Type() == nsAttrValue::eString &&
450 !aDecls.PropertyIsSet(eCSSProperty_math_depth)) {
451 auto str = value->GetStringValue();
452 str.CompressWhitespace();
453 if (str.Length() > 0) {
454 nsresult errorCode;
455 int32_t intValue = str.ToInteger(&errorCode);
456 if (NS_SUCCEEDED(errorCode)) {
457 char16_t ch = str.CharAt(0);
458 bool isRelativeScriptLevel = (ch == '+' || ch == '-');
459 aDecls.SetMathDepthValue(intValue, isRelativeScriptLevel);
460 } else {
461 ReportParseErrorNoTag(str, nsGkAtoms::scriptlevel_, aDecls.Document());
462 }
463 }
464 }
465
466 // mathsize
467 //
468 // "Specifies the size to display the token content. The values 'small' and
469 // 'big' choose a size smaller or larger than the current font size, but
470 // leave the exact proportions unspecified; 'normal' is allowed for
471 // completeness, but since it is equivalent to '100%' or '1em', it has no
472 // effect."
473 //
474 // values: "small" | "normal" | "big" | length
475 // default: inherited
476 //
477 // fontsize
478 //
479 // "Specified the size for the token. Deprecated in favor of mathsize."
480 //
481 // values: length
482 // default: inherited
483 //
484 // In both cases, we don't allow negative values.
485 // Unitless values give a multiple of the default value.
486 //
487 bool parseSizeKeywords = !StaticPrefs::mathml_mathsize_names_disabled();
488 value = aAttributes->GetAttr(nsGkAtoms::mathsize_);
489 if (!value) {
490 parseSizeKeywords = false;
491 value = aAttributes->GetAttr(nsGkAtoms::fontsize_);
492 if (value) {
493 aDecls.Document()->WarnOnceAbout(
494 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
495 }
496 }
497 if (value && value->Type() == nsAttrValue::eString &&
498 !aDecls.PropertyIsSet(eCSSProperty_font_size)) {
499 auto str = value->GetStringValue();
500 nsCSSValue fontSize;
501 uint32_t flags = PARSE_ALLOW_UNITLESS | CONVERT_UNITLESS_TO_PERCENT;
502 if (parseSizeKeywords) {
503 // Do not warn for invalid value if mathsize keywords are accepted.
504 flags |= PARSE_SUPPRESS_WARNINGS;
505 }
506 if (!ParseNumericValue(str, fontSize, flags, nullptr) &&
507 parseSizeKeywords) {
508 static const char sizes[3][7] = {"small", "normal", "big"};
509 static const StyleFontSizeKeyword values[MOZ_ARRAY_LENGTH(sizes)] = {
510 StyleFontSizeKeyword::Small, StyleFontSizeKeyword::Medium,
511 StyleFontSizeKeyword::Large};
512 str.CompressWhitespace();
513 for (uint32_t i = 0; i < ArrayLength(sizes); ++i) {
514 if (str.EqualsASCII(sizes[i])) {
515 aDecls.Document()->WarnOnceAbout(
516 dom::DeprecatedOperations::eMathML_DeprecatedMathSizeValue);
517 aDecls.SetKeywordValue(eCSSProperty_font_size, values[i]);
518 break;
519 }
520 }
521 } else if (fontSize.GetUnit() == eCSSUnit_Percent) {
522 aDecls.SetPercentValue(eCSSProperty_font_size,
523 fontSize.GetPercentValue());
524 } else if (fontSize.GetUnit() != eCSSUnit_Null) {
525 aDecls.SetLengthValue(eCSSProperty_font_size, fontSize);
526 }
527 }
528
529 // fontfamily
530 //
531 // "Should be the name of a font that may be available to a MathML renderer,
532 // or a CSS font specification; See Section 6.5 Using CSS with MathML and
533 // CSS for more information. Deprecated in favor of mathvariant."
534 //
535 // values: string
536 //
537 value = aAttributes->GetAttr(nsGkAtoms::fontfamily_);
538 if (value) {
539 aDecls.Document()->WarnOnceAbout(
540 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
541 }
542 if (value && value->Type() == nsAttrValue::eString &&
543 !aDecls.PropertyIsSet(eCSSProperty_font_family)) {
544 aDecls.SetFontFamily(NS_ConvertUTF16toUTF8(value->GetStringValue()));
545 }
546
547 // fontstyle
548 //
549 // "Specified the font style to use for the token. Deprecated in favor of
550 // mathvariant."
551 //
552 // values: "normal" | "italic"
553 // default: normal (except on <mi>)
554 //
555 // Note that the font-style property is reset in layout/style/ when
556 // -moz-math-variant is specified.
557 value = aAttributes->GetAttr(nsGkAtoms::fontstyle_);
558 if (value) {
559 aDecls.Document()->WarnOnceAbout(
560 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
561 if (value->Type() == nsAttrValue::eString &&
562 !aDecls.PropertyIsSet(eCSSProperty_font_style)) {
563 auto str = value->GetStringValue();
564 str.CompressWhitespace();
565 // FIXME(emilio): This should use FontSlantStyle or what not. Or even
566 // better, it looks deprecated since forever, we should just kill it.
567 if (str.EqualsASCII("normal")) {
568 aDecls.SetKeywordValue(eCSSProperty_font_style, NS_FONT_STYLE_NORMAL);
569 } else if (str.EqualsASCII("italic")) {
570 aDecls.SetKeywordValue(eCSSProperty_font_style, NS_FONT_STYLE_ITALIC);
571 }
572 }
573 }
574
575 // fontweight
576 //
577 // "Specified the font weight for the token. Deprecated in favor of
578 // mathvariant."
579 //
580 // values: "normal" | "bold"
581 // default: normal
582 //
583 // Note that the font-weight property is reset in layout/style/ when
584 // -moz-math-variant is specified.
585 value = aAttributes->GetAttr(nsGkAtoms::fontweight_);
586 if (value) {
587 aDecls.Document()->WarnOnceAbout(
588 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
589 if (value->Type() == nsAttrValue::eString &&
590 !aDecls.PropertyIsSet(eCSSProperty_font_weight)) {
591 auto str = value->GetStringValue();
592 str.CompressWhitespace();
593 if (str.EqualsASCII("normal")) {
594 aDecls.SetKeywordValue(eCSSProperty_font_weight,
595 FontWeight::Normal().ToFloat());
596 } else if (str.EqualsASCII("bold")) {
597 aDecls.SetKeywordValue(eCSSProperty_font_weight,
598 FontWeight::Bold().ToFloat());
599 }
600 }
601 }
602
603 // mathvariant
604 //
605 // "Specifies the logical class of the token. Note that this class is more
606 // than styling, it typically conveys semantic intent;"
607 //
608 // values: "normal" | "bold" | "italic" | "bold-italic" | "double-struck" |
609 // "bold-fraktur" | "script" | "bold-script" | "fraktur" | "sans-serif" |
610 // "bold-sans-serif" | "sans-serif-italic" | "sans-serif-bold-italic" |
611 // "monospace" | "initial" | "tailed" | "looped" | "stretched"
612 // default: normal (except on <mi>)
613 //
614 value = aAttributes->GetAttr(nsGkAtoms::mathvariant_);
615 if (value && value->Type() == nsAttrValue::eString &&
616 !aDecls.PropertyIsSet(eCSSProperty__moz_math_variant)) {
617 auto str = value->GetStringValue();
618 str.CompressWhitespace();
619 static const char sizes[19][23] = {"normal",
620 "bold",
621 "italic",
622 "bold-italic",
623 "script",
624 "bold-script",
625 "fraktur",
626 "double-struck",
627 "bold-fraktur",
628 "sans-serif",
629 "bold-sans-serif",
630 "sans-serif-italic",
631 "sans-serif-bold-italic",
632 "monospace",
633 "initial",
634 "tailed",
635 "looped",
636 "stretched"};
637 static const int32_t values[MOZ_ARRAY_LENGTH(sizes)] = {
638 NS_MATHML_MATHVARIANT_NORMAL,
639 NS_MATHML_MATHVARIANT_BOLD,
640 NS_MATHML_MATHVARIANT_ITALIC,
641 NS_MATHML_MATHVARIANT_BOLD_ITALIC,
642 NS_MATHML_MATHVARIANT_SCRIPT,
643 NS_MATHML_MATHVARIANT_BOLD_SCRIPT,
644 NS_MATHML_MATHVARIANT_FRAKTUR,
645 NS_MATHML_MATHVARIANT_DOUBLE_STRUCK,
646 NS_MATHML_MATHVARIANT_BOLD_FRAKTUR,
647 NS_MATHML_MATHVARIANT_SANS_SERIF,
648 NS_MATHML_MATHVARIANT_BOLD_SANS_SERIF,
649 NS_MATHML_MATHVARIANT_SANS_SERIF_ITALIC,
650 NS_MATHML_MATHVARIANT_SANS_SERIF_BOLD_ITALIC,
651 NS_MATHML_MATHVARIANT_MONOSPACE,
652 NS_MATHML_MATHVARIANT_INITIAL,
653 NS_MATHML_MATHVARIANT_TAILED,
654 NS_MATHML_MATHVARIANT_LOOPED,
655 NS_MATHML_MATHVARIANT_STRETCHED};
656 for (uint32_t i = 0; i < ArrayLength(sizes); ++i) {
657 if (str.LowerCaseEqualsASCII(sizes[i])) {
658 aDecls.SetKeywordValue(eCSSProperty__moz_math_variant, values[i]);
659 break;
660 }
661 }
662 }
663
664 // mathbackground
665 //
666 // "Specifies the background color to be used to fill in the bounding box of
667 // the element and its children. The default, 'transparent', lets the
668 // background color, if any, used in the current rendering context to show
669 // through."
670 //
671 // values: color | "transparent"
672 // default: "transparent"
673 //
674 // background
675 //
676 // "Specified the background color to be used to fill in the bounding box of
677 // the element and its children. Deprecated in favor of mathbackground."
678 //
679 // values: color | "transparent"
680 // default: "transparent"
681 //
682 value = aAttributes->GetAttr(nsGkAtoms::mathbackground_);
683 if (!value) {
684 value = aAttributes->GetAttr(nsGkAtoms::background);
685 if (value) {
686 aDecls.Document()->WarnOnceAbout(
687 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
688 }
689 }
690 if (value) {
691 nscolor color;
692 if (value->GetColorValue(color)) {
693 aDecls.SetColorValueIfUnset(eCSSProperty_background_color, color);
694 }
695 }
696
697 // mathcolor
698 //
699 // "Specifies the foreground color to use when drawing the components of this
700 // element, such as the content for token elements or any lines, surds, or
701 // other decorations. It also establishes the default mathcolor used for
702 // child elements when used on a layout element."
703 //
704 // values: color
705 // default: inherited
706 //
707 // color
708 //
709 // "Specified the color for the token. Deprecated in favor of mathcolor."
710 //
711 // values: color
712 // default: inherited
713 //
714 value = aAttributes->GetAttr(nsGkAtoms::mathcolor_);
715 if (!value) {
716 value = aAttributes->GetAttr(nsGkAtoms::color);
717 if (value) {
718 aDecls.Document()->WarnOnceAbout(
719 dom::DeprecatedOperations::eMathML_DeprecatedStyleAttribute);
720 }
721 }
722 nscolor color;
723 if (value && value->GetColorValue(color)) {
724 aDecls.SetColorValueIfUnset(eCSSProperty_color, color);
725 }
726
727 // width
728 //
729 // "Specifies the desired width of the entire table and is intended for
730 // visual user agents. When the value is a percentage value, the value is
731 // relative to the horizontal space a MathML renderer has available for the
732 // math element. When the value is "auto", the MathML renderer should
733 // calculate the table width from its contents using whatever layout
734 // algorithm it chooses. "
735 //
736 // values: "auto" | length
737 // default: auto
738 //
739 if (!aDecls.PropertyIsSet(eCSSProperty_width)) {
740 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width);
741 nsCSSValue width;
742 // This does not handle auto and unitless values
743 if (value && value->Type() == nsAttrValue::eString) {
744 ParseNumericValue(value->GetStringValue(), width, 0, aDecls.Document());
745 if (width.GetUnit() == eCSSUnit_Percent) {
746 aDecls.SetPercentValue(eCSSProperty_width, width.GetPercentValue());
747 } else if (width.GetUnit() != eCSSUnit_Null) {
748 aDecls.SetLengthValue(eCSSProperty_width, width);
749 }
750 }
751 }
752
753 // dir
754 //
755 // Overall Directionality of Mathematics Formulas:
756 // "The overall directionality for a formula, basically the direction of the
757 // Layout Schemata, is specified by the dir attribute on the containing math
758 // element (see Section 2.2 The Top-Level math Element). The default is ltr.
759 // [...] The overall directionality is usually set on the math, but may also
760 // be switched for individual subformula by using the dir attribute on mrow
761 // or mstyle elements."
762 //
763 // Bidirectional Layout in Token Elements:
764 // "Specifies the initial directionality for text within the token:
765 // ltr (Left To Right) or rtl (Right To Left). This attribute should only be
766 // needed in rare cases involving weak or neutral characters;
767 // see Section 3.1.5.1 Overall Directionality of Mathematics Formulas for
768 // further discussion. It has no effect on mspace."
769 //
770 // values: "ltr" | "rtl"
771 // default: inherited
772 //
773 value = aAttributes->GetAttr(nsGkAtoms::dir);
774 if (value && value->Type() == nsAttrValue::eString &&
775 !aDecls.PropertyIsSet(eCSSProperty_direction)) {
776 auto str = value->GetStringValue();
777 static const char dirs[][4] = {"ltr", "rtl"};
778 static const StyleDirection dirValues[MOZ_ARRAY_LENGTH(dirs)] = {
779 StyleDirection::Ltr, StyleDirection::Rtl};
780 for (uint32_t i = 0; i < ArrayLength(dirs); ++i) {
781 if (str.LowerCaseEqualsASCII(dirs[i])) {
782 aDecls.SetKeywordValue(eCSSProperty_direction, dirValues[i]);
783 break;
784 }
785 }
786 }
787
788 // displaystyle
789 // https://mathml-refresh.github.io/mathml-core/#dfn-displaystyle
790 value = aAttributes->GetAttr(nsGkAtoms::displaystyle_);
791 if (value && value->Type() == nsAttrValue::eString &&
792 !aDecls.PropertyIsSet(eCSSProperty_math_style)) {
793 auto str = value->GetStringValue();
794 static const char displaystyles[][6] = {"false", "true"};
795 static const uint8_t mathStyle[MOZ_ARRAY_LENGTH(displaystyles)] = {
796 NS_STYLE_MATH_STYLE_COMPACT, NS_STYLE_MATH_STYLE_NORMAL};
797 for (uint32_t i = 0; i < ArrayLength(displaystyles); ++i) {
798 if (str.LowerCaseEqualsASCII(displaystyles[i])) {
799 aDecls.SetKeywordValue(eCSSProperty_math_style, mathStyle[i]);
800 break;
801 }
802 }
803 }
804 }
805
GetEventTargetParent(EventChainPreVisitor & aVisitor)806 void MathMLElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
807 Element::GetEventTargetParent(aVisitor);
808
809 GetEventTargetParentForLinks(aVisitor);
810 }
811
PostHandleEvent(EventChainPostVisitor & aVisitor)812 nsresult MathMLElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
813 return PostHandleEventForLinks(aVisitor);
814 }
815
NS_IMPL_ELEMENT_CLONE(MathMLElement)816 NS_IMPL_ELEMENT_CLONE(MathMLElement)
817
818 EventStates MathMLElement::IntrinsicState() const {
819 return Link::LinkState() | MathMLElementBase::IntrinsicState() |
820 (mIncrementScriptLevel ? NS_EVENT_STATE_INCREMENT_SCRIPT_LEVEL
821 : EventStates());
822 }
823
IsNodeOfType(uint32_t aFlags) const824 bool MathMLElement::IsNodeOfType(uint32_t aFlags) const { return false; }
825
SetIncrementScriptLevel(bool aIncrementScriptLevel,bool aNotify)826 void MathMLElement::SetIncrementScriptLevel(bool aIncrementScriptLevel,
827 bool aNotify) {
828 if (aIncrementScriptLevel == mIncrementScriptLevel) return;
829 mIncrementScriptLevel = aIncrementScriptLevel;
830
831 NS_ASSERTION(aNotify, "We always notify!");
832
833 UpdateState(true);
834 }
835
TabIndexDefault()836 int32_t MathMLElement::TabIndexDefault() {
837 nsCOMPtr<nsIURI> uri;
838 return IsLink(getter_AddRefs(uri)) ? 0 : -1;
839 }
840
841 // XXX Bug 1586011: Share logic with other element classes.
IsFocusableInternal(int32_t * aTabIndex,bool aWithMouse)842 bool MathMLElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
843 Document* doc = GetComposedDoc();
844 if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
845 // In designMode documents we only allow focusing the document.
846 if (aTabIndex) {
847 *aTabIndex = -1;
848 }
849 return false;
850 }
851
852 int32_t tabIndex = TabIndex();
853 if (aTabIndex) {
854 *aTabIndex = tabIndex;
855 }
856
857 nsCOMPtr<nsIURI> uri;
858 if (!IsLink(getter_AddRefs(uri))) {
859 // If a tabindex is specified at all we're focusable
860 return GetTabIndexAttrValue().isSome();
861 }
862
863 if (!OwnerDoc()->LinkHandlingEnabled()) {
864 return false;
865 }
866
867 // Links that are in an editable region should never be focusable, even if
868 // they are in a contenteditable="false" region.
869 if (nsContentUtils::IsNodeInEditableRegion(this)) {
870 if (aTabIndex) {
871 *aTabIndex = -1;
872 }
873 return false;
874 }
875
876 if (aTabIndex && (sTabFocusModel & eTabFocus_linksMask) == 0) {
877 *aTabIndex = -1;
878 }
879
880 return true;
881 }
882
IsLink(nsIURI ** aURI) const883 bool MathMLElement::IsLink(nsIURI** aURI) const {
884 bool hasHref = false;
885 const nsAttrValue* href = mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_None);
886 if (href) {
887 // MathML href
888 // The REC says: "When user agents encounter MathML elements with both href
889 // and xlink:href attributes, the href attribute should take precedence."
890 hasHref = true;
891 } else if (!StaticPrefs::mathml_xlink_disabled()) {
892 // To be a clickable XLink for styling and interaction purposes, we require:
893 //
894 // xlink:href - must be set
895 // xlink:type - must be unset or set to "" or set to "simple"
896 // xlink:show - must be unset or set to "", "new" or "replace"
897 // xlink:actuate - must be unset or set to "" or "onRequest"
898 //
899 // For any other values, we're either not a *clickable* XLink, or the end
900 // result is poorly specified. Either way, we return false.
901
902 static Element::AttrValuesArray sTypeVals[] = {nsGkAtoms::_empty,
903 nsGkAtoms::simple, nullptr};
904
905 static Element::AttrValuesArray sShowVals[] = {
906 nsGkAtoms::_empty, nsGkAtoms::_new, nsGkAtoms::replace, nullptr};
907
908 static Element::AttrValuesArray sActuateVals[] = {
909 nsGkAtoms::_empty, nsGkAtoms::onRequest, nullptr};
910
911 // Optimization: check for href first for early return
912 href = mAttrs.GetAttr(nsGkAtoms::href, kNameSpaceID_XLink);
913 if (href &&
914 FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::type, sTypeVals,
915 eCaseMatters) != Element::ATTR_VALUE_NO_MATCH &&
916 FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show, sShowVals,
917 eCaseMatters) != Element::ATTR_VALUE_NO_MATCH &&
918 FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::actuate, sActuateVals,
919 eCaseMatters) != Element::ATTR_VALUE_NO_MATCH) {
920 OwnerDoc()->WarnOnceAbout(
921 dom::DeprecatedOperations::eMathML_DeprecatedXLinkAttribute);
922 hasHref = true;
923 }
924 }
925
926 if (hasHref) {
927 // Get absolute URI
928 nsAutoString hrefStr;
929 href->ToString(hrefStr);
930 nsContentUtils::NewURIWithDocumentCharset(aURI, hrefStr, OwnerDoc(),
931 GetBaseURI());
932 // must promise out param is non-null if we return true
933 return !!*aURI;
934 }
935
936 *aURI = nullptr;
937 return false;
938 }
939
GetLinkTarget(nsAString & aTarget)940 void MathMLElement::GetLinkTarget(nsAString& aTarget) {
941 if (StaticPrefs::mathml_xlink_disabled()) {
942 MathMLElementBase::GetLinkTarget(aTarget);
943 return;
944 }
945
946 const nsAttrValue* target =
947 mAttrs.GetAttr(nsGkAtoms::target, kNameSpaceID_XLink);
948 if (target) {
949 OwnerDoc()->WarnOnceAbout(
950 dom::DeprecatedOperations::eMathML_DeprecatedXLinkAttribute);
951 target->ToString(aTarget);
952 }
953
954 if (aTarget.IsEmpty()) {
955 static Element::AttrValuesArray sShowVals[] = {nsGkAtoms::_new,
956 nsGkAtoms::replace, nullptr};
957
958 bool hasDeprecatedShowAttribute = true;
959 switch (FindAttrValueIn(kNameSpaceID_XLink, nsGkAtoms::show, sShowVals,
960 eCaseMatters)) {
961 case ATTR_MISSING:
962 hasDeprecatedShowAttribute = false;
963 break;
964 case 0:
965 aTarget.AssignLiteral("_blank");
966 return;
967 case 1:
968 return;
969 }
970 if (hasDeprecatedShowAttribute) {
971 OwnerDoc()->WarnOnceAbout(
972 dom::DeprecatedOperations::eMathML_DeprecatedXLinkAttribute);
973 }
974 OwnerDoc()->GetBaseTarget(aTarget);
975 }
976 }
977
GetHrefURI() const978 already_AddRefed<nsIURI> MathMLElement::GetHrefURI() const {
979 nsCOMPtr<nsIURI> hrefURI;
980 return IsLink(getter_AddRefs(hrefURI)) ? hrefURI.forget() : nullptr;
981 }
982
983 // XXX Bug 1586014: Share logic with other element classes.
RecompileScriptEventListeners()984 void MathMLElement::RecompileScriptEventListeners() {
985 int32_t i, count = mAttrs.AttrCount();
986 for (i = 0; i < count; ++i) {
987 const nsAttrName* name = mAttrs.AttrNameAt(i);
988
989 // Eventlistenener-attributes are always in the null namespace
990 if (!name->IsAtom()) {
991 continue;
992 }
993
994 nsAtom* attr = name->Atom();
995 if (!IsEventAttributeName(attr)) {
996 continue;
997 }
998
999 nsAutoString value;
1000 GetAttr(kNameSpaceID_None, attr, value);
1001 SetEventHandler(GetEventNameForAttr(attr), value, true);
1002 }
1003 }
1004
IsEventAttributeNameInternal(nsAtom * aName)1005 bool MathMLElement::IsEventAttributeNameInternal(nsAtom* aName) {
1006 // The intent is to align MathML event attributes on HTML5, so the flag
1007 // EventNameType_HTML is used here.
1008 return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML);
1009 }
1010
BeforeSetAttr(int32_t aNamespaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)1011 nsresult MathMLElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
1012 const nsAttrValueOrString* aValue,
1013 bool aNotify) {
1014 if (aNamespaceID == kNameSpaceID_None) {
1015 if (!aValue && IsEventAttributeName(aName)) {
1016 if (EventListenerManager* manager = GetExistingListenerManager()) {
1017 manager->RemoveEventHandler(GetEventNameForAttr(aName));
1018 }
1019 }
1020 }
1021
1022 return MathMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
1023 }
1024
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)1025 nsresult MathMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1026 const nsAttrValue* aValue,
1027 const nsAttrValue* aOldValue,
1028 nsIPrincipal* aSubjectPrincipal,
1029 bool aNotify) {
1030 // It is important that this be done after the attribute is set/unset.
1031 // We will need the updated attribute value because notifying the document
1032 // that content states have changed will call IntrinsicState, which will try
1033 // to get updated information about the visitedness from Link.
1034 if (aName == nsGkAtoms::href && (aNameSpaceID == kNameSpaceID_None ||
1035 (!StaticPrefs::mathml_xlink_disabled() &&
1036 aNameSpaceID == kNameSpaceID_XLink))) {
1037 if (aValue && aNameSpaceID == kNameSpaceID_XLink) {
1038 OwnerDoc()->WarnOnceAbout(
1039 dom::DeprecatedOperations::eMathML_DeprecatedXLinkAttribute);
1040 }
1041 // Note: When unsetting href, there may still be another href since there
1042 // are 2 possible namespaces.
1043 Link::ResetLinkState(aNotify, aValue || Link::ElementHasHref());
1044 }
1045
1046 if (aNameSpaceID == kNameSpaceID_None) {
1047 if (IsEventAttributeName(aName) && aValue) {
1048 MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
1049 "Expected string value for script body");
1050 SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue());
1051 }
1052 }
1053
1054 return MathMLElementBase::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue,
1055 aSubjectPrincipal, aNotify);
1056 }
1057
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)1058 JSObject* MathMLElement::WrapNode(JSContext* aCx,
1059 JS::Handle<JSObject*> aGivenProto) {
1060 return MathMLElement_Binding::Wrap(aCx, this, aGivenProto);
1061 }
1062