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 "