about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/html/dom.nim41
-rw-r--r--src/html/env.nim28
-rw-r--r--src/html/script.nim24
-rw-r--r--src/local/pager.nim52
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),