diff --git a/CHANGELOG b/CHANGELOG index 4f93905..6d99751 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ *SVN* +* Add Ajax.Response object which supports the following methods: responseJSON, headerJSON, getHeader, getAllHeaders and handles browser discrepancies in the other response methods. Add sanitizeJSON, evalJS and evalJSON to Ajax.Request. Closes #8122, #8006, #7295. [Tobie Langel] + +* Add an isRunningFromRake property to unit tests. [Tobie Langel] + +* Add support for Opera browser in jstest.rb. [Tobie Langel] + * Inheritance branch merged to trunk; robust inheritance support for Class.create. Closes #5459. [Dean Edwards, Alex Arnell, Andrew Dupont, Mislav Mahronic] - To access a method's superclass method, add "$super" as the first argument. (The naming is significant.) Works like Function#wrap. - Class.create now takes two optional arguments. The first is an existing class to subclass; the second is an object literal defining the instance properties/methods. Either can be omitted. Backwards-compatible with old Class.create. @@ -9,6 +15,8 @@ * Add Function#argumentNames, which returns an ordered array of the function's named arguments. [sam] +* Prevent a crash in Safari 1.3 on String#stripScripts and String#extractScripts. Closes #8332. [grant, Tobie Langel] + * Add Prototype.Browser.MobileSafari which evaluates to true on the iPhone's browser. [sam] * Optimize Selector#match and Element#match for simple selectors. Closes #9082. [Andrew Dupont] diff --git a/Rakefile b/Rakefile index aa77d84..deaea8f 100644 --- a/Rakefile +++ b/Rakefile @@ -49,7 +49,7 @@ JavaScriptTestTask.new(:test_units) do |t| t.run(test_file) unless tests_to_run && !tests_to_run.include?(test_name) end - %w( safari firefox ie konqueror ).each do |browser| + %w( safari firefox ie konqueror opera ).each do |browser| t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser) end end diff --git a/src/ajax.js b/src/ajax.js index 716416f..439c680 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -56,7 +56,9 @@ Ajax.Base.prototype = { asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', - parameters: '' + parameters: '', + evalJSON: true, + evalJS: true } Object.extend(this.options, options || {}); @@ -64,7 +66,7 @@ Ajax.Base.prototype = { if (typeof this.options.parameters == 'string') this.options.parameters = this.options.parameters.toQueryParams(); } -} +}; Ajax.Request = Class.create(); Ajax.Request.Events = @@ -101,8 +103,9 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } try { - if (this.options.onCreate) this.options.onCreate(this.transport); - Ajax.Responders.dispatch('onCreate', this, this.transport); + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); @@ -167,33 +170,39 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { }, success: function() { - return !this.transport.status - || (this.transport.status >= 200 && this.transport.status < 300); + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); }, - + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState]; - var transport = this.transport, json = this.evalJSON(); + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; - (this.options['on' + this.transport.status] + (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(transport, json); + || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } - var contentType = this.getHeader('Content-type'); - if (contentType && contentType.strip(). - match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) - this.evalResponse(); + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); } try { - (this.options['on' + state] || Prototype.emptyFunction)(transport, json); - Ajax.Responders.dispatch('on' + state, this, transport, json); + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } @@ -210,13 +219,6 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } catch (e) { return null } }, - evalJSON: function() { - try { - var json = this.getHeader('X-JSON'); - return json ? json.evalJSON() : null; - } catch (e) { return null } - }, - evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); @@ -231,6 +233,76 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { } }); +Ajax.Response = Class.create(); +Ajax.Response.prototype = { + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this.getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = xml === undefined ? null : xml; + this.responseJSON = this.getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + try { + return json ? json.evalJSON(this.request.options.sanitizeJSON) : null; + } catch (e) { + this.request.dispatchException(e); + } + }, + + getResponseJSON: function() { + var options = this.request.options; + try { + if (options.evalJSON == 'force' || (options.evalJSON && + (this.getHeader('Content-type') || '').include('application/json'))) + return this.transport.responseText.evalJSON(options.sanitizeJSON); + return null; + } catch (e) { + this.request.dispatchException(e); + } + } +}; + Ajax.Updater = Class.create(); Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { @@ -244,29 +316,29 @@ Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function(transport, param) { - this.updateContent(); - onComplete(transport, param); + this.options.onComplete = (function(response, param) { + this.updateContent(response.responseText); + onComplete(response, param); }).bind(this); this.request(url); }, - updateContent: function() { - var receiver = this.container[this.success() ? 'success' : 'failure']; - var response = this.transport.responseText, options = this.options; + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; - if (!options.evalScripts) response = response.stripScripts(); + if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (typeof options.insertion == 'string') { - var insertion = {}; insertion[options.insertion] = response; + var insertion = {}; insertion[options.insertion] = responseText; receiver.insert(insertion); } - else options.insertion(receiver, response); + else options.insertion(receiver, responseText); } - else receiver.update(response); + else receiver.update(responseText); } if (this.success()) { @@ -302,12 +374,12 @@ Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, - updateComplete: function(request) { + updateComplete: function(responseText) { if (this.options.decay) { - this.decay = (request.responseText == this.lastText ? + this.decay = (responseText == this.lastText ? this.decay * this.options.decay : 1); - this.lastText = request.responseText; + this.lastText = responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, diff --git a/test/lib/jstest.rb b/test/lib/jstest.rb index 92f12c0..2f686ed 100644 --- a/test/lib/jstest.rb +++ b/test/lib/jstest.rb @@ -134,6 +134,34 @@ class KonquerorBrowser < Browser end end +class OperaBrowser < Browser + def initialize(path='c:\Program Files\Opera\Opera.exe') + @path = path + end + + def setup + if windows? + puts %{ + MAJOR ANNOYANCE on Windows. + You have to shut down Opera manually after each test + for the script to proceed. + Any suggestions on fixing this is GREATLY appreciated! + Thank you for your understanding. + } + end + end + + def visit(url) + applescript('tell application "Opera" to GetURL "' + url + '"') if macos? + system("#{@path} #{url}") if windows? + system("opera #{url}") if linux? + end + + def to_s + "Opera" + end +end + # shut up, webrick :-) class ::WEBrick::HTTPServer def access_log(config, req, res) @@ -185,7 +213,10 @@ class JavaScriptTestTask < ::Rake::TaskLib @server.mount_proc("/content-type") do |req, res| res.body = req["content-type"] end - + @server.mount_proc("/response") do |req, res| + req.query.each {|k, v| res[k] = v unless k == 'responseBody'} + res.body = req.query["responseBody"] + end yield self if block_given? define end @@ -238,6 +269,8 @@ class JavaScriptTestTask < ::Rake::TaskLib IEBrowser.new when :konqueror KonquerorBrowser.new + when :opera + OperaBrowser.new else browser end diff --git a/test/lib/unittest.js b/test/lib/unittest.js index 1be1fc6..ab58699 100644 --- a/test/lib/unittest.js +++ b/test/lib/unittest.js @@ -290,6 +290,9 @@ Test.Unit.Assertions.prototype = { if (this.errors > 0) return 'error'; return 'passed'; }, + isRunningFromRake: (function() { + return window.location.port == 4711; + })(), assert: function(expression) { var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; try { expression ? this.pass() : diff --git a/test/unit/ajax.html b/test/unit/ajax.html index 100bf70..0bf782f 100644 --- a/test/unit/ajax.html +++ b/test/unit/ajax.html @@ -28,14 +28,59 @@ diff --git a/test/unit/fixtures/data.json b/test/unit/fixtures/data.json new file mode 100644 index 0000000..85391eb --- /dev/null +++ b/test/unit/fixtures/data.json @@ -0,0 +1 @@ +{test: 123} \ No newline at end of file diff --git a/test/unit/fixtures/empty.html b/test/unit/fixtures/empty.html new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/unit_tests.html b/test/unit/unit_tests.html index fb8c85a..d21a42a 100644 --- a/test/unit/unit_tests.html +++ b/test/unit/unit_tests.html @@ -45,16 +45,26 @@ //