summaryrefslogtreecommitdiffstats
path: root/layout/style/ImportScanner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style/ImportScanner.cpp')
-rw-r--r--layout/style/ImportScanner.cpp235
1 files changed, 235 insertions, 0 deletions
diff --git a/layout/style/ImportScanner.cpp b/layout/style/ImportScanner.cpp
new file mode 100644
index 0000000000..22d6c554f4
--- /dev/null
+++ b/layout/style/ImportScanner.cpp
@@ -0,0 +1,235 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ImportScanner.h"
+
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+static inline bool IsWhitespace(char16_t aChar) {
+ return nsContentUtils::IsHTMLWhitespace(aChar);
+}
+
+static inline bool OptionalSupportsMatches(const nsAString& aAfterRuleValue) {
+ // Ensure pref for @import supports() is enabled before wanting to check.
+ if (!StaticPrefs::layout_css_import_supports_enabled()) {
+ return true;
+ }
+
+ // Empty, don't bother checking.
+ if (aAfterRuleValue.IsEmpty()) {
+ return true;
+ }
+
+ NS_ConvertUTF16toUTF8 value(aAfterRuleValue);
+ return Servo_CSSSupportsForImport(&value);
+}
+
+void ImportScanner::ResetState() {
+ mInImportRule = false;
+ // We try to avoid freeing the buffers here.
+ mRuleName.Truncate(0);
+ mRuleValue.Truncate(0);
+ mAfterRuleValue.Truncate(0);
+}
+
+void ImportScanner::Start() {
+ Stop();
+ mState = State::Idle;
+}
+
+void ImportScanner::EmitUrl() {
+ MOZ_ASSERT(mState == State::AfterRuleValue);
+ if (mInImportRule) {
+ // Trim trailing whitespace from an unquoted URL.
+ if (mUrlValueDelimiterClosingChar == ')') {
+ // FIXME: Add a convenience function in nsContentUtils or something?
+ mRuleValue.Trim(" \t\n\r\f", false);
+ }
+
+ // If a supports(...) condition is given as part of import conditions,
+ // only emit the URL if it matches, as there is no use preloading
+ // imports for features we do not support, as this cannot change
+ // mid-page.
+ if (OptionalSupportsMatches(mAfterRuleValue)) {
+ mUrlsFound.AppendElement(std::move(mRuleValue));
+ }
+ }
+ ResetState();
+ MOZ_ASSERT(mRuleValue.IsEmpty());
+}
+
+nsTArray<nsString> ImportScanner::Stop() {
+ if (mState == State::AfterRuleValue) {
+ EmitUrl();
+ }
+ mState = State::OutsideOfStyleElement;
+ ResetState();
+ return std::move(mUrlsFound);
+}
+
+nsTArray<nsString> ImportScanner::Scan(Span<const char16_t> aFragment) {
+ MOZ_ASSERT(ShouldScan());
+
+ for (char16_t c : aFragment) {
+ mState = Scan(c);
+ if (mState == State::Done) {
+ break;
+ }
+ }
+
+ return std::move(mUrlsFound);
+}
+
+auto ImportScanner::Scan(char16_t aChar) -> State {
+ switch (mState) {
+ case State::OutsideOfStyleElement:
+ case State::Done:
+ MOZ_ASSERT_UNREACHABLE("How?");
+ return mState;
+ case State::Idle: {
+ // TODO(emilio): Maybe worth caring about html-style comments like:
+ // <style>
+ // <!--
+ // @import url(stuff);
+ // -->
+ // </style>
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '/') {
+ return State::MaybeAtCommentStart;
+ }
+ if (aChar == '@') {
+ MOZ_ASSERT(mRuleName.IsEmpty());
+ return State::AtRuleName;
+ }
+ return State::Done;
+ }
+ case State::MaybeAtCommentStart: {
+ return aChar == '*' ? State::AtComment : State::Done;
+ }
+ case State::AtComment: {
+ return aChar == '*' ? State::MaybeAtCommentEnd : mState;
+ }
+ case State::MaybeAtCommentEnd: {
+ return aChar == '/' ? State::Idle : State::AtComment;
+ }
+ case State::AtRuleName: {
+ if (IsAsciiAlpha(aChar)) {
+ if (mRuleName.Length() > kMaxRuleNameLength - 1) {
+ return State::Done;
+ }
+ mRuleName.Append(aChar);
+ return mState;
+ }
+ if (IsWhitespace(aChar)) {
+ mInImportRule = mRuleName.LowerCaseEqualsLiteral("import");
+ if (mInImportRule) {
+ return State::AtRuleValue;
+ }
+ // Ignorable rules, we skip until the next semi-colon for these.
+ if (mRuleName.LowerCaseEqualsLiteral("charset") ||
+ mRuleName.LowerCaseEqualsLiteral("layer")) {
+ MOZ_ASSERT(mRuleValue.IsEmpty());
+ return State::AfterRuleValue;
+ }
+ }
+ return State::Done;
+ }
+ case State::AtRuleValue: {
+ MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
+ if (mRuleValue.IsEmpty()) {
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '"' || aChar == '\'') {
+ mUrlValueDelimiterClosingChar = aChar;
+ return State::AtRuleValueDelimited;
+ }
+ if (aChar == 'u' || aChar == 'U') {
+ mRuleValue.Append('u');
+ return mState;
+ }
+ return State::Done;
+ }
+ if (mRuleValue.Length() == 1) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("u"));
+ if (aChar == 'r' || aChar == 'R') {
+ mRuleValue.Append('r');
+ return mState;
+ }
+ return State::Done;
+ }
+ if (mRuleValue.Length() == 2) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("ur"));
+ if (aChar == 'l' || aChar == 'L') {
+ mRuleValue.Append('l');
+ }
+ return mState;
+ }
+ if (mRuleValue.Length() == 3) {
+ MOZ_ASSERT(mRuleValue.EqualsLiteral("url"));
+ if (aChar == '(') {
+ mUrlValueDelimiterClosingChar = ')';
+ mRuleValue.Truncate(0);
+ return State::AtRuleValueDelimited;
+ }
+ return State::Done;
+ }
+ MOZ_ASSERT_UNREACHABLE(
+ "How? We should find a paren or a string delimiter");
+ return State::Done;
+ }
+ case State::AtRuleValueDelimited: {
+ MOZ_ASSERT(mInImportRule, "Should only get to this state for @import");
+ if (aChar == mUrlValueDelimiterClosingChar) {
+ return State::AfterRuleValue;
+ }
+ if (mUrlValueDelimiterClosingChar == ')' && mRuleValue.IsEmpty()) {
+ if (IsWhitespace(aChar)) {
+ return mState;
+ }
+ if (aChar == '"' || aChar == '\'') {
+ // Handle url("") and url('').
+ mUrlValueDelimiterClosingChar = aChar;
+ return mState;
+ }
+ }
+ if (!mRuleValue.Append(aChar, mozilla::fallible)) {
+ mRuleValue.Truncate(0);
+ return State::Done;
+ }
+ return mState;
+ }
+ case State::AfterRuleValue: {
+ if (aChar == ';') {
+ EmitUrl();
+ return State::Idle;
+ }
+ // If there's a selector here and the import was unterminated, just give
+ // up.
+ if (aChar == '{') {
+ return State::Done;
+ }
+
+ if (!mAfterRuleValue.Append(aChar, mozilla::fallible)) {
+ mAfterRuleValue.Truncate(0);
+ return State::Done;
+ }
+
+ return mState; // There can be all sorts of stuff here like media
+ // queries or what not.
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("Forgot to handle a state?");
+ return State::Done;
+}
+
+} // namespace mozilla