diff options
author | bptato <nincsnevem662@gmail.com> | 2023-05-14 13:25:36 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-05-14 13:25:36 +0200 |
commit | 58dee598d30c5d107f9c469eb01c660b39832f9a (patch) | |
tree | 3529a5b3499b33ad92ae394c22ad7782cf6fb7c1 /src | |
parent | 938bb0d0edd2a688e4ab9ca775b2d30ffb907d72 (diff) | |
download | chawan-58dee598d30c5d107f9c469eb01c660b39832f9a.tar.gz |
Async resource loading, exception handling fixes
Diffstat (limited to 'src')
-rw-r--r-- | src/buffer/buffer.nim | 78 | ||||
-rw-r--r-- | src/io/http.nim | 5 | ||||
-rw-r--r-- | src/io/promise.nim | 19 | ||||
-rw-r--r-- | src/ips/forkserver.nim | 24 |
4 files changed, 102 insertions, 24 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index a824deee..bb255f14 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -53,6 +53,12 @@ type GET_SOURCE, GET_LINES, UPDATE_HOVER, PASS_FD, CONNECT, GOTO_ANCHOR, CANCEL, GET_TITLE + # LOADING_PAGE: istream open + # LOADING_RESOURCES: istream closed, resources open + # LOADED: istream closed, resources closed + BufferState* = enum + LOADING_PAGE, LOADING_RESOURCES, LOADED + HoverType* = enum HOVER_TITLE = "TITLE" HOVER_LINK = "URL" @@ -87,7 +93,7 @@ type pstream: Stream # pipe stream srenderer: StreamRenderer connected: bool - loaded: bool # istream is closed + state: BufferState prevnode: StyledNode loader: FileLoader config: BufferConfig @@ -274,6 +280,19 @@ func getClickable(styledNode: StyledNode): Element = func submitForm(form: HTMLFormElement, submitter: Element): Option[Request] +func canSubmitOnClick(fae: FormAssociatedElement): bool = + if fae.form == nil: + return false + if fae.form.canSubmitImplicitly(): + return true + if fae.tagType == TAG_BUTTON: + if HTMLButtonElement(fae).ctype == BUTTON_SUBMIT: + return true + if fae.tagType == TAG_INPUT: + if HTMLInputElement(fae).inputType in {INPUT_SUBMIT, INPUT_BUTTON}: + return true + return false + func getClickHover(styledNode: StyledNode): string = let clickable = styledNode.getClickable() if clickable != nil: @@ -284,7 +303,7 @@ func getClickHover(styledNode: StyledNode): string = #TODO this is inefficient and also quite stupid if clickable.tagType in FormAssociatedElements: let fae = FormAssociatedElement(clickable) - if fae.form != nil and fae.form.canSubmitImplicitly(): + if fae.canSubmitOnClick(): let req = fae.form.submitForm(fae) if req.isSome: return $req.get.url @@ -522,7 +541,7 @@ proc updateHover*(buffer: Buffer, cursorx, cursory: int): UpdateHoverResult {.pr buffer.prevnode = thisnode -proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement) = +proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement): EmptyPromise = let href = elem.attr("href") if href == "": return let url = parseURL(href, document.url.some) @@ -533,16 +552,20 @@ proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement) = if media != "": let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) if not media.applies(): return - let fs = buffer.loader.doRequest(newRequest(url)) - if fs.body != nil and fs.contenttype == "text/css": - elem.sheet = parseStylesheet(fs.body) + return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = + if res.contenttype == "text/css": + elem.sheet = parseStylesheet(res.body)) -proc loadResources(buffer: Buffer, document: Document) = +proc loadResources(buffer: Buffer, document: Document): EmptyPromise = + var promises: seq[EmptyPromise] if document.html != nil: for elem in document.html.elements(TAG_LINK): let elem = HTMLLinkElement(elem) if elem.rel == "stylesheet": - buffer.loadResource(document, elem) + let p = buffer.loadResource(document, elem) + if p != nil: + promises.add(p) + return all(promises) type ConnectResult* = object code*: int @@ -609,8 +632,12 @@ proc connect*(buffer: Buffer): ConnectResult {.proxy.} = const BufferSize = 4096 -proc finishLoad(buffer: Buffer) = - if buffer.loaded: return +proc finishLoad(buffer: Buffer): EmptyPromise = + if buffer.state != LOADING_PAGE: + let p = EmptyPromise() + p.resolve() + return p + var p: EmptyPromise case buffer.contenttype of "text/html": buffer.sstream.setPosition(0) @@ -623,10 +650,16 @@ proc finishLoad(buffer: Buffer) = buffer.sstream.setPosition(0) let (doc, _) = parseHTML(buffer.sstream, cs = some(cs), window = buffer.window, url = buffer.url) buffer.document = doc - buffer.loadResources(buffer.document) + p = buffer.loadResources(buffer.document) + buffer.state = LOADING_RESOURCES + else: + p = EmptyPromise() + p.resolve() + buffer.state = LOADED buffer.selector.unregister(buffer.fd) + buffer.fd = -1 buffer.istream.close() - buffer.loaded = true + return p type LoadResult* = tuple[ atend: bool, @@ -635,7 +668,7 @@ type LoadResult* = tuple[ ] proc load*(buffer: Buffer): LoadResult {.proxy, task.} = - if buffer.loaded: + if buffer.state == LOADED: return (true, buffer.lines.len, -1) else: buffer.savetask = true @@ -653,9 +686,14 @@ proc resolveTask[T](buffer: Buffer, cmd: BufferCommand, res: T) = proc onload(buffer: Buffer) = var res: LoadResult = (false, buffer.lines.len, -1) - if buffer.loaded: + case buffer.state + of LOADING_RESOURCES: + assert false + of LOADED: buffer.resolveTask(LOAD, res) return + of LOADING_PAGE: + discard let op = buffer.sstream.getPosition() var s = newString(buffer.readbufsize) try: @@ -674,7 +712,9 @@ proc onload(buffer: Buffer) = buffer.do_reshape() if buffer.istream.atEnd(): res.atend = true - buffer.finishLoad() + buffer.finishLoad().then(proc() = + buffer.resolveTask(LOAD, res)) + return buffer.resolveTask(LOAD, res) except ErrorAgain, ErrorWouldBlock: if buffer.readbufsize > 1: @@ -689,9 +729,10 @@ proc render*(buffer: Buffer): int {.proxy.} = return buffer.lines.len proc cancel*(buffer: Buffer): int {.proxy.} = - if buffer.loaded: return + #TODO TODO TODO cancel resource loading too + if buffer.state != LOADING_PAGE: return buffer.istream.close() - buffer.loaded = true + buffer.state = LOADED case buffer.contenttype of "text/html": buffer.sstream.setPosition(0) @@ -1057,9 +1098,10 @@ proc passFd*(buffer: Buffer) {.proxy.} = proc getSource*(buffer: Buffer) {.proxy.} = let ssock = initServerSocket() let stream = ssock.acceptSocketStream() - buffer.finishLoad() + let op = buffer.sstream.getPosition() buffer.sstream.setPosition(0) stream.write(buffer.sstream.readAll()) + buffer.sstream.setPosition(op) stream.close() ssock.close() diff --git a/src/io/http.nim b/src/io/http.nim index 60387faf..2eefb9ce 100644 --- a/src/io/http.nim +++ b/src/io/http.nim @@ -52,7 +52,10 @@ proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: point let op = cast[HandleData](userdata) if not op.statusline: op.statusline = true - op.ostream.swrite(int(CURLE_OK)) + try: + op.ostream.swrite(int(CURLE_OK)) + except IOError: # Broken pipe + return 0 var status: int op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) op.ostream.swrite(status) diff --git a/src/io/promise.nim b/src/io/promise.nim index 46825244..00702cec 100644 --- a/src/io/promise.nim +++ b/src/io/promise.nim @@ -1,10 +1,14 @@ import tables type + PromiseState = enum + PROMISE_PENDING, PROMISE_FULFILLED, PROMISE_REJECTED + EmptyPromise* = ref object of RootObj cb: (proc()) next: EmptyPromise opaque: pointer + state: PromiseState Promise*[T] = ref object of EmptyPromise res: T @@ -37,6 +41,7 @@ proc resolve*(promise: EmptyPromise) = if promise.cb != nil: promise.cb() promise.cb = nil + promise.state = PROMISE_FULFILLED promise = promise.next if promise == nil: break @@ -62,6 +67,8 @@ proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} = if promise == nil: return promise.cb = cb promise.next = EmptyPromise() + if promise.state == PROMISE_FULFILLED: + promise.resolve() return promise.next proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} = @@ -103,3 +110,15 @@ proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] next.res = y next.resolve())) return next + +proc all*(promises: seq[EmptyPromise]): EmptyPromise = + let res = EmptyPromise() + var i = 0 + for promise in promises: + promise.then(proc() = + inc i + if i == promises.len: + res.resolve()) + if promises.len == 0: + res.resolve() + return res diff --git a/src/ips/forkserver.nim b/src/ips/forkserver.nim index 5cfc7c9a..009c5491 100644 --- a/src/ips/forkserver.nim +++ b/src/ips/forkserver.nim @@ -69,8 +69,15 @@ proc forkLoader(ctx: var ForkServerContext, config: LoaderConfig): Pid = ctx.children.setLen(0) zeroMem(addr ctx, sizeof(ctx)) discard close(pipefd[0]) # close read - runFileLoader(pipefd[1], config) - assert false + try: + runFileLoader(pipefd[1], config) + except CatchableError: + let e = getCurrentException() + # taken from system/excpt.nim + let msg = e.getStackTrace() & "Error: unhandled exception: " & e.msg & + " [" & $e.name & "]" + eprint(msg) + doAssert false let readfd = pipefd[0] # get read discard close(pipefd[1]) # close write var readf: File @@ -108,8 +115,15 @@ proc forkBuffer(ctx: var ForkServerContext): Pid = discard close(stdin.getFileHandle()) discard close(stdout.getFileHandle()) let loader = FileLoader(process: loaderPid) - launchBuffer(config, source, attrs, loader, mainproc) - assert false + try: + launchBuffer(config, source, attrs, loader, mainproc) + except CatchableError: + let e = getCurrentException() + # taken from system/excpt.nim + let msg = e.getStackTrace() & "Error: unhandled exception: " & e.msg & + " [" & $e.name & "]" + eprint(msg) + doAssert false ctx.children.add((pid, loaderPid)) return pid @@ -187,7 +201,7 @@ proc newForkServer*(): ForkServer = discard close(pipefd_out[1]) discard close(pipefd_err[1]) runForkServer() - assert false + doAssert false else: discard close(pipefd_in[0]) # close read discard close(pipefd_out[1]) # close write |