about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bindings/quickjs.nim3
-rw-r--r--src/display/client.nim24
-rw-r--r--src/html/env.nim2
-rw-r--r--src/js/javascript.nim32
-rw-r--r--src/js/module.nim23
5 files changed, 81 insertions, 3 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index ed2cc15d..97aefcc7 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -350,6 +350,7 @@ proc JS_NewFloat64*(ctx: JSContext, val: cdouble): JSValue
 proc JS_NewAtomLen*(ctx: JSContext, str: cstring, len: csize_t): JSAtom
 proc JS_ValueToAtom*(ctx: JSContext, val: JSValue): JSAtom
 proc JS_AtomToValue*(ctx: JSContext, atom: JSAtom): JSValue
+proc JS_AtomToCString*(ctx: JSContext, atom: JSAtom): cstring
 proc JS_FreeAtom*(ctx: JSContext, atom: JSAtom)
 proc JS_FreeAtomRT*(rt: JSRuntime, atom: JSAtom)
 
@@ -432,6 +433,8 @@ proc JS_ThrowInternalError*(ctx: JSContext, fmt: cstring): JSValue {.varargs, di
 proc JS_SetModuleLoaderFunc*(rt: JSRuntime,
   module_normalize: JSModuleNormalizeFunc, module_loader: JSModuleLoaderFunc,
   opaque: pointer)
+proc JS_GetImportMeta*(ctx: JSContext, m: JSModuleDef): JSValue
+proc JS_GetModuleName*(ctx: JSContext, m: JSModuleDef): JSAtom
 
 proc JS_EnqueueJob*(ctx: JSContext, job_func: JSJobFunc, argc: cint, argv: ptr JSValue): cint
 proc JS_IsJobPending*(rt: JSRuntime): JS_BOOL
diff --git a/src/display/client.nim b/src/display/client.nim
index de09885c..d705eab1 100644
--- a/src/display/client.nim
+++ b/src/display/client.nim
@@ -4,6 +4,7 @@ import options
 import os
 import selectors
 import streams
+import cstrutils
 import tables
 import terminal
 
@@ -31,6 +32,7 @@ import ips/serialize
 import ips/serversocket
 import ips/socketstream
 import js/javascript
+import js/module
 import js/timeout
 import types/cookie
 import types/dispatcher
@@ -377,6 +379,27 @@ proc headlessLoop(client: Client) =
         client.runJSJobs()
     client.acceptBuffers()
 
+proc clientLoadJSModule(ctx: JSContext, module_name: cstring,
+    opaque: pointer): JSModuleDef {.cdecl.} =
+  let global = JS_GetGlobalObject(ctx)
+  JS_FreeValue(ctx, global)
+  var x: Option[URL]
+  if module_name.startsWith("/") or module_name.startsWith("./") or
+      module_name.startsWith("../"):
+    let cur = getCurrentDir()
+    x = parseURL($module_name, parseURL("file://" & cur))
+  else:
+    x = parseURL($module_name)
+  if x.isNone or x.get.scheme != "file":
+    JS_ThrowTypeError(ctx, "Invalid URL: %s", module_name)
+    return nil
+  try:
+    let f = readFile($x.get.path)
+    return finishLoadModule(ctx, f, module_name)
+  except IOError:
+    JS_ThrowTypeError(ctx, "Failed to open file %s", module_name)
+    return nil
+
 #TODO this is dumb
 proc readFile(client: Client, path: string): string {.jsfunc.} =
   try:
@@ -496,6 +519,7 @@ proc newClient*(config: Config, dispatcher: Dispatcher): Client =
   result.loader = dispatcher.forkserver.newFileLoader()
   result.jsrt = newJSRuntime()
   result.jsrt.setInterruptHandler(interruptHandler, cast[pointer](result))
+  JS_SetModuleLoaderFunc(result.jsrt, nil, clientLoadJSModule, nil)
   let ctx = result.jsrt.newJSContext()
   result.jsctx = ctx
   result.pager = newPager(config, result.attrs, dispatcher, ctx)
diff --git a/src/html/env.nim b/src/html/env.nim
index bda8c2c2..544f5083 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -95,7 +95,7 @@ proc addScripting*(window: Window, selector: Selector[int]) =
   var global = JS_GetGlobalObject(ctx)
   ctx.registerType(Window, asglobal = true)
   ctx.setOpaque(global, window)
-  ctx.setProperty(global, "window", global)
+  ctx.defineProperty(global, "window", global)
   JS_FreeValue(ctx, global)
   ctx.addconsoleModule()
   ctx.addNavigatorModule()
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index c6459956..18ff3850 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -218,6 +218,9 @@ func getOpaque*(ctx: JSContext, val: JSValue, class: string): pointer =
     return opaque
   return getOpaque0(val)
 
+func getOpaque*[T](ctx: JSContext, val: JSValue): pointer =
+  getOpaque(ctx, val, $T)
+
 proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer = nil) =
   JS_SetInterruptHandler(rt, cb, opaque)
 
