1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
import css/selectorparser
import css/sheet
import css/values
import html/dom
import html/tags
# Container to hold a style and a node.
# Pseudo-elements are implemented using StyledNode objects without nodes. Input
# elements are implemented as internal "pseudo-elements."
#
# To avoid having to invalidate the entire tree on pseudo-class changes, each
# node holds a list of nodes their CSS values depend on. (This list may include
# the node itself.) In addition, nodes also store each value valid for
# dependency d. These are then used for checking the validity of StyledNodes.
#
# In other words - say we have to apply the author stylesheets of the following
# document:
#
# <style>
# div:hover { color: red; }
# :not(input:checked) + p { display: none; }
# </style>
# <div>This div turns red on hover.</div>
# <input type=checkbox>
# <p>This paragraph is only shown when the checkbox above is checked.
#
# That produces the following dependency graph (simplified):
# div -> div (hover)
# p -> input (checked)
#
# Then, to check if a node has been invalidated, we just iterate over all
# recorded dependencies of each StyledNode, and check if their registered value
# of the pseudo-class still matches that of its associated element.
#
# So in our example, for div we check if div's :hover pseudo-class has changed,
# for p we check whether input's :checked pseudo-class has changed.
type
StyledType* = enum
STYLED_ELEMENT, STYLED_TEXT, STYLED_REPLACEMENT
DependencyType* = enum
DEPEND_HOVER, DEPEND_CHECKED, DEPEND_FOCUS
DependencyInfo* = object
# All nodes we depend on, for each dependency type d.
nodes*: array[DependencyType, seq[StyledNode]]
# Previous value. The owner StyledNode is marked as invalid when one of
# these no longer matches the DOM value.
prev: array[DependencyType, bool]
StyledNode* = ref object
parent*: StyledNode
node*: Node
pseudo*: PseudoElem
case t*: StyledType
of STYLED_TEXT:
text*: string
of STYLED_ELEMENT:
computed*: CSSComputedValues
children*: seq[StyledNode]
depends*: DependencyInfo
sheets*: seq[CSSStylesheet]
of STYLED_REPLACEMENT: # replaced elements: quotes, or (TODO) markers, images
content*: CSSContent
iterator branch*(node: StyledNode): StyledNode {.inline.} =
var node = node
while node != nil:
yield node
node = node.parent
iterator elementList*(node: StyledNode): StyledNode {.inline.} =
for child in node.children:
yield child
iterator elementList_rev*(node: StyledNode): StyledNode {.inline.} =
for i in countdown(node.children.high, 0):
yield node.children[i]
func findElement*(root: StyledNode, elem: Element): StyledNode =
var stack: seq[StyledNode]
for child in root.elementList_rev:
if child.t == STYLED_ELEMENT and child.pseudo == PSEUDO_NONE:
stack.add(child)
let en = Node(elem)
while stack.len > 0:
let node = stack.pop()
if node.node == en:
return node
for child in node.elementList_rev:
if child.t == STYLED_ELEMENT and child.pseudo == PSEUDO_NONE:
stack.add(child)
func isDomElement*(styledNode: StyledNode): bool {.inline.} =
styledNode.t == STYLED_ELEMENT and styledNode.pseudo == PSEUDO_NONE
func checked(element: Element): bool =
if element.tagType == TAG_INPUT:
let input = HTMLInputElement(element)
result = input.checked
func isValid*(styledNode: StyledNode): bool =
if styledNode.t == STYLED_TEXT:
return true
if styledNode.t == STYLED_REPLACEMENT:
return true
if styledNode.node != nil and Element(styledNode.node).invalid:
return false
for d in DependencyType:
for child in styledNode.depends.nodes[d]:
assert child.node != nil
let elem = Element(child.node)
case d
of DEPEND_HOVER:
if child.depends.prev[d] != elem.hover:
return false
of DEPEND_CHECKED:
if child.depends.prev[d] != elem.checked:
return false
of DEPEND_FOCUS:
let focus = elem.document.focus == elem
if child.depends.prev[d] != focus:
return false
return true
proc applyDependValues*(styledNode: StyledNode) =
let elem = Element(styledNode.node)
styledNode.depends.prev[DEPEND_HOVER] = elem.hover
styledNode.depends.prev[DEPEND_CHECKED] = elem.checked
let focus = elem.document.focus == elem
styledNode.depends.prev[DEPEND_FOCUS] = focus
elem.invalid = false
proc addDependency*(styledNode, dep: StyledNode, t: DependencyType) =
if dep notin styledNode.depends.nodes[t]:
styledNode.depends.nodes[t].add(dep)
func newStyledElement*(parent: StyledNode, element: Element, computed: CSSComputedValues, reg: DependencyInfo): StyledNode =
result = StyledNode(t: STYLED_ELEMENT, computed: computed, node: element, parent: parent)
result.depends = reg
result.parent = parent
func newStyledElement*(parent: StyledNode, element: Element): StyledNode =
result = StyledNode(t: STYLED_ELEMENT, node: element, parent: parent)
result.parent = parent
# Root
func newStyledElement*(element: Element): StyledNode =
result = StyledNode(t: STYLED_ELEMENT, node: element)
func newStyledElement*(parent: StyledNode, pseudo: PseudoElem, computed: CSSComputedValues, reg: sink DependencyInfo): StyledNode =
result = StyledNode(t: STYLED_ELEMENT, computed: computed, pseudo: pseudo, parent: parent)
result.depends = reg
result.parent = parent
func newStyledElement*(parent: StyledNode, pseudo: PseudoElem, computed: CSSComputedValues): StyledNode =
result = StyledNode(t: STYLED_ELEMENT, computed: computed, pseudo: pseudo, parent: parent)
result.parent = parent
func newStyledText*(parent: StyledNode, text: string): StyledNode =
result = StyledNode(t: STYLED_TEXT, text: text, parent: parent)
result.parent = parent
func newStyledText*(parent: StyledNode, text: Text): StyledNode =
result = StyledNode(t: STYLED_TEXT, text: text.data, node: text, parent: parent)
result.parent = parent
func newStyledReplacement*(parent: StyledNode, content: CSSContent): StyledNode =
return StyledNode(
t: STYLED_REPLACEMENT,
parent: parent,
content: content
)
|