diff options
author | bptato <nincsnevem662@gmail.com> | 2024-08-15 19:11:49 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-08-15 19:23:55 +0200 |
commit | 4bf895db711f3d4d229d3f18fbb2145cce2a73af (patch) | |
tree | 2e81c7399de03aebb9dfa166eba6ee809a75cd2e | |
parent | 885a3493b6cad4b4247a200928fe61e41883aaba (diff) | |
download | chawan-4bf895db711f3d4d229d3f18fbb2145cce2a73af.tar.gz |
xhr: more progress
* add responseText, response * add net tests -> currently sync XHR only; should find a way to do async tests... * update monoucha -> simplified & updated some related code that no longer worked properly
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | Makefile | 15 | ||||
m--------- | lib/monoucha | 0 | ||||
-rw-r--r-- | src/html/chadombuilder.nim | 5 | ||||
-rw-r--r-- | src/html/dom.nim | 72 | ||||
-rw-r--r-- | src/html/env.nim | 12 | ||||
-rw-r--r-- | src/html/xmlhttprequest.nim | 115 | ||||
-rw-r--r-- | src/js/encoding.nim | 28 | ||||
-rw-r--r-- | src/loader/loader.nim | 2 | ||||
-rw-r--r-- | src/loader/response.nim | 13 | ||||
-rw-r--r-- | src/local/pager.nim | 11 | ||||
-rw-r--r-- | src/types/blob.nim | 21 | ||||
-rw-r--r-- | src/version.nim | 2 | ||||
-rw-r--r-- | test/asserts.js | 23 | ||||
l---------[-rw-r--r--] | test/js/asserts.js | 20 | ||||
-rw-r--r-- | test/js/console.html | 5 | ||||
-rwxr-xr-x | test/js/run_js_tests.sh | 6 | ||||
-rwxr-xr-x | test/layout/run_layout_tests.sh | 8 | ||||
-rw-r--r-- | test/net/all.expected | 1 | ||||
l--------- | test/net/asserts.js | 1 | ||||
-rw-r--r-- | test/net/config.toml | 12 | ||||
-rw-r--r-- | test/net/ping | 1 | ||||
-rw-r--r-- | test/net/run.nim | 47 | ||||
-rwxr-xr-x | test/net/run.sh | 17 | ||||
-rw-r--r-- | test/net/xhr.html (renamed from test/js/xhr.html) | 9 |
25 files changed, 328 insertions, 127 deletions
diff --git a/.gitignore b/.gitignore index 070630d8..da0eff4f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -a -cha -target/ -.obj/ +/a +/cha +/target/ +/.obj/ +/test/net/run diff --git a/Makefile b/Makefile index cfc1b71b..e5b94be4 100644 --- a/Makefile +++ b/Makefile @@ -208,7 +208,18 @@ uninstall: submodule: git submodule update --init -.PHONY: test -test: +test/net/run: test/net/run.nim + $(NIMC) test/net/run.nim + +.PHONY: test_js +test_js: (cd test/js; ./run_js_tests.sh) + +test_layout: (cd test/layout; ./run_layout_tests.sh) + +test_net: test/net/run + (cd test/net; ./run) + +.PHONY: test +test: test_js test_layout test_net diff --git a/lib/monoucha b/lib/monoucha -Subproject 20cef6c267447ff389262dbd555fa44f7576c4e +Subproject cf86be35a5e6a0dce00d39a1c9ea57e0b7aa69e diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index 5470deb2..bedd0d66 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -349,10 +349,10 @@ proc finish*(wrapper: HTML5ParserWrapper) = r.parser = nil wrapper.refs.setLen(0) -proc newDOMParser(): DOMParser {.jsctor.} = +proc newDOMParser*(): DOMParser {.jsctor.} = return DOMParser() -proc parseFromString(ctx: JSContext; parser: DOMParser; str, t: string): +proc parseFromString*(ctx: JSContext; parser: DOMParser; str, t: string): JSResult[Document] {.jsfunc.} = case t of "text/html": @@ -361,7 +361,6 @@ proc parseFromString(ctx: JSContext; parser: DOMParser; str, t: string): window.document.url else: newURL("about:blank").get - #TODO this is probably broken in client (or at least sub-optimal) let builder = newChaDOMBuilder(url, window, window.factory, ccIrrelevant) var parser = initHTML5Parser(builder, HTML5ParserOpts[Node, CAtom]()) let res = parser.parseChunk(str) diff --git a/src/html/dom.nim b/src/html/dom.nim index 208f8168..7bd3ff9a 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -464,7 +464,7 @@ proc resetState(state: var DrawingState) = state.path = newPath() proc create2DContext*(jctx: JSContext; target: HTMLCanvasElement; - options: Option[JSValue]): CanvasRenderingContext2D = + options = JS_UNDEFINED): CanvasRenderingContext2D = let ctx = CanvasRenderingContext2D( bitmap: target.bitmap, canvas: target @@ -1273,10 +1273,10 @@ func childNodes(node: Node): NodeList {.jsfget.} = func length(tokenList: DOMTokenList): uint32 {.jsfget.} = return uint32(tokenList.toks.len) -func item(tokenList: DOMTokenList; i: int): Option[string] {.jsfunc.} = +proc item(ctx: JSContext; tokenList: DOMTokenList; i: int): JSValue {.jsfunc.} = if i < tokenList.toks.len: - return some(tokenList.element.document.toStr(tokenList.toks[i])) - return none(string) + return ctx.toJS(tokenList.toks[i]) + return JS_NULL func contains*(tokenList: DOMTokenList; a: CAtom): bool = return a in tokenList.toks @@ -1381,8 +1381,12 @@ func supports(tokenList: DOMTokenList; token: string): func value(tokenList: DOMTokenList): string {.jsfget.} = return $tokenList -func getter(tokenList: DOMTokenList; i: uint32): Option[string] {.jsgetprop.} = - return tokenList.item(int(i)) +proc getter(ctx: JSContext; this: DOMTokenList; atom: JSAtom): JSValue + {.jsgetprop.} = + var u: uint32 + if ctx.fromJS(atom, u).isSome: + return ctx.item(this, int(u)) + return JS_NULL # DOMStringMap func validateAttributeName(name: string): Err[DOMException] = @@ -1450,9 +1454,14 @@ func hasprop(nodeList: NodeList; i: uint32): bool {.jshasprop.} = func item(nodeList: NodeList; i: int): Node {.jsfunc.} = if i < nodeList.len: return nodeList.snapshot[i] + return nil -func getter(nodeList: NodeList; i: uint32): Option[Node] {.jsgetprop.} = - return option(nodeList.item(int(i))) +func getter(ctx: JSContext; nodeList: NodeList; atom: JSAtom): Node + {.jsgetprop.} = + var u: uint32 + if ctx.fromJS(atom, u).isSome: + return nodeList.item(int(u)) + return nil func names(ctx: JSContext; nodeList: NodeList): JSPropertyEnumList {.jspropnames.} = @@ -1482,21 +1491,21 @@ func namedItem(collection: HTMLCollection; s: string): Element {.jsfunc.} = return it return nil -func getter(ctx: JSContext; collection: HTMLCollection; atom: JSAtom): - Opt[Option[Element]] {.jsgetprop.} = +func getter(ctx: JSContext; collection: HTMLCollection; atom: JSAtom): Element + {.jsgetprop.} = var u: uint32 if ctx.fromJS(atom, u).isSome: - return ok(option(collection.item(u))) + return collection.item(u) var s: string if ctx.fromJS(atom, s).isSome: - return ok(option(collection.namedItem(s))) - return err() + return collection.namedItem(s) + return nil func names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList {.jspropnames.} = let L = collection.length var list = newJSPropertyEnumList(ctx, L) - var ids: OrderedSet[CAtom] + var ids = initOrderedSet[CAtom]() for u in 0 ..< L: list.add(u) let elem = collection.item(u) @@ -1509,25 +1518,32 @@ func names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList return list # HTMLAllCollection -proc length(collection: HTMLAllCollection): uint32 {.jsfget.} = - return uint32(collection.len) +proc length(this: HTMLAllCollection): uint32 {.jsfget.} = + return uint32(this.len) -func hasprop(collection: HTMLAllCollection; i: uint32): bool {.jshasprop.} = - return int(i) < collection.len +func hasprop(ctx: JSContext; this: HTMLAllCollection; atom: JSAtom): bool + {.jshasprop.} = + var u: uint32 + if ctx.fromJS(atom, u).isSome: + return int(u) < this.len + return false -func item(collection: HTMLAllCollection; i: uint32): Element {.jsfunc.} = - let i = int(i) - if i < collection.len: - return Element(collection.snapshot[i]) +func item(this: HTMLAllCollection; u: uint32): Element {.jsfunc.} = + let i = int(u) + if i < this.len: + return Element(this.snapshot[i]) return nil -func getter(collection: HTMLAllCollection; i: uint32): Option[Element] +func getter(ctx: JSContext; this: HTMLAllCollection; atom: JSAtom): Element {.jsgetprop.} = - return option(collection.item(i)) + var u: uint32 + if ctx.fromJS(atom, u).isSome: + return this.item(u) + return nil -func names(ctx: JSContext; collection: HTMLAllCollection): JSPropertyEnumList +func names(ctx: JSContext; this: HTMLAllCollection): JSPropertyEnumList {.jspropnames.} = - let L = collection.length + let L = this.length var list = newJSPropertyEnumList(ctx, L) for u in 0 ..< L: list.add(u) @@ -3822,7 +3838,7 @@ proc fetchClassicScript(element: HTMLScriptElement; url: URL; if response.res != 0: element.onComplete(ScriptResult(t: srtNull)) return - window.loader.resume(response.outputId) + response.resume() let s = response.body.recvAll() let cs = if cs == CHARSET_UNKNOWN: CHARSET_UTF_8 else: cs let source = s.decodeAll(cs) @@ -4409,7 +4425,7 @@ func getElementReflectFunctions(): seq[TabGetSet] = )) proc getContext*(jctx: JSContext; this: HTMLCanvasElement; contextId: string; - options = none(JSValue)): RenderingContext {.jsfunc.} = + options = JS_UNDEFINED): RenderingContext {.jsfunc.} = if contextId == "2d": if this.ctx2d != nil: return this.ctx2d diff --git a/src/html/env.nim b/src/html/env.nim index 4ecdc08b..ea55dcbc 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -71,12 +71,11 @@ proc item(pluginArray: var PluginArray): JSValue {.jsfunc.} = JS_NULL proc length(pluginArray: var PluginArray): uint32 {.jsfget.} = 0 proc item(mimeTypeArray: var MimeTypeArray): JSValue {.jsfunc.} = JS_NULL proc length(mimeTypeArray: var MimeTypeArray): uint32 {.jsfget.} = 0 -proc getter(pluginArray: var PluginArray; i: uint32): Option[JSValue] +proc getter(pluginArray: var PluginArray; atom: JSAtom): JSValue {.jsgetprop.} = + return JS_NULL +proc getter(mimeTypeArray: var MimeTypeArray; atom: JSAtom): JSValue {.jsgetprop.} = - discard -proc getter(mimeTypeArray: var MimeTypeArray; i: uint32): Option[JSValue] - {.jsgetprop.} = - discard + return JS_NULL # Screen proc availWidth(screen: var Screen): int64 {.jsfget.} = @@ -116,8 +115,7 @@ func key(this: var Storage; i: uint32): Option[string] {.jsfunc.} = return some(this.map[int(i)].value) return none(string) -func getItem(this: var Storage; s: string): Option[string] - {.jsfunc.} = +func getItem(this: var Storage; s: string): Option[string] {.jsfunc.} = let i = this.find(s) if i != -1: return some(this.map[i].value) diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim index ed1b7035..68039978 100644 --- a/src/html/xmlhttprequest.nim +++ b/src/html/xmlhttprequest.nim @@ -2,8 +2,10 @@ import std/options import std/strutils import std/tables +import chagashi/charset import chagashi/decoder import html/catom +import html/chadombuilder import html/dom import html/event import html/script @@ -19,6 +21,7 @@ import monoucha/javascript import monoucha/jserror import monoucha/quickjs import monoucha/tojs +import types/blob import types/opt import types/url import utils/twtstr @@ -56,7 +59,9 @@ type response: Response responseType {.jsget.}: XMLHttpRequestResponseType timeout {.jsget.}: uint32 + responseObject: JSValue received: string + contentTypeOverride: string ProgressEvent = ref object of Event lengthComputable {.jsget.}: bool @@ -77,9 +82,13 @@ func newXMLHttpRequest(): XMLHttpRequest {.jsctor.} = let upload = XMLHttpRequestUpload() return XMLHttpRequest( upload: upload, - headers: newHeaders() + headers: newHeaders(), + responseObject: JS_UNDEFINED ) +proc finalize(rt: JSRuntime; this: XMLHttpRequest) {.jsfin.} = + JS_FreeValueRT(rt, this.responseObject) + proc newProgressEvent(ctype: CAtom; init = ProgressEventInit()): ProgressEvent {.jsctor.} = let event = ProgressEvent( @@ -170,6 +179,14 @@ proc setRequestHeader(this: XMLHttpRequest; name, value: string): this.headers.table[name.toHeaderCase()] = @[value] ok() +proc setTimeout(ctx: JSContext; this: XMLHttpRequest; value: uint32): + Err[DOMException] {.jsfset: "timeout".} = + if ctx.getWindow() != nil and xhrfSync in this.flags: + return errDOMException("timeout may not be set on synchronous XHR", + "InvalidAccessError") + this.timeout = value + ok() + proc fireProgressEvent(window: Window; target: EventTarget; name: StaticAtom; loaded, length: int64) = let event = newProgressEvent(window.factory.toAtom(name), ProgressEventInit( @@ -316,6 +333,7 @@ proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): JSResult[void] response.opaque = XHROpaque(this: this, window: window, len: len) response.onRead = onReadXHR response.onFinish = onFinishXHR + response.resume() #TODO timeout ) else: # sync @@ -324,6 +342,7 @@ proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): JSResult[void] let response = window.loader.doRequest(request) if response.res == 0: #TODO timeout + response.resume() try: this.received = response.body.recvAll() #TODO report timing @@ -352,11 +371,40 @@ proc statusText(this: XMLHttpRequest): string {.jsfget.} = proc getResponseHeader(ctx: JSContext; this: XMLHttpRequest; name: string): JSValue {.jsfunc.} = - #TODO this can throw, but I don't think the standard allows that? - return ctx.get(this.response.headers, name) + let res = ctx.get(this.response.headers, name) + if JS_IsException(res): + return JS_NULL + return res #TODO getAllResponseHeaders +func getCharset(this: XMLHttpRequest): Charset = + let override = this.contentTypeOverride.toLowerAscii() + let cs = override.getContentTypeAttr("charset").getCharset() + if cs != CHARSET_UNKNOWN: + return cs + return this.response.getCharset(CHARSET_UTF_8) + +proc responseText(ctx: JSContext; this: XMLHttpRequest): JSValue {.jsfget.} = + if this.responseType notin {xhrtUnknown, xhrtText}: + let ex = newDOMException("response type was expected to be '' or 'text'", + "InvalidStateError") + return JS_Throw(ctx, ctx.toJS(ex)) + if this.readyState notin {xhrsLoading, xhrsDone}: + return ctx.toJS("") + let charset = this.getCharset() + #TODO XML encoding stuff? + return ctx.toJS(this.received.decodeAll(charset)) + +proc overrideMimeType(this: XMLHttpRequest; s: string): DOMResult[void] + {.jsfunc.} = + if this.readyState notin {xhrsLoading, xhrsDone}: + return errDOMException("readyState must not be loading or done", + "InvalidStateError") + #TODO parse + this.contentTypeOverride = s + return ok() + proc setResponseType(ctx: JSContext; this: XMLHttpRequest; value: XMLHttpRequestResponseType): Err[DOMException] {.jsfset: "responseType".} = @@ -372,13 +420,60 @@ proc setResponseType(ctx: JSContext; this: XMLHttpRequest; this.responseType = value ok() -proc setTimeout(ctx: JSContext; this: XMLHttpRequest; value: uint32): - Err[DOMException] {.jsfset: "timeout".} = - if ctx.getWindow() != nil and xhrfSync in this.flags: - return errDOMException("timeout may not be set on synchronous XHR", - "InvalidAccessError") - this.timeout = value - ok() +func getContentType(this: XMLHttpRequest): string = + if this.contentTypeOverride != "": + return this.contentTypeOverride + return this.response.getContentType() + +proc ptrify(s: var string): pointer = + if s.len == 0: + return nil + var sr = new(string) + sr[] = move(s) + GC_ref(sr) + return cast[pointer](sr) + +proc deallocPtrified(p: pointer) = + if p != nil: + let sr = cast[ref string](p) + GC_unref(sr) + +proc abufFree(rt: JSRuntime; opaque, p: pointer) {.cdecl.} = + deallocPtrified(opaque) + +proc blobFree(opaque, p: pointer) {.nimcall.} = + deallocPtrified(opaque) + +proc response(ctx: JSContext; this: XMLHttpRequest): JSValue {.jsfget.} = + if this.responseType in {xhrtText, xhrtUnknown}: + return ctx.responseText(this) + if this.readyState != xhrsDone: + return JS_NULL + if JS_IsUndefined(this.responseObject): + case this.responseType + of xhrtArraybuffer: + let len = csize_t(this.received.len) + let p = cast[ptr UncheckedArray[uint8]](cstring(this.received)) + let opaque = this.received.ptrify() + this.responseObject = JS_NewArrayBuffer(ctx, p, len, abufFree, opaque, + false) + of xhrtBlob: + let len = this.received.len + let p = cast[ptr UncheckedArray[uint8]](cstring(this.received)) + let opaque = this.received.ptrify() + let blob = newBlob(p, len, this.getContentType(), blobFree, opaque) + this.responseObject = ctx.toJS(blob) + of xhrtDocument: + #TODO this is certainly not compliant + let res = ctx.parseFromString(newDOMParser(), this.received, "text/html") + this.responseObject = ctx.toJS(res) + of xhrtJSON: + this.responseObject = JS_ParseJSON(ctx, cstring(this.received), + csize_t(this.received.len), cstring"<input>") + else: discard + if JS_IsException(this.responseObject): + this.responseObject = JS_UNDEFINED + return this.responseObject # Event reflection diff --git a/src/js/encoding.nim b/src/js/encoding.nim index 7d1bd126..7aae5eb2 100644 --- a/src/js/encoding.nim +++ b/src/js/encoding.nim @@ -40,31 +40,29 @@ func newJSTextDecoder(label = "utf-8"; options = TextDecoderOptions()): func fatal(this: JSTextDecoder): bool {.jsfget.} = return this.errorMode == demFatal -proc decode0(this: JSTextDecoder; ctx: JSContext; input: JSArrayBufferView; - stream: bool): JSResult[JSValue] = - let H = int(input.abuf.len) - 1 - var oq = "" - for chunk in this.tdctx.decode(input.abuf.p.toOpenArray(0, H), not stream): - oq &= chunk - if this.tdctx.failed: - this.tdctx.failed = false - return errTypeError("Failed to decode string") - return ok(JS_NewStringLen(ctx, cstring(oq), csize_t(oq.len))) - type TextDecodeOptions = object of JSDict stream {.jsdefault.}: bool #TODO AllowSharedBufferSource proc decode(ctx: JSContext; this: JSTextDecoder; - input = none(JSArrayBufferView); options = TextDecodeOptions()): - JSResult[JSValue] {.jsfunc.} = + input = none(JSArrayBufferView); options = TextDecodeOptions()): JSValue + {.jsfunc.} = if not this.stream: this.tdctx = initTextDecoderContext(this.encoding, this.errorMode) this.bomSeen = false this.stream = options.stream if input.isSome: - return this.decode0(ctx, input.get, options.stream) - return ok(JS_NewString(ctx, "")) + let input = input.get + let H = int(input.abuf.len) - 1 + var oq = "" + let stream = this.stream + for chunk in this.tdctx.decode(input.abuf.p.toOpenArray(0, H), not stream): + oq &= chunk + if this.tdctx.failed: + this.tdctx.failed = false + return JS_ThrowTypeError(ctx, "failed to decode string") + return JS_NewStringLen(ctx, cstring(oq), csize_t(oq.len)) + return JS_NewString(ctx, "") func jencoding(this: JSTextDecoder): string {.jsfget: "encoding".} = return $this.encoding diff --git a/src/loader/loader.nim b/src/loader/loader.nim index dfc95b8b..c9025153 100644 --- a/src/loader/loader.nim +++ b/src/loader/loader.nim @@ -1167,6 +1167,8 @@ proc doRequest*(loader: FileLoader; request: Request): Response = r.sread(response.headers) # packet 3 # Only a stream of the response body may arrive after this point. response.body = stream + response.resumeFun = proc(outputId: int) = + loader.resume(outputId) else: var msg: string r.sread(msg) # packet 1 diff --git a/src/loader/response.nim b/src/loader/response.nim index 8143ccbf..6463080a 100644 --- a/src/loader/response.nim +++ b/src/loader/response.nim @@ -89,13 +89,12 @@ proc close*(response: Response) {.jsfunc.} = response.body = nil func getCharset*(this: Response; fallback: Charset): Charset = - if "Content-Type" notin this.headers.table: - return fallback - let header = this.headers.table["Content-Type"][0].toLowerAscii() - let cs = header.getContentTypeAttr("charset").getCharset() - if cs == CHARSET_UNKNOWN: - return fallback - return cs + this.headers.table.withValue("Content-Type", p): + let header = p[][0].toLowerAscii() + let cs = header.getContentTypeAttr("charset").getCharset() + if cs != CHARSET_UNKNOWN: + return cs + return fallback func getContentType*(this: Response; fallback = "application/octet-stream"): string = diff --git a/src/local/pager.nim b/src/local/pager.nim index a0c2d221..43f0d8c4 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -234,10 +234,9 @@ proc reflect(ctx: JSContext; this_val: JSValue; argc: cint; let fun = func_data[1] return JS_Call(ctx, fun, obj, argc, argv) -proc getter(ctx: JSContext; pager: Pager; a: JSAtom): Option[JSValue] - {.jsgetprop.} = +proc getter(ctx: JSContext; pager: Pager; a: JSAtom): JSValue {.jsgetprop.} = if pager.container != nil: - let cval = toJS(ctx, pager.container) + let cval = ctx.toJS(pager.container) let val = JS_GetProperty(ctx, cval, a) if JS_IsFunction(ctx, val): let func_data = @[cval, val] @@ -245,11 +244,11 @@ proc getter(ctx: JSContext; pager: Pager; a: JSAtom): Option[JSValue] func_data.toJSValueArray()) JS_FreeValue(ctx, cval) JS_FreeValue(ctx, val) - return some(fun) + return fun JS_FreeValue(ctx, cval) if not JS_IsUndefined(val): - return some(val) - return none(JSValue) + return val + return JS_NULL proc searchNext(pager: Pager; n = 1) {.jsfunc.} = if pager.regex.isSome: diff --git a/src/types/blob.nim b/src/types/blob.nim index acffedf8..c0278bde 100644 --- a/src/types/blob.nim +++ b/src/types/blob.nim @@ -8,12 +8,13 @@ import monoucha/jstypes import utils/mimeguess type - DeallocFun = proc(blob: Blob) {.closure, raises: [].} + DeallocFun = proc(opaque, p: pointer) {.nimcall, raises: [].} Blob* = ref object of RootObj size* {.jsget.}: uint64 ctype* {.jsget: "type".}: string buffer*: pointer + opaque*: pointer deallocFun*: DeallocFun fd*: Option[FileHandle] @@ -25,19 +26,24 @@ jsDestructor(Blob) jsDestructor(WebFile) proc newBlob*(buffer: pointer; size: int; ctype: string; - deallocFun: DeallocFun): Blob = + deallocFun: DeallocFun; opaque: pointer = nil): Blob = return Blob( buffer: buffer, size: uint64(size), ctype: ctype, - deallocFun: deallocFun + deallocFun: deallocFun, + opaque: opaque ) +proc deallocBlob*(opaque, p: pointer) = + if p != nil: + dealloc(p) + proc finalize(blob: Blob) {.jsfin.} = if blob.fd.isSome: discard close(blob.fd.get) - if blob.deallocFun != nil and blob.buffer != nil: - blob.deallocFun(blob) + if blob.deallocFun != nil: + blob.deallocFun(blob.opaque, blob.buffer) blob.buffer = nil proc finalize(file: WebFile) {.jsfin.} = @@ -58,11 +64,6 @@ type FilePropertyBag = object of BlobPropertyBag #TODO lastModified: int64 -proc deallocBlob*(blob: Blob) = - if blob.buffer != nil: - dealloc(blob.buffer) - blob.buffer = nil - proc newWebFile(ctx: JSContext; fileBits: seq[string]; fileName: string; options = FilePropertyBag()): WebFile {.jsctor.} = let file = WebFile( diff --git a/src/version.nim b/src/version.nim index fe9d331a..fc8d4947 100644 --- a/src/version.nim +++ b/src/version.nim @@ -29,4 +29,4 @@ tryImport monoucha/version, "monoucha" static: checkVersion("chagashi", 0, 5, 4) checkVersion("chame", 1, 0, 1) - checkVersion("monoucha", 0, 3, 1) + checkVersion("monoucha", 0, 4, 0) diff --git a/test/asserts.js b/test/asserts.js new file mode 100644 index 00000000..6ef3033a --- /dev/null +++ b/test/asserts.js @@ -0,0 +1,23 @@ +function assert(x, msg) { + const mymsg = msg ? ": " + msg : ""; + if (!x) + throw new TypeError("Assertion failed" + mymsg); +} + +function assert_throws(expr, error) { + try { + eval(expr); + } catch (e) { + if (e instanceof Error) + return; + } + throw new TypeError("Assertion failed"); +} + +function assert_equals(a, b) { + assert(a === b, "Expected " + b + " but got " + a); +} + +function assert_instanceof(a, b) { + assert(a instanceof b, a + " not an instance of " + b); +} diff --git a/test/js/asserts.js b/test/js/asserts.js index e84f2d71..8471bc87 100644..120000 --- a/test/js/asserts.js +++ b/test/js/asserts.js @@ -1,19 +1 @@ -function assert(x, msg) { - const mymsg = msg ? ": " + msg : ""; - if (!x) - throw new TypeError("Assertion failed" + mymsg); -} - -function assert_throws(expr, error) { - try { - eval(expr); - } catch (e) { - if (e instanceof Error) - return; - } - throw new TypeError("Assertion failed"); -} - -function assert_equals(a, b) { - assert(a === b, "Expected " + b + " but got " + a); -} +../asserts.js \ No newline at end of file diff --git a/test/js/console.html b/test/js/console.html deleted file mode 100644 index 67b244a7..00000000 --- a/test/js/console.html +++ /dev/null @@ -1,5 +0,0 @@ -<!doctype html> -<div>Success</div> -<script> -console.log("Hello, world!") -</script> diff --git a/test/js/run_js_tests.sh b/test/js/run_js_tests.sh index ac531a67..3f484449 100755 --- a/test/js/run_js_tests.sh +++ b/test/js/run_js_tests.sh @@ -1,11 +1,11 @@ #!/bin/sh -if ! test "$CHA_TEST_BIN" -then test -f ../../cha && CHA_TEST_BIN=../../cha || CHA_TEST_BIN=cha +if ! test "$CHA" +then test -f ../../cha && CHA=../../cha || CHA=cha fi failed=0 for h in *.html do printf '%s\r' "$h" - if ! "$CHA_TEST_BIN" -C config.toml "$h" | diff all.expected - + if ! "$CHA" -dC config.toml "$h" | diff all.expected - then failed=$(($failed+1)) printf 'FAIL: %s\n' "$h" fi diff --git a/test/layout/run_layout_tests.sh b/test/layout/run_layout_tests.sh index 20b85ce1..429d2ba1 100755 --- a/test/layout/run_layout_tests.sh +++ b/test/layout/run_layout_tests.sh @@ -1,6 +1,6 @@ #!/bin/sh -if test -z "$CHA_TEST_BIN" -then test -f ../../cha && CHA_TEST_BIN=../../cha || CHA_TEST_BIN=cha +if test -z "$CHA" +then test -f ../../cha && CHA=../../cha || CHA=cha fi failed=0 for h in *.html @@ -8,12 +8,12 @@ do printf '%s\r' "$h" expected="$(basename "$h" .html).expected" color_expected="$(basename "$h" .html).color.expected" if test -f "$expected" - then if ! "$CHA_TEST_BIN" -C config.toml "$h" | diff "$expected" - + then if ! "$CHA" -C config.toml "$h" | diff "$expected" - then failed=$(($failed+1)) printf 'FAIL: %s\n' "$h" fi elif test -f "$color_expected" - then if ! "$CHA_TEST_BIN" -C config.color.toml "$h" | diff "$color_expected" - + then if ! "$CHA" -C config.color.toml "$h" | diff "$color_expected" - then failed=$(($failed+1)) printf 'FAIL: %s\n' "$h" fi diff --git a/test/net/all.expected b/test/net/all.expected new file mode 100644 index 00000000..35821117 --- /dev/null +++ b/test/net/all.expected @@ -0,0 +1 @@ +Success diff --git a/test/net/asserts.js b/test/net/asserts.js new file mode 120000 index 00000000..8471bc87 --- /dev/null +++ b/test/net/asserts.js @@ -0,0 +1 @@ +../asserts.js \ No newline at end of file diff --git a/test/net/config.toml b/test/net/config.toml new file mode 100644 index 00000000..ce723641 --- /dev/null +++ b/test/net/config.toml @@ -0,0 +1,12 @@ +[buffer] +scripting = true + +[display] +columns = 80 +lines = 24 +pixels-per-column = 9 +pixels-per-line = 18 +force-columns = true +force-lines = true +force-pixels-per-column = true +force-pixels-per-line = true diff --git a/test/net/ping b/test/net/ping new file mode 100644 index 00000000..8e554694 --- /dev/null +++ b/test/net/ping @@ -0,0 +1 @@ +pong diff --git a/test/net/run.nim b/test/net/run.nim new file mode 100644 index 00000000..d27bbef3 --- /dev/null +++ b/test/net/run.nim @@ -0,0 +1,47 @@ +import std/asyncdispatch +import std/asynchttpserver +import std/os +import std/posix + +import utils/twtstr + +proc cb(req: Request) {.async.} = + const headers = {"Content-type": "text/html; charset=utf-8"} + if req.url.path == "/stop": + await req.respond(Http200, "", headers.newHttpHeaders()) + quit(0) + let s = readFile(req.url.path.after('/')) + #echo (req.reqMethod, req.url.path, req.headers) + await req.respond(Http200, s, headers.newHttpHeaders()) + +proc runServer(server: AsyncHttpServer) {.async.} = + while true: + if server.shouldAcceptRequest(): + await server.acceptRequest(cb) + else: + # too many concurrent connections, `maxFDs` exceeded + # wait 500ms for FDs to be closed + await sleepAsync(500) + +proc main() {.async.} = + var server = newAsyncHttpServer() + if paramCount() >= 1 and paramStr(1) == "-x": + server.listen(Port(8000)) + await server.runServer() + quit(0) + server.listen(Port(0)) + let port = server.getPort() + case fork() + of 0: + let cmd = getAppFileName().beforeLast('/') & "/run.sh " & $uint16(port) + discard execl("/bin/sh", "sh", "-c", cstring(cmd), nil) + quit(1) + of -1: + stderr.write("Failed to start run.sh") + quit(1) + else: + await server.runServer() + var x: cint + quit(WEXITSTATUS(wait(addr x))) + +waitFor main() diff --git a/test/net/run.sh b/test/net/run.sh new file mode 100755 index 00000000..1ea90e3a --- /dev/null +++ b/test/net/run.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +if ! test "$CHA" +then test -f ../../cha && CHA=../../cha || CHA=cha +fi + +failed=0 +for h in *.html +do printf '%s\r' "$h" + if ! "$CHA" -dC config.toml "http://localhost:$1/$h" | diff all.expected - + then failed=$(($failed+1)) + printf 'FAIL: %s\n' "$h" + fi +done +printf '\n' +$CHA -d "http://localhost:$1/stop" >/dev/null +exit "$failed" diff --git a/test/js/xhr.html b/test/net/xhr.html index 4bd66b95..2d034772 100644 --- a/test/js/xhr.html +++ b/test/net/xhr.html @@ -6,12 +6,15 @@ const x = new XMLHttpRequest(); assert(x.onreadystatechange === null); function myFunction() { - console.log("change"); + ; } x.onreadystatechange = myFunction; assert(myFunction === x.onreadystatechange); assert(x.readyState === XMLHttpRequest.UNSENT); -x.open("GET", ""); -document.getElementById("x").textContent = "Success"; +x.open("GET", "ping", false); +assert_throws("x.responseType = 'document'"); x.send(); +assert_equals(x.readyState, XMLHttpRequest.DONE); +assert_equals(x.responseText.trim(), "pong"); +document.getElementById("x").textContent = "Success"; </script> |