mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			388 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			388 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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;
 | 
						|
}
 |