From 5e788a0315b937ebfaea47ee15103b093bd4edb3 Mon Sep 17 00:00:00 2001 From: gaye Date: Tue, 16 Apr 2013 15:50:24 -0700 Subject: Add an abort routine to Caldav.Xhr --- .gitignore | 15 +++--- caldav.js | 90 ++++++++++++++++++++++++------------ lib/caldav/request/abstract.js | 4 ++ lib/caldav/request/asset.js | 12 +++-- lib/caldav/request/calendar_home.js | 13 ++++-- lib/caldav/xhr.js | 61 +++++++++++++++--------- test-agent/config.json | 3 -- test/caldav/request/abstract_test.js | 7 +++ test/caldav/xhr_test.js | 36 +++++++++++++-- 9 files changed, 168 insertions(+), 73 deletions(-) delete mode 100644 test-agent/config.json diff --git a/.gitignore b/.gitignore index bca33be..206e778 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ -node_modules .DS_Store -data/ -vendor/ -sandbox/ -.gitignore -test/servers/servers.json \ No newline at end of file + +/data/ +/node_modules/ +/sandbox/ +/vendor/ + +/test/servers/servers.json +/test-agent/config.json + diff --git a/caldav.js b/caldav.js index ba6bc7a..dfe8694 100644 --- a/caldav.js +++ b/caldav.js @@ -2025,6 +2025,7 @@ function write (chunk) { Xhr.prototype = { globalXhrOptions: null, xhrClass: Native, + xhr: null, method: 'GET', async: true, waiting: false, @@ -2035,7 +2036,7 @@ function write (chunk) { headers: {}, data: null, - _seralize: function _seralize() { + _serialize: function _serialize() { return this.data; }, @@ -2051,32 +2052,46 @@ function write (chunk) { ); }, + /** + * Aborts the request if it has already been sent. + * @param {Function=} cb An optional callback function. + */ + abort: function(cb) { + if (this.waiting) { + this.xhr.abort(); + this.waiting = false; + } + + if (cb !== undefined) { + cb(); + } + }, + /** * Sends request to server. * * @param {Function} callback success/failure handler. */ send: function send(callback) { - var header, xhr; + var header; if (typeof(callback) === 'undefined') { callback = this.callback; } - if (this.globalXhrOptions) { - xhr = new this.xhrClass(this.globalXhrOptions); - } else { - xhr = new this.xhrClass(); - } + this.xhr = new this.xhrClass( + this.globalXhrOptions ? this.globalXhrOptions : undefined); + // This hack is in place due to some platform // bug in gecko when using mozSystem xhr // the credentials only seem to work as expected // when constructing them manually. if (!this.globalXhrOptions || !this.globalXhrOptions.mozSystem) { - xhr.open(this.method, this.url, this.async, this.user, this.password); + this.xhr.open( + this.method, this.url, this.async, this.user, this.password); } else { - xhr.open(this.method, this.url, this.async); - xhr.setRequestHeader('Authorization', this._credentials( + this.xhr.open(this.method, this.url, this.async); + this.xhr.setRequestHeader('Authorization', this._credentials( this.user, this.password )); @@ -2085,12 +2100,12 @@ function write (chunk) { var useMozChunkedText = false; if (this.globalXhrOptions && this.globalXhrOptions.useMozChunkedText) { useMozChunkedText = true; - xhr.responseType = 'moz-chunked-text'; + this.xhr.responseType = 'moz-chunked-text'; } for (header in this.headers) { if (Object.hasOwnProperty.call(this.headers, header)) { - xhr.setRequestHeader(header, this.headers[header]); + this.xhr.setRequestHeader(header, this.headers[header]); } } @@ -2098,19 +2113,19 @@ function write (chunk) { var hasProgressEvents = false; // check for progress event support. - if ('onprogress' in xhr) { + if ('onprogress' in this.xhr) { hasProgressEvents = true; var last = 0; if (useMozChunkedText) { - xhr.onprogress = (function onChunkedProgress(event) { + this.xhr.onprogress = (function onChunkedProgress(event) { if (this.ondata) { - this.ondata(xhr.responseText); + this.ondata(this.xhr.responseText); } }.bind(this)); } else { - xhr.onprogress = (function onProgress(event) { - var chunk = xhr.responseText.substr(last, event.loaded); + this.xhr.onprogress = (function onProgress(event) { + var chunk = this.xhr.responseText.substr(last, event.loaded); last = event.loaded; if (this.ondata) { this.ondata(chunk); @@ -2119,10 +2134,10 @@ function write (chunk) { } } - xhr.onreadystatechange = (function onReadyStateChange() { + this.xhr.onreadystatechange = (function onReadyStateChange() { var data; - if (xhr.readyState === 4) { - data = xhr.responseText; + if (this.xhr.readyState === 4) { + data = this.xhr.responseText; // emulate progress events for node... // this really lame we should probably just @@ -2133,14 +2148,14 @@ function write (chunk) { } this.waiting = false; - callback(null, xhr); + callback(null, this.xhr); } }.bind(this)); this.waiting = true; - xhr.send(this._seralize()); + this.xhr.send(this._serialize()); - return xhr; + return this.xhr; } }; @@ -2694,6 +2709,8 @@ function write (chunk) { * @param {Function} callback node style callback. * Receives three arguments * error, parsedData, xhr. + * @return {Caldav.Xhr} The xhr request so that the caller + * has a chance to abort the request. */ send: function(callback) { var self = this; @@ -2715,6 +2732,8 @@ function write (chunk) { callback(new Errors.CaldavHttpError(xhr.status)); } }); + + return req; } }; @@ -2799,6 +2818,8 @@ function write (chunk) { * * @param {Object} [options] calendar options. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ get: function(options, callback) { if (typeof(options) === 'function') { @@ -2808,7 +2829,7 @@ function write (chunk) { var req = this._buildRequest('GET', options); - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); }, @@ -2819,6 +2840,8 @@ function write (chunk) { * @param {Object} [options] see get. * @param {String} data post content. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ put: function(options, data, callback) { if (typeof(options) === 'string') { @@ -2834,7 +2857,7 @@ function write (chunk) { var req = this._buildRequest('PUT', options); req.data = data; - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); }, @@ -2844,6 +2867,8 @@ function write (chunk) { * * @param {Object} [options] see get. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ delete: function(options, callback) { if (typeof(options) === 'function') { @@ -2853,7 +2878,7 @@ function write (chunk) { var req = this._buildRequest('DELETE', options); - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); } @@ -3092,6 +3117,10 @@ function write (chunk) { Propfind: ns.require('request/propfind'), + /** + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. + */ _findPrincipal: function(url, callback) { var find = new this.Propfind(this.connection, { url: url @@ -3100,7 +3129,7 @@ function write (chunk) { find.prop('current-user-principal'); find.prop('principal-URL'); - find.send(function(err, data) { + return find.send(function(err, data) { var principal; if (err) { @@ -3121,7 +3150,6 @@ function write (chunk) { } else { callback(new Errors.CaldavHttpError(404)); } - }); }, @@ -3133,7 +3161,7 @@ function write (chunk) { find.prop(['caldav', 'calendar-home-set']); - find.send(function(err, data) { + return find.send(function(err, data) { if (err) { callback(err); return; @@ -3152,10 +3180,12 @@ function write (chunk) { * * @param {Function} callback node style where second argument * are the details of the home calendar. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ send: function(callback) { var self = this; - self._findPrincipal(self.url, function(err, url) { + return self._findPrincipal(self.url, function(err, url) { if (!url) { callback(err); diff --git a/lib/caldav/request/abstract.js b/lib/caldav/request/abstract.js index 7116217..9edf6fd 100644 --- a/lib/caldav/request/abstract.js +++ b/lib/caldav/request/abstract.js @@ -54,6 +54,8 @@ * @param {Function} callback node style callback. * Receives three arguments * error, parsedData, xhr. + * @return {Caldav.Xhr} The xhr request so that the caller + * has a chance to abort the request. */ send: function(callback) { var self = this; @@ -75,6 +77,8 @@ callback(new Errors.CaldavHttpError(xhr.status)); } }); + + return req; } }; diff --git a/lib/caldav/request/asset.js b/lib/caldav/request/asset.js index b26e4f7..98c943a 100644 --- a/lib/caldav/request/asset.js +++ b/lib/caldav/request/asset.js @@ -71,6 +71,8 @@ * * @param {Object} [options] calendar options. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ get: function(options, callback) { if (typeof(options) === 'function') { @@ -80,7 +82,7 @@ var req = this._buildRequest('GET', options); - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); }, @@ -91,6 +93,8 @@ * @param {Object} [options] see get. * @param {String} data post content. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ put: function(options, data, callback) { if (typeof(options) === 'string') { @@ -106,7 +110,7 @@ var req = this._buildRequest('PUT', options); req.data = data; - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); }, @@ -116,6 +120,8 @@ * * @param {Object} [options] see get. * @param {Function} callback node style [err, data, xhr]. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ delete: function(options, callback) { if (typeof(options) === 'function') { @@ -125,7 +131,7 @@ var req = this._buildRequest('DELETE', options); - req.send(function(err, xhr) { + return req.send(function(err, xhr) { callback(err, xhr.responseText, xhr); }); } diff --git a/lib/caldav/request/calendar_home.js b/lib/caldav/request/calendar_home.js index 24b27bc..a1f8ca6 100644 --- a/lib/caldav/request/calendar_home.js +++ b/lib/caldav/request/calendar_home.js @@ -52,6 +52,10 @@ Propfind: ns.require('request/propfind'), + /** + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. + */ _findPrincipal: function(url, callback) { var find = new this.Propfind(this.connection, { url: url @@ -60,7 +64,7 @@ find.prop('current-user-principal'); find.prop('principal-URL'); - find.send(function(err, data) { + return find.send(function(err, data) { var principal; if (err) { @@ -81,7 +85,6 @@ } else { callback(new Errors.CaldavHttpError(404)); } - }); }, @@ -93,7 +96,7 @@ find.prop(['caldav', 'calendar-home-set']); - find.send(function(err, data) { + return find.send(function(err, data) { if (err) { callback(err); return; @@ -112,10 +115,12 @@ * * @param {Function} callback node style where second argument * are the details of the home calendar. + * @return {Caldav.Xhr} The underlying xhr request so that the caller + * has a chance to abort the request. */ send: function(callback) { var self = this; - self._findPrincipal(self.url, function(err, url) { + return self._findPrincipal(self.url, function(err, url) { if (!url) { callback(err); diff --git a/lib/caldav/xhr.js b/lib/caldav/xhr.js index 77ecd5c..e88e789 100644 --- a/lib/caldav/xhr.js +++ b/lib/caldav/xhr.js @@ -43,6 +43,7 @@ Xhr.prototype = { globalXhrOptions: null, xhrClass: Native, + xhr: null, method: 'GET', async: true, waiting: false, @@ -53,7 +54,7 @@ headers: {}, data: null, - _seralize: function _seralize() { + _serialize: function _serialize() { return this.data; }, @@ -69,32 +70,46 @@ ); }, + /** + * Aborts the request if it has already been sent. + * @param {Function=} cb An optional callback function. + */ + abort: function(cb) { + if (this.waiting) { + this.xhr.abort(); + this.waiting = false; + } + + if (cb !== undefined) { + cb(); + } + }, + /** * Sends request to server. * * @param {Function} callback success/failure handler. */ send: function send(callback) { - var header, xhr; + var header; if (typeof(callback) === 'undefined') { callback = this.callback; } - if (this.globalXhrOptions) { - xhr = new this.xhrClass(this.globalXhrOptions); - } else { - xhr = new this.xhrClass(); - } + this.xhr = new this.xhrClass( + this.globalXhrOptions ? this.globalXhrOptions : undefined); + // This hack is in place due to some platform // bug in gecko when using mozSystem xhr // the credentials only seem to work as expected // when constructing them manually. if (!this.globalXhrOptions || !this.globalXhrOptions.mozSystem) { - xhr.open(this.method, this.url, this.async, this.user, this.password); + this.xhr.open( + this.method, this.url, this.async, this.user, this.password); } else { - xhr.open(this.method, this.url, this.async); - xhr.setRequestHeader('Authorization', this._credentials( + this.xhr.open(this.method, this.url, this.async); + this.xhr.setRequestHeader('Authorization', this._credentials( this.user, this.password )); @@ -103,12 +118,12 @@ var useMozChunkedText = false; if (this.globalXhrOptions && this.globalXhrOptions.useMozChunkedText) { useMozChunkedText = true; - xhr.responseType = 'moz-chunked-text'; + this.xhr.responseType = 'moz-chunked-text'; } for (header in this.headers) { if (Object.hasOwnProperty.call(this.headers, header)) { - xhr.setRequestHeader(header, this.headers[header]); + this.xhr.setRequestHeader(header, this.headers[header]); } } @@ -116,19 +131,19 @@ var hasProgressEvents = false; // check for progress event support. - if ('onprogress' in xhr) { + if ('onprogress' in this.xhr) { hasProgressEvents = true; var last = 0; if (useMozChunkedText) { - xhr.onprogress = (function onChunkedProgress(event) { + this.xhr.onprogress = (function onChunkedProgress(event) { if (this.ondata) { - this.ondata(xhr.responseText); + this.ondata(this.xhr.responseText); } }.bind(this)); } else { - xhr.onprogress = (function onProgress(event) { - var chunk = xhr.responseText.substr(last, event.loaded); + this.xhr.onprogress = (function onProgress(event) { + var chunk = this.xhr.responseText.substr(last, event.loaded); last = event.loaded; if (this.ondata) { this.ondata(chunk); @@ -137,10 +152,10 @@ } } - xhr.onreadystatechange = (function onReadyStateChange() { + this.xhr.onreadystatechange = (function onReadyStateChange() { var data; - if (xhr.readyState === 4) { - data = xhr.responseText; + if (this.xhr.readyState === 4) { + data = this.xhr.responseText; // emulate progress events for node... // this really lame we should probably just @@ -151,14 +166,14 @@ } this.waiting = false; - callback(null, xhr); + callback(null, this.xhr); } }.bind(this)); this.waiting = true; - xhr.send(this._seralize()); + this.xhr.send(this._serialize()); - return xhr; + return this.xhr; } }; diff --git a/test-agent/config.json b/test-agent/config.json deleted file mode 100644 index 0453099..0000000 --- a/test-agent/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{"tests": [ -"/test/caldav/connection_test.js","/test/caldav/index_test.js","/test/caldav/query_builder_test.js","/test/caldav/request/abstract_test.js","/test/caldav/request/asset_test.js","/test/caldav/request/calendar_home_test.js","/test/caldav/request/calendar_query_test.js","/test/caldav/request/propfind_test.js","/test/caldav/request/resources_test.js","/test/caldav/resources/calendar_test.js","/test/caldav/sax/base_test.js","/test/caldav/sax/calendar_data_handler_test.js","/test/caldav/sax/dav_response_test.js","/test/caldav/sax_test.js","/test/caldav/template_test.js","/test/caldav/xhr_test.js","/test/servers/home_test.js","/test/servers/query_test.js","/test/servers/resources_test.js" - ]} diff --git a/test/caldav/request/abstract_test.js b/test/caldav/request/abstract_test.js index 21bde48..e2fb3d1 100644 --- a/test/caldav/request/abstract_test.js +++ b/test/caldav/request/abstract_test.js @@ -70,6 +70,13 @@ suite('caldav/request/abstract.js', function() { return FakeXhr.instances.pop(); } + test('should return a Caldav.Xhr object', function() { + var req = subject.send(function() {}); + assert.deepEqual(url, req.url) + assert.deepEqual(con.user, req.user); + assert.deepEqual(con.password, req.password); + }); + suite('error', function() { var calledWith; diff --git a/test/caldav/xhr_test.js b/test/caldav/xhr_test.js index 6130346..a3e6b66 100644 --- a/test/caldav/xhr_test.js +++ b/test/caldav/xhr_test.js @@ -76,10 +76,9 @@ suite('webacls/xhr', function() { }); suite('when xhr is a success and responds /w data', function() { - var response = '', cb; + var response = '', cb, xhr; setup(function(done) { - var xhr; request({ data: data, url: url, @@ -88,8 +87,8 @@ suite('webacls/xhr', function() { cb = callback.bind(this, done); xhr = subject.send(cb); - //should be waiting inbetween requests - assert.equal(subject.waiting, true); + // should be waiting inbetween requests + assert.deepEqual(subject.waiting, true); xhr.readyState = 4; xhr.responseText = response; @@ -101,6 +100,35 @@ suite('webacls/xhr', function() { }); }); + suite('when abort is called on the request', function() { + var aborted, xhr; + + setup(function() { + request({ + data: data, + url: url, + method: 'PUT' + }); + xhr = subject.send(callback); + + // should be waiting inbetween requests + assert.deepEqual(subject.waiting, true); + + aborted = false; + }); + + test('underlying request should be aborted', function(done) { + xhr.abort = function() { + aborted = true; + }; + + subject.abort(function() { + assert.deepEqual(true, aborted); + assert.deepEqual(false, subject.waiting); + done(); + }); + }); + }); }); suite('requests real files', function() { -- cgit