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/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] # async, but worse EmptyPromise = ref object of RootObj cb: (proc()) next: EmptyPromise stream: Stream Promise*[T] = ref object of EmptyPromise res: T BufferInterface* = ref object stream*: Stream packetid: int promises: Table[int, EmptyPromise] proc newBufferInterface*(ostream: Stream): BufferInterface = result = BufferInterface( stream: ostream, packetid: 1 # ids below 1 are invalid ) proc fulfill*(iface: BufferInterface, packetid, len: int) = var promise: EmptyPromise if iface.promises.pop(packetid, promise): if promise.stream != nil and promise.cb == nil and len != 0: var abc = alloc(len) var x = 0 while x < len: x += promise.stream.readData(abc, len) dealloc(abc) while promise != nil: if promise.cb != nil: promise.cb() promise = promise.next proc hasPromises*(iface: BufferInterface): bool = return iface.promises.len > 0 proc then*(promise: EmptyPromise, cb: (proc())): EmptyPromise {.discardable.} = if promise == nil: return promise.cb = cb promise.next = EmptyPromise() return promise.next proc then*[T](promise: Promise[T], cb: (proc(x: T))): EmptyPromise {.discardable.} = if promise == nil: return return promise.then(proc() = if promise.stream != nil: promise.stream.sread(promise.res) cb(promise.res)) # Warning: we assume these aren't discarded. proc then*[T](promise: EmptyPromise, cb: (proc(): Promise[T])): Promise[T] = if promise == nil: return let next = Promise[T]() promise.then(proc() = let p2 = cb() if p2 != nil: p2.then(proc(x: T) = next.res = x next.cb())) return next proc then*[T, U](promise: Promise[T], cb: (proc(x: T): Promise[U])): Promise[U] = if promise == nil: return let next = Promise[U]() promise.then(proc(x: T) = let p2 = cb(x) if p2 != nil: p2.then(proc(y: U) = next.res = y next.cb())) return next # 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 if retval.kind == nnkEmpty: retval2 = ident("EmptyPromise") else: 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: `thisval`.promises[`thisval`.packetid] = `retval2`(stream: `thisval`.stream) inc `thisval`.packetid) var pragmas: NimNode if retval.kind == nnkEmpty: body.add(quote do: return `thisval`.promises[`thisval`.packetid - 1]) pragmas = newNimNode(nnkPragma).add(ident("discardable")) else: body.add(quote do: return `retval2`(`thisval`.promises[`thisval`.packetid - 1])) 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 "