about summary refs log tree commit diff stats
path: root/src/html/xmlhttprequest.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/html/xmlhttprequest.nim')
-rw-r--r--src/html/xmlhttprequest.nim148
1 files changed, 115 insertions, 33 deletions
diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim
index 16b6e012..ed1b7035 100644
--- a/src/html/xmlhttprequest.nim
+++ b/src/html/xmlhttprequest.nim
@@ -7,6 +7,7 @@ import html/catom
 import html/dom
 import html/event
 import html/script
+import io/dynstream
 import io/promise
 import js/domexception
 import loader/headers
@@ -55,16 +56,17 @@ type
     response: Response
     responseType {.jsget.}: XMLHttpRequestResponseType
     timeout {.jsget.}: uint32
+    received: string
 
   ProgressEvent = ref object of Event
     lengthComputable {.jsget.}: bool
-    loaded {.jsget.}: uint32
-    total {.jsget.}: uint32
+    loaded {.jsget.}: int64 #TODO should be uint64
+    total {.jsget.}: int64 #TODO ditto
 
   ProgressEventInit = object of EventInit
     lengthComputable: bool
-    loaded: uint32
-    total: uint32
+    loaded: int64
+    total: int64
 
 jsDestructor(XMLHttpRequestEventTarget)
 jsDestructor(XMLHttpRequestUpload)
@@ -106,16 +108,25 @@ proc parseMethod(s: string): DOMResult[HttpMethod] =
   else:
     errDOMException("Invalid method", "SyntaxError")
 
-#TODO the standard says that no async should be treated differently from
-# undefined. idk if (and where) this actually matters.
 proc open(ctx: JSContext; this: XMLHttpRequest; httpMethod, url: string;
-    async = true; username = ""; password = ""): Err[DOMException] {.jsfunc.} =
+    misc: varargs[JSValue]): Err[DOMException] {.jsfunc.} =
   let httpMethod = ?parseMethod(httpMethod)
   let global = ctx.getGlobal()
   let x = parseURL(url, some(global.document.baseURL))
   if x.isNone:
     return errDOMException("Invalid URL", "SyntaxError")
   let parsedURL = x.get
+  var async = true
+  if misc.len > 0: # standard weirdness
+    ?ctx.fromJS(misc[0], async)
+    if misc.len > 1 and not JS_IsNull(misc[1]):
+      var username: string
+      ?ctx.fromJS(misc[1], username)
+      parsedURL.setUsername(username)
+    if misc.len > 2 and not JS_IsNull(misc[2]):
+      var password: string
+      ?ctx.fromJS(misc[2], password)
+      parsedURL.setPassword(password)
   if not async and ctx.getWindow() != nil and
       (this.timeout != 0 or this.responseType != xhrtUnknown):
     return errDOMException("Today's horoscope: don't go outside",
@@ -160,7 +171,7 @@ proc setRequestHeader(this: XMLHttpRequest; name, value: string):
   ok()
 
 proc fireProgressEvent(window: Window; target: EventTarget; name: StaticAtom;
-    loaded, length: uint32) =
+    loaded, length: int64) =
   let event = newProgressEvent(window.factory.toAtom(name), ProgressEventInit(
     loaded: loaded,
     total: length,
@@ -177,15 +188,15 @@ proc errorSteps(window: Window; this: XMLHttpRequest; name: StaticAtom) =
   this.readyState = xhrsDone
   this.response = makeNetworkError()
   this.flags.excl(xhrfSend)
-  #TODO sync?
-  window.fireEvent(satReadystatechange, this)
-  if xhrfUploadComplete notin this.flags:
-    this.flags.incl(xhrfUploadComplete)
-    if xhrfUploadListener in this.flags:
-      window.fireProgressEvent(this.upload, name, 0, 0)
-      window.fireProgressEvent(this.upload, satLoadend, 0, 0)
-  window.fireProgressEvent(this, name, 0, 0)
-  window.fireProgressEvent(this, satLoadend, 0, 0)
+  if xhrfSync notin this.flags:
+    window.fireEvent(satReadystatechange, this)
+    if xhrfUploadComplete notin this.flags:
+      this.flags.incl(xhrfUploadComplete)
+      if xhrfUploadListener in this.flags:
+        window.fireProgressEvent(this.upload, name, 0, 0)
+        window.fireProgressEvent(this.upload, satLoadend, 0, 0)
+    window.fireProgressEvent(this, name, 0, 0)
+    window.fireProgressEvent(this, satLoadend, 0, 0)
 
 proc handleErrors(window: Window; this: XMLHttpRequest): DOMException =
   if xhrfSend notin this.flags:
@@ -204,7 +215,52 @@ proc handleErrors(window: Window; this: XMLHttpRequest): DOMException =
       return newDOMException("Network error in XHR", "NetworkError")
   return nil
 
-proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): DOMResult[void]
+type XHROpaque = ref object of RootObj
+  this: XMLHttpRequest
+  window: Window
+  len: int64 #TODO should be uint64
+
+proc onReadXHR(response: Response) =
+  const BufferSize = 4096
+  let opaque = XHROpaque(response.opaque)
+  let this = opaque.this
+  let window = opaque.window
+  while true:
+    try:
+      let olen = this.received.len
+      this.received.setLen(olen + BufferSize)
+      let n = response.body.recvData(addr this.received[olen], BufferSize)
+      if n < BufferSize:
+        this.received.setLen(olen + n)
+      if n == 0:
+        break
+    except ErrorAgain:
+      break
+  if this.readyState == xhrsHeadersReceived:
+    this.readyState = xhrsLoading
+  window.fireEvent(satReadystatechange, this)
+  window.fireProgressEvent(this, satProgress, int64(this.received.len),
+    opaque.len)
+
+proc onFinishXHR(response: Response; success: bool) =
+  let opaque = XHROpaque(response.opaque)
+  let this = opaque.this
+  let window = opaque.window
+  if success:
+    discard window.handleErrors(this)
+    if response.responseType != rtError:
+      let recvLen = int64(this.received.len)
+      window.fireProgressEvent(this, satProgress, recvLen, opaque.len)
+      this.readyState = xhrsDone
+      this.flags.excl(xhrfSend)
+      window.fireEvent(satReadystatechange, this)
+      window.fireProgressEvent(this, satLoad, recvLen, opaque.len)
+      window.fireProgressEvent(this, satLoadend, recvLen, opaque.len)
+  else:
+    this.response = makeNetworkError()
+    discard window.handleErrors(this)
+
+proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): JSResult[void]
     {.jsfunc.} =
   ?this.checkOpened()
   ?this.checkSendFlag()
