about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/chapath.nim5
-rw-r--r--src/config/config.nim6
-rw-r--r--src/html/catom.nim9
-rw-r--r--src/html/dom.nim64
-rw-r--r--src/html/env.nim5
-rw-r--r--src/html/event.nim35
-rw-r--r--src/html/xmlhttprequest.nim20
-rw-r--r--src/io/bufwriter.nim9
-rw-r--r--src/io/promise.nim24
-rw-r--r--src/js/console.nim29
-rw-r--r--src/js/domexception.nim2
-rw-r--r--src/js/encoding.nim15
-rw-r--r--src/js/jscolor.nim34
-rw-r--r--src/js/timeout.nim6
-rw-r--r--src/loader/headers.nim26
-rw-r--r--src/loader/loaderhandle.nim9
-rw-r--r--src/loader/request.nim137
-rw-r--r--src/local/client.nim12
-rw-r--r--src/local/container.nim2
-rw-r--r--src/local/pager.nim64
-rw-r--r--src/server/buffer.nim5
-rw-r--r--src/types/blob.nim2
-rw-r--r--src/types/url.nim9
-rw-r--r--src/utils/luwrap.nim11
-rw-r--r--src/version.nim2
25 files changed, 276 insertions, 266 deletions
diff --git a/src/config/chapath.nim b/src/config/chapath.nim
index 8397cb3d..9878e09b 100644
--- a/src/config/chapath.nim
+++ b/src/config/chapath.nim
@@ -4,7 +4,6 @@ import std/posix
 
 import monoucha/fromjs
 import monoucha/javascript
-import monoucha/jserror
 import monoucha/tojs
 import types/opt
 import utils/twtstr
@@ -283,8 +282,8 @@ proc unquote(p: string): ChaPathResult[string] =
 proc toJS*(ctx: JSContext; p: ChaPath): JSValue =
   toJS(ctx, $p)
 
-proc fromJSChaPath*(ctx: JSContext; val: JSValue): JSResult[ChaPath] =
-  return cast[JSResult[ChaPath]](fromJS[string](ctx, val))
+proc fromJS*(ctx: JSContext; val: JSValue; res: var ChaPath): Opt[void] =
+  return ctx.fromJS(val, string(res))
 
 proc unquote*(p: ChaPath): ChaPathResult[string] =
   let s = ?unquote(string(p))
diff --git a/src/config/config.nim b/src/config/config.nim
index 3c733bc5..b0064aa2 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -13,7 +13,6 @@ import js/jscolor
 import loader/headers
 import monoucha/fromjs
 import monoucha/javascript
-import monoucha/jserror
 import monoucha/jspropenumlist
 import monoucha/jsregex
 import monoucha/jstypes
@@ -195,9 +194,8 @@ jsDestructor(Config)
 converter toStr*(p: ChaPathResolved): string {.inline.} =
   return string(p)
 
-proc fromJSChaPathResolved(ctx: JSContext; val: JSValue):
-    JSResult[ChaPathResolved] =
-  return cast[JSResult[ChaPathResolved]](fromJS[string](ctx, val))
+proc fromJS(ctx: JSContext; val: JSValue; res: var ChaPathResolved): Opt[void] =
+  return ctx.fromJS(val, string(res))
 
 proc `[]=`(a: var ActionMap; b, c: string) =
   a.t[b] = c
diff --git a/src/html/catom.nim b/src/html/catom.nim
index 0ff7b949..111abb8d 100644
--- a/src/html/catom.nim
+++ b/src/html/catom.nim
@@ -6,7 +6,6 @@ import std/strutils
 import chame/tags
 import monoucha/fromjs
 import monoucha/javascript
-import monoucha/jserror
 import monoucha/tojs
 import types/opt
 import utils/twtstr
@@ -207,9 +206,11 @@ proc toAtom*(ctx: JSContext; atom: StaticAtom): CAtom =
 proc toStaticAtom*(ctx: JSContext; atom: CAtom): StaticAtom =
   return ctx.getFactory().toStaticAtom(atom)
 
-proc fromJSCAtom*(ctx: JSContext; val: JSValue): JSResult[CAtom] =
-  let s = ?fromJS[string](ctx, val)
-  return ok(ctx.getFactory().toAtom(s))
+proc fromJS*(ctx: JSContext; val: JSValue; res: var CAtom): Opt[void] =
+  var s: string
+  ?ctx.fromJS(val, s)
+  res = ctx.getFactory().toAtom(s)
+  return ok()
 
 proc toJS*(ctx: JSContext; atom: CAtom): JSValue =
   return ctx.toJS(ctx.getFactory().toStr(atom))
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 950bdd7b..e58be7fc 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -908,13 +908,15 @@ proc reflectAttr(element: Element; name: CAtom; value: Option[string])
 # preparation for Worker support.)
 func getGlobal*(ctx: JSContext): Window =
   let global = JS_GetGlobalObject(ctx)
-  let window = fromJS[Window](ctx, global).get
+  var window: Window
+  assert ctx.fromJS(global, window).isSome
   JS_FreeValue(ctx, global)
   return window
 
 func getWindow*(ctx: JSContext): Window =
   let global = JS_GetGlobalObject(ctx)
-  let window = fromJS[Window](ctx, global).get
+  var window: Window
+  assert ctx.fromJS(global, window).isSome
   JS_FreeValue(ctx, global)
   return window
 
@@ -1481,12 +1483,15 @@ func namedItem(collection: HTMLCollection; s: string): Element {.jsfunc.} =
       return it
   return nil
 
-func getter[T: uint32|string](collection: HTMLCollection; u: T):
-    Option[Element] {.jsgetprop.} =
-  when T is uint32:
-    return option(collection.item(u))
-  else:
-    return option(collection.namedItem(u))
+func getter(ctx: JSContext; collection: HTMLCollection; atom: JSAtom):
+    Opt[Option[Element]] {.jsgetprop.} =
+  var u: uint32
+  if ctx.fromJS(atom, u).isSome:
+    return ok(option(collection.item(u)))
+  var s: string
+  if ctx.fromJS(atom, s).isSome:
+    return ok(option(collection.namedItem(s)))
+  return err()
 
 func names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList
     {.jspropnames.} =
