diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buffer/buffer.nim | 19 | ||||
-rw-r--r-- | src/display/client.nim | 1 | ||||
-rw-r--r-- | src/html/dom.nim | 29 | ||||
-rw-r--r-- | src/html/env.nim | 2 | ||||
-rw-r--r-- | src/io/loader.nim | 23 | ||||
-rw-r--r-- | src/io/promise.nim | 28 | ||||
-rw-r--r-- | src/io/readablestream.nim | 32 | ||||
-rw-r--r-- | src/io/request.nim | 51 | ||||
-rw-r--r-- | src/io/response.nim | 50 | ||||
-rw-r--r-- | src/js/javascript.nim | 27 |
10 files changed, 164 insertions, 98 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index 7a51792a..6c2a8eda 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -28,7 +28,6 @@ import img/png import io/loader import io/posixstream import io/promise -import io/request import io/teestream import io/window import ips/serialize @@ -559,8 +558,14 @@ proc loadResource(buffer: Buffer, elem: HTMLLinkElement): EmptyPromise = let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media))) if not media.applies(document.window): return return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = - if res.contenttype == "text/css": - elem.sheet = parseStylesheet(res.body)) + if res.res == 0: #TODO remove res + #TODO we should use ReadableStreams for this (which would allow us to + # parse CSS asynchronously) + # yet another hack: needed because closing a stream before + # unregistering breaks + if res.contenttype == "text/css": + elem.sheet = parseStylesheet(res.body) + res.unregisterFun()) proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise = let document = buffer.document @@ -569,10 +574,12 @@ proc loadResource(buffer: Buffer, elem: HTMLImageElement): EmptyPromise = let url = parseURL(src, document.url.some) if url.isSome: let url = url.get - return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) = + return buffer.loader.fetch(newRequest(url)).then(proc(res: Response): Promise[string] = if res.contenttype == "image/png": - let pngData = res.body.readAll() - elem.bitmap = fromPNG(toOpenArrayByte(pngData, 0, pngData.high))) + #TODO using text() for PNG is wrong + return res.text() + ).then(proc(pngData: string) = + elem.bitmap = fromPNG(toOpenArrayByte(pngData, 0, pngData.high))) proc loadResources(buffer: Buffer): EmptyPromise = let document = buffer.document diff --git a/src/display/client.nim b/src/display/client.nim index 9783650f..88185fb7 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -552,6 +552,7 @@ proc newClient*(config: Config, dispatcher: Dispatcher): Client = ctx.addBlobModule() ctx.addFormDataModule() ctx.addRequestModule() + ctx.addResponseModule() ctx.addLineEditModule() ctx.addConfigModule() ctx.addPagerModule() diff --git a/src/html/dom.nim b/src/html/dom.nim index 3d901eb9..20b10674 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -15,9 +15,10 @@ import encoding/decoderstream import html/tags import img/bitmap import img/painter -import img/png import img/path +import img/png import io/loader +import io/promise import io/request import io/window import js/javascript @@ -2371,26 +2372,30 @@ proc createClassicScript(source: string, baseURL: URL, options: ScriptOptions, m #TODO settings object proc fetchClassicScript(element: HTMLScriptElement, url: URL, - options: ScriptOptions, cors: CORSAttribute, - cs: Charset, onComplete: (proc(element: HTMLScriptElement, - res: ScriptResult))) = + options: ScriptOptions, cors: CORSAttribute, + cs: Charset, onComplete: (proc(element: HTMLScriptElement, + res: ScriptResult))) = if not element.scriptingEnabled: element.onComplete(ScriptResult(t: RESULT_NULL)) else: let loader = element.document.window.loader if loader.isSome: let request = createPotentialCORSRequest(url, RequestDestination.SCRIPT, cors) - #TODO this should be async... - let r = loader.get.doRequest(request) - if r.res != 0 or r.body == nil: - element.onComplete(ScriptResult(t: RESULT_NULL)) - else: - #TODO use charset from content-type + loader.get.fetch(request).then(proc(r: Response): auto = + if r.res != 0 or r.body == nil: + #TODO remove res + element.onComplete(ScriptResult(t: RESULT_NULL)) + else: + #TODO use charset from content-type + #TODO text() should decode + return r.text() + ).then(proc(s: string) = + let ss = newStringStream(s) #TODO unnecessary copy let cs = if cs == CHARSET_UNKNOWN: CHARSET_UTF_8 else: cs - let source = newDecoderStream(r.body, cs = cs).readAll() + let source = newDecoderStream(ss, cs = cs).readAll() #TODO use response url let script = createClassicScript(source, url, options, false) - element.markAsReady(ScriptResult(t: RESULT_SCRIPT, script: script)) + element.markAsReady(ScriptResult(t: RESULT_SCRIPT, script: script))) proc log*(console: console, ss: varargs[string]) {.jsfunc.} = var s = "" diff --git a/src/html/env.nim b/src/html/env.nim index ca7e497d..5883dabf 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -6,6 +6,7 @@ import html/htmlparser import io/loader import io/promise import io/request +import io/response import io/window import js/intl import js/javascript @@ -120,6 +121,7 @@ proc addScripting*(window: Window, selector: Selector[int]) = ctx.addBlobModule() ctx.addFormDataModule() ctx.addRequestModule() + ctx.addResponseModule() proc runJSJobs*(window: Window) = window.jsrt.runJSJobs(window.console.err) diff --git a/src/io/loader.nim b/src/io/loader.nim index f40882fc..1b5d8847 100644 --- a/src/io/loader.nim +++ b/src/io/loader.nim @@ -26,6 +26,7 @@ import io/http import io/posixstream import io/promise import io/request +import io/response import io/urlfilter import ips/serialize import ips/serversocket @@ -37,6 +38,9 @@ import types/referer import types/url import utils/twtstr +export request +export response + type FileLoader* = ref object process*: Pid @@ -260,14 +264,6 @@ proc fetch*(loader: FileLoader, input: Request): Promise[Response] = ) return promise -proc newResponse(res: int, request: Request, stream: Stream = nil): Response = - return Response( - res: res, - url: request.url, - body: stream, - bodyRead: Promise[string]() - ) - const BufferSize = 4096 proc onConnected*(loader: FileLoader, fd: int) = @@ -278,12 +274,15 @@ proc onConnected*(loader: FileLoader, fd: int) = var res: int stream.sread(res) if res == 0: - let response = newResponse(res, request, stream) + let response = newResponse(res, request, fd, stream) assert loader.unregisterFun != nil + let realCloseImpl = stream.closeImpl + stream.closeImpl = nil response.unregisterFun = proc() = loader.ongoing.del(fd) loader.unregistered.add(fd) loader.unregisterFun(fd) + realCloseImpl(stream) stream.sread(response.status) stream.sread(response.headers) applyHeaders(request, response) @@ -292,6 +291,7 @@ proc onConnected*(loader: FileLoader, fd: int) = response: response, readbufsize: BufferSize, ) + SocketStream(stream).source.getFd().setBlocking(false) promise.resolve(response) else: loader.unregisterFun(fd) @@ -316,10 +316,7 @@ proc onRead*(loader: FileLoader, fd: int) = buffer[].buf.setLen(olen + n) if response.body.atEnd(): response.bodyRead.resolve(buffer[].buf) - loader.unregisterFun(fd) - loader.ongoing.del(fd) - loader.unregistered.add(fd) - response.body.close() + response.unregisterFun() break except ErrorAgain, ErrorWouldBlock: assert buffer.readbufsize > 1 diff --git a/src/io/promise.nim b/src/io/promise.nim index 0e1b6115..d825c4fa 100644 --- a/src/io/promise.nim +++ b/src/io/promise.nim @@ -64,7 +64,9 @@ func empty*(map: PromiseMap): bool = map.tab.len == 0 proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} = - if promise == nil: return + if promise == nil: + doAssert false + return promise.cb = cb promise.next = EmptyPromise() if promise.state == PROMISE_FULFILLED: @@ -72,7 +74,7 @@ proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} = return promise.next proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} = - if promise == nil: return + doAssert promise != nil return promise.then(proc() = if promise.get != nil: promise.get(promise.opaque, promise.res) @@ -80,28 +82,32 @@ proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable cb(promise.res)) proc then*[T](promise: EmptyPromise, cb: (proc(): Promise[T])): Promise[T] {.discardable.} = - if promise == nil: return + doAssert promise != nil let next = Promise[T]() promise.then(proc() = - let p2 = cb() + var p2 = cb() if p2 != nil: p2.then(proc(x: T) = next.res = x - next.resolve())) + next.resolve()) + else: + next.resolve()) return next proc then*[T](promise: Promise[T], cb: (proc(x: T): EmptyPromise)): EmptyPromise {.discardable.} = - if promise == nil: return + doAssert promise != nil let next = EmptyPromise() promise.then(proc(x: T) = let p2 = cb(x) if p2 != nil: p2.then(proc() = - next.resolve())) + next.resolve()) + else: + next.resolve()) return next proc then*[T, U](promise: Promise[T], cb: (proc(x: T): U)): Promise[U] {.discardable.} = - if promise == nil: return + doAssert promise != nil let next = Promise[U]() promise.then(proc(x: T) = next.res = cb(x) @@ -109,14 +115,16 @@ proc then*[T, U](promise: Promise[T], cb: (proc(x: T): U)): Promise[U] {.discard return next proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] {.discardable.} = - if promise == nil: return + doAssert promise != nil let next = Promise[U]() promise.then(proc(x: T) = let p2 = cb(x) if p2 != nil: p2.then(proc(y: U) = next.res = y - next.resolve())) + next.resolve()) + else: + next.resolve()) return next proc all*(promises: seq[EmptyPromise]): EmptyPromise = diff --git a/src/io/readablestream.nim b/src/io/readablestream.nim new file mode 100644 index 00000000..16a650e2 --- /dev/null +++ b/src/io/readablestream.nim @@ -0,0 +1,32 @@ +#TODO.... + +type + UnderlyingSourceStartCallback = proc(controller: ReadableStreamController): + Option[JSValue] # may be undefined! + UnderlyingSourcePullCallback = proc(controller: ReadableStreamController): + EmptyPromise + UnderlyingSourceCancelCallback = proc(reason = none(JSValue)): EmptyPromise + + ReadableStreamType = enum + BYOB = "byob" + + UnderlyingSource* = object + start*: Option[UnderlyingSourceStartCallback] + pull*: Option[UnderlyingSourcePullCallback] + cancel*: Option[UnderlyingSourcePullCallback] + #TODO mark real name being type + ctype*: Option[ReadableStreamType] + + QueuingStrategySize = proc(chunk: JSValue): float64 # unrestricted + + QueuingStrategy* = object + highWaterMark*: float64 # unrestricted + size*: QueuingStrategySize + + ReadableStream* = object + underlyingSource: UnderlyingSource + +proc newReadableStream(underlyingSource = none(UnderlyingSource), + strategy = none(QueuingStrategySize)): ReadableStream = + let this = ReadableStream() + discard diff --git a/src/io/request.nim b/src/io/request.nim index 61fee6dd..11e16718 100644 --- a/src/io/request.nim +++ b/src/io/request.nim @@ -4,7 +4,6 @@ import strutils import tables import bindings/quickjs -import io/promise import js/javascript import types/formdata import types/url @@ -74,18 +73,6 @@ type destination* {.jsget.}: RequestDestination credentialsMode* {.jsget.}: CredentialsMode proxy*: URL #TODO do something with this - - Response* = ref object - body*: Stream - bodyUsed* {.jsget.}: bool - res* {.jsget.}: int - contenttype* {.jsget.}: string - status* {.jsget.}: int - headers* {.jsget.}: Headers - redirect*: Request - url*: URL #TODO should be urllist? - unregisterFun*: proc() - bodyRead*: Promise[string] ReadableStream* = ref object of Stream isource*: Stream @@ -95,19 +82,11 @@ type Headers* = ref object table* {.jsget.}: Table[string, seq[string]] -proc Request_url(ctx: JSContext, this: JSValue, magic: cint): JSValue {.cdecl.} = - let op = getOpaque0(this) - if unlikely(not ctx.isInstanceOf(this, "Request") or op == nil): - return JS_ThrowTypeError(ctx, "Value is not an instance of %s", "Request") - let request = cast[Request](op) - return toJS(ctx, $request.url) +proc js_url(this: Request): string {.jsfget: "url".} = + return $this.url -proc Request_referrer(ctx: JSContext, this: JSValue, magic: cint): JSValue {.cdecl.} = - let op = getOpaque0(this) - if unlikely(not ctx.isInstanceOf(this, "Request") or op == nil): - return JS_ThrowTypeError(ctx, "Value is not an instance of %s", "Request") - let request = cast[Request](op) - return toJS(ctx, $request.referer) +proc js_referrer(this: Request): string {.jsfget: "referrer".} = + return $this.referer iterator pairs*(headers: Headers): (string, string) = for k, vs in headers.table: @@ -304,22 +283,6 @@ func getOrDefault*(headers: Headers, k: string, default = ""): string = else: default -#TODO: this should be a property of body -proc close*(response: Response) {.jsfunc.} = - response.bodyUsed = true - if response.unregisterFun != nil: - response.unregisterFun() - if response.body != nil: - response.body.close() - -proc text*(response: Response): Promise[string] {.jsfunc.} = - return response.bodyRead - -proc json(ctx: JSContext, this: Response): Promise[JSValue] {.jsfunc.} = - return this.text().then(proc(s: string): JSValue = - return JS_ParseJSON(ctx, cstring(s), cast[csize_t](s.len), - cstring"<input>")) - func credentialsMode*(attribute: CORSAttribute): CredentialsMode = case attribute of NO_CORS, ANONYMOUS: @@ -328,9 +291,5 @@ func credentialsMode*(attribute: CORSAttribute): CredentialsMode = return INCLUDE proc addRequestModule*(ctx: JSContext) = - ctx.registerType(Request, extra_getset = [ - TabGetSet(name: "url", get: Request_url), - TabGetSet(name: "referrer", get: Request_referrer) - ]) - ctx.registerType(Response) + ctx.registerType(Request) ctx.registerType(Headers) diff --git a/src/io/response.nim b/src/io/response.nim new file mode 100644 index 00000000..7d0f789a --- /dev/null +++ b/src/io/response.nim @@ -0,0 +1,50 @@ +import streams + +import bindings/quickjs +import io/promise +import io/request +import js/javascript +import types/url + +type + Response* = ref object + fd*: int + body*: Stream + bodyUsed* {.jsget.}: bool + res* {.jsget.}: int + contenttype* {.jsget.}: string + status* {.jsget.}: int + headers* {.jsget.}: Headers + redirect*: Request + url*: URL #TODO should be urllist? + unregisterFun*: proc() + bodyRead*: Promise[string] + +proc newResponse*(res: int, request: Request, fd = -1, stream: Stream = nil): + Response = + return Response( + res: res, + url: request.url, + body: stream, + bodyRead: Promise[string](), + fd: fd + ) + +#TODO: this should be a property of body +proc close*(response: Response) {.jsfunc.} = + response.bodyUsed = true + if response.unregisterFun != nil: + response.unregisterFun() + if response.body != nil: + response.body.close() + +proc text*(response: Response): Promise[string] {.jsfunc.} = + return response.bodyRead + +proc json(ctx: JSContext, this: Response): Promise[JSValue] {.jsfunc.} = + return this.text().then(proc(s: string): JSValue = + return JS_ParseJSON(ctx, cstring(s), cast[csize_t](s.len), + cstring"<input>")) + +proc addResponseModule*(ctx: JSContext) = + ctx.registerType(Response) diff --git a/src/js/javascript.nim b/src/js/javascript.nim index 4de01aa9..9afff65a 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -869,6 +869,7 @@ type jsFunCall: NimNode jsCallAndRet: NimNode minArgs: int + actualMinArgs: int # minArgs without JSContext i: int # nim parameters accounted for j: int # js parameters accounted for (not including fix ones, e.g. `this') res: NimNode @@ -1171,8 +1172,7 @@ proc addFixParam(gen: var JSFuncGenerator, name: string) = inc gen.i proc addRequiredParams(gen: var JSFuncGenerator) = - let minArgs = gen.funcParams.getMinArgs() - while gen.i < minArgs: + while gen.i < gen.minArgs: let s = ident("arg_" & $gen.i) let tt = gen.funcParams[gen.i][1] if tt.typeKind == ntyGenericParam: @@ -1245,12 +1245,7 @@ export JS_ThrowTypeError, JS_ThrowRangeError, JS_ThrowSyntaxError, proc newJSProcBody(gen: var JSFuncGenerator, isva: bool): NimNode = let tt = gen.thisType let fn = gen.funcName - var ma = gen.minArgs - if gen.thisname.isSome: - ma -= 1 - if gen.passCtx: - ma -= 1 - assert ma >= 0 + var ma = gen.actualMinArgs result = newStmtList() if isva: result.add(quote do: @@ -1331,6 +1326,15 @@ proc addThisName(gen: var JSFuncGenerator, thisname: Option[string]) = gen.thisType = gen.returnType.get.strVal gen.newName = ident($gen.t & "_" & gen.funcName) +func getActualMinArgs(gen: var JSFuncGenerator): int = + var ma = gen.minArgs + if gen.thisname.isSome: + dec ma + if gen.passCtx: + dec ma + assert ma >= 0 + return ma + proc setupGenerator(fun: NimNode, t: BoundFunctionType, thisname = some("this"), jsname: string = ""): JSFuncGenerator = let jsFunCallList = newStmtList() @@ -1351,6 +1355,7 @@ proc setupGenerator(fun: NimNode, t: BoundFunctionType, jsFunCall: newCall(fun[0]) ) gen.addJSContext() + gen.actualMinArgs = gen.getActualMinArgs() # must come after passctx is set gen.addThisName(thisname) return gen @@ -1486,8 +1491,8 @@ macro jsgetprop*(fun: typed) = macro jsfgetn(jsname: static string, fun: typed) = var gen = setupGenerator(fun, GETTER, jsname = jsname) - if gen.minArgs != 1 or gen.funcParams.len != gen.minArgs: - error("jsfget functions must only have one parameter.") + if gen.actualMinArgs != 0 or gen.funcParams.len != gen.minArgs: + error("jsfget functions must only accept one parameter.") if gen.returnType.isnone: error("jsfget functions must have a return type.") if gen.newName.strVal in existing_funcs: @@ -1515,7 +1520,7 @@ macro jsfget*(jsname: static string, fun: typed) = macro jsfsetn(jsname: static string, fun: typed) = var gen = setupGenerator(fun, SETTER, jsname = jsname) - if gen.minArgs != 2 or gen.funcParams.len != gen.minArgs: + if gen.actualMinArgs != 1 or gen.funcParams.len != gen.minArgs: error("jsfset functions must accept two parameters") if gen.returnType.issome: error("jsfset functions must not have a return type") |