about summary refs log tree commit diff stats
path: root/src/js/opaque.nim
blob: ae79481b54fa8c80c84f8f4f5ccb516132085b6a (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
import tables

import bindings/quickjs
import js/error
import types/opt

type
  JSSymbolRefs* = enum
    ITERATOR = "iterator"
    ASYNC_ITERATOR = "asyncIterator"
    TO_STRING_TAG = "toStringTag"

  JSStrRefs* = enum
    DONE = "done"
    VALUE = "value"
    NEXT = "next"
    PROTOTYPE = "prototype"

  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
    sym_refs*: array[JSSymbolRefs, JSAtom]
    str_refs*: array[JSStrRefs, JSAtom]
    Array_prototype_values*: JSValue
    Object_prototype_valueOf*: JSValue
    Uint8Array_ctor*: JSValue
    err_ctors*: array[JSErrorEnum, JSValue]
    htmldda*: JSClassID # only one of these exists: document.all.

  JSRuntimeOpaque* = ref object
    plist*: Table[pointer, pointer] # Nim, JS
    flist*: seq[seq[JSCFunctionListEntry]]
    fins*: Table[JSClassID, proc(val: JSValue)]
    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)
    block:
      let sym = JS_GetPropertyStr(ctx, global, "Symbol")
      for s in JSSymbolRefs:
        let name = $s
        let val = JS_GetPropertyStr(ctx, sym, cstring(name))
        assert JS_IsSymbol(val)
        opaque.sym_refs[s] = JS_ValueToAtom(ctx, val)
        JS_FreeValue(ctx, val)
      JS_FreeValue(ctx, sym)
      for s in JSStrRefs:
        let ss = $s
        opaque.str_refs[s] = JS_NewAtomLen(ctx, cstring(ss), csize_t(ss.len))
    block:
      let arrproto = JS_GetClassProto(ctx, JS_CLASS_ARRAY)
      opaque.Array_prototype_values = JS_GetPropertyStr(ctx, arrproto,
        "values")
      JS_FreeValue(ctx, arrproto)
    block:
      let objproto = JS_GetClassProto(ctx, JS_CLASS_OBJECT)
      opaque.Object_prototype_valueOf = JS_GetPropertyStr(ctx, objproto, "valueOf")
      JS_FreeValue(ctx, objproto)
    block:
      let u8actor = JS_GetPropertyStr(ctx, global, "Uint8Array")
      opaque.Uint8Array_ctor = u8actor
    for e in JSErrorEnum:
      let s = $e
      let err = JS_GetPropertyStr(ctx, global, cstring(s))
      opaque.err_ctors[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: JSValue = 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: JSValue = 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))