diff --git a/mithril.js b/mithril.js index c2eb76e9..7f9ae2f1 100644 --- a/mithril.js +++ b/mithril.js @@ -203,6 +203,7 @@ var buildQueryString = function(object) { else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) } } +var FILE_PROTOCOL_REGEX = new RegExp('^file://', 'i') var _8 = function($window, Promise) { var callbackCount = 0 var oncompletion @@ -238,14 +239,20 @@ var _8 = function($window, Promise) { var promise0 = new Promise(function(resolve, reject) { if (args.method == null) args.method = "GET" args.method = args.method.toUpperCase() - var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE" + var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify if (typeof args.deserialize !== "function") args.deserialize = deserialize if (typeof args.extract !== "function") args.extract = extract args.url = interpolate(args.url, args.data) if (useBody) args.data = args.serialize(args.data) else args.url = assemble(args.url, args.data) - var xhr = new $window.XMLHttpRequest() + var xhr = new $window.XMLHttpRequest(), + aborted = false, + _abort = xhr.abort + xhr.abort = function abort() { + aborted = true + _abort.call(xhr) + } xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) if (args.serialize === JSON.stringify && useBody) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") @@ -259,12 +266,12 @@ var _8 = function($window, Promise) { } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { - // Don't throw errors on xhr.abort(). XMLHttpRequests ends up in a state of - // xhr.status == 0 and xhr.readyState == 4 if aborted after open, but before completion. - if (xhr.status && xhr.readyState === 4) { + // Don't throw errors on xhr.abort(). + if(aborted) return + if (xhr.readyState === 4) { try { var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) - if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { + if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { resolve(cast(args.type, response)) } else { @@ -795,6 +802,11 @@ var coreRenderer = function($window) { if (vnode.tag === "select" && key2 === "value" && vnode.dom.value == value && vnode.dom === $doc.activeElement) return //setting option[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "option" && key2 === "value" && vnode.dom.value == value) return + // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. + if (vnode.tag === "input" && key2 === "type") { + element.setAttribute(key2, value) + return + } element[key2] = value } else { diff --git a/mithril.min.js b/mithril.min.js index 7dbf8beb..732ad9c3 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1,42 +1,43 @@ -new function(){function w(a,d,h,f,g,l){return{tag:a,key:d,attrs:h,children:f,text:g,dom:l,domSize:void 0,state:{},events:void 0,instance:void 0,skip:!1}}function z(a){if(null==a||"string"!==typeof a&&"function"!==typeof a&&"function"!==typeof a.view)throw Error("The selector must be either a string or a component.");if("string"===typeof a&&void 0===H[a]){for(var d,h,f=[],g={};d=O.exec(a);){var l=d[1],k=d[2];""===l&&""!==k?h=k:"#"===l?g.id=k:"."===l?f.push(k):"["===d[3][0]&&((l=d[6])&&(l=l.replace(/\\(["'])/g, -"$1").replace(/\\\\/g,"\\")),"class"===d[4]?f.push(l):g[d[4]]=l||!0)}0c.indexOf("?")?"?":"&";c+=f+d}return c}function k(c){try{return""!==c?JSON.parse(c):null}catch(A){throw Error(c);}} -function m(c){return c.responseText}function n(c,a){if("function"===typeof c)if(Array.isArray(a))for(var d=0;dp.status||304===p.status)d(n(c.type,a));else{var q=Error(p.responseText),b;for(b in a)q[b]= -a[b];f(q)}}catch(e){f(e)}};h&&null!=c.data?p.send(c.data):p.send()});return!0===c.background?A:t(A)},jsonp:function(c,k){var m=h();c=f(c,k);var x=new d(function(d,f){var h=c.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+t++,k=a.document.createElement("script");a[h]=function(f){k.parentNode.removeChild(k);d(n(c.type,f));delete a[h]};k.onerror=function(){k.parentNode.removeChild(k);f(Error("JSONP request failed"));delete a[h]};null==c.data&&(c.data={});c.url=g(c.url,c.data);c.data[c.callbackKey|| -"callback"]=h;k.src=l(c.url,c.data);a.document.documentElement.appendChild(k)});return!0===c.background?x:m(x)},setCompletionCallback:function(a){x=a}}}(window,v),N=function(a){function d(a,b,e,c,d,f,g){for(;e=q&&A>=r;){var u=b[q],y=e[r];if(u!==y||c)if(null==u)q++;else if(null==y)r++;else if(u.key===y.key){var C=null!=t&&q>=b.length-t.length||null==t&&c;q++;r++;l(a,u,y,f,m(b,q,g),C,p);c&&u.tag===y.tag&&n(a,k(u),g)}else if(u=b[B],u!==y||c)if(null==u)B--;else if(null==y)r++;else if(u.key===y.key)C=null!=t&&B>=b.length-t.length||null==t&&c,l(a,u,y,f,m(b,B+1,g),C,p),(c||r=q&&A>=r;){u=b[B];y=e[A];if(u!==y||c)if(null==u)B--;else{if(null!=y)if(u.key===y.key)C=null!=t&&B>=b.length-t.length||null==t&&c,l(a,u,y,f,m(b,B+1,g),C,p),c&&u.tag===y.tag&&n(a,k(u),g),null!=u.dom&&(g=u.dom),B--;else{if(!E){E=b;var u=B,C={},v;for(v=0;vc.indexOf("?")?"?":"&";c+=f+d}return c}function k(c){try{return""!==c?JSON.parse(c):null}catch(t){throw Error(c); +}}function q(c){return c.responseText}function p(c,a){if("function"===typeof c)if(Array.isArray(a))for(var d=0;dn.status||304===n.status||R.test(c.url))d(p(c.type, +b));else{var m=Error(n.responseText),a;for(a in b)m[a]=b[a];f(m)}}catch(C){f(C)}};h&&null!=c.data?n.send(c.data):n.send()});return!0===c.background?t:r(t)},jsonp:function(c,k){var q=h();c=f(c,k);var u=new d(function(d,f){var h=c.callbackName||"_mithril_"+Math.round(1E16*Math.random())+"_"+r++,k=a.document.createElement("script");a[h]=function(f){k.parentNode.removeChild(k);d(p(c.type,f));delete a[h]};k.onerror=function(){k.parentNode.removeChild(k);f(Error("JSONP request failed"));delete a[h]};null== +c.data&&(c.data={});c.url=g(c.url,c.data);c.data[c.callbackKey||"callback"]=h;k.src=l(c.url,c.data);a.document.documentElement.appendChild(k)});return!0===c.background?u:q(u)},setCompletionCallback:function(a){u=a}}}(window,v),N=function(a){function d(a,b,c,d,f,g,k){for(;c=m&&t>=e;){var y=b[m],z=c[e];if(y!==z||f)if(null==y)m++;else if(null==z)e++;else if(y.key===z.key){var C=null!=x&&m>=b.length-x.length||null==x&&f;m++;e++;l(a,y,z,g,q(b,m,B),C,n);f&&y.tag===z.tag&&p(a,k(y),B)}else if(y=b[r],y!==z||f)if(null==y)r--;else if(null==z)e++;else if(y.key===z.key)C=null!=x&&r>=b.length-x.length||null==x&&f, +l(a,y,z,g,q(b,r+1,B),C,n),(f||e=m&&t>=e;){y=b[r];z=c[t];if(y!==z||f)if(null==y)r--;else{if(null!=z)if(y.key===z.key)C=null!=x&&r>=b.length-x.length||null==x&&f,l(a,y,z,g,q(b,r+1,B),C,n),f&&y.tag===z.tag&&p(a,k(y),B),null!=y.dom&&(B=y.dom),r--;else{if(!E){E=b;var y=r,C={},v;for(v=0;v= 200 && xhr.status < 300) || xhr.status === 304) { + if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { resolve(cast(args.type, response)) } else { diff --git a/request/tests/test-request.js b/request/tests/test-request.js index ade0465c..273d592c 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -390,6 +390,56 @@ o.spec("xhr", function() { o(xhr.getRequestHeader("Accept")).equals("application/json, text/*") } }) + o("doesn't fail on abort", function(done) { + var s = new Date + mock.$defineRoutes({ + "GET /item": function() { + return {status: 200, responseText: JSON.stringify({a: 1})} + } + }) + + var failed = false + var resolved = false + function handleAbort(xhr) { + var onreadystatechange = xhr.onreadystatechange // probably not set yet + var testonreadystatechange = function() { + onreadystatechange.call(xhr) + setTimeout(function() { // allow promises to (not) resolve first + o(failed).equals(false) + o(resolved).equals(false) + done() + }, 0) + } + Object.defineProperty(xhr, 'onreadystatechange', { + set: function(val) { onreadystatechange = val } + , get: function() { return testonreadystatechange } + }) + xhr.abort() + } + xhr({method: "GET", url: "/item", config: handleAbort}).catch(function() { + failed = true + }) + .then(function() { + resolved = true + }) + }) + o("doesn't fail on file:// status 0", function(done) { + var s = new Date + mock.$defineRoutes({ + "GET /item": function() { + return {status: 0, responseText: JSON.stringify({a: 1})} + } + }) + var failed = false + xhr({method: "GET", url: "file:///item"}).catch(function() { + failed = true + }).then(function(data) { + o(failed).equals(false) + o(data).deepEquals({a: 1}) + }).then(function() { + done() + }) + }) /*o("data maintains after interpolate", function() { mock.$defineRoutes({ "PUT /items/:x": function() { @@ -463,5 +513,15 @@ o.spec("xhr", function() { }) }) }) + o("rejects on cors-like error", function(done) { + mock.$defineRoutes({ + "GET /item": function(request) { + return {status: 0} + } + }) + xhr({method: "GET", url: "/item"}).catch(function(e) { + o(e instanceof Error).equals(true) + }).then(done) + }) }) }) diff --git a/test-utils/xhrMock.js b/test-utils/xhrMock.js index 37d48e92..57c2887f 100644 --- a/test-utils/xhrMock.js +++ b/test-utils/xhrMock.js @@ -15,6 +15,7 @@ module.exports = function() { XMLHttpRequest: function XMLHttpRequest() { var args = {} var headers = {} + var aborted = false this.setRequestHeader = function(header, value) { headers[header] = value } @@ -32,11 +33,15 @@ module.exports = function() { } this.send = function(body) { var self = this - var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) - var data = handler({url: args.pathname, query: args.search || {}, body: body || null}) + if(!aborted) { + var handler = routes[args.method + " " + args.pathname] || serverErrorHandler.bind(null, args.pathname) + var data = handler({url: args.pathname, query: args.search || {}, body: body || null}) + self.status = data.status + self.responseText = data.responseText + } else { + self.status = 0 + } self.readyState = 4 - self.status = data.status - self.responseText = data.responseText if (args.async === true) { var s = new Date callAsync(function() { @@ -44,6 +49,9 @@ module.exports = function() { }) } } + this.abort = function() { + aborted = true + } }, document: { createElement: function(tag) {