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"
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()) {
25 // Empty, don't bother checking.
26 if (aAfterRuleValue
.IsEmpty()) {
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() {
47 void ImportScanner::EmitUrl() {
48 MOZ_ASSERT(mState
== State::AfterRuleValue
);
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
60 if (OptionalSupportsMatches(mAfterRuleValue
)) {
61 mUrlsFound
.AppendElement(std::move(mRuleValue
));
65 MOZ_ASSERT(mRuleValue
.IsEmpty());
68 nsTArray
<nsString
> ImportScanner::Stop() {
69 if (mState
== State::AfterRuleValue
) {
72 mState
= State::OutsideOfStyleElement
;
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
) {
82 if (mState
== State::Done
) {
87 return std::move(mUrlsFound
);
90 auto ImportScanner::Scan(char16_t aChar
) -> State
{
92 case State::OutsideOfStyleElement
:
94 MOZ_ASSERT_UNREACHABLE("How?");
97 // TODO(emilio): Maybe worth caring about html-style comments like:
100 // @import url(stuff);
103 if (IsWhitespace(aChar
)) {
107 return State::MaybeAtCommentStart
;
110 MOZ_ASSERT(mRuleName
.IsEmpty());
111 return State::AtRuleName
;
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) {
129 mRuleName
.Append(aChar
);
132 if (IsWhitespace(aChar
)) {
133 mInImportRule
= mRuleName
.LowerCaseEqualsLiteral("import");
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
;
146 case State::AtRuleValue
: {
147 MOZ_ASSERT(mInImportRule
, "Should only get to this state for @import");
148 if (mRuleValue
.IsEmpty()) {
149 if (IsWhitespace(aChar
)) {
152 if (aChar
== '"' || aChar
== '\'') {
153 mUrlValueDelimiterClosingChar
= aChar
;
154 return State::AtRuleValueDelimited
;
156 if (aChar
== 'u' || aChar
== 'U') {
157 mRuleValue
.Append('u');
162 if (mRuleValue
.Length() == 1) {
163 MOZ_ASSERT(mRuleValue
.EqualsLiteral("u"));
164 if (aChar
== 'r' || aChar
== 'R') {
165 mRuleValue
.Append('r');
170 if (mRuleValue
.Length() == 2) {
171 MOZ_ASSERT(mRuleValue
.EqualsLiteral("ur"));
172 if (aChar
== 'l' || aChar
== 'L') {
173 mRuleValue
.Append('l');
177 if (mRuleValue
.Length() == 3) {
178 MOZ_ASSERT(mRuleValue
.EqualsLiteral("url"));
180 mUrlValueDelimiterClosingChar
= ')';
181 mRuleValue
.Truncate(0);
182 return State::AtRuleValueDelimited
;
186 MOZ_ASSERT_UNREACHABLE(
187 "How? We should find a paren or a string delimiter");
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
)) {
199 if (aChar
== '"' || aChar
== '\'') {
200 // Handle url("") and url('').
201 mUrlValueDelimiterClosingChar
= aChar
;
205 if (!mRuleValue
.Append(aChar
, mozilla::fallible
)) {
206 mRuleValue
.Truncate(0);
211 case State::AfterRuleValue
: {
216 // If there's a selector here and the import was unterminated, just give
222 if (!mAfterRuleValue
.Append(aChar
, mozilla::fallible
)) {
223 mAfterRuleValue
.Truncate(0);
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?");
235 } // namespace mozilla