import macros import nativesockets import net import options import os import selectors import streams import tables import unicode when defined(posix): import posix import buffer/cell import css/cascade import css/cssparser import css/mediaquery import css/sheet import css/stylednode import data/charset import config/config import html/dom import html/env import html/htmlparser import html/tags import io/loader import io/request import io/posixstream import io/promise import io/teestream import ips/serialize import ips/serversocket import ips/socketstream import js/regex import io/window import layout/box import render/renderdocument import render/rendertext import types/buffersource import types/color import types/cookie import types/referer import types/url import utils/twtstr 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_NEXT_MATCH, FIND_PREV_MATCH, GET_SOURCE, GET_LINES, UPDATE_HOVER, PASS_FD, CONNECT, GOTO_ANCHOR, CANCEL, GET_TITLE HoverType* = enum HOVER_TITLE = "TITLE" HOVER_LINK = "URL" BufferMatch* = object success*: bool x*: int y*: int str*: string Buffer* = ref object fd: int alive: bool cs: Charset readbufsize: int contenttype: string lines: FlexibleGrid rendered: bool source: BufferSource width: int height: int attrs: WindowAttributes window: Window document: Document viewport: Viewport prevstyled: StyledNode url: URL selector: Selector[int] istream: Stream sstream: Stream available: int pstream: Stream # pipe stream srenderer: StreamRenderer connected: bool loaded: bool # istream is closed prevnode: StyledNode loader: FileLoader config: BufferConfig userstyle: CSSStylesheet timeouts: Table[int, (proc())] tasks: array[BufferCommand, int] #TODO this should have arguments savetask: bool hovertext: array[HoverType, string] 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 ) 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: `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 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 } func getClickable(styledNode: StyledNode): Element = if styledNode == nil or styledNode.node == nil: return nil if styledNode.t == STYLED_ELEMENT: let element = Element(styledNode.node) if element.tagType in ClickableElements: return element styledNode.node.findAncestor(ClickableElements) func getClickHover(styledNode: StyledNode): string = let clickable = styledNode.getClickable() if clickable != nil: case clickable.tagType of TAG_A: return HTMLAnchorElement(clickable).href of TAG_INPUT: return "" of TAG_OPTION: return "