about summary refs log tree commit diff stats
path: root/src/js/jsopaque.nim
blob: 2ce6bbc12a73749b0fe412c358780f161516b9af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import std/tables

import bindings/quickjs
import js/jserror
import types/opt

type
  JSSymbolRef* = enum
    jsyIterator = "iterator"
    jsyAsyncIterator = "asyncIterator"
    jsyToStringTag = "toStringTag"

  JSStrRef* = enum
    jstDone = "done"
    jstValue = "value"
    jstNext = "next"
    jstPrototype = "prototype"
    jstThen = "then"

  JSValueRef* = enum
    jsvArrayPrototypeValues = "Array.prototype.values"
    jsvUint8Array = "Uint8Array"
    jsvObjectPrototypeValueOf = "Object.prototype.valueOf"
    jsvSet = "Set"
    jsvFunction = "Function"

  JSContextOpaque* = ref object
    creg*: Table[string, JSClassID]
    typemap*: Table[pointer, JSClassID]
    ctors*: Table[JSClassID, JSValue]
    parents*: Table[JSClassID, JSClassID]
    # Parent unforgeables are merged on class creation.
    # (i.e. to set all unforgeables on the prototype chain, it is enough to set)
    # `unforgeable[classid]'.)
    unforgeable*: Table[JSClassID, seq[JSCFunctionListEntry]]
    gclaz*: string
    gparent*: JSClassID
    symRefs*: array[JSSymbolRef, JSAtom]
    strRefs*: array[JSStrRef, JSAtom]
    valRefs*: array[JSValueRef, JSValue]
    errCtorRefs*: array[JSErrorEnum, JSValue]
    htmldda*: JSClassID # only one of these exists: document.all.
    globalUnref*: proc() {.closure.}

  JSFinalizerFunction* = proc(rt: JSRuntime; val: JSValue) {.nimcall,
    raises: [].}

  JSRuntimeOpaque* = ref object
    plist*: Table[pointer, pointer] # Nim, JS
    flist*: seq[seq[JSCFunctionListEntry]]
    fins*: Table[JSClassID, JSFinalizerFunction]
    refmap*: Table[pointer, tuple[cref, cunref: (proc() {.closure.})]]
    destroying*: pointer

func newJSContextOpaque*(ctx: JSContext): JSContextOpaque =
  let opaque = JSContextOpaque()
  block: # get well-known symbols and other functions
    let global = JS_GetGlobalObject(ctx)
    let sym = JS_GetPropertyStr(ctx, global, "Symbol")
    for s in JSSymbolRef:
      let name = $s
      let val = JS_GetPropertyStr(ctx, sym, cstring(name))
      assert JS_IsSymbol(val)
      opaque.symRefs[s] = JS_ValueToAtom(ctx, val)
      JS_FreeValue(ctx, val)
    JS_FreeValue(ctx, sym)
    for s in JSStrRef:
      let ss = $s
      opaque.strRefs[s] = JS_NewAtomLen(ctx, cstring(ss), csize_t(ss.len))
    for s in JSValueRef:
      let ss = $s
      let ret = JS_Eval(ctx, cstring(ss), csize_t(ss.len), "<init>", 0)
      assert JS_IsFunction(ctx, ret)
      opaque.valRefs[s] = ret
    for e in JSErrorEnum:
      let s = $e
      let err = JS_GetPropertyStr(ctx, global, cstring(s))
      opaque.errCtorRefs[e] = err
    JS_FreeValue(ctx, global)
  return opaque

func getOpaque*(ctx: JSContext): JSContextOpaque =
  return cast[JSContextOpaque](JS_GetContextOpaque(ctx))

func getOpaque*(rt: JSRuntime): JSRuntimeOpaque =
  return cast[JSRuntimeOpaque](JS_GetRuntimeOpaque(rt))

func isGlobal*(ctx: JSContext; class: string): bool =
  assert class != ""
  return ctx.getOpaque().gclaz == class

proc setOpaque*(ctx: JSContext; val: JSValue; opaque: pointer) =
  let rt = JS_GetRuntime(ctx)
  let rtOpaque = rt.getOpaque()
  let p = JS_VALUE_GET_PTR(val)
  rtOpaque.plist[opaque] = p
  JS_SetOpaque(val, opaque)

# getOpaque, but doesn't work for global objects.
func getOpaque0*(val: JSValue): pointer =
  if JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT:
    return JS_GetOpaque(val, JS_GetClassID(val))

func getGlobalOpaque0*(ctx: JSContext; val = JS_UNDEFINED): Opt[pointer] =
  let global = JS_GetGlobalObject(ctx)
  if JS_IsUndefined(val) or val == global:
    let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT)
    JS_FreeValue(ctx, global)
    return ok(opaque)
  JS_FreeValue(ctx, global)
  return err()

func getGlobalOpaque*(ctx: JSContext; T: typedesc; val = JS_UNDEFINED): Opt[T] =
  return ok(cast[T](?getGlobalOpaque0(ctx, val)))

func getOpaque*(ctx: JSContext; val: JSValue; class: string): pointer =
  # Unfortunately, we can't change the global object's class.
  #TODO: or maybe we can, but I'm afraid of breaking something.
  # This needs further investigation.
  if ctx.isGlobal(class):
    let global = JS_GetGlobalObject(ctx)
    let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT)
    JS_FreeValue(ctx, global)
    return opaque
  return getOpaque0(val)

func getOpaque*[T: ref object](ctx: JSContext; val: JSValue): T =
  cast[T](getOpaque(ctx, val, $T))