michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsStyleUtil.h" michael@0: #include "nsStyleConsts.h" michael@0: michael@0: #include "nsIContent.h" michael@0: #include "nsCSSProps.h" michael@0: #include "nsRuleNode.h" michael@0: #include "nsROCSSPrimitiveValue.h" michael@0: #include "nsIContentPolicy.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsIURI.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: //------------------------------------------------------------------------------ michael@0: // Font Algorithm Code michael@0: //------------------------------------------------------------------------------ michael@0: michael@0: // Compare two language strings michael@0: bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue, michael@0: const nsAString& aSelectorValue, michael@0: const nsStringComparator& aComparator) michael@0: { michael@0: bool result; michael@0: uint32_t selectorLen = aSelectorValue.Length(); michael@0: uint32_t attributeLen = aAttributeValue.Length(); michael@0: if (selectorLen > attributeLen) { michael@0: result = false; michael@0: } michael@0: else { michael@0: nsAString::const_iterator iter; michael@0: if (selectorLen != attributeLen && michael@0: *aAttributeValue.BeginReading(iter).advance(selectorLen) != michael@0: char16_t('-')) { michael@0: // to match, the aAttributeValue must have a dash after the end of michael@0: // the aSelectorValue's text (unless the aSelectorValue and the michael@0: // aAttributeValue have the same text) michael@0: result = false; michael@0: } michael@0: else { michael@0: result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString, michael@0: nsAString& aReturn, michael@0: char16_t quoteChar) michael@0: { michael@0: NS_PRECONDITION(quoteChar == '\'' || quoteChar == '"', michael@0: "CSS strings must be quoted with ' or \""); michael@0: aReturn.Append(quoteChar); michael@0: michael@0: const char16_t* in = aString.BeginReading(); michael@0: const char16_t* const end = aString.EndReading(); michael@0: for (; in != end; in++) { michael@0: if (*in < 0x20 || (*in >= 0x7F && *in < 0xA0)) { michael@0: // Escape U+0000 through U+001F and U+007F through U+009F numerically. michael@0: aReturn.AppendPrintf("\\%hX ", *in); michael@0: } else { michael@0: if (*in == '"' || *in == '\'' || *in == '\\') { michael@0: // Escape backslash and quote characters symbolically. michael@0: // It's not technically necessary to escape the quote michael@0: // character that isn't being used to delimit the string, michael@0: // but we do it anyway because that makes testing simpler. michael@0: aReturn.Append(char16_t('\\')); michael@0: } michael@0: aReturn.Append(*in); michael@0: } michael@0: } michael@0: michael@0: aReturn.Append(quoteChar); michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn) michael@0: { michael@0: // The relevant parts of the CSS grammar are: michael@0: // ident [-]?{nmstart}{nmchar}* michael@0: // nmstart [_a-z]|{nonascii}|{escape} michael@0: // nmchar [_a-z0-9-]|{nonascii}|{escape} michael@0: // nonascii [^\0-\177] michael@0: // escape {unicode}|\\[^\n\r\f0-9a-f] michael@0: // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? michael@0: // from http://www.w3.org/TR/CSS21/syndata.html#tokenization michael@0: michael@0: const char16_t* in = aIdent.BeginReading(); michael@0: const char16_t* const end = aIdent.EndReading(); michael@0: michael@0: if (in == end) michael@0: return true; michael@0: michael@0: // A leading dash does not need to be escaped as long as it is not the michael@0: // *only* character in the identifier. michael@0: if (in + 1 != end && *in == '-') { michael@0: aReturn.Append(char16_t('-')); michael@0: ++in; michael@0: } michael@0: michael@0: // Escape a digit at the start (including after a dash), michael@0: // numerically. If we didn't escape it numerically, it would get michael@0: // interpreted as a numeric escape for the wrong character. michael@0: // A second dash immediately after a leading dash must also be michael@0: // escaped, but this may be done symbolically. michael@0: if (in != end && (*in == '-' || michael@0: ('0' <= *in && *in <= '9'))) { michael@0: if (*in == '-') { michael@0: aReturn.Append(char16_t('\\')); michael@0: aReturn.Append(char16_t('-')); michael@0: } else { michael@0: aReturn.AppendPrintf("\\%hX ", *in); michael@0: } michael@0: ++in; michael@0: } michael@0: michael@0: for (; in != end; ++in) { michael@0: char16_t ch = *in; michael@0: if (ch == 0x00) { michael@0: return false; michael@0: } michael@0: if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) { michael@0: // Escape U+0000 through U+001F and U+007F through U+009F numerically. michael@0: aReturn.AppendPrintf("\\%hX ", *in); michael@0: } else { michael@0: // Escape ASCII non-identifier printables as a backslash plus michael@0: // the character. michael@0: if (ch < 0x7F && michael@0: ch != '_' && ch != '-' && michael@0: (ch < '0' || '9' < ch) && michael@0: (ch < 'A' || 'Z' < ch) && michael@0: (ch < 'a' || 'z' < ch)) { michael@0: aReturn.Append(char16_t('\\')); michael@0: } michael@0: aReturn.Append(ch); michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::AppendBitmaskCSSValue(nsCSSProperty aProperty, michael@0: int32_t aMaskedValue, michael@0: int32_t aFirstMask, michael@0: int32_t aLastMask, michael@0: nsAString& aResult) michael@0: { michael@0: for (int32_t mask = aFirstMask; mask <= aLastMask; mask <<= 1) { michael@0: if (mask & aMaskedValue) { michael@0: AppendASCIItoUTF16(nsCSSProps::LookupPropertyValue(aProperty, mask), michael@0: aResult); michael@0: aMaskedValue &= ~mask; michael@0: if (aMaskedValue) { // more left michael@0: aResult.Append(char16_t(' ')); michael@0: } michael@0: } michael@0: } michael@0: NS_ABORT_IF_FALSE(aMaskedValue == 0, "unexpected bit remaining in bitfield"); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::AppendAngleValue(const nsStyleCoord& aAngle, nsAString& aResult) michael@0: { michael@0: MOZ_ASSERT(aAngle.IsAngleValue(), "Should have angle value"); michael@0: michael@0: // Append number. michael@0: AppendCSSNumber(aAngle.GetAngleValue(), aResult); michael@0: michael@0: // Append unit. michael@0: switch (aAngle.GetUnit()) { michael@0: case eStyleUnit_Degree: aResult.AppendLiteral("deg"); break; michael@0: case eStyleUnit_Grad: aResult.AppendLiteral("grad"); break; michael@0: case eStyleUnit_Radian: aResult.AppendLiteral("rad"); break; michael@0: case eStyleUnit_Turn: aResult.AppendLiteral("turn"); break; michael@0: default: NS_NOTREACHED("unrecognized angle unit"); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::AppendPaintOrderValue(uint8_t aValue, michael@0: nsAString& aResult) michael@0: { michael@0: static_assert michael@0: (NS_STYLE_PAINT_ORDER_BITWIDTH * NS_STYLE_PAINT_ORDER_LAST_VALUE <= 8, michael@0: "SVGStyleStruct::mPaintOrder and local variables not big enough"); michael@0: michael@0: if (aValue == NS_STYLE_PAINT_ORDER_NORMAL) { michael@0: aResult.AppendLiteral("normal"); michael@0: return; michael@0: } michael@0: michael@0: // Append the minimal value necessary for the given paint order. michael@0: static_assert(NS_STYLE_PAINT_ORDER_LAST_VALUE == 3, michael@0: "paint-order values added; check serialization"); michael@0: michael@0: // The following relies on the default order being the order of the michael@0: // constant values. michael@0: michael@0: const uint8_t MASK = (1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1; michael@0: michael@0: uint32_t lastPositionToSerialize = 0; michael@0: for (uint32_t position = NS_STYLE_PAINT_ORDER_LAST_VALUE - 1; michael@0: position > 0; michael@0: position--) { michael@0: uint8_t component = michael@0: (aValue >> (position * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK; michael@0: uint8_t earlierComponent = michael@0: (aValue >> ((position - 1) * NS_STYLE_PAINT_ORDER_BITWIDTH)) & MASK; michael@0: if (component < earlierComponent) { michael@0: lastPositionToSerialize = position - 1; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: for (uint32_t position = 0; position <= lastPositionToSerialize; position++) { michael@0: if (position > 0) { michael@0: aResult.AppendLiteral(" "); michael@0: } michael@0: uint8_t component = aValue & MASK; michael@0: switch (component) { michael@0: case NS_STYLE_PAINT_ORDER_FILL: michael@0: aResult.AppendLiteral("fill"); michael@0: break; michael@0: michael@0: case NS_STYLE_PAINT_ORDER_STROKE: michael@0: aResult.AppendLiteral("stroke"); michael@0: break; michael@0: michael@0: case NS_STYLE_PAINT_ORDER_MARKERS: michael@0: aResult.AppendLiteral("markers"); michael@0: break; michael@0: michael@0: default: michael@0: NS_NOTREACHED("unexpected paint-order component value"); michael@0: } michael@0: aValue >>= NS_STYLE_PAINT_ORDER_BITWIDTH; michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::AppendFontFeatureSettings(const nsTArray& aFeatures, michael@0: nsAString& aResult) michael@0: { michael@0: for (uint32_t i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) { michael@0: const gfxFontFeature& feat = aFeatures[i]; michael@0: michael@0: if (i != 0) { michael@0: aResult.AppendLiteral(", "); michael@0: } michael@0: michael@0: // output tag michael@0: char tag[7]; michael@0: tag[0] = '"'; michael@0: tag[1] = (feat.mTag >> 24) & 0xff; michael@0: tag[2] = (feat.mTag >> 16) & 0xff; michael@0: tag[3] = (feat.mTag >> 8) & 0xff; michael@0: tag[4] = feat.mTag & 0xff; michael@0: tag[5] = '"'; michael@0: tag[6] = 0; michael@0: aResult.AppendASCII(tag); michael@0: michael@0: // output value, if necessary michael@0: if (feat.mValue == 0) { michael@0: // 0 ==> off michael@0: aResult.AppendLiteral(" off"); michael@0: } else if (feat.mValue > 1) { michael@0: aResult.AppendLiteral(" "); michael@0: aResult.AppendInt(feat.mValue); michael@0: } michael@0: // else, omit value if 1, implied by default michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::AppendFontFeatureSettings(const nsCSSValue& aSrc, michael@0: nsAString& aResult) michael@0: { michael@0: nsCSSUnit unit = aSrc.GetUnit(); michael@0: michael@0: if (unit == eCSSUnit_Normal) { michael@0: aResult.AppendLiteral("normal"); michael@0: return; michael@0: } michael@0: michael@0: NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep, michael@0: "improper value unit for font-feature-settings:"); michael@0: michael@0: nsTArray featureSettings; michael@0: nsRuleNode::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings); michael@0: AppendFontFeatureSettings(featureSettings, aResult); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::GetFunctionalAlternatesName(int32_t aFeature, michael@0: nsAString& aFeatureName) michael@0: { michael@0: aFeatureName.Truncate(); michael@0: nsCSSKeyword key = michael@0: nsCSSProps::ValueToKeywordEnum(aFeature, michael@0: nsCSSProps::kFontVariantAlternatesFuncsKTable); michael@0: michael@0: NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "bad alternate feature type"); michael@0: AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(key), aFeatureName); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::SerializeFunctionalAlternates( michael@0: const nsTArray& aAlternates, michael@0: nsAString& aResult) michael@0: { michael@0: nsAutoString funcName, funcParams; michael@0: uint32_t numValues = aAlternates.Length(); michael@0: michael@0: uint32_t feature = 0; michael@0: for (uint32_t i = 0; i < numValues; i++) { michael@0: const gfxAlternateValue& v = aAlternates.ElementAt(i); michael@0: if (feature != v.alternate) { michael@0: feature = v.alternate; michael@0: if (!funcName.IsEmpty() && !funcParams.IsEmpty()) { michael@0: if (!aResult.IsEmpty()) { michael@0: aResult.Append(char16_t(' ')); michael@0: } michael@0: michael@0: // append the previous functional value michael@0: aResult.Append(funcName); michael@0: aResult.Append(char16_t('(')); michael@0: aResult.Append(funcParams); michael@0: aResult.Append(char16_t(')')); michael@0: } michael@0: michael@0: // function name michael@0: GetFunctionalAlternatesName(v.alternate, funcName); michael@0: NS_ASSERTION(!funcName.IsEmpty(), "unknown property value name"); michael@0: michael@0: // function params michael@0: funcParams.Truncate(); michael@0: AppendEscapedCSSIdent(v.value, funcParams); michael@0: } else { michael@0: if (!funcParams.IsEmpty()) { michael@0: funcParams.Append(NS_LITERAL_STRING(", ")); michael@0: } michael@0: AppendEscapedCSSIdent(v.value, funcParams); michael@0: } michael@0: } michael@0: michael@0: // append the previous functional value michael@0: if (!funcName.IsEmpty() && !funcParams.IsEmpty()) { michael@0: if (!aResult.IsEmpty()) { michael@0: aResult.Append(char16_t(' ')); michael@0: } michael@0: michael@0: aResult.Append(funcName); michael@0: aResult.Append(char16_t('(')); michael@0: aResult.Append(funcParams); michael@0: aResult.Append(char16_t(')')); michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: nsStyleUtil::ComputeFunctionalAlternates(const nsCSSValueList* aList, michael@0: nsTArray& aAlternateValues) michael@0: { michael@0: gfxAlternateValue v; michael@0: michael@0: aAlternateValues.Clear(); michael@0: for (const nsCSSValueList* curr = aList; curr != nullptr; curr = curr->mNext) { michael@0: // list contains function units michael@0: if (curr->mValue.GetUnit() != eCSSUnit_Function) { michael@0: continue; michael@0: } michael@0: michael@0: // element 0 is the propval in ident form michael@0: const nsCSSValue::Array *func = curr->mValue.GetArrayValue(); michael@0: michael@0: // lookup propval michael@0: nsCSSKeyword key = func->Item(0).GetKeywordValue(); michael@0: NS_ASSERTION(key != eCSSKeyword_UNKNOWN, "unknown alternate property value"); michael@0: michael@0: int32_t alternate; michael@0: if (key == eCSSKeyword_UNKNOWN || michael@0: !nsCSSProps::FindKeyword(key, michael@0: nsCSSProps::kFontVariantAlternatesFuncsKTable, michael@0: alternate)) { michael@0: NS_NOTREACHED("keyword not a font-variant-alternates value"); michael@0: continue; michael@0: } michael@0: v.alternate = alternate; michael@0: michael@0: // other elements are the idents associated with the propval michael@0: // append one alternate value for each one michael@0: uint32_t numElems = func->Count(); michael@0: for (uint32_t i = 1; i < numElems; i++) { michael@0: const nsCSSValue& value = func->Item(i); michael@0: NS_ASSERTION(value.GetUnit() == eCSSUnit_Ident, michael@0: "weird unit found in variant alternate"); michael@0: if (value.GetUnit() != eCSSUnit_Ident) { michael@0: continue; michael@0: } michael@0: value.GetStringValue(v.value); michael@0: aAlternateValues.AppendElement(v); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ float michael@0: nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha) michael@0: { michael@0: // Alpha values are expressed as decimals, so we should convert michael@0: // back, using as few decimal places as possible for michael@0: // round-tripping. michael@0: // First try two decimal places: michael@0: float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f; michael@0: if (FloatToColorComponent(rounded) != aAlpha) { michael@0: // Use three decimal places. michael@0: rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f; michael@0: } michael@0: return rounded; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsStyleUtil::IsSignificantChild(nsIContent* aChild, bool aTextIsSignificant, michael@0: bool aWhitespaceIsSignificant) michael@0: { michael@0: NS_ASSERTION(!aWhitespaceIsSignificant || aTextIsSignificant, michael@0: "Nonsensical arguments"); michael@0: michael@0: bool isText = aChild->IsNodeOfType(nsINode::eTEXT); michael@0: michael@0: if (!isText && !aChild->IsNodeOfType(nsINode::eCOMMENT) && michael@0: !aChild->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { michael@0: return true; michael@0: } michael@0: michael@0: return aTextIsSignificant && isText && aChild->TextLength() != 0 && michael@0: (aWhitespaceIsSignificant || michael@0: !aChild->TextIsOnlyWhitespace()); michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsStyleUtil::CSPAllowsInlineStyle(nsIContent* aContent, michael@0: nsIPrincipal* aPrincipal, michael@0: nsIURI* aSourceURI, michael@0: uint32_t aLineNumber, michael@0: const nsSubstring& aStyleText, michael@0: nsresult* aRv) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (aRv) { michael@0: *aRv = NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(!aContent || aContent->Tag() == nsGkAtoms::style, michael@0: "aContent passed to CSPAllowsInlineStyle " michael@0: "for an element that is not