@@ -243,19 +299,44 @@ proc send(ctx: JSContext; this: XMLHttpRequest; body = JS_NULL): DOMResult[void]
     let v = ctx.toJS(jsRequest)
     let p = window.windowFetch(v)
     JS_FreeValue(ctx, v)
-    if p.isSome:
-      p.get.then(proc(res: JSResult[Response]) =
-        if res.isNone:
-          this.response = makeNetworkError()
-          discard window.handleErrors(this)
-          return
-        let response = res.get
-        this.response = response
-        this.readyState = xhrsHeadersReceived
-        window.fireEvent(satReadystatechange, this)
-      )
+    if p.isNone:
+      return err(p.error)
+    p.get.then(proc(res: JSResult[Response]) =
+      if res.isNone:
+        this.response = makeNetworkError()
+        discard window.handleErrors(this)
+        return
+      let response = res.get
+      this.response = response
+      this.readyState = xhrsHeadersReceived
+      window.fireEvent(satReadystatechange, this)
+      if this.readyState != xhrsHeadersReceived:
+        return
+      let len = max(response.getContentLength(), 0)
+      response.opaque = XHROpaque(this: this, window: window, len: len)
+      response.onRead = onReadXHR
+      response.onFinish = onFinishXHR
+      #TODO timeout
+    )
   else: # sync
-    discard #TODO
+    #TODO cors requests?
+    if window.settings.origin.isSameOrigin(request.url.origin):
+      let response = window.loader.doRequest(request)
+      if response.res == 0:
+        #TODO timeout
+        try:
+          this.received = response.body.recvAll()
+          #TODO report timing
+          let len = max(response.getContentLength(), 0)
+          response.opaque = XHROpaque(this: this, window: window, len: len)
+          response.onFinishXHR(true)
+          return ok()
+        except IOError:
+          discard
+    let ex = window.handleErrors(this)
+    this.response = makeNetworkError()
+    if ex != nil:
+      return err(ex)
   ok()
 
 #TODO abort
@@ -269,9 +350,10 @@ proc status(this: XMLHttpRequest): uint16 {.jsfget.} =
 proc statusText(this: XMLHttpRequest): string {.jsfget.} =
   return ""
 
-proc getResponseHeader(this: XMLHttpRequest; name: string): string {.jsfunc.} =
-  #TODO ?
-  return this.response.headers.table.getOrDefault(name)[0]
+proc getResponseHeader(ctx: JSContext; this: XMLHttpRequest; name: string):
+    JSValue {.jsfunc.} =
+  #TODO this can throw, but I don't think the standard allows that?
+  return ctx.get(this.response.headers, name)
 
 #TODO getAllResponseHeaders