diff options
author | bptato <nincsnevem662@gmail.com> | 2024-03-14 20:57:45 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-03-14 21:05:16 +0100 |
commit | d26766c4c4015990703e84e8136f96d222edbc97 (patch) | |
tree | 7f412f8ca98d2b04323da5cf2fd607efbd6c408d /src/html | |
parent | a8f05f18fdd64485c26b453e62e8073b50e271ef (diff) | |
download | chawan-d26766c4c4015990703e84e8136f96d222edbc97.tar.gz |
Move around some modules
* extern -> gone, runproc absorbed by pager, others moved into io/ * display -> local/ (where else would we display?) * xhr -> html/ * move out WindowAttributes from term, so we don't depend on local from server
Diffstat (limited to 'src/html')
-rw-r--r-- | src/html/dom.nim | 2 | ||||
-rw-r--r-- | src/html/env.nim | 6 | ||||
-rw-r--r-- | src/html/formdata.nim | 177 | ||||
-rw-r--r-- | src/html/xmlhttprequest.nim | 118 |
4 files changed, 299 insertions, 4 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index 2f36d81a..163e4a66 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -10,7 +10,6 @@ import css/cssparser import css/mediaquery import css/sheet import css/values -import display/term import html/catom import html/enums import html/event @@ -37,6 +36,7 @@ import types/matrix import types/referrer import types/url import types/vector +import types/winattrs import utils/mimeguess import utils/strwidth import utils/twtstr diff --git a/src/html/env.nim b/src/html/env.nim index 29104707..3ffff116 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -2,12 +2,13 @@ import std/selectors import std/streams import bindings/quickjs -import display/term import html/catom import html/chadombuilder import html/dom import html/event +import html/formdata import html/script +import html/xmlhttprequest import io/promise import js/base64 import js/console @@ -24,8 +25,7 @@ import loader/request import loader/response import types/blob import types/url -import xhr/formdata -import xhr/xmlhttprequest +import types/winattrs # NavigatorID proc appCodeName(navigator: ptr Navigator): string {.jsfget.} = "Mozilla" diff --git a/src/html/formdata.nim b/src/html/formdata.nim new file mode 100644 index 00000000..bbf9a843 --- /dev/null +++ b/src/html/formdata.nim @@ -0,0 +1,177 @@ +import std/base64 +import std/streams + +import html/catom +import html/dom +import html/enums +import js/domexception +import js/javascript +import js/tojs +import types/blob +import types/formdata +import utils/twtstr + +import chame/tags + +proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, + encoding = "UTF-8"): seq[FormDataEntry] + +proc generateBoundary(): string = + let urandom = newFileStream("/dev/urandom") + let s = urandom.readStr(32) + urandom.close() + # 32 * 4 / 3 (padded) = 44 + prefix string is 22 bytes = 66 bytes + return "----WebKitFormBoundary" & base64.encode(s) + +proc newFormData0*(): FormData = + return FormData(boundary: generateBoundary()) + +proc newFormData*(form: HTMLFormElement = nil, + submitter: HTMLElement = nil): DOMResult[FormData] {.jsctor.} = + let this = newFormData0() + if form != nil: + if submitter != nil: + if not submitter.isSubmitButton(): + return errDOMException("Submitter must be a submit button", + "InvalidStateError") + if FormAssociatedElement(submitter).form != form: + return errDOMException("Submitter's form owner is not form", + "InvalidStateError") + if not form.constructingEntryList: + this.entries = constructEntryList(form, submitter) + return ok(this) + +#TODO filename should not be allowed for string entries +# in other words, this should be an overloaded function, not just an or type +proc append*[T: string|Blob](this: FormData, name: string, value: T, + filename = opt(string)) {.jsfunc.} = + when T is Blob: + let filename = if filename.isSome: + filename.get + elif value of WebFile: + WebFile(value).name + else: + "blob" + this.entries.add(FormDataEntry( + name: name, + isstr: false, + value: value, + filename: filename + )) + else: # string + this.entries.add(FormDataEntry( + name: name, + isstr: true, + svalue: value + )) + +proc delete(this: FormData, name: string) {.jsfunc.} = + for i in countdown(this.entries.high, 0): + if this.entries[i].name == name: + this.entries.delete(i) + +proc get(ctx: JSContext, this: FormData, name: string): JSValue {.jsfunc.} = + for entry in this.entries: + if entry.name == name: + if entry.isstr: + return toJS(ctx, entry.svalue) + else: + return toJS(ctx, entry.value) + return JS_NULL + +proc getAll(ctx: JSContext, this: FormData, name: string): seq[JSValue] + {.jsfunc.} = + for entry in this.entries: + if entry.name == name: + if entry.isstr: + result.add(toJS(ctx, entry.svalue)) + else: + result.add(toJS(ctx, entry.value)) + +proc add(list: var seq[FormDataEntry], entry: tuple[name, value: string]) = + list.add(FormDataEntry( + name: entry.name, + isstr: true, + svalue: entry.value + )) + +func toNameValuePairs*(list: seq[FormDataEntry]): + seq[tuple[name, value: string]] = + for entry in list: + if entry.isstr: + result.add((entry.name, entry.svalue)) + else: + result.add((entry.name, entry.name)) + +# https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set +# Warning: we skip the first "constructing entry list" check; the caller must +# do it. +proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, + encoding = "UTF-8"): seq[FormDataEntry] = + assert not form.constructingEntryList + form.constructingEntryList = true + var entrylist: seq[FormDataEntry] = @[] + for field in form.controls: + if field.findAncestor({TAG_DATALIST}) != nil or + field.attrb(atDisabled) or + field.isButton() and Element(field) != submitter: + continue + if field of HTMLInputElement: + let field = HTMLInputElement(field) + if field.inputType in {INPUT_CHECKBOX, INPUT_RADIO} and not field.checked: + continue + if field.inputType == INPUT_IMAGE: + var name = field.attr(atName) + if name != "": + name &= '.' + entrylist.add((name & 'x', $field.xcoord)) + entrylist.add((name & 'y', $field.ycoord)) + continue + #TODO custom elements + let name = field.attr(atName) + if name == "": + continue + if field of HTMLSelectElement: + let field = HTMLSelectElement(field) + for option in field.options: + if option.selected and not option.isDisabled: + entrylist.add((name, option.value)) + elif field of HTMLInputElement: + let field = HTMLInputElement(field) + case field.inputType + of INPUT_CHECKBOX, INPUT_RADIO: + let v = field.attr(atValue) + let value = if v != "": + v + else: + "on" + entrylist.add((name, value)) + of INPUT_FILE: + #TODO file + discard + of INPUT_HIDDEN: + if name.equalsIgnoreCase("_charset_"): + entrylist.add((name, encoding)) + else: + entrylist.add((name, field.value)) + else: + entrylist.add((name, field.value)) + elif field of HTMLButtonElement: + entrylist.add((name, HTMLButtonElement(field).value)) + elif field of HTMLTextAreaElement: + entrylist.add((name, HTMLTextAreaElement(field).value)) + else: + assert false, "Tag type " & $field.tagType & + " not accounted for in constructEntryList" + if field of HTMLTextAreaElement or + field of HTMLInputElement and + HTMLInputElement(field).inputType in AutoDirInput: + let dirname = field.attr(atDirname) + if dirname != "": + let dir = "ltr" #TODO bidi + entrylist.add((dirname, dir)) + form.constructingEntryList = false + return entrylist + +proc addFormDataModule*(ctx: JSContext) = + ctx.registerType(FormData) diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim new file mode 100644 index 00000000..e9905760 --- /dev/null +++ b/src/html/xmlhttprequest.nim @@ -0,0 +1,118 @@ +import std/options +import std/strutils + +import bindings/quickjs +import html/dom +import html/event +import js/domexception +import js/fromjs +import js/javascript +import loader/headers +import loader/request +import loader/response +import types/url + +type + XMLHttpRequestResponseType = enum + TYPE_UNKNOWN = "" + TYPE_ARRAYBUFFER = "arraybuffer" + TYPE_BLOB = "blob" + TYPE_DOCUMENT = "document" + TYPE_JSON = "json" + TYPE_TEXT = "text" + + XMLHttpRequestState = enum + UNSENT = 0u16 + OPENED = 1u16 + HEADERS_RECEIVED = 2u16 + LOADING = 3u16 + DONE = 4u16 + + XMLHttpRequestFlag = enum + SEND_FLAG, UPLOAD_LISTENER_FLAG, SYNC_FLAG + + XMLHttpRequestEventTarget = ref object of EventTarget + onloadstart {.jsgetset.}: EventHandler + onprogress {.jsgetset.}: EventHandler + onabort {.jsgetset.}: EventHandler + onerror {.jsgetset.}: EventHandler + onload {.jsgetset.}: EventHandler + ontimeout {.jsgetset.}: EventHandler + onloadend {.jsgetset.}: EventHandler + + XMLHttpRequestUpload = ref object of XMLHttpRequestEventTarget + + XMLHttpRequest = ref object of XMLHttpRequestEventTarget + onreadystatechange {.jsgetset.}: EventHandler + readyState: XMLHttpRequestState + upload {.jsget.}: XMLHttpRequestUpload + flags: set[XMLHttpRequestFlag] + requestMethod: HttpMethod + requestURL: URL + authorRequestHeaders: Headers + response: Response + responseType {.jsgetset.}: XMLHttpRequestResponseType + +jsDestructor(XMLHttpRequestEventTarget) +jsDestructor(XMLHttpRequestUpload) +jsDestructor(XMLHttpRequest) + +func newXMLHttpRequest(): XMLHttpRequest {.jsctor.} = + let upload = XMLHttpRequestUpload() + return XMLHttpRequest( + upload: upload, + authorRequestHeaders: newHeaders() + ) + +func readyState(this: XMLHttpRequest): uint16 {.jsfget.} = + return uint16(this.readyState) + +proc parseMethod(s: string): DOMResult[HttpMethod] = + return case s.toLowerAscii() + of "get": ok(HTTP_GET) + of "delete": ok(HTTP_DELETE) + of "head": ok(HTTP_HEAD) + of "options": ok(HTTP_OPTIONS) + of "patch": ok(HTTP_PATCH) + of "post": ok(HTTP_POST) + of "put": ok(HTTP_PUT) + of "connect", "trace", "track": + errDOMException("Forbidden method", "SecurityError") + else: + errDOMException("Invalid method", "SyntaxError") + +proc open(ctx: JSContext, this: XMLHttpRequest, httpMethod, url: string): + Err[DOMException] {.jsfunc.} = + let httpMethod = ?parseMethod(httpMethod) + let global = JS_GetGlobalObject(ctx) + let window = fromJS[Window](ctx, global) + JS_FreeValue(ctx, global) + let x = if window.isSome: + parseURL(url, some(window.get.document.baseURL)) + else: + parseURL(url) + if x.isNone: + return errDOMException("Invalid URL", "SyntaxError") + let parsedURL = x.get + #TODO async, username, password arguments + let async = true + #TODO if async is false... probably just throw. + #TODO terminate fetch controller + this.flags.excl(SEND_FLAG) + this.flags.excl(UPLOAD_LISTENER_FLAG) + if async: + this.flags.excl(SYNC_FLAG) + else: + this.flags.incl(SYNC_FLAG) + this.requestMethod = httpMethod + this.authorRequestHeaders = newHeaders() + this.response = makeNetworkError() + this.requestURL = parsedURL + return ok() + +proc addXMLHttpRequestModule*(ctx: JSContext) = + let eventTargetCID = ctx.getClass("EventTarget") + let xhretCID = ctx.registerType(XMLHttpRequestEventTarget, eventTargetCID) + ctx.registerType(XMLHttpRequestUpload, xhretCID) + let xhrCID = ctx.registerType(XMLHttpRequest, xhretCID) + ctx.defineConsts(xhrCID, XMLHttpRequestState, uint16) |