------------------------------------------------------------- AJAX method itself
function jajax(o,done,fail,_u) {
            let xhr = jajax.createXHR(FALSE);
            if ( !xhr ) {
                fail && fail(NULL, 'xhr', `Couldn't create XHR object`);
                return xhr;
            }

            if ( type(o) == 'String' ) {
                if ( done && type(done) == 'Object' ) {
                    done.url = o;
                    o    = done;
                    done = fail;
                    fail = _u;
                }
                else {
                    o = { url: o };
                }
            }

            o = extend({
                url        : '',    // String
                type       : 'GET', // String - alias of .method
                cache      : FALSE, // Bool
                timeout    : NULL,  // Number
                contentType: 'application/x-www-form-urlencoded; charset=UTF-8', // String
                dataType   : '',    // String
                mimeType   : '',    // String
                data       : NULL,  // Object
                headers    : NULL,  // Object
                error      : NULL,  // Function
                success    : NULL   // Function
            }, o);

            var headers = {}
            ,   setHeader = (name, value) => { headers[name.toLowerCase()] = [name, value]; }
            ,   method = o.method || o.type
            ,   url = o.url
            ,   abort_to
            ,   responseType
            ,   resolve, reject, then, _catch
            ,   onerror = function (error, type, xh, res) {
                    if(o.error) {
                        o.error(xh || res, type, error, res);
                    }
                    if(fail) {
                        fail(xh || res, type, error, res);
                    }
                    if ( reject ) {
                        if ( !error ) {
                            if ( type ) {
                                error = new Error(`jajax error: ${type}`);
                                error.type = type;
                            }
                            else {
                                error = new Error(`jajax error: unknown`);
                            }
                            error.xhr = xh || res;
                            error.response = res;
                        }
                        reject(error);
                    }
                }
            ,   onsuccecc = function (xh, result, res) {
                    var dataType = o.dataType || o.mimeType && jajax.mimeToDataType(o.mimeType) || responseType
                    ,   error
                    ;
                    try {
                        switch(dataType) {
                            case 'json': {
                                result = 'responseJSON' in xh ? xh.responseJSON : (/^\s*$/.test(result) ? NULL : jajax.parseJSON(result));
                            } break;

                            case 'xml': {
                                result = xh.responseXML;
                            } break;

                            case 'script': {
                                // http://perfectionkills.com/global-eval-what-are-the-options/
                                (1,eval)(result); //jshint ignore:line
                            } break;

                            case 'text': /*falls through*/
                            default: {
                                // result = xh.responseText
                            } break;
                        }
                    } catch (e) { error = e; }

                    if (error) {
                        onerror(error, 'parsererror', xh, res);
                    }
                    else {
                        if (o.success) {
                            o.success(result, xh.statusText, xh, res);
                        }
                        if(done) {
                            done(result, xh.statusText, xh, res);
                        }
                        if ( resolve ) {
                            resolve(result);
                        }
                    }
                }
            ,   oncomplete = function (xh, res) {
                    abort_to && clearTimeout(abort_to);

                    let response = xh.responseText || xh.response;

                    // responseType from response headers
                    responseType = jajax.mimeToDataType(xh.getResponseHeader('content-type'));

                    // If responseType is 'json', make sure there is xh.responseJSON
                    if ( responseType == 'json' && !('responseJSON' in xh) ) {
                        if ( /^\s*$/.test(response) ) {
                            xh.responseJSON = NULL;
                        }
                        else {
                            try {
                                xh.responseJSON = jajax.parseJSON(response);
                            }
                            catch(err) {}
                        }
                    }


                    if( (xh.status >= 200 && xh.status < 300) || xh.status == 304 ) {
                        onsuccecc(xh, response, res);
                    }
                    else {
                        onerror(xh.statusText || NULL, xh.status ? 'error' : 'abort', xh, res);
                    }
                }
            ;

            if ( typeof Promise == 'function' ) {
                let prom = new Promise((_resolve, _reject) => {
                    resolve = _resolve;
                    reject = _reject;
                });
                then = prom.then.bind(prom);
                _catch = prom.catch.bind(prom);
            }

            each({'X-Requested-With': 'XMLHttpRequest'}, setHeader);

            if(o.contentType) {
                setHeader('Content-Type', o.contentType);
            }
            setHeader('Accept', (accepts[o.dataType] ? accepts[o.dataType] + ', ': '') + allTypes + '; q=0.01');

            each(o.headers, setHeader);

            method = method.toLowerCase();

            if ( o.data && (method == 'get' || method == 'head') ) {
                url += (url.indexOf('?') < 0 ? '?' : '&') + jajax.param(o.data);
                o.data = NULL;
            }

            if(jajax.xhr_type == 'REQUEST') {
                each(o.data, function (n,v,d) { if(v == NULL) delete d[n]; });
                var h = {
                        url,
                        content: o.data || {},
                        headers: {},
                        onComplete(res) {
                            if(h.aborted) return;
                            var _xhr = {
                                getResponseHeader(name) {
                                    return res.headers[name];
                                },
                                status: res.status,
                                statusText: res.statusText,
                                responseText: res.text,
                                responseJSON: res.json
                            };
                            oncomplete(_xhr, res);
                        }
                    }
                ;
                each(headers, (i,a) => { if(a[0] && a[0] != NULL) h.headers[a[0]] = a[1]; });
                if(o.contentType) h.contentType = o.contentType;

                if (o.timeout > 0) abort_to = setTimeout(() => {
                    h.aborted = now();
                    // xhr.abort(); // @todo: abort request
                    onerror(NULL, 'timeout', xhr);
                }, o.timeout);

                let _xhr;
                try {
                    _xhr = xhr.Request(h);
                    _xhr[method]();
                }
                catch(er) {
                    // If method not supported. try to override it
                    try {
                        h.headers['X-HTTP-Method-Override'] = method.toUpperCase();
                        _xhr = xhr.Request(h);
                        _xhr.post();
                    }
                    catch(er) {
                        // If override doesn't work, try XHR - synchronous in FF
                        switch (method) {
                            case 'delete': {
                                // Firefox REQUEST object doesn't support DELETE,
                                // need to create manual xhr object
                                const XHR = require("sdk/net/xhr");
                                _xhr = new XHR.XMLHttpRequest();
                                _xhr.open(method, url);
                                each(h.headers, (n,v) => { _xhr.setRequestHeader(n, v); });
                                _xhr.send();
                                oncomplete(_xhr);
                            } break;
                            default: throw er;
                        }
                    }
                }
                if ( _xhr ) {
                    xhr = _xhr;
                }
            }
            else {
                xhr.open(method, url, TRUE);

                each(headers, (i,a) => { if(a[1] != NULL) xhr.setRequestHeader( a[0], a[1] + NIL ); });

                if (o.timeout > 0) abort_to = setTimeout(() => {
                    xhr.onreadystatechange = noop;
                    xhr.abort();
                    onerror(NULL, 'timeout', xhr);
                }, o.timeout);

                const callback = () => {
                    if (xhr.readyState == 4) {
                        xhr.onreadystatechange = noop;
                        oncomplete(xhr);
                    }
                };

                xhr.send(o.data ? typeof o.data == 'string' ? o.data : jajax.param(o.data) : NULL);

                if(xhr.readyState === 4) {
                    // (IE6 & IE7) if it's in cache and has been
                    // retrieved directly we need to fire the callback
                    setTimeout( callback );
                }
                else {
                    xhr.onreadystatechange = callback;
                }
            }

            if ( then && !xhr.then ) {
                xhr.then = then;
                if ( _catch ) xhr.catch = _catch;
            }

            return xhr;
        }