diff options
author | bptato <nincsnevem662@gmail.com> | 2023-06-19 20:16:39 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-06-19 20:17:06 +0200 |
commit | 82fb1f70ab275884c42dd769b2af8f9df5389e88 (patch) | |
tree | b7a83ca2e2d22e926959f2525169f2a2e4530e38 | |
parent | 070cfca46f60c3a00fe6dd66457f454a1a217897 (diff) | |
download | chawan-82fb1f70ab275884c42dd769b2af8f9df5389e88.tar.gz |
Get rid of the .jserr pragma
-rw-r--r-- | src/buffer/buffer.nim | 20 | ||||
-rw-r--r-- | src/display/client.nim | 2 | ||||
-rw-r--r-- | src/display/pager.nim | 2 | ||||
-rw-r--r-- | src/html/dom.nim | 314 | ||||
-rw-r--r-- | src/html/htmlparser.nim | 12 | ||||
-rw-r--r-- | src/img/path.nim | 29 | ||||
-rw-r--r-- | src/io/request.nim | 18 | ||||
-rw-r--r-- | src/ips/serialize.nim | 6 | ||||
-rw-r--r-- | src/js/javascript.nim | 81 | ||||
-rw-r--r-- | src/types/url.nim | 21 | ||||
-rw-r--r-- | src/utils/opt.nim | 5 | ||||
-rw-r--r-- | src/xhr/formdata.nim | 15 |
12 files changed, 248 insertions, 277 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index e0b24cae..74f0545e 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -793,17 +793,15 @@ proc cancel*(buffer: Buffer): int {.proxy.} = #https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm proc serializeMultipartFormData(entries: seq[FormDataEntry]): FormData = - {.cast(noSideEffect).}: - # This is correct, because newFormData with no params has no side effects. - let formData = newFormData() - for entry in entries: - let name = makeCRLF(entry.name) - if entry.isstr: - let value = makeCRLF(entry.svalue) - formData.append(name, value) - else: - formData.append(name, entry.value, entry.filename) - return formData + let formData = newFormData0() + for entry in entries: + let name = makeCRLF(entry.name) + if entry.isstr: + let value = makeCRLF(entry.svalue) + formData.append(name, value) + else: + formData.append(name, entry.value, entry.filename) + return formData proc serializePlainTextFormData(kvs: seq[(string, string)]): string = for it in kvs: diff --git a/src/display/client.nim b/src/display/client.nim index 8196df26..af4b3fb2 100644 --- a/src/display/client.nim +++ b/src/display/client.nim @@ -434,7 +434,7 @@ proc newConsole(pager: Pager, tty: File): Console = raise newException(Defect, "Failed to open console pipe.") let url = newURL("javascript:console.show()") result.container = pager.readPipe0(some("text/plain"), none(Charset), - pipefd[0], option(url), "Browser console") + pipefd[0], option(url.get(nil)), "Browser console") var f: File if not open(f, pipefd[1], fmWrite): raise newException(Defect, "Failed to open file for console pipe.") diff --git a/src/display/pager.nim b/src/display/pager.nim index bed2b4b5..857b93dd 100644 --- a/src/display/pager.nim +++ b/src/display/pager.nim @@ -674,7 +674,7 @@ proc readPipe0*(pager: Pager, ctype: Option[string], cs: Option[Charset], fd: fd, contenttype: some(ctype.get("text/plain")), charset: cs, - location: location.get(newURL("file://-")) + location: location.get(newURL("file://-").get) ) let bufferconfig = pager.config.getBufferConfig(source.location) return pager.dispatcher.newBuffer(bufferconfig, source, title = title) diff --git a/src/html/dom.nim b/src/html/dom.nim index 20b10674..1fa3b043 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -21,6 +21,7 @@ import io/loader import io/promise import io/request import io/window +import js/exception import js/javascript import js/timeout import types/blob @@ -619,25 +620,21 @@ proc quadraticCurveTo(ctx: CanvasRenderingContext2D, cpx, cpy, x, y: float64) {.jsfunc.} = ctx.state.path.quadraticCurveTo(cpx, cpy, x, y) -proc arcTo(ctx: CanvasRenderingContext2D, x1, y1, x2, y2, radius: float64) - {.jsfunc.} = - if not ctx.state.path.arcTo(x1, y1, x2, y2, radius): - #TODO should be DOMException - JS_ERR JS_TypeError, "IndexSizeError" +proc arcTo(ctx: CanvasRenderingContext2D, x1, y1, x2, y2, radius: float64): + Err[DOMException] {.jsfunc.} = + return ctx.state.path.arcTo(x1, y1, x2, y2, radius) proc arc(ctx: CanvasRenderingContext2D, x, y, radius, startAngle, - endAngle: float64, counterclockwise = false) {.jsfunc.} = - if not ctx.state.path.arc(x, y, radius, startAngle, endAngle, - counterclockwise): - #TODO should be DOMException - JS_ERR JS_TypeError, "IndexSizeError" + endAngle: float64, counterclockwise = false): Err[DOMException] + {.jsfunc.} = + return ctx.state.path.arc(x, y, radius, startAngle, endAngle, + counterclockwise) proc ellipse(ctx: CanvasRenderingContext2D, x, y, radiusX, radiusY, rotation, - startAngle, endAngle: float64, counterclockwise = false) {.jsfunc.} = - if not ctx.state.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, - endAngle, counterclockwise): - #TODO should be DOMException - JS_ERR JS_TypeError, "IndexSizeError" + startAngle, endAngle: float64, counterclockwise = false): Err[DOMException] + {.jsfunc.} = + return ctx.state.path.ellipse(x, y, radiusX, radiusY, rotation, startAngle, + endAngle, counterclockwise) proc rect(ctx: CanvasRenderingContext2D, x, y, w, h: float64) {.jsfunc.} = ctx.state.path.rect(x, y, w, h) @@ -981,76 +978,71 @@ proc update(tokenList: DOMTokenList) = return tokenList.element.attr(tokenList.localName, tokenList.toks.join(' ')) -proc add(tokenList: DOMTokenList, tokens: varargs[string]) {.jserr, jsfunc.} = +func validateDOMToken(tok: string): Err[DOMException] = + if tok == "": + return err(newDOMException("Got an empty string", "SyntaxError")) + if AsciiWhitespace in tok: + return err(newDOMException("Got a string containing whitespace", + "InvalidCharacterError")) + +proc add(tokenList: DOMTokenList, tokens: varargs[string]): Err[DOMException] + {.jsfunc.} = for tok in tokens: - if tok == "": - #TODO should be DOMException - JS_ERR JS_TypeError, "SyntaxError" - if AsciiWhitespace in tok: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" + ?validateDOMToken(tok) for tok in tokens: tokenList.toks.add(tok) tokenList.update() + return ok() -proc remove(tokenList: DOMTokenList, tokens: varargs[string]) {.jserr, jsfunc.} = +proc remove(tokenList: DOMTokenList, tokens: varargs[string]): + Err[DOMException] {.jsfunc.} = for tok in tokens: - if tok == "": - #TODO should be DOMException - JS_ERR JS_TypeError, "SyntaxError" - if AsciiWhitespace in tok: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" + ?validateDOMToken(tok) for tok in tokens: let i = tokenList.toks.find(tok) if i != -1: tokenList.toks.delete(i) tokenList.update() + return ok() -proc toggle(tokenList: DOMTokenList, token: string, force = none(bool)): bool {.jserr, jsfunc.} = - if token == "": - #TODO should be DOMException - JS_ERR JS_TypeError, "SyntaxError" - if AsciiWhitespace in token: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" +proc toggle(tokenList: DOMTokenList, token: string, force = none(bool)): + Result[bool, DOMException] {.jsfunc.} = + ?validateDOMToken(token) let i = tokenList.toks.find(token) if i != -1: if not force.get(false): tokenList.toks.delete(i) tokenList.update() - return false - return true + return ok(false) + return ok(true) if force.get(true): tokenList.toks.add(token) tokenList.update() - return true - return false + return ok(true) + return ok(false) -proc replace(tokenList: DOMTokenList, token, newToken: string): bool {.jserr, jsfunc.} = - if token == "" or newToken == "": - #TODO should be DOMException - JS_ERR JS_TypeError, "SyntaxError" - if AsciiWhitespace in token or AsciiWhitespace in newToken: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" +proc replace(tokenList: DOMTokenList, token, newToken: string): + Result[bool, DOMException] {.jsfunc.} = + ?validateDOMToken(token) + ?validateDOMToken(newToken) let i = tokenList.toks.find(token) if i == -1: - return false + return ok(false) tokenList.toks[i] = newToken tokenList.update() - return true + return ok(true) const SupportedTokensMap = { "abcd": @["adsf"] #TODO }.toTable() -func supports(tokenList: DOMTokenList, token: string): bool {.jserr, jsfunc.} = +func supports(tokenList: DOMTokenList, token: string): + Result[bool, JSError] {.jsfunc.} = if tokenList.localName in SupportedTokensMap: let lowercase = token.toLowerAscii() - return lowercase in SupportedTokensMap[tokenList.localName] - else: - JS_ERR JS_TypeError, "No supported tokens defined for attribute " & tokenList.localName + return ok(lowercase in SupportedTokensMap[tokenList.localName]) + return err(newTypeError("No supported tokens defined for attribute " & + tokenList.localName)) func `$`(tokenList: DOMTokenList): string {.jsfunc.} = return tokenList.toks.join(' ') @@ -1796,7 +1788,7 @@ func newHTMLElement*(document: Document, localName: string, func newDocument*(): Document {.jsctor.} = result = Document( nodeType: DOCUMENT_NODE, - url: newURL("about:blank"), + url: newURL("about:blank").get, index: -1 ) result.document = result @@ -1842,7 +1834,7 @@ func baseURL*(document: Document): Url = if href == "": return document.url if document.url == nil: - return newURL("about:blank") #TODO ??? + return newURL("about:blank").get #TODO ??? let url = parseURL(href, some(document.url)) if url.isNone: return document.url @@ -1960,20 +1952,30 @@ proc attrulgz(element: Element, name: string, value: uint32) = if value > 0: element.attrul(name, value) -proc setAttribute(element: Element, qualifiedName, value: string) {.jserr, jsfunc.} = - if not qualifiedName.matchNameProduction(): - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" +func validateAttributeName(name: string, isq: static bool = false): + Err[DOMException] = + when isq: + if name.matchNameProduction(): + return ok() + else: + if name.matchQNameProduction(): + return ok() + return err(newDOMException("Invalid character in attribute name", + "InvalidCharacterError")) + +proc setAttribute(element: Element, qualifiedName, value: string): + Err[DOMException] {.jsfunc.} = + ?validateAttributeName(qualifiedName) let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml: qualifiedName.toLowerAscii2() else: qualifiedName element.attr(qualifiedName, value) + return ok() -proc setAttributeNS(element: Element, namespace, qualifiedName, value: string) {.jserr, jsfunc.} = - if not qualifiedName.matchQNameProduction(): - #TODO this should be a DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" +proc setAttributeNS(element: Element, namespace, qualifiedName, + value: string): Err[DOMException] {.jsfunc.} = + ?validateAttributeName(qualifiedName, isq = true) let ps = qualifiedName.until(':') let prefix = if ps.len < qualifiedName.len: ps else: "" let localName = qualifiedName.substr(prefix.len) @@ -1981,14 +1983,14 @@ proc setAttributeNS(element: Element, namespace, qualifiedName, value: string) { prefix == "xml" and namespace != $Namespace.XML or (qualifiedName == "xmlns" or prefix == "xmlns") and namespace != $Namespace.XMLNS or namespace == $Namespace.XMLNS and qualifiedName != "xmlns" and prefix != "xmlns": - #TODO this should be a DOMException - JS_ERR JS_TypeError, "NamespaceError" + return err(newDOMException("Unexpected namespace", "NamespaceError")) element.attr0(qualifiedName, value) let i = element.attributes.findAttrNS(namespace, localName) if i != -1: element.attributes.attrlist[i].value = value else: element.attributes.attrlist.add(element.newAttr(localName, value, prefix, namespace)) + return ok() proc removeAttribute(element: Element, qualifiedName: string) {.jsfunc.} = let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml: @@ -2002,10 +2004,9 @@ proc removeAttributeNS(element: Element, namespace, localName: string) {.jsfunc. if i != -1: element.delAttr(i) -proc toggleAttribute(element: Element, qualifiedName: string, force = none(bool)): bool {.jserr, jsfunc.} = - if not qualifiedName.matchNameProduction(): - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" +proc toggleAttribute(element: Element, qualifiedName: string, + force = none(bool)): Result[bool, DOMException] {.jsfunc.} = + ?validateAttributeName(qualifiedName) let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml: qualifiedName.toLowerAscii2() else: @@ -2013,51 +2014,55 @@ proc toggleAttribute(element: Element, qualifiedName: string, force = none(bool) if not element.attrb(qualifiedName): if force.get(true): element.attr(qualifiedName, "") - return true - return false + return ok(true) + return ok(false) if not force.get(false): element.delAttr(qualifiedName) - return false - return true + return ok(false) + return ok(true) proc value(attr: Attr, s: string) {.jsfset.} = attr.value = s if attr.ownerElement != nil: attr.ownerElement.attr0(attr.name, s) -proc setNamedItem(map: NamedNodeMap, attr: Attr): Option[Attr] {.jserr, jsfunc.} = +proc setNamedItem(map: NamedNodeMap, attr: Attr): Result[Attr, DOMException] + {.jsfunc.} = if attr.ownerElement != nil and attr.ownerElement != map.element: - #TODO should be DOMException - JS_ERR JS_TypeError, "InUseAttributeError" + return err(newDOMException("Attribute is currently in use", + "InUseAttributeError")) if attr.name in map.element.attrs: - return some(attr) + return ok(attr) let i = map.findAttr(attr.name) if i != -1: - result = some(map.attrlist[i]) + result = ok(map.attrlist[i]) map.attrlist.delete(i) + else: + result = ok(nil) map.element.attrs[attr.name] = attr.value map.attrlist.add(attr) -proc setNamedItemNS(map: NamedNodeMap, attr: Attr): Option[Attr] {.jsfunc.} = - map.setNamedItem(attr) +proc setNamedItemNS(map: NamedNodeMap, attr: Attr): Result[Attr, DOMException] + {.jsfunc.} = + return map.setNamedItem(attr) -proc removeNamedItem(map: NamedNodeMap, qualifiedName: string): Attr {.jserr, jsfunc.} = +proc removeNamedItem(map: NamedNodeMap, qualifiedName: string): + Result[Attr, DOMException] {.jsfunc.} = let i = map.findAttr(qualifiedName) if i != -1: let attr = map.attrlist[i] map.element.delAttr(i) - return attr - #TODO should be DOMException - JS_ERR JS_TypeError, "Not found" + return ok(attr) + return err(newDOMException("Item not found", "NotFoundError")) -proc removeNamedItemNS(map: NamedNodeMap, namespace, localName: string): Attr {.jserr, jsfunc.} = +proc removeNamedItemNS(map: NamedNodeMap, namespace, localName: string): + Result[Attr, DOMException] {.jsfunc.} = let i = map.findAttrNS(namespace, localName) if i != -1: let attr = map.attrlist[i] map.element.delAttr(i) - return attr - #TODO should be DOMException - JS_ERR JS_TypeError, "Not found" + return ok(attr) + return err(newDOMException("Item not found", "NotFoundError")) proc id(element: Element, id: string) {.jsfset.} = element.id = id @@ -2202,43 +2207,53 @@ proc insertionSteps(insertedNode: Node) = element.resetFormOwner() # WARNING the ordering of the arguments in the standard is whack so this doesn't match that -func preInsertionValidity*(parent, node, before: Node): bool = +func preInsertionValidity*(parent, node, before: Node): Err[DOMException] = if parent.nodeType notin {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE}: - # HierarchyRequestError - return false + return err(newDOMException("Parent must be a document, document fragment, " & + "or element", "HierarchyRequestError")) if node.isHostIncludingInclusiveAncestor(parent): - # HierarchyRequestError - return false + return err(newDOMException("Parent must be an ancestor", "HierarchyRequestError")) if before != nil and before.parentNode != parent: - # NotFoundError - return false - if node.nodeType notin {DOCUMENT_FRAGMENT_NODE, DOCUMENT_TYPE_NODE, ELEMENT_NODE} + CharacterDataNodes: - # HierarchyRequestError - return false - if (node.nodeType == TEXT_NODE and parent.nodeType == DOCUMENT_NODE) or - (node.nodeType == DOCUMENT_TYPE_NODE and parent.nodeType != DOCUMENT_NODE): - # HierarchyRequestError - return false + return err(newDOMException("Reference node is not a child of parent", + "NotFoundError")) + if node.nodeType notin {DOCUMENT_FRAGMENT_NODE, DOCUMENT_TYPE_NODE, + ELEMENT_NODE} + CharacterDataNodes: + return err(newDOMException("Cannot insert node type", + "HierarchyRequestError")) + if node.nodeType == TEXT_NODE and parent.nodeType == DOCUMENT_NODE: + return err(newDOMException("Cannot insert text into document", + "HierarchyRequestError")) + if node.nodeType == DOCUMENT_TYPE_NODE and parent.nodeType != DOCUMENT_NODE: + return err(newDOMException("Document type can only be inserted into " & + "document", "HierarchyRequestError")) if parent.nodeType == DOCUMENT_NODE: case node.nodeType of DOCUMENT_FRAGMENT_NODE: let elems = node.countChildren(ELEMENT_NODE) if elems > 1 or node.hasChild(TEXT_NODE): - # HierarchyRequestError - return false - elif elems == 1 and (parent.hasChild(ELEMENT_NODE) or before != nil and (before.nodeType == DOCUMENT_TYPE_NODE or before.hasNextSibling(DOCUMENT_TYPE_NODE))): - # HierarchyRequestError - return false + return err(newDOMException("Document fragment has invalid children", + "HierarchyRequestError")) + elif elems == 1 and (parent.hasChild(ELEMENT_NODE) or + before != nil and (before.nodeType == DOCUMENT_TYPE_NODE or + before.hasNextSibling(DOCUMENT_TYPE_NODE))): + return err(newDOMException("Document fragment has invalid children", + "HierarchyRequestError")) of ELEMENT_NODE: - if parent.hasChild(ELEMENT_NODE) or before != nil and (before.nodeType == DOCUMENT_TYPE_NODE or before.hasNextSibling(DOCUMENT_TYPE_NODE)): - # HierarchyRequestError - return false + if parent.hasChild(ELEMENT_NODE): + return err(newDOMException("Document already has an element child", + "HierarchyRequestError")) + elif before != nil and (before.nodeType == DOCUMENT_TYPE_NODE or + before.hasNextSibling(DOCUMENT_TYPE_NODE)): + return err(newDOMException("Cannot insert element before document " & + "type", "HierarchyRequestError")) of DOCUMENT_TYPE_NODE: - if parent.hasChild(DOCUMENT_TYPE_NODE) or before != nil and before.hasPreviousSibling(ELEMENT_NODE) or before == nil and parent.hasChild(ELEMENT_NODE): - # HierarchyRequestError - return false + if parent.hasChild(DOCUMENT_TYPE_NODE) or + before != nil and before.hasPreviousSibling(ELEMENT_NODE) or + before == nil and parent.hasChild(ELEMENT_NODE): + return err(newDOMException("Cannot insert document type before " & + "an element node", "HierarchyRequestError")) else: discard - return true # no exception reached + return ok() # no exception reached proc insertNode(parent, node, before: Node) = parent.document.adopt(node) @@ -2282,18 +2297,17 @@ proc insert*(parent, node, before: Node) = for node in nodes: insertNode(parent, node, before) -proc insertBefore(parent, node, before: Node): Node {.jserr, jsfunc.} = - if parent.preInsertionValidity(node, before): - let referenceChild = if before == node: - node.nextSibling - else: - before - parent.insert(node, referenceChild) - return node - #TODO use preInsertionValidity result - JS_ERR JS_TypeError, "Pre-insertion validity violated" +proc insertBefore(parent, node, before: Node): Result[Node, DOMException] + {.jsfunc.} = + ?parent.preInsertionValidity(node, before) + let referenceChild = if before == node: + node.nextSibling + else: + before + parent.insert(node, referenceChild) + return ok(node) -proc appendChild(parent, node: Node): Node {.jsfunc.} = +proc appendChild(parent, node: Node): Result[Node, DOMException] {.jsfunc.} = return parent.insertBefore(node, nil) proc append*(parent, node: Node) = @@ -2301,11 +2315,12 @@ proc append*(parent, node: Node) = #TODO replaceChild -proc removeChild(parent, node: Node): Node {.jsfunc.} = - #TODO should be DOMException +proc removeChild(parent, node: Node): Result[Node, DOMException] {.jsfunc.} = if node.parentNode != parent: - JS_ERR JS_TypeError, "NotFoundError" + return err(newDOMException("Node is not a child of parent", + "NotFoundError")) node.remove() + return ok(node) proc replaceAll(parent, node: Node) = for i in countdown(parent.childList.high, 0): @@ -2576,10 +2591,11 @@ proc prepare*(element: HTMLScriptElement) = element.execute() #TODO options/custom elements -proc createElement(document: Document, localName: string): Element {.jserr, jsfunc.} = +proc createElement(document: Document, localName: string): + Result[Element, DOMException] {.jsfunc.} = if not localName.matchNameProduction(): - #TODO DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" + return err(newDOMException("Invalid character in element name", + "InvalidCharacterError")) let localName = if not document.isxml: localName.toLowerAscii2() else: @@ -2588,20 +2604,23 @@ proc createElement(document: Document, localName: string): Element {.jserr, jsfu Namespace.HTML else: NO_NAMESPACE - return document.newHTMLElement(localName, namespace) + return ok(document.newHTMLElement(localName, namespace)) #TODO createElementNS proc createDocumentFragment(document: Document): DocumentFragment {.jsfunc.} = return newDocumentFragment(document) -proc createDocumentType(implementation: DOMImplementation, qualifiedName, publicId, systemId: string): DocumentType {.jserr, jsfunc.} = +proc createDocumentType(implementation: DOMImplementation, qualifiedName, + publicId, systemId: string): Result[DocumentType, DOMException] {.jsfunc.} = if not qualifiedName.matchQNameProduction(): - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" - return implementation.document.newDocumentType(qualifiedName, publicId, systemId) + return err(newDOMException("Invalid character in document type name", + "InvalidCharacterError")) + return ok(implementation.document.newDocumentType(qualifiedName, publicId, + systemId)) -proc createHTMLDocument(implementation: DOMImplementation, title = none(string)): Document {.jsfunc.} = +proc createHTMLDocument(implementation: DOMImplementation, title = + none(string)): Document {.jsfunc.} = let doc = newDocument() doc.contentType = "text/html" doc.append(doc.newDocumentType("html")) @@ -2617,23 +2636,24 @@ proc createHTMLDocument(implementation: DOMImplementation, title = none(string)) #TODO set origin return doc -proc createCDATASection(document: Document, data: string): CDATASection {.jserr, jsfunc.} = +proc createCDATASection(document: Document, data: string): Result[CDATASection, DOMException] {.jsfunc.} = if not document.isxml: - #TODO should be DOMException - JS_ERR JS_TypeError, "NotSupportedError" + return err(newDOMException("CDATA sections are not supported in HTML", + "NotSupportedError")) if "]]>" in data: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" - return newCDATASection(document, data) + return err(newDOMException("CDATA sections may not contain the string ]]>", + "InvalidCharacterError")) + return ok(newCDATASection(document, data)) proc createComment*(document: Document, data: string): Comment {.jsfunc.} = return newComment(document, data) -proc createProcessingInstruction(document: Document, target, data: string): ProcessingInstruction {.jsfunc.} = +proc createProcessingInstruction(document: Document, target, data: string): + Result[ProcessingInstruction, DOMException] {.jsfunc.} = if not target.matchNameProduction() or "?>" in data: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidCharacterError" - return newProcessingInstruction(document, target, data) + return err(newDOMException("Invalid data for processing instruction", + "InvalidCharacterError")) + return ok(newProcessingInstruction(document, target, data)) # Forward definition hack (these are set in selectors.nim) var doqsa*: proc (node: Node, q: string): seq[Element] diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim index f6af8709..c3df8e3d 100644 --- a/src/html/htmlparser.nim +++ b/src/html/htmlparser.nim @@ -14,6 +14,7 @@ import encoding/decoderstream import html/dom import html/tags import html/htmltokenizer +import js/exception import js/javascript import types/url import utils/twtstr @@ -239,7 +240,7 @@ proc insert(location: AdjustedInsertionLocation, node: Node) = proc insertForeignElement(parser: var HTML5Parser, token: Token, namespace: Namespace): Element = let location = parser.appropriatePlaceForInsert() let element = parser.createElement(token, namespace, location.inside) - if location.inside.preInsertionValidity(element, location.before): + if location.inside.preInsertionValidity(element, location.before).isOk: #TODO custom elements location.insert(element) parser.pushElement(element) @@ -2239,15 +2240,16 @@ proc parseHTML*(inputStream: Stream, charsets: seq[Charset] = @[], proc newDOMParser*(): DOMParser {.jsctor.} = new(result) -proc parseFromString(parser: DOMParser, str: string, t: string): Document {.jserr, jsfunc.} = +proc parseFromString(parser: DOMParser, str: string, t: string): + Result[Document, JSError] {.jsfunc.} = case t of "text/html": let res = parseHTML(newStringStream(str)) - return res + return ok(res) of "text/xml", "application/xml", "application/xhtml+xml", "image/svg+xml": - JS_ERR JS_InternalError, "XML parsing is not supported yet" + return err(newInternalError("XML parsing is not supported yet")) else: - JS_ERR JS_TypeError, "Invalid mime type" + return err(newTypeError("Invalid mime type")) proc addHTMLModule*(ctx: JSContext) = ctx.registerType(DOMParser) diff --git a/src/img/path.nim b/src/img/path.nim index 4c112f28..9180fbdd 100644 --- a/src/img/path.nim +++ b/src/img/path.nim @@ -4,6 +4,8 @@ import math import types/line import types/vector +import js/exception +import utils/opt type Path* = ref object @@ -324,12 +326,13 @@ proc bezierCurveTo*(path: Path, cp0x, cp0y, cp1x, cp1y, x, y: float64) = let p = Vector2D(x: x, y: y) path.addBezierSegment(cp0, cp1, p) -proc arcTo*(path: Path, x1, y1, x2, y2, radius: float64): bool = +proc arcTo*(path: Path, x1, y1, x2, y2, radius: float64): Err[DOMException] = for v in [x1, y1, x2, y2, radius]: if classify(v) in {fcInf, fcNegInf, fcNan}: - return + return ok() if radius < 0: - return false + return err(newDOMException("Expected positive radius, but got negative", + "IndexSizeError")) path.ensureSubpath(x1, y1) #TODO this should be transformed by the inverse of the transformation matrix let v0 = path.subpaths[^1].points[^1] @@ -353,7 +356,7 @@ proc arcTo*(path: Path, x1, y1, x2, y2, radius: float64): bool = ) path.addStraightSegment(tv0) path.addArcSegment(origin, tv2, radius, true) #TODO always inner? - return true + return ok() func resolveEllipsePoint(o: Vector2D, angle, radiusX, radiusY, rotation: float64): Vector2D = @@ -370,12 +373,13 @@ func resolveEllipsePoint(o: Vector2D, angle, radiusX, radiusY, return Vector2D(x: relx, y: rely).rotate(rotation) + o proc arc*(path: Path, x, y, radius, startAngle, endAngle: float64, - counterclockwise: bool): bool = + counterclockwise: bool): Err[DOMException] = for v in [x, y, radius, startAngle, endAngle]: if classify(v) in {fcInf, fcNegInf, fcNan}: - return + return ok() if radius < 0: - return false + return err(newDOMException("Expected positive radius, but got negative", + "IndexSizeError")) let o = Vector2D(x: x, y: y) var s = resolveEllipsePoint(o, startAngle, radius, radius, 0) var e = resolveEllipsePoint(o, endAngle, radius, radius, 0) @@ -388,15 +392,16 @@ proc arc*(path: Path, x, y, radius, startAngle, endAngle: float64, else: path.moveTo(s) path.addArcSegment(o, e, radius, abs(startAngle - endAngle) < PI) - return true + return ok() proc ellipse*(path: Path, x, y, radiusX, radiusY, rotation, startAngle, - endAngle: float64, counterclockwise: bool): bool = + endAngle: float64, counterclockwise: bool): Err[DOMException] = for v in [x, y, radiusX, radiusY, rotation, startAngle, endAngle]: if classify(v) in {fcInf, fcNegInf, fcNan}: - return + return ok() if radiusX < 0 or radiusY < 0: - return false + return err(newDOMException("Expected positive radius, but got negative", + "IndexSizeError")) let o = Vector2D(x: x, y: y) var s = resolveEllipsePoint(o, startAngle, radiusX, radiusY, rotation) var e = resolveEllipsePoint(o, endAngle, radiusX, radiusY, rotation) @@ -409,7 +414,7 @@ proc ellipse*(path: Path, x, y, radiusX, radiusY, rotation, startAngle, else: path.moveTo(s) path.addEllipseSegment(o, e, radiusX, radiusY) - return true + return ok() proc rect*(path: Path, x, y, w, h: float64) = for v in [x, y, w, h]: diff --git a/src/io/request.nim b/src/io/request.nim index 8bc2bfc8..86c8a38d 100644 --- a/src/io/request.nim +++ b/src/io/request.nim @@ -4,6 +4,7 @@ import strutils import tables import bindings/quickjs +import js/exception import js/javascript import types/formdata import types/url @@ -225,13 +226,10 @@ func createPotentialCORSRequest*(url: URL, destination: RequestDestination, cors #TODO resource as Request #TODO init as an actual dictionary func newRequest*(ctx: JSContext, resource: string, - init = none(JSValue)): Request {.jserr, jsctor.} = - let x = parseURL(resource) - if x.isNone: - JS_ERR JS_TypeError, resource & " is not a valid URL." - if x.get.username != "" or x.get.password != "": - JS_ERR JS_TypeError, resource & " is not a valid URL." - let url = x.get + init = none(JSValue)): Result[Request, JSError] {.jsctor.} = + let url = ?newURL(resource) + if url.username != "" or url.password != "": + return err(newTypeError("Input URL contains a username or password")) let fallbackMode = some(RequestMode.CORS) #TODO none if resource is request var httpMethod = HTTP_GET var body = opt(string) @@ -254,7 +252,7 @@ func newRequest*(ctx: JSContext, resource: string, #TODO inputbody if (multipart.isSome or body.isSome) and httpMethod in {HTTP_GET, HTTP_HEAD}: - JS_ERR JS_TypeError, "HEAD or GET Request cannot have a body." + return err(newTypeError("HEAD or GET Request cannot have a body.")) let jheaders = JS_GetPropertyStr(ctx, init, "headers") hl.fill(ctx, jheaders) credentials = fromJS[CredentialsMode](ctx, JS_GetPropertyStr(ctx, init, @@ -263,8 +261,8 @@ func newRequest*(ctx: JSContext, resource: string, .get(mode) #TODO find a standard compatible way to implement this proxyUrl = fromJS[URL](ctx, JS_GetPropertyStr(ctx, init, "proxyUrl")) - return newRequest(url, httpMethod, hl, body, multipart, mode, credentials, - proxy = proxyUrl.get(nil)) + return ok(newRequest(url, httpMethod, hl, body, multipart, mode, credentials, + proxy = proxyUrl.get(nil))) proc add*(headers: var Headers, k, v: string) = let k = k.toHeaderCase() diff --git a/src/ips/serialize.nim b/src/ips/serialize.nim index f7fb934e..54b44a41 100644 --- a/src/ips/serialize.nim +++ b/src/ips/serialize.nim @@ -167,7 +167,11 @@ proc sread*(stream: Stream, url: var URL) = if s == "": url = nil else: - url = newURL(s) + let x = newURL(s) + if x.isSome: + url = x.get + else: + url = nil func slen*(url: URL): int = if url == nil: diff --git a/src/js/javascript.nim b/src/js/javascript.nim index e868dde3..ced12bf7 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -109,11 +109,7 @@ type LegacyJSError* = object of CatchableError #TODO remove these - JS_SyntaxError* = object of LegacyJSError JS_TypeError* = object of LegacyJSError - JS_ReferenceError* = object of LegacyJSError - JS_RangeError* = object of LegacyJSError - JS_InternalError* = object of LegacyJSError const QuickJSErrors = [ JS_EVAL_ERROR0, @@ -840,13 +836,15 @@ proc toJS[T, E](ctx: JSContext, opt: Result[T, E]): JSValue = if opt.isSome: when not (T is void): return toJS(ctx, opt.get) - return JS_UNDEFINED + else: + return JS_UNDEFINED else: when not (E is void): let res = toJS(ctx, opt.error) if not JS_IsNull(res): return JS_Throw(ctx, res) - return JS_NULL + else: + return JS_NULL proc toJS(ctx: JSContext, s: seq): JSValue = let a = JS_NewArray(ctx) @@ -1387,16 +1385,6 @@ proc newJSProc(gen: var JSFuncGenerator, params: openArray[NimNode], isva = true result = newProc(gen.newName, params, jsBody, pragmas = jsPragmas) gen.res = result -# WARNING: for now, this only works correctly when the .jserr pragma was -# declared on the parent function. -# Note: this causes the entire nim function body to be inlined inside the JS -# interface function. -#TODO: remove this. -macro JS_ERR*(a: typed, b: string) = - result = quote do: - block when_js: - raise newException(`a`, `b`) - func getFuncName(fun: NimNode, jsname: string): string = if jsname != "": return jsname @@ -1423,10 +1411,14 @@ proc addThisName(gen: var JSFuncGenerator, thisname: Option[string]) = gen.thisType = $gen.funcParams[gen.i][1] gen.newName = ident($gen.t & "_" & gen.thisType & "_" & gen.funcName) else: - if gen.returnType.get.kind == nnkRefTy: - gen.thisType = gen.returnType.get[0].strVal + let rt = gen.returnType.get + if rt.kind == nnkRefTy: + gen.thisType = rt[0].strVal else: - gen.thisType = gen.returnType.get.strVal + if rt.kind == nnkBracketExpr: + gen.thisType = rt[1].strVal + else: + gen.thisType = rt.strVal gen.newName = ident($gen.t & "_" & gen.funcName) func getActualMinArgs(gen: var JSFuncGenerator): int = @@ -1462,57 +1454,6 @@ proc setupGenerator(fun: NimNode, t: BoundFunctionType, gen.addThisName(thisname) return gen -# this might be pretty slow... -#TODO ideally we wouldn't need separate functions at all. Not sure how that -# could be achieved, maybe using options? -proc rewriteExceptions(gen: var JSFuncGenerator, errors: var seq[string], node: NimNode) = - for i in countdown(node.len - 1, 0): - let c = node[i] - if c.kind == nnkCommand and c[0].eqIdent ident("JS_ERR"): - if gen.copied == nil: - gen.copied = copy(gen.original) - if gen.returnType.isSome: - node[i] = quote do: - zeroMem(addr result, sizeof(result)) - return - else: - node[i] = quote do: - return - if c[1].strVal notin errors: - errors.add(c[1].strVal) - elif c.len > 0: - gen.rewriteExceptions(errors, c) - -proc rewriteExceptions(gen: var JSFuncGenerator) = - let ostmts = gen.original.findChild(it.kind == nnkStmtList) - var errors: seq[string] - gen.rewriteExceptions(errors, ostmts) - assert gen.copied != nil - var name: string - if gen.copied[0].kind == nnkIdent: - name = gen.copied[0].strVal - elif gen.copied[0].kind == nnkPostfix: - name = gen.copied[0][1].strVal - else: - error("No JS_ERR statement found in proc with jserr pragma.") - name &= "_exceptions" - gen.copied[0] = ident(name) - js_errors[name] = errors - -macro jserr*(fun: untyped) = - var gen: JSFuncGenerator - gen.original = fun - gen.rewriteExceptions() - var pragma = gen.original.findChild(it.kind == nnkPragma) - for i in 0..<pragma.len: - if pragma[i].eqIdent(ident("jsctor")) or pragma[i].eqIdent(ident("jsfunc")) or pragma[i].eqIdent(ident("jsget")) or pragma[i].eqIdent(ident("jsset")): - pragma.del(i) - gen.original.addPragma(quote do: used) # may be unused, but we have to keep it - gen.copied.addPragma(quote do: inline) - - #TODO mark original as used or something - result = newStmtList(gen.original, gen.copied) - macro jsctor*(fun: typed) = var gen = setupGenerator(fun, CONSTRUCTOR, thisname = none(string)) if gen.newName.strVal in existing_funcs: diff --git a/src/types/url.nim b/src/types/url.nim index 9e786347..36be9e06 100644 --- a/src/types/url.nim +++ b/src/types/url.nim @@ -5,6 +5,7 @@ import options import unicode import math +import js/exception import js/javascript import types/blob import utils/twtstr @@ -962,23 +963,23 @@ proc newURL*(url: URL): URL = result.searchParams[] = url.searchParams[] result.searchParams.url = some(result) -#TODO add Option wrapper -proc newURL*(s: string, base: Option[string] = none(string)): URL {.jserr, jsctor.} = +proc newURL*(s: string, base: Option[string] = none(string)): + Result[URL, JSError] {.jsctor.} = if base.issome: let baseUrl = parseURL(base.get) - if baseUrl.isnone: - JS_ERR JS_TypeError, base.get & " is not a valid URL" + if baseUrl.isNone: + return err(newTypeError(base.get & " is not a valid URL")) let url = parseURL(s, baseUrl) - if url.isnone: - JS_ERR JS_TypeError, s & " is not a valid URL" - return url.get + if url.isNone: + return err(newTypeError(s & " is not a valid URL")) + return ok(url.get) let url = parseURL(s) - if url.isnone: - JS_ERR JS_TypeError, s & " is not a valid URL" + if url.isNone: + return err(newTypeError(s & " is not a valid URL")) url.get.searchParams = newURLSearchParams() url.get.searchParams.url = url url.get.searchParams.initURLSearchParams(url.get.query.get("")) - return url.get + return ok(url.get) proc origin0*(url: URL): Origin = case url.scheme diff --git a/src/utils/opt.nim b/src/utils/opt.nim index 3766516b..1a86af8e 100644 --- a/src/utils/opt.nim +++ b/src/utils/opt.nim @@ -84,9 +84,6 @@ func error*[T, E](res: Result[T, E]): E {.inline.} = res.ex template valType*[T, E](res: type Result[T, E]): auto = T template errType*[T, E](res: type Result[T, E]): auto = E -func isSameErr[T, E, F](a: type Result[T, E], b: type F): bool = - return E is F - template `?`*[T, E](res: Result[T, E]): auto = let x = res # for when res is a funcall if x.has: @@ -97,7 +94,7 @@ template `?`*[T, E](res: Result[T, E]): auto = else: when typeof(result) is Result[T, E]: return x - elif isSameErr(typeof(result), E): + elif typeof(result).errType is E: return err(x.error) else: return err() diff --git a/src/xhr/formdata.nim b/src/xhr/formdata.nim index cbb9f63e..a1832274 100644 --- a/src/xhr/formdata.nim +++ b/src/xhr/formdata.nim @@ -1,5 +1,6 @@ import html/dom import html/tags +import js/exception import js/javascript import types/blob import types/formdata @@ -8,18 +9,22 @@ import utils/twtstr proc constructEntryList*(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): Option[seq[FormDataEntry]] +proc newFormData0*(): FormData = + return FormData() + proc newFormData*(form: HTMLFormElement = nil, - submitter: HTMLElement = nil): FormData {.jserr, jsctor.} = + submitter: HTMLElement = nil): Result[FormData, JSError] {.jsctor.} = let this = FormData() if form != nil: if submitter != nil: if not submitter.isSubmitButton(): - JS_ERR JS_TypeError, "Submitter must be a submit button" + return err(newDOMException("Submitter must be a submit button", + "InvalidStateError")) if FormAssociatedElement(submitter).form != form: - #TODO should be DOMException - JS_ERR JS_TypeError, "InvalidStateError" + return err(newDOMException("Submitter's form owner is not form", + "InvalidStateError")) this.entries = constructEntryList(form, submitter).get(@[]) - return this + return ok(this) #TODO as jsfunc proc append*(this: FormData, name: string, svalue: string, filename = "") = |