Commit 30ecdaf6 authored by Yoav Weiss's avatar Yoav Weiss Committed by Commit Bot

Fix CSSPreloadScanner to avoid missing rules (reland)

This fixes a couple of bugs with the CSSPReloadScanner that made it miss
rules in which the URL wasn't quoted or the rule didn't end with a
semicolon. It also adds tentative WPT tests for that functionality, as
the tests are also relevant for WebKit.

This is a reland of
https://chromium-review.googlesource.com/c/chromium/src/+/1331042

Bug: 903785
Change-Id: I401c252a42fbb96dee9c7942e0a4f8d5b6850244
TBR: kouhei
Reviewed-on: https://chromium-review.googlesource.com/c/1349980Reviewed-by: default avatarYoav Weiss <yoav@yoav.ws>
Commit-Queue: Yoav Weiss <yoav@yoav.ws>
Cr-Commit-Position: refs/heads/master@{#610796}
parent 58d1b65e
...@@ -156,12 +156,18 @@ inline void CSSPreloadScanner::Tokenize(UChar c, ...@@ -156,12 +156,18 @@ inline void CSSPreloadScanner::Tokenize(UChar c,
} }
break; break;
case kRuleValue: case kRuleValue:
if (IsHTMLSpace<UChar>(c)) if (IsHTMLSpace<UChar>(c)) {
state_ = kAfterRuleValue; state_ = kAfterRuleValue;
else if (c == ';') } else if (c == ';') {
EmitRule(source); EmitRule(source);
else } else {
rule_value_.Append(c); rule_value_.Append(c);
// When reading the rule and hitting ')', which signifies the URL end,
// emit the rule.
if (c == ')') {
EmitRule(source);
}
}
break; break;
case kAfterRuleValue: case kAfterRuleValue:
if (IsHTMLSpace<UChar>(c)) if (IsHTMLSpace<UChar>(c))
...@@ -185,14 +191,20 @@ static String ParseCSSStringOrURL(const String& string) { ...@@ -185,14 +191,20 @@ static String ParseCSSStringOrURL(const String& string) {
wtf_size_t offset = 0; wtf_size_t offset = 0;
wtf_size_t reduced_length = string.length(); wtf_size_t reduced_length = string.length();
// Remove whitespace from the rule start
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) { while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset; ++offset;
--reduced_length; --reduced_length;
} }
// Remove whitespace from the rule end
// TODO(yoav): Evaluate performance benefits of using raw string operations.
// TODO(yoav): Look into moving parsing to use better parsing primitives.
while (reduced_length && while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) {
--reduced_length; --reduced_length;
}
// Skip the "url(" prefix and the ")" suffix
if (reduced_length >= 5 && (string[offset] == 'u' || string[offset] == 'U') && if (reduced_length >= 5 && (string[offset] == 'u' || string[offset] == 'U') &&
(string[offset + 1] == 'r' || string[offset + 1] == 'R') && (string[offset + 1] == 'r' || string[offset + 1] == 'R') &&
(string[offset + 2] == 'l' || string[offset + 2] == 'L') && (string[offset + 2] == 'l' || string[offset + 2] == 'L') &&
...@@ -201,28 +213,23 @@ static String ParseCSSStringOrURL(const String& string) { ...@@ -201,28 +213,23 @@ static String ParseCSSStringOrURL(const String& string) {
reduced_length -= 5; reduced_length -= 5;
} }
// Skip whitespace before and after the URL inside the "url()" parenthesis.
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) { while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset; ++offset;
--reduced_length; --reduced_length;
} }
while (reduced_length && while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) IsHTMLSpace<UChar>(string[offset + reduced_length - 1])) {
--reduced_length; --reduced_length;
}
if (reduced_length < 2 || // Remove single-quotes or double-quotes from the URL
string[offset] != string[offset + reduced_length - 1] || if ((reduced_length >= 2) &&
!(string[offset] == '\'' || string[offset] == '"')) (string[offset] == string[offset + reduced_length - 1]) &&
return String(); (string[offset] == '\'' || string[offset] == '"')) {
offset++; offset++;
reduced_length -= 2; reduced_length -= 2;
while (reduced_length && IsHTMLSpace<UChar>(string[offset])) {
++offset;
--reduced_length;
} }
while (reduced_length &&
IsHTMLSpace<UChar>(string[offset + reduced_length - 1]))
--reduced_length;
return string.Substring(offset, reduced_length); return string.Substring(offset, reduced_length);
} }
......
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
var t = async_test('Imported inline CSS with no quote is not blocked on pending CSS');
</script>
<link rel=stylesheet href="resources/dummy.css?first&pipe=trickle(d1)">
<script>
var this_script_is_neccessary_to_block_the_inline_style_processing = true;
</script>
<style>
@import url(resources/dummy.css?second);
</style>
<script>
window.addEventListener("load", t.step_func_done(() => {
let entries = performance.getEntriesByType('resource');
let first;
let second;
for (entry of entries) {
if (entry.name.includes("first")) {
first = entry;
}
if (entry.name.includes("second")) {
second = entry;
}
}
assert_true(first.responseEnd > second.startTime, "The second resource start time should not be blocked on the first resource response");
}));
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
var t = async_test('Imported inline CSS with no semicolon is not blocked on pending CSS');
</script>
<link rel=stylesheet href="resources/dummy.css?first&pipe=trickle(d1)">
<script>
var this_script_is_neccessary_to_block_the_inline_style_processing = true;
</script>
<style>
@import url("resources/dummy.css?second")
</style>
<script>
window.addEventListener("load", t.step_func_done(() => {
let entries = performance.getEntriesByType('resource');
let first;
let second;
for (entry of entries) {
if (entry.name.includes("first")) {
first = entry;
}
if (entry.name.includes("second")) {
second = entry;
}
}
assert_true(first.responseEnd > second.startTime, "The second resource start time should not be blocked on the first resource response");
}));
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
var t = async_test('Imported inline CSS with no quote is not blocked on pending CSS');
</script>
<link rel=stylesheet href="resources/dummy.css?first&pipe=trickle(d1)">
<script>
var this_script_is_neccessary_to_block_the_inline_style_processing = true;
</script>
<style>@import url("resources/dummy.css?second")</style>
<script>
window.addEventListener("load", t.step_func_done(() => {
let entries = performance.getEntriesByType('resource');
let first;
let second;
for (entry of entries) {
if (entry.name.includes("first")) {
first = entry;
}
if (entry.name.includes("second")) {
second = entry;
}
}
assert_true(first.responseEnd > second.startTime, "The second resource start time should not be blocked on the first resource response");
}));
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
var t = async_test('Imported inline CSS is not blocked on pending CSS');
</script>
<link rel=stylesheet href="resources/dummy.css?first&pipe=trickle(d1)">
<script>
var this_script_is_neccessary_to_block_the_inline_style_processing = true;
</script>
<style>
@import url('resources/dummy.css?second');
</style>
<script>
window.addEventListener("load", t.step_func_done(() => {
let entries = performance.getEntriesByType('resource');
let first;
let second;
for (entry of entries) {
if (entry.name.includes("first")) {
first = entry;
}
if (entry.name.includes("second")) {
second = entry;
}
}
assert_true(first.responseEnd > second.startTime, "The second resource start time should not be blocked on the first resource response");
}));
</script>
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
var t = async_test('Imported inline CSS is not blocked on pending CSS');
</script>
<link rel=stylesheet href="resources/dummy.css?first&pipe=trickle(d1)">
<script>
var this_script_is_neccessary_to_block_the_inline_style_processing = true;
</script>
<style>
@import url("resources/dummy.css?second");
</style>
<script>
window.addEventListener("load", t.step_func_done(() => {
let entries = performance.getEntriesByType('resource');
let first;
let second;
for (entry of entries) {
if (entry.name.includes("first")) {
first = entry;
}
if (entry.name.includes("second")) {
second = entry;
}
}
assert_true(first.responseEnd > second.startTime, "The second resource start time should not be blocked on the first resource response");
}));
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment