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"
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
) {
37 uint32_t selectorLen
= aSelectorValue
.Length();
38 uint32_t attributeLen
= aAttributeValue
.Length();
39 if (selectorLen
> attributeLen
) {
42 nsAString::const_iterator iter
;
43 if (selectorLen
!= attributeLen
&&
44 *aAttributeValue
.BeginReading(iter
).advance(selectorLen
) !=
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)
51 result
= StringBeginsWith(aAttributeValue
, aSelectorValue
, aComparator
);
57 bool nsStyleUtil::LangTagCompare(const nsACString
& aAttributeValue
,
58 const nsACString
& aSelectorValue
) {
59 if (aAttributeValue
.IsEmpty() || aSelectorValue
.IsEmpty()) {
63 class MOZ_RAII AutoLangTag final
{
65 AutoLangTag() = delete;
66 AutoLangTag(const AutoLangTag
& aOther
) = delete;
67 explicit AutoLangTag(const nsACString
& aLangTag
) {
68 mLangTag
= intl::ffi::lang_tag_new(&aLangTag
);
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
) {
82 intl::ffi::lang_tag_destroy(mLangTag
);
84 mLangTag
= intl::ffi::lang_tag_new(&aLangTag
);
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()) {
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();
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
))
131 ++p
; // we know the next character is not whitespace
136 void nsStyleUtil::AppendEscapedCSSString(const nsAString
& aString
,
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
);
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('\\'));
162 aReturn
.Append(quoteChar
);
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.
189 aReturn
.Append(char16_t('\\'));
190 aReturn
.Append(char16_t('-'));
194 aReturn
.Append(char16_t('-'));
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
);
206 for (; in
!= end
; ++in
) {
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
);
214 // Escape ASCII non-identifier printables as a backslash plus
216 if (ch
< 0x7F && ch
!= '_' && ch
!= '-' && (ch
< '0' || '9' < ch
) &&
217 (ch
< 'A' || 'Z' < ch
) && (ch
< 'a' || 'z' < ch
)) {
218 aReturn
.Append(char16_t('\\'));
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
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
;
240 void nsStyleUtil::GetSerializedColorValue(nscolor aColor
,
241 nsAString
& aSerializedColor
) {
242 MOZ_ASSERT(aSerializedColor
.IsEmpty());
244 const bool hasAlpha
= NS_GET_A(aColor
) != 255;
246 aSerializedColor
.AppendLiteral("rgba(");
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
));
256 aSerializedColor
.AppendLiteral(", ");
257 float alpha
= nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor
));
258 nsStyleUtil::AppendCSSNumber(alpha
, aSerializedColor
);
260 aSerializedColor
.AppendLiteral(")");
264 bool nsStyleUtil::IsSignificantChild(nsIContent
* aChild
,
265 bool aWhitespaceIsSignificant
) {
266 bool isText
= aChild
->IsText();
268 if (!isText
&& !aChild
->IsComment() && !aChild
->IsProcessingInstruction()) {
272 return isText
&& aChild
->TextLength() != 0 &&
273 (aWhitespaceIsSignificant
|| !aChild
->TextIsOnlyWhitespace());
277 bool nsStyleUtil::ThreadSafeIsSignificantChild(const nsIContent
* aChild
,
278 bool aWhitespaceIsSignificant
) {
279 bool isText
= aChild
->IsText();
281 if (!isText
&& !aChild
->IsComment() && !aChild
->IsProcessingInstruction()) {
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
;
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
) {
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
)) {
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
) {
344 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
345 if (aTriggeringPrincipal
&& BasePrincipal::Cast(aTriggeringPrincipal
)
346 ->OverridesCSP(aDocument
->NodePrincipal())) {
347 nsCOMPtr
<nsIExpandedPrincipal
> ep
= do_QueryInterface(aTriggeringPrincipal
);
352 csp
= aDocument
->GetCsp();
356 // No CSP --> the style is allowed
360 // Hack to allow Devtools to edit inline styles
361 if (csp
->GetSkipAllowInlineStyleCheck()) {
365 bool isStyleElement
= false;
368 if (aElement
&& aElement
->NodeInfo()->NameAtom() == nsGkAtoms::style
) {
369 isStyleElement
= true;
371 static_cast<nsString
*>(aElement
->GetProperty(nsGkAtoms::nonce
));
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
;