tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }