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