import std/macros import std/nativesockets import std/net import std/options import std/os import std/posix import std/selectors import std/streams import std/tables import std/unicode import bindings/quickjs import config/config import css/cascade import css/cssparser import css/mediaquery import css/sheet import css/stylednode import css/values import display/winattrs import html/catom import html/chadombuilder import html/dom import html/enums import html/env import html/event import img/png import io/posixstream import io/promise import io/serialize import io/serversocket import io/socketstream import io/teestream import js/error import js/fromjs import js/javascript import js/regex import js/timeout import js/tojs import loader/connecterror import loader/headers import loader/loader import render/renderdocument import render/rendertext import types/blob import types/buffersource import types/cell import types/color import types/cookie import types/formdata import types/opt import types/referer import types/url import utils/strwidth import utils/twtstr import xhr/formdata as formdata_impl import chakasu/charset import chakasu/decoderstream import chakasu/encoderstream import chame/tags type LoadInfo* = enum CONNECT, DOWNLOAD, RENDER, DONE BufferCommand* = enum LOAD, RENDER, WINDOW_CHANGE, FIND_ANCHOR, READ_SUCCESS, READ_CANCELED, CLICK, FIND_NEXT_LINK, FIND_PREV_LINK, FIND_NTH_LINK, FIND_REV_NTH_LINK, FIND_NEXT_MATCH, FIND_PREV_MATCH, GET_SOURCE, GET_LINES, UPDATE_HOVER, PASS_FD, CONNECT, CONNECT2, GOTO_ANCHOR, CANCEL, GET_TITLE, SELECT, REDIRECT_TO_FD, READ_FROM_FD, SET_CONTENT_TYPE, CLONE, FIND_PREV_PARAGRAPH, FIND_NEXT_PARAGRAPH # LOADING_PAGE: istream open # LOADING_RESOURCES: istream closed, resources open # LOADED: istream closed, resources closed BufferState* = enum LOADING_PAGE, LOADING_RESOURCES, LOADED HoverType* = enum HOVER_TITLE = "TITLE" HOVER_LINK = "URL" BufferMatch* = object success*: bool x*: int y*: int str*: string Buffer* = ref object rfd: int # file descriptor of command pipe fd: int # file descriptor of buffer source alive: bool readbufsize: int lines: FlexibleGrid rendered: bool source: BufferSource width: int height: int attrs: WindowAttributes window: Window document: Document prevstyled: StyledNode selector: Selector[int] istream: Stream sstream: Stream available: int pstream: SocketStream # pipe stream srenderer: StreamRenderer connected: bool state: BufferState prevnode: StyledNode loader: FileLoader config: BufferConfig userstyle: CSSStylesheet tasks: array[BufferCommand, int] #TODO this should have arguments savetask: bool hovertext: array[HoverType, string] estream: Stream # error stream ishtml: bool ssock: ServerSocket factory: CAtomFactory uastyle: CSSStylesheet quirkstyle: CSSStylesheet InterfaceOpaque = ref object stream: Stream len: int BufferInterface* = ref object map: PromiseMap packetid: int opaque: InterfaceOpaque stream*: Stream proc getFromOpaque[T](opaque: pointer, res: var T) = let opaque = cast[InterfaceOpaque](opaque) if opaque.len != 0: opaque.stream.sread(res) proc newBufferInterface*(stream: Stream): BufferInterface = let opaque = InterfaceOpaque(stream: stream) result = BufferInterface( map: newPromiseMap(cast[pointer](opaque)), packetid: 1, # ids below 1 are invalid opaque: opaque, stream: stream ) # After cloning a buffer, we need a new interface to the new buffer process. # Here we create a new interface for that clone. proc cloneInterface*(stream: Stream): BufferInterface = let iface = newBufferInterface(stream) # We have just fork'ed the buffer process inside an interface function, # from which the new buffer is going to return as well. So we must also # consume the return value of the clone function, which is the pid 0. var len: int var pid: Pid stream.sread(len) stream.sread(iface.packetid) stream.sread(pid) return iface proc resolve*(iface: BufferInterface, packetid, len: int) = iface.opaque.len = len iface.map.resolve(packetid) proc hasPromises*(iface: BufferInterface): bool = return not iface.map.empty() # get enum identifier of proxy function func getFunId(fun: NimNode): string = let name = fun[0] # sym result = name.strVal.toScreamingSnakeCase() if result[^1] == '=': result = "SET_" & result[0..^2] proc buildInterfaceProc(fun: NimNode, funid: string): tuple[fun, name: NimNode] = let name = fun[0] # sym let params = fun[3] # formalparams let retval = params[0] # sym var body = newStmtList() assert params.len >= 2 # return type, this value let nup = ident(funid) # add this to enums let this2 = newIdentDefs(ident("iface"), ident("BufferInterface")) let thisval = this2[0] body.add(quote do: `thisval`.stream.swrite(BufferCommand.`nup`) `thisval`.stream.swrite(`thisval`.packetid)) var params2: seq[NimNode] var retval2: NimNode var addfun: NimNode if retval.kind == nnkEmpty: addfun = quote do: `thisval`.map.addEmptyPromise(`thisval`.packetid) retval2 = ident("EmptyPromise") else: addfun = quote do: addPromise[`retval`](`thisval`.map, `thisval`.packetid, getFromOpaque[`retval`]) retval2 = newNimNode(nnkBracketExpr).add( ident("Promise"), retval) params2.add(retval2) params2.add(this2) for i in 2 ..< params.len: let param = params[i] for i in 0 ..< param.len - 2: let id2 = newIdentDefs(ident(param[i].strVal), param[^2]) params2.add(id2) for i in 2 ..< params2.len: let s = params2[i][0] # sym e.g. url body.add(quote do: when typeof(`s`) is FileHandle: SocketStream(`thisval`.stream).sendFileHandle(`s`) else: `thisval`.stream.swrite(`s`)) body.add(quote do: `thisval`.stream.flush()) body.add(quote do: let promise = `addfun` inc `thisval`.packetid return promise) var pragmas: NimNode if retval.kind == nnkEmpty: pragmas = newNimNode(nnkPragma).add(ident("discardable")) else: pragmas = newEmptyNode() return (newProc(name, params2, body, pragmas = pragmas), nup) type ProxyFunction = ref object iname: NimNode # internal name ename: NimNode # enum name params: seq[NimNode] istask: bool ProxyMap = Table[string, ProxyFunction] # Name -> ProxyFunction var ProxyFunctions {.compileTime.}: ProxyMap proc getProxyFunction(funid: string): ProxyFunction = if funid notin ProxyFunctions: ProxyFunctions[funid] = ProxyFunction() return ProxyFunctions[funid] macro proxy0(fun: untyped) = fun[0] = ident(fun[0].strVal & "_internal") return fun macro proxy1(fun: typed) = let funid = getFunId(fun) let iproc = buildInterfaceProc(fun, funid) let pfun = getProxyFunction(funid) pfun.iname = ident(fun[0].strVal & "_internal") pfun.ename = iproc[1] pfun.params.add(fun[3][0]) var params2: seq[NimNode] params2.add(fun[3][0]) for i in 1 ..< fun[3].len: let param = fun[3][i] pfun.params.add(param) for i in 0 ..< param.len - 2: let id2 = newIdentDefs(ident(param[i].strVal), param[^2]) params2.add(id2) ProxyFunctions[funid] = pfun return iproc[0] macro proxy(fun: typed) = quote do: proxy0(`fun`) proxy1(`fun`) macro task(fun: typed) = let funid = getFunId(fun) let pfun = getProxyFunction(funid) pfun.istask = true fun func url(buffer: Buffer): URL = return buffer.source.location func charsets(buffer: Buffer): seq[Charset] = if buffer.source.charset != CHARSET_UNKNOWN: return @[buffer.source.charset] return buffer.config.charsets func getTitleAttr(node: StyledNode): string = if node == nil: return "" if node.t == STYLED_ELEMENT and node.node != nil: let element = Element(node.node) if element.attrb("title"): return element.attr("title") if node.node != nil: var node = node.node for element in node.ancestors: if element.attrb("title"): return element.attr("title") #TODO pseudo-elements const ClickableElements = { TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA, TAG_LABEL } func isClickable(styledNode: StyledNode): bool = if styledNode.t != STYLED_ELEMENT or styledNode.node == nil: return false if styledNode.computed{"visibility"} != VISIBILITY_VISIBLE: return false let element = Element(styledNode.node) if element.tagType == TAG_A: return HTMLAnchorElement(element).href != "" return element.tagType in ClickableElements func getClickable(styledNode: StyledNode): Element = var styledNode = styledNode while styledNode != nil: if styledNode.isClickable(): return Element(styledNode.node) styledNode = styledNode.parent proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] func canSubmitOnClick(fae: FormAssociatedElement): bool = if fae.form == nil: return false if fae.form.canSubmitImplicitly(): return true if fae.tagType == TAG_BUTTON: if HTMLButtonElement(fae).ctype == BUTTON_SUBMIT: return true if fae.tagType == TAG_INPUT: if HTMLInputElement(fae).inputType in {INPUT_SUBMIT, INPUT_BUTTON}: return true return false proc getClickHover(styledNode: StyledNode): string = let clickable = styledNode.getClickable() if clickable != nil: case clickable.tagType of TAG_A: return HTMLAnchorElement(clickable).href of TAG_INPUT: #TODO this is inefficient and also quite stupid if clickable.tagType in FormAssociatedElements: let fae = FormAssociatedElement(clickable) if fae.canSubmitOnClick(): let req = fae.form.submitForm(fae) if req.isSome: return $req.get.url return "" of TAG_OPTION: return "