tor-browser

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

ImportScanner.cpp (6644B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 sw=2 et tw=78: */
      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 "ImportScanner.h"
      8 
      9 #include "mozilla/ServoBindings.h"
     10 #include "nsContentUtils.h"
     11 
     12 namespace mozilla {
     13 
     14 static inline bool IsWhitespace(char16_t aChar) {
     15  return nsContentUtils::IsHTMLWhitespace(aChar);
     16 }
     17 
     18 static inline bool OptionalSupportsMatches(const nsAString& aAfterRuleValue) {
     19  // Empty, don't bother checking.
     20  if (aAfterRuleValue.IsEmpty()) {
     21    return true;
     22  }
     23 
     24  NS_ConvertUTF16toUTF8 value(aAfterRuleValue);
     25  return Servo_CSSSupportsForImport(&value);
     26 }
     27 
     28 void ImportScanner::ResetState() {
     29  mInImportRule = false;
     30  // We try to avoid freeing the buffers here.
     31  mRuleName.Truncate(0);
     32  mRuleValue.Truncate(0);
     33  mAfterRuleValue.Truncate(0);
     34 }
     35 
     36 void ImportScanner::Start() {
     37  Stop();
     38  mState = State::Idle;
     39 }
     40 
     41 void ImportScanner::EmitUrl() {
     42  MOZ_ASSERT(mState == State::AfterRuleValue);
     43  if (mInImportRule) {
     44    // Trim trailing whitespace from an unquoted URL.
     45    if (mUrlValueDelimiterClosingChar == ')') {
     46      // FIXME: Add a convenience function in nsContentUtils or something?
     47      mRuleValue.Trim(" \t\n\r\f", false);
     48    }
     49 
     50    // If a supports(...) condition is given as part of import conditions,
     51    // only emit the URL if it matches, as there is no use preloading
     52    // imports for features we do not support, as this cannot change
     53    // mid-page.
     54    if (OptionalSupportsMatches(mAfterRuleValue)) {
     55      mUrlsFound.AppendElement(std::move(mRuleValue));
     56    }
     57  }
     58  ResetState();
     59  MOZ_ASSERT(mRuleValue.IsEmpty());
     60 }
     61 
     62 nsTArray<nsString> ImportScanner::Stop() {
     63  if (mState == State::AfterRuleValue) {
     64    EmitUrl();
     65  }
     66  mState = State::OutsideOfStyleElement;
     67  ResetState();
     68  return std::move(mUrlsFound);
     69 }
     70 
     71 nsTArray<nsString> ImportScanner::Scan(Span<const char16_t> aFragment) {
     72  MOZ_ASSERT(ShouldScan());
     73 
     74  for (char16_t c : aFragment) {
     75    mState = Scan(c);
     76    if (mState == State::Done) {
     77      break;
     78    }
     79  }
     80 
     81  return std::move(mUrlsFound);
     82 }
     83 
     84 auto ImportScanner::Scan(char16_t aChar) -> State {
     85  switch (mState) {
     86    case State::OutsideOfStyleElement:
     87    case State::Done:
     88      MOZ_ASSERT_UNREACHABLE("How?");
     89      return mState;
     90    case State::Idle: {
     91      // TODO(emilio): Maybe worth caring about html-style comments like:
     92      // <style>
     93      // <!--
     94      //   @import url(stuff);
     95      // -->
     96      // </style>
     97      if (IsWhitespace(aChar)) {
     98        return mState;
     99      }
    100      if (aChar == '/') {
    101        return State::MaybeAtCommentStart;
    102      }
    103      if (aChar == '@') {
    104        MOZ_ASSERT(mRuleName.IsEmpty());
    105        return State::AtRuleName;
    106      }
    107      return State::Done;
    108    }
    109    case State::MaybeAtCommentStart: {
    110      return aChar == '*' ? State::AtComment : State::Done;
    111    }
    112    case State::AtComment: {
    113      return aChar == '*' ? State::MaybeAtCommentEnd : mState;
    114    }
    115    case State::MaybeAtCommentEnd: {
    116      return aChar == '/' ? State::Idle : State::AtComment;
    117    }
    118    case State::AtRuleName: {
    119      if (IsAsciiAlpha(aChar)) {
    120        if (mRuleName.Length() > kMaxRuleNameLength - 1) {
    121          return State::Done;
    122        }
    123        mRuleName.Append(aChar);
    124        return mState;
    125      }
    126      if (IsWhitespace(aChar)) {
    127        mInImportRule = mRuleName.LowerCaseEqualsLiteral("import");
    128        if (mInImportRule) {
    129          return State::AtRuleValue;
    130        }
    131        // Ignorable rules, we skip until the next semi-colon for these.
    132        if (mRuleName.LowerCaseEqualsLiteral("charset") ||
    133            mRuleName.LowerCaseEqualsLiteral("layer")) {
    134          MOZ_ASSERT(mRuleValue.IsEmpty());
    135          return State::AfterRuleValue;
    136        }
    137      }
    138      return State::Done;
    139    }
    140    case State::AtRuleValue: {
    141      MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
    142      if (mRuleValue.IsEmpty()) {
    143        if (IsWhitespace(aChar)) {
    144          return mState;
    145        }
    146        if (aChar == '"' || aChar == '\'') {
    147          mUrlValueDelimiterClosingChar = aChar;
    148          return State::AtRuleValueDelimited;
    149        }
    150        if (aChar == 'u' || aChar == 'U') {
    151          mRuleValue.Append('u');
    152          return mState;
    153        }
    154        return State::Done;
    155      }
    156      if (mRuleValue.Length() == 1) {
    157        MOZ_ASSERT(mRuleValue.EqualsLiteral("u"));
    158        if (aChar == 'r' || aChar == 'R') {
    159          mRuleValue.Append('r');
    160          return mState;
    161        }
    162        return State::Done;
    163      }
    164      if (mRuleValue.Length() == 2) {
    165        MOZ_ASSERT(mRuleValue.EqualsLiteral("ur"));
    166        if (aChar == 'l' || aChar == 'L') {
    167          mRuleValue.Append('l');
    168        }
    169        return mState;
    170      }
    171      if (mRuleValue.Length() == 3) {
    172        MOZ_ASSERT(mRuleValue.EqualsLiteral("url"));
    173        if (aChar == '(') {
    174          mUrlValueDelimiterClosingChar = ')';
    175          mRuleValue.Truncate(0);
    176          return State::AtRuleValueDelimited;
    177        }
    178        return State::Done;
    179      }
    180      MOZ_ASSERT_UNREACHABLE(
    181          "How? We should find a paren or a string delimiter");
    182      return State::Done;
    183    }
    184    case State::AtRuleValueDelimited: {
    185      MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
    186      if (aChar == mUrlValueDelimiterClosingChar) {
    187        return State::AfterRuleValue;
    188      }
    189      if (mUrlValueDelimiterClosingChar == ')' && mRuleValue.IsEmpty()) {
    190        if (IsWhitespace(aChar)) {
    191          return mState;
    192        }
    193        if (aChar == '"' || aChar == '\'') {
    194          // Handle url("") and url('').
    195          mUrlValueDelimiterClosingChar = aChar;
    196          return mState;
    197        }
    198      }
    199      if (!mRuleValue.Append(aChar, mozilla::fallible)) {
    200        mRuleValue.Truncate(0);
    201        return State::Done;
    202      }
    203      return mState;
    204    }
    205    case State::AfterRuleValue: {
    206      if (aChar == ';') {
    207        EmitUrl();
    208        return State::Idle;
    209      }
    210      // If there's a selector here and the import was unterminated, just give
    211      // up.
    212      if (aChar == '{') {
    213        return State::Done;
    214      }
    215 
    216      if (!mAfterRuleValue.Append(aChar, mozilla::fallible)) {
    217        mAfterRuleValue.Truncate(0);
    218        return State::Done;
    219      }
    220 
    221      return mState;  // There can be all sorts of stuff here like media
    222                      // queries or what not.
    223    }
    224  }
    225  MOZ_ASSERT_UNREACHABLE("Forgot to handle a state?");
    226  return State::Done;
    227 }
    228 
    229 }  // namespace mozilla