Skip to content

Commit 3bacdcb

Browse files
committed
vaev-engine: Initial implementation of pseudo elements.
1 parent e1323a9 commit 3bacdcb

File tree

16 files changed

+655
-491
lines changed

16 files changed

+655
-491
lines changed

src/vaev-engine/dom/element.cpp

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import :dom.attr;
66
import :dom.node;
77
import :dom.names;
88
import :dom.text;
9-
import :dom.token_list;
9+
import :dom.tokenList;
1010

1111
using namespace Karm;
1212

@@ -17,14 +17,25 @@ export struct SpecifiedValues;
1717

1818
namespace Vaev::Dom {
1919

20+
// https://drafts.csswg.org/css-pseudo/#CSSPseudoElement-interface
2021
export struct PseudoElement {
22+
// https://drafts.csswg.org/css-pseudo/#dom-csspseudoelement-type
23+
static Symbol const BEFORE;
24+
static Symbol const AFTER;
25+
static Symbol const MARKER;
26+
27+
Symbol type;
2128
Opt<Rc<Style::SpecifiedValues>> _specifiedValues = NONE;
2229

2330
Rc<Style::SpecifiedValues> specifiedValues() const {
2431
return _specifiedValues.unwrap("unstyled pseudo-element");
2532
}
2633
};
2734

35+
Symbol const PseudoElement::BEFORE = "::before"_sym;
36+
Symbol const PseudoElement::AFTER = "::after"_sym;
37+
Symbol const PseudoElement::MARKER = "::marker"_sym;
38+
2839
// https://dom.spec.whatwg.org/#interface-element
2940
export struct Element : Node {
3041
static constexpr auto TYPE = NodeType::ELEMENT;
@@ -35,45 +46,18 @@ export struct Element : Node {
3546
Opt<Rc<Style::SpecifiedValues>> _specifiedValues; // FIXME: We should not have this store here
3647
TokenList classList;
3748
Opt<Rc<Scene::Node>> imageContent;
49+
Map<Symbol, Rc<PseudoElement>> _pseudoElements;
50+
51+
// MARK: Node --------------------------------------------------------------
3852

3953
Element(QualifiedName const& qualifiedName)
4054
: qualifiedName(qualifiedName) {
4155
}
4256

43-
Symbol namespaceUri() const {
44-
return qualifiedName.ns;
45-
}
46-
47-
Symbol localName() const {
48-
return qualifiedName.name;
49-
}
50-
5157
NodeType nodeType() const override {
5258
return TYPE;
5359
}
5460

55-
Rc<Style::SpecifiedValues> specifiedValues() const {
56-
return _specifiedValues.unwrap("unstyled element");
57-
}
58-
59-
Opt<Str> id() const {
60-
return this->getAttributeUnqualified("id"_sym);
61-
}
62-
63-
Opt<Str> style() const {
64-
return this->getAttributeUnqualified("style"_sym);
65-
}
66-
67-
// https://dom.spec.whatwg.org/#concept-descendant-text-content
68-
String textContent() {
69-
StringBuilder sb;
70-
for (auto child : iterDepthFirst()) {
71-
if (auto text = child->is<Text>())
72-
sb.append(text->data());
73-
}
74-
return sb.take();
75-
}
76-
7761
void _repr(Io::Emit& e) const override {
7862
e(" qualifiedName={}", qualifiedName);
7963
if (this->attributes.len()) {
@@ -85,6 +69,26 @@ export struct Element : Node {
8569
}
8670
}
8771

72+
// MARK: Name --------------------------------------------------------------
73+
74+
Symbol namespaceUri() const {
75+
return qualifiedName.ns;
76+
}
77+
78+
Symbol localName() const {
79+
return qualifiedName.name;
80+
}
81+
82+
// MARK: Attributes --------------------------------------------------------
83+
84+
Opt<Str> id() const {
85+
return getAttributeUnqualified("id"_sym);
86+
}
87+
88+
Opt<Str> style() const {
89+
return getAttributeUnqualified("style"_sym);
90+
}
91+
8892
void setAttribute(QualifiedName name, String value) {
8993
if (name == Html::CLASS_ATTR or name == Svg::CLASS_ATTR) {
9094
for (auto class_ : iterSplit(value, ' ')) {
@@ -121,6 +125,42 @@ export struct Element : Node {
121125
return attr->value;
122126
return NONE;
123127
}
128+
129+
// MARK: Style -------------------------------------------------------------
130+
131+
Rc<Style::SpecifiedValues> specifiedValues() const {
132+
return _specifiedValues.unwrap("unstyled element");
133+
}
134+
135+
// MARK: Content -----------------------------------------------------------
136+
137+
// https://dom.spec.whatwg.org/#concept-descendant-text-content
138+
String textContent() {
139+
StringBuilder sb;
140+
for (auto child : iterDepthFirst()) {
141+
if (auto text = child->is<Text>())
142+
sb.append(text->data());
143+
}
144+
return sb.take();
145+
}
146+
147+
// MARK: Pseudo Elements ---------------------------------------------------
148+
149+
void clearPseudoElement() {
150+
_pseudoElements.clear();
151+
}
152+
153+
bool hasPseudoElement(Symbol type) const {
154+
return _pseudoElements.has(type);
155+
}
156+
157+
void addPseudoElement(Rc<PseudoElement> pseudoElement) {
158+
_pseudoElements.put(pseudoElement->type, pseudoElement);
159+
}
160+
161+
Opt<Rc<PseudoElement>> getPseudoElement(Symbol type) const {
162+
return _pseudoElements.tryGet(type);
163+
}
124164
};
125165

126166
} // namespace Vaev::Dom

src/vaev-engine/dom/mod.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ export import :dom.node;
1010
export import :dom.names;
1111
export import :dom.node;
1212
export import :dom.text;
13-
export import :dom.token_list;
13+
export import :dom.tokenList;
1414
export import :dom.tree;
1515
export import :dom.window;

src/vaev-engine/dom/token-list.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export module Vaev.Engine:dom.token_list;
1+
export module Vaev.Engine:dom.tokenList;
22

33
import Karm.Core;
44

src/vaev-engine/layout/builder/mod.cpp

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ struct BuilderContext {
170170

171171
// https://www.w3.org/TR/css-inline-3/#model
172172
void flushRootInlineBoxIntoAnonymousBox() {
173-
if (not rootInlineBox().active())
173+
if (not _rootInlineBox or not rootInlineBox().active())
174174
return;
175175

176176
// The root inline box inherits from its parent block container, but is otherwise unstyleable.
@@ -289,6 +289,7 @@ struct BuilderContext {
289289
};
290290

291291
static void _buildNode(BuilderContext bc, Gc::Ref<Dom::Node> node);
292+
static void _buildPseudoElement(BuilderContext bc, Rc<Dom::PseudoElement> pseudoElement);
292293

293294
// MARK: Build void/leaves ---------------------------------------------------------
294295

@@ -424,11 +425,6 @@ bool _alwaysInlineBlock(Gc::Ref<Dom::Element> el) {
424425
static void _buildChildren(BuilderContext bc, Gc::Ref<Dom::Node> parent);
425426

426427
static void createAndBuildInlineFlowfromElement(BuilderContext bc, Rc<Style::SpecifiedValues> style, Gc::Ref<Dom::Element> el) {
427-
if (el->qualifiedName == Html::BR_TAG) {
428-
bc.flushRootInlineBoxIntoAnonymousBox();
429-
return;
430-
}
431-
432428
if (isVoidElement(el)) {
433429
_buildVoidElement(bc, el);
434430
return;
@@ -442,9 +438,7 @@ static void createAndBuildInlineFlowfromElement(BuilderContext bc, Rc<Style::Spe
442438
}
443439

444440
static void buildBlockFlowFromElement(BuilderContext bc, Gc::Ref<Dom::Element> el) {
445-
if (el->qualifiedName == Html::BR_TAG) {
446-
// do nothing
447-
} else if (el->qualifiedName == Svg::SVG_TAG) {
441+
if (el->qualifiedName == Svg::SVG_TAG) {
448442
bc.content() = _buildSVG(el);
449443
} else if (isVoidElement(el)) {
450444
_buildVoidElement(bc, el);
@@ -668,6 +662,10 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
668662
searchAndBuildCaption();
669663
}
670664

665+
if (auto before = el->getPseudoElement(Dom::PseudoElement::BEFORE)) {
666+
_buildPseudoElement(tableWrapperBc, before.unwrap());
667+
}
668+
671669
// An anonymous table-row box must be generated around each sequence of consecutive children of a table-root
672670
// box which are not proper table child boxes.
673671
_buildTableChildrenWhileWrappingIntoAnonymousBox(tableWrapperBc.toTableContent(tableBox), *el, tableBoxStyle, true, [](Display const& display) {
@@ -678,6 +676,10 @@ static void _buildTableBox(BuilderContext tableWrapperBc, Gc::Ref<Dom::Element>
678676
if (not captionsOnTop) {
679677
searchAndBuildCaption();
680678
}
679+
680+
if (auto before = el->getPseudoElement(Dom::PseudoElement::AFTER)) {
681+
_buildPseudoElement(tableWrapperBc, before.unwrap());
682+
}
681683
}
682684

683685
static Box _createTableWrapperAndBuildTable(BuilderContext bc, Rc<Style::SpecifiedValues> tableStyle, Gc::Ref<Dom::Element> tableBoxEl) {
@@ -742,9 +744,18 @@ static void _innerDisplayDispatchCreationOfInlineLevelBox(BuilderContext bc, Gc:
742744
// MARK: Dispatching from Node to builder based on outside role ------------------------------------------------------
743745

744746
static void _buildChildren(BuilderContext bc, Gc::Ref<Dom::Node> parent) {
747+
auto el = parent->is<Dom::Element>();
748+
if (auto before = el ? el->getPseudoElement(Dom::PseudoElement::BEFORE) : NONE) {
749+
_buildPseudoElement(bc, before.unwrap());
750+
}
751+
745752
for (auto child = parent->firstChild(); child; child = child->nextSibling()) {
746753
_buildNode(bc, *child);
747754
}
755+
756+
if (auto after = el ? el->getPseudoElement(Dom::PseudoElement::AFTER) : NONE) {
757+
_buildPseudoElement(bc, after.unwrap());
758+
}
748759
}
749760

750761
static void _buildChildBoxDisplay(BuilderContext bc, Gc::Ref<Dom::Node> node, Display display) {
@@ -801,6 +812,29 @@ static void _buildNode(BuilderContext bc, Gc::Ref<Dom::Node> node) {
801812
}
802813
}
803814

815+
export Box _buildBlockPseudoElement(Dom::PseudoElement& el) {
816+
auto proseStyle = _proseStyleFromStyle(*el.specifiedValues(), el.specifiedValues()->fontFace);
817+
818+
if (el.specifiedValues()->content.is<String>()) {
819+
auto prose = makeRc<Gfx::Prose>(proseStyle);
820+
prose->append(el.specifiedValues()->content.unwrap<String>().str());
821+
return {el.specifiedValues(), InlineBox{prose}, nullptr};
822+
}
823+
824+
return {el.specifiedValues(), nullptr};
825+
}
826+
827+
static void _buildPseudoElement(BuilderContext bc, Rc<Dom::PseudoElement> pseudoElement) {
828+
auto style = pseudoElement->specifiedValues();
829+
auto display = style->display;
830+
if (display == Display::NONE)
831+
return;
832+
833+
// FIXME: Treat all pseudo element as block
834+
bc.flushRootInlineBoxIntoAnonymousBox();
835+
bc.addToParentBox(_buildBlockPseudoElement(*pseudoElement));
836+
}
837+
804838
// MARK: Entry points -----------------------------------------------------------------
805839

806840
static auto dumpBoxes = Debug::Flag::debug("web-boxes"s, "Dump the constructed boxes"s);

src/vaev-engine/style/computer.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import :dom.document;
1111
import :dom.element;
1212
import :style.specified;
1313
import :style.stylesheet;
14+
import :style.ruleIndex;
1415

1516
namespace Vaev::Style {
1617

1718
export struct Computer {
1819
Media _media;
1920
StyleSheetList const& _stylesheets;
2021
Rc<Font::Database> _fontDatabase;
21-
StyleRuleLookup _styleRuleLookup = {};
22+
RuleIndex _ruleIndex = {};
2223

2324
// MARK: Cascading ---------------------------------------------------------
2425

@@ -244,7 +245,7 @@ export struct Computer {
244245

245246
// https://drafts.csswg.org/css-cascade/#cascade-origin
246247
Rc<SpecifiedValues> computeFor(SpecifiedValues const& parent, Gc::Ref<Dom::Element> el) {
247-
MatchingRules matchingRules = _styleRuleLookup.buildMatchingRules(el);
248+
MatchingRules matchingRules = _ruleIndex.match(el, NONE);
248249

249250
// Non-CSS Presentational Hints
250251
auto hints = _considerPresentationalHint(el);
@@ -278,6 +279,11 @@ export struct Computer {
278279
return values;
279280
}
280281

282+
Rc<SpecifiedValues> computeFor(SpecifiedValues const& parent, Gc::Ref<Dom::Element> el, Symbol pseudoElement) {
283+
MatchingRules matchingRules = _ruleIndex.match(el, pseudoElement);
284+
return _evalCascade(parent, matchingRules);
285+
}
286+
281287
Rc<PageSpecifiedValues> computeFor(SpecifiedValues const& parent, Page const& page) {
282288
auto computed = makeRc<PageSpecifiedValues>(parent);
283289

@@ -295,6 +301,35 @@ export struct Computer {
295301

296302
// MARK: Styling -----------------------------------------------------------
297303

304+
void generatePseudoElement(SpecifiedValues const& parentSpecifiedValues, Dom::Element& el, Symbol type) {
305+
auto specifiedValues = computeFor(parentSpecifiedValues, el, type);
306+
307+
// HACK: This is basically nonsense to avoid doing too much font lookup,
308+
// and it should be remove once the style engine get refactored
309+
// and computed values are properly handled.
310+
if (not parentSpecifiedValues.font.sameInstance(specifiedValues->font) and
311+
(parentSpecifiedValues.font->families != specifiedValues->font->families or
312+
parentSpecifiedValues.font->weight != specifiedValues->font->weight)) {
313+
auto font = _lookupFontface(*specifiedValues);
314+
specifiedValues->fontFace = font;
315+
} else {
316+
specifiedValues->fontFace = parentSpecifiedValues.fontFace;
317+
}
318+
319+
// https://drafts.csswg.org/css-content/#valdef-content-none
320+
// On pseudo-elements it inhibits the creation of the pseudo-element as if it had display: none.
321+
if (specifiedValues->content == Keywords::NONE)
322+
return;
323+
324+
// https://drafts.csswg.org/css-content/#valdef-content-normal
325+
if (specifiedValues->content == Keywords::NORMAL and
326+
(type == Dom::PseudoElement::BEFORE or
327+
type == Dom::PseudoElement::AFTER))
328+
return;
329+
330+
el.addPseudoElement(makeRc<Dom::PseudoElement>(type, specifiedValues));
331+
}
332+
298333
void styleElement(SpecifiedValues const& parentSpecifiedValues, Dom::Element& el) {
299334
auto specifiedValues = computeFor(parentSpecifiedValues, el);
300335
el._specifiedValues = specifiedValues;
@@ -311,6 +346,13 @@ export struct Computer {
311346
specifiedValues->fontFace = parentSpecifiedValues.fontFace;
312347
}
313348

349+
if (specifiedValues->display == Display::Item::YES) {
350+
generatePseudoElement(*specifiedValues, el, Dom::PseudoElement::MARKER);
351+
}
352+
353+
generatePseudoElement(*specifiedValues, el, Dom::PseudoElement::AFTER);
354+
generatePseudoElement(*specifiedValues, el, Dom::PseudoElement::BEFORE);
355+
314356
for (auto child = el.firstChild(); child; child = child->nextSibling()) {
315357
if (auto childEl = child->is<Dom::Element>())
316358
styleElement(*specifiedValues, *childEl);
@@ -337,7 +379,7 @@ export struct Computer {
337379
void _addRuleToLookup(Cursor<Rule> rule) {
338380
rule->visit(Visitor{
339381
[&](StyleRule const& r) {
340-
_styleRuleLookup.add(r);
382+
_ruleIndex.add(r);
341383
},
342384
[&](MediaRule const& r) {
343385
if (r.match(_media))

src/vaev-engine/style/defs/pseudo.inc renamed to src/vaev-engine/style/defs/pseudo-class.inc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,3 @@ PSEUDO(ONLY_OF_TYPE, "only-of-type"s)
5858

5959
PSEUDO(LANG, "lang"s)
6060
PSEUDO(DIR, "dir"s)
61-
62-
PSEUDO(BEFORE, "before"s)
63-
PSEUDO(AFTER, "after"s)
64-
PSEUDO(FIRST_LETTER, "first-letter"s)
65-
PSEUDO(FIRST_LINE, "first-line"s)

0 commit comments

Comments
 (0)