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/html | |
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/html')
-rw-r--r-- | src/html/dom.nim | 144 | ||||
-rw-r--r-- | src/html/env.nim | 3 |
2 files changed, 125 insertions, 22 deletions
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 ), |