@@ -270,6 +273,23 @@ proc setProperty*(ctx: JSContext, val: JSValue, name: string, prop: JSValue) =
 proc setProperty*(ctx: JSContext, val: JSValue, name: string, fun: JSCFunction, argc: int = 0) =
   ctx.setProperty(val, name, ctx.newJSCFunction(name, fun, argc))
 
+proc defineProperty*[T](ctx: JSContext, this: JSValue, name: string,
+    prop: T) =
+  when T is JSValue:
+    if JS_DefinePropertyValueStr(ctx, this, cstring(name), prop, cint(0)) <= 0:
+      raise newException(Defect, "Failed to define property string: " & name)
+  else:
+    defineProperty(ctx, this, name, toJS(ctx, prop))
+
+proc definePropertyCWE*[T](ctx: JSContext, this: JSValue, name: string,
+    prop: T) =
+  when T is JSValue:
+    if JS_DefinePropertyValueStr(ctx, this, cstring(name), prop,
+        JS_PROP_C_W_E) <= 0:
+      raise newException(Defect, "Failed to define property string: " & name)
+  else:
+    definePropertyCWE(ctx, this, name, toJS(ctx, prop))
+
 func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
                  ctor: JSCFunction, funcs: JSFunctionList, nimt: pointer,
                  parent: JSClassID, asglobal: bool, nointerface: bool,
@@ -315,7 +335,7 @@ func newJSClass*(ctx: JSContext, cdef: JSClassDefConst, tname: string,
   ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor)
   if not nointerface:
     let global = JS_GetGlobalObject(ctx)
-    ctx.setProperty(global, $cdef.class_name, jctor)
+    ctx.defineProperty(global, $cdef.class_name, jctor)
     JS_FreeValue(ctx, global)
 
 type FuncParam = tuple[name: string, t: NimNode, val: Option[NimNode], generic: Option[NimNode]]
@@ -553,6 +573,7 @@ proc fromJSTable[A, B](ctx: JSContext, val: JSValue): Option[Table[A, B]] =
       return none(Table[A, B])
     result.get[kn.get] = vn.get
 
+proc toJS*(ctx: JSContext, s: cstring): JSValue
 proc toJS*(ctx: JSContext, s: string): JSValue
 proc toJS(ctx: JSContext, r: Rune): JSValue
 proc toJS(ctx: JSContext, n: int64): JSValue
@@ -694,8 +715,11 @@ func fromJS[T: string|uint32](ctx: JSContext, atom: JSAtom): Option[T] =
 proc getJSFunction*[T, U](ctx: JSContext, val: JSValue): Option[(proc(x: T): Option[U])] =
   return fromJS[(proc(x: T): Option[U])](ctx, val)
 
+proc toJS*(ctx: JSContext, s: cstring): JSValue =
+  return JS_NewString(ctx, s)
+
 proc toJS*(ctx: JSContext, s: string): JSValue =
-  return JS_NewString(ctx, cstring(s))
+  return toJS(ctx, cstring(s))
 
 proc toJS(ctx: JSContext, r: Rune): JSValue =
   return toJS(ctx, $r)
@@ -1765,3 +1789,7 @@ binary objects: {m.binary_object_count} {m.binary_object_size}"""
 
 proc eval*(ctx: JSContext, s: string, file: string, eval_flags: int): JSValue =
   return JS_Eval(ctx, cstring(s), cint(s.len), cstring(file), cint(eval_flags))
+
+proc compileModule*(ctx: JSContext, s: string, file: cstring): JSValue =
+  return JS_Eval(ctx, cstring(s), cint(s.len), file,
+    cint(JS_EVAL_TYPE_MODULE or JS_EVAL_FLAG_COMPILE_ONLY))
diff --git a/src/js/module.nim b/src/js/module.nim
new file mode 100644
index 00000000..adc9830d
--- /dev/null
+++ b/src/js/module.nim
@@ -0,0 +1,23 @@
+import bindings/quickjs
+import js/javascript
+
+proc setImportMeta(ctx: JSContext, funcVal: JSValue, isMain: bool) =
+  let m = cast[JSModuleDef](JS_VALUE_GET_PTR(func_val))
+  let moduleNameAtom = JS_GetModuleName(ctx, m)
+  let moduleName = JS_AtomToCString(ctx, moduleNameAtom)
+  let metaObj = JS_GetImportMeta(ctx, m)
+  definePropertyCWE(ctx, metaObj, "url", moduleName)
+  definePropertyCWE(ctx, metaObj, "main", isMain)
+  JS_FreeValue(ctx, metaObj)
+  JS_FreeAtom(ctx, moduleNameAtom)
+  JS_FreeCString(ctx, moduleName)
+
+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)