From fca83cb1854751b495c77e34979681c740fd8e9e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 26 Jul 2013 13:10:52 -0400 Subject: [PATCH] Upgrade Ember to Fix CVE-2013-4170 --- app/assets/javascripts/discourse.js | 3 +- .../discourse/ember/event_dispatcher.js | 33 + .../javascripts/external_development/ember.js | 1066 ++++++++++------- .../javascripts/external_production/ember.js | 846 +++++++------ app/assets/javascripts/main_include.js | 2 + 5 files changed, 1152 insertions(+), 798 deletions(-) create mode 100644 app/assets/javascripts/discourse/ember/event_dispatcher.js diff --git a/app/assets/javascripts/discourse.js b/app/assets/javascripts/discourse.js index 4dbe46d574d..03f79618618 100644 --- a/app/assets/javascripts/discourse.js +++ b/app/assets/javascripts/discourse.js @@ -309,8 +309,6 @@ Discourse = Ember.Application.createWithMixins({ } }, - - /** Start up the Discourse application. @@ -332,3 +330,4 @@ Discourse = Ember.Application.createWithMixins({ }); Discourse.Router = Discourse.Router.reopen({ location: 'discourse_location' }); + diff --git a/app/assets/javascripts/discourse/ember/event_dispatcher.js b/app/assets/javascripts/discourse/ember/event_dispatcher.js new file mode 100644 index 00000000000..700186f3169 --- /dev/null +++ b/app/assets/javascripts/discourse/ember/event_dispatcher.js @@ -0,0 +1,33 @@ +/* + Discourse is not interested in watching `mouseMove` or `touchMove` events on an Ember views, + so we remove them from the events the EventDispatcher watches for. +*/ +Ember.EventDispatcher.reopen({ + events: { + touchstart : 'touchStart', + touchend : 'touchEnd', + touchcancel : 'touchCancel', + keydown : 'keyDown', + keyup : 'keyUp', + keypress : 'keyPress', + mousedown : 'mouseDown', + mouseup : 'mouseUp', + contextmenu : 'contextMenu', + click : 'click', + dblclick : 'doubleClick', + focusin : 'focusIn', + focusout : 'focusOut', + mouseenter : 'mouseEnter', + mouseleave : 'mouseLeave', + submit : 'submit', + input : 'input', + change : 'change', + dragstart : 'dragStart', + drag : 'drag', + dragenter : 'dragEnter', + dragleave : 'dragLeave', + dragover : 'dragOver', + drop : 'drop', + dragend : 'dragEnd' + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/external_development/ember.js b/app/assets/javascripts/external_development/ember.js index 1f97cd13969..ba60c0b8501 100755 --- a/app/assets/javascripts/external_development/ember.js +++ b/app/assets/javascripts/external_development/ember.js @@ -1,5 +1,5 @@ -// Version: v1.0.0-pre.2-1739-ga301b4a -// Last commit: a301b4a (2013-07-15 10:43:23 -0700) +// Version: v1.0.0-pre.2-1804-g79d5a07 +// Last commit: 79d5a07 (2013-07-26 08:48:32 -0700) (function() { @@ -156,8 +156,8 @@ Ember.deprecateFunc = function(message, func) { })(); -// Version: v1.0.0-pre.2-1739-ga301b4a -// Last commit: a301b4a (2013-07-15 10:43:23 -0700) +// Version: v1.0.0-pre.2-1804-g79d5a07 +// Last commit: 79d5a07 (2013-07-26 08:48:32 -0700) (function() { @@ -378,7 +378,7 @@ function assertPolyfill(test, message) { // attempt to preserve the stack throw new Error("assertion failed: " + message); } catch(error) { - setTimeout(function(){ + setTimeout(function() { throw error; }, 0); } @@ -460,7 +460,7 @@ Ember.merge = function(original, updates) { Ember.isNone(undefined); // true Ember.isNone(''); // false Ember.isNone([]); // false - Ember.isNone(function(){}); // false + Ember.isNone(function() {}); // false ``` @method isNone @@ -565,7 +565,7 @@ var canRedefineProperties, canDefinePropertyOnDOM; // Catch IE8 where Object.defineProperty exists but only works on DOM elements if (defineProperty) { try { - defineProperty({}, 'a',{get:function(){}}); + defineProperty({}, 'a',{get:function() {}}); } catch (e) { defineProperty = null; } @@ -596,7 +596,7 @@ if (defineProperty) { // This is for Safari 5.0, which supports Object.defineProperty, but not // on DOM nodes. - canDefinePropertyOnDOM = (function(){ + canDefinePropertyOnDOM = (function() { try { defineProperty(document.createElement('div'), 'definePropertyOnDOM', {}); return true; @@ -608,7 +608,7 @@ if (defineProperty) { if (!canRedefineProperties) { defineProperty = null; } else if (!canDefinePropertyOnDOM) { - defineProperty = function(obj, keyName, desc){ + defineProperty = function(obj, keyName, desc) { var isNode; if (typeof Node === "object") { @@ -1218,7 +1218,7 @@ if (needsFinallyFix) { } finally { try { finalResult = finalizer.call(binding); - } catch (e){ + } catch (e) { finalError = e; } } @@ -1270,7 +1270,7 @@ if (needsFinallyFix) { } finally { try { finalResult = finalizer.call(binding); - } catch (e){ + } catch (e) { finalError = e; } } @@ -1469,7 +1469,7 @@ Ember.Instrumentation.instrument = function(name, payload, callback, binding) { var beforeValues = [], listener, i, l; - function tryable(){ + function tryable() { for (i=0, l=listeners.length; i 1) { set(this, dependentKey, value); return value; @@ -4383,7 +4404,7 @@ Ember.computed.alias = function(dependentKey) { @return {Ember.ComputedProperty} computed property which creates an one way computed property to the original value for property. - Where `computed.alias` aliases `get` and `set`, and allows for bidirectional + Where `computed.alias` aliases `get` and `set`, and allows for bidirectional data flow, `computed.oneWay` only provides an aliased `get`. The `set` will not mutate the upstream property, rather causes the current property to become the value set. This causes the downstream property to permentantly @@ -5150,7 +5171,7 @@ var Backburner = requireModule('backburner').Backburner, call. ```javascript - Ember.run(function(){ + Ember.run(function() { // code to be execute within a RunLoop }); ``` @@ -5193,7 +5214,7 @@ Ember.run = function(target, method) { If invoked when not within a run loop: ```javascript - Ember.run.join(function(){ + Ember.run.join(function() { // creates a new run-loop }); ``` @@ -5201,9 +5222,9 @@ Ember.run = function(target, method) { Alternatively, if called within an existing run loop: ```javascript - Ember.run(function(){ + Ember.run(function() { // creates a new run-loop - Ember.run.join(function(){ + Ember.run.join(function() { // joins with the existing run-loop, and queues for invocation on // the existing run-loops action queue. }); @@ -5296,12 +5317,12 @@ Ember.run.end = function() { the `Ember.run.queues` property. ```javascript - Ember.run.schedule('sync', this, function(){ + Ember.run.schedule('sync', this, function() { // this will be executed in the first RunLoop queue, when bindings are synced console.log("scheduled on sync queue"); }); - Ember.run.schedule('actions', this, function(){ + Ember.run.schedule('actions', this, function() { // this will be executed in the 'actions' queue, after bindings have synced. console.log("scheduled on actions queue"); }); @@ -5369,7 +5390,7 @@ Ember.run.sync = function() { together, which is often more efficient than using a real setTimeout. ```javascript - Ember.run.later(myContext, function(){ + Ember.run.later(myContext, function() { // code here will execute within a RunLoop in about 500ms with this == myContext }, 500); ``` @@ -5417,7 +5438,7 @@ Ember.run.once = function(target, method) { calls. ```javascript - Ember.run(function(){ + Ember.run(function() { var sayHi = function() { console.log('hi'); } Ember.run.scheduleOnce('afterRender', myContext, sayHi); Ember.run.scheduleOnce('afterRender', myContext, sayHi); @@ -5462,7 +5483,7 @@ Ember.run.scheduleOnce = function(queue, target, method) { `Ember.run.later` with a wait time of 1ms. ```javascript - Ember.run.next(myContext, function(){ + Ember.run.next(myContext, function() { // code to be executed in the next run loop, which will be scheduled after the current one }); ``` @@ -5524,17 +5545,17 @@ Ember.run.next = function() { `Ember.run.once()`, or `Ember.run.next()`. ```javascript - var runNext = Ember.run.next(myContext, function(){ + var runNext = Ember.run.next(myContext, function() { // will not be executed }); Ember.run.cancel(runNext); - var runLater = Ember.run.later(myContext, function(){ + var runLater = Ember.run.later(myContext, function() { // will not be executed }, 500); Ember.run.cancel(runLater); - var runOnce = Ember.run.once(myContext, function(){ + var runOnce = Ember.run.once(myContext, function() { // will not be executed }); Ember.run.cancel(runOnce); @@ -6682,7 +6703,7 @@ Alias.prototype = new Ember.Descriptor(); App.PaintSample = Ember.Object.extend({ color: 'red', colour: Ember.alias('color'), - name: function(){ + name: function() { return "Zed"; }, moniker: Ember.alias("name") @@ -6710,7 +6731,7 @@ Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.a ```javascript App.Person = Ember.Object.extend({ - name: function(){ + name: function() { return 'Tomhuda Katzdale'; }, moniker: Ember.aliasMethod('name') @@ -6779,7 +6800,7 @@ Ember.immediateObserver = function() { }.observesBefore('content.value'), valueDidChange: function(obj, keyName, value) { // only run if updating a value already in the DOM - if(this.get('state') === 'inDOM') { + if (this.get('state') === 'inDOM') { var color = value > this.changingFrom ? 'green' : 'red'; // logic } @@ -6815,65 +6836,82 @@ Ember Metal (function() { define("rsvp/all", - ["rsvp/defer","exports"], + ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; - var defer = __dependency1__.defer; + var Promise = __dependency1__.Promise; + /* global toString */ + function all(promises) { - var results = [], deferred = defer(), remaining = promises.length; - - if (remaining === 0) { - deferred.resolve([]); + if (Object.prototype.toString.call(promises) !== "[object Array]") { + throw new TypeError('You must pass an array to all.'); } - var resolver = function(index) { - return function(value) { - resolveAll(index, value); - }; - }; + return new Promise(function(resolve, reject) { + var results = [], remaining = promises.length, + promise; - var resolveAll = function(index, value) { - results[index] = value; - if (--remaining === 0) { - deferred.resolve(results); + if (remaining === 0) { + resolve([]); } - }; - var rejectAll = function(error) { - deferred.reject(error); - }; - - for (var i = 0; i < promises.length; i++) { - if (promises[i] && typeof promises[i].then === 'function') { - promises[i].then(resolver(i), rejectAll); - } else { - resolveAll(i, promises[i]); + function resolver(index) { + return function(value) { + resolveAll(index, value); + }; } - } - return deferred.promise; + + function resolveAll(index, value) { + results[index] = value; + if (--remaining === 0) { + resolve(results); + } + } + + for (var i = 0; i < promises.length; i++) { + promise = promises[i]; + + if (promise && typeof promise.then === 'function') { + promise.then(resolver(i), reject); + } else { + resolveAll(i, promise); + } + } + }); } + __exports__.all = all; }); - define("rsvp/async", ["exports"], function(__exports__) { "use strict"; var browserGlobal = (typeof window !== 'undefined') ? window : {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; var async; - if (typeof process !== 'undefined' && - {}.toString.call(process) === '[object process]') { - async = function(callback, binding) { + // old node + function useNextTick() { + return function(callback, arg) { process.nextTick(function() { - callback.call(binding); + callback(arg); }); }; - } else if (BrowserMutationObserver) { + } + + // node >= 0.10.x + function useSetImmediate() { + return function(callback, arg) { + /* global setImmediate */ + setImmediate(function(){ + callback(arg); + }); + }; + } + + function useMutationObserver() { var queue = []; var observer = new BrowserMutationObserver(function() { @@ -6881,8 +6919,8 @@ define("rsvp/async", queue = []; toProcess.forEach(function(tuple) { - var callback = tuple[0], binding = tuple[1]; - callback.call(binding); + var callback = tuple[0], arg= tuple[1]; + callback(arg); }); }); @@ -6893,24 +6931,35 @@ define("rsvp/async", window.addEventListener('unload', function(){ observer.disconnect(); observer = null; - }); + }, false); - async = function(callback, binding) { - queue.push([callback, binding]); + return function(callback, arg) { + queue.push([callback, arg]); element.setAttribute('drainQueue', 'drainQueue'); }; - } else { - async = function(callback, binding) { + } + + function useSetTimeout() { + return function(callback, arg) { setTimeout(function() { - callback.call(binding); + callback(arg); }, 1); }; } + if (typeof setImmediate === 'function') { + async = useSetImmediate(); + } else if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { + async = useNextTick(); + } else if (BrowserMutationObserver) { + async = useMutationObserver(); + } else { + async = useSetTimeout(); + } + __exports__.async = async; }); - define("rsvp/config", ["rsvp/async","exports"], function(__dependency1__, __exports__) { @@ -6920,9 +6969,9 @@ define("rsvp/config", var config = {}; config.async = async; + __exports__.config = config; }); - define("rsvp/defer", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { @@ -6930,20 +6979,24 @@ define("rsvp/defer", var Promise = __dependency1__.Promise; function defer() { - var deferred = {}; + var deferred = { + // pre-allocate shape + resolve: undefined, + reject: undefined, + promise: undefined + }; - var promise = new Promise(function(resolve, reject) { + deferred.promise = new Promise(function(resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; }); - deferred.promise = promise; return deferred; } + __exports__.defer = defer; }); - define("rsvp/events", ["exports"], function(__exports__) { @@ -7045,7 +7098,6 @@ define("rsvp/events", __exports__.EventTarget = EventTarget; }); - define("rsvp/hash", ["rsvp/defer","exports"], function(__dependency1__, __exports__) { @@ -7053,13 +7105,13 @@ define("rsvp/hash", var defer = __dependency1__.defer; function size(object) { - var size = 0; + var s = 0; for (var prop in object) { - size++; + s++; } - return size; + return s; } function hash(promises) { @@ -7097,9 +7149,9 @@ define("rsvp/hash", return deferred.promise; } + __exports__.hash = hash; }); - define("rsvp/node", ["rsvp/promise","rsvp/all","exports"], function(__dependency1__, __dependency2__, __exports__) { @@ -7122,6 +7174,7 @@ define("rsvp/node", function denodeify(nodeFunc) { return function() { var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; + var thisArg = this; var promise = new Promise(function(nodeResolve, nodeReject) { resolve = nodeResolve; @@ -7132,7 +7185,7 @@ define("rsvp/node", nodeArgs.push(makeNodeCallbackFor(resolve, reject)); try { - nodeFunc.apply(this, nodeArgs); + nodeFunc.apply(thisArg, nodeArgs); } catch(e) { reject(e); } @@ -7142,9 +7195,9 @@ define("rsvp/node", }; } + __exports__.denodeify = denodeify; }); - define("rsvp/promise", ["rsvp/config","rsvp/events","exports"], function(__dependency1__, __dependency2__, __exports__) { @@ -7192,6 +7245,8 @@ define("rsvp/promise", this.trigger('error', { detail: event.detail }); }, this); + this.on('error', onerror); + try { resolver(resolvePromise, rejectPromise); } catch(e) { @@ -7199,6 +7254,12 @@ define("rsvp/promise", } }; + function onerror(event) { + if (config.onerror) { + config.onerror(event.detail); + } + } + var invokeCallback = function(type, promise, callback, event) { var hasCallback = isFunction(callback), value, error, succeeded, failed; @@ -7232,18 +7293,25 @@ define("rsvp/promise", Promise.prototype = { constructor: Promise, + isRejected: undefined, + isFulfilled: undefined, + rejectedReason: undefined, + fulfillmentValue: undefined, + then: function(done, fail) { - var thenPromise = new Promise(function() {}); + this.off('error', onerror); + + var thenPromise = new this.constructor(function() {}); if (this.isFulfilled) { - config.async(function() { - invokeCallback('resolve', thenPromise, done, { detail: this.fulfillmentValue }); + config.async(function(promise) { + invokeCallback('resolve', thenPromise, done, { detail: promise.fulfillmentValue }); }, this); } if (this.isRejected) { - config.async(function() { - invokeCallback('reject', thenPromise, fail, { detail: this.rejectedReason }); + config.async(function(promise) { + invokeCallback('reject', thenPromise, fail, { detail: promise.rejectedReason }); }, this); } @@ -7270,32 +7338,40 @@ define("rsvp/promise", } function handleThenable(promise, value) { - var then = null; + var then = null, + resolved; - if (objectOrFunction(value)) { - try { - then = value.then; - } catch(e) { - reject(promise, e); - return true; + try { + if (promise === value) { + throw new TypeError("A promises callback cannot return that same promise."); } - if (isFunction(then)) { - try { + if (objectOrFunction(value)) { + then = value.then; + + if (isFunction(then)) { then.call(value, function(val) { + if (resolved) { return true; } + resolved = true; + if (value !== val) { resolve(promise, val); } else { fulfill(promise, val); } }, function(val) { + if (resolved) { return true; } + resolved = true; + reject(promise, val); }); - } catch (e) { - reject(promise, e); + + return true; } - return true; } + } catch (error) { + reject(promise, error); + return true; } return false; @@ -7320,19 +7396,12 @@ define("rsvp/promise", __exports__.Promise = Promise; }); - define("rsvp/reject", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } - - function reject(reason) { return new Promise(function (resolve, reject) { reject(reason); @@ -7342,48 +7411,21 @@ define("rsvp/reject", __exports__.reject = reject; }); - define("rsvp/resolve", ["rsvp/promise","exports"], function(__dependency1__, __exports__) { "use strict"; var Promise = __dependency1__.Promise; - - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } - - function resolve(thenable){ - var promise = new Promise(function(resolve, reject){ - var then; - - try { - if ( objectOrFunction(thenable) ) { - then = thenable.then; - - if (typeof then === "function") { - then.call(thenable, resolve, reject); - } else { - resolve(thenable); - } - - } else { - resolve(thenable); - } - - } catch(error) { - reject(error); - } + function resolve(thenable) { + return new Promise(function(resolve, reject) { + resolve(thenable); }); - - return promise; } __exports__.resolve = resolve; }); - define("rsvp", ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { @@ -7414,8 +7456,6 @@ define("rsvp", __exports__.reject = reject; }); - - })(); (function() { @@ -7665,7 +7705,7 @@ define("container", register: function(type, name, factory, options) { var fullName; - if (type.indexOf(':') !== -1){ + if (type.indexOf(':') !== -1) { options = factory; factory = name; fullName = type; @@ -7739,6 +7779,20 @@ define("container", return this.resolver(fullName) || this.registry.get(fullName); }, + /** + A hook that can be used to describe how the resolver will + attempt to find the factory. + + For example, the default Ember `.describe` returns the full + class name (including namespace) where Ember's resolver expects + to find the `fullName`. + + @method describe + */ + describe: function(fullName) { + return fullName; + }, + /** A hook to enable custom fullName normalization behaviour @@ -8313,7 +8367,11 @@ Ember.copy = function(obj, deep) { @return {String} A description of the object */ Ember.inspect = function(obj) { - if (typeof obj !== 'object' || obj === null) { + var type = Ember.typeOf(obj); + if (type === 'array') { + return '[' + obj + ']'; + } + if (type !== 'object') { return obj + ''; } @@ -8496,9 +8554,9 @@ Ember.String = { // first, replace any ORDERED replacements. var idx = 0; // the current index for non-numerical replacements return str.replace(/%@([0-9]+)?/g, function(s, argIndex) { - argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ; + argIndex = (argIndex) ? parseInt(argIndex, 10) - 1 : idx++; s = formats[argIndex]; - return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString(); + return (s === null) ? '(null)' : (s === undefined) ? '' : Ember.inspect(s); }) ; }, @@ -9647,7 +9705,7 @@ Ember.Enumerable = Ember.Mixin.create({ */ uniq: function() { var ret = Ember.A(); - this.forEach(function(k){ + this.forEach(function(k) { if (a_indexOf(ret, k)<0) ret.push(k); }); return ret; @@ -9907,7 +9965,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot */ objectsAt: function(indexes) { var self = this; - return map(indexes, function(idx){ return self.objectAt(idx); }); + return map(indexes, function(idx) { return self.objectAt(idx); }); }, // overrides Ember.Enumerable version @@ -9939,7 +9997,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot }), // optimized version from Enumerable - contains: function(obj){ + contains: function(obj) { return this.indexOf(obj) >= 0; }, @@ -10004,7 +10062,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot if (startAt < 0) startAt += len; for(idx=startAt;idx"'`]/g; +var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/; + +function escapeAttribute(value) { + // Stolen shamelessly from Handlebars + + var escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + var string = value.toString(); + + if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; } + return string.replace(BAD_CHARS_REGEXP, escapeChar); +} + /** `Ember.RenderBuffer` gathers information regarding the a view and generates the final representation. `Ember.RenderBuffer` will generate HTML which can be pushed @@ -15032,14 +15120,14 @@ Ember._RenderBuffer.prototype = style = this.elementStyle, attr, prop; - buffer += '<' + tagName; + buffer += '<' + stripTagName(tagName); if (id) { - buffer += ' id="' + this._escapeAttribute(id) + '"'; + buffer += ' id="' + escapeAttribute(id) + '"'; this.elementId = null; } if (classes) { - buffer += ' class="' + this._escapeAttribute(classes.join(' ')) + '"'; + buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"'; this.classes = null; } @@ -15048,7 +15136,7 @@ Ember._RenderBuffer.prototype = for (prop in style) { if (style.hasOwnProperty(prop)) { - buffer += prop + ':' + this._escapeAttribute(style[prop]) + ';'; + buffer += prop + ':' + escapeAttribute(style[prop]) + ';'; } } @@ -15060,7 +15148,7 @@ Ember._RenderBuffer.prototype = if (attrs) { for (attr in attrs) { if (attrs.hasOwnProperty(attr)) { - buffer += ' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"'; + buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"'; } } @@ -15075,7 +15163,7 @@ Ember._RenderBuffer.prototype = if (value === true) { buffer += ' ' + prop + '="' + prop + '"'; } else { - buffer += ' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"'; + buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"'; } } } @@ -15090,7 +15178,7 @@ Ember._RenderBuffer.prototype = pushClosingTag: function() { var tagName = this.tagNames.pop(); - if (tagName) { this.buffer += ''; } + if (tagName) { this.buffer += ''; } }, currentTagName: function() { @@ -15177,7 +15265,7 @@ Ember._RenderBuffer.prototype = if (this._hasElement && this._element) { // Firefox versions < 11 do not have support for element.outerHTML. var thisElement = this.element(), outerHTML = thisElement.outerHTML; - if (typeof outerHTML === 'undefined'){ + if (typeof outerHTML === 'undefined') { return Ember.$('
').append(thisElement).html(); } return outerHTML; @@ -15188,32 +15276,7 @@ Ember._RenderBuffer.prototype = innerString: function() { return this.buffer; - }, - - _escapeAttribute: function(value) { - // Stolen shamelessly from Handlebars - - var escape = { - "<": "<", - ">": ">", - '"': """, - "'": "'", - "`": "`" - }; - - var badChars = /&(?!\w+;)|[<>"'`]/g; - var possible = /[&<>"'`]/; - - var escapeChar = function(chr) { - return escape[chr] || "&"; - }; - - var string = value.toString(); - - if(!possible.test(string)) { return string; } - return string.replace(badChars, escapeChar); } - }; })(); @@ -15254,7 +15317,7 @@ Ember.EventDispatcher = Ember.Object.extend(/** @scope Ember.EventDispatcher.pro */ events: { touchstart : 'touchStart', - // touchmove : 'touchMove', + touchmove : 'touchMove', touchend : 'touchEnd', touchcancel : 'touchCancel', keydown : 'keyDown', @@ -15265,7 +15328,7 @@ Ember.EventDispatcher = Ember.Object.extend(/** @scope Ember.EventDispatcher.pro contextmenu : 'contextMenu', click : 'click', dblclick : 'doubleClick', - // mousemove : 'mouseMove', + mousemove : 'mouseMove', focusin : 'focusIn', focusout : 'focusOut', mouseenter : 'mouseEnter', @@ -15529,8 +15592,11 @@ var childViewsProperty = Ember.computed(function() { var childViews = this._childViews, ret = Ember.A(), view = this; a_forEach(childViews, function(view) { + var currentChildViews; if (view.isVirtual) { - ret.pushObjects(get(view, 'childViews')); + if (currentChildViews = get(view, 'childViews')) { + ret.pushObjects(currentChildViews); + } } else { ret.push(view); } @@ -15825,8 +15891,8 @@ class: MyView = Ember.View.extend({ classNameBindings: ['propertyA', 'propertyB'], propertyA: 'from-a', - propertyB: function(){ - if(someLogic){ return 'from-b'; } + propertyB: function() { + if (someLogic) { return 'from-b'; } }.property() }); ``` @@ -16007,7 +16073,7 @@ class: MyTextInput = Ember.View.extend({ tagName: 'input', attributeBindings: ['disabled'], - disabled: function(){ + disabled: function() { if (someLogic) { return true; } else { @@ -16116,7 +16182,7 @@ class: aController = Ember.Object.create({ firstName: 'Barry', - excitedGreeting: function(){ + excitedGreeting: function() { return this.get("content.firstName") + "!!!" }.property() }); @@ -16187,7 +16253,7 @@ class: ```javascript AView = Ember.View.extend({ - click: function(event){ + click: function(event) { // will be called when when an instance's // rendered element is clicked } @@ -16208,7 +16274,7 @@ class: ```javascript AView = Ember.View.extend({ eventManager: Ember.Object.create({ - doubleClick: function(event, view){ + doubleClick: function(event, view) { // will be called when when an instance's // rendered element or any rendering // of this views's descendent @@ -16223,11 +16289,11 @@ class: ```javascript AView = Ember.View.extend({ - mouseEnter: function(event){ + mouseEnter: function(event) { // will never trigger. }, eventManager: Ember.Object.create({ - mouseEnter: function(event, view){ + mouseEnter: function(event, view) { // takes presedence over AView#mouseEnter } }) @@ -16245,7 +16311,7 @@ class: OuterView = Ember.View.extend({ template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"), eventManager: Ember.Object.create({ - mouseEnter: function(event, view){ + mouseEnter: function(event, view) { // view might be instance of either // OuterView or InnerView depending on // where on the page the user interaction occured @@ -16254,12 +16320,12 @@ class: }); InnerView = Ember.View.extend({ - click: function(event){ + click: function(event) { // will be called if rendered inside // an OuterView because OuterView's // eventManager doesn't handle click events }, - mouseEnter: function(event){ + mouseEnter: function(event) { // will never be called if rendered inside // an OuterView. } @@ -16469,6 +16535,14 @@ Ember.View = Ember.CoreView.extend( } }).volatile(), + /** + The parent context for this template. + */ + parentContext: function() { + var parentView = get(this, '_parentView'); + return parentView && get(parentView, '_context'); + }, + /** @private @@ -16570,7 +16644,7 @@ Ember.View = Ember.CoreView.extend( var view = get(this, 'parentView'); while (view) { - if(view instanceof klass) { return view; } + if (view instanceof klass) { return view; } view = get(view, 'parentView'); } }, @@ -16591,7 +16665,7 @@ Ember.View = Ember.CoreView.extend( function(view) { return klass.detect(view.constructor); }; while (view) { - if( isOfType(view) ) { return view; } + if (isOfType(view)) { return view; } view = get(view, 'parentView'); } }, @@ -16624,7 +16698,7 @@ Ember.View = Ember.CoreView.extend( var view = get(this, 'parentView'); while (view) { - if(get(view, 'parentView') instanceof klass) { return view; } + if (get(view, 'parentView') instanceof klass) { return view; } view = get(view, 'parentView'); } }, @@ -17147,7 +17221,7 @@ Ember.View = Ember.CoreView.extend( */ invokeRecursively: function(fn, includeSelf) { var childViews = (includeSelf === false) ? this._childViews : [this]; - var currentViews, view; + var currentViews, view, currentChildViews; while (childViews.length) { currentViews = childViews.slice(); @@ -17155,16 +17229,17 @@ Ember.View = Ember.CoreView.extend( for (var i=0, l=currentViews.length; isomeString
') + * ``` + * + * @method htmlSafe + * @for Ember.String + * @static + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ Ember.String.htmlSafe = function(str) { return new Handlebars.SafeString(str); }; @@ -20494,11 +20587,18 @@ var htmlSafe = Ember.String.htmlSafe; if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) { /** - See `Ember.String.htmlSafe`. - - @method htmlSafe - @for String - */ + * Mark a string as being safe for unescaped output with Handlebars. + * + * ```javascript + * '
someString
'.htmlSafe() + * ``` + * + * See `Ember.String.htmlSafe`. + * + * @method htmlSafe + * @for String + * @return {Handlebars.SafeString} a string that will not be html escaped by Handlebars + */ String.prototype.htmlSafe = function() { return htmlSafe(this); }; @@ -20998,7 +21098,7 @@ var forEach = Ember.ArrayPolyfills.forEach; var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers; -function exists(value){ +function exists(value) { return !Ember.isNone(value); } @@ -21023,7 +21123,7 @@ function bind(property, options, preserveContext, shouldDisplay, valueNormalizer var template, context, result = handlebarsGet(currentContext, property, options); - result = valueNormalizer(result); + result = valueNormalizer ? valueNormalizer(result) : result; context = preserveContext ? currentContext : result; if (shouldDisplay(result)) { @@ -21348,7 +21448,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) { ```javascript AView = Ember.View.extend({ - someProperty: function(){ + someProperty: function() { return "aValue"; }.property() }) @@ -21451,7 +21551,7 @@ EmberHandlebars.registerHelper('bindAttr', function(options) { var path = attrs[attr], normalized; - Ember.assert(fmt("You must provide a String for a bound attribute, not %@", [path]), typeof path === 'string'); + Ember.assert(fmt("You must provide an expression as the value of bound attribute. You specified: %@=%@", [attr, path]), typeof path === 'string'); normalized = normalizePath(ctx, path, options.data); @@ -22135,10 +22235,10 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { if (hash.itemView) { var controller = data.keywords.controller; - Ember.assert('itemView given, but no container is available', controller && controller.container); + Ember.assert('You specified an itemView, but the current context has no container to look the itemView up in. This probably means that you created a view manually, instead of through the container. Instead, use container.lookup("view:viewName"), which will properly instantiate your view.', controller && controller.container); var container = controller.container; itemViewClass = container.resolve('view:' + Ember.String.camelize(hash.itemView)); - Ember.assert('itemView not found in container', !!itemViewClass); + Ember.assert('You specified the itemView ' + hash.itemView + ", but it was not found at " + container.describe("view:" + hash.itemView) + " (and it was not registered in the container)", !!itemViewClass); } else if (hash.itemViewClass) { itemViewClass = handlebarsGet(collectionPrototype, hash.itemViewClass, options); } else { @@ -22156,7 +22256,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { if (hash.hasOwnProperty(prop)) { match = prop.match(/^item(.)(.*)$/); - if(match && prop !== 'itemController') { + if (match && prop !== 'itemController') { // Convert itemShouldFoo -> shouldFoo itemHash[match[1].toLowerCase() + match[2]] = hash[prop]; // Delete from hash as this will end up getting passed to the @@ -22183,7 +22283,7 @@ Ember.Handlebars.registerHelper('collection', function(path, options) { } if (emptyViewClass) { hash.emptyView = emptyViewClass; } - if(!hash.keyword){ + if (!hash.keyword) { itemHash._context = Ember.computed.alias('content'); } @@ -22230,7 +22330,7 @@ var handlebarsGet = Ember.Handlebars.get; Ember.Handlebars.registerHelper('unbound', function(property, fn) { var options = arguments[arguments.length - 1], helper, context, out; - if(arguments.length > 2) { + if (arguments.length > 2) { // Unbound helper call. options.data.isUnbound = true; helper = Ember.Handlebars.helpers[arguments[0]] || Ember.Handlebars.helperMissing; @@ -22288,7 +22388,7 @@ Ember.Handlebars.registerHelper('log', function(property, options) { @for Ember.Handlebars.helpers @param {String} property */ -Ember.Handlebars.registerHelper('debugger', function() { +Ember.Handlebars.registerHelper('debugger', function(options) { debugger; }); @@ -22334,6 +22434,11 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, { return this._super(); }, + _assertArrayLike: function(content) { + Ember.assert("The value that #each loops over must be an Array. You passed " + content.constructor + ", but it should have been an ArrayController", !Ember.ControllerMixin.detect(content) || (content && content.isGenerated) || content instanceof Ember.ArrayController); + Ember.assert("The value that #each loops over must be an Array. You passed " + ((Ember.ControllerMixin.detect(content) && content.get('model') !== undefined) ? ("" + content.get('model') + " (wrapped in " + content + ")") : ("" + content)), Ember.Array.detect(content)); + }, + disableContentObservers: function(callback) { Ember.removeBeforeObserver(this, 'content', null, '_contentWillChange'); Ember.removeObserver(this, 'content', null, '_contentDidChange'); @@ -22583,7 +22688,7 @@ GroupedEach.prototype = { ```javascript App.DeveloperController = Ember.ObjectController.extend({ - isAvailableForHire: function(){ + isAvailableForHire: function() { return !this.get('content.isEmployed') && this.get('content.isSeekingWork'); }.property('isEmployed', 'isSeekingWork') }) @@ -22816,7 +22921,7 @@ var get = Ember.get, set = Ember.set; @return {String} HTML string */ Ember.Handlebars.registerHelper('yield', function(options) { - var view = options.data.view, template; + var currentView = options.data.view, view = currentView, template; while (view && !get(view, 'layout')) { view = get(view, 'parentView'); @@ -22826,7 +22931,15 @@ Ember.Handlebars.registerHelper('yield', function(options) { template = get(view, 'template'); - if (template) { template(this, options); } + var keywords = view._parentView.cloneKeywords(); + + currentView.appendChild(Ember.View, { + tagName: '', + template: template, + context: get(view._parentView, 'context'), + controller: get(view._parentView, 'controller'), + templateData: {keywords: keywords} + }); }); })(); @@ -22928,17 +23041,23 @@ Ember.Checkbox = Ember.View.extend({ tagName: 'input', - attributeBindings: ['type', 'checked', 'disabled', 'tabindex', 'name'], + attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'], type: "checkbox", checked: false, disabled: false, + indeterminate: false, init: function() { this._super(); this.on("change", this, this._updateElementValue); }, + didInsertElement: function() { + this._super(); + this.get('element').indeterminate = !!this.get('indeterminate'); + }, + _updateElementValue: function() { set(this, 'checked', this.$().prop('checked')); } @@ -23702,7 +23821,7 @@ helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this; function program1(depth0,data) { - + var buffer = '', hashTypes, hashContexts; data.buffer.push("