summary refs log tree commit diff stats
path: root/lib/js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/js')
-rw-r--r--lib/js/asyncjs.nim212
-rw-r--r--lib/js/dom.nim261
-rw-r--r--lib/js/dom_extensions.nim5
-rw-r--r--lib/js/jsconsole.nim1
-rw-r--r--lib/js/jscore.nim53
-rw-r--r--lib/js/jsffi.nim194
-rw-r--r--lib/js/jsre.nim46
7 files changed, 471 insertions, 301 deletions
diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim
index c62ac633f..9b043f3e5 100644
--- a/lib/js/asyncjs.nim
+++ b/lib/js/asyncjs.nim
@@ -17,37 +17,42 @@
 ##
 ## This is roughly equivalent to the `async` keyword in JavaScript code.
 ##
-## .. code-block:: nim
-##  proc loadGame(name: string): Future[Game] {.async.} =
-##    # code
+##   ```nim
+##   proc loadGame(name: string): Future[Game] {.async.} =
+##     # code
+##   ```
 ##
 ## should be equivalent to
 ##
-## .. code-block:: javascript
+##   ```javascript
 ##   async function loadGame(name) {
 ##     // code
 ##   }
+##   ```
 ##
 ## A call to an asynchronous procedure usually needs `await` to wait for
 ## the completion of the `Future`.
 ##
-## .. code-block:: nim
+##   ```nim
 ##   var game = await loadGame(name)
+##   ```
 ##
 ## Often, you might work with callback-based API-s. You can wrap them with
 ## asynchronous procedures using promises and `newPromise`:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] =
 ##     var promise = newPromise() do (resolve: proc(response: Game)):
 ##       cbBasedLoadGame(name) do (game: Game):
 ##         resolve(game)
 ##     return promise
+##   ```
 ##
 ## Forward definitions work properly, you just need to always add the `{.async.}` pragma:
 ##
-## .. code-block:: nim
+##   ```nim
 ##   proc loadGame(name: string): Future[Game] {.async.}
+##   ```
 ##
 ## JavaScript compatibility
 ## ========================
@@ -57,13 +62,14 @@
 ## If you need to use this module with older versions of JavaScript, you can
 ## use a tool that backports the resulting JavaScript code, as babel.
 
-# xxx code-block:: javascript above gives `LanguageXNotSupported` warning.
+# xxx code: javascript above gives `LanguageXNotSupported` warning.
 
 when not defined(js) and not defined(nimsuggest):
   {.fatal: "Module asyncjs is designed to be used with the JavaScript backend.".}
 
 import std/jsffi
 import std/macros
+import std/private/since
 
 type
   Future*[T] = ref object
@@ -84,6 +90,8 @@ proc replaceReturn(node: var NimNode) =
       node[z] = nnkReturnStmt.newTree(value)
     elif son.kind == nnkAsgn and son[0].kind == nnkIdent and $son[0] == "result":
       node[z] = nnkAsgn.newTree(son[0], nnkCall.newTree(jsResolve, son[1]))
+    elif son.kind in RoutineNodes:
+      discard
     else:
       replaceReturn(son)
     inc z
@@ -94,10 +102,18 @@ proc isFutureVoid(node: NimNode): bool =
            node[1].kind == nnkIdent and $node[1] == "void"
 
 proc generateJsasync(arg: NimNode): NimNode =
-  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
+  if arg.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo, nnkProcTy}:
       error("Cannot transform this node kind into an async proc." &
             " proc/method definition or lambda node expected.")
 
+  # Transform type X = proc (): something {.async.}
+  # into      type X = proc (): Future[something]
+  if arg.kind == nnkProcTy:
+    result = arg
+    if arg[0][0].kind == nnkEmpty:
+      result[0][0] = quote do: Future[void]
+    return result
+
   result = arg
   var isVoid = false
   let jsResolve = ident("jsResolve")
@@ -158,100 +174,96 @@ proc newPromise*(handler: proc(resolve: proc())): Future[void] {.importjs: "(new
   ## A helper for wrapping callback-based functions
   ## into promises and async procedures.
 
-template typeOrVoid[T](a: T): type =
-  # xxx this is useful, make it public in std/typetraits in future work
-  T
-
 template maybeFuture(T): untyped =
   # avoids `Future[Future[T]]`
   when T is Future: T
   else: Future[T]
 
-when defined(nimExperimentalAsyncjsThen):
-  import std/private/since
-  since (1, 5, 1):
-    #[
-    TODO:
-    * map `Promise.all()`
-    * proc toString*(a: Error): cstring {.importjs: "#.toString()".}
-
-    Note:
-    We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible
-    in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
-    and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho
-    ]#
-
-    type Error*  {.importjs: "Error".} = ref object of JsRoot
-      ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
-      message*: cstring
-      name*: cstring
-
-    type OnReject* = proc(reason: Error)
-
-    proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto =
-      ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
-      ## Returns a `Future` from the return type of `onSuccess(T.default)`.
-      runnableExamples("-d:nimExperimentalAsyncjsThen"):
-        from std/sugar import `=>`
-
-        proc fn(n: int): Future[int] {.async.} =
-          if n >= 7: raise newException(ValueError, "foobar: " & $n)
-          else: result = n * 2
-
-        proc asyncFact(n: int): Future[int] {.async.} =
-          if n > 0: result = n * await asyncFact(n-1)
-          else: result = 1
-
-        proc main() {.async.} =
-          block: # then
-            assert asyncFact(3).await == 3*2
-            assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2
-            let x1 = await fn(3)
-            assert x1 == 3 * 2
-            let x2 = await fn(4)
-              .then((a: int) => a.float)
-              .then((a: float) => $a)
-            assert x2 == "8.0"
-
-          block: # then with `onReject` callback
-            var witness = 1
-            await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
-            assert witness == 2
-            await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
-            assert witness == 3
-
-      template impl(call): untyped =
-        when typeOrVoid(call) is void:
-          var ret: Future[void]
-        else:
-          var ret = default(maybeFuture(typeof(call)))
-        typeof(ret)
-      when T is void:
-        type A = impl(onSuccess())
+
+since (1, 5, 1):
+  #[
+  TODO:
+  * map `Promise.all()`
+  * proc toString*(a: Error): cstring {.importjs: "#.toString()".}
+
+  Note:
+  We probably can't have a `waitFor` in js in browser (single threaded), but maybe it would be possible
+  in in nodejs, see https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
+  and https://stackoverflow.com/questions/61377358/javascript-wait-for-async-call-to-finish-before-returning-from-function-witho
+  ]#
+
+  type Error*  {.importjs: "Error".} = ref object of JsRoot
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
+    message*: cstring
+    name*: cstring
+
+  type OnReject* = proc(reason: Error)
+
+  proc then*[T](future: Future[T], onSuccess: proc, onReject: OnReject = nil): auto =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
+    ## Returns a `Future` from the return type of `onSuccess(T.default)`.
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc asyncFact(n: int): Future[int] {.async.} =
+        if n > 0: result = n * await asyncFact(n-1)
+        else: result = 1
+
+      proc main() {.async.} =
+        block: # then
+          assert asyncFact(3).await == 3*2
+          assert asyncFact(3).then(asyncFact).await == 6*5*4*3*2
+          let x1 = await fn(3)
+          assert x1 == 3 * 2
+          let x2 = await fn(4)
+            .then((a: int) => a.float)
+            .then((a: float) => $a)
+          assert x2 == "8.0"
+
+        block: # then with `onReject` callback
+          var witness = 1
+          await fn(6).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 2
+          await fn(7).then((a: int) => (witness = 2), (r: Error) => (witness = 3))
+          assert witness == 3
+
+    template impl(call): untyped =
+      # see D20210421T014713
+      when typeof(block: call) is void:
+        var ret: Future[void]
       else:
