import std/selectors import bindings/quickjs import html/catom import html/chadombuilder import html/dom import html/event import html/formdata import html/script import html/xmlhttprequest import io/filestream import io/promise import js/base64 import js/console import js/domexception import js/encoding import js/jserror import js/intl import js/javascript import js/jstypes import js/timeout import js/tojs import loader/headers import loader/loader import loader/request import loader/response import types/blob import types/url import types/winattrs # NavigatorID proc appCodeName(navigator: var Navigator): string {.jsfget.} = "Mozilla" proc appName(navigator: var Navigator): string {.jsfget.} = "Netscape" proc appVersion(navigator: var Navigator): string {.jsfget.} = "5.0 (Windows)" proc platform(navigator: var Navigator): string {.jsfget.} = "Win32" proc product(navigator: var Navigator): string {.jsfget.} = "Gecko" proc productSub(navigator: var Navigator): string {.jsfget.} = "20100101" proc userAgent(navigator: var Navigator): string {.jsfget.} = #TODO TODO TODO this should be configurable "chawan" proc vendor(navigator: var Navigator): string {.jsfget.} = "" proc vendorSub(navigator: var Navigator): string {.jsfget.} = "" proc taintEnabled(navigator: var Navigator): bool {.jsfget.} = false proc oscpu(navigator: var Navigator): string {.jsfget.} = "Windows NT 10.0" # NavigatorLanguage proc language(navigator: var Navigator): string {.jsfget.} = "en-US" proc languages(navigator: var Navigator): seq[string] {.jsfget.} = @["en-US"] #TODO frozen array? # NavigatorOnline proc onLine(navigator: var Navigator): bool {.jsfget.} = true # at the very least, the terminal is on-line :) #TODO NavigatorContentUtils # NavigatorCookies # "this website needs cookies to be enabled to function correctly" # It's probably better to lie here. proc cookieEnabled(navigator: var Navigator): bool {.jsfget.} = true # NavigatorPlugins proc pdfViewerEnabled(navigator: var Navigator): bool {.jsfget.} = false proc javaEnabled(navigator: var Navigator): bool {.jsfunc.} = false proc namedItem(pluginArray: var PluginArray): string {.jsfunc.} = "" proc namedItem(mimeTypeArray: var MimeTypeArray): string {.jsfunc.} = "" proc item(pluginArray: var PluginArray): JSValue {.jsfunc.} = JS_NULL proc length(pluginArray: var PluginArray): uint32 {.jsfget.} = 0 proc item(mimeTypeArray: var MimeTypeArray): JSValue {.jsfunc.} = JS_NULL proc length(mimeTypeArray: var MimeTypeArray): uint32 {.jsfget.} = 0 proc getter(pluginArray: var PluginArray; i: uint32): Option[JSValue] {.jsgetprop.} = discard proc getter(mimeTypeArray: var MimeTypeArray; i: uint32): Option[JSValue] {.jsgetprop.} = discard # Screen proc availWidth(screen: var Screen): int64 {.jsfget.} = #TODO this is a fingerprinting vector, but users should be able to allow it # selectively # for now just return something standard-ish 80 * 9 proc availHeight(screen: var Screen): int64 {.jsfget.} = #TODO see above 24 * 18 proc width(screen: var Screen): int64 {.jsfget.} = screen.availWidth proc height(screen: var Screen): int64 {.jsfget.} = screen.availHeight proc colorDepth(screen: var Screen): int64 {.jsfget.} = 24 proc pixelDepth(screen: var Screen): int64 {.jsfget.} = screen.colorDepth proc addNavigatorModule*(ctx: JSContext) = ctx.registerType(Navigator) ctx.registerType(PluginArray) ctx.registerType(MimeTypeArray) ctx.registerType(Screen) proc fetch[T: JSRequest|string](window: Window; input: T; init = none(RequestInit)): JSResult[FetchPromise] {.jsfunc.} = if window.loader.isSome: let input = ?newRequest(window.jsctx, input, init) #TODO cors requests? if not window.settings.origin.isSameOrigin(input.request.url.origin): let promise = FetchPromise() let err = newTypeError("NetworkError when attempting to fetch resource") promise.resolve(JSResult[Response].err(err)) return ok(promise) return ok(window.loader.get.fetch(input.request)) return ok(nil) proc setTimeout[T: JSValue|string](window: Window; handler: T; timeout = 0i32): int32 {.jsfunc.} = return window.timeouts.setTimeout(handler, timeout) proc setInterval[T: JSValue|string](window: Window; handler: T; interval = 0i32): int32 {.jsfunc.} = return window.timeouts.setInterval(handler, interval) proc clearTimeout(window: Window; id: int32) {.jsfunc.} = window.timeouts.clearTimeout(id) proc clearInterval(window: Window; id: int32) {.jsfunc.} = window.timeouts.clearInterval(id) func console(window: Window): Console {.jsfget.} = return window.internalConsole proc screenX(window: Window): int64 {.jsfget.} = 0 proc screenY(window: Window): int64 {.jsfget.} = 0 proc screenLeft(window: Window): int64 {.jsfget.} = 0 proc screenTop(window: Window): int64 {.jsfget.} = 0 proc outerWidth(window: Window): int64 {.jsfget.} = window.screen.availWidth proc outerHeight(window: Window): int64 {.jsfget.} = window.screen.availHeight proc devicePixelRatio(window: Window): float64 {.jsfget.} = 1 proc setLocation(window: Window; s: string): Err[JSError] {.jsfset: "location".} = window.document.setLocation(s) func getWindow(window: Window): Window {.jsuffget: "window".} = return window #TODO [Replaceable] func getSelf(window: Window): Window {.jsfget: "self".} = return window #TODO [Replaceable] func getFrames(window: Window): Window {.jsfget: "frames".} = return window func getTop(window: Window): Window {.jsuffget: "top".} = return window #TODO frames? #TODO [Replaceable] func getParent(window: Window): Window {.jsfget: "parent".} = return window #TODO frames? proc atob(window: Window; data: string): DOMResult[NarrowString] {.jsfunc.} = return atob(data) proc btoa(ctx: JSContext; window: Window; data: JSValue): DOMResult[string] {.jsfunc.} = return btoa(ctx, data) proc getComputedStyle(window: Window; element: Element; pseudoElt = none(Element)): JSResult[CSSStyleDeclaration] {.jsfunc.} = #TODO implement this properly return ok(element.style) proc setOnLoad(ctx: JSContext; window: Window; val: JSValue) {.jsfset: "onload".} = if JS_IsFunction(ctx, val): let this = ctx.toJS(window) ctx.definePropertyC(this, "onload", JS_DupValue(ctx, val)) #TODO I haven't checked but this might also be wrong doAssert ctx.addEventListener(window, "load", val).isSome JS_FreeValue(ctx, this) proc addWindowModule*(ctx: JSContext) = ctx.addEventModule() let eventTargetCID = ctx.getClass("EventTarget") ctx.registerType(Window, parent = eventTargetCID, asglobal = true) proc addWindowModule2*(ctx: JSContext) = ctx.addEventModule() let eventTargetCID = ctx.getClass("EventTarget") ctx.registerType(Window, parent = eventTargetCID, asglobal = true, globalparent = true) proc addScripting*(window: Window; selector: Selector[int]) = let rt = newJSRuntime() let ctx = rt.newJSContext() window.jsrt = rt window.jsctx = ctx window.importMapsAllowed = true window.timeouts = newTimeoutState( selector = selector, jsctx = ctx, err = window.console.err, evalJSFree = (proc(src, file: string) = let ret = window.jsctx.eval(src, file, JS_EVAL_TYPE_GLOBAL) if JS_IsException(ret): window.console.log("Exception in document", $window.document.url, window.jsctx.getExceptionMsg()) else: JS_FreeValue(ctx, ret) ) ) ctx.addWindowModule() ctx.setGlobal(window) ctx.addDOMExceptionModule() ctx.addConsoleModule() ctx.addNavigatorModule() ctx.addDOMModule() ctx.addURLModule() ctx.addHTMLModule() ctx.addIntlModule() ctx.addBlobModule() ctx.addFormDataModule() ctx.addXMLHttpRequestModule() ctx.addHeadersModule() ctx.addRequestModule() ctx.addResponseModule() ctx.addEncodingModule() proc runJSJobs*(window: Window) = while true: let r = window.jsrt.runJSJobs() if r.isSome: break let ctx = r.error ctx.writeException(window.console.err) proc newWindow*(scripting, images: bool; selector: Selector[int]; attrs: WindowAttributes; factory: CAtomFactory; navigate: proc(url: URL); loader: FileLoader; url: URL): Window = let err = newDynFileStream(stderr) let window = Window( attrs: attrs, internalConsole: newConsole(err), navigator: Navigator(), internalLoader: some(loader), images: images, settings: EnvironmentSettings( scripting: scripting, origin: url.origin ), navigate: navigate, factory: factory ) window.location = window.newLocation() if scripting: window.addScripting(selector) return window