diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-20 18:08:12 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-01-20 19:34:27 +0100 |
commit | 66b18695de1e6629341e08faf487a693cc26211a (patch) | |
tree | 23e5d44a803051d34040278bdc245cc92fbc418e | |
parent | 2291b75ed7133b77bce37fcc07500d5081682892 (diff) | |
download | chawan-66b18695de1e6629341e08faf487a693cc26211a.tar.gz |
cascade: collapse StyledNode tree into DOM
We now compute styles on-demand, which is both more efficient and simpler than the convoluted tree diffing logic we previously used.
-rw-r--r-- | src/css/cascade.nim | 399 | ||||
-rw-r--r-- | src/css/cssvalues.nim | 5 | ||||
-rw-r--r-- | src/css/layout.nim | 19 | ||||
-rw-r--r-- | src/css/match.nim | 2 | ||||
-rw-r--r-- | src/css/selectorparser.nim | 10 | ||||
-rw-r--r-- | src/css/stylednode.nim | 72 | ||||
-rw-r--r-- | src/html/chadombuilder.nim | 5 | ||||
-rw-r--r-- | src/html/dom.nim | 269 | ||||
-rw-r--r-- | src/server/buffer.nim | 70 |
9 files changed, 317 insertions, 534 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 0b4369df..7fff1434 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -7,14 +7,13 @@ import css/cssparser import css/cssvalues import css/lunit import css/match -import css/mediaquery import css/selectorparser import css/sheet -import css/stylednode import html/catom import html/dom import html/enums import html/script +import types/bitmap import types/color import types/jscolor import types/opt @@ -46,6 +45,7 @@ type proc applyValue(vals: CSSValues; entry: CSSComputedEntry; parentComputed, previousOrigin: CSSValues; initMap: var InitMap; initType: InitType; nextInitType: set[InitType]; window: Window) +proc applyStyle*(element: Element) proc calcRule(tosorts: var ToSorts; element: Element; depends: var DependencyInfo; rule: CSSRuleDef) = @@ -63,9 +63,8 @@ proc add(entry: var RuleListEntry; rule: CSSRuleDef) = entry.normalVars.add(rule.normalVars) entry.importantVars.add(rule.importantVars) -proc calcRules(map: var RuleListMap; styledNode: StyledNode; - sheet: CSSStylesheet; origin: CSSOrigin) = - let element = styledNode.element +proc calcRules(map: var RuleListMap; element: Element; + sheet: CSSStylesheet; origin: CSSOrigin; depends: var DependencyInfo) = var rules: seq[CSSRuleDef] = @[] sheet.tagTable.withValue(element.localName, v): rules.add(v[]) @@ -82,7 +81,7 @@ proc calcRules(map: var RuleListMap; styledNode: StyledNode; rules.add(rule) var tosorts = ToSorts.default for rule in rules: - tosorts.calcRule(element, styledNode.element.depends, rule) + tosorts.calcRule(element, depends, rule) for pseudo, it in tosorts.mpairs: it.sort(proc(x, y: RulePair): int = let n = cmp(x.specificity, y.specificity) @@ -278,12 +277,14 @@ proc applyPresHints(computed: CSSValues; element: Element; set_cv cptHeight, length, resolveLength(cuEm, float32(size), attrs) else: discard -proc applyDeclarations0(rules: RuleList; parent, element: Element; +proc applyDeclarations(rules: RuleList; parent, element: Element; window: Window): CSSValues = result = CSSValues() var parentComputed: CSSValues = nil var parentVars: CSSVariableMap = nil if parent != nil: + if parent.computed == nil: + parent.applyStyle() parentComputed = parent.computed parentVars = parentComputed.vars for origin in CSSOrigin: @@ -352,25 +353,17 @@ func hasValues(rules: RuleList): bool = return true return false -func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet = - if ss == nil: - return nil - var res = CSSStylesheet() - res[] = ss[] - for mq in ss.mqList: - if mq.query.applies(window.settings.scripting, window.attrsp): - res.add(mq.children.applyMediaQuery(window)) - return res - -proc applyDeclarations(styledNode: StyledNode; parent: Element; - window: Window; ua, user: CSSStylesheet; author: seq[CSSStylesheet]) = +proc applyStyle*(element: Element) = + let document = element.document + let window = document.window + var depends = DependencyInfo.default var map = RuleListMap.default - map.calcRules(styledNode, ua, coUserAgent) - if user != nil: - map.calcRules(styledNode, user, coUser) - for rule in author: - map.calcRules(styledNode, rule, coAuthor) - let style = styledNode.element.cachedStyle + for sheet in document.uaSheets: + map.calcRules(element, sheet, coUserAgent, depends) + map.calcRules(element, document.userSheet, coUser, depends) + for sheet in document.authorSheets: + map.calcRules(element, sheet, coAuthor, depends) + let style = element.cachedStyle if window.styling and style != nil: for decl in style.decls: #TODO variables @@ -380,263 +373,111 @@ proc applyDeclarations(styledNode: StyledNode; parent: Element; map[peNone][coAuthor].important.add(vals) else: map[peNone][coAuthor].normal.add(vals) - let element = styledNode.element - element.computedMap[peNone] = map[peNone].applyDeclarations0(parent, element, - window) + element.applyStyleDependencies(depends) + element.computedMap[peNone] = + map[peNone].applyDeclarations(element.parentElement, element, window) for pseudo in peBefore..peAfter: if map[pseudo].hasValues() or window.settings.scripting == smApp: - let computed = map[pseudo].applyDeclarations0(element, nil, window) + let computed = map[pseudo].applyDeclarations(element, nil, window) element.computedMap[pseudo] = computed -type CascadeFrame = object - styledParent: StyledNode - child: Node - pseudo: PseudoElement - cachedChild: StyledNode - cachedChildren: seq[StyledNode] - -proc getAuthorSheets(document: Document): seq[CSSStylesheet] = - var author: seq[CSSStylesheet] = @[] - for sheet in document.sheets(): - author.add(sheet.applyMediaQuery(document.window)) - return author - -proc applyRulesFrameValid(frame: var CascadeFrame): StyledNode = - let styledParent = frame.styledParent - let cachedChild = frame.cachedChild - # Pseudo elements can't have invalid children. - if cachedChild.t == stElement and cachedChild.pseudo == peNone: - # Refresh child nodes: - # * move old seq to a temporary location in frame - # * create new seq, assuming capacity == len of the previous pass - frame.cachedChildren = move(cachedChild.children) - cachedChild.children = newSeqOfCap[StyledNode](frame.cachedChildren.len) - if styledParent != nil: - styledParent.children.add(cachedChild) - return cachedChild - -proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet; - author: seq[CSSStylesheet]; window: Window): StyledNode = - let pseudo = frame.pseudo - let styledParent = frame.styledParent - let child = frame.child - case pseudo - of peNone: # not a pseudo-element, but a real one - assert child != nil - if child of Element: - let element = Element(child) - let styledChild = newStyledElement(element) - if styledParent == nil: # root element - styledChild.applyDeclarations(nil, window, ua, user, author) - else: - styledParent.children.add(styledChild) - styledChild.applyDeclarations(styledParent.element, window, ua, user, - author) - return styledChild - elif child of Text: - let text = Text(child) - let styledChild = styledParent.newStyledText(text) - styledParent.children.add(styledChild) - return styledChild - of peBefore, peAfter: - let parent = styledParent.element - if parent.computedMap[pseudo] != nil and - parent.computedMap[pseudo]{"content"}.len > 0: - let styledPseudo = styledParent.newStyledElement(pseudo) - for content in parent.computedMap[pseudo]{"content"}: - let child = styledPseudo.newStyledReplacement(content, peNone) - styledPseudo.children.add(child) - styledParent.children.add(styledPseudo) - of peInputText: - let s = HTMLInputElement(styledParent.element).inputString() - if s.len > 0: - let content = styledParent.element.document.newText(s) - let styledText = styledParent.newStyledText(content) - # Note: some pseudo-elements (like input text) generate text nodes - # directly, so we have to cache them like this. - styledText.pseudo = pseudo - styledParent.children.add(styledText) - of peTextareaText: - let s = HTMLTextAreaElement(styledParent.element).textAreaString() - if s.len > 0: - let content = styledParent.element.document.newText(s) - let styledText = styledParent.newStyledText(content) - styledText.pseudo = pseudo - styledParent.children.add(styledText) - of peImage: - let content = CSSContent( - t: ContentImage, - bmp: HTMLImageElement(styledParent.element).bitmap - ) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peSVG: - let content = CSSContent( - t: ContentImage, - bmp: SVGSVGElement(styledParent.element).bitmap - ) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peCanvas: - let bmp = HTMLCanvasElement(styledParent.element).bitmap - if bmp != nil and bmp.cacheId != 0: - let content = CSSContent( - t: ContentImage, - bmp: bmp - ) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peVideo: - let content = CSSContent(t: ContentVideo) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peAudio: - let content = CSSContent(t: ContentAudio) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peIFrame: - let content = CSSContent(t: ContentIFrame) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - of peNewline: - let content = CSSContent(t: ContentNewline) - let styledText = styledParent.newStyledReplacement(content, pseudo) - styledParent.children.add(styledText) - return nil - -proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledParent: StyledNode; child: Element; i: var int) = - var cached: StyledNode = nil - if frame.cachedChildren.len > 0: - for j in countdown(i, 0): - let it = frame.cachedChildren[j] - if it.t == stElement and it.pseudo == peNone and it.element == child: - i = j - 1 - cached = it - break - styledStack.add(CascadeFrame( - styledParent: styledParent, - child: child, - pseudo: peNone, - cachedChild: cached - )) - -proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledParent: StyledNode; child: Text; i: var int) = - var cached: StyledNode = nil - if frame.cachedChildren.len > 0: - for j in countdown(i, 0): - let it = frame.cachedChildren[j] - if it.t == stText and it.text == child: - i = j - 1 - cached = it - break - styledStack.add(CascadeFrame( - styledParent: styledParent, - child: child, - pseudo: peNone, - cachedChild: cached - )) - -proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledParent: StyledNode; pseudo: PseudoElement; i: var int) = - # 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: - var cached: StyledNode = nil - for j in countdown(i, 0): - let it = frame.cachedChildren[j] - if it.pseudo == pseudo: - cached = it - i = j - 1 - break - # When calculating pseudo-element rules, their dependencies are added - # to their parent's dependency list; so invalidating a pseudo-element - # invalidates its parent too, which in turn automatically rebuilds - # the pseudo-element. - # In other words, we can just do this: - if cached != nil: - styledStack.add(CascadeFrame( - styledParent: styledParent, - pseudo: pseudo, - cachedChild: cached - )) - else: - styledStack.add(CascadeFrame( - styledParent: styledParent, - pseudo: pseudo, - cachedChild: nil - )) - -# Append children to styledChild. -proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame; - styledChild: StyledNode) = - # i points to the child currently being inspected. - var idx = frame.cachedChildren.len - 1 - let element = styledChild.element - # reset invalid flag here to avoid a type conversion above - element.invalid = false - styledStack.stackAppend(frame, styledChild, peAfter, idx) - case element.tagType - of TAG_TEXTAREA: - styledStack.stackAppend(frame, styledChild, peTextareaText, idx) - of TAG_IMG: styledStack.stackAppend(frame, styledChild, peImage, idx) - of TAG_VIDEO: styledStack.stackAppend(frame, styledChild, peVideo, idx) - of TAG_AUDIO: styledStack.stackAppend(frame, styledChild, peAudio, idx) - of TAG_BR: styledStack.stackAppend(frame, styledChild, peNewline, idx) - of TAG_CANVAS: styledStack.stackAppend(frame, styledChild, peCanvas, idx) - of TAG_IFRAME: styledStack.stackAppend(frame, styledChild, peIFrame, idx) - elif element.tagType(Namespace.SVG) == TAG_SVG: - styledStack.stackAppend(frame, styledChild, peSVG, idx) - else: - for i in countdown(element.childList.high, 0): - let child = element.childList[i] - if child of Element: - styledStack.stackAppend(frame, styledChild, Element(child), idx) - elif child of Text: - styledStack.stackAppend(frame, styledChild, Text(child), idx) - if element.tagType == TAG_INPUT: - styledStack.stackAppend(frame, styledChild, peInputText, idx) - styledStack.stackAppend(frame, styledChild, peBefore, idx) - -# Builds a StyledNode tree, optionally based on a previously cached version. -proc applyRules(document: Document; ua, user: CSSStylesheet; - cachedTree: StyledNode): StyledNode = - let html = document.documentElement - if html == nil: +# Abstraction over the DOM to pretend that elements, text, replaced and +# pseudo-elements are derived from the same type. +type + StyledType* = enum + stElement, stText, stReplacement + + StyledNode* = object + element*: Element + pseudo*: PseudoElement + case t*: StyledType + of stText: + text*: CharacterData + of stElement: + discard + of stReplacement: + # replaced elements: quotes, images, or (TODO) markers + content*: CSSContent + +when defined(debug): + func `$`*(node: StyledNode): string = + case node.t + of stText: + return "#text " & node.text.data + of stElement: + if node.pseudo != peNone: + return $node.element.tagType & "::" & $node.pseudo + return $node.element + of stReplacement: + return "#replacement" + +# Defined here so it isn't accidentally used in dom. +#TODO it may be better to do it in dom anyway, so we can cache it... +func newCharacterData*(data: sink string): CharacterData = + return CharacterData(data: data) + +template computed*(styledNode: StyledNode): CSSValues = + styledNode.element.computedMap[styledNode.pseudo] + +proc initStyledElement*(element: Element): StyledNode = + if element.computed == nil: + element.applyStyle() + return StyledNode(t: stElement, element: element) + +proc initStyledReplacement(parent: Element; content: sink CSSContent): + StyledNode = + return StyledNode(t: stReplacement, element: parent, content: content) + +proc initStyledImage(parent: Element; bmp: NetworkBitmap): StyledNode = + return initStyledReplacement(parent, CSSContent(t: ContentImage, bmp: bmp)) + +proc initStyledPseudo(parent: Element; pseudo: PseudoElement): StyledNode = + return StyledNode(t: stElement, pseudo: pseudo, element: parent) + +proc initStyledText(parent: Element; text: CharacterData): StyledNode = + return StyledNode(t: stText, element: parent, text: text) + +proc initStyledText(parent: Element; s: sink string): StyledNode = + return initStyledText(parent, newCharacterData(s)) + +# Many yields; we use a closure iterator to avoid bloating the code. +iterator children*(styledNode: StyledNode): StyledNode {.closure.} = + if styledNode.t != stElement: return - let author = document.getAuthorSheets() - var styledStack = @[CascadeFrame( - child: html, - pseudo: peNone, - cachedChild: cachedTree - )] - var root: StyledNode = nil - var toReset: seq[Element] = @[] - while styledStack.len > 0: - var frame = styledStack.pop() - let styledParent = frame.styledParent - let valid = frame.cachedChild != nil and frame.cachedChild.isValid(toReset) - let styledChild = if valid: - frame.applyRulesFrameValid() + if styledNode.pseudo == peNone: + let parent = styledNode.element + if parent.computedMap[peBefore] != nil and + parent.computedMap[peBefore]{"content"}.len > 0: + yield initStyledPseudo(parent, peBefore) + case parent.tagType + of TAG_INPUT: + #TODO cache (just put value in a CharacterData) + let s = HTMLInputElement(parent).inputString() + if s.len > 0: + yield initStyledText(parent, s) + of TAG_TEXTAREA: + #TODO cache (do the same as with input, and add borders in render) + yield initStyledText(parent, HTMLTextAreaElement(parent).textAreaString()) + of TAG_IMG: yield initStyledImage(parent, HTMLImageElement(parent).bitmap) + of TAG_CANVAS: + yield initStyledImage(parent, HTMLImageElement(parent).bitmap) + of TAG_VIDEO: yield initStyledText(parent, "[video]") + of TAG_AUDIO: yield initStyledText(parent, "[audio]") + of TAG_BR: + yield initStyledReplacement(parent, CSSContent(t: ContentNewline)) + of TAG_IFRAME: yield initStyledText(parent, "[iframe]") + elif parent.tagType(Namespace.SVG) == TAG_SVG: + yield initStyledImage(parent, SVGSVGElement(parent).bitmap) else: - # From here on, computed values of this node's children are invalid - # because of property inheritance. - frame.cachedChild = nil - frame.applyRulesFrameInvalid(ua, user, author, document.window) - if styledChild != nil: - if styledParent == nil: - # Root element - root = styledChild - if styledChild.t == stElement and styledChild.pseudo == peNone: - # note: following resets styledChild.node's invalid flag - styledStack.appendChildren(frame, styledChild) - for element in toReset: - element.invalidDeps = {} - return root - -proc applyStylesheets*(document: Document; uass, userss: CSSStylesheet; - previousStyled: StyledNode): StyledNode = - let uass = uass.applyMediaQuery(document.window) - let userss = userss.applyMediaQuery(document.window) - return document.applyRules(uass, userss, previousStyled) + for it in parent.childList: + if it of Element: + yield initStyledElement(Element(it)) + elif it of Text: + yield initStyledText(parent, Text(it)) + if parent.computedMap[peAfter] != nil and + parent.computedMap[peAfter]{"content"}.len > 0: + yield initStyledPseudo(parent, peAfter) + else: + let parent = styledNode.element + for content in parent.computedMap[styledNode.pseudo]{"content"}: + yield parent.initStyledReplacement(content) diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim index 7da9bbc4..537f856d 100644 --- a/src/css/cssvalues.nim +++ b/src/css/cssvalues.nim @@ -266,9 +266,8 @@ type BorderCollapseCollapse = "collapse" CSSContentType* = enum - ContentNone, ContentString, ContentOpenQuote, ContentCloseQuote, - ContentNoOpenQuote, ContentNoCloseQuote, ContentImage, ContentVideo, - ContentAudio, ContentNewline, ContentIFrame + ContentString, ContentOpenQuote, ContentCloseQuote, ContentNoOpenQuote, + ContentNoCloseQuote, ContentImage, ContentNewline CSSFloat* = enum FloatNone = "none" diff --git a/src/css/layout.nim b/src/css/layout.nim index de255afa..471c7364 100644 --- a/src/css/layout.nim +++ b/src/css/layout.nim @@ -2,9 +2,9 @@ import std/algorithm import std/math import css/box +import css/cascade import css/cssvalues import css/lunit -import css/stylednode import html/dom import types/bitmap import types/winattrs @@ -49,17 +49,10 @@ type myRootProperties: CSSValues # placeholder text data imgText: CharacterData - audioText: CharacterData - videoText: CharacterData - iframeText: CharacterData luctx: LUContext const DefaultSpan = Span(start: 0, send: LUnit.high) -# Defined here so it isn't accidentally used in dom. -func newCharacterData(data: sink string): CharacterData = - return CharacterData(data: data) - func minWidth(sizes: ResolvedSizes): LUnit = return sizes.bounds.a[dtHorizontal].start @@ -3158,7 +3151,6 @@ proc buildFromElem(ctx: var BlockBuilderContext; styledNode: StyledNode; proc buildReplacement(ctx: var BlockBuilderContext; child: StyledNode; parent: Element; computed: CSSValues) = case child.content.t - of ContentNone: assert false # unreachable for `content' of ContentOpenQuote: let quotes = parent.computed{"quotes"} let s = if quotes == nil: @@ -3199,12 +3191,6 @@ proc buildReplacement(ctx: var BlockBuilderContext; child: StyledNode; )) else: ctx.pushInlineText(computed, parent, ctx.lctx.imgText) - of ContentVideo: - ctx.pushInlineText(computed, parent, ctx.lctx.videoText) - of ContentAudio: - ctx.pushInlineText(computed, parent, ctx.lctx.audioText) - of ContentIFrame: - ctx.pushInlineText(computed, parent, ctx.lctx.iframeText) of ContentNewline: ctx.pushInline(InlineBox( t: ibtNewline, @@ -3446,9 +3432,6 @@ proc layout*(root: StyledNode; attrsp: ptr WindowAttributes): BlockBox = positioned: @[PositionedItem(), PositionedItem()], myRootProperties: rootProperties(), imgText: newCharacterData("[img]"), - videoText: newCharacterData("[video]"), - audioText: newCharacterData("[audio]"), - iframeText: newCharacterData("[iframe]"), luctx: LUContext() ) let box = BlockBox(computed: root.computed, node: root.element) diff --git a/src/css/match.nim b/src/css/match.nim index c23ac8da..737dd1d6 100644 --- a/src/css/match.nim +++ b/src/css/match.nim @@ -216,7 +216,7 @@ func matches*(element: Element; cxsel: ComplexSelector; depends: var DependencyInfo): bool = var e = element var pmatch = mtTrue - var mdepends = DependencyInfo() + var mdepends = DependencyInfo.default for i in countdown(cxsel.high, 0): var match = mtFalse case cxsel[i].ct diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim index 026fe140..70da9119 100644 --- a/src/css/selectorparser.nim +++ b/src/css/selectorparser.nim @@ -13,16 +13,6 @@ type peNone = "-cha-none" peBefore = "before" peAfter = "after" - # internal - peInputText = "-cha-input-text" - peTextareaText = "-cha-textarea-text" - peImage = "-cha-image" - peNewline = "-cha-newline" - peVideo = "-cha-video" - peAudio = "-cha-audio" - peCanvas = "-cha-canvas" - peSVG = "-cha-svg" - peIFrame = "-cha-iframe" PseudoClass* = enum pcFirstChild = "first-child" diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim deleted file mode 100644 index 5a123d9f..00000000 --- a/src/css/stylednode.nim +++ /dev/null @@ -1,72 +0,0 @@ -import css/cssvalues -import css/selectorparser -import html/dom - -# Container to hold a style and a node. -#TODO: maintaining a separate tree for styles is inefficient, and at -# this point, most of it has been moved to the DOM anyway. -# -# The only purpose it has left is to arrange text, element and -# pseudo-element nodes into a single seq, but this should be done -# with an iterator instead. - -type - StyledType* = enum - stElement, stText, stReplacement - - StyledNode* = ref object - element*: Element - pseudo*: PseudoElement - case t*: StyledType - of stText: - text*: CharacterData - of stElement: - children*: seq[StyledNode] - of stReplacement: - # replaced elements: quotes, or (TODO) markers, images - content*: CSSContent - -when defined(debug): - func `$`*(node: StyledNode): string = - if node == nil: - return "nil" - case node.t - of stText: - return "#text " & node.text.data - of stElement: - if node.pseudo != peNone: - return $node.element.tagType & "::" & $node.pseudo - return $node.element - of stReplacement: - return "#replacement" - -template computed*(styledNode: StyledNode): CSSValues = - styledNode.element.computedMap[styledNode.pseudo] - -proc isValid*(styledNode: StyledNode; toReset: var seq[Element]): bool = - if styledNode.t in {stText, stReplacement} or styledNode.pseudo != peNone: - # pseudo elements do not have selector dependencies - return true - return styledNode.element.isValid(toReset) - -func newStyledElement*(element: Element): StyledNode = - return StyledNode(t: stElement, element: element) - -func newStyledElement*(parent: StyledNode; pseudo: PseudoElement): StyledNode = - return StyledNode( - t: stElement, - pseudo: pseudo, - element: parent.element - ) - -func newStyledText*(parent: StyledNode; text: Text): StyledNode = - return StyledNode(t: stText, text: text, element: parent.element) - -func newStyledReplacement*(parent: StyledNode; content: sink CSSContent; - pseudo: PseudoElement): StyledNode = - return StyledNode( - t: stReplacement, - element: parent.element, - content: content, - pseudo: pseudo - ) diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index 2359e65e..676ae0d8 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -88,6 +88,7 @@ proc restart*(wrapper: HTML5ParserWrapper; charset: Charset) = proc setQuirksModeImpl(builder: ChaDOMBuilder; quirksMode: QuirksMode) = if not builder.document.parserCannotChangeModeFlag: builder.document.mode = quirksMode + builder.document.applyQuirksSheet() proc setEncodingImpl(builder: ChaDOMBuilder; encoding: string): SetEncodingResult = @@ -162,7 +163,7 @@ proc insertTextImpl(builder: ChaDOMBuilder; parent: Node; text: string; if prevSibling != nil and prevSibling of Text: Text(prevSibling).data &= text if parent of Element: - Element(parent).invalid = true + Element(parent).invalidate() else: let text = builder.document.createTextNode(text) discard parent.insertBefore(text, before) @@ -209,6 +210,8 @@ proc elementPoppedImpl(builder: ChaDOMBuilder; element: Node) = if window != nil: let svg = SVGSVGElement(element) window.loadResource(svg) + elif element of HTMLStyleElement: + HTMLStyleElement(element).updateSheet() proc newChaDOMBuilder(url: URL; window: Window; factory: CAtomFactory; confidence: CharsetConfidence; charset = DefaultCharset): ChaDOMBuilder = diff --git a/src/html/dom.nim b/src/html/dom.nim index 92011371..167d4b74 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -1,5 +1,6 @@ import std/algorithm import std/deques +import std/hashes import std/math import std/options import std/posix @@ -72,12 +73,11 @@ type DependencyType* = enum dtHover, dtChecked, dtFocus, dtTarget - DependencyInfoItem = object - t: DependencyType - element: Element + DependencyMap = object + dependsOn: Table[Element, seq[Element]] + dependedBy: Table[Element, seq[Element]] - DependencyInfo* = object - items: seq[DependencyInfoItem] + DependencyInfo* = array[DependencyType, seq[Element]] Location = ref object window: Window @@ -123,7 +123,7 @@ type userAgent*: string referrer* {.jsget.}: string autofocus*: bool - maybeRestyle*: proc() + maybeRestyle*: proc(element: Element) performance* {.jsget.}: Performance # Navigator stuff @@ -213,6 +213,7 @@ type throwOnDynamicMarkupInsertion: int activeParserWasAborted: bool writeBuffers*: seq[DocumentWriteBuffer] + styleDependencies: array[DependencyType, DependencyMap] scriptsToExecSoon*: seq[HTMLScriptElement] scriptsToExecInOrder*: Deque[HTMLScriptElement] @@ -229,8 +230,10 @@ type invalid*: bool # whether the document must be rendered again cachedAll: HTMLAllCollection - cachedSheets: seq[CSSStylesheet] - cachedSheetsInvalid*: bool + + uaSheets*: seq[CSSStylesheet] + userSheet*: CSSStylesheet + authorSheets*: seq[CSSStylesheet] cachedForms: HTMLCollection parser*: RootRef @@ -269,10 +272,7 @@ type namespaceURI {.jsget.}: CAtom prefix {.jsget.}: CAtom internalHover: bool - invalid*: bool - # The owner StyledNode is marked as invalid when one of these no longer - # matches the DOM value. - invalidDeps*: set[DependencyType] + selfDepends: set[DependencyType] localName* {.jsget.}: CAtom id* {.jsget.}: CAtom name {.jsget.}: CAtom @@ -282,8 +282,6 @@ type cachedAttributes: NamedNodeMap cachedStyle*: CSSStyleDeclaration computedMap*: array[peNone..peAfter, CSSValues] - # All elements our style depends on, for each dependency type d. - depends*: DependencyInfo AttrDummyElement = ref object of Element @@ -343,7 +341,7 @@ type value* {.jsget.}: Option[int32] HTMLStyleElement* = ref object of HTMLElement - sheet: CSSStylesheet + sheet*: CSSStylesheet HTMLLinkElement* = ref object of HTMLElement sheets: seq[CSSStylesheet] @@ -540,8 +538,8 @@ proc newHTMLElement*(document: Document; tagType: TagType): HTMLElement proc parseColor(element: Element; s: string): ARGBColor proc reflectAttr(element: Element; name: CAtom; value: Option[string]) proc remove*(node: Node) -proc setInvalid*(element: Element) -proc setInvalid*(element: Element; dep: DependencyType) +proc invalidate*(element: Element) +proc invalidate*(element: Element; dep: DependencyType) # Forward declaration hacks # set in css/match @@ -2628,6 +2626,9 @@ func serializeFragment*(node: Node): string = result.serializeFragment(node) # Element +proc hash(element: Element): Hash = + return hash(cast[pointer](element)) + func innerHTML(element: Element): string {.jsfget.} = #TODO xml return element.serializeFragment() @@ -2658,22 +2659,50 @@ func crossOrigin(element: HTMLImageElement): CORSAttribute {.jsfget.} = func referrerpolicy(element: HTMLScriptElement): Option[ReferrerPolicy] = return strictParseEnum[ReferrerPolicy](element.attr(satReferrerpolicy)) -proc sheets*(document: Document): seq[CSSStylesheet] = - if document.cachedSheetsInvalid and document.window.styling: - document.cachedSheets.setLen(0) +func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet = + if ss == nil: + return nil + var res = CSSStylesheet() + res[] = ss[] + for mq in ss.mqList: + if mq.query.applies(window.settings.scripting, window.attrsp): + res.add(mq.children.applyMediaQuery(window)) + return move(res) + +proc applyUASheet*(document: Document) = + const ua = staticRead"res/ua.css" + document.uaSheets.add(ua.parseStylesheet(document.factory, nil, + document.window.attrsp).applyMediaQuery(document.window)) + if document.documentElement != nil: + document.documentElement.invalidate() + +proc applyQuirksSheet*(document: Document) = + const quirks = staticRead"res/quirk.css" + document.uaSheets.add(quirks.parseStylesheet(document.factory, nil, + document.window.attrsp).applyMediaQuery(document.window)) + if document.documentElement != nil: + document.documentElement.invalidate() + +proc applyUserSheet*(document: Document; user: string) = + document.userSheet = user.parseStylesheet(document.factory, nil, + document.window.attrsp).applyMediaQuery(document.window) + if document.documentElement != nil: + document.documentElement.invalidate() + +#TODO this should be cached & called incrementally +proc applyAuthorSheets*(document: Document) = + let window = document.window + if window.styling and document.documentElement != nil: + document.authorSheets = @[] for elem in document.documentElement.descendants: if elem of HTMLStyleElement: let style = HTMLStyleElement(elem) - style.sheet = style.textContent.parseStylesheet(document.factory, - document.baseURL, document.window.attrsp) - document.cachedSheets.add(style.sheet) + document.authorSheets.add(style.sheet) elif elem of HTMLLinkElement: let link = HTMLLinkElement(elem) if link.enabled.get(not link.relList.containsIgnoreCase(satAlternate)): - document.cachedSheets.add(link.sheets) - else: discard - document.cachedSheetsInvalid = false - return document.cachedSheets + document.authorSheets.add(link.sheets) + document.documentElement.invalidate() func isButton*(element: Element): bool = if element of HTMLButtonElement: @@ -2755,10 +2784,10 @@ func focus*(document: Document): Element = proc setFocus*(document: Document; element: Element) = if document.focus != nil: - document.focus.setInvalid(dtFocus) + document.focus.invalidate(dtFocus) document.internalFocus = element if element != nil: - element.setInvalid(dtFocus) + element.invalidate(dtFocus) proc focus(ctx: JSContext; element: Element) {.jsfunc.} = let window = ctx.getWindow() @@ -2773,16 +2802,16 @@ func target*(document: Document): Element = proc setTarget*(document: Document; element: Element) = if document.target != nil: - document.target.setInvalid(dtTarget) + document.target.invalidate(dtTarget) document.internalTarget = element if element != nil: - element.setInvalid(dtTarget) + element.invalidate(dtTarget) func hover*(element: Element): bool = return element.internalHover proc setHover*(element: Element; hover: bool) = - element.setInvalid(dtHover) + element.invalidate(dtHover) element.internalHover = hover func findAutoFocus*(document: Document): Element = @@ -2969,9 +2998,9 @@ func length(this: HTMLFormElement): int {.jsfget.} = func jsForm(this: HTMLInputElement): HTMLFormElement {.jsfget: "form".} = return this.form -proc setValue(this: HTMLInputElement; value: string) {.jsfset: "value".} = +proc setValue*(this: HTMLInputElement; value: string) {.jsfset: "value".} = this.value = value - this.setInvalid() + this.invalidate() proc setType(this: HTMLInputElement; s: string) {.jsfset: "type".} = this.attr(satType, s) @@ -2984,11 +3013,11 @@ proc setChecked*(input: HTMLInputElement; b: bool) {.jsfset: "checked".} = # fully invalidate them on checked change. if input.inputType == itRadio: for radio in input.radiogroup: - radio.setInvalid(dtChecked) - radio.setInvalid() + radio.invalidate(dtChecked) + radio.invalidate() radio.internalChecked = false - input.setInvalid(dtChecked) - input.setInvalid() + input.invalidate(dtChecked) + input.invalidate() input.internalChecked = b func inputString*(input: HTMLInputElement): string = @@ -3076,7 +3105,7 @@ func select*(option: HTMLOptionElement): HTMLSelectElement = proc setSelected*(option: HTMLOptionElement; selected: bool) {.jsfset: "selected".} = - option.setInvalid(dtChecked) + option.invalidate(dtChecked) option.selected = selected let select = option.select if select != nil and not select.attrb(satMultiple): @@ -3088,12 +3117,12 @@ proc setSelected*(option: HTMLOptionElement; selected: bool) if option.selected: if prevSelected != nil: prevSelected.selected = false - prevSelected.setInvalid(dtChecked) + prevSelected.invalidate(dtChecked) prevSelected = option if select.attrul(satSize).get(1) == 1 and prevSelected == nil and firstOption != nil: firstOption.selected = true - firstOption.setInvalid(dtChecked) + firstOption.invalidate(dtChecked) # <select> func jsForm(this: HTMLSelectElement): HTMLFormElement {.jsfget: "form".} = @@ -3229,7 +3258,7 @@ proc setSelectedIndex*(this: HTMLSelectElement; n: int) it.dirty = true else: it.selected = false - it.setInvalid(dtChecked) + it.invalidate(dtChecked) it.invalidateCollections() inc i @@ -3271,6 +3300,15 @@ proc remove(ctx: JSContext; this: HTMLSelectElement; idx: varargs[JSValue]): this.remove() ok() +# <style> +proc updateSheet*(this: HTMLStyleElement) = + let document = this.document + let window = document.window + if window != nil: + this.sheet = this.textContent.parseStylesheet(document.factory, + document.baseURL, window.attrsp).applyMediaQuery(window) + document.applyAuthorSheets() + # <table> func caption(this: HTMLTableElement): Element {.jsfget.} = return this.findFirstChildOf(TAG_CAPTION) @@ -3750,15 +3788,27 @@ proc delAttr(element: Element; i: int; keep = false) = map.attrlist.del(j) # ordering does not matter element.reflectAttr(name, none(string)) element.invalidateCollections() - element.setInvalid() + element.invalidate() # Styles. -# +template computed*(element: Element): CSSValues = + element.computedMap[peNone] + +proc invalidate*(element: Element) = + let valid = element.computed != nil + for it in element.computedMap.mitems: + it = nil + if element.document != nil: + element.document.invalid = true + if valid: + for it in element.elementList: + it.invalidate() + # 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. +# each element holds a list of elements their CSS values depend on. +# (This list may include the element itself.) In addition, elements +# 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: @@ -3783,36 +3833,37 @@ proc delAttr(element: Element; i: int; keep = false) = # 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. -proc setInvalid*(element: Element) = - element.invalid = true - if element.document != nil: - element.document.invalid = true -proc setInvalid*(element: Element; dep: DependencyType) = - element.invalidDeps.incl(dep) - if element.document != nil: - element.document.invalid = true - -template computed*(element: Element): CSSValues = - element.computedMap[peNone] +proc invalidate*(element: Element; dep: DependencyType) = + if dep in element.selfDepends: + element.invalidate() + element.document.styleDependencies[dep].dependedBy.withValue(element, p): + for it in p[]: + it.invalidate() -proc isValid*(element: Element; toReset: var seq[Element]): bool = - if element.invalid: - toReset.add(element) - return false - for it in element.depends.items: - if it.t in it.element.invalidDeps: - toReset.add(it.element) - return false - return true +proc applyStyleDependencies*(element: Element; depends: DependencyInfo) = + let document = element.document + element.selfDepends = {} + for t, map in document.styleDependencies.mpairs: + map.dependsOn.withValue(element, p): + for it in p[]: + map.dependedBy.del(it) + document.styleDependencies[t].dependsOn.del(element) + for el in depends[t]: + if el == element: + element.selfDepends.incl(t) + continue + document.styleDependencies[t].dependedBy.mgetOrPut(el, @[]).add(element) + document.styleDependencies[t].dependsOn.mgetOrPut(element, @[]).add(el) proc add*(depends: var DependencyInfo; element: Element; t: DependencyType) = - depends.items.add(DependencyInfoItem(t: t, element: element)) + depends[t].add(element) proc merge*(a: var DependencyInfo; b: DependencyInfo) = - for it in b.items: - if it notin a.items: - a.items.add(it) + for t, it in b: + for x in it: + if x notin a[t]: + a[t].add(x) proc newCSSStyleDeclaration(element: Element; value: string; computed = false; readonly = false): CSSStyleDeclaration = @@ -3959,7 +4010,7 @@ proc getComputedStyle0*(window: Window; element: Element; of "": peNone else: return newCSSStyleDeclaration(nil, "") if window.settings.scripting == smApp: - window.maybeRestyle() + window.maybeRestyle(element) return newCSSStyleDeclaration(element, $element.computedMap[pseudo], computed = true, readonly = true) # In lite mode, we just parse the "style" attribute and hope for @@ -3989,8 +4040,10 @@ proc loadSheet(window: Window; link: HTMLLinkElement; url: URL; applies: bool) = if applies: # Note: we intentionally load all sheets to prevent media query # based tracking. - link.sheets.add(sheet) - window.document.cachedSheetsInvalid = true + link.sheets.add(sheet.applyMediaQuery(window)) + window.document.applyAuthorSheets() + if window.document.documentElement != nil: + window.document.documentElement.invalidate() for url in sheet.importList: window.loadSheet(link, url, true) #TODO media query ) @@ -4024,8 +4077,9 @@ proc getImageId(window: Window): int = proc loadResource*(window: Window; image: HTMLImageElement) = if not window.images: - image.invalid = image.invalid or image.bitmap != nil - image.bitmap = nil + if image.bitmap != nil: + image.invalidate() + image.bitmap = nil image.fetchStarted = false return if image.fetchStarted: @@ -4124,16 +4178,17 @@ proc loadResource*(window: Window; image: HTMLImageElement) = cachedURL.bmp = bmp for share in cachedURL.shared: share.bitmap = bmp - share.setInvalid() - image.setInvalid() + share.invalidate() + image.invalidate() ) ) window.pendingResources.add(p) proc loadResource*(window: Window; svg: SVGSVGElement) = if not window.images: - svg.invalid = svg.invalid or svg.bitmap != nil - svg.bitmap = nil + if svg.bitmap != nil: + svg.invalidate() + svg.bitmap = nil svg.fetchStarted = false return if svg.fetchStarted: @@ -4144,7 +4199,7 @@ proc loadResource*(window: Window; svg: SVGSVGElement) = window.svgCache.withValue(s, elp): svg.bitmap = elp.bitmap if svg.bitmap != nil: # already decoded - svg.setInvalid() + svg.invalidate() else: # tell me when you're done elp.shared.add(svg) return @@ -4197,8 +4252,8 @@ proc loadResource*(window: Window; svg: SVGSVGElement) = ) for share in svg.shared: share.bitmap = svg.bitmap - share.setInvalid() - svg.setInvalid() + share.invalidate() + svg.invalidate() ) window.pendingResources.add(p) @@ -4291,7 +4346,7 @@ proc reflectAttr(element: Element; name: CAtom; value: Option[string]) = if name == satDisabled: # IE won :( if link.enabled.isNone: - link.document.cachedSheetsInvalid = true + link.document.applyAuthorSheets() link.enabled = some(value.isNone) if link.isConnected and name in {satHref, satRel, satDisabled}: link.fetchStarted = false @@ -4354,7 +4409,7 @@ proc attr*(element: Element; name: CAtom; value: string) = if i >= 0: element.attrs[i].value = value element.invalidateCollections() - element.setInvalid() + element.invalidate() else: element.attrs.insert(AttrData( qualifiedName: name, @@ -4385,7 +4440,7 @@ proc attrns*(element: Element; localName: CAtom; prefix: NamespacePrefix; element.attrs[i].qualifiedName = qualifiedName element.attrs[i].value = value element.invalidateCollections() - element.setInvalid() + element.invalidate() else: element.attrs.insert(AttrData( prefix: prefixAtom, @@ -4536,14 +4591,15 @@ proc remove*(node: Node; suppressObservers: bool) = parent.invalidateCollections() node.invalidateCollections() if parent of Element: - Element(parent).setInvalid() + Element(parent).invalidate() node.parentNode = nil node.index = -1 if element != nil: element.elIndex = -1 - if element.document != nil and - (element of HTMLStyleElement or element of HTMLLinkElement): - element.document.cachedSheetsInvalid = true + if element.document != nil: + if element of HTMLStyleElement or element of HTMLLinkElement: + element.document.applyAuthorSheets() + element.applyStyleDependencies(DependencyInfo.default) #TODO assigned, shadow root, shadow root again, custom nodes, registered # observers #TODO not suppress observers => queue tree mutation record @@ -4579,7 +4635,7 @@ proc resetElement*(element: Element) = input.files.setLen(0) else: input.value = input.attr(satValue) - input.setInvalid() + input.invalidate() of TAG_SELECT: let select = HTMLSelectElement(element) var firstOption: HTMLOptionElement = nil @@ -4592,7 +4648,7 @@ proc resetElement*(element: Element) = if option.selected: if not multiple and prevSelected != nil: prevSelected.selected = false - prevSelected.setInvalid(dtChecked) + prevSelected.invalidate(dtChecked) prevSelected = option if not multiple and select.attrul(satSize).get(1) == 1 and prevSelected == nil and firstOption != nil: @@ -4600,7 +4656,7 @@ proc resetElement*(element: Element) = of TAG_TEXTAREA: let textarea = HTMLTextAreaElement(element) textarea.value = textarea.childTextContent() - textarea.setInvalid() + textarea.invalidate() else: discard proc setForm*(element: FormAssociatedElement; form: HTMLFormElement) = @@ -4645,7 +4701,8 @@ proc resetFormOwner(element: FormAssociatedElement) = element.setForm(HTMLFormElement(ancestor)) proc elementInsertionSteps(element: Element) = - if element of HTMLOptionElement: + case element.tagType + of TAG_OPTION: if element.parentElement != nil: let parent = element.parentElement var select: HTMLSelectElement @@ -4656,21 +4713,23 @@ proc elementInsertionSteps(element: Element) = select = HTMLSelectElement(parent.parentElement) if select != nil: select.resetElement() - elif element of FormAssociatedElement: - let element = FormAssociatedElement(element) - if element.parserInserted: - return - element.resetFormOwner() - elif element of HTMLLinkElement: + of TAG_LINK: let window = element.document.window if window != nil: let link = HTMLLinkElement(element) window.loadResource(link) - elif element of HTMLImageElement: + of TAG_IMAGE: let window = element.document.window if window != nil: let image = HTMLImageElement(element) window.loadResource(image) + of TAG_STYLE: + HTMLStyleElement(element).updateSheet() + elif element of FormAssociatedElement: + let element = FormAssociatedElement(element) + if element.parserInserted: + return + element.resetFormOwner() func isValidParent(node: Node): bool = return node of Element or node of Document or node of DocumentFragment @@ -4761,7 +4820,7 @@ proc insertNode(parent, node, before: Node) = parent.invalidateCollections() if node.document != nil and (node of HTMLStyleElement or node of HTMLLinkElement): - node.document.cachedSheetsInvalid = true + node.document.applyAuthorSheets() for el in node.elementsIncl: #TODO shadow root el.elementInsertionSteps() @@ -4783,7 +4842,7 @@ proc insert*(parent, node, before: Node; suppressObservers = false) = #TODO live ranges discard if parent of Element: - Element(parent).setInvalid() + Element(parent).invalidate() for node in nodes: insertNode(parent, node, before) @@ -4986,7 +5045,7 @@ proc setTextContent(ctx: JSContext; node: Node; data: JSValue): Err[void] proc reset*(form: HTMLFormElement) = for control in form.controls: control.resetElement() - control.setInvalid() + control.invalidate() proc renderBlocking(element: Element): bool = if "render" in element.attr(satBlocking).split(AsciiWhitespace): diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 9258b293..ce44ac56 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -17,7 +17,6 @@ import css/layout import css/lunit import css/render import css/sheet -import css/stylednode import html/catom import html/chadombuilder import html/dom @@ -94,11 +93,9 @@ type lines: FlexibleGrid loader: FileLoader needsBOMSniff: bool - needsReshape: bool outputId: int pollData: PollData prevHover: Element - prevStyled: StyledNode pstream: SocketStream # control stream quirkstyle: CSSStylesheet reportedBytesRead: int @@ -107,9 +104,7 @@ type savetask: bool state: BufferState tasks: array[BufferCommand, int] #TODO this should have arguments - uastyle: CSSStylesheet url: URL # URL before readFromFd - userstyle: CSSStylesheet window: Window BufferIfaceItem = object @@ -766,34 +761,15 @@ proc checkRefresh*(buffer: Buffer): CheckRefreshResult {.proxy.} = return CheckRefreshResult(n: -1) return CheckRefreshResult(n: n, url: url.get) -proc maybeRestyle(buffer: Buffer) = - if buffer.document == nil: - return - if buffer.document.invalid or buffer.document.cachedSheetsInvalid: - let uastyle = if buffer.document.mode != QUIRKS: - buffer.uastyle - else: - buffer.quirkstyle - if buffer.document.cachedSheetsInvalid: - buffer.prevStyled = nil - let styledRoot = buffer.document.applyStylesheets(uastyle, - buffer.userstyle, buffer.prevStyled) - buffer.prevStyled = styledRoot - buffer.document.invalid = false - buffer.needsReshape = true - proc maybeReshape(buffer: Buffer): bool {.discardable.} = - if buffer.document == nil: + if buffer.document == nil and buffer.document.documentElement != nil: return # not parsed yet, nothing to render - buffer.maybeRestyle() - if buffer.needsReshape: - buffer.rootBox = nil - # applyStylesheets may return nil if there is no <html> element. - if buffer.prevStyled != nil: - buffer.rootBox = buffer.prevStyled.layout(addr buffer.attrs) + if buffer.document.invalid: + let root = initStyledElement(buffer.document.documentElement) + buffer.rootBox = root.layout(addr buffer.attrs) buffer.lines.renderDocument(buffer.bgcolor, buffer.rootBox, addr buffer.attrs, buffer.images) - buffer.needsReshape = false + buffer.document.invalid = false return true return false @@ -814,7 +790,8 @@ proc processData0(buffer: Buffer; data: UnsafeSlice): bool = Text(lastChild).data &= data else: plaintext.insert(buffer.document.createTextNode($data), nil) - plaintext.setInvalid() + #TODO just invalidate document? + plaintext.invalidate() true func canSwitch(buffer: Buffer): bool {.inline.} = @@ -831,7 +808,9 @@ proc switchCharset(buffer: Buffer) = buffer.initDecoder() buffer.htmlParser.restart(buffer.charset) buffer.document = buffer.htmlParser.builder.document - buffer.prevStyled = nil + buffer.document.applyUASheet() + buffer.document.applyUserSheet(buffer.config.userstyle) + buffer.document.invalid = true proc bomSniff(buffer: Buffer; iq: openArray[uint8]): int = if iq[0] == 0xFE and iq[1] == 0xFF: @@ -1125,6 +1104,7 @@ proc onload(buffer: Buffer) = reprocess = false else: # EOF buffer.finishLoad().then(proc() = + buffer.document.invalid = true buffer.maybeReshape() buffer.state = bsLoaded buffer.document.readyState = rsComplete @@ -1164,11 +1144,15 @@ proc getTitle*(buffer: Buffer): string {.proxy, task.} = proc forceReshape0(buffer: Buffer) = if buffer.document != nil: buffer.document.invalid = true - buffer.needsReshape = true buffer.maybeReshape() proc forceReshape2(buffer: Buffer) = - buffer.prevStyled = nil + if buffer.document != nil and buffer.document.documentElement != nil: + buffer.document.documentElement.invalidate() + buffer.document.applyUASheet() + if buffer.document.mode == QUIRKS: + buffer.document.applyQuirksSheet() + buffer.document.applyUserSheet(buffer.config.userstyle) buffer.forceReshape0() proc forceReshape*(buffer: Buffer) {.proxy.} = @@ -1370,20 +1354,19 @@ proc readSuccess*(buffer: Buffer; s: string; hasFd: bool): ReadSuccessResult case input.inputType of itFile: input.files = @[newWebFile(s, fd)] - input.setInvalid() + input.invalidate() buffer.maybeReshape() res.repaint = true res.open = buffer.implicitSubmit(input) else: - input.value = s - input.setInvalid() + input.setValue(s) buffer.maybeReshape() res.repaint = true res.open = buffer.implicitSubmit(input) of TAG_TEXTAREA: let textarea = HTMLTextAreaElement(buffer.document.focus) textarea.value = s - textarea.setInvalid() + textarea.invalidate() buffer.maybeReshape() res.repaint = true else: discard @@ -1959,7 +1942,9 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes; if buffer.config.scripting != smFalse: buffer.window.navigate = proc(url: URL) = buffer.navigate(url) if buffer.config.scripting == smApp: - buffer.window.maybeRestyle = proc() = buffer.maybeRestyle() + buffer.window.maybeRestyle = proc(element: Element) = + if element.computed == nil: + element.applyStyle() buffer.charset = buffer.charsetStack.pop() buffer.fd = istream.fd buffer.istream = istream @@ -1970,14 +1955,7 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes; loader.unregisterFun = proc(fd: int) = buffer.pollData.unregister(fd) buffer.pollData.register(buffer.rfd, POLLIN) - const css = staticRead"res/ua.css" - const quirk = css & staticRead"res/quirk.css" buffer.initDecoder() - let attrsp = addr buffer.attrs - buffer.uastyle = css.parseStylesheet(factory, nil, attrsp) - buffer.quirkstyle = quirk.parseStylesheet(factory, nil, attrsp) - buffer.userstyle = buffer.config.userstyle.parseStylesheet(factory, nil, - attrsp) buffer.htmlParser = newHTML5ParserWrapper( buffer.window, buffer.url, @@ -1987,6 +1965,8 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes; ) assert buffer.htmlParser.builder.document != nil buffer.document = buffer.htmlParser.builder.document + buffer.document.applyUASheet() + buffer.document.applyUserSheet(buffer.config.userstyle) buffer.runBuffer() buffer.cleanup() quit(0) |