Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / style / ImportScanner.cpp
blob22d6c554f4bf19e5bb00a1e84a4cc1a0e122cecb
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/. */
7 #include "ImportScanner.h"
9 #include "mozilla/ServoBindings.h"
10 #include "mozilla/StaticPrefs_layout.h"
11 #include "nsContentUtils.h"
13 namespace mozilla {
15 static inline bool IsWhitespace(char16_t aChar) {
16 return nsContentUtils::IsHTMLWhitespace(aChar);
19 static inline bool OptionalSupportsMatches(const nsAString& aAfterRuleValue) {
20 // Ensure pref for @import supports() is enabled before wanting to check.
21 if (!StaticPrefs::layout_css_import_supports_enabled()) {
22 return true;
25 // Empty, don't bother checking.
26 if (aAfterRuleValue.IsEmpty()) {
27 return true;
30 NS_ConvertUTF16toUTF8 value(aAfterRuleValue);
31 return Servo_CSSSupportsForImport(&value);
34 void ImportScanner::ResetState() {
35 mInImportRule = false;
36 // We try to avoid freeing the buffers here.
37 mRuleName.Truncate(0);
38 mRuleValue.Truncate(0);
39 mAfterRuleValue.Truncate(0);
42 void ImportScanner::Start() {
43 Stop();
44 mState = State::Idle;
47 void ImportScanner::EmitUrl() {
48 MOZ_ASSERT(mState == State::AfterRuleValue);
49 if (mInImportRule) {
50 // Trim trailing whitespace from an unquoted URL.
51 if (mUrlValueDelimiterClosingChar == ')') {
52 // FIXME: Add a convenience function in nsContentUtils or something?
53 mRuleValue.Trim(" \t\n\r\f", false);
56 // If a supports(...) condition is given as part of import conditions,
57 // only emit the URL if it matches, as there is no use preloading
58 // imports for features we do not support, as this cannot change
59 // mid-page.
60 if (OptionalSupportsMatches(mAfterRuleValue)) {
61 mUrlsFound.AppendElement(std::move(mRuleValue));
64 ResetState();
65 MOZ_ASSERT(mRuleValue.IsEmpty());
68 nsTArray<nsString> ImportScanner::Stop() {
69 if (mState == State::AfterRuleValue) {
70 EmitUrl();
72 mState = State::OutsideOfStyleElement;
73 ResetState();
74 return std::move(mUrlsFound);
77 nsTArray<nsString> ImportScanner::Scan(Span<const char16_t> aFragment) {
78 MOZ_ASSERT(ShouldScan());
80 for (char16_t c : aFragment) {
81 mState = Scan(c);
82 if (mState == State::Done) {
83 break;
87 return std::move(mUrlsFound);
90 auto ImportScanner::Scan(char16_t aChar) -> State {
91 switch (mState) {
92 case State::OutsideOfStyleElement:
93 case State::Done:
94 MOZ_ASSERT_UNREACHABLE("How?");
95 return mState;
96 case State::Idle: {
97 // TODO(emilio): Maybe worth caring about html-style comments like:
98 // <style>
99 // <!--
100 // @import url(stuff);
101 // -->
102 // </style>
103 if (IsWhitespace(aChar)) {
104 return mState;
106 if (aChar == '/') {
107 return State::MaybeAtCommentStart;
109 if (aChar == '@') {
110 MOZ_ASSERT(mRuleName.IsEmpty());
111 return State::AtRuleName;
113 return State::Done;
115 case State::MaybeAtCommentStart: {
116 return aChar == '*' ? State::AtComment : State::Done;
118 case State::AtComment: {
119 return aChar == '*' ? State::MaybeAtCommentEnd : mState;
121 case State::MaybeAtCommentEnd: {
122 return aChar == '/' ? State::Idle : State::AtComment;
124 case State::AtRuleName: {
125 if (IsAsciiAlpha(aChar)) {
126 if (mRuleName.Length() > kMaxRuleNameLength - 1) {
127 return State::Done;
129 mRuleName.Append(aChar);
130 return mState;
132 if (IsWhitespace(aChar)) {
133 mInImportRule = mRuleName.LowerCaseEqualsLiteral("import");
134 if (mInImportRule) {
135 return State::AtRuleValue;
137 // Ignorable rules, we skip until the next semi-colon for these.
138 if (mRuleName.LowerCaseEqualsLiteral("charset") ||
139 mRuleName.LowerCaseEqualsLiteral("layer")) {
140 MOZ_ASSERT(mRuleValue.IsEmpty());
141 return State::AfterRuleValue;
144 return State::Done;
146 case State::AtRuleValue: {
147 MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
148 if (mRuleValue.IsEmpty()) {
149 if (IsWhitespace(aChar)) {
150 return mState;
152 if (aChar == '"' || aChar == '\'') {
153 mUrlValueDelimiterClosingChar = aChar;
154 return State::AtRuleValueDelimited;
156 if (aChar == 'u' || aChar == 'U') {
157 mRuleValue.Append('u');
158 return mState;
160 return State::Done;
162 if (mRuleValue.Length() == 1) {
163 MOZ_ASSERT(mRuleValue.EqualsLiteral("u"));
164 if (aChar == 'r' || aChar == 'R') {
165 mRuleValue.Append('r');
166 return mState;
168 return State::Done;
170 if (mRuleValue.Length() == 2) {
171 MOZ_ASSERT(mRuleValue.EqualsLiteral("ur"));
172 if (aChar == 'l' || aChar == 'L') {
173 mRuleValue.Append('l');
175 return mState;
177 if (mRuleValue.Length() == 3) {
178 MOZ_ASSERT(mRuleValue.EqualsLiteral("url"));
179 if (aChar == '(') {
180 mUrlValueDelimiterClosingChar = ')';
181 mRuleValue.Truncate(0);
182 return State::AtRuleValueDelimited;
184 return State::Done;
186 MOZ_ASSERT_UNREACHABLE(
187 "How? We should find a paren or a string delimiter");
188 return State::Done;
190 case State::AtRuleValueDelimited: {
191 MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
192 if (aChar == mUrlValueDelimiterClosingChar) {
193 return State::AfterRuleValue;
195 if (mUrlValueDelimiterClosingChar == ')' && mRuleValue.IsEmpty()) {
196 if (IsWhitespace(aChar)) {
197 return mState;
199 if (aChar == '"' || aChar == '\'') {
200 // Handle url("") and url('').
201 mUrlValueDelimiterClosingChar = aChar;
202 return mState;
205 if (!mRuleValue.Append(aChar, mozilla::fallible)) {
206 mRuleValue.Truncate(0);
207 return State::Done;
209 return mState;
211 case State::AfterRuleValue: {
212 if (aChar == ';') {
213 EmitUrl();
214 return State::Idle;
216 // If there's a selector here and the import was unterminated, just give
217 // up.
218 if (aChar == '{') {
219 return State::Done;
222 if (!mAfterRuleValue.Append(aChar, mozilla::fallible)) {
223 mAfterRuleValue.Truncate(0);
224 return State::Done;
227 return mState; // There can be all sorts of stuff here like media
228 // queries or what not.
231 MOZ_ASSERT_UNREACHABLE("Forgot to handle a state?");
232 return State::Done;
235 } // namespace mozilla