/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsStyleUtil.h" #include "nsStyleConsts.h" #include "mozilla/dom/Document.h" #include "mozilla/ExpandedPrincipal.h" #include "mozilla/intl/MozLocaleBindings.h" #include "mozilla/intl/oxilangtag_ffi_generated.h" #include "mozilla/TextUtils.h" #include "nsIContent.h" #include "nsCSSProps.h" #include "nsContentUtils.h" #include "nsROCSSPrimitiveValue.h" #include "nsStyleStruct.h" #include "nsIContentPolicy.h" #include "nsIContentSecurityPolicy.h" #include "nsLayoutUtils.h" #include "nsPrintfCString.h" #include using namespace mozilla; //------------------------------------------------------------------------------ // Font Algorithm Code //------------------------------------------------------------------------------ // Compare two language strings bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue, const nsAString& aSelectorValue, const nsStringComparator& aComparator) { bool result; uint32_t selectorLen = aSelectorValue.Length(); uint32_t attributeLen = aAttributeValue.Length(); if (selectorLen > attributeLen) { result = false; } else { nsAString::const_iterator iter; if (selectorLen != attributeLen && *aAttributeValue.BeginReading(iter).advance(selectorLen) != char16_t('-')) { // to match, the aAttributeValue must have a dash after the end of // the aSelectorValue's text (unless the aSelectorValue and the // aAttributeValue have the same text) result = false; } else { result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator); } } return result; } bool nsStyleUtil::LangTagCompare(const nsACString& aAttributeValue, const nsACString& aSelectorValue) { if (aAttributeValue.IsEmpty() || aSelectorValue.IsEmpty()) { return false; } class MOZ_RAII AutoLangTag final { public: AutoLangTag() = delete; AutoLangTag(const AutoLangTag& aOther) = delete; explicit AutoLangTag(const nsACString& aLangTag) { mLangTag = intl::ffi::lang_tag_new(&aLangTag); } ~AutoLangTag() { if (mLangTag) { intl::ffi::lang_tag_destroy(mLangTag); } } bool IsValid() const { return mLangTag; } operator intl::ffi::LangTag*() const { return mLangTag; } void Reset(const nsACString& aLangTag) { if (mLangTag) { intl::ffi::lang_tag_destroy(mLangTag); } mLangTag = intl::ffi::lang_tag_new(&aLangTag); } private: intl::ffi::LangTag* mLangTag = nullptr; }; AutoLangTag langAttr(aAttributeValue); // Non-BCP47 extension: recognize '_' as an alternative subtag delimiter. nsAutoCString attrTemp; if (!langAttr.IsValid()) { if (aAttributeValue.Contains('_')) { attrTemp = aAttributeValue; attrTemp.ReplaceChar('_', '-'); langAttr.Reset(attrTemp); } } if (!langAttr.IsValid()) { return false; } return intl::ffi::lang_tag_matches(langAttr, &aSelectorValue); } bool nsStyleUtil::ValueIncludes(const nsAString& aValueList, const nsAString& aValue, const nsStringComparator& aComparator) { const char16_t *p = aValueList.BeginReading(), *p_end = aValueList.EndReading(); while (p < p_end) { // skip leading space while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) ++p; const char16_t* val_start = p; // look for space or end while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) ++p; const char16_t* val_end = p; if (val_start < val_end && aValue.Equals(Substring(val_start, val_end), aComparator)) return true; ++p; // we know the next character is not whitespace } return false; } void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString, nsAString& aReturn, char16_t quoteChar) { MOZ_ASSERT(quoteChar == '\'' || quoteChar == '"', "CSS strings must be quoted with ' or \""); aReturn.Append(quoteChar); const char16_t* in = aString.BeginReading(); const char16_t* const end = aString.EndReading(); for (; in != end; in++) { if (*in < 0x20 || *in == 0x7F) { // Escape U+0000 through U+001F and U+007F numerically. aReturn.AppendPrintf("\\%x ", *in); } else { if (*in == '"' || *in == '\'' || *in == '\\') { // Escape backslash and quote characters symbolically. // It's not technically necessary to escape the quote // character that isn't being used to delimit the string, // but we do it anyway because that makes testing simpler. aReturn.Append(char16_t('\\')); } aReturn.Append(*in); } } aReturn.Append(quoteChar); } /* static */ void nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn) { // The relevant parts of the CSS grammar are: // ident ([-]?{nmstart}|[-][-]){nmchar}* // nmstart [_a-z]|{nonascii}|{escape} // nmchar [_a-z0-9-]|{nonascii}|{escape} // nonascii [^\0-\177] // escape {unicode}|\\[^\n\r\f0-9a-f] // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? // from http://www.w3.org/TR/CSS21/syndata.html#tokenization but // modified for idents by // http://dev.w3.org/csswg/cssom/#serialize-an-identifier and // http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier const char16_t* in = aIdent.BeginReading(); const char16_t* const end = aIdent.EndReading(); if (in == end) return; // A leading dash does not need to be escaped as long as it is not the // *only* character in the identifier. if (*in == '-') { if (in + 1 == end) { aReturn.Append(char16_t('\\')); aReturn.Append(char16_t('-')); return; } aReturn.Append(char16_t('-')); ++in; } // Escape a digit at the start (including after a dash), // numerically. If we didn't escape it numerically, it would get // interpreted as a numeric escape for the wrong character. if (in != end && ('0' <= *in && *in <= '9')) { aReturn.AppendPrintf("\\%x ", *in); ++in; } for (; in != end; ++in) { char16_t ch = *in; if (ch == 0x00) { aReturn.Append(char16_t(0xFFFD)); } else if (ch < 0x20 || 0x7F == ch) { // Escape U+0000 through U+001F and U+007F numerically. aReturn.AppendPrintf("\\%x ", *in); } else { // Escape ASCII non-identifier printables as a backslash plus // the character. if (ch < 0x7F && ch != '_' && ch != '-' && (ch < '0' || '9' < ch) && (ch < 'A' || 'Z' < ch) && (ch < 'a' || 'z' < ch)) { aReturn.Append(char16_t('\\')); } aReturn.Append(ch); } } } /* static */ float nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha) { // Alpha values are expressed as decimals, so we should convert // back, using as few decimal places as possible for // round-tripping. // First try two decimal places: float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f; if (FloatToColorComponent(rounded) != aAlpha) { // Use three decimal places. rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f; } return rounded; } /* static */ void nsStyleUtil::GetSerializedColorValue(nscolor aColor, nsAString& aSerializedColor) { MOZ_ASSERT(aSerializedColor.IsEmpty()); const bool hasAlpha = NS_GET_A(aColor) != 255; if (hasAlpha) { aSerializedColor.AppendLiteral("rgba("); } else { aSerializedColor.AppendLiteral("rgb("); } aSerializedColor.AppendInt(NS_GET_R(aColor)); aSerializedColor.AppendLiteral(", "); aSerializedColor.AppendInt(NS_GET_G(aColor)); aSerializedColor.AppendLiteral(", "); aSerializedColor.AppendInt(NS_GET_B(aColor)); if (hasAlpha) { aSerializedColor.AppendLiteral(", "); float alpha = nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)); nsStyleUtil::AppendCSSNumber(alpha, aSerializedColor); } aSerializedColor.AppendLiteral(")"); } /* static */ bool nsStyleUtil::IsSignificantChild(nsIContent* aChild, bool aWhitespaceIsSignificant) { bool isText = aChild->IsText(); if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) { return true; } return isText && aChild->TextLength() != 0 && (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace()); } /* static */ bool nsStyleUtil::ThreadSafeIsSignificantChild(const nsIContent* aChild, bool aWhitespaceIsSignificant) { bool isText = aChild->IsText(); if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) { return true; } return isText && aChild->TextLength() != 0 && (aWhitespaceIsSignificant || !aChild->ThreadSafeTextIsOnlyWhitespace()); } // For a replaced element whose concrete object size is no larger than the // element's content-box, this method checks whether the given // "object-position" coordinate might cause overflow in its dimension. static bool ObjectPositionCoordMightCauseOverflow( const LengthPercentage& aCoord) { // Any nonzero length in "object-position" can push us to overflow // (particularly if our concrete object size is exactly the same size as the // replaced element's content-box). if (!aCoord.ConvertsToPercentage()) { return !aCoord.ConvertsToLength() || aCoord.ToLengthInCSSPixels() != 0.0f; } // Percentages are interpreted as a fraction of the extra space. So, // percentages in the 0-100% range are safe, but values outside of that // range could cause overflow. float percentage = aCoord.ToPercentage(); return percentage < 0.0f || percentage > 1.0f; } /* static */ bool nsStyleUtil::ObjectPropsMightCauseOverflow( const nsStylePosition* aStylePos) { auto objectFit = aStylePos->mObjectFit; // "object-fit: cover" & "object-fit: none" can give us a render rect that's // larger than our container element's content-box. if (objectFit == StyleObjectFit::Cover || objectFit == StyleObjectFit::None) { return true; } // (All other object-fit values produce a concrete object size that's no // larger than the constraint region.) // Check each of our "object-position" coords to see if it could cause // overflow in its dimension: const Position& objectPosistion = aStylePos->mObjectPosition; if (ObjectPositionCoordMightCauseOverflow(objectPosistion.horizontal) || ObjectPositionCoordMightCauseOverflow(objectPosistion.vertical)) { return true; } return false; } /* static */ bool nsStyleUtil::CSPAllowsInlineStyle( dom::Element* aElement, dom::Document* aDocument, nsIPrincipal* aTriggeringPrincipal, uint32_t aLineNumber, uint32_t aColumnNumber, const nsAString& aStyleText, nsresult* aRv) { nsresult rv; if (aRv) { *aRv = NS_OK; } nsCOMPtr csp; if (aTriggeringPrincipal && BasePrincipal::Cast(aTriggeringPrincipal) ->OverridesCSP(aDocument->NodePrincipal())) { nsCOMPtr ep = do_QueryInterface(aTriggeringPrincipal); if (ep) { csp = ep->GetCsp(); } } else { csp = aDocument->GetCsp(); } if (!csp) { // No CSP --> the style is allowed return true; } // Hack to allow Devtools to edit inline styles if (csp->GetSkipAllowInlineStyleCheck()) { return true; } bool isStyleElement = false; // Query the nonce. nsAutoString nonce; if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) { isStyleElement = true; nsString* cspNonce = static_cast(aElement->GetProperty(nsGkAtoms::nonce)); if (cspNonce) { nonce = *cspNonce; } } bool allowInlineStyle = true; rv = csp->GetAllowsInline( isStyleElement ? nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE : nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE, !isStyleElement /* aHasUnsafeHash */, nonce, false, // aParserCreated only applies to scripts aElement, nullptr, // nsICSPEventListener aStyleText, aLineNumber, aColumnNumber, &allowInlineStyle); NS_ENSURE_SUCCESS(rv, false); return allowInlineStyle; }