-        type A = impl(onSuccess(default(T)))
-      var ret: A
-      asm "`ret` = `future`.then(`onSuccess`, `onReject`)"
-      return ret
-
-    proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
-      ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
-      runnableExamples("-d:nimExperimentalAsyncjsThen"):
-        from std/sugar import `=>`
-        from std/strutils import contains
-
-        proc fn(n: int): Future[int] {.async.} =
-          if n >= 7: raise newException(ValueError, "foobar: " & $n)
-          else: result = n * 2
-
-        proc main() {.async.} =
-          var reason: Error
-          await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work
-          assert reason == nil
-          await fn(7).catch((r: Error) => (reason = r))
-          assert reason != nil
-          assert  "foobar: 7" in $reason.message
-
-        discard main()
-
-      asm "`result` = `future`.catch(`onReject`)"
+        var ret = default(maybeFuture(typeof(call)))
+      typeof(ret)
+    when T is void:
+      type A = impl(onSuccess())
+    else:
+      type A = impl(onSuccess(default(T)))
+    var ret: A
+    {.emit: "`ret` = `future`.then(`onSuccess`, `onReject`);".}
+    return ret
+
+  proc catch*[T](future: Future[T], onReject: OnReject): Future[void] =
+    ## See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
+    runnableExamples("-r:off"):
+      from std/sugar import `=>`
+      from std/strutils import contains
+
+      proc fn(n: int): Future[int] {.async.} =
+        if n >= 7: raise newException(ValueError, "foobar: " & $n)
+        else: result = n * 2
+
+      proc main() {.async.} =
+        var reason: Error
+        await fn(6).catch((r: Error) => (reason = r)) # note: `()` are needed, `=> reason = r` would not work
+        assert reason == nil
+        await fn(7).catch((r: Error) => (reason = r))
+        assert reason != nil
+        assert  "foobar: 7" in $reason.message
+
+      discard main()
+
+    {.emit: "`result` = `future`.catch(`onReject`);".}
diff --git a/lib/js/dom.nim b/lib/js/dom.nim
index a133f1f69..be2a34db1 100644
--- a/lib/js/dom.nim
+++ b/lib/js/dom.nim
@@ -9,6 +9,37 @@
 
 ## Declaration of the Document Object Model for the `JavaScript backend
 ## <backends.html#backends-the-javascript-target>`_.
+##
+##
+## Document Ready
+## --------------
+##
+## * Basic example of a document ready:
+runnableExamples"-b:js -r:off":
+  proc example(e: Event) = echo "Document is ready"
+  document.addEventListener("DOMContentLoaded", example)  # You can also use "load" event.
+## * This example runs 5 seconds after the document ready:
+runnableExamples"-b:js -r:off":
+  proc example() = echo "5 seconds after document ready"
+  proc domReady(e: Event) = discard setTimeout(example, 5_000) # Document is ready.
+  document.addEventListener("DOMContentLoaded", domReady)
+## Document onUnload
+## -----------------
+##
+## * Simple example of how to implement code that runs when the page unloads:
+runnableExamples"-b:js -r:off":
+  proc example(e: Event) = echo "Document is unloaded"
+  document.addEventListener("unload", example)  # You can also use "beforeunload".
+## Document Autorefresh
+## --------------------
+##
+## * Minimal example of a document autorefresh:
+runnableExamples"-b:js -r:off":
+  proc example() = window.location.reload()
+  discard setTimeout(example, 5_000)
+## - For more examples, see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
+
+
 import std/private/since
 when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
@@ -17,8 +48,7 @@ const
   DomApiVersion* = 3 ## the version of DOM API we try to follow. No guarantees though.
 
 type
-  EventTarget* = ref EventTargetObj
-  EventTargetObj {.importc.} = object of RootObj
+  EventTarget* {.importc.} = ref object of RootObj
     onabort*: proc (event: Event) {.closure.}
     onblur*: proc (event: Event) {.closure.}
     onchange*: proc (event: Event) {.closure.}
@@ -129,8 +159,7 @@ type
 
   Storage* {.importc.} = ref object
 
-  Window* = ref WindowObj
-  WindowObj {.importc.} = object of EventTargetObj
+  Window* {.importc.} = ref object of EventTarget
     document*: Document
     event*: Event
     history*: History
@@ -159,11 +188,9 @@ type
     sessionStorage*: Storage
     parent*: Window
 
-  Frame* = ref FrameObj
-  FrameObj {.importc.} = object of WindowObj
+  Frame* {.importc.} = ref object of Window
 
-  ClassList* = ref ClassListObj
-  ClassListObj {.importc.} = object of RootObj
+  ClassList* {.importc.} = ref object of RootObj
 
   NodeType* = enum
     ElementNode = 1,
@@ -179,8 +206,7 @@ type
     DocumentFragmentNode,
     NotationNode
 
-  Node* = ref NodeObj
-  NodeObj {.importc.} = object of EventTargetObj
+  Node* {.importc.} = ref object of EventTarget
     attributes*: seq[Node]
     childNodes*: seq[Node]
     children*: seq[Node]
@@ -204,8 +230,7 @@ type
     parentElement*: Element
     isConnected*: bool
 
-  Document* = ref DocumentObj
-  DocumentObj {.importc.} = object of NodeObj
+  Document* {.importc.} = ref object of Node
     activeElement*: Element
     documentElement*: Element
     alinkColor*: cstring
@@ -216,11 +241,13 @@ type
     defaultCharset*: cstring
     fgColor*: cstring
     head*: Element
+    hidden*: bool
     lastModified*: cstring
     linkColor*: cstring
     referrer*: cstring
     title*: cstring
     URL*: cstring
+    visibilityState*: cstring
     vlinkColor*: cstring
     anchors*: seq[AnchorElement]
     forms*: seq[FormElement]
@@ -230,8 +257,7 @@ type
     links*: seq[LinkElement]
     fonts*: FontFaceSet
 
-  Element* = ref ElementObj
-  ElementObj {.importc.} = object of NodeObj
+  Element* {.importc.} = ref object of Node
     className*: cstring
     classList*: ClassList
     checked*: bool
@@ -252,8 +278,7 @@ type
     offsetLeft*: int
     offsetTop*: int
 
-  ValidityState* = ref ValidityStateObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_
-  ValidityStateObj {.importc.} = object
+  ValidityState* {.importc.} = ref object ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ValidityState>`_
     badInput*: bool
     customError*: bool
     patternMismatch*: bool
@@ -266,25 +291,21 @@ type
     valid*: bool
     valueMissing*: bool
 
-  Blob* = ref BlobObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_
-  BlobObj {.importc.} = object of RootObj
+  Blob* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Blob>`_
     size*: int
     `type`*: cstring
 