@@ -2622,7 +2627,7 @@ func newText*(document: Document; data: string): Text =
   )
 
 func newText(ctx: JSContext; data = ""): Text {.jsctor.} =
-  let window = ctx.getGlobalOpaque(Window).get
+  let window = ctx.getGlobal()
   return window.document.newText(data)
 
 func newCDATASection(document: Document; data: string): CDATASection =
@@ -2645,7 +2650,7 @@ func newDocumentFragment(document: Document): DocumentFragment =
   return DocumentFragment(internalDocument: document, index: -1)
 
 func newDocumentFragment(ctx: JSContext): DocumentFragment {.jsctor.} =
-  let window = ctx.getGlobalOpaque(Window).get
+  let window = ctx.getGlobal()
   return window.document.newDocumentFragment()
 
 func newComment(document: Document; data: string): Comment =
@@ -2656,7 +2661,7 @@ func newComment(document: Document; data: string): Comment =
   )
 
 func newComment(ctx: JSContext; data: string = ""): Comment {.jsctor.} =
-  let window = ctx.getGlobalOpaque(Window).get
+  let window = ctx.getGlobal()
   return window.document.newComment(data)
 
 #TODO custom elements
@@ -4288,7 +4293,7 @@ const (ReflectTable, TagReflectMap, ReflectAllStartIndex) = (func(): (
 proc jsReflectGet(ctx: JSContext; this: JSValue; magic: cint): JSValue
     {.cdecl.} =
   let entry = ReflectTable[uint16(magic)]
-  let op = getOpaque0(this)
+  let op = this.getOpaque()
   if unlikely(not ctx.isInstanceOf(this, "Element") or op == nil):
     return JS_ThrowTypeError(ctx,
       "Reflected getter called on a value that is not an element")
@@ -4320,40 +4325,41 @@ proc jsReflectSet(ctx: JSContext; this, val: JSValue; magic: cint): JSValue
     return JS_ThrowTypeError(ctx,
       "Reflected getter called on a value that is not an element")
   let entry = ReflectTable[uint16(magic)]
-  let op = getOpaque0(this)
+  let op = this.getOpaque()
   assert op != nil
   let element = cast[Element](op)
   if element.tagType notin entry.tags:
     return JS_ThrowTypeError(ctx, "Invalid tag type %s", element.tagType)
   case entry.t
   of rtStr:
-    let x = fromJS[string](ctx, val)
-    if x.isSome:
-      element.attr(entry.attrname, x.get)
+    var x: string
+    if ctx.fromJS(val, x).isSome:
+      element.attr(entry.attrname, x)
   of rtBool:
-    let x = fromJS[bool](ctx, val)
-    if x.isSome:
-      if x.get:
+    var x: bool
+    if ctx.fromJS(val, x).isSome:
+      if x:
         element.attr(entry.attrname, "")
       else:
         let i = element.findAttr(entry.attrname)
         if i != -1:
           element.delAttr(i)
   of rtLong:
-    let x = fromJS[int32](ctx, val)
-    if x.isSome:
-      element.attrl(entry.attrname, x.get)
+    var x: int32
+    if ctx.fromJS(val, x).isSome:
+      element.attrl(entry.attrname, x)
   of rtUlong:
-    let x = fromJS[uint32](ctx, val)
-    if x.isSome:
-      element.attrul(entry.attrname, x.get)
+    var x: uint32
+    if ctx.fromJS(val, x).isSome:
+      element.attrul(entry.attrname, x)
   of rtUlongGz:
-    let x = fromJS[uint32](ctx, val)
-    if x.isSome:
-      element.attrulgz(entry.attrname, x.get)
+    var x: uint32
+    if ctx.fromJS(val, x).isSome:
+      element.attrulgz(entry.attrname, x)
   of rtFunction:
     if JS_IsFunction(ctx, val):
-      let target = fromJS[EventTarget](ctx, this).get
+      var target: EventTarget
+      assert ctx.fromJS(this, target).isSome
       ctx.definePropertyC(this, $entry.attrname, JS_DupValue(ctx, val))
       #TODO I haven't checked but this might also be wrong
       let ctype = ctx.getGlobal().toAtom(entry.ctype)
diff --git a/src/html/env.nim b/src/html/env.nim
index fed27096..4ecdc08b 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -166,8 +166,9 @@ proc addNavigatorModule*(ctx: JSContext) =
   ctx.registerType(History)
   ctx.registerType(Storage)
 
-proc fetch(window: Window; input: JSValue; init = none(RequestInit)):
-    JSResult[FetchPromise] {.jsfunc.} =
+proc fetch(window: Window; input: JSValue;
+    init = RequestInit(window: JS_UNDEFINED)): JSResult[FetchPromise]
+    {.jsfunc.} =
   let input = ?newRequest(window.jsctx, input, init)
   #TODO cors requests?
   if not window.settings.origin.isSameOrigin(input.request.url.origin):
diff --git a/src/html/event.nim b/src/html/event.nim
index 67131e63..732af7bc 100644
--- a/src/html/event.nim
+++ b/src/html/event.nim
@@ -68,17 +68,17 @@ jsDestructor(EventTarget)
 # Forward declaration hack
 var isDefaultPassive*: proc(target: EventTarget): bool {.nimcall,
   noSideEffect.} = nil
-var getParent*: proc(ctx: JSContext; target: EventTarget, event: Event):
+var getParent*: proc(ctx: JSContext; target: EventTarget; event: Event):
   EventTarget {.nimcall.}
 
 type
   EventInit* = object of JSDict
-    bubbles: bool
-    cancelable: bool
-    composed: bool
+    bubbles* {.jsdefault.}: bool
+    cancelable* {.jsdefault.}: bool
+    composed* {.jsdefault.}: bool
 
   CustomEventInit = object of EventInit
-    detail: JSValue
+    detail* {.jsdefault: JS_NULL.}: JSValue
 
 # Event
 proc innerEventCreationSteps*(event: Event; eventInitDict: EventInit) =
@@ -161,11 +161,11 @@ func composed(this: Event): bool {.jsfget.} =
   return efComposed in this.flags
 
 # CustomEvent
-proc newCustomEvent(ctype: CAtom; eventInitDict = CustomEventInit()):
-    JSResult[CustomEvent] {.jsctor.} =
+proc newCustomEvent(ctx: JSContext; ctype: CAtom;
+    eventInitDict = CustomEventInit()): JSResult[CustomEvent] {.jsctor.} =
   let event = CustomEvent()
   event.innerEventCreationSteps(eventInitDict)
-  event.detail = eventInitDict.detail
+  event.detail = JS_DupValue(ctx, eventInitDict.detail)
   event.ctype = ctype
   return ok(event)
 
@@ -240,12 +240,13 @@ proc removeAnEventListener(eventTarget: EventTarget; ctx: JSContext; i: int) =
   eventTarget.eventListeners.delete(i)
 
 proc flatten(ctx: JSContext; options: JSValue): bool =
+  result = false
   if JS_IsBool(options):
-    return fromJS[bool](ctx, options).get(false)
+    discard ctx.fromJS(options, result)
   if JS_IsObject(options):
     let x = JS_GetPropertyStr(ctx, options, "capture")
-    return fromJS[bool](ctx, x).get(false)
-  return false
+    discard ctx.fromJS(x, result)
+    JS_FreeValue(ctx, x)
 
 proc flattenMore(ctx: JSContext; options: JSValue):
     tuple[
@@ -254,20 +255,18 @@ proc flattenMore(ctx: JSContext; options: JSValue):
       passive: Option[bool]
       #TODO signals
     ] =
-  if JS_IsUndefined(options):
-    return
   let capture = flatten(ctx, options)
   var once = false
-  var passive: Option[bool]
+  var passive = none(bool)
   if JS_IsObject(options):
     let jsOnce = JS_GetPropertyStr(ctx, options, "once")
-    once = fromJS[bool](ctx, jsOnce).get(false)
+    discard ctx.fromJS(jsOnce, once)
     JS_FreeValue(ctx, jsOnce)
     let jsPassive = JS_GetPropertyStr(ctx, options, "passive")
-    let x = fromJS[bool](ctx, jsPassive)
+    var x: bool
+    if ctx.fromJS(jsPassive, x).isSome:
+      passive = some(x)
     JS_FreeValue(ctx, jsPassive)
-    if x.isSome:
-      passive = some(x.get)
   return (capture, once, passive)
 
 proc addEventListener*(ctx: JSContext; eventTarget: EventTarget; ctype: CAtom;
diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim
index c402f6b4..16b6e012 100644
--- a/src/html/xmlhttprequest.nim
+++ b/src/html/xmlhttprequest.nim
@@ -2,6 +2,7 @@ import std/options
 import std/strutils
 import std/tables
 
+import chagashi/decoder
 import html/catom
 import html/dom
 import html/event
@@ -169,7 +170,8 @@ proc fireProgressEvent(window: Window; target: EventTarget; name: StaticAtom;
 
 # Forward declaration hack
 var windowFetch*: proc(window: Window; input: JSValue;
-  init = none(RequestInit)): JSResult[FetchPromise] {.nimcall.} = nil
+  init = RequestInit(window: JS_UNDEFINED)): JSResult[FetchPromise]
+  {.nimcall.} = nil
 
 proc errorSteps(window: Window; this: XMLHttpRequest; name: StaticAtom) =
   this.readyState = xhrsDone
@@ -211,15 +213,16 @@ proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): DOMResult[void]
     body = JS_NULL
   let request = newRequest(this.requestURL, this.requestMethod, this.headers)
   if not JS_IsNull(body):
-    let document = fromJS[Document](ctx, body)
-    if document.isSome:
-      #TODO replace surrogates
-      let document = document.get
-      request.body = RequestBody(t: rbtString, s: document.serializeFragment())
+    var document: Document = nil
+    if ctx.fromJS(body, document).isSome:
+      request.body = RequestBody(
+        t: rbtString,
+        s: document.serializeFragment().toValidUTF8() # replace surrogates
+      )
     #TODO else...
     if "Content-Type" in this.headers:
       request.headers["Content-Type"].setContentTypeAttr("charset", "UTF-8")
-    elif document.isSome:
+    elif document != nil:
       request.headers["Content-Type"] = "text/html;charset=UTF-8"
   let jsRequest = JSRequest(
     #TODO unsafe request flag, client, cors credentials mode,
@@ -326,7 +329,8 @@ proc jsReflectSet(ctx: JSContext; this, val: JSValue; magic: cint): JSValue
     {.cdecl.} =
   if JS_IsFunction(ctx, val):
     let atom = ReflectMap[magic]
-    let target = fromJS[EventTarget](ctx, this).get
+    var target: EventTarget
+    assert ctx.fromJS(this, target).isSome
     ctx.definePropertyC(this, "on" & $atom, JS_DupValue(ctx, val))
     #TODO I haven't checked but this might also be wrong
     doAssert ctx.addEventListener(target, ctx.toAtom(atom), val).isSome
diff --git a/src/io/bufwriter.nim b/src/io/bufwriter.nim
index 437fa24f..241d77cc 100644
--- a/src/io/bufwriter.nim
+++ b/src/io/bufwriter.nim
@@ -21,11 +21,10 @@ type BufferedWriter* = object
   bufLen: int
   sendAux: seq[FileHandle]
 
-{.warning[Deprecated]: off.}:
-  proc `=destroy`(writer: var BufferedWriter) =
-    if writer.buffer != nil:
-      dealloc(writer.buffer)
-      writer.buffer = nil
+proc `=destroy`(writer: var BufferedWriter) =
+  if writer.buffer != nil:
+    dealloc(writer.buffer)
+    writer.buffer = nil
 
 proc swrite*(writer: var BufferedWriter; n: SomeNumber)
 proc swrite*[T](writer: var BufferedWriter; s: set[T])
diff --git a/src/io/promise.nim b/src/io/promise.nim
index 41ccceb4..3c01e214 100644
--- a/src/io/promise.nim
+++ b/src/io/promise.nim
@@ -1,7 +1,6 @@
 import std/tables
 
 import monoucha/quickjs
-import monoucha/jserror
 import monoucha/javascript
 import monoucha/jsutils
 import monoucha/jsopaque
@@ -201,24 +200,27 @@ proc promiseThenCallback(ctx: JSContext; this_val: JSValue; argc: cint;
     JS_SetOpaque(fun, nil)
   return JS_UNDEFINED
 
-proc fromJSEmptyPromise*(ctx: JSContext; val: JSValue): JSResult[EmptyPromise] =
+proc fromJS*(ctx: JSContext; val: JSValue; res: var EmptyPromise): Opt[void] =
   if not JS_IsObject(val):
-    return errTypeError("Value is not an object")
-  var p = EmptyPromise()
-  GC_ref(p)
+    JS_ThrowTypeError(ctx, "value is not an object")
+    return err()
+  res = EmptyPromise()
+  GC_ref(res)
   let tmp = JS_NewObject(ctx)
-  JS_SetOpaque(tmp, cast[pointer](p))
+  JS_SetOpaque(tmp, cast[pointer](res))
   let fun = JS_NewCFunctionData(ctx, promiseThenCallback, 0, 0, 1,
     tmp.toJSValueArray())
   JS_FreeValue(ctx, tmp)
-  let res = JS_Invoke(ctx, val, ctx.getOpaque().strRefs[jstThen], 1,
+  let val = JS_Invoke(ctx, val, ctx.getOpaque().strRefs[jstThen], 1,
     fun.toJSValueArray())
   JS_FreeValue(ctx, fun)
-  if JS_IsException(res):
-    JS_FreeValue(ctx, res)
+  if JS_IsException(val):
+    JS_FreeValue(ctx, val)
+    GC_unref(res)
+    res = nil
     return err()
-  JS_FreeValue(ctx, res)
-  return ok(p)
+  JS_FreeValue(ctx, val)
+  return ok()
 
 proc toJS*(ctx: JSContext; promise: EmptyPromise): JSValue =
   if promise == nil:
diff --git a/src/js/console.nim b/src/js/console.nim
index b61dedf7..755910a5 100644
--- a/src/js/console.nim
+++ b/src/js/console.nim
@@ -1,7 +1,6 @@
 import io/dynstream
 import monoucha/fromjs
 import monoucha/javascript
-import monoucha/jserror
 import types/opt
 
 type Console* = ref object
@@ -33,11 +32,13 @@ proc log*(console: Console; ss: varargs[string]) =
 proc error*(console: Console; ss: varargs[string]) =
   console.log(ss)
 
-proc log*(ctx: JSContext; console: Console; ss: varargs[JSValue]):
-    JSResult[void] {.jsfunc.} =
+proc log*(ctx: JSContext; console: Console; ss: varargs[JSValue]): Opt[void]
+    {.jsfunc.} =
   var buf = ""
   for i, val in ss:
-    buf &= ?fromJS[string](ctx, val)
+    var res: string
+    ?ctx.fromJS(val, res)
+    buf &= res
     if i != ss.high:
       buf &= ' '
   buf &= '\n'
@@ -49,20 +50,20 @@ proc clear(console: Console) {.jsfunc.} =
     console.clearFun()
 
 # For now, these are the same as log().
-proc debug(ctx: JSContext; console: Console; ss: varargs[JSValue]):
-    JSResult[void] {.jsfunc.} =
+proc debug(ctx: JSContext; console: Console; ss: varargs[JSValue]): Opt[void]
+    {.jsfunc.} =
   return log(ctx, console, ss)
 
-proc error(ctx: JSContext; console: Console; ss: varargs[JSValue]):
-    JSResult[void] {.jsfunc.} =
+proc error(ctx: JSContext; console: Console; ss: varargs[JSValue]): Opt[void]
+    {.jsfunc.} =
   return log(ctx, console, ss)
 
-proc info(ctx: JSContext; console: Console; ss: varargs[JSValue]):
-    JSResult[void] {.jsfunc.} =
+proc info(ctx: JSContext; console: Console; ss: varargs[JSValue]): Opt[void]
+    {.jsfunc.} =
   return log(ctx, console, ss)
 
-proc warn(ctx: JSContext; console: Console; ss: varargs[JSValue]):
-    JSResult[void] {.jsfunc.} =
+proc warn(ctx: JSContext; console: Console; ss: varargs[JSValue]): Opt[void]
+    {.jsfunc.} =
   return log(ctx, console, ss)
 
 proc show(console: Console) {.jsfunc.} =
@@ -81,7 +82,3 @@ proc addConsoleModule*(ctx: JSContext) =
 proc writeException*(ctx: JSContext; s: DynStream) =
   s.write(ctx.getExceptionMsg())
   s.sflush()
-
-proc writeException*(ctx: JSContext; s: DynStream; err: JSError) =
-  s.write(ctx.getExceptionMsg(err))
-  s.sflush()
diff --git a/src/js/domexception.nim b/src/js/domexception.nim
index 1fab7ed9..d07e88f4 100644
--- a/src/js/domexception.nim
+++ b/src/js/domexception.nim
@@ -1,5 +1,3 @@
-import std/tables
-
 import monoucha/javascript
 import monoucha/jserror
 import monoucha/quickjs
diff --git a/src/js/encoding.nim b/src/js/encoding.nim
index 2394cc6c..7d1bd126 100644
--- a/src/js/encoding.nim
+++ b/src/js/encoding.nim
@@ -13,7 +13,7 @@ type
     encoding: Charset
     ignoreBOM {.jsget.}: bool
     errorMode: DecoderErrorMode
-    doNotFlush: bool
+    stream: bool
     bomSeen: bool
     tdctx: TextDecoderContext
 
@@ -21,10 +21,10 @@ jsDestructor(JSTextDecoder)
 jsDestructor(JSTextEncoder)
 
 type TextDecoderOptions = object of JSDict
-  fatal: bool
-  ignoreBOM: bool
+  fatal {.jsdefault.}: bool
+  ignoreBOM {.jsdefault.}: bool
 
-func newJSTextDecoder(label = "utf-8", options = TextDecoderOptions()):
+func newJSTextDecoder(label = "utf-8"; options = TextDecoderOptions()):
     JSResult[JSTextDecoder] {.jsctor.} =
   let encoding = getCharset(label)
   if encoding in {CHARSET_UNKNOWN, CHARSET_REPLACEMENT}:
@@ -52,17 +52,16 @@ proc decode0(this: JSTextDecoder; ctx: JSContext; input: JSArrayBufferView;
   return ok(JS_NewStringLen(ctx, cstring(oq), csize_t(oq.len)))
 
 type TextDecodeOptions = object of JSDict
-  stream: bool
+  stream {.jsdefault.}: bool
 
 #TODO AllowSharedBufferSource
 proc decode(ctx: JSContext; this: JSTextDecoder;
     input = none(JSArrayBufferView); options = TextDecodeOptions()):
     JSResult[JSValue] {.jsfunc.} =
-  if not this.doNotFlush:
+  if not this.stream:
     this.tdctx = initTextDecoderContext(this.encoding, this.errorMode)
     this.bomSeen = false
-  if this.doNotFlush != options.stream:
-    this.doNotFlush = options.stream
+  this.stream = options.stream
   if input.isSome:
     return this.decode0(ctx, input.get, options.stream)
   return ok(JS_NewString(ctx, ""))
diff --git a/src/js/jscolor.nim b/src/js/jscolor.nim
index 8491e778..fef9c8aa 100644
--- a/src/js/jscolor.nim
+++ b/src/js/jscolor.nim
@@ -2,7 +2,6 @@ import std/strutils
 
 import monoucha/fromjs
 import monoucha/javascript
-import monoucha/jserror
 import monoucha/quickjs
 import monoucha/tojs
 import types/color
@@ -10,12 +9,12 @@ import types/opt
 import utils/charcategory
 import utils/twtstr
 
-func parseLegacyColor*(s: string): JSResult[RGBColor] =
+func parseLegacyColor*(s: string): Result[RGBColor, cstring] =
   if s == "":
-    return errTypeError("Color value must not be the empty string")
+    return err(cstring"color value must not be the empty string")
   let s = s.strip(chars = AsciiWhitespace).toLowerAscii()
   if s == "transparent":
-    return errTypeError("Color must not be transparent")
+    return err(cstring"color must not be transparent")
   return ok(parseLegacyColor0(s))
 
 proc toJS*(ctx: JSContext; rgb: RGBColor): JSValue =
@@ -25,8 +24,15 @@ proc toJS*(ctx: JSContext; rgb: RGBColor): JSValue =
   res.pushHex(rgb.b)
   return toJS(ctx, res)
 
-proc fromJSRGBColor*(ctx: JSContext; val: JSValue): JSResult[RGBColor] =
-  return parseLegacyColor(?fromJS[string](ctx, val))
+proc fromJS*(ctx: JSContext; val: JSValue; res: var RGBColor): Err[void] =
+  var s: string
+  ?ctx.fromJS(val, s)
+  let x = parseLegacyColor(s)
+  if x.isNone:
+    JS_ThrowTypeError(ctx, x.error)
+    return err()
+  res = x.get
+  return ok()
 
 proc toJS*(ctx: JSContext; rgba: ARGBColor): JSValue =
   var res = "#"
@@ -36,12 +42,16 @@ proc toJS*(ctx: JSContext; rgba: ARGBColor): JSValue =
   res.pushHex(rgba.a)
   return toJS(ctx, res)
 
-proc fromJSARGBColor*(ctx: JSContext; val: JSValue): JSResult[ARGBColor] =
+proc fromJS*(ctx: JSContext; val: JSValue; res: var ARGBColor): Err[void] =
   if JS_IsNumber(val):
     # as hex
-    return ok(ARGBColor(?fromJS[uint32](ctx, val)))
+    ?ctx.fromJS(val, uint32(res))
+    return ok()
   # parse
-  let x = parseARGBColor(?fromJS[string](ctx, val))
-  if x.isSome:
-    return ok(x.get)
-  return errTypeError("Unrecognized color")
+  var s: string
+  ?ctx.fromJS(val, s)
+  if (let x = parseARGBColor(s); x.isSome):
+    res = x.get
+    return ok()
+  JS_ThrowTypeError(ctx, "unrecognized color")
+  return err()
diff --git a/src/js/timeout.nim b/src/js/timeout.nim
index f8b8ed8a..0213156a 100644
--- a/src/js/timeout.nim
+++ b/src/js/timeout.nim
@@ -75,9 +75,9 @@ proc runEntry(state: var TimeoutState; entry: TimeoutEntry; name: string) =
       state.jsctx.writeException(state.err)
     JS_FreeValue(state.jsctx, ret)
   else:
-    let s = fromJS[string](state.jsctx, entry.val)
-    if s.isSome:
-      state.evalJSFree(s.get, name)
+    var s: string
+    if state.jsctx.fromJS(entry.val, s).isSome:
+      state.evalJSFree(s, name)
 
 proc runTimeoutFd*(state: var TimeoutState; fd: int): bool =
   if fd notin state.timeoutFds:
diff --git a/src/loader/headers.nim b/src/loader/headers.nim
index bed8f8e8..6b598cc2 100644
--- a/src/loader/headers.nim
+++ b/src/loader/headers.nim
@@ -34,21 +34,23 @@ jsDestructor(Headers)
 
 const HTTPWhitespace = {'\n', '\r', '\t', ' '}
 
-proc fromJSHeadersInit(ctx: JSContext; val: JSValue): JSResult[HeadersInit] =
+proc fromJS(ctx: JSContext; val: JSValue; res: var HeadersInit): Err[void] =
   if JS_IsUndefined(val) or JS_IsNull(val):
-    return err(nil)
-  if (let x = fromJS[Headers](ctx, val); x.isSome):
-    var s: seq[(string, string)] = @[]
-    for k, v in x.get.table:
+    return err()
+  var headers: Headers
+  if ctx.fromJS(val, headers).isSome:
+    res = HeadersInit(t: hitSequence, s: @[])
+    for k, v in headers.table:
       for vv in v:
-        s.add((k, vv))
-    return ok(HeadersInit(t: hitSequence, s: s))
+        res.s.add((k, vv))
+    return ok()
   if ctx.isSequence(val):
-    let x = fromJS[seq[(string, string)]](ctx, val)
-    if x.isSome:
-      return ok(HeadersInit(t: hitSequence, s: x.get))
-  let x = ?fromJS[Table[string, string]](ctx, val)
-  return ok(HeadersInit(t: hitTable, tab: x))
+    res = HeadersInit(t: hitSequence)
+    if ctx.fromJS(val, res.s).isSome:
+      return ok()
+  res = HeadersInit(t: hitTable)
+  ?ctx.fromJS(val, res.tab)
+  return ok()
 
 const TokenChars = {
   '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'
diff --git a/src/loader/loaderhandle.nim b/src/loader/loaderhandle.nim
index cb05efa1..11c33606 100644
--- a/src/loader/loaderhandle.nim
+++ b/src/loader/loaderhandle.nim
@@ -56,11 +56,10 @@ type
     when defined(debug):
       url*: URL
 
-{.warning[Deprecated]:off.}:
-  proc `=destroy`(buffer: var LoaderBufferObj) =
-    if buffer.page != nil:
-      dealloc(buffer.page)
-      buffer.page = nil
+proc `=destroy`(buffer: var LoaderBufferObj) =
+  if buffer.page != nil:
+    dealloc(buffer.page)
+    buffer.page = nil
 
 # for debugging
 when defined(debug):
diff --git a/src/loader/request.nim b/src/loader/request.nim
index 23be8ba8..2af5dcf3 100644
--- a/src/loader/request.nim
+++ b/src/loader/request.nim
@@ -161,44 +161,36 @@ type
       str: string
 
   RequestInit* = object of JSDict
-    #TODO aliasing in dicts
-    `method`: HttpMethod # default: GET
-    headers: Option[HeadersInit]
-    body: Option[BodyInit]
-    referrer: Option[string]
-    referrerPolicy: Option[ReferrerPolicy]
-    credentials: Option[CredentialsMode]
-    mode: Option[RequestMode]
-    window: Option[JSValue]
+    `method`* {.jsdefault.}: Option[HttpMethod] #TODO aliasing in dicts
+    headers* {.jsdefault.}: Option[HeadersInit]
+    body* {.jsdefault.}: Option[BodyInit]
+    referrer* {.jsdefault.}: Option[string]
+    referrerPolicy* {.jsdefault.}: Option[ReferrerPolicy]
+    credentials* {.jsdefault.}: Option[CredentialsMode]
+    mode* {.jsdefault.}: Option[RequestMode]
+    window* {.jsdefault: JS_UNDEFINED.}: JSValue
 
-proc fromJSBodyInit(ctx: JSContext; val: JSValue): JSResult[BodyInit] =
-  if JS_IsUndefined(val) or JS_IsNull(val):
-    return err(nil)
-  block formData:
-    let x = fromJS[FormData](ctx, val)
-    if x.isSome:
-      return ok(BodyInit(t: bitFormData, formData: x.get))
-  block blob:
-    let x = fromJS[Blob](ctx, val)
-    if x.isSome:
-      return ok(BodyInit(t: bitBlob, blob: x.get))
-  block searchParams:
-    let x = fromJS[URLSearchParams](ctx, val)
-    if x.isSome:
-      return ok(BodyInit(t: bitUrlSearchParams, searchParams: x.get))
-  block str:
-    let x = fromJS[string](ctx, val)
-    if x.isSome:
-      return ok(BodyInit(t: bitString, str: x.get))
-  return errTypeError("Invalid body init type")
+proc fromJS(ctx: JSContext; val: JSValue; res: var BodyInit): Opt[void] =
+  if not JS_IsUndefined(val) and not JS_IsNull(val):
+    res = BodyInit(t: bitFormData)
+    if ctx.fromJS(val, res.formData).isSome:
+      return ok()
+    res = BodyInit(t: bitBlob)
+    if ctx.fromJS(val, res.blob).isSome:
+      return ok()
+    res = BodyInit(t: bitUrlSearchParams)
+    if ctx.fromJS(val, res.searchParams).isSome:
+      return ok()
+    res = BodyInit(t: bitString)
+    if ctx.fromJS(val, res.str).isSome:
+      return ok()
+  JS_ThrowTypeError(ctx, "invalid body init type")
+  return err()
 
 var getAPIBaseURLImpl*: proc(ctx: JSContext): URL {.noSideEffect, nimcall.}
 
-proc newRequest*(ctx: JSContext; resource: JSValue; init = none(RequestInit)):
-    JSResult[JSRequest] {.jsctor.} =
-  defer:
-    if init.isSome and init.get.window.isSome:
-      JS_FreeValue(ctx, init.get.window.get)
+proc newRequest*(ctx: JSContext; resource: JSValue;
+    init = RequestInit(window: JS_UNDEFINED)): JSResult[JSRequest] {.jsctor.} =
   let headers = newHeaders(hgRequest)
   var fallbackMode = opt(rmCors)
   var window = RequestWindow(t: rwtClient)
@@ -207,51 +199,50 @@ proc newRequest*(ctx: JSContext; resource: JSValue; init = none(RequestInit)):
   var httpMethod = hmGet
   var referrer: URL = nil
   var url: URL = nil
-  if JS_IsString(resource):
-    let s = ?fromJS[string](ctx, resource)
-    url = ?parseJSURL(s, option(ctx.getAPIBaseURLImpl()))
-  else:
-    let resource = ?fromJS[JSRequest](ctx, resource)
-    url = resource.url
-    httpMethod = resource.request.httpMethod
-    headers.table = resource.headers.table
-    referrer = resource.request.referrer
-    credentials = resource.credentialsMode
-    body = resource.request.body
+  if (var res: JSRequest; ctx.fromJS(resource, res).isSome):
+    url = res.url
+    httpMethod = res.request.httpMethod
+    headers.table = res.headers.table
+    referrer = res.request.referrer
+    credentials = res.credentialsMode
+    body = res.request.body
     fallbackMode = opt(RequestMode)
-    window = resource.window
+    window = res.window
+  else:
+    var s: string
+    ?ctx.fromJS(resource, s)
+    url = ?parseJSURL(s, option(ctx.getAPIBaseURLImpl()))
   if url.username != "" or url.password != "":
     return errTypeError("Input URL contains a username or password")
   var mode = fallbackMode.get(rmNoCors)
   let destination = rdNone
   #TODO origin, window
-  if init.isSome: #TODO spec wants us to check if it's "not empty"...
-    let init = init.get
-    if init.window.isSome:
-      if not JS_IsNull(init.window.get):
-        return errTypeError("Expected window to be null")
-      window = RequestWindow(t: rwtNoWindow)
-    if mode == rmNavigate:
-      mode = rmSameOrigin
-    #TODO flags?
-    #TODO referrer
-    httpMethod = init.`method`
-    if init.body.isSome:
-      let ibody = init.body.get
-      case ibody.t
-      of bitFormData:
-        body = RequestBody(t: rbtMultipart, multipart: ibody.formData)
-      of bitString:
-        body = RequestBody(t: rbtString, s: ibody.str)
-      else: discard #TODO
-      if httpMethod in {hmGet, hmHead}:
-        return errTypeError("HEAD or GET Request cannot have a body.")
-    if init.headers.isSome:
-      ?headers.fill(init.headers.get)
-    if init.credentials.isSome:
-      credentials = init.credentials.get
-    if init.mode.isSome:
-      mode = init.mode.get
+  if not JS_IsUndefined(init.window):
+    if not JS_IsNull(init.window):
+      return errTypeError("Expected window to be null")
+    window = RequestWindow(t: rwtNoWindow)
+  if mode == rmNavigate:
+    mode = rmSameOrigin
+  #TODO flags?
+  #TODO referrer
+  if init.`method`.isSome:
+    httpMethod = init.`method`.get
+  if init.body.isSome:
+    let ibody = init.body.get
+    case ibody.t
+    of bitFormData:
+      body = RequestBody(t: rbtMultipart, multipart: ibody.formData)
+    of bitString:
+      body = RequestBody(t: rbtString, s: ibody.str)
+    else: discard #TODO
+    if httpMethod in {hmGet, hmHead}:
+      return errTypeError("HEAD or GET Request cannot have a body.")
+  if init.headers.isSome:
+    ?headers.fill(init.headers.get)
+  if init.credentials.isSome:
+    credentials = init.credentials.get
+  if init.mode.isSome:
+    mode = init.mode.get
   if mode == rmNoCors:
     headers.guard = hgRequestNoCors
   return ok(JSRequest(
diff --git a/src/local/client.nim b/src/local/client.nim
index f63668d2..d3fd9ebe 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -148,9 +148,9 @@ proc command0(client: Client; src: string; filename = "<command>";
     client.jsctx.writeException(client.console.err)
   else:
     if not silence:
-      let str = fromJS[string](client.jsctx, ret)
-      if str.isSome:
-        client.console.log(str.get)
+      var res: string
+      if client.jsctx.fromJS(ret, res).isSome:
+        client.console.log(res)
   JS_FreeValue(client.jsctx, ret)
 
 proc command(client: Client; src: string) =
@@ -207,9 +207,9 @@ proc evalAction(client: Client; action: string; arg0: int32): EmptyPromise =
   if JS_IsException(ret):
     client.jsctx.writeException(client.console.err)
   elif JS_IsObject(ret):
-    let maybep = fromJSEmptyPromise(ctx, ret)
-    if maybep.isSome:
-      p = maybep.get
+    var maybep: EmptyPromise
+    if ctx.fromJS(ret, maybep).isSome:
+      p = maybep
   JS_FreeValue(ctx, ret)
   return p
 
diff --git a/src/local/container.nim b/src/local/container.nim
index 8a057885..108c631e 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -1583,7 +1583,7 @@ proc cursorPrevMatch*(container: Container; regex: Regex; wrap, refresh: bool;
 
 type
   SelectionOptions = object of JSDict
-    selectionType: SelectionType
+    selectionType {.jsdefault.}: SelectionType
 
 proc cursorToggleSelection(container: Container; n = 1;
     opts = SelectionOptions()): Highlight {.jsfunc.} =
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 44237da8..79fe0e79 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -1152,13 +1152,13 @@ proc applySiteconf(pager: Pager; url: var URL; charsetOverride: Charset;
       let fun = sc.rewrite_url.get
       var arg0 = ctx.toJS(url)
       let ret = JS_Call(ctx, fun, JS_UNDEFINED, 1, arg0.toJSValueArray())
-      let nu = fromJS[URL](ctx, ret)
-      if nu.isSome:
-        if nu.get != nil:
-          url = nu.get
-      elif JS_IsException(ret):
+      var nu: URL
+      if ctx.fromJS(ret, nu).isSome:
+        if nu != nil:
+          url = nu
+      else:
         #TODO should writeException the message to console
-        pager.alert("Error rewriting URL: " & ctx.getExceptionMsg(nu.error))
+        pager.alert("Error rewriting URL: " & ctx.getExceptionMsg())
       JS_FreeValue(ctx, arg0)
       JS_FreeValue(ctx, ret)
     if sc.cookie.isSome:
@@ -1254,13 +1254,13 @@ proc omniRewrite(pager: Pager; s: string): string =
       let ctx = pager.jsctx
       var arg0 = ctx.toJS(s)
       let jsRet = JS_Call(ctx, fun, JS_UNDEFINED, 1, arg0.toJSValueArray())
-      let ret = fromJS[string](ctx, jsRet)
-      JS_FreeValue(ctx, jsRet)
-      JS_FreeValue(ctx, arg0)
-      if ret.isSome:
-        return ret.get
+      defer: JS_FreeValue(ctx, jsRet)
+      defer: JS_FreeValue(ctx, arg0)
+      var res: string
+      if ctx.fromJS(jsRet, res).isSome:
+        return res
       pager.alert("Error in substitution of " & $rule.match & " for " & s &
-        ": " & ctx.getExceptionMsg(ret.error))
+        ": " & ctx.getExceptionMsg())
   return s
 
 # When the user has passed a partial URL as an argument, they might've meant
@@ -1480,18 +1480,22 @@ proc load(pager: Pager; s = "") {.jsfunc.} =
 
 # Go to specific URL (for JS)
 type GotoURLDict = object of JSDict
-  contentType: Option[string]
-  replace: Container
+  contentType {.jsdefault.}: Option[string]
+  replace {.jsdefault.}: Container
 
 proc jsGotoURL(pager: Pager; v: JSValue; t = GotoURLDict()): JSResult[void]
     {.jsfunc: "gotoURL".} =
-  let request = if (let x = fromJS[JSRequest](pager.jsctx, v); x.isSome):
-    x.get.request
-  elif (let x = fromJS[URL](pager.jsctx, v); x.isSome):
-    newRequest(x.get)
+  var request: Request = nil
+  var jsRequest: JSRequest = nil
+  if pager.jsctx.fromJS(v, jsRequest).isSome:
+    request = jsRequest.request
   else:
-    let s = ?fromJS[string](pager.jsctx, v)
-    newRequest(?newURL(s))
+    var url: URL = nil
+    if pager.jsctx.fromJS(v, url).isNone:
+      var s: string
+      ?pager.jsctx.fromJS(v, s)
+      url = ?newURL(s)
+    request = newRequest(url)
   discard pager.gotoURL(request, contentType = t.contentType,
     replace = t.replace)
   return ok()
@@ -1508,27 +1512,27 @@ proc setEnvVars(pager: Pager) {.jsfunc.} =
   except OSError:
     pager.alert("Warning: failed to set some environment variables")
 
-#TODO use default values instead...
 type ExternDict = object of JSDict
-  setenv: Option[bool]
-  suspend: Option[bool]
-  wait: bool
+  setenv {.jsdefault: true.}: bool
+  suspend {.jsdefault: true.}: bool
+  wait {.jsdefault: false.}: bool
 
 #TODO we should have versions with retval as int?
-proc extern(pager: Pager; cmd: string; t = ExternDict()): bool {.jsfunc.} =
-  if t.setenv.get(true):
+proc extern(pager: Pager; cmd: string;
+    t = ExternDict(setenv: true, suspend: true)): bool {.jsfunc.} =
+  if t.setenv:
     pager.setEnvVars()
-  if t.suspend.get(true):
+  if t.suspend:
     return runProcess(pager.term, cmd, t.wait)
   else:
     return runProcess(cmd)
 
-proc externCapture(pager: Pager; cmd: string): Opt[string] {.jsfunc.} =
+proc externCapture(pager: Pager; cmd: string): Option[string] {.jsfunc.} =
   pager.setEnvVars()
   var s: string
   if not runProcessCapture(cmd, s):
-    return err()
-  return ok(s)
+    return none(string)
+  return some(s)
 
 proc externInto(pager: Pager; cmd, ins: string): bool {.jsfunc.} =
   pager.setEnvVars()
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index a1e273cc..7abac9e1 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -1426,10 +1426,11 @@ proc evalJSURL(buffer: Buffer; url: URL): Opt[string] =
     return err() # error
   if JS_IsUndefined(ret):
     return err() # no need to navigate
-  let s = ?fromJS[string](ctx, ret)
+  var res: string
+  ?ctx.fromJS(ret, res)
   JS_FreeValue(ctx, ret)
   # Navigate to result.
-  return ok(s)
+  return ok(res)
 
 proc click(buffer: Buffer; anchor: HTMLAnchorElement): ClickResult =
   var repaint = buffer.restoreFocus()
diff --git a/src/types/blob.nim b/src/types/blob.nim
index fb0bd92a..acffedf8 100644
--- a/src/types/blob.nim
+++ b/src/types/blob.nim
@@ -52,7 +52,7 @@ proc newWebFile*(name: string; fd: FileHandle): WebFile =
 
 type
   BlobPropertyBag = object of JSDict
-    `type`: string
+    `type` {.jsdefault.}: string
     #TODO endings
 
   FilePropertyBag = object of BlobPropertyBag
diff --git a/src/types/url.nim b/src/types/url.nim
index 23485602..7ecc9531 100644
--- a/src/types/url.nim
+++ b/src/types/url.nim
@@ -1060,11 +1060,12 @@ proc newURL*(url: URL): URL =
   result = URL()
   url.cloneInto(result)
 
-proc setHref(url: URL; s: string): Err[JSError] {.jsfset: "href".} =
+proc setHref(ctx: JSContext; url: URL; s: string) {.jsfset: "href".} =
   let purl = basicParseURL(s)
-  if purl.isNone:
-    return errTypeError(s & " is not a valid URL")
-  purl.get.cloneInto(url)
+  if purl.isSome:
+    purl.get.cloneInto(url)
+  else:
+    JS_ThrowTypeError(ctx, "%s is not a valid URL", s)
 
 func isIP*(url: URL): bool =
   return url.host.t in {htIpv4, htIpv6}
diff --git a/src/utils/luwrap.nim b/src/utils/luwrap.nim
index 926cf0c0..6081cdf8 100644
--- a/src/utils/luwrap.nim
+++ b/src/utils/luwrap.nim
@@ -94,12 +94,11 @@ type
 
   LUContext* = ref LUContextObj
 
-{.warning[Deprecated]: off.}:
-  proc `=destroy`*(ctx: var LUContextObj) =
-    for lur, cr in ctx.crs.mpairs:
-      if lur in ctx.inited:
-        cr_free(addr cr)
-    ctx.inited = {}
+proc `=destroy`*(ctx: var LUContextObj) =
+  for lur, cr in ctx.crs.mpairs:
+    if lur in ctx.inited:
+      cr_free(addr cr)
+  ctx.inited = {}
 
 proc initGeneralCategory(ctx: LUContext; lur: LURangeType) =
   if lur notin ctx.inited:
diff --git a/src/version.nim b/src/version.nim
index 8de8caea..2593a9c4 100644
--- a/src/version.nim
+++ b/src/version.nim
@@ -29,4 +29,4 @@ tryImport monoucha/version, "monoucha"
 static:
   checkVersion("chagashi", 0, 5, 4)
   checkVersion("chame", 1, 0, 1)
-  checkVersion("monoucha", 0, 2, 3)
+  checkVersion("monoucha", 0, 3, 0)