Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsStyleUtil.h"
7 #include "nsStyleConsts.h"
9 #include "nsIContent.h"
10 #include "nsCSSProps.h"
11 #include "nsRuleNode.h"
12 #include "nsROCSSPrimitiveValue.h"
13 #include "nsIContentPolicy.h"
14 #include "nsIContentSecurityPolicy.h"
15 #include "nsIURI.h"
17 using namespace mozilla;
19 //------------------------------------------------------------------------------
20 // Font Algorithm Code
21 //------------------------------------------------------------------------------
23 // Compare two language strings
24 bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue,
25 const nsAString& aSelectorValue,
26 const nsStringComparator& aComparator)
27 {
28 bool result;
29 uint32_t selectorLen = aSelectorValue.Length();
30 uint32_t attributeLen = aAttributeValue.Length();
31 if (selectorLen > attributeLen) {
32 result = false;
33 }
34 else {
35 nsAString::const_iterator iter;
36 if (selectorLen != attributeLen &&
37 *aAttributeValue.BeginReading(iter).advance(selectorLen) !=
38 char16_t('-')) {
39 // to match, the aAttributeValue must have a dash after the end of
40 // the aSelectorValue's text (unless the aSelectorValue and the
41 // aAttributeValue have the same text)
42 result = false;
43 }
44 else {
45 result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator);
46 }
47 }
48 return result;
49 }
51 void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString,
52 nsAString& aReturn,
53 char16_t quoteChar)
54 {
55 NS_PRECONDITION(quoteChar == '\'' || quoteChar == '"',
56 "CSS strings must be quoted with ' or \"");
57 aReturn.Append(quoteChar);
59 const char16_t* in = aString.BeginReading();
60 const char16_t* const end = aString.EndReading();
61 for (; in != end; in++) {
62 if (*in < 0x20 || (*in >= 0x7F && *in < 0xA0)) {
63 // Escape U+0000 through U+001F and U+007F through U+009F numerically.
64 aReturn.AppendPrintf("\\%hX ", *in);
65 } else {
66 if (*in == '"' || *in == '\'' || *in == '\\') {
67 // Escape backslash and quote characters symbolically.
68 // It's not technically necessary to escape the quote
69 // character that isn't being used to delimit the string,
70 // but we do it anyway because that makes testing simpler.
71 aReturn.Append(char16_t('\\'));
72 }
73 aReturn.Append(*in);
74 }
75 }
77 aReturn.Append(quoteChar);
78 }
80 /* static */ bool
81 nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn)
82 {
83 // The relevant parts of the CSS grammar are:
84 // ident [-]?{nmstart}{nmchar}*
85 // nmstart [_a-z]|{nonascii}|{escape}
86 // nmchar [_a-z0-9-]|{nonascii}|{escape}
87 // nonascii [^\0-\177]
88 // escape {unicode}|\\[^\n\r\f0-9a-f]
89 // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
90 // from http://www.w3.org/TR/CSS21/syndata.html#tokenization
92 const char16_t* in = aIdent.BeginReading();
93 const char16_t* const end = aIdent.EndReading();
95 if (in == end)
96 return true;
98 // A leading dash does not need to be escaped as long as it is not the
99 // *only* character in the identifier.
100 if (in + 1 != end && *in == '-') {
101 aReturn.Append(char16_t('-'));
102 ++in;
103 }
105 // Escape a digit at the start (including after a dash),
106 // numerically. If we didn't escape it numerically, it would get
107 // interpreted as a numeric escape for the wrong character.
108 // A second dash immediately after a leading dash must also be
109 // escaped, but this may be done symbolically.
110 if (in != end && (*in == '-' ||
111 ('0' <= *in && *in <= '9'))) {
112 if (*in == '-') {
113 aReturn.Append(char16_t('\\'));
114 aReturn.Append(char16_t('-'));
115 } else {
116 aReturn.AppendPrintf("\\%hX ", *in);
117 }
118 ++in;
119 }
121 for (; in != end; ++in) {
122 char16_t ch = *in;
123 if (ch == 0x00) {
124 return false;
125 }
126 if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) {
127 // Escape U+0000 through U+001F and U+007F through U+009F numerically.
128 aReturn.AppendPrintf("\\%hX ", *in);
129 } else {
130 // Escape ASCII non-identifier printables as a backslash plus
131 // the character.
132 if (ch < 0x7F &&
133 ch != '_' && ch != '-' &&
134 (ch < '0' || '9' < ch) &&
135 (ch < 'A' || 'Z' < ch) &&
136 (ch < 'a' || 'z' < ch)) {
137 aReturn.Append(char16_t('\\'));
138 }
139 aReturn.Append(ch);
140 }
141 }
142 return true;
143 }
145 /* static */ void
146 nsStyleUtil::AppendBitmaskCSSValue(nsCSSProperty aProperty,
147 int32_t aMaskedValue,
148 int32_t aFirstMask,
149 int32_t aLastMask,
150 nsAString& aResult)
151 {
152 for (int32_t mask = aFirstMask; mask <= aLastMask; mask <<= 1) {
153 if (mask & aMaskedValue) {
154 AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, mask),
155 aResult);
156 aMaskedValue &= ~mask;
157 if (aMaskedValue) { // more left
158 aResult.Append(char16_t(' '));
159 }
160 }
161 }
162 NS_ABORT_IF_FALSE(aMaskedValue == 0, "unexpected bit remaining in bitfield");
163 }
165 /* static */ void
166 nsStyleUtil::AppendAngleValue(const nsStyleCoord& aAngle, nsAString& aResult)
167 {
168 MOZ_ASSERT(aAngle.IsAngleValue(), "Should have angle value");
170 // Append number.
171 AppendCSSNumber(aAngle.GetAngleValue(), aResult);
173 // Append unit.
174 switch (aAngle.GetUnit()) {
175 case eStyleUnit_Degree: aResult.AppendLiteral("deg"); break;
176 case eStyleUnit_Grad: aResult.AppendLiteral("grad"); break;
177 case eStyleUnit_Radian: aResult.AppendLiteral("rad"); break;
178 case eStyleUnit_Turn: aResult.AppendLiteral("turn"); break;
179 default: NS_NOTREACHED("unrecognized angle unit");
180 }
181 }
183 /* static */ void
184 nsStyleUtil::AppendPaintOrderValue(uint8_t aValue,
185 nsAString& aResult)
186 {
187 static_assert
188 (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8,
189 "SVGStyleStruct::mPaintOrder and local variables not big enough");
191 if (aValue == NS_STYLE_PAINT_ORDER_NORMAL) {
192 aResult.AppendLiteral("normal");
193 return;
194 }
196 // Append the minimal value necessary for the given paint order.
197 static_assert(NS_STYLE_PAINT_ORDER_LAST_VALUE == 3,
198 "paint-order values added; check serialization");
200 // The following relies on the default order being the order of the
201 // constant values.
203 const uint8_t MASK = (1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1;
205 uint32_t lastPositionToSerialize = 0;
206 for (uint32_t position = NS_STYLE_PAINT_ORDER_LAST_VALUE - 1;
207 position > 0;
208 position--) {
209 uint8_t component =
210 (aValue >> (position * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
211 uint8_t earlierComponent =
212 (aValue >> ((position - 1) * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK;
213 if (component < earlierComponent) {
214 lastPositionToSerialize = position - 1;
215 break;
216 }
217 }
219 for (uint32_t position = 0; position <= lastPositionToSerialize; position++) {
220 if (position > 0) {
221 aResult.AppendLiteral(" ");
222 }
223 uint8_t component = aValue & MASK;
224 switch (component) {
225 case NS_STYLE_PAINT_ORDER_FILL:
226 aResult.AppendLiteral("fill");
227 break;
229 case NS_STYLE_PAINT_ORDER_STROKE:
230 aResult.AppendLiteral("stroke");
231 break;
233 case NS_STYLE_PAINT_ORDER_MARKERS:
234 aResult.AppendLiteral("markers");
235 break;
237 default:
238 NS_NOTREACHED("unexpected paint-order component value");
239 }
240 aValue >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
241 }
242 }
244 /* static */ void
245 nsStyleUtil::AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
246 nsAString& aResult)
247 {
248 for (uint32_t i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) {
249 const gfxFontFeature& feat = aFeatures[i];
251 if (i != 0) {
252 aResult.AppendLiteral(", ");
253 }
255 // output tag
256 char tag[7];
257 tag[0] = '"';
258 tag[1] = (feat.mTag >> 24) & 0xff;
259 tag[2] = (feat.mTag >> 16) & 0xff;
260 tag[3] = (feat.mTag >> 8) & 0xff;
261 tag[4] = feat.mTag & 0xff;
262 tag[5] = '"';
263 tag[6] = 0;
264 aResult.AppendASCII(tag);
266 // output value, if necessary
267 if (feat.mValue == 0) {
268 // 0 ==> off
269 aResult.AppendLiteral(" off");
270 } else if (feat.mValue > 1) {
271 aResult.AppendLiteral(" ");
272 aResult.AppendInt(feat.mValue);
273 }
274 // else, omit value if 1, implied by default
275 }
276 }
278 /* static */ void
279 nsStyleUtil::AppendFontFeatureSettings(const nsCSSValue& aSrc,
280 nsAString& aResult)
281 {
282 nsCSSUnit unit = aSrc.GetUnit();
284 if (unit == eCSSUnit_Normal) {
285 aResult.AppendLiteral("normal");
286 return;
287 }
289 NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
290 "improper value unit for font-feature-settings:");
292 nsTArray<gfxFontFeature> featureSettings;
293 nsRuleNode::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings);
294 AppendFontFeatureSettings(featureSettings, aResult);
295 }
297 /* static */ void
298 nsStyleUtil::GetFunctionalAlternatesName(int32_t aFeature,
299 nsAString& aFeatureName)
300 {
301 aFeatureName.Truncate();
302 nsCSSKeyword key =
303 nsCSSProps::ValueToKeywordEnum(aFeature,
304 nsCSSProps::kFontVariantAlternatesFuncsKTable);
306 NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "bad alternate feature type");
307 AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(key), aFeatureName);
308 }
310 /* static */ void
311 nsStyleUtil::SerializeFunctionalAlternates(
312 const nsTArray<gfxAlternateValue>& aAlternates,
313 nsAString& aResult)
314 {
315 nsAutoString funcName, funcParams;
316 uint32_t numValues = aAlternates.Length();
318 uint32_t feature = 0;
319 for (uint32_t i = 0; i < numValues; i++) {
320 const gfxAlternateValue& v = aAlternates.ElementAt(i);
321 if (feature != v.alternate) {
322 feature = v.alternate;
323 if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
324 if (!aResult.IsEmpty()) {
325 aResult.Append(char16_t(' '));
326 }
328 // append the previous functional value
329 aResult.Append(funcName);
330 aResult.Append(char16_t('('));
331 aResult.Append(funcParams);
332 aResult.Append(char16_t(')'));
333 }
335 // function name
336 GetFunctionalAlternatesName(v.alternate, funcName);
337 NS_ASSERTION(!funcName.IsEmpty(), "unknown property value name");
339 // function params
340 funcParams.Truncate();
341 AppendEscapedCSSIdent(v.value, funcParams);
342 } else {
343 if (!funcParams.IsEmpty()) {
344 funcParams.Append(NS_LITERAL_STRING(", "));
345 }
346 AppendEscapedCSSIdent(v.value, funcParams);
347 }
348 }
350 // append the previous functional value
351 if (!funcName.IsEmpty() && !funcParams.IsEmpty()) {
352 if (!aResult.IsEmpty()) {
353 aResult.Append(char16_t(' '));
354 }
356 aResult.Append(funcName);
357 aResult.Append(char16_t('('));
358 aResult.Append(funcParams);
359 aResult.Append(char16_t(')'));
360 }
361 }
363 /* static */ void
364 nsStyleUtil::ComputeFunctionalAlternates(const nsCSSValueList* aList,
365 nsTArray<gfxAlternateValue>& aAlternateValues)
366 {
367 gfxAlternateValue v;
369 aAlternateValues.Clear();
370 for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) {
371 // list contains function units
372 if (curr->mValue.GetUnit() != eCSSUnit_Function) {
373 continue;
374 }
376 // element 0 is the propval in ident form
377 const nsCSSValue::Array *func = curr->mValue.GetArrayValue();
379 // lookup propval
380 nsCSSKeyword key = func->Item(0).GetKeywordValue();
381 NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "unknown alternate property value");
383 int32_t alternate;
384 if (key == eCSSKeyword_UNKNOWN ||
385 !nsCSSProps::FindKeyword(key,
386 nsCSSProps::kFontVariantAlternatesFuncsKTable,
387 alternate)) {
388 NS_NOTREACHED("keyword not a font-variant-alternates value");
389 continue;
390 }
391 v.alternate = alternate;
393 // other elements are the idents associated with the propval
394 // append one alternate value for each one
395 uint32_t numElems = func->Count();
396 for (uint32_t i = 1; i < numElems; i++) {
397 const nsCSSValue& value = func->Item(i);
398 NS_ASSERTION(value.GetUnit() == eCSSUnit_Ident,
399 "weird unit found in variant alternate");
400 if (value.GetUnit() != eCSSUnit_Ident) {
401 continue;
402 }
403 value.GetStringValue(v.value);
404 aAlternateValues.AppendElement(v);
405 }
406 }
407 }
409 /* static */ float
410 nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha)
411 {
412 // Alpha values are expressed as decimals, so we should convert
413 // back, using as few decimal places as possible for
414 // round-tripping.
415 // First try two decimal places:
416 float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f;
417 if (FloatToColorComponent(rounded) != aAlpha) {
418 // Use three decimal places.
419 rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f;
420 }
421 return rounded;
422 }
424 /* static */ bool
425 nsStyleUtil::IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant,
426 bool aWhitespaceIsSignificant)
427 {
428 NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant,
429 "Nonsensical arguments");
431 bool isText = aChild->IsNodeOfType(nsINode::eTEXT);
433 if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) &&
434 !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) {
435 return true;
436 }
438 return aTextIsSignificant && isText && aChild->TextLength() != 0 &&
439 (aWhitespaceIsSignificant ||
440 !aChild->TextIsOnlyWhitespace());
441 }
443 /* static */ bool
444 nsStyleUtil::CSPAllowsInlineStyle(nsIContent* aContent,
445 nsIPrincipal* aPrincipal,
446 nsIURI* aSourceURI,
447 uint32_t aLineNumber,
448 const nsSubstring& aStyleText,
449 nsresult* aRv)
450 {
451 nsresult rv;
453 if (aRv) {
454 *aRv = NS_OK;
455 }
457 MOZ_ASSERT(!aContent || aContent->Tag() == nsGkAtoms::style,
458 "aContent passed to CSPAllowsInlineStyle "
459 "for an element that is not <style>");
461 nsCOMPtr<nsIContentSecurityPolicy> csp;
462 rv = aPrincipal->GetCsp(getter_AddRefs(csp));
464 if (NS_FAILED(rv)) {
465 if (aRv)
466 *aRv = rv;
467 return false;
468 }
470 if (!csp) {
471 // No CSP --> the style is allowed
472 return true;
473 }
475 // An inline style can be allowed because all inline styles are allowed,
476 // or else because it is whitelisted by a nonce-source or hash-source. This
477 // is a logical OR between whitelisting methods, so the allowInlineStyle
478 // outparam can be reused for each check as long as we stop checking as soon
479 // as it is set to true. This also optimizes performance by avoiding the
480 // overhead of unnecessary checks.
481 bool allowInlineStyle = true;
482 nsAutoTArray<unsigned short, 3> violations;
484 bool reportInlineViolation;
485 rv = csp->GetAllowsInlineStyle(&reportInlineViolation, &allowInlineStyle);
486 if (NS_FAILED(rv)) {
487 if (aRv)
488 *aRv = rv;
489 return false;
490 }
491 if (reportInlineViolation) {
492 violations.AppendElement(static_cast<unsigned short>(
493 nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE));
494 }
496 nsAutoString nonce;
497 if (!allowInlineStyle) {
498 // We can only find a nonce if aContent is provided
499 bool foundNonce = !!aContent &&
500 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
501 if (foundNonce) {
502 bool reportNonceViolation;
503 rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_STYLESHEET,
504 &reportNonceViolation, &allowInlineStyle);
505 if (NS_FAILED(rv)) {
506 if (aRv)
507 *aRv = rv;
508 return false;
509 }
511 if (reportNonceViolation) {
512 violations.AppendElement(static_cast<unsigned short>(
513 nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_STYLE));
514 }
515 }
516 }
518 if (!allowInlineStyle) {
519 bool reportHashViolation;
520 rv = csp->GetAllowsHash(aStyleText, nsIContentPolicy::TYPE_STYLESHEET,
521 &reportHashViolation, &allowInlineStyle);
522 if (NS_FAILED(rv)) {
523 if (aRv)
524 *aRv = rv;
525 return false;
526 }
527 if (reportHashViolation) {
528 violations.AppendElement(static_cast<unsigned short>(
529 nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_STYLE));
530 }
531 }
533 // What violation(s) should be reported?
534 //
535 // 1. If the style tag has a nonce attribute, and the nonce does not match
536 // the policy, report VIOLATION_TYPE_NONCE_STYLE.
537 // 2. If the policy has at least one hash-source, and the hashed contents of
538 // the style tag did not match any of them, report VIOLATION_TYPE_HASH_STYLE
539 // 3. Otherwise, report VIOLATION_TYPE_INLINE_STYLE if appropriate.
540 //
541 // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
542 // every VIOLATION_TYPE_NONCE_STYLE and VIOLATION_TYPE_HASH_STYLE are also
543 // VIOLATION_TYPE_INLINE_STYLE, but reporting the
544 // VIOLATION_TYPE_INLINE_STYLE is redundant and does not help the developer.
545 if (!violations.IsEmpty()) {
546 MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_STYLE,
547 "How did we get any violations without an initial inline style violation?");
548 // This inline style is not allowed by CSP, so report the violation
549 nsAutoCString asciiSpec;
550 aSourceURI->GetAsciiSpec(asciiSpec);
551 nsAutoString styleSample(aStyleText);
553 // cap the length of the style sample at 40 chars.
554 if (styleSample.Length() > 40) {
555 styleSample.Truncate(40);
556 styleSample.AppendLiteral("...");
557 }
559 for (uint32_t i = 0; i < violations.Length(); i++) {
560 // Skip reporting the redundant inline style violation if there are
561 // other (nonce and/or hash violations) as well.
562 if (i > 0 || violations.Length() == 1) {
563 csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
564 styleSample, aLineNumber, nonce, aStyleText);
565 }
566 }
567 }
569 if (!allowInlineStyle) {
570 NS_ASSERTION(!violations.IsEmpty(),
571 "CSP blocked inline style but is not reporting a violation");
572 // The inline style should be blocked.
573 return false;
574 }
575 // CSP allows inline styles.
576 return true;
577 }