diff options
author | bptato <nincsnevem662@gmail.com> | 2025-01-31 23:35:19 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-02-01 00:14:03 +0100 |
commit | fb83af595b803c1d2034a70437381c8550eae3bf (patch) | |
tree | f2c643b6c22b9aabf890f47ddeff6154e6086106 /src | |
parent | 597d63922c5614da4f07bbddd4a5866c48cad555 (diff) | |
download | chawan-fb83af595b803c1d2034a70437381c8550eae3bf.tar.gz |
dom: basic module support
probably breaks with TLA
Diffstat (limited to 'src')
-rw-r--r-- | src/html/dom.nim | 41 | ||||
-rw-r--r-- | src/html/env.nim | 28 | ||||
-rw-r--r-- | src/html/script.nim | 24 | ||||
-rw-r--r-- | src/local/pager.nim | 52 |
4 files changed, 97 insertions, 48 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index 3ffaeb65..83e38afb 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -126,6 +126,7 @@ type referrer* {.jsget.}: string maybeRestyle*: proc(element: Element) performance* {.jsget.}: Performance + currentModuleURL*: URL # Navigator stuff Navigator* = object @@ -5148,15 +5149,32 @@ proc fetchExternalModuleGraph(element: HTMLScriptElement; url: URL; element.fetchDescendantsAndLink(res.script, rdScript, onComplete) ) +proc logException(window: Window; url: URL) = + #TODO excludepassword seems pointless? + window.console.error("Exception in document", + url.serialize(excludepassword = true), window.jsctx.getExceptionMsg()) + proc fetchDescendantsAndLink(element: HTMLScriptElement; script: Script; destination: RequestDestination; onComplete: OnCompleteProc) = - discard + #TODO ummm... + let window = element.document.window + let ctx = window.jsctx + if JS_ResolveModule(ctx, script.record) < 0: + window.logException(script.baseURL) + return + ctx.setImportMeta(script.record, true) + #TODO I think record can be a promise with TLA, and then this doesn't + # work at all + let res = JS_EvalFunction(ctx, script.record) + if JS_IsException(res): + window.logException(script.baseURL) + return + JS_FreeValue(ctx, res) #TODO settings object proc fetchSingleModule(element: HTMLScriptElement; url: URL; destination: RequestDestination; options: ScriptOptions, referrer: URL; isTopLevel: bool; onComplete: OnCompleteProc) = - discard #TODO implement let moduleType = "javascript" #TODO moduleRequest let window = element.document.window @@ -5206,12 +5224,17 @@ proc fetchSingleModule(element: HTMLScriptElement; url: URL; element.onComplete(res) return if contentType.isJavaScriptType(): - let res = ctx.newJSModuleScript(s.get, element.document.baseURL, - options) - if referrerPolicy.isSome: - res.script.options.referrerPolicy = referrerPolicy - settings.moduleMap.set(url, moduleType, res, ctx) - element.onComplete(res) + window.currentModuleURL = url + let res = ctx.newJSModuleScript(s.get, url, options) + #TODO can't we just return null from newJSModuleScript? + if JS_IsException(res.script.record): + window.logException(res.script.baseURL) + element.onComplete(ScriptResult(t: srtNull)) + else: + if referrerPolicy.isSome: + res.script.options.referrerPolicy = referrerPolicy + settings.moduleMap.set(url, moduleType, res, ctx) + element.onComplete(res) else: #TODO non-JS modules discard @@ -5344,7 +5367,7 @@ proc prepare*(element: HTMLScriptElement) = if element.ctype == stClassic: element.fetchClassicScript(url.get, options, classicCORS, encoding, markAsReady) - else: + else: # stModule element.fetchExternalModuleGraph(url.get, options, markAsReady) else: let baseURL = element.document.baseURL diff --git a/src/html/env.nim b/src/html/env.nim index 3cef4c48..d140bd44 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -1,3 +1,4 @@ +import std/strutils import std/tables import css/cssparser @@ -338,6 +339,32 @@ proc setOnLoad(ctx: JSContext; window: Window; val: JSValue) doAssert ctx.addEventListener(window, window.toAtom(satLoad), val).isSome JS_FreeValue(ctx, this) +proc loadJSModule(ctx: JSContext; moduleName: cstringConst; opaque: pointer): + JSModuleDef {.cdecl.} = + let window = ctx.getWindow() + #TODO I suspect this doesn't work with dynamically loaded modules? + # at least we'd have to set currentModuleURL before every script + # execution... + let url = window.currentModuleURL + var x = none(URL) + let moduleName = $moduleName + if url != nil and + (moduleName.startsWith("/") or moduleName.startsWith("./") or + moduleName.startsWith("../")): + x = parseURL($moduleName, some(url)) + if x.isNone or not x.get.origin.isSameOrigin(url.origin): + JS_ThrowTypeError(ctx, "Invalid URL: %s", cstring(moduleName)) + return nil + let request = newRequest(x.get) + let response = window.loader.doRequest(request) + if response.res != 0: + JS_ThrowTypeError(ctx, "Failed to load module %s", cstring(moduleName)) + return nil + response.resume() + let source = response.body.recvAll() + response.close() + return ctx.finishLoadModule(source, moduleName) + proc addWindowModule*(ctx: JSContext) = ctx.addEventModule() let eventTargetCID = ctx.getClass("EventTarget") @@ -371,6 +398,7 @@ proc addScripting*(window: Window) = doAssert JS_DeleteProperty(ctx, jsWindow, performance, 0) == 1 JS_FreeValue(ctx, jsWindow) JS_FreeAtom(ctx, performance) + JS_SetModuleLoaderFunc(rt, normalizeModuleName, loadJSModule, nil) window.performance = newPerformance(window.settings.scripting) if window.settings.scripting == smApp: window.scriptAttrsp = window.attrsp diff --git a/src/html/script.nim b/src/html/script.nim index ac2924ee..84f6c553 100644 --- a/src/html/script.nim +++ b/src/html/script.nim @@ -1,6 +1,7 @@ import monoucha/javascript import monoucha/jsopaque import monoucha/quickjs +import monoucha/tojs import types/referrer import types/url import utils/twtstr @@ -141,6 +142,29 @@ proc newJSModuleScript*(ctx: JSContext; source: string; baseURL: URL; ) ) +proc setImportMeta*(ctx: JSContext; funcVal: JSValue; isMain: bool) = + let m = cast[JSModuleDef](JS_VALUE_GET_PTR(funcVal)) + let moduleNameAtom = JS_GetModuleName(ctx, m) + let metaObj = JS_GetImportMeta(ctx, m) + definePropertyCWE(ctx, metaObj, "url", JS_AtomToValue(ctx, moduleNameAtom)) + definePropertyCWE(ctx, metaObj, "main", false) + JS_FreeValue(ctx, metaObj) + JS_FreeAtom(ctx, moduleNameAtom) + +proc normalizeModuleName*(ctx: JSContext; base_name, name: cstringConst; + opaque: pointer): cstring {.cdecl.} = + return js_strdup(ctx, cstring(name)) + +proc finishLoadModule*(ctx: JSContext; source, name: string): JSModuleDef = + let funcVal = compileModule(ctx, source, name) + if JS_IsException(funcVal): + return nil + ctx.setImportMeta(funcVal, false) + # "the module is already referenced, so we must free it" + # idk how this works, so for now let's just do what qjs does + result = cast[JSModuleDef](JS_VALUE_GET_PTR(funcVal)) + JS_FreeValue(ctx, funcVal) + proc logException*(ctx: JSContext) = ctx.errorImpl(ctx.getExceptionMsg()) diff --git a/src/local/pager.nim b/src/local/pager.nim index d3382db9..388818f5 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -16,6 +16,7 @@ import config/history import config/mailcap import config/mimetypes import css/render +import html/script import io/bufreader import io/bufwriter import io/console @@ -390,49 +391,23 @@ proc gotoLine(ctx: JSContext; pager: Pager; val = JS_UNDEFINED): Opt[void] pager.container.gotoLine(s) return ok() -proc setImportMeta(ctx: JSContext; funcVal: JSValue; isMain: bool) = - let m = cast[JSModuleDef](JS_VALUE_GET_PTR(funcVal)) - let moduleNameAtom = JS_GetModuleName(ctx, m) - let metaObj = JS_GetImportMeta(ctx, m) - definePropertyCWE(ctx, metaObj, "url", JS_AtomToValue(ctx, moduleNameAtom)) - definePropertyCWE(ctx, metaObj, "main", isMain) - JS_FreeValue(ctx, metaObj) - JS_FreeAtom(ctx, moduleNameAtom) - -proc finishLoadModule(ctx: JSContext; f: string; name: cstring): JSModuleDef = - let funcVal = compileModule(ctx, f, $name) - if JS_IsException(funcVal): - return nil - setImportMeta(ctx, funcVal, false) - # "the module is already referenced, so we must free it" - # idk how this works, so for now let's just do what qjs does - result = cast[JSModuleDef](JS_VALUE_GET_PTR(funcVal)) - JS_FreeValue(ctx, funcVal) - -proc normalizeModuleName(ctx: JSContext; base_name, name: cstringConst; - opaque: pointer): cstring {.cdecl.} = - return js_strdup(ctx, cstring(name)) - -proc clientLoadJSModule(ctx: JSContext; module_name: cstringConst; - opaque: pointer): JSModuleDef {.cdecl.} = - let global = JS_GetGlobalObject(ctx) - JS_FreeValue(ctx, global) - var x: Option[URL] - if module_name[0] == '/' or module_name[0] == '.' and - (module_name[1] == '/' or - module_name[1] == '.' and module_name[2] == '/'): +proc loadJSModule(ctx: JSContext; moduleName: cstringConst; opaque: pointer): + JSModuleDef {.cdecl.} = + let moduleName = $moduleName + let x = if moduleName.startsWith("/") or moduleName.startsWith("./") or + moduleName.startsWith("../"): let cur = getCurrentDir() - x = parseURL($module_name, parseURL("file://" & cur & "/")) + parseURL(moduleName, parseURL("file://" & cur & "/")) else: - x = parseURL($module_name) + parseURL(moduleName) if x.isNone or x.get.scheme != "file": - JS_ThrowTypeError(ctx, "Invalid URL: %s", module_name) + JS_ThrowTypeError(ctx, "Invalid URL: %s", cstring(moduleName)) return nil try: - let f = readFile(x.get.pathname) - return finishLoadModule(ctx, f, cstring(module_name)) + let source = readFile(x.get.pathname) + return ctx.finishLoadModule(source, moduleName) except IOError: - JS_ThrowTypeError(ctx, "Failed to open file %s", module_name) + JS_ThrowTypeError(ctx, "Failed to open file %s", cstring(moduleName)) return nil proc interruptHandler(rt: JSRuntime; opaque: pointer): cint {.cdecl.} = @@ -469,8 +444,7 @@ proc newPager*(config: Config; forkserver: ForkServer; ctx: JSContext; cookieJars: newCookieJarMap() ) pager.timeouts = newTimeoutState(pager.jsctx, evalJSFree, pager) - JS_SetModuleLoaderFunc(pager.jsrt, normalizeModuleName, clientLoadJSModule, - nil) + JS_SetModuleLoaderFunc(pager.jsrt, normalizeModuleName, loadJSModule, nil) JS_SetInterruptHandler(pager.jsrt, interruptHandler, cast[pointer](pager)) let clientConfig = LoaderClientConfig( defaultHeaders: newHeaders(pager.config.network.defaultHeaders), |