diff options
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)) |