nsStyleUtil.cpp (12675B)
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 9 #include <cctype> 10 11 #include "mozilla/ExpandedPrincipal.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/PolicyContainer.h" 14 #include "mozilla/intl/MozLocaleBindings.h" 15 #include "mozilla/intl/oxilangtag_ffi_generated.h" 16 #include "nsCSSProps.h" 17 #include "nsContentUtils.h" 18 #include "nsIContent.h" 19 #include "nsIContentPolicy.h" 20 #include "nsIContentSecurityPolicy.h" 21 #include "nsLayoutUtils.h" 22 #include "nsPrintfCString.h" 23 #include "nsROCSSPrimitiveValue.h" 24 #include "nsStyleConsts.h" 25 #include "nsStyleStruct.h" 26 27 using namespace mozilla; 28 29 //------------------------------------------------------------------------------ 30 // Font Algorithm Code 31 //------------------------------------------------------------------------------ 32 33 // Compare two language strings 34 bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue, 35 const nsAString& aSelectorValue, 36 const nsStringComparator& aComparator) { 37 bool result; 38 uint32_t selectorLen = aSelectorValue.Length(); 39 uint32_t attributeLen = aAttributeValue.Length(); 40 if (selectorLen > attributeLen) { 41 result = false; 42 } else { 43 nsAString::const_iterator iter; 44 if (selectorLen != attributeLen && 45 *aAttributeValue.BeginReading(iter).advance(selectorLen) != 46 char16_t('-')) { 47 // to match, the aAttributeValue must have a dash after the end of 48 // the aSelectorValue's text (unless the aSelectorValue and the 49 // aAttributeValue have the same text) 50 result = false; 51 } else { 52 result = StringBeginsWith(aAttributeValue, aSelectorValue, aComparator); 53 } 54 } 55 return result; 56 } 57 58 bool nsStyleUtil::LangTagCompare(const nsACString& aAttributeValue, 59 const nsACString& aSelectorValue) { 60 if (aAttributeValue.IsEmpty() || aSelectorValue.IsEmpty()) { 61 return false; 62 } 63 64 class MOZ_RAII AutoLangTag final { 65 public: 66 AutoLangTag() = delete; 67 AutoLangTag(const AutoLangTag& aOther) = delete; 68 explicit AutoLangTag(const nsACString& aLangTag) { 69 mLangTag = intl::ffi::lang_tag_new(&aLangTag); 70 } 71 72 ~AutoLangTag() { 73 if (mLangTag) { 74 intl::ffi::lang_tag_destroy(mLangTag); 75 } 76 } 77 78 bool IsValid() const { return mLangTag; } 79 operator intl::ffi::LangTag*() const { return mLangTag; } 80 81 void Reset(const nsACString& aLangTag) { 82 if (mLangTag) { 83 intl::ffi::lang_tag_destroy(mLangTag); 84 } 85 mLangTag = intl::ffi::lang_tag_new(&aLangTag); 86 } 87 88 private: 89 intl::ffi::LangTag* mLangTag = nullptr; 90 }; 91 92 AutoLangTag langAttr(aAttributeValue); 93 94 // Non-BCP47 extension: recognize '_' as an alternative subtag delimiter. 95 nsAutoCString attrTemp; 96 if (!langAttr.IsValid()) { 97 if (aAttributeValue.Contains('_')) { 98 attrTemp = aAttributeValue; 99 attrTemp.ReplaceChar('_', '-'); 100 langAttr.Reset(attrTemp); 101 } 102 } 103 104 if (!langAttr.IsValid()) { 105 return false; 106 } 107 108 return intl::ffi::lang_tag_matches(langAttr, &aSelectorValue); 109 } 110 111 bool nsStyleUtil::ValueIncludes(const nsAString& aValueList, 112 const nsAString& aValue, 113 const nsStringComparator& aComparator) { 114 const char16_t *p = aValueList.BeginReading(), 115 *p_end = aValueList.EndReading(); 116 117 while (p < p_end) { 118 // skip leading space 119 while (p != p_end && nsContentUtils::IsHTMLWhitespace(*p)) { 120 ++p; 121 } 122 123 const char16_t* val_start = p; 124 125 // look for space or end 126 while (p != p_end && !nsContentUtils::IsHTMLWhitespace(*p)) { 127 ++p; 128 } 129 130 const char16_t* val_end = p; 131 132 if (val_start < val_end && 133 aValue.Equals(Substring(val_start, val_end), aComparator)) { 134 return true; 135 } 136 137 ++p; // we know the next character is not whitespace 138 } 139 return false; 140 } 141 142 void nsStyleUtil::AppendQuotedCSSString(const nsACString& aString, 143 nsACString& aReturn, char aQuoteChar) { 144 MOZ_ASSERT(aQuoteChar == '\'' || aQuoteChar == '"', 145 "CSS strings must be quoted with ' or \""); 146 147 aReturn.Append(aQuoteChar); 148 149 const char* in = aString.BeginReading(); 150 const char* const end = aString.EndReading(); 151 for (; in != end; in++) { 152 if (*in == '\\' || *in == aQuoteChar) { 153 // Escape backslash and quote characters symbolically. 154 // It's not technically necessary to escape the quote 155 // character that isn't being used to delimit the string, 156 // but we do it anyway because that makes testing simpler. 157 aReturn.Append('\\'); 158 } 159 aReturn.Append(*in); 160 } 161 aReturn.Append(aQuoteChar); 162 } 163 164 /* static */ 165 void nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, 166 nsAString& aReturn) { 167 // The relevant parts of the CSS grammar are: 168 // ident ([-]?{nmstart}|[-][-]){nmchar}* 169 // nmstart [_a-z]|{nonascii}|{escape} 170 // nmchar [_a-z0-9-]|{nonascii}|{escape} 171 // nonascii [^\0-\177] 172 // escape {unicode}|\\[^\n\r\f0-9a-f] 173 // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? 174 // from http://www.w3.org/TR/CSS21/syndata.html#tokenization but 175 // modified for idents by 176 // http://dev.w3.org/csswg/cssom/#serialize-an-identifier and 177 // http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier 178 179 const char16_t* in = aIdent.BeginReading(); 180 const char16_t* const end = aIdent.EndReading(); 181 182 if (in == end) { 183 return; 184 } 185 186 // A leading dash does not need to be escaped as long as it is not the 187 // *only* character in the identifier. 188 if (*in == '-') { 189 if (in + 1 == end) { 190 aReturn.Append(char16_t('\\')); 191 aReturn.Append(char16_t('-')); 192 return; 193 } 194 195 aReturn.Append(char16_t('-')); 196 ++in; 197 } 198 199 // Escape a digit at the start (including after a dash), 200 // numerically. If we didn't escape it numerically, it would get 201 // interpreted as a numeric escape for the wrong character. 202 if (in != end && ('0' <= *in && *in <= '9')) { 203 aReturn.AppendPrintf("\\%x ", *in); 204 ++in; 205 } 206 207 for (; in != end; ++in) { 208 char16_t ch = *in; 209 if (ch == 0x00) { 210 aReturn.Append(char16_t(0xFFFD)); 211 } else if (ch < 0x20 || 0x7F == ch) { 212 // Escape U+0000 through U+001F and U+007F numerically. 213 aReturn.AppendPrintf("\\%x ", *in); 214 } else { 215 // Escape ASCII non-identifier printables as a backslash plus 216 // the character. 217 if (ch < 0x7F && ch != '_' && ch != '-' && (ch < '0' || '9' < ch) && 218 (ch < 'A' || 'Z' < ch) && (ch < 'a' || 'z' < ch)) { 219 aReturn.Append(char16_t('\\')); 220 } 221 aReturn.Append(ch); 222 } 223 } 224 } 225 226 /* static */ 227 float nsStyleUtil::ColorComponentToFloat(uint8_t aAlpha) { 228 // Alpha values are expressed as decimals, so we should convert 229 // back, using as few decimal places as possible for 230 // round-tripping. 231 // First try two decimal places: 232 float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f; 233 if (FloatToColorComponent(rounded) != aAlpha) { 234 // Use three decimal places. 235 rounded = NS_roundf(float(aAlpha) * 1000.0f / 255.0f) / 1000.0f; 236 } 237 return rounded; 238 } 239 240 /* static */ 241 void nsStyleUtil::GetSerializedColorValue(nscolor aColor, 242 nsAString& aSerializedColor) { 243 MOZ_ASSERT(aSerializedColor.IsEmpty()); 244 245 const bool hasAlpha = NS_GET_A(aColor) != 255; 246 if (hasAlpha) { 247 aSerializedColor.AppendLiteral("rgba("); 248 } else { 249 aSerializedColor.AppendLiteral("rgb("); 250 } 251 aSerializedColor.AppendInt(NS_GET_R(aColor)); 252 aSerializedColor.AppendLiteral(", "); 253 aSerializedColor.AppendInt(NS_GET_G(aColor)); 254 aSerializedColor.AppendLiteral(", "); 255 aSerializedColor.AppendInt(NS_GET_B(aColor)); 256 if (hasAlpha) { 257 aSerializedColor.AppendLiteral(", "); 258 float alpha = nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)); 259 nsStyleUtil::AppendCSSNumber(alpha, aSerializedColor); 260 } 261 aSerializedColor.AppendLiteral(")"); 262 } 263 264 /* static */ 265 bool nsStyleUtil::IsSignificantChild(nsIContent* aChild, 266 bool aWhitespaceIsSignificant) { 267 bool isText = aChild->IsText(); 268 269 if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) { 270 return true; 271 } 272 273 return isText && aChild->TextLength() != 0 && 274 (aWhitespaceIsSignificant || !aChild->TextIsOnlyWhitespace()); 275 } 276 277 /* static */ 278 bool nsStyleUtil::ThreadSafeIsSignificantChild(const nsIContent* aChild, 279 bool aWhitespaceIsSignificant) { 280 bool isText = aChild->IsText(); 281 282 if (!isText && !aChild->IsComment() && !aChild->IsProcessingInstruction()) { 283 return true; 284 } 285 286 return isText && aChild->TextLength() != 0 && 287 (aWhitespaceIsSignificant || 288 !aChild->ThreadSafeTextIsOnlyWhitespace()); 289 } 290 291 // For a replaced element whose concrete object size is no larger than the 292 // element's content-box, this method checks whether the given 293 // "object-position" coordinate might cause overflow in its dimension. 294 static bool ObjectPositionCoordMightCauseOverflow( 295 const LengthPercentage& aCoord) { 296 // Any nonzero length in "object-position" can push us to overflow 297 // (particularly if our concrete object size is exactly the same size as the 298 // replaced element's content-box). 299 if (!aCoord.ConvertsToPercentage()) { 300 return !aCoord.ConvertsToLength() || aCoord.ToLengthInCSSPixels() != 0.0f; 301 } 302 303 // Percentages are interpreted as a fraction of the extra space. So, 304 // percentages in the 0-100% range are safe, but values outside of that 305 // range could cause overflow. 306 float percentage = aCoord.ToPercentage(); 307 return percentage < 0.0f || percentage > 1.0f; 308 } 309 310 /* static */ 311 bool nsStyleUtil::ObjectPropsMightCauseOverflow( 312 const nsStylePosition* aStylePos) { 313 auto objectFit = aStylePos->mObjectFit; 314 315 // "object-fit: cover" & "object-fit: none" can give us a render rect that's 316 // larger than our container element's content-box. 317 if (objectFit == StyleObjectFit::Cover || objectFit == StyleObjectFit::None) { 318 return true; 319 } 320 // (All other object-fit values produce a concrete object size that's no 321 // larger than the constraint region.) 322 323 // Check each of our "object-position" coords to see if it could cause 324 // overflow in its dimension: 325 const Position& objectPosistion = aStylePos->mObjectPosition; 326 if (ObjectPositionCoordMightCauseOverflow(objectPosistion.horizontal) || 327 ObjectPositionCoordMightCauseOverflow(objectPosistion.vertical)) { 328 return true; 329 } 330 331 return false; 332 } 333 334 /* static */ 335 bool nsStyleUtil::CSPAllowsInlineStyle( 336 dom::Element* aElement, dom::Document* aDocument, 337 nsIPrincipal* aTriggeringPrincipal, uint32_t aLineNumber, 338 uint32_t aColumnNumber, const nsAString& aStyleText, nsresult* aRv) { 339 nsresult rv; 340 341 if (aRv) { 342 *aRv = NS_OK; 343 } 344 345 nsCOMPtr<nsIContentSecurityPolicy> csp; 346 if (aTriggeringPrincipal && BasePrincipal::Cast(aTriggeringPrincipal) 347 ->OverridesCSP(aDocument->NodePrincipal())) { 348 nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aTriggeringPrincipal); 349 if (ep) { 350 // Bug 1548468: Move CSP off ExpandedPrincipal 351 csp = ep->GetCsp(); 352 } 353 } else { 354 csp = PolicyContainer::GetCSP(aDocument->GetPolicyContainer()); 355 } 356 357 if (!csp) { 358 // No CSP --> the style is allowed 359 return true; 360 } 361 362 // Hack to allow Devtools to edit inline styles 363 if (csp->GetSkipAllowInlineStyleCheck()) { 364 return true; 365 } 366 367 bool isStyleElement = false; 368 // Query the nonce. 369 nsAutoString nonce; 370 if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) { 371 isStyleElement = true; 372 nsString* cspNonce = 373 static_cast<nsString*>(aElement->GetProperty(nsGkAtoms::nonce)); 374 if (cspNonce) { 375 nonce = *cspNonce; 376 } 377 } 378 379 bool allowInlineStyle = true; 380 rv = csp->GetAllowsInline( 381 isStyleElement ? nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE 382 : nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE, 383 !isStyleElement /* aHasUnsafeHash */, nonce, 384 false, // aParserCreated only applies to scripts 385 aElement, nullptr, // nsICSPEventListener 386 aStyleText, aLineNumber, aColumnNumber, &allowInlineStyle); 387 NS_ENSURE_SUCCESS(rv, false); 388 389 return allowInlineStyle; 390 }