summaryrefslogtreecommitdiffstats
path: root/layout/style/nsStyleUtil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/nsStyleUtil.cpp')
-rw-r--r--layout/style/nsStyleUtil.cpp388
1 files changed, 388 insertions, 0 deletions
diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp
new file mode 100644
index 0000000000..d2a45dd4c2
--- /dev/null
+++ b/layout/style/nsStyleUtil.cpp
@@ -0,0 +1,388 @@
+/* -*- 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 <cctype>
+
+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<nsIContentSecurityPolicy> csp;
+ if (aTriggeringPrincipal && BasePrincipal::Cast(aTriggeringPrincipal)
+ ->OverridesCSP(aDocument->NodePrincipal())) {
+ nsCOMPtr<nsIExpandedPrincipal> 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<nsString*>(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;
+}