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 "nsStyleUtil.h"
8 #include "nsStyleConsts.h"
9 
10 #include "nsIContent.h"
11 #include "nsCSSProps.h"
12 #include "nsContentUtils.h"
13 #include "nsROCSSPrimitiveValue.h"
14 #include "nsStyleStruct.h"
15 #include "nsIContentPolicy.h"
16 #include "nsIContentSecurityPolicy.h"
17 #include "nsIURI.h"
18 #include "nsISupportsPrimitives.h"
19 #include "nsLayoutUtils.h"
20 #include "nsPrintfCString.h"
21 #include <cctype>
22 
23 using namespace mozilla;
24 
25 //------------------------------------------------------------------------------
26 // Font Algorithm Code
27 //------------------------------------------------------------------------------
28 
29 // Compare two language strings
DashMatchCompare(const nsAString & aAttributeValue,const nsAString & aSelectorValue,const nsStringComparator & aComparator)30 bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue,
31                                    const nsAString& aSelectorValue,
32                                    const nsStringComparator& aComparator) {
33   bool result;
34   uint32_t selectorLen = aSelectorValue.Length();
35   uint32_t attributeLen = aAttributeValue.Length();
36   if (selectorLen > attributeLen) {
37     result = false;
38   } else {
39     nsAString::const_iterator iter;
40     if (selectorLen != attributeLen &&
41         *aAttributeValue.BeginReading(iter).advance(selectorLen) !=
42             char16_t('-')) {
43       // to match, the aAttributeValue must have a dash after the end of
44       // the aSelectorValue's text (unless the aSelectorValue and the
45       // aAttributeValue have the same text)
46       result = false;
47     } else {
48       result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator);
49     }
50   }
51   return result;
52 }
53 
ValueIncludes(const nsAString & aValueList,const nsAString & aValue,const nsStringComparator & aComparator)54 bool nsStyleUtil::ValueIncludes(const nsAString& aValueList,
55                                 const nsAString& aValue,
56                                 const nsStringComparator& aComparator) {
57   const char16_t *p = aValueList.BeginReading(),
58                  *p_end = aValueList.EndReading();
59 
60   while (p < p_end) {
61     // skip leading space
62     while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) ++p;
63 
64     const char16_t* val_start = p;
65 
66     // look for space or end
67     while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) ++p;
68 
69     const char16_t* val_end = p;
70 
71     if (val_start < val_end &&
72         aValue.Equals(Substring(val_start, val_end), aComparator))
73       return true;
74 
75     ++p;  // we know the next character is not whitespace
76   }
77   return false;
78 }
79 
AppendEscapedCSSString(const nsAString & aString,nsAString & aReturn,char16_t quoteChar)80 void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString,
81                                          nsAString& aReturn,
82                                          char16_t quoteChar) {
83   NS_PRECONDITION(quoteChar == '\'' || quoteChar == '"',
84                   "CSS strings must be quoted with ' or \"");
85   aReturn.Append(quoteChar);
86 
87   const char16_t* in = aString.BeginReading();
88   const char16_t* const end = aString.EndReading();
89   for (; in != end; in++) {
90     if (*in < 0x20 || *in == 0x7F) {
91       // Escape U+0000 through U+001F and U+007F numerically.
92       aReturn.AppendPrintf("\\%x ", *in);
93     } else {
94       if (*in == '"' || *in == '\'' || *in == '\\') {
95         // Escape backslash and quote characters symbolically.
96         // It's not technically necessary to escape the quote
97         // character that isn't being used to delimit the string,
98         // but we do it anyway because that makes testing simpler.
99         aReturn.Append(char16_t('\\'));
100       }
101       aReturn.Append(*in);
102     }
103   }
104 
105   aReturn.Append(quoteChar);
106 }
107 
AppendEscapedCSSIdent(const nsAString & aIdent,nsAString & aReturn)108 /* static */ void nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent,
109                                                      nsAString& aReturn) {
110   // The relevant parts of the CSS grammar are:
111   //   ident    ([-]?{nmstart}|[-][-]){nmchar}*
112   //   nmstart  [_a-z]|{nonascii}|{escape}
113   //   nmchar   [_a-z0-9-]|{nonascii}|{escape}
114   //   nonascii [^\0-\177]
115   //   escape   {unicode}|\\[^\n\r\f0-9a-f]
116   //   unicode  \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
117   // from http://www.w3.org/TR/CSS21/syndata.html#tokenization but
118   // modified for idents by
119   // http://dev.w3.org/csswg/cssom/#serialize-an-identifier and
120   // http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
121 
122   const char16_t* in = aIdent.BeginReading();
123   const char16_t* const end = aIdent.EndReading();
124 
125   if (in == end) return;
126 
127   // A leading dash does not need to be escaped as long as it is not the
128   // *only* character in the identifier.
129   if (*in == '-') {
130     if (in + 1 == end) {
131       aReturn.Append(char16_t('\\'));
132       aReturn.Append(char16_t('-'));
133       return;
134     }
135 
136     aReturn.Append(char16_t('-'));
137     ++in;
138   }
139 
140   // Escape a digit at the start (including after a dash),
141   // numerically.  If we didn't escape it numerically, it would get
142   // interpreted as a numeric escape for the wrong character.
143   if (in != end && ('0' <= *in && *in <= '9')) {
144     aReturn.AppendPrintf("\\%x ", *in);
145     ++in;
146   }
147 
148   for (; in != end; ++in) {
149     char16_t ch = *in;
150     if (ch == 0x00) {
151       aReturn.Append(char16_t(0xFFFD));
152     } else if (ch < 0x20 || 0x7F == ch) {
153       // Escape U+0000 through U+001F and U+007F numerically.
154       aReturn.AppendPrintf("\\%x ", *in);
155     } else {
156       // Escape ASCII non-identifier printables as a backslash plus
157       // the character.
158       if (ch < 0x7F && ch != '_' && ch != '-' && (ch < '0' || '9' < ch) &&
159           (ch < 'A' || 'Z' < ch) && (ch < 'a' || 'z' < ch)) {
160         aReturn.Append(char16_t('\\'));
161       }
162       aReturn.Append(ch);
163     }
164   }
165 }
166 
167 // unquoted family names must be a sequence of idents
168 // so escape any parts that require escaping
AppendUnquotedFamilyName(const nsAString & aFamilyName,nsAString & aResult)169 static void AppendUnquotedFamilyName(const nsAString& aFamilyName,
170                                      nsAString& aResult) {
171   const char16_t *p, *p_end;
172   aFamilyName.BeginReading(p);
173   aFamilyName.EndReading(p_end);
174 
175   bool moreThanOne = false;
176   while (p < p_end) {
177     const char16_t* identStart = p;
178     while (++p != p_end && *p != ' ') /* nothing */
179       ;
180 
181     nsDependentSubstring ident(identStart, p);
182     if (!ident.IsEmpty()) {
183       if (moreThanOne) {
184         aResult.Append(' ');
185       }
186       nsStyleUtil::AppendEscapedCSSIdent(ident, aResult);
187       moreThanOne = true;
188     }
189 
190     ++p;
191   }
192 }
193 
AppendEscapedCSSFontFamilyList(const nsTArray<mozilla::FontFamilyName> & aNames,nsAString & aResult)194 /* static */ void nsStyleUtil::AppendEscapedCSSFontFamilyList(
195     const nsTArray<mozilla::FontFamilyName>& aNames, nsAString& aResult) {
196   size_t i, len = aNames.Length();
197   for (i = 0; i < len; i++) {
198     if (i != 0) {
199       aResult.AppendLiteral(", ");
200     }
201     const FontFamilyName& name = aNames[i];
202     switch (name.mType) {
203       case eFamily_named:
204         AppendUnquotedFamilyName(name.mName, aResult);
205         break;
206       case eFamily_named_quoted:
207         AppendEscapedCSSString(name.mName, aResult);
208         break;
209       default:
210         name.AppendToString(aResult);
211     }
212   }
213 }
214 
AppendEscapedCSSFontFamilyList(const mozilla::FontFamilyList & aFamilyList,nsAString & aResult)215 /* static */ void nsStyleUtil::AppendEscapedCSSFontFamilyList(
216     const mozilla::FontFamilyList& aFamilyList, nsAString& aResult) {
217   if (aFamilyList.IsEmpty()) {
218     FontFamilyType defaultGeneric = aFamilyList.GetDefaultFontType();
219     // If the font list is empty, then serialize the default generic.
220     // See also: gfxFontGroup::BuildFontList()
221     if (defaultGeneric != eFamily_none) {
222       FontFamilyName(defaultGeneric).AppendToString(aResult);
223     } else {
224       NS_NOTREACHED("No fonts to serialize");
225     }
226     return;
227   }
228 
229   AppendEscapedCSSFontFamilyList(aFamilyList.GetFontlist().get(), aResult);
230 }
231 
AppendBitmaskCSSValue(nsCSSPropertyID aProperty,int32_t aMaskedValue,int32_t aFirstMask,int32_t aLastMask,nsAString & aResult)232 /* static */ void nsStyleUtil::AppendBitmaskCSSValue(nsCSSPropertyID aProperty,
233                                                      int32_t aMaskedValue,
234                                                      int32_t aFirstMask,
235                                                      int32_t aLastMask,
236                                                      nsAString& aResult) {
237   for (int32_t mask = aFirstMask; mask <= aLastMask; mask <<= 1) {
238     if (mask & aMaskedValue) {
239       AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, mask),
240                          aResult);
241       aMaskedValue &= ~mask;
242       if (aMaskedValue) {  // more left
243         aResult.Append(char16_t(' '));
244       }
245     }
246   }
247   MOZ_ASSERT(aMaskedValue == 0, "unexpected bit remaining in bitfield");
248 }
249 
AppendAngleValue(const nsStyleCoord & aAngle,nsAString & aResult)250 /* static */ void nsStyleUtil::AppendAngleValue(const nsStyleCoord& aAngle,
251                                                 nsAString& aResult) {
252   MOZ_ASSERT(aAngle.IsAngleValue(), "Should have angle value");
253 
254   // Append number.
255   AppendCSSNumber(aAngle.GetAngleValue(), aResult);
256 
257   // Append unit.
258   switch (aAngle.GetUnit()) {
259     case eStyleUnit_Degree:
260       aResult.AppendLiteral("deg");
261       break;
262     case eStyleUnit_Grad:
263       aResult.AppendLiteral("grad");
264       break;
265     case eStyleUnit_Radian:
266       aResult.AppendLiteral("rad");
267       break;
268     case eStyleUnit_Turn:
269       aResult.AppendLiteral("turn");
270       break;
271     default:
272       NS_NOTREACHED("unrecognized angle unit");
273   }
274 }
275 
AppendPaintOrderValue(uint8_t aValue,nsAString & aResult)276 /* static */ void nsStyleUtil::AppendPaintOrderValue(uint8_t aValue,
277                                                      nsAString& aResult) {
278   static_assert(
279       NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
280       "SVGStyleStruct::mPaintOrder and local variables not big enough");
281 
282   if (aValue == NS_STYLE_PAINT_ORDER_NORMAL) {
283     aResult.AppendLiteral("normal");
284     return;
285   }
286 
287   // Append the minimal value necessary for the given paint order.
288   static_assert(NS_STYLE_PAINT_ORDER_LAST_VALUE == 3,
289                 "paint-order values added; check serialization");
290 
291   // The following relies on the default order being the order of the
292   // constant values.
293 
294   const uint8_t MASK = (1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1;
295 
296   uint32_t lastPositionToSerialize = 0;
297   for (uint32_t position = NS_STYLE_PAINT_ORDER_LAST_VALUE - 1; position > 0;
298        position--) {
299     uint8_t component =
300         (aValue >> (position * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
301     uint8_t earlierComponent =
302         (aValue >> ((position - 1) * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
303     if (component < earlierComponent) {
304       lastPositionToSerialize = position - 1;
305       break;
306     }
307   }
308 
309   for (uint32_t position = 0; position <= lastPositionToSerialize; position++) {
310     if (position > 0) {
311       aResult.Append(' ');
312     }
313     uint8_t component = aValue & MASK;
314     switch (component) {
315       case NS_STYLE_PAINT_ORDER_FILL:
316         aResult.AppendLiteral("fill");
317         break;
318 
319       case NS_STYLE_PAINT_ORDER_STROKE:
320         aResult.AppendLiteral("stroke");
321         break;
322 
323       case NS_STYLE_PAINT_ORDER_MARKERS:
324         aResult.AppendLiteral("markers");
325         break;
326 
327       default:
328         NS_NOTREACHED("unexpected paint-order component value");
329     }
330     aValue >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
331   }
332 }
333 
AppendFontTagAsString(uint32_t aTag,nsAString & aResult)334 /* static */ void nsStyleUtil::AppendFontTagAsString(uint32_t aTag,
335                                                      nsAString& aResult) {
336   // A font tag (for feature/variation settings) is a 4-char code interpreted
337   // as a bigendian 32-bit value and stored/processed as a uint32_t.
338   // To serialize it, we put the four bytes (which are all guaranteed to be
339   // printable ASCII values) into a string, starting from the high byte of the
340   // value, then append that to the result with CSS escaping and quotes.
341   nsAutoString tagStr;
342   for (int shiftAmount = 24; shiftAmount >= 0; shiftAmount -= 8) {
343     char c = (aTag >> shiftAmount) & 0xff;
344     MOZ_ASSERT(isascii(c) && isprint(c),
345                "parser should have restricted tag to printable ASCII chars");
346     tagStr.Append(c);
347   }
348   AppendEscapedCSSString(tagStr, aResult);
349 }
350 
AppendFontFeatureSettings(const nsTArray<gfxFontFeature> & aFeatures,nsAString & aResult)351 /* static */ void nsStyleUtil::AppendFontFeatureSettings(
352     const nsTArray<gfxFontFeature>& aFeatures, nsAString& aResult) {
353   for (uint32_t i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) {
354     const gfxFontFeature& feat = aFeatures[i];
355 
356     if (i != 0) {
357       aResult.AppendLiteral(", ");
358     }
359 
360     AppendFontTagAsString(feat.mTag, aResult);
361 
362     // omit value if it's 1, implied by default
363     if (feat.mValue != 1) {
364       aResult.Append(' ');
365       aResult.AppendInt(feat.mValue);
366     }
367   }
368 }
369 
AppendFontFeatureSettings(const nsCSSValue & aSrc,nsAString & aResult)370 /* static */ void nsStyleUtil::AppendFontFeatureSettings(const nsCSSValue& aSrc,
371                                                          nsAString& aResult) {
372   nsCSSUnit unit = aSrc.GetUnit();
373 
374   if (unit == eCSSUnit_Normal) {
375     aResult.AppendLiteral("normal");
376     return;
377   }
378 
379   NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
380                   "improper value unit for font-feature-settings:");
381 
382   nsTArray<gfxFontFeature> featureSettings;
383   nsLayoutUtils::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings);
384   AppendFontFeatureSettings(featureSettings, aResult);
385 }
386 
AppendFontVariationSettings(const nsTArray<gfxFontVariation> & aVariations,nsAString & aResult)387 /* static */ void nsStyleUtil::AppendFontVariationSettings(
388     const nsTArray<gfxFontVariation>& aVariations, nsAString& aResult) {
389   for (uint32_t i = 0, numVars = aVariations.Length(); i < numVars; i++) {
390     const gfxFontVariation& var = aVariations[i];
391 
392     if (i != 0) {
393       aResult.AppendLiteral(", ");
394     }
395 
396     // output tag
397     AppendFontTagAsString(var.mTag, aResult);
398 
399     // output value
400     aResult.Append(' ');
401     aResult.AppendFloat(var.mValue);
402   }
403 }
404 
AppendFontVariationSettings(const nsCSSValue & aSrc,nsAString & aResult)405 /* static */ void nsStyleUtil::AppendFontVariationSettings(
406     const nsCSSValue& aSrc, nsAString& aResult) {
407   nsCSSUnit unit = aSrc.GetUnit();
408 
409   if (unit == eCSSUnit_Normal) {
410     aResult.AppendLiteral("normal");
411     return;
412   }
413 
414   NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
415                   "improper value unit for font-variation-settings:");
416 
417   nsTArray<gfxFontVariation> variationSettings;
418   nsLayoutUtils::ComputeFontVariations(aSrc.GetPairListValue(),
419                                        variationSettings);
420   AppendFontVariationSettings(variationSettings, aResult);
421 }
422 
GetFunctionalAlternatesName(int32_t aFeature,nsAString & aFeatureName)423 /* static */ void nsStyleUtil::GetFunctionalAlternatesName(
424     int32_t aFeature, nsAString& aFeatureName) {
425   aFeatureName.Truncate();
426   nsCSSKeyword key = nsCSSProps::ValueToKeywordEnum(
427       aFeature, nsCSSProps::kFontVariantAlternatesFuncsKTable);
428 
429   NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "bad alternate feature type");
430   AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(key), aFeatureName);
431 }
432 
SerializeFunctionalAlternates(const nsTArray<gfxAlternateValue> & aAlternates,nsAString & aResult)433 /* static */ void nsStyleUtil::SerializeFunctionalAlternates(
434     const nsTArray<gfxAlternateValue>& aAlternates, nsAString& aResult) {
435   nsAutoString funcName, funcParams;
436   uint32_t numValues = aAlternates.Length();
437 
438   uint32_t feature = 0;
439   for (uint32_t i = 0; i < numValues; i++) {
440     const gfxAlternateValue& v = aAlternates.ElementAt(i);
441     if (feature != v.alternate) {
442       feature = v.alternate;
443       if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
444         if (!aResult.IsEmpty()) {
445           aResult.Append(char16_t(' '));
446         }
447 
448         // append the previous functional value
449         aResult.Append(funcName);
450         aResult.Append(char16_t('('));
451         aResult.Append(funcParams);
452         aResult.Append(char16_t(')'));
453       }
454 
455       // function name
456       GetFunctionalAlternatesName(v.alternate, funcName);
457       NS_ASSERTION(!funcName.IsEmpty(), "unknown property value name");
458 
459       // function params
460       funcParams.Truncate();
461       AppendEscapedCSSIdent(v.value, funcParams);
462     } else {
463       if (!funcParams.IsEmpty()) {
464         funcParams.AppendLiteral(", ");
465       }
466       AppendEscapedCSSIdent(v.value, funcParams);
467     }
468   }
469 
470   // append the previous functional value
471   if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
472     if (!aResult.IsEmpty()) {
473       aResult.Append(char16_t(' '));
474     }
475 
476     aResult.Append(funcName);
477     aResult.Append(char16_t('('));
478     aResult.Append(funcParams);
479     aResult.Append(char16_t(')'));
480   }
481 }
482 
ComputeFunctionalAlternates(const nsCSSValueList * aList,nsTArray<gfxAlternateValue> & aAlternateValues)483 /* static */ void nsStyleUtil::ComputeFunctionalAlternates(
484     const nsCSSValueList* aList,
485     nsTArray<gfxAlternateValue>& aAlternateValues) {
486   gfxAlternateValue v;
487 
488   aAlternateValues.Clear();
489   for (const nsCSSValueList* curr = aList; curr != nullptr;
490        curr = curr->mNext) {
491     // list contains function units
492     if (curr->mValue.GetUnit() != eCSSUnit_Function) {
493       continue;
494     }
495 
496     // element 0 is the propval in ident form
497     const nsCSSValue::Array* func = curr->mValue.GetArrayValue();
498 
499     // lookup propval
500     nsCSSKeyword key = func->Item(0).GetKeywordValue();
501     NS_ASSERTION(key != eCSSKeyword_UNKNOWN,
502                  "unknown alternate property value");
503 
504     int32_t alternate;
505     if (!nsCSSProps::FindKeyword(
506             key, nsCSSProps::kFontVariantAlternatesFuncsKTable, alternate)) {
507       NS_NOTREACHED("keyword not a font-variant-alternates value");
508       continue;
509     }
510     v.alternate = alternate;
511 
512     // other elements are the idents associated with the propval
513     // append one alternate value for each one
514     uint32_t numElems = func->Count();
515     for (uint32_t i = 1; i < numElems; i++) {
516       const nsCSSValue& value = func->Item(i);
517       NS_ASSERTION(value.GetUnit() == eCSSUnit_Ident,
518                    "weird unit found in variant alternate");
519       if (value.GetUnit() != eCSSUnit_Ident) {
520         continue;
521       }
522       value.GetStringValue(v.value);
523       aAlternateValues.AppendElement(v);
524     }
525   }
526 }
527 
AppendSerializedUnicodePoint(uint32_t aCode,nsACString & aBuf)528 static void AppendSerializedUnicodePoint(uint32_t aCode, nsACString& aBuf) {
529   aBuf.Append(nsPrintfCString("%0X", aCode));
530 }
531 
532 // A unicode-range: descriptor is represented as an array of integers,
533 // to be interpreted as a sequence of pairs: min max min max ...
534 // It is in source order.  (Possibly it should be sorted and overlaps
535 // consolidated, but right now we don't do that.)
AppendUnicodeRange(const nsCSSValue & aValue,nsAString & aResult)536 /* static */ void nsStyleUtil::AppendUnicodeRange(const nsCSSValue& aValue,
537                                                   nsAString& aResult) {
538   NS_PRECONDITION(
539       aValue.GetUnit() == eCSSUnit_Null || aValue.GetUnit() == eCSSUnit_Array,
540       "improper value unit for unicode-range:");
541   aResult.Truncate();
542   if (aValue.GetUnit() != eCSSUnit_Array) return;
543 
544   nsCSSValue::Array const& sources = *aValue.GetArrayValue();
545   nsAutoCString buf;
546 
547   MOZ_ASSERT(sources.Count() % 2 == 0,
548              "odd number of entries in a unicode-range: array");
549 
550   for (uint32_t i = 0; i < sources.Count(); i += 2) {
551     uint32_t min = sources[i].GetIntValue();
552     uint32_t max = sources[i + 1].GetIntValue();
553 
554     // We don't try to replicate the U+XX?? notation.
555     buf.AppendLiteral("U+");
556     AppendSerializedUnicodePoint(min, buf);
557 
558     if (min != max) {
559       buf.Append('-');
560       AppendSerializedUnicodePoint(max, buf);
561     }
562     buf.AppendLiteral(", ");
563   }
564   buf.Truncate(buf.Length() - 2);  // remove the last comma-space
565   CopyASCIItoUTF16(buf, aResult);
566 }
567 
AppendSerializedFontSrc(const nsCSSValue & aValue,nsAString & aResult)568 /* static */ void nsStyleUtil::AppendSerializedFontSrc(const nsCSSValue& aValue,
569                                                        nsAString& aResult) {
570   // A src: descriptor is represented as an array value; each entry in
571   // the array can be eCSSUnit_URL, eCSSUnit_Local_Font, or
572   // eCSSUnit_Font_Format.  Blocks of eCSSUnit_Font_Format may appear
573   // only after one of the first two.  (css3-fonts only contemplates
574   // annotating URLs with formats, but we handle the general case.)
575 
576   NS_PRECONDITION(aValue.GetUnit() == eCSSUnit_Array,
577                   "improper value unit for src:");
578 
579   const nsCSSValue::Array& sources = *aValue.GetArrayValue();
580   size_t i = 0;
581 
582   while (i < sources.Count()) {
583     nsAutoString formats;
584 
585     if (sources[i].GetUnit() == eCSSUnit_URL) {
586       aResult.AppendLiteral("url(");
587       nsDependentString url(sources[i].GetOriginalURLValue());
588       nsStyleUtil::AppendEscapedCSSString(url, aResult);
589       aResult.Append(')');
590     } else if (sources[i].GetUnit() == eCSSUnit_Local_Font) {
591       aResult.AppendLiteral("local(");
592       nsDependentString local(sources[i].GetStringBufferValue());
593       nsStyleUtil::AppendEscapedCSSString(local, aResult);
594       aResult.Append(')');
595     } else {
596       NS_NOTREACHED("entry in src: descriptor with improper unit");
597       i++;
598       continue;
599     }
600 
601     i++;
602     formats.Truncate();
603     while (i < sources.Count() &&
604            sources[i].GetUnit() == eCSSUnit_Font_Format) {
605       formats.Append('"');
606       formats.Append(sources[i].GetStringBufferValue());
607       formats.AppendLiteral("\", ");
608       i++;
609     }
610     if (formats.Length() > 0) {
611       formats.Truncate(formats.Length() - 2);  // remove the last comma
612       aResult.AppendLiteral(" format(");
613       aResult.Append(formats);
614       aResult.Append(')');
615     }
616     aResult.AppendLiteral(", ");
617   }
618   aResult.Truncate(aResult.Length() - 2);  // remove the last comma-space
619 }
620 
AppendStepsTimingFunction(nsTimingFunction::Type aType,uint32_t aSteps,nsAString & aResult)621 /* static */ void nsStyleUtil::AppendStepsTimingFunction(
622     nsTimingFunction::Type aType, uint32_t aSteps, nsAString& aResult) {
623   MOZ_ASSERT(aType == nsTimingFunction::Type::StepStart ||
624              aType == nsTimingFunction::Type::StepEnd);
625 
626   aResult.AppendLiteral("steps(");
627   aResult.AppendInt(aSteps);
628   if (aType == nsTimingFunction::Type::StepStart) {
629     aResult.AppendLiteral(", start)");
630   } else {
631     aResult.AppendLiteral(")");
632   }
633 }
634 
AppendFramesTimingFunction(uint32_t aFrames,nsAString & aResult)635 /* static */ void nsStyleUtil::AppendFramesTimingFunction(uint32_t aFrames,
636                                                           nsAString& aResult) {
637   aResult.AppendLiteral("frames(");
638   aResult.AppendInt(aFrames);
639   aResult.AppendLiteral(")");
640 }
641 
AppendCubicBezierTimingFunction(float aX1,float aY1,float aX2,float aY2,nsAString & aResult)642 /* static */ void nsStyleUtil::AppendCubicBezierTimingFunction(
643     float aX1, float aY1, float aX2, float aY2, nsAString& aResult) {
644   // set the value from the cubic-bezier control points
645   // (We could try to regenerate the keywords if we want.)
646   aResult.AppendLiteral("cubic-bezier(");
647   aResult.AppendFloat(aX1);
648   aResult.AppendLiteral(", ");
649   aResult.AppendFloat(aY1);
650   aResult.AppendLiteral(", ");
651   aResult.AppendFloat(aX2);
652   aResult.AppendLiteral(", ");
653   aResult.AppendFloat(aY2);
654   aResult.Append(')');
655 }
656 
AppendCubicBezierKeywordTimingFunction(nsTimingFunction::Type aType,nsAString & aResult)657 /* static */ void nsStyleUtil::AppendCubicBezierKeywordTimingFunction(
658     nsTimingFunction::Type aType, nsAString& aResult) {
659   switch (aType) {
660     case nsTimingFunction::Type::Ease:
661     case nsTimingFunction::Type::Linear:
662     case nsTimingFunction::Type::EaseIn:
663     case nsTimingFunction::Type::EaseOut:
664     case nsTimingFunction::Type::EaseInOut: {
665       nsCSSKeyword keyword = nsCSSProps::ValueToKeywordEnum(
666           static_cast<int32_t>(aType),
667           nsCSSProps::kTransitionTimingFunctionKTable);
668       AppendASCIItoUTF16(nsCSSKeywords::GetStringValue(keyword), aResult);
669       break;
670     }
671     default:
672       MOZ_ASSERT_UNREACHABLE("unexpected aType");
673       break;
674   }
675 }
676 
ColorComponentToFloat(uint8_t aAlpha)677 /* static */ float nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha) {
678   // Alpha values are expressed as decimals, so we should convert
679   // back, using as few decimal places as possible for
680   // round-tripping.
681   // First try two decimal places:
682   float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f;
683   if (FloatToColorComponent(rounded) != aAlpha) {
684     // Use three decimal places.
685     rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f;
686   }
687   return rounded;
688 }
689 
IsSignificantChild(nsIContent * aChild,bool aWhitespaceIsSignificant)690 /* static */ bool nsStyleUtil::IsSignificantChild(
691     nsIContent* aChild, bool aWhitespaceIsSignificant) {
692   bool isText = aChild->IsNodeOfType(nsINode::eTEXT);
693 
694   if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) &&
695       !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
696     return true;
697   }
698 
699   return isText && aChild->TextLength() != 0 &&
700          (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace());
701 }
702 
ThreadSafeIsSignificantChild(const nsIContent * aChild,bool aWhitespaceIsSignificant)703 /* static */ bool nsStyleUtil::ThreadSafeIsSignificantChild(
704     const nsIContent* aChild, bool aWhitespaceIsSignificant) {
705   bool isText = aChild->IsNodeOfType(nsINode::eTEXT);
706 
707   if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) &&
708       !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
709     return true;
710   }
711 
712   return isText && aChild->TextLength() != 0 &&
713          (aWhitespaceIsSignificant ||
714           !aChild->ThreadSafeTextIsOnlyWhitespace());
715 }
716 
717 // For a replaced element whose concrete object size is no larger than the
718 // element's content-box, this method checks whether the given
719 // "object-position" coordinate might cause overflow in its dimension.
ObjectPositionCoordMightCauseOverflow(const Position::Coord & aCoord)720 static bool ObjectPositionCoordMightCauseOverflow(
721     const Position::Coord& aCoord) {
722   // Any nonzero length in "object-position" can push us to overflow
723   // (particularly if our concrete object size is exactly the same size as the
724   // replaced element's content-box).
725   if (aCoord.mLength != 0) {
726     return true;
727   }
728 
729   // Percentages are interpreted as a fraction of the extra space. So,
730   // percentages in the 0-100% range are safe, but values outside of that
731   // range could cause overflow.
732   if (aCoord.mHasPercent &&
733       (aCoord.mPercent < 0.0f || aCoord.mPercent > 1.0f)) {
734     return true;
735   }
736   return false;
737 }
738 
ObjectPropsMightCauseOverflow(const nsStylePosition * aStylePos)739 /* static */ bool nsStyleUtil::ObjectPropsMightCauseOverflow(
740     const nsStylePosition* aStylePos) {
741   auto objectFit = aStylePos->mObjectFit;
742 
743   // "object-fit: cover" & "object-fit: none" can give us a render rect that's
744   // larger than our container element's content-box.
745   if (objectFit == NS_STYLE_OBJECT_FIT_COVER ||
746       objectFit == NS_STYLE_OBJECT_FIT_NONE) {
747     return true;
748   }
749   // (All other object-fit values produce a concrete object size that's no
750   // larger than the constraint region.)
751 
752   // Check each of our "object-position" coords to see if it could cause
753   // overflow in its dimension:
754   const Position& objectPosistion = aStylePos->mObjectPosition;
755   if (ObjectPositionCoordMightCauseOverflow(objectPosistion.mXPosition) ||
756       ObjectPositionCoordMightCauseOverflow(objectPosistion.mYPosition)) {
757     return true;
758   }
759 
760   return false;
761 }
762 
CSPAllowsInlineStyle(Element * aElement,nsIPrincipal * aPrincipal,nsIPrincipal * aTriggeringPrincipal,nsIURI * aSourceURI,uint32_t aLineNumber,const nsAString & aStyleText,nsresult * aRv)763 /* static */ bool nsStyleUtil::CSPAllowsInlineStyle(
764     Element* aElement, nsIPrincipal* aPrincipal,
765     nsIPrincipal* aTriggeringPrincipal, nsIURI* aSourceURI,
766     uint32_t aLineNumber, const nsAString& aStyleText, nsresult* aRv) {
767   nsresult rv;
768 
769   if (aRv) {
770     *aRv = NS_OK;
771   }
772 
773   MOZ_ASSERT(!aElement || aElement->NodeInfo()->NameAtom() == nsGkAtoms::style,
774              "aElement passed to CSPAllowsInlineStyle "
775              "for an element that is not <style>");
776 
777   nsIPrincipal* principal = aPrincipal;
778   if (aTriggeringPrincipal &&
779       BasePrincipal::Cast(aTriggeringPrincipal)->OverridesCSP(aPrincipal)) {
780     principal = aTriggeringPrincipal;
781   }
782 
783   nsCOMPtr<nsIContentSecurityPolicy> csp;
784   rv = principal->GetCsp(getter_AddRefs(csp));
785 
786   if (NS_FAILED(rv)) {
787     if (aRv) *aRv = rv;
788     return false;
789   }
790 
791   if (!csp) {
792     // No CSP --> the style is allowed
793     return true;
794   }
795 
796   // query the nonce
797   nsAutoString nonce;
798   if (aElement) {
799     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
800   }
801 
802   nsCOMPtr<nsISupportsString> styleText(
803       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
804   if (styleText) {
805     styleText->SetData(aStyleText);
806   }
807 
808   bool allowInlineStyle = true;
809   rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_STYLESHEET, nonce,
810                             false,  // aParserCreated only applies to scripts
811                             styleText, aLineNumber, &allowInlineStyle);
812   NS_ENSURE_SUCCESS(rv, false);
813 
814   return allowInlineStyle;
815 }
816