XMLHttpRequest onprogress chronicles
Tags: frontend, javascript, api, webdev
Published: 2019-01-10 04:22:20

---
title: XMLHttpRequest onprogress chronicles
published: true
tags: frontend, javascript, api, webdev
cover_image: https://thepracticaldev.s3.amazonaws.com/i/mcgjlq2lhzxsx403isu8.png
series: API archaeology
---
## Preamble

When you want to implement something in a cross-browser way, you are in for a ride down the bugtracker hole. After some ~~exhausting~~thorough research, I felt the urge to share my findings on `XMLHttpRequest.prototype.onprogress`.

## Rationale—why fetch doesn't cut it

Before going further, I'd like to explain why I prefer `XMLHttpRequest` over `fetch` for download monitoring: browser vendors didn't ship `Response.prototype.body` from the get go i.e. **`fetch` didn't support it initially**.
```
interface ProgressEvent : Event {
  readonly attribute boolean lengthComputable;
  readonly attribute unsigned long long loaded;
  readonly attribute unsigned long long total;
};
```
And _even_ if the browsers that you currently target do provide that readable stream, `XMLHttpRequest` would remain the superior choice for an arcane discrepancy: when the `content-length` response header is present _but not exposed_, `total` will be populated with the response body's size **irregardless of the `Access-Control-Expose-Headers` field's value**.

## Genesis

```
interface LSProgressEvent : Event {
  readonly attribute unsigned long position;
  readonly attribute unsigned long totalSize;
};
```
Its first incarnation was implemented by Firefox 0.9.3! Back then the [`ProgressEvent` interface](https://www.w3.org/TR/progress-events/) didn't exist so they relied on the little known [`LSProgressEvent` interface](https://www.w3.org/TR/DOM-Level-3-LS/load-save.html#LS-LSProgressEvent); to remain compatible WebKit had to support both interfaces until Mozilla finally dropped the latter.
```
interface XMLHttpRequest : XMLHttpRequestEventTarget {
  …
  attribute EventHandler onprogress;
  attribute EventHandler onreadystatechange;
  …
};
```
For other browsers you had to fallback on `XMLHttpRequest.prototype.onreadystatechange` which had its own [shortcomings](https://www.quirksmode.org/blog/archives/2005/09/xmlhttp_notes_r_2.html). Sadly, the native version of `XMLHttpRequest` introduced in Internet Explorer 7 [didn't expose partial results](https://msdn.microsoft.com/en-us/windows/ms534361(v=vs.71)#readystate_interactive).

## Browsers' Defects

### Mozilla

Probably due to their early implementation, Gecko-powered browsers had many bugs to account for, notably:

- until version 9, the `addEventListener` variant of `onprogress` wasn't supported
- between version 3.5 and 8, you had to fallback on the `onload` handler to compensate for the inane absence of the last progress event that used to be fired by `onprogress` when it reached the 100% mark
- until version 34, when a `Content-Encoding` response header field was present the `loaded` property reflected the number of bytes _after_ decompression instead of the raw bytes transferred which resulted—if a `Content-Length` was sent by the server—in `loaded` exceeding `total` once all the data was received

### Microsoft

Internet Explorer 8 brought the non-standard `XDomainRequest.prototype.onprogress`. Since it didn't pass any arguments to the callback you had to track `XDomainRequest.prototype.responseText` from within the closure. We had to wait another 3 years for Internet Explorer 10 to finally support all XMLHttpRequest Level 2 events—progress included.

### WebKit/Blink

- if `lengthComputable === false`—i.e. the `Content-Length` response header is missing—`total` and `totalSize` used to return UINT64_MAX instead of `0`
- when the `Content-Encoding` is set, `total` erroneously returns `0` even if the `Content-Length` is positive

### Opera 12

```
interface XMLHttpRequest : XMLHttpRequestEventTarget {
  …
  void overrideMimeType(DOMString mime);
  attribute XMLHttpRequestResponseType responseType;
  …
};
```
For the `loaded` property to be accurate relative to the `total` property, the response body had to be treated as binary. To that end you had 2 possibilities:

- setting the `responseType` to either `"blob"` or `"arraybuffer"`
- tampering with the media type using `overrideMimeType`

## Why?!

If you are wondering why I know so much about these quirks, it comes down to me being the maintainer of [cb-fetch](https://github.com/Mouvedia/cb-fetch/), a **cross-browser** HTTP client that abstracts away all this mess for you. Well it does way more than that, by all means check it out!  
My goal is to reach **100 stars** on GitHub before the next release.

# Archaeology
![student](https://thepracticaldev.s3.amazonaws.com/i/r1i0bkux0zbmht4qlpca.jpg)
I consider myself an API archaeologist. Do you like that kind of exhaustive examination of a subject? Does it belong on dev.to?