diff --git a/browser/gui/app.cpp b/browser/gui/app.cpp index f98abf0e..0cecab99 100644 --- a/browser/gui/app.cpp +++ b/browser/gui/app.cpp @@ -91,9 +91,9 @@ std::string element_text(layout::LayoutBox const *element) { return std::get(element->node->node).name; } -std::string stylesheet_to_string(std::vector const &stylesheet) { +std::string stylesheet_to_string(css::StyleSheet const &stylesheet) { std::stringstream ss; - for (auto const &rule : stylesheet) { + for (auto const &rule : stylesheet.rules) { ss << css::to_string(rule) << std::endl; } return std::move(ss).str(); diff --git a/css/default.cpp b/css/default.cpp index 05380c40..1a5eef7a 100644 --- a/css/default.cpp +++ b/css/default.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2021-2022 Robin Lindén +// SPDX-FileCopyrightText: 2021-2023 Robin Lindén // // SPDX-License-Identifier: BSD-2-Clause @@ -13,7 +13,7 @@ namespace { #include "css/default_css.h" } // namespace -std::vector default_style() { +StyleSheet default_style() { return css::parse(std::string_view{reinterpret_cast(css_default_css), css_default_css_len}); } diff --git a/css/default.h b/css/default.h index 666fbef7..8f79a1f9 100644 --- a/css/default.h +++ b/css/default.h @@ -1,17 +1,15 @@ -// SPDX-FileCopyrightText: 2021 Robin Lindén +// SPDX-FileCopyrightText: 2021-2023 Robin Lindén // // SPDX-License-Identifier: BSD-2-Clause #ifndef CSS_DEFAULT_H_ #define CSS_DEFAULT_H_ -#include "css/rule.h" - -#include +#include "css/style_sheet.h" namespace css { -std::vector default_style(); +StyleSheet default_style(); } // namespace css diff --git a/css/parser.cpp b/css/parser.cpp index a8534c78..41f5573b 100644 --- a/css/parser.cpp +++ b/css/parser.cpp @@ -241,8 +241,8 @@ constexpr std::optional Parser::consume_while(T const &pred) { return input_.substr(start, pos_ - start); } -std::vector Parser::parse_rules() { - std::vector rules; +StyleSheet Parser::parse_rules() { + StyleSheet style; bool in_media_query{false}; std::optional media_query; @@ -255,7 +255,7 @@ std::vector Parser::parse_rules() { auto tmp_query = consume_while([](char c) { return c != '{'; }); if (!tmp_query) { spdlog::error("Eof while looking for end of media-query"); - return rules; + return style; } if (auto last_char = tmp_query->find_last_not_of(' '); last_char != std::string_view::npos) { @@ -276,7 +276,7 @@ std::vector Parser::parse_rules() { auto kind = consume_while([](char c) { return c != ' ' && c != '{' && c != '('; }); if (!kind) { spdlog::error("Eof while looking for end of at-rule"); - return rules; + return style; } spdlog::warn("Encountered unhandled {} at-rule", *kind); @@ -289,7 +289,7 @@ std::vector Parser::parse_rules() { while (peek() != '}') { if (auto rule = parse_rule(); !rule) { spdlog::error("Eof while looking for end of rule in unknown at-rule"); - return rules; + return style; } skip_whitespace_and_comments(); @@ -303,11 +303,11 @@ std::vector Parser::parse_rules() { auto rule = parse_rule(); if (!rule) { spdlog::error("Eof while parsing rule"); - return rules; + return style; } - rules.push_back(*std::move(rule)); - rules.back().media_query = media_query; + style.rules.push_back(*std::move(rule)); + style.rules.back().media_query = media_query; skip_whitespace_and_comments(); @@ -319,7 +319,7 @@ std::vector Parser::parse_rules() { } } - return rules; + return style; } constexpr std::optional Parser::peek() const { diff --git a/css/parser.h b/css/parser.h index e90154b6..df5aea40 100644 --- a/css/parser.h +++ b/css/parser.h @@ -8,13 +8,13 @@ #include "css/property_id.h" #include "css/rule.h" +#include "css/style_sheet.h" #include #include #include #include #include -#include namespace css { @@ -25,7 +25,7 @@ class Parser { public: explicit Parser(std::string_view input) : input_{input} {} - std::vector parse_rules(); + StyleSheet parse_rules(); private: std::string_view input_; @@ -77,7 +77,7 @@ class Parser { void expand_font(std::map &declarations, std::string_view value) const; }; -inline std::vector parse(std::string_view input) { +inline StyleSheet parse(std::string_view input) { return Parser{input}.parse_rules(); } diff --git a/css/parser_test.cpp b/css/parser_test.cpp index ad295a16..f6ad2d66 100644 --- a/css/parser_test.cpp +++ b/css/parser_test.cpp @@ -87,7 +87,7 @@ ValueT get_and_erase( void text_decoration_tests() { etest::test("parser: text-decoration, 1 value", [] { - auto rules = css::parse("p { text-decoration: underline; }"); + auto rules = css::parse("p { text-decoration: underline; }").rules; auto const &p = rules.at(0); expect_eq(p.declarations, std::map{ @@ -99,7 +99,7 @@ void text_decoration_tests() { // This will fail once we support CSS Level 3 text-decorations. etest::test("parser: text-decoration, 2 values", [] { - auto rules = css::parse("p { text-decoration: underline dotted; }"); + auto rules = css::parse("p { text-decoration: underline dotted; }").rules; auto const &p = rules.at(0); expect_eq(p.declarations, std::map{}); }); @@ -111,7 +111,7 @@ int main() { text_decoration_tests(); etest::test("parser: simple rule", [] { - auto rules = css::parse("body { width: 50px; }"sv); + auto rules = css::parse("body { width: 50px; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -121,7 +121,7 @@ int main() { }); etest::test("selector with spaces", [] { - auto rules = css::parse("p a { color: green; }"); + auto rules = css::parse("p a { color: green; }").rules; expect_eq(rules, std::vector{{ .selectors{{"p a"}}, @@ -130,7 +130,7 @@ int main() { }); etest::test("parser: minified", [] { - auto rules = css::parse("body{width:50px;font-family:inherit}head,p{display:none}"sv); + auto rules = css::parse("body{width:50px;font-family:inherit}head,p{display:none}"sv).rules; require(rules.size() == 2); auto first = rules[0]; @@ -146,7 +146,7 @@ int main() { }); etest::test("parser: multiple rules", [] { - auto rules = css::parse("body { width: 50px; }\np { font-size: 8em; }"sv); + auto rules = css::parse("body { width: 50px; }\np { font-size: 8em; }"sv).rules; require(rules.size() == 2); auto body = rules[0]; @@ -161,7 +161,7 @@ int main() { }); etest::test("parser: multiple selectors", [] { - auto rules = css::parse("body, p { width: 50px; }"sv); + auto rules = css::parse("body, p { width: 50px; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -171,7 +171,7 @@ int main() { }); etest::test("parser: multiple declarations", [] { - auto rules = css::parse("body { width: 50px; height: 300px; }"sv); + auto rules = css::parse("body { width: 50px; height: 300px; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -182,7 +182,7 @@ int main() { }); etest::test("parser: class", [] { - auto rules = css::parse(".cls { width: 50px; }"sv); + auto rules = css::parse(".cls { width: 50px; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -192,7 +192,7 @@ int main() { }); etest::test("parser: id", [] { - auto rules = css::parse("#cls { width: 50px; }"sv); + auto rules = css::parse("#cls { width: 50px; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -202,7 +202,7 @@ int main() { }); etest::test("parser: empty rule", [] { - auto rules = css::parse("body {}"sv); + auto rules = css::parse("body {}"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -211,12 +211,12 @@ int main() { }); etest::test("parser: no rules", [] { - auto rules = css::parse(""sv); + auto rules = css::parse(""sv).rules; expect(rules.empty()); }); etest::test("parser: top-level comments", [] { - auto rules = css::parse("body { width: 50px; }/* comment. */ p { font-size: 8em; } /* comment. */"sv); + auto rules = css::parse("body { width: 50px; }/* comment. */ p { font-size: 8em; } /* comment. */"sv).rules; require(rules.size() == 2); auto body = rules[0]; @@ -233,10 +233,11 @@ int main() { etest::test("parser: comments almost everywhere", [] { // body { width: 50px; } p { padding: 8em 4em; } with comments added everywhere currently supported. auto rules = css::parse(R"(/**/body {/**/width:50px;/**/}/* - */p {/**/padding:/**/8em 4em;/**//**/}/**/)"sv); + */p {/**/padding:/**/8em 4em;/**//**/}/**/)"sv) + .rules; // TODO(robinlinden): Support comments in more places. // auto rules = css::parse(R"(/**/body/**/{/**/width/**/:/**/50px/**/;/**/}/* - // */p/**/{/**/padding/**/:/**/8em/**/4em/**/;/**//**/}/**/)"sv); + // */p/**/{/**/padding/**/:/**/8em/**/4em/**/;/**//**/}/**/)"sv).rules; require_eq(rules.size(), 2UL); auto body = rules[0]; @@ -259,7 +260,8 @@ int main() { article { width: 50px; } p { font-size: 9em; } } - a { background-color: indigo; })"sv); + a { background-color: indigo; })"sv) + .rules; require(rules.size() == 3); auto article = rules[0]; @@ -282,7 +284,7 @@ int main() { }); etest::test("parser: minified media query", [] { - auto rules = css::parse("@media(max-width:300px){p{font-size:10px;}}"); + auto rules = css::parse("@media(max-width:300px){p{font-size:10px;}}").rules; require_eq(rules.size(), std::size_t{1}); auto const &rule = rules[0]; expect_eq(rule.media_query, css::MediaQuery{css::MediaQuery::Width{.max = 300}}); @@ -292,7 +294,7 @@ int main() { }); etest::test("parser: bad media query", [] { - auto rules = css::parse("@media (rip: 0) { p { font-size: 10px; } }"); + auto rules = css::parse("@media (rip: 0) { p { font-size: 10px; } }").rules; auto const &rule = rules.at(0); expect_eq(rule.media_query, std::nullopt); expect_eq(rule.selectors, std::vector{"p"s}); @@ -302,7 +304,8 @@ int main() { etest::test("parser: 2 media queries in a row", [] { auto rules = css::parse( - "@media (max-width: 1px) { p { font-size: 1em; } } @media (min-width: 2px) { a { color: blue; } }"); + "@media (max-width: 1px) { p { font-size: 1em; } } @media (min-width: 2px) { a { color: blue; } }") + .rules; require_eq(rules.size(), std::size_t{2}); expect_eq(rules[0], css::Rule{.selectors{{"p"}}, @@ -316,7 +319,7 @@ int main() { auto box_shorthand_one_value = [](std::string property, std::string value, std::string post_fix = "") { return [=]() mutable { - auto rules = css::parse(fmt::format("p {{ {}: {}; }}"sv, property, value)); + auto rules = css::parse(fmt::format("p {{ {}: {}; }}"sv, property, value)).rules; require(rules.size() == 1); if (property == "border-style") { @@ -350,7 +353,7 @@ int main() { std::array values, std::string post_fix = "") { return [=]() mutable { - auto rules = css::parse(fmt::format("p {{ {}: {} {}; }}"sv, property, values[0], values[1])); + auto rules = css::parse(fmt::format("p {{ {}: {} {}; }}"sv, property, values[0], values[1])).rules; require(rules.size() == 1); if (property == "border-style") { @@ -384,7 +387,8 @@ int main() { std::array values, std::string post_fix = "") { return [=]() mutable { - auto rules = css::parse(fmt::format("p {{ {}: {} {} {}; }}"sv, property, values[0], values[1], values[2])); + auto rules = + css::parse(fmt::format("p {{ {}: {} {} {}; }}"sv, property, values[0], values[1], values[2])).rules; require(rules.size() == 1); if (property == "border-style") { @@ -419,7 +423,8 @@ int main() { std::string post_fix = "") { return [=]() mutable { auto rules = css::parse( - fmt::format("p {{ {}: {} {} {} {}; }}"sv, property, values[0], values[1], values[2], values[3])); + fmt::format("p {{ {}: {} {} {} {}; }}"sv, property, values[0], values[1], values[2], values[3])) + .rules; require(rules.size() == 1); if (property == "border-style") { @@ -460,12 +465,13 @@ int main() { {5}-top{1}: {3}; {5}-left{1}: {4}; }})"sv, - property, - post_fix, - values[0], - values[1], - values[2], - workaround_for_border_style)); + property, + post_fix, + values[0], + values[1], + values[2], + workaround_for_border_style)) + .rules; require(rules.size() == 1); if (property == "border-style") { @@ -506,13 +512,14 @@ int main() { {6}-left{1}: {3}; {0}: {4} {5}; }})"sv, - property, - post_fix, - values[0], - values[1], - values[2], - values[3], - workaround_for_border_style)); + property, + post_fix, + values[0], + values[1], + values[2], + values[3], + workaround_for_border_style)) + .rules; require(rules.size() == 1); if (property == "border-style") { @@ -543,7 +550,7 @@ int main() { } etest::test("parser: shorthand background color", [] { - auto rules = css::parse("p { background: red }"sv); + auto rules = css::parse("p { background: red }"sv).rules; require(rules.size() == 1); auto &p = rules[0]; @@ -552,7 +559,7 @@ int main() { }); etest::test("parser: shorthand font with only size and generic font family", [] { - auto rules = css::parse("p { font: 1.5em sans-serif; }"sv); + auto rules = css::parse("p { font: 1.5em sans-serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -563,7 +570,7 @@ int main() { }); etest::test("parser: shorthand font with size, line height, and generic font family", [] { - auto rules = css::parse("p { font: 10%/2.5 monospace; }"sv); + auto rules = css::parse("p { font: 10%/2.5 monospace; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -575,7 +582,7 @@ int main() { }); etest::test("parser: shorthand font with absolute size, line height, and font family", [] { - auto rules = css::parse(R"(p { font: x-large/110% "New Century Schoolbook", serif; })"sv); + auto rules = css::parse(R"(p { font: x-large/110% "New Century Schoolbook", serif; })"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -587,7 +594,7 @@ int main() { }); etest::test("parser: shorthand font with italic font style", [] { - auto rules = css::parse(R"(p { font: italic 120% "Helvetica Neue", serif; })"sv); + auto rules = css::parse(R"(p { font: italic 120% "Helvetica Neue", serif; })"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -599,7 +606,7 @@ int main() { }); etest::test("parser: shorthand font with oblique font style", [] { - auto rules = css::parse(R"(p { font: oblique 12pt "Helvetica Neue", serif; })"sv); + auto rules = css::parse(R"(p { font: oblique 12pt "Helvetica Neue", serif; })"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -611,7 +618,7 @@ int main() { }); etest::test("parser: shorthand font with font style oblique with angle", [] { - auto rules = css::parse("p { font: oblique 25deg 10px serif; }"sv); + auto rules = css::parse("p { font: oblique 25deg 10px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -623,7 +630,7 @@ int main() { }); etest::test("parser: shorthand font with bold font weight", [] { - auto rules = css::parse("p { font: italic bold 20em/50% serif; }"sv); + auto rules = css::parse("p { font: italic bold 20em/50% serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -637,7 +644,7 @@ int main() { }); etest::test("parser: shorthand font with bolder font weight", [] { - auto rules = css::parse("p { font: normal bolder 100px serif; }"sv); + auto rules = css::parse("p { font: normal bolder 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -649,7 +656,7 @@ int main() { }); etest::test("parser: shorthand font with lighter font weight", [] { - auto rules = css::parse("p { font: lighter 100px serif; }"sv); + auto rules = css::parse("p { font: lighter 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -661,7 +668,7 @@ int main() { }); etest::test("parser: shorthand font with 1000 font weight", [] { - auto rules = css::parse("p { font: 1000 oblique 100px serif; }"sv); + auto rules = css::parse("p { font: 1000 oblique 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -674,7 +681,7 @@ int main() { }); etest::test("parser: shorthand font with 550 font weight", [] { - auto rules = css::parse("p { font: italic 550 100px serif; }"sv); + auto rules = css::parse("p { font: italic 550 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -687,7 +694,7 @@ int main() { }); etest::test("parser: shorthand font with 1 font weight", [] { - auto rules = css::parse("p { font: oblique 1 100px serif; }"sv); + auto rules = css::parse("p { font: oblique 1 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -700,7 +707,7 @@ int main() { }); etest::test("parser: shorthand font with smal1-caps font variant", [] { - auto rules = css::parse("p { font: small-caps 900 100px serif; }"sv); + auto rules = css::parse("p { font: small-caps 900 100px serif; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -713,7 +720,7 @@ int main() { }); etest::test("parser: shorthand font with condensed font stretch", [] { - auto rules = css::parse(R"(p { font: condensed oblique 25deg 753 12pt "Helvetica Neue", serif; })"sv); + auto rules = css::parse(R"(p { font: condensed oblique 25deg 753 12pt "Helvetica Neue", serif; })"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -727,7 +734,7 @@ int main() { }); etest::test("parser: shorthand font with exapnded font stretch", [] { - auto rules = css::parse("p { font: italic expanded bold xx-smal/80% monospace; }"sv); + auto rules = css::parse("p { font: italic expanded bold xx-smal/80% monospace; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -742,7 +749,7 @@ int main() { }); etest::test("parser: font, single-argument", [] { - auto rules = css::parse("p { font: status-bar; }"sv); + auto rules = css::parse("p { font: status-bar; }"sv).rules; require(rules.size() == 1); auto p = rules[0]; @@ -751,7 +758,7 @@ int main() { }); etest::test("parser: shorthand font with ultra-exapnded font stretch", [] { - auto rules = css::parse("p { font: small-caps italic ultra-expanded bold medium Arial, monospace; }"sv); + auto rules = css::parse("p { font: small-caps italic ultra-expanded bold medium Arial, monospace; }"sv).rules; require(rules.size() == 1); auto body = rules[0]; @@ -766,7 +773,7 @@ int main() { }); etest::test("parser: border-radius shorthand, 1 value", [] { - auto rules = css::parse("div { border-radius: 5px; }"); + auto rules = css::parse("div { border-radius: 5px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -779,7 +786,7 @@ int main() { }); etest::test("parser: border-radius shorthand, 2 values", [] { - auto rules = css::parse("div { border-radius: 1px 2px; }"); + auto rules = css::parse("div { border-radius: 1px 2px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -792,7 +799,7 @@ int main() { }); etest::test("parser: border-radius shorthand, 3 values", [] { - auto rules = css::parse("div { border-radius: 1px 2px 3px; }"); + auto rules = css::parse("div { border-radius: 1px 2px 3px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -805,7 +812,7 @@ int main() { }); etest::test("parser: border-radius shorthand, 4 values", [] { - auto rules = css::parse("div { border-radius: 1px 2px 3px 4px; }"); + auto rules = css::parse("div { border-radius: 1px 2px 3px 4px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -818,7 +825,7 @@ int main() { }); etest::test("parser: border-radius, 1 value, separate horizontal and vertical", [] { - auto rules = css::parse("div { border-radius: 5px / 10px; }"); + auto rules = css::parse("div { border-radius: 5px / 10px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -831,7 +838,7 @@ int main() { }); etest::test("parser: border-radius, 2 values, separate horizontal and vertical", [] { - auto rules = css::parse("div { border-radius: 5px / 10px 15px; }"); + auto rules = css::parse("div { border-radius: 5px / 10px 15px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -844,7 +851,7 @@ int main() { }); etest::test("parser: border-radius, 3 values, separate horizontal and vertical", [] { - auto rules = css::parse("div { border-radius: 5px / 10px 15px 20px; }"); + auto rules = css::parse("div { border-radius: 5px / 10px 15px 20px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -857,7 +864,7 @@ int main() { }); etest::test("parser: border-radius, 4 values, separate horizontal and vertical", [] { - auto rules = css::parse("div { border-radius: 5px / 10px 15px 20px 25px; }"); + auto rules = css::parse("div { border-radius: 5px / 10px 15px 20px 25px; }").rules; require(rules.size() == 1); auto const &div = rules[0]; expect_eq(div.declarations, @@ -882,7 +889,7 @@ int main() { })"sv; // No rules produced (yet!) since this isn't handled aside from not crashing. - auto rules = css::parse(css); + auto rules = css::parse(css).rules; expect(rules.empty()); }); @@ -898,7 +905,7 @@ int main() { })"sv; // No rules produced (yet!) since this isn't handled aside from not crashing. - auto rules = css::parse(css); + auto rules = css::parse(css).rules; expect(rules.empty()); }); @@ -911,7 +918,7 @@ int main() { url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); })"sv; - auto rules = css::parse(css); + auto rules = css::parse(css).rules; expect_eq(rules.size(), std::size_t{1}); expect_eq(rules[0].selectors, std::vector{"@font-face"s}); expect_eq(rules[0].declarations.size(), std::size_t{2}); @@ -924,7 +931,7 @@ int main() { }); etest::test("parser: border shorthand, all values", [] { - auto rules = css::parse("p { border: 5px black solid; }"); + auto rules = css::parse("p { border: 5px black solid; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, @@ -945,7 +952,7 @@ int main() { }); etest::test("parser: border shorthand, color+style", [] { - auto rules = css::parse("p { border-bottom: #123 dotted; }"); + auto rules = css::parse("p { border-bottom: #123 dotted; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, @@ -957,7 +964,7 @@ int main() { }); etest::test("parser: border shorthand, width+style", [] { - auto rules = css::parse("p { border-left: ridge 30em; }"); + auto rules = css::parse("p { border-left: ridge 30em; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, @@ -969,7 +976,7 @@ int main() { }); etest::test("parser: border shorthand, width", [] { - auto rules = css::parse("p { border-right: thin; }"); + auto rules = css::parse("p { border-right: thin; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, @@ -981,7 +988,7 @@ int main() { }); etest::test("parser: border shorthand, width, first character a dot", [] { - auto rules = css::parse("p { border-right: .3em; }"); + auto rules = css::parse("p { border-right: .3em; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, @@ -993,7 +1000,7 @@ int main() { }); etest::test("parser: border shorthand, too many values", [] { - auto rules = css::parse("p { border-top: outset #123 none solid; }"); + auto rules = css::parse("p { border-top: outset #123 none solid; }").rules; require(rules.size() == 1); auto const &p = rules[0]; expect_eq(p.declarations, std::map{}); diff --git a/css/style_sheet.h b/css/style_sheet.h new file mode 100644 index 00000000..d5251abc --- /dev/null +++ b/css/style_sheet.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2021-2023 Robin Lindén +// +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef CSS_STYLE_SHEET_H_ +#define CSS_STYLE_SHEET_H_ + +#include "css/rule.h" + +#include +#include + +namespace css { + +struct StyleSheet { + std::vector rules; + [[nodiscard]] bool operator==(StyleSheet const &) const = default; + + void splice(StyleSheet &&other) { + rules.reserve(rules.size() + other.rules.size()); + rules.insert( + end(rules), std::make_move_iterator(begin(other.rules)), std::make_move_iterator(end(other.rules))); + } +}; + +} // namespace css + +#endif diff --git a/css/style_sheet_test.cpp b/css/style_sheet_test.cpp new file mode 100644 index 00000000..4b1718cd --- /dev/null +++ b/css/style_sheet_test.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 Robin Lindén +// +// SPDX-License-Identifier: BSD-2-Clause + +#include "css/style_sheet.h" + +#include "etest/etest2.h" + +#include +#include + +int main() { + etest::Suite s; + + s.add_test("StyleSheet::splice", [](etest::IActions &a) { + css::StyleSheet a1; + a1.rules.push_back({.selectors = {"a"}}); + a1.rules.push_back({.selectors = {"b"}}); + css::StyleSheet a2; + a2.rules.push_back({.selectors = {"c"}}); + a2.rules.push_back({.selectors = {"d"}}); + + a1.splice(std::move(a2)); + a.expect_eq(a1.rules, std::vector{{{"a"}}, {{"b"}}, {{"c"}}, {{"d"}}}); + }); + + return s.run(); +} diff --git a/engine/engine.cpp b/engine/engine.cpp index 166a0b74..e4ce7f5a 100644 --- a/engine/engine.cpp +++ b/engine/engine.cpp @@ -13,7 +13,6 @@ #include #include -#include #include using namespace std::literals; @@ -84,10 +83,7 @@ void Engine::on_navigation_success() { // Style can only contain text, and we enforce this in our HTML parser. auto const &style_content = std::get(style->children[0]); - auto new_rules = css::parse(style_content.text); - stylesheet_.reserve(stylesheet_.size() + new_rules.size()); - stylesheet_.insert( - end(stylesheet_), std::make_move_iterator(begin(new_rules)), std::make_move_iterator(end(new_rules))); + stylesheet_.splice(css::parse(style_content.text)); } auto head_links = dom::nodes_by_xpath(dom_.html(), "/html/head/link"); @@ -99,10 +95,10 @@ void Engine::on_navigation_success() { // Start downloading all stylesheets. spdlog::info("Loading {} stylesheets", head_links.size()); - std::vector>> future_new_rules; + std::vector> future_new_rules; future_new_rules.reserve(head_links.size()); for (auto const *link : head_links) { - future_new_rules.push_back(std::async(std::launch::async, [=, this]() -> std::vector { + future_new_rules.push_back(std::async(std::launch::async, [=, this]() -> css::StyleSheet { auto const &href = link->attributes.at("href"); auto stylesheet_url = uri::Uri::parse(href, uri_); @@ -149,13 +145,10 @@ void Engine::on_navigation_success() { // In order, wait for the download to finish and merge with the big stylesheet. for (auto &future_rules : future_new_rules) { - auto rules = future_rules.get(); - stylesheet_.reserve(stylesheet_.size() + rules.size()); - stylesheet_.insert( - end(stylesheet_), std::make_move_iterator(begin(rules)), std::make_move_iterator(end(rules))); + stylesheet_.splice(future_rules.get()); } - spdlog::info("Styling dom w/ {} rules", stylesheet_.size()); + spdlog::info("Styling dom w/ {} rules", stylesheet_.rules.size()); styled_ = style::style_tree(dom_.html_node, stylesheet_, {.window_width = layout_width_}); layout_ = layout::create_layout(*styled_, layout_width_, whitespace_mode_); on_page_loaded_(); diff --git a/engine/engine.h b/engine/engine.h index 2890d8c4..b47fdb85 100644 --- a/engine/engine.h +++ b/engine/engine.h @@ -6,7 +6,7 @@ #ifndef ENGINE_ENGINE_H_ #define ENGINE_ENGINE_H_ -#include "css/rule.h" +#include "css/style_sheet.h" #include "dom/dom.h" #include "layout/layout.h" #include "protocol/iprotocol_handler.h" @@ -39,7 +39,7 @@ class Engine { uri::Uri const &uri() const { return uri_; } protocol::Response const &response() const { return response_; } dom::Document const &dom() const { return dom_; } - std::vector const &stylesheet() const { return stylesheet_; } + css::StyleSheet const &stylesheet() const { return stylesheet_; } layout::LayoutBox const *layout() const { return layout_.has_value() ? &*layout_ : nullptr; } private: @@ -58,7 +58,7 @@ class Engine { uri::Uri uri_{}; protocol::Response response_{}; dom::Document dom_{}; - std::vector stylesheet_{}; + css::StyleSheet stylesheet_{}; std::unique_ptr styled_{}; std::optional layout_{}; diff --git a/engine/engine_test.cpp b/engine/engine_test.cpp index ad991b00..6982eee7 100644 --- a/engine/engine_test.cpp +++ b/engine/engine_test.cpp @@ -63,7 +63,7 @@ int main() { }}; engine::Engine e{std::make_unique(std::move(responses))}; e.navigate(uri::Uri::parse("hax://example.com")); - expect_eq(e.stylesheet().back(), + expect_eq(e.stylesheet().rules.back(), css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, @@ -200,8 +200,8 @@ int main() { }; engine::Engine e{std::make_unique(std::move(responses))}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(contains(e.stylesheet(), {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}})); - expect(contains(e.stylesheet(), {.selectors{"p"}, .declarations{{css::PropertyId::Color, "green"}}})); + expect(contains(e.stylesheet().rules, {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}})); + expect(contains(e.stylesheet().rules, {.selectors{"p"}, .declarations{{css::PropertyId::Color, "green"}}})); }); etest::test("stylesheet link, unsupported Content-Encoding", [] { @@ -219,7 +219,7 @@ int main() { }; engine::Engine e{std::make_unique(std::move(responses))}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(!contains(e.stylesheet(), {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}})); + expect(!contains(e.stylesheet().rules, {.selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}})); }); // p { font-size: 123em; }, gzipped. @@ -244,12 +244,12 @@ int main() { }; engine::Engine e{std::make_unique(responses)}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - != end(e.stylesheet())); + != end(e.stylesheet().rules)); // And again, but with x-gzip instead. responses["hax://example.com/lol.css"s] = Response{ @@ -260,12 +260,12 @@ int main() { }; e = engine::Engine{std::make_unique(responses)}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - != end(e.stylesheet())); + != end(e.stylesheet().rules)); }); etest::test("stylesheet link, gzip Content-Encoding, bad header", [gzipped_css]() mutable { @@ -285,12 +285,12 @@ int main() { }; engine::Engine e{std::make_unique(std::move(responses))}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - == end(e.stylesheet())); + == end(e.stylesheet().rules)); }); etest::test("stylesheet link, gzip Content-Encoding, crc32 mismatch", [gzipped_css]() mutable { @@ -310,12 +310,12 @@ int main() { }; engine::Engine e{std::make_unique(std::move(responses))}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - == end(e.stylesheet())); + == end(e.stylesheet().rules)); }); etest::test("stylesheet link, gzip Content-Encoding, served zlib", [zlibbed_css] { @@ -333,12 +333,12 @@ int main() { }; engine::Engine e{std::make_unique(responses)}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - == end(e.stylesheet())); + == end(e.stylesheet().rules)); }); etest::test("stylesheet link, deflate Content-Encoding", [zlibbed_css] { @@ -356,12 +356,12 @@ int main() { }; engine::Engine e{std::make_unique(responses)}; e.navigate(uri::Uri::parse("hax://example.com")); - expect(std::ranges::find(e.stylesheet(), + expect(std::ranges::find(e.stylesheet().rules, css::Rule{ .selectors{"p"}, .declarations{{css::PropertyId::FontSize, "123em"}}, }) - != end(e.stylesheet())); + != end(e.stylesheet().rules)); }); etest::test("redirect", [] { diff --git a/style/style.cpp b/style/style.cpp index ffe306ba..9503a5d5 100644 --- a/style/style.cpp +++ b/style/style.cpp @@ -129,10 +129,10 @@ bool is_match(style::StyledNode const &node, std::string_view selector) { } std::vector> matching_rules( - style::StyledNode const &node, std::vector const &stylesheet, css::MediaQuery::Context const &ctx) { + style::StyledNode const &node, css::StyleSheet const &stylesheet, css::MediaQuery::Context const &ctx) { std::vector> matched_rules; - for (auto const &rule : stylesheet) { + for (auto const &rule : stylesheet.rules) { if (rule.media_query.has_value() && !rule.media_query->evaluate(ctx)) { continue; } @@ -147,7 +147,7 @@ std::vector> matching_rules( if (style_attr != element->attributes.end()) { // TODO(robinlinden): Incredibly hacky, but our //css parser doesn't support // parsing only declarations. Replace with the //css2 parser once possible. - auto element_style = css::parse("dummy{"s + style_attr->second + "}"s); + auto element_style = css::parse("dummy{"s + style_attr->second + "}"s).rules; // The above should always parse to 1 rule when using the old parser. assert(element_style.size() == 1); if (element_style.size() == 1) { @@ -162,7 +162,7 @@ std::vector> matching_rules( namespace { void style_tree_impl(StyledNode ¤t, dom::Node const &root, - std::vector const &stylesheet, + css::StyleSheet const &stylesheet, css::MediaQuery::Context const &ctx) { auto const *element = std::get_if(&root); if (element == nullptr) { @@ -183,7 +183,7 @@ void style_tree_impl(StyledNode ¤t, } // namespace std::unique_ptr style_tree( - dom::Node const &root, std::vector const &stylesheet, css::MediaQuery::Context const &ctx) { + dom::Node const &root, css::StyleSheet const &stylesheet, css::MediaQuery::Context const &ctx) { // TODO(robinlinden): std::make_unique once Clang supports it (C++20/p0960). Not supported as of Clang 14. auto tree_root = std::unique_ptr(new StyledNode{root}); style_tree_impl(*tree_root, root, stylesheet, ctx); diff --git a/style/style.h b/style/style.h index 26086354..81a8109d 100644 --- a/style/style.h +++ b/style/style.h @@ -7,7 +7,7 @@ #include "css/media_query.h" #include "css/property_id.h" -#include "css/rule.h" +#include "css/style_sheet.h" #include "dom/dom.h" #include "style/styled_node.h" @@ -22,20 +22,19 @@ namespace style { bool is_match(StyledNode const &, std::string_view selector); std::vector> matching_rules( - StyledNode const &, std::vector const &stylesheet, css::MediaQuery::Context const &); + StyledNode const &, css::StyleSheet const &stylesheet, css::MediaQuery::Context const &); inline bool is_match(dom::Element const &e, std::string_view selector) { return is_match(StyledNode{e}, selector); } -inline std::vector> matching_rules(dom::Element const &element, - std::vector const &stylesheet, - css::MediaQuery::Context const &context = {}) { +inline std::vector> matching_rules( + dom::Element const &element, css::StyleSheet const &stylesheet, css::MediaQuery::Context const &context = {}) { return matching_rules(StyledNode{element}, stylesheet, context); } std::unique_ptr style_tree( - dom::Node const &root, std::vector const &stylesheet, css::MediaQuery::Context const & = {}); + dom::Node const &root, css::StyleSheet const &stylesheet, css::MediaQuery::Context const & = {}); } // namespace style diff --git a/style/style_test.cpp b/style/style_test.cpp index fd14b180..650ec286 100644 --- a/style/style_test.cpp +++ b/style/style_test.cpp @@ -6,6 +6,7 @@ #include "style/styled_node.h" #include "css/rule.h" +#include "css/style_sheet.h" #include "etest/etest.h" #include @@ -40,7 +41,7 @@ void inline_css_tests() { etest::test("inline css: overrides the stylesheet", [] { dom::Node dom = dom::Element{"div", {{"style", {"font-size:2px"}}}}; - auto styled = style::style_tree(dom, {css::Rule{{"div"}, {{css::PropertyId::FontSize, "2000px"}}}}, {}); + auto styled = style::style_tree(dom, {{css::Rule{{"div"}, {{css::PropertyId::FontSize, "2000px"}}}}}, {}); // The last property is the one that's applied. expect_eq(styled->properties, @@ -139,10 +140,11 @@ int main() { }); etest::test("matching_rules: simple names", [] { - std::vector stylesheet; + css::StyleSheet stylesheet; expect(style::matching_rules(dom::Element{"div"}, stylesheet).empty()); - stylesheet.push_back(css::Rule{.selectors = {"span", "p"}, .declarations = {{css::PropertyId::Width, "80px"}}}); + stylesheet.rules.push_back( + css::Rule{.selectors = {"span", "p"}, .declarations = {{css::PropertyId::Width, "80px"}}}); expect(style::matching_rules(dom::Element{"div"}, stylesheet).empty()); @@ -158,7 +160,7 @@ int main() { expect(p_rules[0] == std::pair{css::PropertyId::Width, "80px"s}); } - stylesheet.push_back( + stylesheet.rules.push_back( css::Rule{.selectors = {"span", "hr"}, .declarations = {{css::PropertyId::Height, "auto"}}}); expect(style::matching_rules(dom::Element{"div"}, stylesheet).empty()); @@ -184,14 +186,14 @@ int main() { }); etest::test("matching_rules: media query", [] { - std::vector stylesheet{ + css::StyleSheet stylesheet{{ css::Rule{.selectors{"p"}, .declarations{{css::PropertyId::Color, "red"}}}, - }; + }}; expect_eq(style::matching_rules(dom::Element{"p"}, stylesheet), std::vector{std::pair{css::PropertyId::Color, "red"s}}); - stylesheet[0].media_query = css::MediaQuery::parse("(min-width: 700px)"); + stylesheet.rules[0].media_query = css::MediaQuery::parse("(min-width: 700px)"); expect(style::matching_rules(dom::Element{"p"}, stylesheet).empty()); expect_eq(style::matching_rules(dom::Element{"p"}, stylesheet, {.window_width = 700}), @@ -219,10 +221,10 @@ int main() { root.children.emplace_back(dom::Element{"head"}); root.children.emplace_back(dom::Element{"body", {}, {dom::Element{"p"}}}); - std::vector stylesheet{ + css::StyleSheet stylesheet{{ {.selectors = {"p"}, .declarations = {{css::PropertyId::Height, "100px"}}}, {.selectors = {"body"}, .declarations = {{css::PropertyId::FontSize, "500em"}}}, - }; + }}; style::StyledNode expected{root}; expected.children.push_back({root.children[0], {}, {}, &expected});