srctree

Robin Linden parent d5ed4fec 77e70324
css: Add a style sheet struct

This seems like a reasonable thing to do to support things like CSS variables and !important declarations.
browser/gui/app.cpp added: 181, removed: 153, total 28
@@ -91,9 +91,9 @@ std::string element_text(layout::LayoutBox const *element) {
return std::get<dom::Element>(element->node->node).name;
}
 
std::string stylesheet_to_string(std::vector<css::Rule> 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();
 
css/default.cpp added: 181, removed: 153, total 28
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2021-2022 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
@@ -13,7 +13,7 @@ namespace {
#include "css/default_css.h"
} // namespace
 
std::vector<css::Rule> default_style() {
StyleSheet default_style() {
return css::parse(std::string_view{reinterpret_cast<char const *>(css_default_css), css_default_css_len});
}
 
 
css/default.h added: 181, removed: 153, total 28
@@ -1,17 +1,15 @@
// SPDX-FileCopyrightText: 2021 Robin Lindén <dev@robinlinden.eu>
// SPDX-FileCopyrightText: 2021-2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef CSS_DEFAULT_H_
#define CSS_DEFAULT_H_
 
#include "css/rule.h"
 
#include <vector>
#include "css/style_sheet.h"
 
namespace css {
 
std::vector<css::Rule> default_style();
StyleSheet default_style();
 
} // namespace css
 
 
css/parser.cpp added: 181, removed: 153, total 28
@@ -241,8 +241,8 @@ constexpr std::optional<std::string_view> Parser::consume_while(T const &pred) {
return input_.substr(start, pos_ - start);
}
 
std::vector<css::Rule> Parser::parse_rules() {
std::vector<css::Rule> rules;
StyleSheet Parser::parse_rules() {
StyleSheet style;
bool in_media_query{false};
std::optional<MediaQuery> media_query;
 
@@ -255,7 +255,7 @@ std::vector<css::Rule> 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<css::Rule> 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<css::Rule> 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<css::Rule> 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<css::Rule> Parser::parse_rules() {
}
}
 
return rules;
return style;
}
 
constexpr std::optional<char> Parser::peek() const {
 
css/parser.h added: 181, removed: 153, total 28
@@ -8,13 +8,13 @@
 
#include "css/property_id.h"
#include "css/rule.h"
#include "css/style_sheet.h"
 
#include <concepts>
#include <cstddef>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
 
namespace css {
 
@@ -25,7 +25,7 @@ class Parser {
public:
explicit Parser(std::string_view input) : input_{input} {}
 
std::vector<css::Rule> parse_rules();
StyleSheet parse_rules();
 
private:
std::string_view input_;
@@ -77,7 +77,7 @@ private:
void expand_font(std::map<PropertyId, std::string> &declarations, std::string_view value) const;
};
 
inline std::vector<Rule> parse(std::string_view input) {
inline StyleSheet parse(std::string_view input) {
return Parser{input}.parse_rules();
}
 
 
css/parser_test.cpp added: 181, removed: 153, total 28
@@ -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<css::PropertyId, std::string>{
@@ -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<css::PropertyId, std::string>{});
});
@@ -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<css::Rule>{{
.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<std::string, 2> 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<std::string, 3> 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<css::PropertyId, std::string>{});
 
filename was Deleted added: 181, removed: 153, total 28
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef CSS_STYLE_SHEET_H_
#define CSS_STYLE_SHEET_H_
 
#include "css/rule.h"
 
#include <vector>
 
namespace css {
 
struct StyleSheet {
std::vector<Rule> rules;
[[nodiscard]] bool operator==(StyleSheet const &) const = default;
};
 
} // namespace css
 
#endif
 
engine/engine.cpp added: 181, removed: 153, total 28
@@ -84,10 +84,11 @@ void Engine::on_navigation_success() {
 
// Style can only contain text, and we enforce this in our HTML parser.
auto const &style_content = std::get<dom::Text>(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)));
auto new_rules = css::parse(style_content.text).rules;
stylesheet_.rules.reserve(stylesheet_.rules.size() + new_rules.size());
stylesheet_.rules.insert(end(stylesheet_.rules),
std::make_move_iterator(begin(new_rules)),
std::make_move_iterator(end(new_rules)));
}
 
auto head_links = dom::nodes_by_xpath(dom_.html(), "/html/head/link");
@@ -143,19 +144,19 @@ void Engine::on_navigation_success() {
return {};
}
 
return css::parse(style_data.body);
return css::parse(style_data.body).rules;
}));
}
 
