diff options
author | bptato <nincsnevem662@gmail.com> | 2024-02-08 17:42:18 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-02-08 18:01:43 +0100 |
commit | bb67136fd92e1bf777f51816bdb73ff0a0001f4c (patch) | |
tree | 89f0c45bd6b986ba424dc35012d4b29b6fc6f06c /src | |
parent | 250cbcfaf6cb57b152659fb9e3c195ef4938adae (diff) | |
download | chawan-bb67136fd92e1bf777f51816bdb73ff0a0001f4c.tar.gz |
buffer: load external resources when they are attached
We no longer have to wait for the entire document to be loaded to start loading CSS.
Diffstat (limited to 'src')
-rw-r--r-- | src/css/cascade.nim | 2 | ||||
-rw-r--r-- | src/html/dom.nim | 144 | ||||
-rw-r--r-- | src/html/env.nim | 3 | ||||
-rw-r--r-- | src/server/buffer.nim | 87 |
4 files changed, 133 insertions, 103 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index 0168e940..4429dee4 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -79,6 +79,8 @@ func applies*(mqlist: MediaQueryList, window: Window): bool = return true return false +appliesFwdDecl = applies + type ToSorts = array[PseudoElem, seq[(int, seq[CSSDeclaration])]] diff --git a/src/html/dom.nim b/src/html/dom.nim index cdcb1dbf..c5140759 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -7,6 +7,7 @@ import std/strutils import std/tables import css/cssparser +import css/mediaquery import css/sheet import css/values import display/winattrs @@ -18,6 +19,7 @@ import img/bitmap import img/painter import img/path import img/png +import io/promise import js/console import js/domexception import js/error @@ -77,6 +79,8 @@ type navigate*: proc(url: URL) importMapsAllowed*: bool factory*: CAtomFactory + loadingResourcePromises*: seq[EmptyPromise] + images*: bool # Navigator stuff Navigator* = object @@ -276,6 +280,7 @@ type HTMLLinkElement* = ref object of HTMLElement sheet*: CSSStylesheet relList {.jsget.}: DOMTokenList + fetchStarted: bool HTMLFormElement* = ref object of HTMLElement smethod*: string @@ -360,6 +365,7 @@ type HTMLImageElement* = ref object of HTMLElement bitmap*: Bitmap + fetchStarted: bool jsDestructor(Navigator) jsDestructor(PluginArray) @@ -2363,11 +2369,6 @@ func form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} = proc setRelList(link: HTMLLinkElement, s: string) {.jsfset: "relList".} = link.attr(atRel, s) -proc setSheet*(link: HTMLLinkElement, sheet: CSSStylesheet) = - link.sheet = sheet - if link.document != nil: - link.document.cachedSheetsInvalid = true - # <form> proc setRelList(form: HTMLFormElement, s: string) {.jsfset: "relList".} = form.attr(atRel, s) @@ -2706,6 +2707,80 @@ proc style*(element: Element): CSSStyleDeclaration {.jsfget.} = element.style_cached = CSSStyleDeclaration(element: element) return element.style_cached +# Forward declaration hack +var appliesFwdDecl*: proc(mqlist: MediaQueryList, window: Window): bool + {.nimcall, noSideEffect.} + +# see https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet +#TODO make this somewhat compliant with ^this +proc loadResource(window: Window, link: HTMLLinkElement) = + if link.fetchStarted: + return + link.fetchStarted = true + let href = link.attr(atHref) + if href == "": + return + let url = parseURL(href, window.document.url.some) + if url.isSome and window.loader.isSome: + let loader = window.loader.get + let url = url.get + let media = link.media + if media != "": + let cvals = parseListOfComponentValues(newStringStream(media)) + let media = parseMediaQueryList(cvals) + if not media.appliesFwdDecl(window): + return + let p = loader.fetch( + newRequest(url) + ).then(proc(res: JSResult[Response]): Promise[JSResult[string]] = + if res.isOk: + let res = res.get + #TODO we should use ReadableStreams for this (which would allow us to + # parse CSS asynchronously) + if res.contentType == "text/css": + return res.text() + res.unregisterFun() + ).then(proc(s: JSResult[string]) = + if s.isOk: + #TODO this is extremely inefficient, and text() should return + # utf8 anyways + let ss = newStringStream(s.get) + #TODO non-utf-8 css + let ds = newDecoderStream(ss, cs = CHARSET_UTF_8) + let source = newEncoderStream(ds, cs = CHARSET_UTF_8) + link.sheet = parseStylesheet(source, window.factory) + window.document.cachedSheetsInvalid = true + ) + window.loadingResourcePromises.add(p) + +proc loadResource(window: Window, image: HTMLImageElement) = + if not window.images or image.fetchStarted: + return + image.fetchStarted = true + let src = image.attr(atSrc) + if src == "": + return + let url = parseURL(src, window.document.url.some) + if url.isSome and window.loader.isSome: + let url = url.get + let loader = window.loader.get + let p = loader.fetch(newRequest(url)) + .then(proc(res: JSResult[Response]): Promise[JSResult[Blob]] = + if res.isErr: + return + let res = res.get + if res.contentType == "image/png": + return res.blob() + ).then(proc(pngData: JSResult[Blob]) = + if pngData.isErr: + return + let pngData = pngData.get + let buffer = cast[ptr UncheckedArray[uint8]](pngData.buffer) + let high = int(pngData.size - 1) + image.bitmap = fromPNG(toOpenArray(buffer, 0, high)) + ) + window.loadingResourcePromises.add(p) + proc reflectAttrs(element: Element, name: CAtom, value: string) = let name = element.document.toAttrType(name) template reflect_str(element: Element, n: AttrType, val: untyped) = @@ -2758,6 +2833,12 @@ proc reflectAttrs(element: Element, name: CAtom, value: string) = of "button": return BUTTON_BUTTON) of TAG_LINK: let link = HTMLLinkElement(element) + if link.isConnected and (name == atRel and value == "stylesheet" or + name == atHref): + link.fetchStarted = false + let window = link.document.window + if window != nil: + window.loadResource(link) link.reflect_domtoklist atRel, relList of TAG_A: let anchor = HTMLAnchorElement(element) @@ -2772,6 +2853,14 @@ proc reflectAttrs(element: Element, name: CAtom, value: string) = let canvas = HTMLCanvasElement(element) if canvas.bitmap.width != w or canvas.bitmap.height != h: canvas.bitmap = newBitmap(w, h) + of TAG_IMG: + let image = HTMLImageElement(element) + # https://html.spec.whatwg.org/multipage/images.html#relevant-mutations + if name == atSrc: + image.fetchStarted = false + let window = image.document.window + if window != nil: + window.loadResource(image) else: discard proc attr*(element: Element, name: CAtom, value: string) = @@ -3058,25 +3147,38 @@ proc resetFormOwner(element: FormAssociatedElement) = if form of HTMLFormElement: element.setForm(HTMLFormElement(form)) +proc elementInsertionSteps(element: Element) = + if element of HTMLOptionElement: + if element.parentElement != nil: + let parent = element.parentElement + var select: HTMLSelectElement + if parent of HTMLSelectElement: + select = HTMLSelectElement(parent) + elif parent.tagType == TAG_OPTGROUP and parent.parentElement != nil and + parent.parentElement of HTMLSelectElement: + 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: + let window = element.document.window + if window != nil: + let link = HTMLLinkElement(element) + window.loadResource(link) + elif element of HTMLImageElement: + let window = element.document.window + if window != nil: + let image = HTMLImageElement(element) + window.loadResource(image) + proc insertionSteps(insertedNode: Node) = if insertedNode of Element: let element = Element(insertedNode) - if element of HTMLOptionElement: - if element.parentElement != nil: - let parent = element.parentElement - var select: HTMLSelectElement - if parent of HTMLSelectElement: - select = HTMLSelectElement(parent) - elif parent.tagType == TAG_OPTGROUP and parent.parentElement != nil and - parent.parentElement of HTMLSelectElement: - select = HTMLSelectElement(parent.parentElement) - if select != nil: - select.resetElement() - if element of FormAssociatedElement: - let element = FormAssociatedElement(element) - if element.parserInserted: - return - element.resetFormOwner() + element.elementInsertionSteps() func isValidParent(node: Node): bool = return node of Element or node of Document or node of DocumentFragment diff --git a/src/html/env.nim b/src/html/env.nim index 9eff5e03..8a570b93 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -176,7 +176,7 @@ proc addScripting*(window: Window, selector: Selector[int]) = proc runJSJobs*(window: Window) = window.jsrt.runJSJobs(window.console.err) -proc newWindow*(scripting: bool, selector: Selector[int], +proc newWindow*(scripting, images: bool, selector: Selector[int], attrs: WindowAttributes, factory: CAtomFactory, navigate: proc(url: URL) = nil, loader = none(FileLoader)): Window = let err = newFileStream(stderr) @@ -185,6 +185,7 @@ proc newWindow*(scripting: bool, selector: Selector[int], console: newConsole(err), navigator: Navigator(), loader: loader, + images: images, settings: EnvironmentSettings( scripting: scripting ), diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 704a5d1d..211bb024 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -24,14 +24,12 @@ import html/dom import html/enums import html/env import html/event -import img/png import io/posixstream import io/promise import io/serialize import io/serversocket import io/socketstream import io/teestream -import js/error import js/fromjs import js/javascript import js/regex @@ -42,7 +40,6 @@ import loader/headers import loader/loader import render/renderdocument import render/rendertext -import types/blob import types/buffersource import types/cell import types/color @@ -56,8 +53,6 @@ import utils/twtstr import xhr/formdata as formdata_impl import chakasu/charset -import chakasu/decoderstream -import chakasu/encoderstream import chame/tags @@ -701,82 +696,8 @@ proc updateHover*(buffer: Buffer, cursorx, cursory: int): UpdateHoverResult {.pr buffer.prevnode = thisnode -proc loadResource(buffer: Buffer, link: HTMLLinkElement): EmptyPromise = - let document = buffer.document - let href = link.attr(atHref) - if href == "": return - let url = parseURL(href, document.url.some) - if url.isSome: - let url = url.get - let media = link.media - if media != "": - let cvals = parseListOfComponentValues(newStringStream(media)) - let media = parseMediaQueryList(cvals) - if not media.applies(document.window): return - return buffer.loader.fetch(newRequest(url)) - .then(proc(res: JSResult[Response]): Promise[JSResult[string]] = - if res.isOk: - let res = res.get - #TODO we should use ReadableStreams for this (which would allow us to - # parse CSS asynchronously) - if res.contentType == "text/css": - return res.text() - res.unregisterFun() - ).then(proc(s: JSResult[string]) = - if s.isOk: - #TODO this is extremely inefficient, and text() should return - # utf8 anyways - let ss = newStringStream(s.get) - #TODO non-utf-8 css - let ds = newDecoderStream(ss, cs = CHARSET_UTF_8) - let source = newEncoderStream(ds, cs = CHARSET_UTF_8) - link.setSheet(parseStylesheet(source, buffer.factory)) - ) - -proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise = - let document = buffer.document - let src = elem.attr(atSrc) - if src == "": return - let url = parseURL(src, document.url.some) - if url.isSome: - let url = url.get - return buffer.loader.fetch(newRequest(url)) - .then(proc(res: JSResult[Response]): Promise[JSResult[Blob]] = - if res.isErr: - return - let res = res.get - if res.contentType == "image/png": - return res.blob() - ).then(proc(pngData: JSResult[Blob]) = - if pngData.isErr: - return - let pngData = pngData.get - let buffer = cast[ptr UncheckedArray[uint8]](pngData.buffer) - let high = int(pngData.size - 1) - elem.bitmap = fromPNG(toOpenArray(buffer, 0, high)) - ) - proc loadResources(buffer: Buffer): EmptyPromise = - let document = buffer.document - var promises: seq[EmptyPromise] - if document.html != nil: - var searchElems = {TAG_LINK} - if buffer.config.images: - searchElems.incl(TAG_IMG) - for elem in document.html.elements(searchElems): - var p: EmptyPromise = nil - case elem.tagType - of TAG_LINK: - let elem = HTMLLinkElement(elem) - if elem.attr(atRel) == "stylesheet": - p = buffer.loadResource(elem) - of TAG_IMG: - let elem = HTMLImageElement(elem) - p = buffer.loadResource(elem) - else: discard - if p != nil: - promises.add(p) - return all(promises) + return buffer.window.loadingResourcePromises.all() type ConnectResult* = object invalid*: bool @@ -797,6 +718,7 @@ proc setHTML(buffer: Buffer, ishtml: bool) = if buffer.config.scripting: buffer.window = newWindow( buffer.config.scripting, + buffer.config.images, buffer.selector, buffer.attrs, factory, @@ -806,9 +728,12 @@ proc setHTML(buffer: Buffer, ishtml: bool) = else: buffer.window = newWindow( buffer.config.scripting, + buffer.config.images, buffer.selector, buffer.attrs, - buffer.factory + factory, + nil, + some(buffer.loader) ) buffer.htmlParser = newHTML5ParserWrapper( buffer.sstream, |