diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-14 18:59:17 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-14 21:48:20 +0100 |
commit | 99b72030569ae2f688002bd71d1fb58ad6daec90 (patch) | |
tree | 07846b88ac6016cca4f0ae3638c91f48ad40e7a7 /src | |
parent | b88ff4a313e04632c41d7ef90370599484a6f0bc (diff) | |
download | chawan-99b72030569ae2f688002bd71d1fb58ad6daec90.tar.gz |
css: refactor selector parsing & cascading, some work on variables
Untangled some nested arrays to reduce the number of intermediary seqs in cascade, and collapsed the two rule def sorts into just one (per pseudo element). This should make cascading somewhat faster. Also, we now parse variables, but they aren't resolved yet. Probably a seq won't cut it for var lookup...
Diffstat (limited to 'src')
-rw-r--r-- | src/css/cascade.nim | 215 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 105 | ||||
-rw-r--r-- | src/css/selectorparser.nim | 76 | ||||
-rw-r--r-- | src/css/sheet.nim | 46 | ||||
-rw-r--r-- | src/css/stylednode.nim | 8 | ||||
-rw-r--r-- | src/html/dom.nim | 10 |
6 files changed, 258 insertions, 202 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index aaf68677..b2eef00e 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -21,23 +21,33 @@ import types/opt import types/winattrs type - RuleList* = array[PseudoElem, seq[CSSRuleDef]] + RuleListEntry = object + normal: seq[CSSComputedEntry] + important: seq[CSSComputedEntry] + + RuleList = array[CSSOrigin, RuleListEntry] + + RuleListMap = ref object + rules: array[PseudoElement, RuleList] - RuleListMap* = ref object - ua: RuleList # user agent - user: RuleList - author: seq[RuleList] + RulePair = tuple + specificity: int + rule: CSSRuleDef - ToSorts = array[PseudoElem, seq[(int, CSSRuleDef)]] + ToSorts = array[PseudoElement, seq[RulePair]] proc calcRule(tosorts: var ToSorts; element: Element; depends: var DependencyInfo; rule: CSSRuleDef) = for sel in rule.sels: if element.matches(sel, depends): - let spec = getSpecificity(sel) - tosorts[sel.pseudo].add((spec, rule)) + if tosorts[sel.pseudo].len > 0 and tosorts[sel.pseudo][^1].rule == rule: + tosorts[sel.pseudo][^1].specificity = + max(tosorts[sel.pseudo][^1].specificity, sel.specificity) + else: + tosorts[sel.pseudo].add((sel.specificity, rule)) -func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList = +func calcRules(map: RuleListMap; styledNode: StyledNode; sheet: CSSStylesheet; + origin: CSSOrigin) = var tosorts = ToSorts.default let element = Element(styledNode.node) var rules: seq[CSSRuleDef] = @[] @@ -54,27 +64,28 @@ func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList = rules.add(v[]) for rule in sheet.generalList: rules.add(rule) - rules.sort(ruleDefCmp, order = Ascending) for rule in rules: tosorts.calcRule(element, styledNode.depends, rule) - for i in PseudoElem: - tosorts[i].sort((proc(x, y: (int, CSSRuleDef)): int = - cmp(x[0], y[0]) - ), order = Ascending) - result[i] = newSeqOfCap[CSSRuleDef](tosorts[i].len) - for item in tosorts[i]: - result[i].add(item[1]) + for pseudo, it in tosorts.mpairs: + it.sort(proc(x, y: (int, CSSRuleDef)): int = + let n = cmp(x[0], y[0]) + if n != 0: + return n + return cmp(x[1].idx, y[1].idx), order = Ascending) + for item in it: + map.rules[pseudo][origin].normal.add(item[1].normalVals) + map.rules[pseudo][origin].important.add(item[1].importantVals) proc applyPresHints(computed: CSSValues; element: Element; attrs: WindowAttributes; initMap: var InitMap) = template set_cv(t, x, b: untyped) = - computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, initMap, - itUserAgent) + computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, nil, + initMap, itUserAgent) initMap[t].incl(itUser) template set_cv_new(t, x, b: untyped) = const v = valueType(t) let val = CSSValue(v: v, x: b) - computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent) + computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent) initMap[t].incl(itUser) template map_width = let s = parseDimensionValues(element.attr(satWidth)) @@ -129,7 +140,7 @@ proc applyPresHints(computed: CSSValues; element: Element; template set_bgcolor_is_canvas = let t = cptBgcolorIsCanvas let val = CSSValueBit(bgcolorIsCanvas: true) - computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent) + computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent) initMap[t].incl(itUser) template map_cellspacing = let s = element.attrul(satCellspacing) @@ -185,42 +196,34 @@ proc applyPresHints(computed: CSSValues; element: Element; set_cv cptHeight, length, resolveLength(cuEm, float32(size), attrs) else: discard -type - CSSValueEntryObj = object - normal: seq[CSSComputedEntry] - important: seq[CSSComputedEntry] - - CSSValueEntryMap = array[CSSOrigin, CSSValueEntryObj] - -# element and attrsp are nil if called for a pseudo element. -func buildComputedValues(rules: CSSValueEntryMap; parent: CSSValues; - element: Element; attrsp: ptr WindowAttributes): CSSValues = +func applyDeclarations0(rules: RuleList; parent: CSSValues; element: Element; + window: Window): CSSValues = result = CSSValues() var initMap = InitMap.default for entry in rules[coUserAgent].normal: # user agent - result.applyValue(entry, parent, nil, initMap, itOther) + result.applyValue(entry, nil, parent, nil, initMap, itOther) initMap[entry.t] = {itUserAgent, itUser} let uaProperties = result.copyProperties() # Presentational hints override user agent style, but respect user/author # style. if element != nil: - result.applyPresHints(element, attrsp[], initMap) + result.applyPresHints(element, window.attrsp[], initMap) for entry in rules[coUser].normal: # user - result.applyValue(entry, parent, uaProperties, initMap, itUserAgent) + result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent) initMap[entry.t].incl(itUser) # save user properties so author can use them let userProperties = result.copyProperties() for entry in rules[coAuthor].normal: # author - result.applyValue(entry, parent, userProperties, initMap, itUser) + result.applyValue(entry, nil, parent, userProperties, initMap, itUser) initMap[entry.t].incl(itOther) for entry in rules[coAuthor].important: # author important - result.applyValue(entry, parent, userProperties, initMap, itUser) + result.applyValue(entry, nil, parent, userProperties, initMap, itUser) initMap[entry.t].incl(itOther) for entry in rules[coUser].important: # user important - result.applyValue(entry, parent, uaProperties, initMap, itUserAgent) + result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent) initMap[entry.t].incl(itOther) for entry in rules[coUserAgent].important: # user agent important - result.applyValue(entry, parent, nil, initMap, itUserAgent) + result.applyValue(entry, nil, parent, nil, initMap, itUserAgent) initMap[entry.t].incl(itOther) # set defaults for t in CSSPropertyType: @@ -240,53 +243,21 @@ func buildComputedValues(rules: CSSValueEntryMap; parent: CSSValues; result{"overflow-x"} = result{"overflow-x"}.bfcify() result{"overflow-y"} = result{"overflow-y"}.bfcify() -proc add(map: var CSSValueEntryObj; rules: seq[CSSRuleDef]) = - for rule in rules: - map.normal.add(rule.normalVals) - map.important.add(rule.importantVals) - proc applyDeclarations(styledNode: StyledNode; parent: CSSValues; - map: RuleListMap; window: Window) = - var rules: CSSValueEntryMap - rules[coUserAgent].add(map.ua[peNone]) - rules[coUser].add(map.user[peNone]) - for rule in map.author: - rules[coAuthor].add(rule[peNone]) - var element: Element = nil - if styledNode.node != nil: - element = Element(styledNode.node) - let style = element.cachedStyle - if window.styling and style != nil: - for decl in style.decls: - let vals = parseComputedValues(decl.name, decl.value, window.attrsp[]) - if decl.important: - rules[coAuthor].important.add(vals) - else: - rules[coAuthor].normal.add(vals) - styledNode.computed = rules.buildComputedValues(parent, element, - window.attrsp) + map: RuleListMap; window: Window; pseudo = peNone) = + let element = Element(styledNode.node) + styledNode.computed = map.rules[pseudo].applyDeclarations0(parent, element, + window) if element != nil and window.settings.scripting == smApp: element.computed = styledNode.computed -func hasValues(rules: CSSValueEntryMap): bool = - for origin in CSSOrigin: - if rules[origin].normal.len > 0 or rules[origin].important.len > 0: - return true +func hasValues(map: RuleListMap; pseudo: PseudoElement): bool = + for x in map.rules: + for y in x: + if y.normal.len > 0 or y.important.len > 0: + return true return false -# Either returns a new styled node or nil. -proc applyDeclarations(pseudo: PseudoElem; styledParent: StyledNode; - map: RuleListMap): StyledNode = - var rules: CSSValueEntryMap - rules[coUserAgent].add(map.ua[pseudo]) - rules[coUser].add(map.user[pseudo]) - for rule in map.author: - rules[coAuthor].add(rule[pseudo]) - if rules.hasValues(): - let cvals = rules.buildComputedValues(styledParent.computed, nil, nil) - return styledParent.newStyledElement(pseudo, cvals) - return nil - func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet = if ss == nil: return nil @@ -298,35 +269,32 @@ func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet = return res func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet; - author: seq[CSSStylesheet]): RuleListMap = - let uadecls = calcRules(styledNode, ua) - var userdecls = RuleList.default + author: seq[CSSStylesheet]; window: Window): RuleListMap = + let map = RuleListMap() + map.calcRules(styledNode, ua, coUserAgent) if user != nil: - userdecls = calcRules(styledNode, user) - var authordecls: seq[RuleList] = @[] + map.calcRules(styledNode, user, coUser) for rule in author: - authordecls.add(calcRules(styledNode, rule)) - return RuleListMap( - ua: uadecls, - user: userdecls, - author: authordecls - ) - -proc applyStyle(parent, styledNode: StyledNode; map: RuleListMap; - window: Window) = - let parentComputed = if parent != nil: - parent.computed - else: - rootProperties() - styledNode.applyDeclarations(parentComputed, map, window) + map.calcRules(styledNode, rule, coAuthor) + if styledNode.node != nil: + let style = Element(styledNode.node).cachedStyle + if window.styling and style != nil: + for decl in style.decls: + let vals = parseComputedValues(decl.name, decl.value, window.attrsp[], + window.factory) + if decl.important: + map.rules[peNone][coAuthor].important.add(vals) + else: + map.rules[peNone][coAuthor].normal.add(vals) + return map type CascadeFrame = object styledParent: StyledNode child: Node - pseudo: PseudoElem + pseudo: PseudoElement cachedChild: StyledNode cachedChildren: seq[StyledNode] - parentDeclMap: RuleListMap + parentMap: RuleListMap proc getAuthorSheets(document: Document): seq[CSSStylesheet] = var author: seq[CSSStylesheet] = @[] @@ -350,7 +318,7 @@ proc applyRulesFrameValid(frame: var CascadeFrame): StyledNode = return cachedChild proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; - author: seq[CSSStylesheet]; declmap: var RuleListMap; window: Window): + author: seq[CSSStylesheet]; map: var RuleListMap; window: Window): StyledNode = var styledChild: StyledNode = nil let pseudo = frame.pseudo @@ -359,13 +327,15 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; if frame.pseudo != peNone: case pseudo of peBefore, peAfter: - let declmap = frame.parentDeclMap - let styledPseudo = pseudo.applyDeclarations(styledParent, declmap) - if styledPseudo != nil and styledPseudo.computed{"content"}.len > 0: - for content in styledPseudo.computed{"content"}: - let child = styledPseudo.newStyledReplacement(content, peNone) - styledPseudo.children.add(child) - styledParent.children.add(styledPseudo) + let map = frame.parentMap + if map.hasValues(pseudo): + let styledPseudo = styledParent.newStyledElement(pseudo) + styledPseudo.applyDeclarations(styledParent.computed, map, nil, pseudo) + if styledPseudo.computed{"content"}.len > 0: + for content in styledPseudo.computed{"content"}: + let child = styledPseudo.newStyledReplacement(content, peNone) + styledPseudo.children.add(child) + styledParent.children.add(styledPseudo) of peInputText: let s = HTMLInputElement(styledParent.node).inputString() if s.len > 0: @@ -429,8 +399,8 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; let element = Element(child) styledChild = styledParent.newStyledElement(element) styledParent.children.add(styledChild) - declmap = styledChild.calcRules(ua, user, author) - styledParent.applyStyle(styledChild, declmap, window) + map = styledChild.calcRules(ua, user, author, window) + styledChild.applyDeclarations(styledParent.computed, map, window) elif child of Text: let text = Text(child) styledChild = styledParent.newStyledText(text) @@ -439,8 +409,8 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; # Root element let element = Element(child) styledChild = newStyledElement(element) - declmap = styledChild.calcRules(ua, user, author) - styledParent.applyStyle(styledChild, declmap, window) + map = styledChild.calcRules(ua, user, author, window) + styledChild.applyDeclarations(rootProperties(), map, window) return styledChild proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; @@ -461,8 +431,8 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; )) proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledParent: StyledNode; pseudo: PseudoElem; i: var int; - parentDeclMap: RuleListMap = nil) = + styledParent: StyledNode; pseudo: PseudoElement; i: var int; + parentMap: RuleListMap = nil) = # Can't check for cachedChildren.len here, because we assume that we only have # cached pseudo elems when the parent is also cached. if frame.cachedChild != nil: @@ -483,25 +453,25 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; styledParent: styledParent, pseudo: pseudo, cachedChild: cached, - parentDeclMap: parentDeclMap + parentMap: parentMap )) else: styledStack.add(CascadeFrame( styledParent: styledParent, pseudo: pseudo, cachedChild: nil, - parentDeclMap: parentDeclMap + parentMap: parentMap )) # Append children to styledChild. proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledChild: StyledNode; parentDeclMap: RuleListMap) = + styledChild: StyledNode; parentMap: RuleListMap) = # i points to the child currently being inspected. var idx = frame.cachedChildren.len - 1 let element = Element(styledChild.node) # reset invalid flag here to avoid a type conversion above element.invalid = false - styledStack.stackAppend(frame, styledChild, peAfter, idx, parentDeclMap) + styledStack.stackAppend(frame, styledChild, peAfter, idx, parentMap) case element.tagType of TAG_TEXTAREA: styledStack.stackAppend(frame, styledChild, peTextareaText, idx) @@ -520,7 +490,7 @@ proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; styledStack.stackAppend(frame, styledChild, child, idx) if element.tagType == TAG_INPUT: styledStack.stackAppend(frame, styledChild, peInputText, idx) - styledStack.stackAppend(frame, styledChild, peBefore, idx, parentDeclMap) + styledStack.stackAppend(frame, styledChild, peBefore, idx, parentMap) # Builds a StyledNode tree, optionally based on a previously cached version. proc applyRules(document: Document; ua, user: CSSStylesheet; @@ -538,7 +508,7 @@ proc applyRules(document: Document; ua, user: CSSStylesheet; var toReset: seq[Element] = @[] while styledStack.len > 0: var frame = styledStack.pop() - var declmap: RuleListMap + var map: RuleListMap = nil let styledParent = frame.styledParent let valid = frame.cachedChild != nil and frame.cachedChild.isValid(toReset) let styledChild = if valid: @@ -547,15 +517,14 @@ proc applyRules(document: Document; ua, user: CSSStylesheet; # From here on, computed values of this node's children are invalid # because of property inheritance. frame.cachedChild = nil - frame.applyRulesFrameInvalid(ua, user, author, declmap, - document.window) + frame.applyRulesFrameInvalid(ua, user, author, map, document.window) if styledChild != nil: if styledParent == nil: # Root element root = styledChild if styledChild.t == stElement and styledChild.node != nil: # note: following resets styledChild.node's invalid flag - styledStack.appendChildren(frame, styledChild, declmap) + styledStack.appendChildren(frame, styledChild, map) for element in toReset: element.invalidDeps = {} return root diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index fa24b39b..a2c661d6 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -7,14 +7,13 @@ import std/tables import css/cssparser import css/lunit import css/selectorparser +import html/catom import types/bitmap import types/color import types/opt import types/winattrs import utils/twtstr -export selectorparser.PseudoElem - type CSSPropertyType* = enum # primitive/enum properties: stored as byte @@ -410,13 +409,26 @@ type coUser coAuthor + CSSEntryType* = enum + ceBit, ceObject, ceWord, ceVar + CSSComputedEntry* = object t*: CSSPropertyType global*: CSSGlobalType - #TODO case on t? - bit*: uint8 - word*: CSSValueWord - obj*: CSSValue + case et*: CSSEntryType + of ceBit: + bit*: uint8 + of ceWord: + word*: CSSValueWord + of ceObject: + obj*: CSSValue + of ceVar: + cvar*: CAtom + + CSSVariable* = ref object + name*: CAtom + cvals*: seq[CSSComponentValue] + resolved: seq[tuple[v: CSSValueType; entry: CSSComputedEntry]] static: doAssert sizeof(CSSValueBit) == 1 @@ -1247,34 +1259,66 @@ func cssNumber(cval: CSSComponentValue; positive: bool): Opt[float32] = proc makeEntry*(t: CSSPropertyType; obj: CSSValue; global = cgtNone): CSSComputedEntry = - return CSSComputedEntry(t: t, obj: obj, global: global) + return CSSComputedEntry(et: ceObject, t: t, obj: obj, global: global) proc makeEntry*(t: CSSPropertyType; word: CSSValueWord; global = cgtNone): CSSComputedEntry = - return CSSComputedEntry(t: t, word: word, global: global) + return CSSComputedEntry(et: ceWord, t: t, word: word, global: global) proc makeEntry*(t: CSSPropertyType; bit: CSSValueBit; global = cgtNone): CSSComputedEntry = - return CSSComputedEntry(t: t, bit: bit.dummy, global: global) + return CSSComputedEntry(et: ceBit, t: t, bit: bit.dummy, global: global) proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry = - return CSSComputedEntry(t: t, global: global) + return CSSComputedEntry(et: ceObject, t: t, global: global) + +proc parseVar(fun: CSSFunction; entry: var CSSComputedEntry; + attrs: WindowAttributes; factory: CAtomFactory): Opt[void] = + let i = fun.value.skipBlanks(0) + if i >= fun.value.len or fun.value.skipBlanks(i + 1) < fun.value.len: + return err() + let cval = fun.value[i] + if not (cval of CSSToken): + return err() + let tok = CSSToken(fun.value[i]) + if tok.t != cttIdent: + return err() + entry = CSSComputedEntry( + et: ceVar, + cvar: factory.toAtom(tok.value.substr(2)) + ) + return ok() proc parseValue(cvals: openArray[CSSComponentValue]; - entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] = + entry: var CSSComputedEntry; attrs: WindowAttributes; + factory: CAtomFactory): Opt[void] = var i = cvals.skipBlanks(0) if i >= cvals.len: return err() let cval = cvals[i] - let t = entry.t inc i + if cval of CSSFunction: + let fun = CSSFunction(cval) + if fun.name.equalsIgnoreCase("var"): + if cvals.skipBlanks(i) < cvals.len: + return err() + return fun.parseVar(entry, attrs, factory) + let t = entry.t let v = valueType(t) template set_new(prop, val: untyped) = - entry.obj = CSSValue(v: v, prop: val) + entry = CSSComputedEntry( + t: entry.t, + et: ceObject, + obj: CSSValue(v: v, prop: val) + ) template set_word(prop, val: untyped) = - entry.word = CSSValueWord(prop: val) + entry = CSSComputedEntry( + t: entry.t, + et: ceWord, + word: CSSValueWord(prop: val) + ) template set_bit(prop, val: untyped) = - entry.bit = cast[uint8](val) + entry = CSSComputedEntry(t: entry.t, et: ceBit, bit: cast[uint8](val)) case v of cvtDisplay: set_bit display, ?parseIdent[CSSDisplay](cval) of cvtWhiteSpace: set_bit whiteSpace, ?parseIdent[CSSWhiteSpace](cval) @@ -1426,7 +1470,8 @@ const PropertyPaddingSpec = [ ] proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; - cvals: openArray[CSSComponentValue]; attrs: WindowAttributes): Err[void] = + cvals: openArray[CSSComponentValue]; attrs: WindowAttributes; + factory: CAtomFactory): Err[void] = var i = cvals.skipBlanks(0) if i >= cvals.len: return err() @@ -1439,8 +1484,12 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; if global != cgtNone: res.add(makeEntry(t, global)) else: - var entry = CSSComputedEntry(t: t) - ?cvals.parseValue(entry, attrs) + let et = case t.reprType + of cprtBit: ceBit + of cprtObject: ceObject + of cprtWord: ceWord + var entry = CSSComputedEntry(t: t, et: et) + ?cvals.parseValue(entry, attrs, factory) res.add(entry) of cstAll: if global == cgtNone: @@ -1561,9 +1610,9 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string; return ok() proc parseComputedValues*(name: string; value: seq[CSSComponentValue]; - attrs: WindowAttributes): seq[CSSComputedEntry] = + attrs: WindowAttributes; factory: CAtomFactory): seq[CSSComputedEntry] = var res: seq[CSSComputedEntry] = @[] - if res.parseComputedValues(name, value, attrs).isSome: + if res.parseComputedValues(name, value, attrs, factory).isSome: return res return @[] @@ -1591,8 +1640,13 @@ type InitMap* = array[CSSPropertyType, set[InitType]] + CSSVariableMap* = ref object + parent*: CSSVariableMap + vars*: seq[CSSVariable] + proc applyValue*(vals: CSSValues; entry: CSSComputedEntry; - parent, previousOrigin: CSSValues; inited: InitMap; initType: InitType) = + varMap: CSSVariableMap; parent, previousOrigin: CSSValues; inited: InitMap; + initType: InitType) = case entry.global of cgtInherit: if parent != nil: @@ -1609,10 +1663,11 @@ proc applyValue*(vals: CSSValues; entry: CSSComputedEntry; else: vals.initialOrInheritFrom(parent, entry.t) of cgtNone: - case entry.t.reprType - of cprtBit: vals.bits[entry.t].dummy = entry.bit - of cprtWord: vals.words[entry.t] = entry.word - of cprtObject: vals.objs[entry.t] = entry.obj + case entry.et + of ceBit: vals.bits[entry.t].dummy = entry.bit + of ceWord: vals.words[entry.t] = entry.word + of ceObject: vals.objs[entry.t] = entry.obj + of ceVar: discard #eprint "TODO" func inheritProperties*(parent: CSSValues): CSSValues = result = CSSValues() diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim index c66a1b43..a927b9be 100644 --- a/src/css/selectorparser.nim +++ b/src/css/selectorparser.nim @@ -9,7 +9,7 @@ type SelectorType* = enum stType, stId, stAttr, stClass, stUniversal, stPseudoClass, stPseudoElement - PseudoElem* = enum + PseudoElement* = enum peNone = "-cha-none" peBefore = "before" peAfter = "after" @@ -91,7 +91,7 @@ type of stPseudoClass: pseudo*: PseudoData of stPseudoElement: - elem*: PseudoElem + elem*: PseudoElement PseudoData* = object case t*: PseudoClass @@ -108,10 +108,19 @@ type ct*: CombinatorType # relation to the next entry in a ComplexSelector. sels*: seq[Selector] - ComplexSelector* = seq[CompoundSelector] + ComplexSelector* = object + specificity*: int + pseudo*: PseudoElement + csels: seq[CompoundSelector] SelectorList* = seq[ComplexSelector] +# Forward declarations +proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory; + nested, forgiving: bool): SelectorList +proc parseComplexSelector(state: var SelectorParser): ComplexSelector +func `$`*(cxsel: ComplexSelector): string + iterator items*(sels: CompoundSelector): Selector {.inline.} = for it in sels.sels: yield it @@ -128,7 +137,26 @@ func len*(sels: CompoundSelector): int {.inline.} = proc add*(sels: var CompoundSelector; sel: Selector) {.inline.} = sels.sels.add(sel) -func `$`*(cxsel: ComplexSelector): string +func `[]`*(cxsel: ComplexSelector; i: int): lent CompoundSelector {.inline.} = + return cxsel.csels[i] + +func `[]`*(cxsel: ComplexSelector; i: BackwardsIndex): CompoundSelector + {.inline.} = + return cxsel.csels[i] + +func `[]`*(cxsel: var ComplexSelector; i: BackwardsIndex): var CompoundSelector + {.inline.} = + return cxsel.csels[i] + +func len*(cxsel: ComplexSelector): int {.inline.} = + return cxsel.csels.len + +func high*(cxsel: ComplexSelector): int {.inline.} = + return cxsel.csels.high + +iterator items*(cxsel: ComplexSelector): lent CompoundSelector {.inline.} = + for it in cxsel.csels: + yield it func `$`*(sel: Selector): string = case sel.t @@ -212,8 +240,6 @@ func `$`*(slist: SelectorList): string = result &= $cxsel s = true -func getSpecificity*(cxsel: ComplexSelector): int - func getSpecificity(sel: Selector): int = case sel.t of stId: @@ -225,7 +251,7 @@ func getSpecificity(sel: Selector): int = of pcIs, pcNot: var best = 0 for child in sel.pseudo.fsels: - let s = getSpecificity(child) + let s = child.specificity if s > best: best = s result += best @@ -233,7 +259,7 @@ func getSpecificity(sel: Selector): int = if sel.pseudo.ofsels.len != 0: var best = 0 for child in sel.pseudo.ofsels: - let s = getSpecificity(child) + let s = child.specificity if s > best: best = s result += best @@ -245,19 +271,11 @@ func getSpecificity(sel: Selector): int = of stUniversal: discard -func getSpecificity*(sels: CompoundSelector): int = +func getSpecificity(sels: CompoundSelector): int = + result= 0 for sel in sels: result += getSpecificity(sel) -func getSpecificity*(cxsel: ComplexSelector): int = - for sels in cxsel: - result += getSpecificity(sels) - -func pseudo*(cxsel: ComplexSelector): PseudoElem = - if cxsel[^1][^1].t == stPseudoElement: - return cxsel[^1][^1].elem - return peNone - proc consume(state: var SelectorParser): CSSComponentValue = result = state.cvals[state.at] inc state.at @@ -277,9 +295,6 @@ template get_tok(cval: CSSComponentValue): CSSToken = if not (c of CSSToken): fail CSSToken(c) -proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory; - nested, forgiving: bool): SelectorList - # Functions that may contain other selectors, functions, etc. proc parseRecursiveSelectorFunction(state: var SelectorParser; class: PseudoClass; body: seq[CSSComponentValue]; forgiving: bool): @@ -353,7 +368,7 @@ proc parsePseudoSelector(state: var SelectorParser): Selector = if not state.has(): fail let cval = state.consume() if cval of CSSToken: - template add_pseudo_element(element: PseudoElem) = + template add_pseudo_element(element: PseudoElement) = state.skipWhitespace() if state.nested or state.has() and state.peek() != cttComma: fail return Selector(t: stPseudoElement, elem: element) @@ -391,8 +406,6 @@ proc parsePseudoSelector(state: var SelectorParser): Selector = return state.parseSelectorFunction(CSSFunction(cval)) else: fail -proc parseComplexSelector(state: var SelectorParser): ComplexSelector - proc parseAttributeSelector(state: var SelectorParser; cssblock: CSSSimpleBlock): Selector = if cssblock.token.t != cttLbracket: fail @@ -438,10 +451,7 @@ proc parseAttributeSelector(state: var SelectorParser; t: stAttr, attr: state.factory.toAtom(attr.value), value: value.value, - rel: SelectorRelation( - t: rel, - flag: flag - ) + rel: SelectorRelation(t: rel, flag: flag) ) proc parseClassSelector(state: var SelectorParser): Selector = @@ -504,10 +514,15 @@ proc parseCompoundSelector(state: var SelectorParser): CompoundSelector = fail proc parseComplexSelector(state: var SelectorParser): ComplexSelector = + result = ComplexSelector() while true: state.skipWhitespace() let sels = state.parseCompoundSelector() - result.add(sels) + if state.failed: + break + #TODO propagate from parser + result.specificity += sels.getSpecificity() + result.csels.add(sels) if sels.len == 0: fail if not state.has(): break # finish @@ -526,6 +541,9 @@ proc parseComplexSelector(state: var SelectorParser): ComplexSelector = else: fail if result.len == 0 or result[^1].ct != ctNone: fail + if result[^1][^1].t == stPseudoElement: + #TODO move pseudo check here? + result.pseudo = result[^1][^1].elem proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory; nested, forgiving: bool): SelectorList = diff --git a/src/css/sheet.nim b/src/css/sheet.nim index ee92198c..7d1359f9 100644 --- a/src/css/sheet.nim +++ b/src/css/sheet.nim @@ -1,4 +1,5 @@ import std/options +import std/strutils import std/tables import css/cssparser @@ -6,6 +7,7 @@ import css/cssvalues import css/mediaquery import css/selectorparser import html/catom +import types/opt import types/url import types/winattrs import utils/twtstr @@ -15,11 +17,14 @@ type CSSRuleDef* = ref object of CSSRuleBase sels*: SelectorList + specificity*: int normalVals*: seq[CSSComputedEntry] importantVals*: seq[CSSComputedEntry] + normalVars*: seq[CSSVariable] + importantVars*: seq[CSSVariable] # Absolute position in the stylesheet; used for sorting rules after # retrieval from the cache. - idx: int + idx*: int CSSMediaQueryDef* = ref object of CSSRuleBase children*: CSSStylesheet @@ -130,9 +135,6 @@ proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool = return hashes.tag != CAtomNull or hashes.id != CAtomNull or hashes.class != CAtomNull -proc ruleDefCmp*(a, b: CSSRuleDef): int = - cmp(a.idx, b.idx) - proc add(sheet: CSSStylesheet; rule: CSSRuleDef) = var hashes = SelectorHashes() for cxsel in rule.sels: @@ -179,23 +181,35 @@ proc add*(sheet, sheet2: CSSStylesheet) = sheet.attrTable[key] = value proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) = - let sels = parseSelectors(rule.prelude, sheet.factory) + var sels = parseSelectors(rule.prelude, sheet.factory) if sels.len > 0: - var normalVals: seq[CSSComputedEntry] = @[] - var importantVals: seq[CSSComputedEntry] = @[] let decls = rule.oblock.value.parseDeclarations() + var pass2 = newSeqOfCap[CSSDeclaration](decls.len) + let rule = CSSRuleDef(sels: move(sels), idx: sheet.len) for decl in decls: - let vals = parseComputedValues(decl.name, decl.value, sheet.attrs[]) + if decl.name.startsWith("--"): + let cvar = CSSVariable( + # case sensitive, it seems + name: sheet.factory.toAtom(decl.name.substr(2)), + cvals: move(decl.value) + ) + if decl.important: + rule.importantVars.add(cvar) + else: + rule.normalVars.add(cvar) + pass2.add(decl) + for decl in pass2: if decl.important: - importantVals.add(vals) + let olen = rule.importantVals.len + if rule.importantVals.parseComputedValues(decl.name, decl.value, + sheet.attrs[], sheet.factory).isNone: + rule.importantVals.setLen(olen) else: - normalVals.add(vals) - sheet.add(CSSRuleDef( - sels: sels, - normalVals: normalVals, - importantVals: importantVals, - idx: sheet.len - )) + let olen = rule.normalVals.len + if rule.normalVals.parseComputedValues(decl.name, decl.value, + sheet.attrs[], sheet.factory).isNone: + rule.normalVals.setLen(olen) + sheet.add(rule) inc sheet.len proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) = diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim index 46df5ff0..c1a4cbc8 100644 --- a/src/css/stylednode.nim +++ b/src/css/stylednode.nim @@ -47,7 +47,7 @@ type StyledNode* = ref object parent*: StyledNode node*: Node - pseudo*: PseudoElem + pseudo*: PseudoElement case t*: StyledType of stText: discard @@ -108,11 +108,9 @@ func newStyledElement*(parent: StyledNode; element: Element): StyledNode = func newStyledElement*(element: Element): StyledNode = return StyledNode(t: stElement, node: element) -func newStyledElement*(parent: StyledNode; pseudo: PseudoElem; - computed: CSSValues): StyledNode = +func newStyledElement*(parent: StyledNode; pseudo: PseudoElement): StyledNode = return StyledNode( t: stElement, - computed: computed, pseudo: pseudo, parent: parent ) @@ -124,7 +122,7 @@ func newStyledText*(text: string): StyledNode = return StyledNode(t: stText, node: CharacterData(data: text)) func newStyledReplacement*(parent: StyledNode; content: CSSContent; - pseudo: PseudoElem): StyledNode = + pseudo: PseudoElement): StyledNode = return StyledNode( t: stReplacement, parent: parent, diff --git a/src/html/dom.nim b/src/html/dom.nim index 49233e87..83ad9025 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -3796,8 +3796,9 @@ proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]): if i notin 0 .. this.decls.high: return err() # dummyAttrs can be safely used because the result is discarded. - var dummy: seq[CSSComputedEntry] - ?parseComputedValues(dummy, this.decls[i].name, cvals, dummyAttrs) + var dummy: seq[CSSComputedEntry] = @[] + ?dummy.parseComputedValues(this.decls[i].name, cvals, dummyAttrs, + this.element.document.factory) this.decls[i].value = cvals return ok() @@ -3831,8 +3832,9 @@ proc setProperty(this: CSSStyleDeclaration; name, value: string): # not err! this does not throw. return ok() else: - var dummy: seq[CSSComputedEntry] - let val0 = parseComputedValues(dummy, name, cvals, dummyAttrs) + var dummy: seq[CSSComputedEntry] = @[] + let val0 = dummy.parseComputedValues(name, cvals, dummyAttrs, + this.element.document.factory) if val0.isNone: return ok() this.decls.add(CSSDeclaration(name: name, value: cvals)) |