// 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_.rules.reserve(stylesheet_.rules.size() + rules.size());
stylesheet_.rules.insert(
end(stylesheet_.rules), std::make_move_iterator(begin(rules)), std::make_move_iterator(end(rules)));
}
 
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_();
 
engine/engine.h added: 181, removed: 153, total 28
@@ -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 @@ public:
uri::Uri const &uri() const { return uri_; }
protocol::Response const &response() const { return response_; }
dom::Document const &dom() const { return dom_; }
std::vector<css::Rule> 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 @@ private:
uri::Uri uri_{};
protocol::Response response_{};
dom::Document dom_{};
std::vector<css::Rule> stylesheet_{};
css::StyleSheet stylesheet_{};
std::unique_ptr<style::StyledNode> styled_{};
std::optional<layout::LayoutBox> layout_{};
 
 
engine/engine_test.cpp added: 181, removed: 153, total 28
@@ -63,7 +63,7 @@ int main() {
}};
engine::Engine e{std::make_unique<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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<FakeProtocolHandler>(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", [] {
 
style/style.cpp added: 181, removed: 153, total 28
@@ -129,10 +129,10 @@ bool is_match(style::StyledNode const &node, std::string_view selector) {
}
 
std::vector<std::pair<css::PropertyId, std::string>> matching_rules(
style::StyledNode const &node, std::vector<css::Rule> const &stylesheet, css::MediaQuery::Context const &ctx) {
style::StyledNode const &node, css::StyleSheet const &stylesheet, css::MediaQuery::Context const &ctx) {
std::vector<std::pair<css::PropertyId, std::string>> 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<std::pair<css::PropertyId, std::string>> 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<std::pair<css::PropertyId, std::string>> matching_rules(
namespace {
void style_tree_impl(StyledNode &current,
dom::Node const &root,
std::vector<css::Rule> const &stylesheet,
css::StyleSheet const &stylesheet,
css::MediaQuery::Context const &ctx) {
auto const *element = std::get_if<dom::Element>(&root);
if (element == nullptr) {
@@ -183,7 +183,7 @@ void style_tree_impl(StyledNode &current,
} // namespace
 
std::unique_ptr<StyledNode> style_tree(
dom::Node const &root, std::vector<css::Rule> 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<StyledNode>(new StyledNode{root});
style_tree_impl(*tree_root, root, stylesheet, ctx);
 
style/style.h added: 181, removed: 153, total 28
@@ -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<std::pair<css::PropertyId, std::string>> matching_rules(
StyledNode const &, std::vector<css::Rule> 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<std::pair<css::PropertyId, std::string>> matching_rules(dom::Element const &element,
std::vector<css::Rule> const &stylesheet,
css::MediaQuery::Context const &context = {}) {
inline std::vector<std::pair<css::PropertyId, std::string>> 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<StyledNode> style_tree(
dom::Node const &root, std::vector<css::Rule> const &stylesheet, css::MediaQuery::Context const & = {});
dom::Node const &root, css::StyleSheet const &stylesheet, css::MediaQuery::Context const & = {});
 
} // namespace style
 
 
style/style_test.cpp added: 181, removed: 153, total 28
@@ -6,6 +6,7 @@
#include "style/styled_node.h"
 
#include "css/rule.h"
#include "css/style_sheet.h"
#include "etest/etest.h"
 
#include <fmt/format.h>
@@ -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<css::Rule> 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<css::Rule> 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<css::Rule> 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});