import std/algorithm import std/deques import std/math import std/options import std/sets import std/strutils import std/tables import std/times import chagashi/charset import chagashi/decoder import chame/tags import css/cssparser import css/cssvalues import css/mediaquery import css/sheet import html/catom import html/enums import html/event import html/script import img/bitmap import img/painter import img/path import io/dynstream import io/promise import js/console import js/domexception import js/timeout import loader/headers import loader/loader import loader/request import monoucha/fromjs import monoucha/javascript import monoucha/jserror import monoucha/jsopaque import monoucha/jspropenumlist import monoucha/jsutils import monoucha/quickjs import monoucha/tojs import types/blob import types/color import types/matrix import types/opt import types/referrer import types/url import types/vector import types/winattrs import utils/mimeguess import utils/strwidth import utils/twtstr type FormMethod* = enum fmGet = "get" fmPost = "post" fmDialog = "dialog" FormEncodingType* = enum fetUrlencoded = "application/x-www-form-urlencoded", fetMultipart = "multipart/form-data", fetTextPlain = "text/plain" type DocumentReadyState* = enum rsLoading = "loading" rsInteractive = "interactive" rsComplete = "complete" type DependencyType* = enum dtHover, dtChecked, dtFocus Location = ref object window: Window CachedURLImage = ref object expiry: int64 loading: bool shared: seq[HTMLImageElement] bmp: NetworkBitmap Window* = ref object of EventTarget attrs*: WindowAttributes internalConsole*: Console navigator* {.jsget.}: Navigator screen* {.jsget.}: Screen settings*: EnvironmentSettings loader*: FileLoader location* {.jsget.}: Location jsrt*: JSRuntime jsctx*: JSContext document* {.jsufget.}: Document timeouts*: TimeoutState navigate*: proc(url: URL) importMapsAllowed*: bool factory*: CAtomFactory loadingResourcePromises*: seq[EmptyPromise] imageURLCache: Table[string, CachedURLImage] images*: bool styling*: bool # ID of the next image imageId: int # Navigator stuff Navigator* = object plugins: PluginArray PluginArray* = object MimeTypeArray* = object Screen* = object NamedNodeMap = ref object element: Element attrlist: seq[Attr] Collection = ref CollectionObj CollectionObj = object of RootObj islive: bool childonly: bool root: Node match: proc(node: Node): bool {.noSideEffect.} snapshot: seq[Node] livelen: int NodeList = ref object of Collection HTMLCollection = ref object of Collection HTMLAllCollection = ref object of Collection DOMTokenList = ref object toks*: seq[CAtom] element: Element localName: CAtom DOMStringMap = object target {.cursor.}: HTMLElement Node* = ref object of EventTarget childList*: seq[Node] parentNode* {.jsget.}: Node index*: int # Index in parents children. -1 for nodes without a parent. # Live collection cache: pointers to live collections are saved in all # nodes they refer to. These are removed when the collection is destroyed, # and invalidated when the owner node's children or attributes change. liveCollections: HashSet[pointer] cachedChildNodes: NodeList internalDocument: Document # not nil Attr* = ref object of Node dataIdx: int ownerElement*: Element DOMImplementation = object document: Document DocumentWriteBuffer* = ref object data*: string i*: int Document* = ref object of Node factory*: CAtomFactory charset*: Charset window* {.jsget: "defaultView".}: Window url* {.jsget: "URL".}: URL mode*: QuirksMode currentScript: HTMLScriptElement isxml*: bool implementation {.jsget.}: DOMImplementation origin: Origin readyState* {.jsget.}: DocumentReadyState # document.write ignoreDestructiveWrites: int throwOnDynamicMarkupInsertion: int activeParserWasAborted: bool writeBuffers*: seq[DocumentWriteBuffer] scriptsToExecSoon*: seq[HTMLScriptElement] scriptsToExecInOrder*: Deque[HTMLScriptElement] scriptsToExecOnLoad*: Deque[HTMLScriptElement] parserBlockingScript*: HTMLScriptElement parserCannotChangeModeFlag*: bool internalFocus: Element contentType* {.jsget.}: string renderBlockingElements: seq[Element] invalidCollections: HashSet[pointer] # pointers to Collection objects invalid*: bool # whether the document must be rendered again cachedAll: HTMLAllCollection cachedSheets: seq[CSSStylesheet] cachedSheetsInvalid*: bool cachedChildren: HTMLCollection #TODO I hate this but I really don't want to put chadombuilder into dom too parser*: pointer CharacterData* = ref object of Node data* {.jsget.}: string Text* = ref object of CharacterData Comment* = ref object of CharacterData CDATASection = ref object of CharacterData ProcessingInstruction = ref object of CharacterData target {.jsget.}: string DocumentFragment* = ref object of Node host*: Element cachedChildren*: HTMLCollection DocumentType* = ref object of Node name*: string publicId*: string systemId*: string AttrData* = object qualifiedName*: CAtom localName*: CAtom prefix*: CAtom namespace*: CAtom value*: string Element* = ref object of Node namespace*: Namespace namespacePrefix*: NamespacePrefix prefix*: string localName*: CAtom id*: CAtom name*: CAtom classList* {.jsget.}: DOMTokenList attrs: seq[AttrData] # sorted by int(qualifiedName) attributesInternal: NamedNodeMap internalHover: bool invalid*: bool cachedStyle*: CSSStyleDeclaration cachedChildren: HTMLCollection # The owner StyledNode is marked as invalid when one of these no longer # matches the DOM value. invalidDeps*: set[DependencyType] AttrDummyElement = ref object of Element CSSStyleDeclaration* = ref object decls*: seq[CSSDeclaration] element: Element HTMLElement* = ref object of Element dataset {.jsget.}: DOMStringMap FormAssociatedElement* = ref object of HTMLElement form*: HTMLFormElement parserInserted*: bool HTMLInputElement* = ref object of FormAssociatedElement inputType*: InputType value* {.jsget.}: string internalChecked {.jsget: "checked".}: bool xcoord*: int ycoord*: int file*: WebFile HTMLAnchorElement* = ref object of HTMLElement relList {.jsget.}: DOMTokenList HTMLSelectElement* = ref object of FormAssociatedElement HTMLSpanElement* = ref object of HTMLElement HTMLOptGroupElement* = ref object of HTMLElement HTMLOptionElement* = ref object of HTMLElement selected*: bool HTMLHeadingElement* = ref object of HTMLElement HTMLBRElement* = ref object of HTMLElement HTMLMenuElement* = ref object of HTMLElement HTMLUListElement* = ref object of HTMLElement HTMLOListElement* = ref object of HTMLElement HTMLLIElement* = ref object of HTMLElement value* {.jsget.}: Option[int32] HTMLStyleElement* = ref object of HTMLElement sheet: CSSStylesheet HTMLLinkElement* = ref object of HTMLElement sheet*: CSSStylesheet relList {.jsget.}: DOMTokenList fetchStarted: bool HTMLFormElement* = ref object of HTMLElement constructingEntryList*: bool controls*: seq[FormAssociatedElement] relList {.jsget.}: DOMTokenList HTMLTemplateElement* = ref object of HTMLElement content*: DocumentFragment HTMLUnknownElement* = ref object of HTMLElement HTMLScriptElement* = ref object of HTMLElement parserDocument*: Document preparationTimeDocument*: Document forceAsync*: bool external*: bool readyForParserExec*: bool alreadyStarted*: bool delayingTheLoadEvent: bool ctype: ScriptType internalNonce: string scriptResult*: ScriptResult onReady: (proc()) HTMLBaseElement* = ref object of HTMLElement HTMLAreaElement* = ref object of HTMLElement relList {.jsget.}: DOMTokenList HTMLButtonElement* = ref object of FormAssociatedElement ctype*: ButtonType value* {.jsget, jsset.}: string HTMLTextAreaElement* = ref object of FormAssociatedElement value* {.jsget.}: string HTMLLabelElement* = ref object of HTMLElement HTMLCanvasElement* = ref object of HTMLElement ctx2d: CanvasRenderingContext2D bitmap*: Bitmap DrawingState = object # CanvasTransform transformMatrix: Matrix # CanvasFillStrokeStyles fillStyle: ARGBColor strokeStyle: ARGBColor # CanvasPathDrawingStyles lineWidth: float64 # CanvasTextDrawingStyles textAlign: CSSTextAlign # CanvasPath path: Path RenderingContext = ref object of RootObj CanvasRenderingContext2D = ref object of RenderingContext canvas {.jsget.}: HTMLCanvasElement bitmap: Bitmap state: DrawingState stateStack: seq[DrawingState] TextMetrics = ref object # x-direction width {.jsget.}: float64 actualBoundingBoxLeft {.jsget.}: float64 actualBoundingBoxRight {.jsget.}: float64 # y-direction fontBoundingBoxAscent {.jsget.}: float64 fontBoundingBoxDescent {.jsget.}: float64 actualBoundingBoxAscent {.jsget.}: float64 actualBoundingBoxDescent {.jsget.}: float64 emHeightAscent {.jsget.}: float64 emHeightDescent {.jsget.}: float64 hangingBaseline {.jsget.}: float64 alphabeticBaseline {.jsget.}: float64 ideographicBaseline {.jsget.}: float64 HTMLImageElement* = ref object of HTMLElement bitmap*: Bitmap fetchStarted: bool HTMLVideoElement* = ref object of HTMLElement HTMLAudioElement* = ref object of HTMLElement jsDestructor(Navigator) jsDestructor(PluginArray) jsDestructor(MimeTypeArray) jsDestructor(Screen) jsDestructor(Element) jsDestructor(HTMLElement) jsDestructor(HTMLInputElement) jsDestructor(HTMLAnchorElement) jsDestructor(HTMLSelectElement) jsDestructor(HTMLSpanElement) jsDestructor(HTMLOptGroupElement) jsDestructor(HTMLOptionElement) jsDestructor(HTMLHeadingElement) jsDestructor(HTMLBRElement) jsDestructor(HTMLMenuElement) jsDestructor(HTMLUListElement) jsDestructor(HTMLOListElement) jsDestructor(HTMLLIElement) jsDestructor(HTMLStyleElement) jsDestructor(HTMLLinkElement) jsDestructor(HTMLFormElement) jsDestructor(HTMLTemplateElement) jsDestructor(HTMLUnknownElement) jsDestructor(HTMLScriptElement) jsDestructor(HTMLBaseElement) jsDestructor(HTMLAreaElement) jsDestructor(HTMLButtonElement) jsDestructor(HTMLTextAreaElement) jsDestructor(HTMLLabelElement) jsDestructor(HTMLCanvasElement) jsDestructor(HTMLImageElement) jsDestructor(HTMLVideoElement) jsDestructor(HTMLAudioElement) jsDestructor(Node) jsDestructor(NodeList) jsDestructor(HTMLCollection) jsDestructor(HTMLAllCollection) jsDestructor(Location) jsDestructor(Document) jsDestructor(DOMImplementation) jsDestructor(DOMTokenList) jsDestructor(DOMStringMap) jsDestructor(Comment) jsDestructor(CDATASection) jsDestructor(DocumentFragment) jsDestructor(ProcessingInstruction) jsDestructor(CharacterData) jsDestructor(Text) jsDestructor(DocumentType) jsDestructor(Attr) jsDestructor(NamedNodeMap) jsDestructor(CanvasRenderingContext2D) jsDestructor(TextMetrics) jsDestructor(CSSStyleDeclaration) proc parseColor(element: Element; s: string): ARGBColor func console(window: Window): Console = return window.internalConsole proc resetTransform(state: var DrawingState) = state.transformMatrix = newIdentityMatrix(3) proc resetState(state: var DrawingState) = state.resetTransform() state.fillStyle = rgba(0, 0, 0, 255) state.strokeStyle = rgba(0, 0, 0, 255) state.path = newPath() proc create2DContext*(jctx: JSContext; target: HTMLCanvasElement; options: Option[JSValue]): CanvasRenderingContext2D = let ctx = CanvasRenderingContext2D( bitmap: target.bitmap, canvas: target ) ctx.state.resetState() return ctx # CanvasState proc save(ctx: CanvasRenderingContext2D) {.jsfunc.} = ctx.stateStack.add(ctx.state) proc restore(ctx: CanvasRenderingContext2D) {.jsfunc.} = if ctx.stateStack.len > 0: ctx.state = ctx.stateStack.pop() proc reset(ctx: CanvasRenderingContext2D) {.jsfunc.} = ctx.bitmap.clear() #TODO empty list of subpaths ctx.stateStack.setLen(0) ctx.state.resetState() # CanvasTransform #TODO scale proc rotate(ctx: CanvasRenderingContext2D; angle: float64) {.jsfunc.} = if classify(angle) in {fcInf, fcNegInf, fcNan}: return ctx.state.transformMatrix *= newMatrix( me = @[ cos(angle), -sin(angle), 0, sin(angle), cos(angle), 0, 0, 0, 1 ], w = 3, h = 3 ) proc translate(ctx: CanvasRenderingContext2D; x, y: float64) {.jsfunc.} = for v in [x, y]: if classify(v) in {fcInf, fcNegInf, fcNan}: return ctx.state.transformMatrix *= newMatrix( me = @[ 1f64, 0, x, 0, 1, y, 0, 0, 1 ], w = 3, h = 3 ) proc transform(ctx: CanvasRenderingContext2D; a, b, c, d, e, f: float64) {.jsfunc.} = for v in [a, b, c, d, e, f]: if classify(v) in {fcInf, fcNegInf, fcNan}: return ctx.state.transformMatrix *= newMatrix( me = @[ a, c, e, b, d, f, 0, 0, 1 ], w = 3, h = 3 ) #TODO getTransform, setTransform with DOMMatrix (i.e. we're missing DOMMatrix) proc setTransform(ctx: CanvasRenderingContext2D; a, b, c, d, e, f: float64) {.jsfunc.} = for v in [a, b, c, d, e, f]: if classify(v) in {fcInf, fcNegInf, fcNan}: return ctx.state.resetTransform() ctx.transform(a, b, c, d, e, f) proc resetTransform(ctx: CanvasRenderingContext2D) {.jsfunc.} = ctx.state.resetTransform() func transform(ctx: CanvasRenderingContext2D; v: Vector2D): Vector2D = let mul = ctx.state.transformMatrix * newMatrix(@[v.x, v.y, 1], 1, 3) return Vector2D(x: mul.me[0], y: mul.me[1]) # CanvasFillStrokeStyles proc fillStyle(ctx: CanvasRenderingContext2D): string {.jsfget.} = return ctx.state.fillStyle.serialize() proc fillStyle(ctx: CanvasRenderingContext2D; s: string) {.jsfset.} = #TODO gradient, pattern ctx.state.fillStyle = ctx.canvas.parseColor(s) proc strokeStyle(ctx: CanvasRenderingContext2D): string {.jsfget.} = return ctx.state.strokeStyle.serialize() proc strokeStyle(ctx: CanvasRenderingContext2D; s: string) {.jsfset.} = #TODO gradient, pattern ctx.state.strokeStyle = ctx.canvas.parseColor(s) # CanvasRect proc clearRect(ctx: CanvasRenderingContext2D; x, y, w, h: float64) {.jsfunc.} = for v in [x, y, w, h]: if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO clipping regions (right now we just clip to default) let bw = float64(ctx.bitmap.width) let bh = float64(ctx.bitmap.height) let x0 = uint64(min(max(x, 0), bw)) let x1 = uint64(min(max(x + w, 0), bw)) let y0 = uint64(min(max(y, 0), bh)) let y1 = uint64(min(max(y + h, 0), bh)) ctx.bitmap.clearRect(x0, x1, y0, y1) proc fillRect(ctx: CanvasRenderingContext2D; x, y, w, h: float64) {.jsfunc.} = for v in [x, y, w, h]: if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO do we have to clip here? if w == 0 or h == 0: return let bw = float64(ctx.bitmap.width) let bh = float64(ctx.bitmap.height) let x0 = uint64(min(max(x, 0), bw)) let x1 = uint64(min(max(x + w, 0), bw)) let y0 = uint64(min(max(y, 0), bh)) let y1 = uint64(min(max(y + h, 0), bh)) ctx.bitmap.fillRect(x0, x1, y0, y1, ctx.state.fillStyle) proc strokeRect(ctx: CanvasRenderingContext2D; x, y, w, h: float64) {.jsfunc.} = for v in [x, y, w, h]: if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO do we have to clip here? if w == 0 or h == 0: return let bw = float64(ctx.bitmap.width) let bh = float64(ctx.bitmap.height) let x0 = uint64(min(max(x, 0), bw)) let x1 = uint64(min(max(x + w, 0), bw)) let y0 = uint64(min(max(y, 0), bh)) let y1 = uint64(min(max(y + h, 0), bh)) ctx.bitmap.strokeRect(x0, x1, y0, y1, ctx.state.strokeStyle) # CanvasDrawPath proc beginPath(ctx: CanvasRenderingContext2D) {.jsfunc.} = ctx.state.path.beginPath() proc fill(ctx: CanvasRenderingContext2D; fillRule = cfrNonZero) {.jsfunc.} = #TODO path ctx.state.path.tempClosePath() ctx.bitmap.fillPath(ctx.state.path, ctx.state.fillStyle, fillRule) ctx.state.path.tempOpenPath() proc stroke(ctx: CanvasRenderingContext2D) {.jsfunc.} = #TODO path ctx.bitmap.strokePath(ctx.state.path, ctx.state.strokeStyle) proc clip(ctx: CanvasRenderingContext2D; fillRule = cfrNonZero) {.jsfunc.} = #TODO path discard #TODO implement #TODO clip, ... # CanvasUserInterface # CanvasText const unifont = readFile"res/unifont_jp-15.0.05.png" proc loadUnifont(window: Window) = #TODO this is very wrong, we should move the unifont file in a CGI script or # something if unifontBitmap != nil: return let request = newRequest( newURL("img-codec+png:decode").get, httpMethod = hmPost, body = RequestBody(t: rbtString, s: unifont) ) let response = window.loader.doRequest(request) assert response.res == 0 let dims = response.headers.table["Cha-Image-Dimensions"][0] let width = parseUInt64(dims.until('x'), allowSign = false).get let height = parseUInt64(dims.after('x'), allowSign = false).get let len = int(width) * int(height) let bitmap = ImageBitmap( px: cast[seq[RGBAColorBE]](newSeqUninitialized[uint32](len)), width: width, height: height ) window.loader.resume(response.outputId) response.body.recvDataLoop(addr bitmap.px[0], len * 4) response.body.sclose() unifontBitmap = bitmap #TODO maxwidth proc fillText(ctx: CanvasRenderingContext2D; text: string; x, y: float64) {.jsfunc.} = for v in [x, y]: if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO should not be loaded here... ctx.canvas.internalDocument.window.loadUnifont() let vec = ctx.transform(Vector2D(x: x, y: y)) ctx.bitmap.fillText(text, vec.x, vec.y, ctx.state.fillStyle, ctx.state.textAlign) #TODO maxwidth proc strokeText(ctx: CanvasRenderingContext2D; text: string; x, y: float64) {.jsfunc.} = for v in [x, y]: if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO should not be loaded here... ctx.canvas.internalDocument.window.loadUnifont() let vec = ctx.transform(Vector2D(x: x, y: y)) ctx.bitmap.strokeText(text, vec.x, vec.y, ctx.state.strokeStyle, ctx.state.textAlign) proc measureText(ctx: CanvasRenderingContext2D; text: string): TextMetrics {.jsfunc.} = let tw = text.width() return TextMetrics( width: 8 * float64(tw), actualBoundingBoxLeft: 0, actualBoundingBoxRight: 8 * float64(tw), #TODO and the rest... ) # CanvasDrawImage # CanvasImageData # CanvasPathDrawingStyles proc lineWidth(ctx: CanvasRenderingContext2D): float64 {.jsfget.} = return ctx.state.lineWidth proc lineWidth(ctx: CanvasRenderingContext2D; f: float64) {.jsfset.} = if classify(f) in {fcZero, fcNegZero, fcInf, fcNegInf, fcNan}: return ctx.state.lineWidth = f proc setLineDash(ctx: CanvasRenderingContext2D; segments: seq[float64]) {.jsfunc.} = discard #TODO implement proc getLineDash(ctx: CanvasRenderingContext2D): seq[float64] {.jsfunc.} = discard #TODO implement # CanvasTextDrawingStyles proc textAlign(ctx: CanvasRenderingContext2D): string {.jsfget.} = case ctx.state.textAlign of TextAlignStart: return "start" of TextAlignEnd: return "end" of TextAlignLeft: return "left" of TextAlignRight: return "right" of TextAlignCenter: return "center" else: doAssert false proc textAlign(ctx: CanvasRenderingContext2D; s: string) {.jsfset.} = ctx.state.textAlign = case s of "start": TextAlignStart of "end": TextAlignEnd of "left": TextAlignLeft of "right": TextAlignRight of "center": TextAlignCenter else: ctx.state.textAlign # CanvasPath proc closePath(ctx: CanvasRenderingContext2D) {.jsfunc.} = ctx.state.path.closePath() proc moveTo(ctx: CanvasRenderingContext2D; x, y: float64) {.jsfunc.} = ctx.state.path.moveTo(x, y) proc lineTo(ctx: CanvasRenderingContext2D; x, y: float64) {.jsfunc.} = ctx.state.path.lineTo(x, y) 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): 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): 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): 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) proc roundRect(ctx: CanvasRenderingContext2D; x, y, w, h, radii: float64) {.jsfunc.} = ctx.state.path.roundRect(x, y, w, h, radii) # Reflected attributes. type ReflectType = enum rtStr, rtBool, rtLong, rtUlongGz, rtUlong, rtFunction ReflectEntry = object attrname: StaticAtom funcname: string tags: set[TagType] case t: ReflectType of rtLong: i: int32 of rtUlong, rtUlongGz: u: uint32 of rtFunction: ctype: StaticAtom else: discard func attrType0(s: static string): StaticAtom {.compileTime.} = return strictParseEnum[StaticAtom](s).get template toset(ts: openArray[TagType]): set[TagType] = var tags: system.set[TagType] for tag in ts: tags.incl(tag) tags func makes(name: static string; ts: set[TagType]): ReflectEntry = const attrname = attrType0(name) ReflectEntry( attrname: attrname, funcname: name, t: rtStr, tags: ts ) func makes(attrname, funcname: static string; ts: set[TagType]): ReflectEntry = const attrname = attrType0(attrname) ReflectEntry( attrname: attrname, funcname: funcname, t: rtStr, tags: ts ) func makes(name: static string; ts: varargs[TagType]): ReflectEntry = makes(name, toset(ts)) func makes(attrname, funcname: static string; ts: varargs[TagType]): ReflectEntry = makes(attrname, funcname, toset(ts)) func makeb(attrname, funcname: static string; ts: varargs[TagType]): ReflectEntry = const attrname = attrType0(attrname) ReflectEntry( attrname: attrname, funcname: funcname, t: rtBool, tags: toset(ts) ) func makeb(name: static string; ts: varargs[TagType]): ReflectEntry = makeb(name, name, ts) func makeul(name: static string; ts: varargs[TagType]; default = 0u32): ReflectEntry = const attrname = attrType0(name) ReflectEntry( attrname: attrname, funcname: name, t: rtUlong, tags: toset(ts), u: default ) func makeulgz(name: static string; ts: varargs[TagType]; default = 0u32): ReflectEntry = const attrname = attrType0(name) ReflectEntry( attrname: attrname, funcname: name, t: rtUlongGz, tags: toset(ts), u: default ) func makef(name: static string; ts: set[TagType]; ctype: static string): ReflectEntry = const attrname = attrType0(name) ReflectEntry( attrname: attrname, funcname: name, t: rtFunction, tags: ts, ctype: attrType0(ctype) ) const ReflectTable0 = [ # non-global attributes makes("target", TAG_A, TAG_AREA, TAG_LABEL, TAG_LINK), makes("href", TAG_LINK), makeb("required", TAG_INPUT, TAG_SELECT, TAG_TEXTAREA), makeb("novalidate", "noValidate", TAG_FORM), makes("rel", TAG_A, TAG_LINK, TAG_LABEL), makes("for", "htmlFor", TAG_LABEL), makeul("cols", TAG_TEXTAREA, 20u32), makeul("rows", TAG_TEXTAREA, 1u32), # func jsForm(this: HTMLInputElement): HTMLFormElement {.jsfget: "form".} = return this.form #