-  File* = ref FileObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_
-  FileObj {.importc.} = object of Blob
+  File* {.importc.} = ref object of Blob ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/File>`_
     lastModified*: int
     name*: cstring
 
-  TextAreaElement* = ref TextAreaElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_
-  TextAreaElementObj {.importc.} = object of Element
+  TextAreaElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement>`_
     value*: cstring
     selectionStart*, selectionEnd*: int
     selectionDirection*: cstring
     rows*, cols*: int
 
-  InputElement* = ref InputElementObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_
-  InputElementObj {.importc.} = object of Element
+  InputElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement>`_
     # Properties related to the parent form
     formAction*: cstring
     formEncType*: cstring
@@ -336,15 +357,13 @@ type
     valueAsDate*: cstring
     valueAsNumber*: float
 
-  LinkElement* = ref LinkObj
-  LinkObj {.importc.} = object of ElementObj
+  LinkElement* {.importc.} = ref object of Element
     target*: cstring
     text*: cstring
     x*: int
     y*: int
 
-  EmbedElement* = ref EmbedObj
-  EmbedObj {.importc.} = object of ElementObj
+  EmbedElement* {.importc.} = ref object of Element
     height*: int
     hspace*: int
     src*: cstring
@@ -352,21 +371,18 @@ type
     `type`*: cstring
     vspace*: int
 
-  AnchorElement* = ref AnchorObj
-  AnchorObj {.importc.} = object of ElementObj
+  AnchorElement* {.importc.} = ref object of Element
     text*: cstring
     x*, y*: int
 
-  OptionElement* = ref OptionObj
-  OptionObj {.importc.} = object of ElementObj
+  OptionElement* {.importc.} = ref object of Element
     defaultSelected*: bool
     selected*: bool
     selectedIndex*: int
     text*: cstring
     value*: cstring
 
-  FormElement* = ref FormObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_
-  FormObj {.importc.} = object of ElementObj
+  FormElement* {.importc.} = ref object of Element ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement>`_
     acceptCharset*: cstring
     action*: cstring
     autocomplete*: cstring
@@ -378,8 +394,7 @@ type
     noValidate*: bool
     target*: cstring
 
-  ImageElement* = ref ImageObj
-  ImageObj {.importc.} = object of ElementObj
+  ImageElement* {.importc.} = ref object of Element
     border*: int
     complete*: bool
     height*: int
@@ -389,8 +404,7 @@ type
     vspace*: int
     width*: int
 
-  Style* = ref StyleObj
-  StyleObj {.importc.} = object of RootObj
+  Style* {.importc.} = ref object of RootObj
     alignContent*: cstring
     alignItems*: cstring
     alignSelf*: cstring
@@ -766,8 +780,7 @@ type
     AtTarget,
     BubblingPhase
 
-  Event* = ref EventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_
-  EventObj {.importc.} = object of RootObj
+  Event* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/Event>`_
     bubbles*: bool
     cancelBubble*: bool
     cancelable*: bool
@@ -779,13 +792,11 @@ type
     `type`*: cstring
     isTrusted*: bool
 
-  UIEvent* = ref UIEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_
-  UIEventObj {.importc.} = object of Event
+  UIEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/UIEvent>`_
     detail*: int64
     view*: Window
 
-  KeyboardEvent* = ref KeyboardEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_
-  KeyboardEventObj {.importc.} = object of UIEvent
+  KeyboardEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent>`_
     altKey*, ctrlKey*, metaKey*, shiftKey*: bool
     code*: cstring
     isComposing*: bool
@@ -1147,8 +1158,7 @@ type
     FourthButton = 8,
     FifthButton = 16
 
-  MouseEvent* = ref MouseEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_
-  MouseEventObj {.importc.} = object of UIEvent
+  MouseEvent* {.importc.} = ref object of UIEvent ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent>`_
     altKey*, ctrlKey*, metaKey*, shiftKey*: bool
     button*: int
     buttons*: int
@@ -1165,13 +1175,11 @@ type
     File = "file",
     String = "string"
 
-  DataTransferItem* = ref DataTransferItemObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_
-  DataTransferItemObj {.importc.} = object of RootObj
+  DataTransferItem* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem>`_
     kind*: cstring
     `type`*: cstring
 
-  DataTransfer* = ref DataTransferObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_
-  DataTransferObj {.importc.} = object of RootObj
+  DataTransfer* {.importc.} = ref object of RootObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer>`_
     dropEffect*: cstring
     effectAllowed*: cstring
     files*: seq[Element]
@@ -1213,8 +1221,7 @@ type
     ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent>`_
     clipboardData*: DataTransfer
 
-  StorageEvent* = ref StorageEventObj ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent>`_
-  StorageEventObj {.importc.} = object of Event
+  StorageEvent* {.importc.} = ref object of Event ## see `docs<https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent>`_
     key*: cstring
     newValue*, oldValue*: cstring
     storageArea*: Storage
@@ -1223,8 +1230,7 @@ type
   TouchList* {.importc.} = ref object of RootObj
     length*: int
 
-  Touch* = ref TouchObj
-  TouchObj {.importc.} = object of RootObj
+  Touch* {.importc.} = ref object of RootObj
     identifier*: int
     screenX*, screenY*, clientX*, clientY*, pageX*, pageY*: int
     target*: Element
@@ -1232,12 +1238,10 @@ type
     rotationAngle*: int
     force*: float
 
-  TouchEvent* = ref TouchEventObj
-  TouchEventObj {.importc.} = object of UIEvent
+  TouchEvent* {.importc.} = ref object of UIEvent
     changedTouches*, targetTouches*, touches*: seq[Touch]
 
-  Location* = ref LocationObj
-  LocationObj {.importc.} = object of RootObj
+  Location* {.importc.} = ref object of RootObj
     hash*: cstring
     host*: cstring
     hostname*: cstring
@@ -1248,12 +1252,10 @@ type
     search*: cstring
     origin*: cstring
 
-  History* = ref HistoryObj
-  HistoryObj {.importc.} = object of RootObj
+  History* {.importc.} = ref object of RootObj
     length*: int
 
-  Navigator* = ref NavigatorObj
-  NavigatorObj {.importc.} = object of RootObj
+  Navigator* {.importc.} = ref object of RootObj
     appCodeName*: cstring
     appName*: cstring
     appVersion*: cstring
@@ -1291,8 +1293,7 @@ type
   ToolBar* = LocationBar
   StatusBar* = LocationBar
 
-  Screen = ref ScreenObj
-  ScreenObj {.importc.} = object of RootObj
+  Screen* {.importc.} = ref object of RootObj
     availHeight*: int
     availWidth*: int
     colorDepth*: int
@@ -1301,7 +1302,7 @@ type
     width*: int
 
   TimeOut* {.importc.} = ref object of RootObj
-  Interval* {.importc.} = object of RootObj
+  Interval* {.importc.} = ref object of RootObj
 
   AddEventListenerOptions* = object
     capture*: bool
@@ -1317,41 +1318,46 @@ type
     ready*: FontFaceSetReady
     onloadingdone*: proc(event: Event)
 
+  ScrollIntoViewOptions* = object
+    behavior*: cstring
+    `block`*: cstring
+    inline*: cstring
+
+  MediaQueryList* {.importc.} = ref object of EventTarget
+    matches*: bool
+    media*: cstring
+
 since (1, 3):
   type
     DomParser* = ref object
       ## DOM Parser object (defined on browser only, may not be on NodeJS).
       ## * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
       ##
