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