-      ## .. code-block:: nim
+      ##   ```nim
       ##   let prsr = newDomParser()
       ##   discard prsr.parseFromString("<html><marquee>Hello World</marquee></html>".cstring, "text/html".cstring)
+      ##   ```
 
-    DomException* = ref DOMExceptionObj
+    DomException* {.importc.} = ref object
       ## The DOMException interface represents an abnormal event (called an exception)
       ## which occurs as a result of calling a method or accessing a property of a web API.
       ## Each exception has a name, which is a short "CamelCase" style string identifying
       ## the error or abnormal condition.
       ## https://developer.mozilla.org/en-US/docs/Web/API/DOMException
 
-    DOMExceptionObj {.importc.} = object
-
-    FileReader* = ref FileReaderObj
+    FileReader* {.importc.} = ref object of EventTarget
       ## The FileReader object lets web applications asynchronously read the contents of files
       ## (or raw data buffers) stored on the user's computer, using File or Blob objects to specify
       ## the file or data to read.
       ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader
 
-    FileReaderObj {.importc.} = object of EventTargetObj
-
     FileReaderState* = distinct range[0'u16..2'u16]
     RootNodeOptions* = object of RootObj
       composed*: bool
     DocumentOrShadowRoot* {.importc.} = object of RootObj
       activeElement*: Element
       # styleSheets*: StyleSheetList
-    ShadowRoot* = ref ShadowRootObj
-    ShadowRootObj {.importc.} = object of DocumentOrShadowRoot
+    ShadowRoot* {.importc.} = ref object of DocumentOrShadowRoot
       delegatesFocus*: bool
       host*: Element
       innerHTML*: cstring
@@ -1360,8 +1366,7 @@ since (1, 3):
       mode*: cstring
       delegatesFocus*: bool
 
-    HTMLSlotElement* = ref HTMLSlotElementObj
-    HTMLSlotElementObj {.importc.} = object of RootObj
+    HTMLSlotElement* {.importc.} = ref object of RootObj
       name*: cstring
     SlotOptions* = object of RootObj
       flatten*: bool
@@ -1379,6 +1384,9 @@ proc `class=`*(n: Node; v: cstring) {.importcpp: "#.className = #", nodecl.}
 proc value*(n: Node): cstring {.importcpp: "#.value", nodecl.}
 proc `value=`*(n: Node; v: cstring) {.importcpp: "#.value = #", nodecl.}
 
+proc checked*(n: Node): bool {.importcpp: "#.checked", nodecl.}
+proc `checked=`*(n: Node; v: bool) {.importcpp: "#.checked = #", nodecl.}
+
 proc `disabled=`*(n: Node; v: bool) {.importcpp: "#.disabled = #", nodecl.}
 
 when defined(nodejs):
@@ -1415,7 +1423,7 @@ when defined(nodejs):
         parent.childNodes[i] = newNode
         return
       inc i
-    doAssert false, "old node not in node list"
+    raiseAssert "old node not in node list"
 
   proc removeChild*(parent, child: Node) =
     child.parentNode = nil
@@ -1425,7 +1433,7 @@ when defined(nodejs):
         parent.childNodes.delete(i)
         return
       inc i
-    doAssert false, "old node not in node list"
+    raiseAssert "old node not in node list"
 
   proc insertBefore*(parent, newNode, before: Node) =
     appendChild(parent, newNode)
@@ -1437,7 +1445,7 @@ when defined(nodejs):
         parent.childNodes[i-1] = newNode
         return
       inc i
-    #doAssert false, "before not in node list"
+    #raiseAssert "before not in node list"
 
   proc createElement*(d: Document, identifier: cstring): Element =
     new(result)
@@ -1473,6 +1481,8 @@ else:
 
 proc setTimeout*(action: proc(); ms: int): TimeOut {.importc, nodecl.}
 proc clearTimeout*(t: TimeOut) {.importc, nodecl.}
+proc setInterval*(action: proc(); ms: int): Interval {.importc, nodecl.}
+proc clearInterval*(i: Interval) {.importc, nodecl.}
 
 {.push importcpp.}
 
@@ -1486,17 +1496,19 @@ proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event))
 proc alert*(w: Window, msg: cstring)
 proc back*(w: Window)
 proc blur*(w: Window)
-proc clearInterval*(w: Window, interval: ref Interval)
-proc clearTimeout*(w: Window, timeout: ref TimeOut)
+proc clearInterval*(w: Window, interval: Interval)
+proc clearTimeout*(w: Window, timeout: TimeOut)
 proc close*(w: Window)
 proc confirm*(w: Window, msg: cstring): bool
 proc disableExternalCapture*(w: Window)
 proc enableExternalCapture*(w: Window)
 proc find*(w: Window, text: cstring, caseSensitive = false,
-           backwards = false)
+           backwards = false): bool
 proc focus*(w: Window)
 proc forward*(w: Window)
-proc getComputedStyle*(w: Window, e: Node, pe:Node = nil): Style
+proc getComputedStyle*(w: Window, e: Node, pe: Node = nil): Style
+  ## .. warning:: The returned Style may or may not be read-only at run-time in the browser. getComputedStyle is performance costly.
+
 proc handleEvent*(w: Window, e: Event)
 proc home*(w: Window)
 proc moveBy*(w: Window, x, y: int)
@@ -1510,13 +1522,14 @@ proc resizeTo*(w: Window, x, y: int)
 proc routeEvent*(w: Window, event: Event)
 proc scrollBy*(w: Window, x, y: int)
 proc scrollTo*(w: Window, x, y: int)
-proc setInterval*(w: Window, code: cstring, pause: int): ref Interval
-proc setInterval*(w: Window, function: proc (), pause: int): ref Interval
-proc setTimeout*(w: Window, code: cstring, pause: int): ref TimeOut
-proc setTimeout*(w: Window, function: proc (), pause: int): ref Interval
+proc setInterval*(w: Window, code: cstring, pause: int): Interval
+proc setInterval*(w: Window, function: proc (), pause: int): Interval
+proc setTimeout*(w: Window, code: cstring, pause: int): TimeOut
+proc setTimeout*(w: Window, function: proc (), pause: int): Interval
 proc stop*(w: Window)
 proc requestAnimationFrame*(w: Window, function: proc (time: float)): int
 proc cancelAnimationFrame*(w: Window, id: int)
+proc matchMedia*(w: Window, mediaQueryString: cstring): MediaQueryList
 
 # Node "methods"
 proc appendData*(n: Node, data: cstring)
@@ -1533,6 +1546,7 @@ proc removeAttribute*(n: Node, attr: cstring)
 proc removeAttributeNode*(n, attr: Node)
 proc replaceData*(n: Node, start, len: int, text: cstring)
 proc scrollIntoView*(n: Node)
+proc scrollIntoView*(n: Node, options: ScrollIntoViewOptions)
 proc setAttribute*(n: Node, name, value: cstring)
 proc setAttributeNode*(n: Node, attr: Node)
 proc querySelector*(n: Node, selectors: cstring): Element
@@ -1641,7 +1655,7 @@ proc item*(list: TouchList, i: int): Touch
 proc clearData*(dt: DataTransfer, format: cstring)
 proc getData*(dt: DataTransfer, format: cstring): cstring
 proc setData*(dt: DataTransfer, format: cstring, data: cstring)
-proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int64, yOffset: int64)
+proc setDragImage*(dt: DataTransfer, img: Element, xOffset: int, yOffset: int)
 
 # DataTransferItem "methods"
 proc getAsFile*(dti: DataTransferItem): File
@@ -1668,7 +1682,6 @@ proc `$`*(s: Selection): string = $(s.toString())
 # Storage "methods"
 proc getItem*(s: Storage, key: cstring): cstring
 proc setItem*(s: Storage, key, value: cstring)
-proc hasItem*(s: Storage, key: cstring): bool
 proc clear*(s: Storage)
 proc removeItem*(s: Storage, key: cstring)
 
@@ -1756,5 +1769,73 @@ since (1, 3):
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString
   proc readAsDataURL*(f: FileReader, b: Blob) {.importcpp: "#.readAsDataURL(#)".}
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL
-  proc readAsText*(f: FileReader, b: Blob, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".}
+  proc readAsText*(f: FileReader, b: Blob|File, encoding = cstring"UTF-8") {.importcpp: "#.readAsText(#, #)".}
     ## https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsText
+
+since (1, 5):
+  proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element] {.importcpp.}
+
+
+since (1, 7):
+
+  proc insertAdjacentText*(self: Node; position, data: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentText
+
+  proc insertAdjacentElement*(self: Node; position: cstring; element: Node) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
+
+  proc insertAdjacentHTML*(self: Node; position, html: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
+
+  proc after*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/after
+
+  proc before*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/before
+
+  proc append*(self: Node; element: Node): Node {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/append
+
+  proc closest*(self: Node; cssSelector: cstring): Node {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
+
+  proc hasAttributeNS*(self: Node; namespace, localName: cstring): bool {.importjs: "(#.$1(#, #) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttributeNS
+
+  proc removeAttributeNS*(self: Node; namespace, attributeName: cstring) {.importjs: "#.$1(#, #)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttributeNS
+
+  proc hasPointerCapture*(self: Node; pointerId: SomeNumber): bool {.importjs: "(#.$1(#) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/hasPointerCapture
+
+  proc releasePointerCapture*(self: Node; pointerId: SomeNumber) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/releasePointerCapture
+
+  proc requestPointerLock*(self: Node) {.importjs: "#.$1()".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/requestPointerLock
+
+  proc replaceChildren*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceChildren
+
+  proc replaceWith*(self: Node; replacements: Node) {.importjs: "#.$1(@)", varargs.}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/replaceWith
+
+  proc scrollIntoViewIfNeeded*(self: Node; centerIfNeeded: bool) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded
+
+  proc setHTML*(self: Node; html: cstring) {.importjs: "#.$1(#)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/setHTML
+
+  proc toggleAttribute*(self: Node; name: cstring; force = false): bool {.importjs: "(#.$1(#, #) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute
+
+  proc matches*(self: Node; cssSelector: cstring): bool {.importjs: "(#.$1(#) || false)".}
+    ## https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
+
+
+since (2, 1):
+  type VisualViewport* {.importc.} = ref object of EventTarget
+    offsetLeft*, offsetTop*, pageLeft*, pageTop*, width*, height*, scale*: float
+    onResize*, onScroll*: proc (event: Event) {.closure.}
+
+  func visualViewport*(self: Window): VisualViewport {.importjs: "#.$1", nodecl.}
diff --git a/lib/js/dom_extensions.nim b/lib/js/dom_extensions.nim
deleted file mode 100644
index a1ceff5b4..000000000
--- a/lib/js/dom_extensions.nim
+++ /dev/null
@@ -1,5 +0,0 @@
-import std/dom
-
-{.push importcpp.}
-proc elementsFromPoint*(n: DocumentOrShadowRoot; x, y: float): seq[Element]
-{.pop.}
diff --git a/lib/js/jsconsole.nim b/lib/js/jsconsole.nim
index bf43adddd..e74127334 100644
--- a/lib/js/jsconsole.nim
+++ b/lib/js/jsconsole.nim
@@ -118,6 +118,7 @@ since (1, 5):
 
   func timeStamp*(console: Console; label: cstring) {.importcpp.}
     ## https://developer.mozilla.org/en-US/docs/Web/API/Console/timeStamp
+    ##
     ## ..warning:: non-standard
 
 
diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim
index 693e50799..be353875c 100644
--- a/lib/js/jscore.nim
+++ b/lib/js/jscore.nim
@@ -13,7 +13,7 @@
 ## specific requirements and solely targets JavaScript, you should be using
 ## the relevant functions in the `math`, `json`, and `times` stdlib
 ## modules instead.
-import std/private/since
+import std/private/[since, jsutils]
 
 when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
@@ -74,9 +74,16 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.}
 proc newDate*(): DateTime {.
   importcpp: "new Date()".}
 
-proc newDate*(date: int|int64|string): DateTime {.
+proc newDate*(date: int|string): DateTime {.
   importcpp: "new Date(#)".}
 
+whenJsNoBigInt64:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(#)".}
+do:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(Number(#))".}
+
 proc newDate*(year, month, day, hours, minutes,
              seconds, milliseconds: int): DateTime {.
   importcpp: "new Date(#,#,#,#,#,#,#)".}
@@ -88,20 +95,27 @@ proc getMilliseconds*(d: DateTime): int {.importcpp.}
 proc getMinutes*(d: DateTime): int {.importcpp.}
 proc getMonth*(d: DateTime): int {.importcpp.}
 proc getSeconds*(d: DateTime): int {.importcpp.}
-proc getYear*(d: DateTime): int {.importcpp.}
 proc getTime*(d: DateTime): int {.importcpp.}
-proc toString*(d: DateTime): cstring {.importcpp.}
+proc getTimezoneOffset*(d: DateTime): int {.importcpp.}
 proc getUTCDate*(d: DateTime): int {.importcpp.}
+proc getUTCDay*(d: DateTime): int {.importcpp.}
 proc getUTCFullYear*(d: DateTime): int {.importcpp.}
 proc getUTCHours*(d: DateTime): int {.importcpp.}
 proc getUTCMilliseconds*(d: DateTime): int {.importcpp.}
 proc getUTCMinutes*(d: DateTime): int {.importcpp.}
 proc getUTCMonth*(d: DateTime): int {.importcpp.}
 proc getUTCSeconds*(d: DateTime): int {.importcpp.}
-proc getUTCDay*(d: DateTime): int {.importcpp.}
-proc getTimezoneOffset*(d: DateTime): int {.importcpp.}
+proc getYear*(d: DateTime): int {.importcpp.}
+
 proc setFullYear*(d: DateTime, year: int) {.importcpp.}
 
+func toDateString*(d: DateTime): cstring {.importcpp.}
+func toISOString*(d: DateTime): cstring {.importcpp.}
+func toJSON*(d: DateTime): cstring {.importcpp.}
+proc toString*(d: DateTime): cstring {.importcpp.}
+func toTimeString*(d: DateTime): cstring {.importcpp.}
+func toUTCString*(d: DateTime): cstring {.importcpp.}
+
 #JSON library
 proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.}
 proc parse*(l: JsonLib, s: cstring): JsRoot {.importcpp.}
@@ -110,3 +124,30 @@ proc parse*(l: JsonLib, s: cstring): JsRoot {.importcpp.}
 since (1, 5):
   func debugger*() {.importjs: "debugger@".}
     ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
+
+  func copyWithin*[T](self: openArray[T]; target: int): seq[T] {.importjs: "#.copyWithin(#)".}
+  func copyWithin*[T](self: openArray[T]; target, start: int): seq[T] {.importjs: "#.copyWithin(#, #)".}
+  func copyWithin*[T](self: openArray[T]; target, start, ends: int): seq[T] {.importjs: "#.copyWithin(#, #, #)".} =
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin
+    ## `copyWithin` uses shallow copy.
+    runnableExamples:
+      assert ['a', 'b', 'c', 'd', 'e'].copyWithin(0, 3, 4) == @['d', 'b', 'c', 'd', 'e']
+      assert ['a', 'b', 'c', 'd', 'e'].copyWithin(1, 3) == @['a', 'd', 'e', 'd', 'e']
+      assert [1, 2, 3, 4, 5].copyWithin(-2) == @[1, 2, 3, 1, 2]
+      assert [1, 2, 3, 4, 5].copyWithin(0, 3) == @[4, 5, 3, 4, 5]
+      assert [1, 2, 3, 4, 5].copyWithin(0, 3, 4) == @[4, 2, 3, 4, 5]
+      assert [1, 2, 3, 4, 5].copyWithin(-2, -3, -1) == @[1, 2, 3, 3, 4]
+
+
+since (1, 7):
+  func shift*[T](self: seq[T]): T {.importjs: "#.$1()".} =
+    ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
+    runnableExamples:
+      var arrai = @[1, 2, 3]
+      assert arrai.shift() == 1
+      assert arrai == @[2, 3]
+
+  func queueMicrotask*(function: proc) {.importjs: "$1(#)".} =
+    ## * https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask
+    ## * https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
+    runnableExamples"-r:off": queueMicrotask(proc() = echo "Microtask")
diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim
index 937e3727b..d50d58ae5 100644
--- a/lib/js/jsffi.nim
+++ b/lib/js/jsffi.nim
@@ -64,7 +64,7 @@ proc validJsName(name: string): bool =
     if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}:
       return false
 
-template mangleJsName(name: cstring): cstring =
+template mangleJsName(name: string): string =
   inc nameCounter
   "mangledName" & $nameCounter
 
@@ -163,28 +163,28 @@ macro jsFromAst*(n: untyped): untyped =
 proc `&`*(a, b: cstring): cstring {.importjs: "(# + #)".}
   ## Concatenation operator for JavaScript strings.
 
-proc `+`  *(x, y: JsObject): JsObject {.importjs: "(# + #)".}
-proc `-`  *(x, y: JsObject): JsObject {.importjs: "(# - #)".}
-proc `*`  *(x, y: JsObject): JsObject {.importjs: "(# * #)".}
-proc `/`  *(x, y: JsObject): JsObject {.importjs: "(# / #)".}
-proc `%`  *(x, y: JsObject): JsObject {.importjs: "(# % #)".}
-proc `+=` *(x, y: JsObject): JsObject {.importjs: "(# += #)", discardable.}
-proc `-=` *(x, y: JsObject): JsObject {.importjs: "(# -= #)", discardable.}
-proc `*=` *(x, y: JsObject): JsObject {.importjs: "(# *= #)", discardable.}
-proc `/=` *(x, y: JsObject): JsObject {.importjs: "(# /= #)", discardable.}
-proc `%=` *(x, y: JsObject): JsObject {.importjs: "(# %= #)", discardable.}
-proc `++` *(x:    JsObject): JsObject {.importjs: "(++#)".}
-proc `--` *(x:    JsObject): JsObject {.importjs: "(--#)".}
-proc `>`  *(x, y: JsObject): JsObject {.importjs: "(# > #)".}
-proc `<`  *(x, y: JsObject): JsObject {.importjs: "(# < #)".}
-proc `>=` *(x, y: JsObject): JsObject {.importjs: "(# >= #)".}
-proc `<=` *(x, y: JsObject): JsObject {.importjs: "(# <= #)".}
-proc `**` *(x, y: JsObject): JsObject {.importjs: "((#) ** #)".}
+proc `+`*(x, y: JsObject): JsObject {.importjs: "(# + #)".}
+proc `-`*(x, y: JsObject): JsObject {.importjs: "(# - #)".}
+proc `*`*(x, y: JsObject): JsObject {.importjs: "(# * #)".}
+proc `/`*(x, y: JsObject): JsObject {.importjs: "(# / #)".}
+proc `%`*(x, y: JsObject): JsObject {.importjs: "(# % #)".}
+proc `+=`*(x, y: JsObject): JsObject {.importjs: "(# += #)", discardable.}
+proc `-=`*(x, y: JsObject): JsObject {.importjs: "(# -= #)", discardable.}
+proc `*=`*(x, y: JsObject): JsObject {.importjs: "(# *= #)", discardable.}
+proc `/=`*(x, y: JsObject): JsObject {.importjs: "(# /= #)", discardable.}
+proc `%=`*(x, y: JsObject): JsObject {.importjs: "(# %= #)", discardable.}
+proc `++`*(x:    JsObject): JsObject {.importjs: "(++#)".}
+proc `--`*(x:    JsObject): JsObject {.importjs: "(--#)".}
+proc `>`*(x, y: JsObject): JsObject {.importjs: "(# > #)".}
+proc `<`*(x, y: JsObject): JsObject {.importjs: "(# < #)".}
+proc `>=`*(x, y: JsObject): JsObject {.importjs: "(# >= #)".}
+proc `<=`*(x, y: JsObject): JsObject {.importjs: "(# <= #)".}
+proc `**`*(x, y: JsObject): JsObject {.importjs: "((#) ** #)".}
   # (#) needed, refs https://github.com/nim-lang/Nim/pull/16409#issuecomment-760550812
 proc `and`*(x, y: JsObject): JsObject {.importjs: "(# && #)".}
-proc `or` *(x, y: JsObject): JsObject {.importjs: "(# || #)".}
+proc `or`*(x, y: JsObject): JsObject {.importjs: "(# || #)".}
 proc `not`*(x:    JsObject): JsObject {.importjs: "(!#)".}
-proc `in` *(x, y: JsObject): JsObject {.importjs: "(# in #)".}
+proc `in`*(x, y: JsObject): JsObject {.importjs: "(# in #)".}
 
 proc `[]`*(obj: JsObject, field: cstring): JsObject {.importjs: getImpl.}
   ## Returns the value of a property of name `field` from a JsObject `obj`.
@@ -227,36 +227,40 @@ macro `.`*(obj: JsObject, field: untyped): JsObject =
     assert obj.a.to(int) == 20
   if validJsName($field):
     let importString = "#." & $field
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject): JsObject
-        {.importjs: `importString`, gensym.}
-      helper(`obj`)
+      proc `helperName`(o: JsObject): JsObject
+        {.importjs: `importString`.}
+      `helperName`(`obj`)
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     let importString = "#." & mangledNames[$field]
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject): JsObject
-        {.importjs: `importString`, gensym.}
-      helper(`obj`)
+      proc `helperName`(o: JsObject): JsObject
+        {.importjs: `importString`.}
+      `helperName`(`obj`)
 
 macro `.=`*(obj: JsObject, field, value: untyped): untyped =
   ## Experimental dot accessor (set) for type JsObject.
   ## Sets the value of a property of name `field` in a JsObject `x` to `value`.
   if validJsName($field):
     let importString = "#." & $field & " = #"
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject, v: auto)
-        {.importjs: `importString`, gensym.}
-      helper(`obj`, `value`)
+      proc `helperName`(o: JsObject, v: auto)
+        {.importjs: `importString`.}
+      `helperName`(`obj`, `value`)
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     let importString = "#." & mangledNames[$field] & " = #"
+    let helperName = genSym(nskProc, "helper")
     result = quote do:
-      proc helper(o: JsObject, v: auto)
-        {.importjs: `importString`, gensym.}
-      helper(`obj`, `value`)
+      proc `helperName`(o: JsObject, v: auto)
+        {.importjs: `importString`.}
+      `helperName`(`obj`, `value`)
 
 macro `.()`*(obj: JsObject,
              field: untyped,
@@ -268,26 +272,26 @@ macro `.()`*(obj: JsObject,
   ## so be careful when using this.)
   ##
   ## Example:
-  ##
-  ## .. code-block:: nim
-  ##
-  ##  # Let's get back to the console example:
-  ##  var console {.importc, nodecl.}: JsObject
-  ##  let res = console.log("I return undefined!")
-  ##  console.log(res) # This prints undefined, as console.log always returns
-  ##                   # undefined. Thus one has to be careful, when using
-  ##                   # JsObject calls.
+  ##   ```nim
+  ##   # Let's get back to the console example:
+  ##   var console {.importc, nodecl.}: JsObject
+  ##   let res = console.log("I return undefined!")
+  ##   console.log(res) # This prints undefined, as console.log always returns
+  ##                    # undefined. Thus one has to be careful, when using
+  ##                    # JsObject calls.
+  ##   ```
   var importString: string
   if validJsName($field):
     importString = "#." & $field & "(@)"
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field] & "(@)"
-  result = quote:
-    proc helper(o: JsObject): JsObject
-      {.importjs: `importString`, gensym, discardable.}
-    helper(`obj`)
+  let helperName = genSym(nskProc, "helper")
+  result = quote do:
+    proc `helperName`(o: JsObject): JsObject
+      {.importjs: `importString`, discardable.}
+    `helperName`(`obj`)
   for idx in 0 ..< args.len:
     let paramName = newIdentNode("param" & $idx)
     result[0][3].add newIdentDefs(paramName, newIdentNode("JsObject"))
@@ -302,12 +306,13 @@ macro `.`*[K: cstring, V](obj: JsAssoc[K, V],
     importString = "#." & $field
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field]
+  let helperName = genSym(nskProc, "helper")
   result = quote do:
-    proc helper(o: type(`obj`)): `obj`.V
-      {.importjs: `importString`, gensym.}
-    helper(`obj`)
+    proc `helperName`(o: type(`obj`)): `obj`.V
+      {.importjs: `importString`.}
+    `helperName`(`obj`)
 
 macro `.=`*[K: cstring, V](obj: JsAssoc[K, V],
                                     field: untyped,
@@ -319,12 +324,13 @@ macro `.=`*[K: cstring, V](obj: JsAssoc[K, V],
     importString = "#." & $field & " = #"
   else:
     if not mangledNames.hasKey($field):
-      mangledNames[$field] = $mangleJsName($field)
+      mangledNames[$field] = mangleJsName($field)
     importString = "#." & mangledNames[$field] & " = #"
+  let helperName = genSym(nskProc, "helper")
   result = quote do:
-    proc helper(o: type(`obj`), v: `obj`.V)
-      {.importjs: `importString`, gensym.}
-    helper(`obj`, `value`)
+    proc `helperName`(o: type(`obj`), v: `obj`.V)
+      {.importjs: `importString`.}
+    `helperName`(`obj`, `value`)
 
 macro `.()`*[K: cstring, V: proc](obj: JsAssoc[K, V],
                                            field: untyped,
@@ -348,8 +354,8 @@ iterator pairs*(obj: JsObject): (cstring, JsObject) =
   var k: cstring
   var v: JsObject
   {.emit: "for (var `k` in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
-  {.emit: "  `v`=`obj`[`k`];".}
+  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) { continue; }".}
+  {.emit: "  `v` = `obj`[`k`];".}
   yield (k, v)
   {.emit: "}".}
 
@@ -357,8 +363,8 @@ iterator items*(obj: JsObject): JsObject =
   ## Yields the `values` of each field in a JsObject, wrapped into a JsObject.
   var v: JsObject
   {.emit: "for (var k in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(k)) continue;".}
-  {.emit: "  `v`=`obj`[k];".}
+  {.emit: "  if (!`obj`.hasOwnProperty(k)) { continue; }".}
+  {.emit: "  `v` = `obj`[k];".}
   yield v
   {.emit: "}".}
 
@@ -366,7 +372,7 @@ iterator keys*(obj: JsObject): cstring =
   ## Yields the `names` of each field in a JsObject.
   var k: cstring
   {.emit: "for (var `k` in `obj`) {".}
-  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) continue;".}
+  {.emit: "  if (!`obj`.hasOwnProperty(`k`)) { continue; }".}
   yield k
   {.emit: "}".}
 
@@ -376,8 +382,8 @@ iterator pairs*[K: JsKey, V](assoc: JsAssoc[K, V]): (K,V) =
   var k: cstring
   var v: V
   {.emit: "for (var `k` in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
-  {.emit: "  `v`=`assoc`[`k`];".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) { continue; }".}
+  {.emit: "  `v` = `assoc`[`k`];".}
   yield (k.toJsKey(K), v)
   {.emit: "}".}
 
@@ -385,8 +391,8 @@ iterator items*[K, V](assoc: JsAssoc[K, V]): V =
   ## Yields the `values` in a JsAssoc.
   var v: V
   {.emit: "for (var k in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(k)) continue;".}
-  {.emit: "  `v`=`assoc`[k];".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(k)) { continue; }".}
+  {.emit: "  `v` = `assoc`[k];".}
   yield v
   {.emit: "}".}
 
@@ -394,7 +400,7 @@ iterator keys*[K: JsKey, V](assoc: JsAssoc[K, V]): K =
   ## Yields the `keys` in a JsAssoc.
   var k: cstring
   {.emit: "for (var `k` in `assoc`) {".}
-  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) continue;".}
+  {.emit: "  if (!`assoc`.hasOwnProperty(`k`)) { continue; }".}
   yield k.toJsKey(K)
   {.emit: "}".}
 
@@ -407,21 +413,20 @@ macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
   ##
   ## Example:
   ##
-  ## .. code-block:: nim
+  ##   ```nim
+  ##   # Let's say we have a type with a ton of fields, where some fields do not
+  ##   # need to be set, and we do not want those fields to be set to `nil`:
+  ##   type
+  ##     ExtremelyHugeType = ref object
+  ##       a, b, c, d, e, f, g: int
+  ##       h, i, j, k, l: cstring
+  ##       # And even more fields ...
   ##
-  ##  # Let's say we have a type with a ton of fields, where some fields do not
-  ##  # need to be set, and we do not want those fields to be set to `nil`:
-  ##  type
-  ##    ExtremelyHugeType = ref object
-  ##      a, b, c, d, e, f, g: int
-  ##      h, i, j, k, l: cstring
-  ##      # And even more fields ...
-  ##
-  ##  let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 }
-  ##
-  ##  # This generates roughly the same JavaScript as:
-  ##  {.emit: "var obj = {a: 1, k: "foo", d: 42};".}
+  ##   let obj = ExtremelyHugeType{ a: 1, k: "foo".cstring, d: 42 }
   ##
+  ##   # This generates roughly the same JavaScript as:
+  ##   {.emit: "var obj = {a: 1, k: "foo", d: 42};".}
+  ##   ```
   let a = ident"a"
   var body = quote do:
     var `a` {.noinit.}: `typ`
@@ -463,7 +468,7 @@ proc replaceSyms(n: NimNode): NimNode =
     for i in 0..<n.len:
       result[i] = replaceSyms(n[i])
 
-macro bindMethod*(procedure: typed): auto =
+macro bindMethod*(procedure: typed): auto {.deprecated: "Don't use it with closures".} =
   ## Takes the name of a procedure and wraps it into a lambda missing the first
   ## argument, which passes the JavaScript builtin `this` as the first
   ## argument to the procedure. Returns the resulting lambda.
@@ -471,24 +476,25 @@ macro bindMethod*(procedure: typed): auto =
   ## Example:
   ##
   ## We want to generate roughly this JavaScript:
-  ##
-  ## .. code-block:: js
-  ##  var obj = {a: 10};
-  ##  obj.someMethod = function() {
-  ##    return this.a + 42;
-  ##  };
+  ##   ```js
+  ##   var obj = {a: 10};
+  ##   obj.someMethod = function() {
+  ##     return this.a + 42;
+  ##   };
+  ##   ```
   ##
   ## We can achieve this using the `bindMethod` macro:
   ##
-  ## .. code-block:: nim
-  ##  let obj = JsObject{ a: 10 }
-  ##  proc someMethodImpl(that: JsObject): int =
-  ##    that.a.to(int) + 42
-  ##  obj.someMethod = bindMethod someMethodImpl
+  ##   ```nim
+  ##   let obj = JsObject{ a: 10 }
+  ##   proc someMethodImpl(that: JsObject): int =
+  ##     that.a.to(int) + 42
+  ##   obj.someMethod = bindMethod someMethodImpl
   ##
-  ##  # Alternatively:
-  ##  obj.someMethod = bindMethod
-  ##    proc(that: JsObject): int = that.a.to(int) + 42
+  ##   # Alternatively:
+  ##   obj.someMethod = bindMethod
+  ##     proc(that: JsObject): int = that.a.to(int) + 42
+  ##   ```
   if not (procedure.kind == nnkSym or procedure.kind == nnkLambda):
     error("Argument has to be a proc or a symbol corresponding to a proc.")
   var
diff --git a/lib/js/jsre.nim b/lib/js/jsre.nim
index 7d51db646..2d931eb20 100644
--- a/lib/js/jsre.nim
+++ b/lib/js/jsre.nim
@@ -20,6 +20,7 @@ type RegExp* = ref object of JsRoot
   lastParen*: cstring    ## Ditto.
   leftContext*: cstring  ## Ditto.
   rightContext*: cstring ## Ditto.
+  hasIndices*: bool      ## https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/hasIndices
 
 
 func newRegExp*(pattern: cstring; flags: cstring): RegExp {.importjs: "new RegExp(@)".}
@@ -30,7 +31,19 @@ func newRegExp*(pattern: cstring): RegExp {.importjs: "new RegExp(@)".}
 func compile*(self: RegExp; pattern: cstring; flags: cstring) {.importjs: "#.compile(@)".}
   ## Recompiles a regular expression during execution of a script.
 
-func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "#.exec(#)".}
+func replace*(pattern: cstring; self: RegExp; replacement: cstring): cstring {.importjs: "#.replace(#, #)".}
+  ## Returns a new string with some or all matches of a pattern replaced by given replacement
+
+func replace*(pattern: cstring, self: RegExp, cb: proc (args: varargs[cstring]): cstring): cstring {.importcpp.}
+  ## Returns a new string with some or all matches of a pattern replaced by given callback function
+
+func split*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.split(#) || [])".}
+  ## Divides a string into an ordered list of substrings and returns the array
+
+func match*(pattern: cstring; self: RegExp): seq[cstring] {.importjs: "(#.match(#) || [])".}
+  ## Returns an array of matches of a RegExp against given string
+
+func exec*(self: RegExp; pattern: cstring): seq[cstring] {.importjs: "(#.exec(#) || [])".}
   ## Executes a search for a match in its string parameter.
 
 func toCstring*(self: RegExp): cstring {.importjs: "#.toString()".}
@@ -38,10 +51,6 @@ func toCstring*(self: RegExp): cstring {.importjs: "#.toString()".}
 
 func `$`*(self: RegExp): string = $toCstring(self)
 
-func test*(self: RegExp; pattern: cstring): bool {.importjs: "#.test(#)", deprecated: "Use contains instead".}
-
-func toString*(self: RegExp): cstring {.importjs: "#.toString()", deprecated: "Use toCstring instead".}
-
 func contains*(pattern: cstring; self: RegExp): bool =
   ## Tests for a substring match in its string parameter.
   runnableExamples:
@@ -49,7 +58,21 @@ func contains*(pattern: cstring; self: RegExp): bool =
     assert jsregex in r"abc"
     assert jsregex notin r"abcd"
     assert "xabc".contains jsregex
-  asm "`result` = `self`.test(`pattern`);"
+  {.emit: "`result` = `self`.test(`pattern`);".}
+
+func startsWith*(pattern: cstring; self: RegExp): bool =
+  ## Tests if string starts with given RegExp
+  runnableExamples:
+    let jsregex: RegExp = newRegExp(r"abc", r"i")
+    assert "abcd".startsWith jsregex
+  pattern.contains(newRegExp(("^" & $(self.source)).cstring, self.flags))
+
+func endsWith*(pattern: cstring; self: RegExp): bool =
+  ## Tests if string ends with given RegExp
+  runnableExamples:
+    let jsregex: RegExp = newRegExp(r"bcd", r"i")
+    assert "abcd".endsWith jsregex
+  pattern.contains(newRegExp(($(self.source) & "$").cstring, self.flags))
 
 
 runnableExamples:
@@ -61,3 +84,14 @@ runnableExamples:
   jsregex.compile(r"[0-9]", r"i")
   assert "0123456789abcd".contains jsregex
   assert $jsregex == "/[0-9]/i"
+  jsregex.compile(r"abc", r"i")
+  assert "abcd".startsWith jsregex
+  assert "dabc".endsWith jsregex
+  jsregex.compile(r"\d", r"i")
+  assert "do1ne".split(jsregex) == @["do".cstring, "ne".cstring]
+  jsregex.compile(r"[lw]", r"i")
+  assert "hello world".replace(jsregex,"X") == "heXlo world"
+  jsregex.compile(r"([a-z])\1*", r"g")
+  assert "abbcccdddd".replace(jsregex, proc (m: varargs[cstring]): cstring = ($m[0] & $(m.len)).cstring) == "a1b2c3d4"
+  let digitsRegex: RegExp = newRegExp(r"\d")
+  assert "foo".match(digitsRegex) == @[]