diff --git a/packages/ember-application/lib/system/application.js b/packages/ember-application/lib/system/application.js index d7b61617071..0c0bf785ec7 100644 --- a/packages/ember-application/lib/system/application.js +++ b/packages/ember-application/lib/system/application.js @@ -9,6 +9,7 @@ import Ember from 'ember-metal'; // Ember.deprecate, Ember.assert, Ember.librari import isEnabled from 'ember-metal/features'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; +import EmptyObject from 'ember-metal/empty_object'; import { runLoadHooks } from 'ember-runtime/system/lazy_load'; import Namespace from 'ember-runtime/system/namespace'; import DefaultResolver from 'ember-application/system/resolver'; @@ -722,8 +723,8 @@ if (isEnabled('ember-application-visit')) { } Application.reopenClass({ - initializers: Object.create(null), - instanceInitializers: Object.create(null), + initializers: new EmptyObject(), + instanceInitializers: new EmptyObject(), /** Initializer receives an object which has the following attributes: diff --git a/packages/ember-htmlbars/lib/helpers.js b/packages/ember-htmlbars/lib/helpers.js index 8016f423ca2..b89ed29a085 100644 --- a/packages/ember-htmlbars/lib/helpers.js +++ b/packages/ember-htmlbars/lib/helpers.js @@ -8,8 +8,9 @@ @property helpers */ import Ember from 'ember-metal/core'; +import EmptyObject from 'ember-metal/empty_object'; -var helpers = Object.create(null); +var helpers = new EmptyObject(); /** @module ember diff --git a/packages/ember-metal/lib/alias.js b/packages/ember-metal/lib/alias.js index 77b77ffa2e2..78f6dcbde93 100644 --- a/packages/ember-metal/lib/alias.js +++ b/packages/ember-metal/lib/alias.js @@ -7,10 +7,8 @@ import { defineProperty } from 'ember-metal/properties'; import { ComputedProperty } from 'ember-metal/computed'; -import { - meta, - inspect -} from 'ember-metal/utils'; +import { inspect } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import { addDependentKeys, removeDependentKeys @@ -47,14 +45,14 @@ AliasedProperty.prototype.didUnwatch = function(obj, keyName) { AliasedProperty.prototype.setup = function(obj, keyName) { Ember.assert(`Setting alias '${keyName}' on self`, this.altKey !== keyName); var m = meta(obj); - if (m.watching[keyName]) { + if (m.peekWatching(keyName)) { addDependentKeys(this, obj, keyName, m); } }; AliasedProperty.prototype.teardown = function(obj, keyName) { var m = meta(obj); - if (m.watching[keyName]) { + if (m.peekWatching(keyName)) { removeDependentKeys(this, obj, keyName, m); } }; diff --git a/packages/ember-metal/lib/chains.js b/packages/ember-metal/lib/chains.js index 693de378a56..0111e832ead 100644 --- a/packages/ember-metal/lib/chains.js +++ b/packages/ember-metal/lib/chains.js @@ -1,7 +1,8 @@ import Ember from 'ember-metal/core'; // warn, assert, etc; import { get, normalizeTuple } from 'ember-metal/property_get'; -import { meta as metaFor } from 'ember-metal/utils'; +import { meta as metaFor } from 'ember-metal/meta'; import { watchKey, unwatchKey } from 'ember-metal/watch_key'; +import EmptyObject from 'ember-metal/empty_object'; var FIRST_KEY = /^([^\.]+)/; @@ -19,7 +20,7 @@ function isVolatile(obj) { function Chains() { } -Chains.prototype = Object.create(null); +Chains.prototype = new EmptyObject(); function ChainWatchers(obj) { // this obj would be the referencing chain node's parent node's value @@ -131,19 +132,17 @@ export function flushPendingChains() { ); } +function makeChainWatcher(obj) { + return new ChainWatchers(obj); +} + function addChainWatcher(obj, keyName, node) { if (!isObject(obj)) { return; } let m = metaFor(obj); - - if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { - m.chainWatchers = new ChainWatchers(obj); - } - - m.chainWatchers.add(keyName, node); - + m.writableChainWatchers(makeChainWatcher).add(keyName, node); watchKey(obj, keyName, m); } @@ -154,15 +153,14 @@ function removeChainWatcher(obj, keyName, node) { let m = obj.__ember_meta__; - if (!m || - m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { + if (!m || !m.readableChainWatchers()) { return; } // make meta writable m = metaFor(obj); - m.chainWatchers.remove(keyName, node); + m.readableChainWatchers().remove(keyName, node); unwatchKey(obj, keyName, m); } @@ -222,8 +220,9 @@ function lazyGet(obj, key) { return get(obj, key); // Otherwise attempt to get the cached value of the computed property } else { - if (meta.cache && key in meta.cache) { - return meta.cache[key]; + let cache = meta.readableCache(); + if (cache && key in cache) { + return cache[key]; } } } @@ -423,16 +422,17 @@ export function finishChains(obj) { // We only create meta if we really have to let m = obj.__ember_meta__; if (m) { + m = metaFor(obj); + // finish any current chains node watchers that reference obj - let chainWatchers = m.chainWatchers; + let chainWatchers = m.readableChainWatchers(); if (chainWatchers) { chainWatchers.revalidateAll(); } - // copy chains from prototype - let chains = m.chains; - if (chains && chains.value() !== obj) { - // need to check if meta is writable - metaFor(obj).chains = chains.copy(obj); + // ensure that if we have inherited any chains they have been + // copied onto our own meta. + if (m.readableChains()) { + m.writableChains(); } } } diff --git a/packages/ember-metal/lib/computed.js b/packages/ember-metal/lib/computed.js index d16a8b675ba..46a5a35d564 100644 --- a/packages/ember-metal/lib/computed.js +++ b/packages/ember-metal/lib/computed.js @@ -1,9 +1,7 @@ import Ember from 'ember-metal/core'; import { set } from 'ember-metal/property_set'; -import { - meta, - inspect -} from 'ember-metal/utils'; +import { inspect } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import expandProperties from 'ember-metal/expand_properties'; import EmberError from 'ember-metal/error'; import { @@ -266,9 +264,10 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { // _suspended is set via a CP.set to ensure we don't clear // the cached value set by the setter if (this._cacheable && this._suspended !== obj) { - var meta = metaFor(obj); - if (meta.cache && meta.cache[keyName] !== undefined) { - meta.cache[keyName] = undefined; + let meta = metaFor(obj); + let cache = meta.readableCache(); + if (cache && cache[keyName] !== undefined) { + cache[keyName] = undefined; removeDependentKeys(this, obj, keyName, meta); } } @@ -305,9 +304,9 @@ ComputedPropertyPrototype.get = function(obj, keyName) { var ret, cache, meta; if (this._cacheable) { meta = metaFor(obj); - cache = meta.cache; + cache = meta.writableCache(); - var result = cache && cache[keyName]; + var result = cache[keyName]; if (result === UNDEFINED) { return undefined; @@ -316,18 +315,16 @@ ComputedPropertyPrototype.get = function(obj, keyName) { } ret = this._getter.call(obj, keyName); - cache = meta.cache; - if (!cache) { - cache = meta.cache = {}; - } + if (ret === undefined) { cache[keyName] = UNDEFINED; } else { cache[keyName] = ret; } - if (meta.chainWatchers) { - meta.chainWatchers.revalidate(keyName); + let chainWatchers = meta.readableChainWatchers(); + if (chainWatchers) { + chainWatchers.revalidate(keyName); } addDependentKeys(this, obj, keyName, meta); } else { @@ -401,7 +398,7 @@ ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, valu var cacheable = this._cacheable; var setter = this._setter; var meta = metaFor(obj, cacheable); - var cache = meta.cache; + var cache = meta.readableCache(); var hadCachedValue = false; var cachedValue, ret; @@ -427,7 +424,7 @@ ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, valu if (hadCachedValue && cachedValue === ret) { return; } - var watched = meta.watching[keyName]; + var watched = meta.peekWatching(keyName); if (watched) { propertyWillChange(obj, keyName); } @@ -441,7 +438,7 @@ ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, valu addDependentKeys(this, obj, keyName, meta); } if (!cache) { - cache = meta.cache = {}; + cache = meta.writableCache(); } if (ret === undefined) { cache[keyName] = UNDEFINED; @@ -460,13 +457,13 @@ ComputedPropertyPrototype._set = function computedPropertySet(obj, keyName, valu /* called before property is overridden */ ComputedPropertyPrototype.teardown = function(obj, keyName) { var meta = metaFor(obj); - - if (meta.cache) { - if (keyName in meta.cache) { + let cache = meta.readableCache(); + if (cache) { + if (keyName in cache) { removeDependentKeys(this, obj, keyName, meta); } - if (this._cacheable) { delete meta.cache[keyName]; } + if (this._cacheable) { delete cache[keyName]; } } return null; // no value to restore @@ -560,7 +557,7 @@ export default function computed(func) { */ function cacheFor(obj, key) { var meta = obj['__ember_meta__']; - var cache = meta && meta.cache; + var cache = meta && meta.readableCache(); var ret = cache && cache[key]; if (ret === UNDEFINED) { diff --git a/packages/ember-metal/lib/dependent_keys.js b/packages/ember-metal/lib/dependent_keys.js index 9d93ed650f4..6a7990b8fcd 100644 --- a/packages/ember-metal/lib/dependent_keys.js +++ b/packages/ember-metal/lib/dependent_keys.js @@ -17,50 +17,19 @@ import { // DEPENDENT KEYS // -// data structure: -// meta.deps = { -// 'depKey': { -// 'keyName': count, -// } -// } - -/* - This function returns a map of unique dependencies for a - given object and key. -*/ -function keysForDep(depsMeta, depKey) { - var keys = depsMeta[depKey]; - if (!keys) { - // if there are no dependencies yet for a the given key - // create a new empty list of dependencies for the key - keys = depsMeta[depKey] = {}; - } else if (!depsMeta.hasOwnProperty(depKey)) { - // otherwise if the dependency list is inherited from - // a superclass, clone the hash - keys = depsMeta[depKey] = Object.create(keys); - } - return keys; -} - -function metaForDeps(meta) { - return keysForDep(meta, 'deps'); -} - export function addDependentKeys(desc, obj, keyName, meta) { // the descriptor has a list of dependent keys, so // add all of its dependent keys. - var depsMeta, idx, len, depKey, keys; + var idx, len, depKey, keys; var depKeys = desc._dependentKeys; if (!depKeys) { return; } - depsMeta = metaForDeps(meta); - for (idx = 0, len = depKeys.length; idx < len; idx++) { depKey = depKeys[idx]; // Lookup keys meta for depKey - keys = keysForDep(depsMeta, depKey); + keys = meta.writableDeps(depKey); // Increment the number of times depKey depends on keyName. keys[keyName] = (keys[keyName] || 0) + 1; // Watch the depKey @@ -72,17 +41,15 @@ export function removeDependentKeys(desc, obj, keyName, meta) { // the descriptor has a list of dependent keys, so // remove all of its dependent keys. var depKeys = desc._dependentKeys; - var depsMeta, idx, len, depKey, keys; + var idx, len, depKey, keys; if (!depKeys) { return; } - depsMeta = metaForDeps(meta); - for (idx = 0, len = depKeys.length; idx < len; idx++) { depKey = depKeys[idx]; // Lookup keys meta for depKey - keys = keysForDep(depsMeta, depKey); + keys = meta.writableDeps(depKey); // Decrement the number of times depKey depends on keyName. keys[keyName] = (keys[keyName] || 0) - 1; // Unwatch the depKey diff --git a/packages/ember-metal/lib/empty_object.js b/packages/ember-metal/lib/empty_object.js new file mode 100644 index 00000000000..dbe6d6ddb35 --- /dev/null +++ b/packages/ember-metal/lib/empty_object.js @@ -0,0 +1,19 @@ +// This exists because `Object.create(null)` is absurdly slow compared +// to `new EmptyObject()`. In either case, you want a null prototype +// when you're treating the object instances as arbitrary dictionaries +// and don't want your keys colliding with build-in methods on the +// default object prototype. + +var proto = Object.create(null, { + // without this, we will always still end up with (new + // EmptyObject()).constructor === Object + constructor: { + value: undefined, + enumerable: false, + writable: true + } +}); + +function EmptyObject() {} +EmptyObject.prototype = proto; +export default EmptyObject; diff --git a/packages/ember-metal/lib/events.js b/packages/ember-metal/lib/events.js index 81646951566..6f609b9bb39 100644 --- a/packages/ember-metal/lib/events.js +++ b/packages/ember-metal/lib/events.js @@ -9,14 +9,12 @@ */ import Ember from 'ember-metal/core'; import { - meta as metaFor, apply, applyStr } from 'ember-metal/utils'; +import { meta as metaFor } from 'ember-metal/meta'; -/* listener flags */ -var ONCE = 1; -var SUSPENDED = 2; +import { ONCE, SUSPENDED } from 'ember-metal/meta_listeners'; /* @@ -51,40 +49,10 @@ function indexOf(array, target, method) { return index; } -function actionsFor(obj, eventName) { - var meta = metaFor(obj, true); - var actions; - var listeners = meta.listeners; - - if (!listeners) { - listeners = meta.listeners = Object.create(null); - listeners.__source__ = obj; - } else if (listeners.__source__ !== obj) { - // setup inherited copy of the listeners object - listeners = meta.listeners = Object.create(listeners); - listeners.__source__ = obj; - } - - actions = listeners[eventName]; - - // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype - if (actions && actions.__source__ !== obj) { - actions = listeners[eventName] = listeners[eventName].slice(); - actions.__source__ = obj; - } else if (!actions) { - actions = listeners[eventName] = []; - actions.__source__ = obj; - } - - return actions; -} - export function accumulateListeners(obj, eventName, otherActions) { var meta = obj['__ember_meta__']; - var actions = meta && meta.listeners && meta.listeners[eventName]; - - if (!actions) { return; } - + if (!meta) { return; } + var actions = meta.matchingListeners(eventName); var newActions = []; for (var i = actions.length - 3; i >= 0; i -= 3) { @@ -122,19 +90,12 @@ export function addListener(obj, eventName, target, method, once) { target = null; } - var actions = actionsFor(obj, eventName); - var actionIndex = indexOf(actions, target, method); var flags = 0; - if (once) { flags |= ONCE; } - if (actionIndex !== -1) { - return; - } - - actions.push(target, method, flags); + metaFor(obj).addToListeners(eventName, target, method, flags); if ('function' === typeof obj.didAddListener) { obj.didAddListener(eventName, target, method); @@ -154,7 +115,7 @@ export function addListener(obj, eventName, target, method, once) { @param {Function|String} method A function or the name of a function to be called on `target` @public */ -function removeListener(obj, eventName, target, method) { +export function removeListener(obj, eventName, target, method) { Ember.assert('You must pass at least an object and event name to Ember.removeListener', !!obj && !!eventName); if (!method && 'function' === typeof target) { @@ -162,31 +123,11 @@ function removeListener(obj, eventName, target, method) { target = null; } - function _removeListener(target, method) { - var actions = actionsFor(obj, eventName); - var actionIndex = indexOf(actions, target, method); - - // action doesn't exist, give up silently - if (actionIndex === -1) { return; } - - actions.splice(actionIndex, 3); - + metaFor(obj).removeFromListeners(eventName, target, method, (...args) => { if ('function' === typeof obj.didRemoveListener) { - obj.didRemoveListener(eventName, target, method); - } - } - - if (method) { - _removeListener(target, method); - } else { - var meta = obj['__ember_meta__']; - var actions = meta && meta.listeners && meta.listeners[eventName]; - - if (!actions) { return; } - for (var i = actions.length - 3; i >= 0; i -= 3) { - _removeListener(actions[i], actions[i + 1]); + obj.didRemoveListener(...args); } - } + }); } /** @@ -208,25 +149,7 @@ function removeListener(obj, eventName, target, method) { @param {Function} callback */ export function suspendListener(obj, eventName, target, method, callback) { - if (!method && 'function' === typeof target) { - method = target; - target = null; - } - - var actions = actionsFor(obj, eventName); - var actionIndex = indexOf(actions, target, method); - - if (actionIndex !== -1) { - actions[actionIndex + 2] |= SUSPENDED; // mark the action as suspended - } - - try { - return callback.call(target); - } finally { - if (actionIndex !== -1) { - actions[actionIndex + 2] &= ~SUSPENDED; - } - } + return suspendListeners(obj, [eventName], target, method, callback); } /** @@ -247,30 +170,7 @@ export function suspendListeners(obj, eventNames, target, method, callback) { method = target; target = null; } - - var suspendedActions = []; - var actionsList = []; - - for (let i = 0, l = eventNames.length; i < l; i++) { - let eventName = eventNames[i]; - let actions = actionsFor(obj, eventName); - let actionIndex = indexOf(actions, target, method); - - if (actionIndex !== -1) { - actions[actionIndex + 2] |= SUSPENDED; - suspendedActions.push(actionIndex); - actionsList.push(actions); - } - } - - try { - return callback.call(target); - } finally { - for (let i = 0, l = suspendedActions.length; i < l; i++) { - let actionIndex = suspendedActions[i]; - actionsList[i][actionIndex + 2] &= ~SUSPENDED; - } - } + return metaFor(obj).suspendListeners(eventNames, target, method, callback); } /** @@ -282,18 +182,7 @@ export function suspendListeners(obj, eventNames, target, method, callback) { @param obj */ export function watchedEvents(obj) { - var listeners = obj['__ember_meta__'].listeners; - var ret = []; - - if (listeners) { - for (var eventName in listeners) { - if (eventName !== '__source__' && - listeners[eventName]) { - ret.push(eventName); - } - } - } - return ret; + return metaFor(obj).watchedEvents(); } /** @@ -314,10 +203,10 @@ export function watchedEvents(obj) { export function sendEvent(obj, eventName, params, actions) { if (!actions) { var meta = obj['__ember_meta__']; - actions = meta && meta.listeners && meta.listeners[eventName]; + actions = meta && meta.matchingListeners(eventName); } - if (!actions) { return; } + if (!actions || actions.length === 0) { return; } for (var i = actions.length - 3; i >= 0; i -= 3) { // looping in reverse for once listeners var target = actions[i]; @@ -354,9 +243,8 @@ export function sendEvent(obj, eventName, params, actions) { */ export function hasListeners(obj, eventName) { var meta = obj['__ember_meta__']; - var actions = meta && meta.listeners && meta.listeners[eventName]; - - return !!(actions && actions.length); + if (!meta) { return false; } + return meta.matchingListeners(eventName).length > 0; } /** @@ -369,7 +257,7 @@ export function hasListeners(obj, eventName) { export function listenersFor(obj, eventName) { var ret = []; var meta = obj['__ember_meta__']; - var actions = meta && meta.listeners && meta.listeners[eventName]; + var actions = meta && meta.matchingListeners(eventName); if (!actions) { return ret; } @@ -412,7 +300,3 @@ export function on(...args) { func.__ember_listens__ = events; return func; } - -export { - removeListener -}; diff --git a/packages/ember-metal/lib/main.js b/packages/ember-metal/lib/main.js index e8d7a25ed97..d4a4f12d1ad 100644 --- a/packages/ember-metal/lib/main.js +++ b/packages/ember-metal/lib/main.js @@ -14,9 +14,7 @@ import { unsubscribe as instrumentationUnsubscribe } from 'ember-metal/instrumentation'; import { - EMPTY_META, GUID_KEY, - META_DESC, apply, applyStr, canInvoke, @@ -24,12 +22,16 @@ import { guidFor, inspect, makeArray, - meta, deprecatedTryCatchFinally, tryInvoke, uuid, wrap } from 'ember-metal/utils'; +import { + EMPTY_META, + META_DESC, + meta +} from 'ember-metal/meta'; import EmberError from 'ember-metal/error'; import Cache from 'ember-metal/cache'; import Logger from 'ember-metal/logger'; diff --git a/packages/ember-metal/lib/map.js b/packages/ember-metal/lib/map.js index df8f54c2799..740983d2a36 100644 --- a/packages/ember-metal/lib/map.js +++ b/packages/ember-metal/lib/map.js @@ -23,6 +23,7 @@ import Ember from 'ember-metal/core'; import { guidFor } from 'ember-metal/utils'; +import EmptyObject from 'ember-metal/empty_object'; function missingFunction(fn) { throw new TypeError(`${Object.prototype.toString.call(fn)} is not a function`); @@ -33,10 +34,10 @@ function missingNew(name) { } function copyNull(obj) { - var output = Object.create(null); + var output = new EmptyObject(); for (var prop in obj) { - // hasOwnPropery is not needed because obj is Object.create(null); + // hasOwnPropery is not needed because obj is new EmptyObject(); output[prop] = obj[prop]; } @@ -92,7 +93,7 @@ OrderedSet.prototype = { @private */ clear() { - this.presenceSet = Object.create(null); + this.presenceSet = new EmptyObject(); this.list = []; this.size = 0; }, @@ -246,7 +247,7 @@ function Map() { if (this instanceof this.constructor) { this._keys = OrderedSet.create(); this._keys._silenceRemoveDeprecation = true; - this._values = Object.create(null); + this._values = new EmptyObject(); this.size = 0; } else { missingNew('OrderedSet'); @@ -405,7 +406,7 @@ Map.prototype = { */ clear() { this._keys.clear(); - this._values = Object.create(null); + this._values = new EmptyObject(); this.size = 0; }, diff --git a/packages/ember-metal/lib/meta.js b/packages/ember-metal/lib/meta.js new file mode 100644 index 00000000000..1c7a395ebdb --- /dev/null +++ b/packages/ember-metal/lib/meta.js @@ -0,0 +1,288 @@ +// Remove "use strict"; from transpiled module until +// https://bugs.webkit.org/show_bug.cgi?id=138038 is fixed +// +'REMOVE_USE_STRICT: true'; + +import isEnabled from 'ember-metal/features'; +import { protoMethods as listenerMethods } from 'ember-metal/meta_listeners'; +import EmptyObject from 'ember-metal/empty_object'; + +/** +@module ember-metal +*/ + +/* + This declares several meta-programmed members on the Meta class. Such + meta! + + In general, the `readable` variants will give you an object (if it + already exists) that you can read but should not modify. The + `writable` variants will give you a mutable object, and they will + create it if it didn't already exist. + + The following methods will get generated metaprogrammatically, and + I'm including them here for greppability: + + writableCache, readableCache, writableWatching, readableWatching, + peekWatching, clearWatching, writableMixins, readableMixins, + peekMixins, clearMixins, writableBindings, readableBindings, + peekBindings, clearBindings, writableValues, readableValues, + peekValues, clearValues, writableDeps, readableDeps, getAllDeps + writableChainWatchers, readableChainWatchers, writableChains, + readableChains + +*/ +let members = { + cache: ownMap, + watching: inheritedMap, + mixins: inheritedMap, + bindings: inheritedMap, + values: inheritedMap, + deps: inheritedMapOfMaps, + chainWatchers: ownCustomObject, + chains: inheritedCustomObject +}; + +let memberNames = Object.keys(members); + +function Meta(obj, parentMeta) { + this.cache = undefined; + this.watching = undefined; + this.mixins = undefined; + this.bindings = undefined; + this.values = undefined; + this.deps = undefined; + this.chainWatchers = undefined; + this.chains = undefined; + // used only internally + this.source = obj; + + // when meta(obj).proto === obj, the object is intended to be only a + // prototype and doesn't need to actually be observable itself + this.proto = undefined; + + // The next meta in our inheritance chain. We (will) track this + // explicitly instead of using prototypical inheritance because we + // have detailed knowledge of how each property should really be + // inherited, and we can optimize it much better than JS runtimes. + this.parent = parentMeta; + + this._initializeListeners(); +} + +for (let name in listenerMethods) { + Meta.prototype[name] = listenerMethods[name]; +} +memberNames.forEach(name => members[name](name, Meta)); + +// Implements a member that is a lazily created, non-inheritable +// POJO. +function ownMap(name, Meta) { + let key = memberProperty(name); + let capitalized = capitalize(name); + Meta.prototype['writable' + capitalized] = function() { + return this._getOrCreateOwnMap(key); + }; + Meta.prototype['readable' + capitalized] = function() { return this[key]; }; +} + +Meta.prototype._getOrCreateOwnMap = function(key) { + let ret = this[key]; + if (!ret) { + ret = this[key] = new EmptyObject(); + } + return ret; +}; + +// Implements a member that is a lazily created POJO with inheritable +// values. +function inheritedMap(name, Meta) { + let key = memberProperty(name); + let capitalized = capitalize(name); + + Meta.prototype['writable' + capitalized] = function() { + return this._getOrCreateInheritedMap(key); + }; + + Meta.prototype['readable' + capitalized] = function() { + return this._getInherited(key); + }; + + Meta.prototype['peek' + capitalized] = function(subkey) { + let map = this._getInherited(key); + if (map) { + return map[subkey]; + } + }; + + Meta.prototype['clear' + capitalized] = function() { + this[key] = new EmptyObject(); + }; +} + +Meta.prototype._getOrCreateInheritedMap = function(key) { + let ret = this[key]; + if (!ret) { + if (this.parent) { + ret = this[key] = Object.create(this.parent._getOrCreateInheritedMap(key)); + } else { + ret = this[key] = new EmptyObject(); + } + } + return ret; +}; + +Meta.prototype._getInherited = function(key) { + let pointer = this; + while (pointer !== undefined) { + if (pointer[key]) { + return pointer[key]; + } + pointer = pointer.parent; + } +}; + +// Implements a member that provides a lazily created map of maps, +// with inheritance at both levels. +function inheritedMapOfMaps(name, Meta) { + let key = memberProperty(name); + let capitalized = capitalize(name); + + Meta.prototype['writable' + capitalized] = function(subkey) { + let outerMap = this._getOrCreateInheritedMap(key); + let innerMap = outerMap[subkey]; + if (!innerMap) { + innerMap = outerMap[subkey] = new EmptyObject(); + } else if (!Object.hasOwnProperty.call(outerMap, subkey)) { + innerMap = outerMap[subkey] = Object.create(innerMap); + } + return innerMap; + }; + + Meta.prototype['readable' + capitalized] = function(subkey) { + let map = this._getInherited(key); + if (map) { + return map[subkey]; + } + }; + + Meta.prototype['getAll' + capitalized] = function() { + return this._getInherited(key); + }; +} + +// Implements a member that provides a non-heritable, lazily-created +// object using the method you provide. +function ownCustomObject(name, Meta) { + let key = memberProperty(name); + let capitalized = capitalize(name); + Meta.prototype['writable' + capitalized] = function(create) { + let ret = this[key]; + if (!ret) { + ret = this[key] = create(this.source); + } + return ret; + }; + Meta.prototype['readable' + capitalized] = function() { + return this[key]; + }; +} + +// Implements a member that provides an inheritable, lazily-created +// object using the method you provide. We will derived children from +// their parents by calling your object's `copy()` method. +function inheritedCustomObject(name, Meta) { + let key = memberProperty(name); + let capitalized = capitalize(name); + Meta.prototype['writable' + capitalized] = function(create) { + let ret = this[key]; + if (!ret) { + if (this.parent) { + ret = this[key] = this.parent['writable' + capitalized](create).copy(this.source); + } else { + ret = this[key] = create(this.source); + } + } + return ret; + }; + Meta.prototype['readable' + capitalized] = function() { + return this._getInherited(key); + }; +} + + +function memberProperty(name) { + return '_' + name; +} + +// there's a more general-purpose capitalize in ember-runtime, but we +// don't want to make ember-metal depend on ember-runtime. +function capitalize(name) { + return name.replace(/^\w/, m => m.toUpperCase()); +} + +export var META_DESC = { + writable: true, + configurable: true, + enumerable: false, + value: null +}; + +var EMBER_META_PROPERTY = { + name: '__ember_meta__', + descriptor: META_DESC +}; + +// Placeholder for non-writable metas. +export var EMPTY_META = new Meta(null); + +if (isEnabled('mandatory-setter')) { + EMPTY_META.writableValues(); +} + +/** + Retrieves the meta hash for an object. If `writable` is true ensures the + hash is writable for this object as well. + + The meta object contains information about computed property descriptors as + well as any watched properties and other information. You generally will + not access this information directly but instead work with higher level + methods that manipulate this hash indirectly. + + @method meta + @for Ember + @private + + @param {Object} obj The object to retrieve meta for + @param {Boolean} [writable=true] Pass `false` if you do not intend to modify + the meta hash, allowing the method to avoid making an unnecessary copy. + @return {Object} the meta hash for an object +*/ +export function meta(obj, writable) { + var ret = obj.__ember_meta__; + if (writable === false) { + return ret || EMPTY_META; + } + + if (ret && ret.source === obj) { + return ret; + } + + if (!ret) { + ret = new Meta(obj); + if (isEnabled('mandatory-setter')) { + ret.writableValues(); + } + } else { + ret = new Meta(obj, ret); + } + + if (obj.__defineNonEnumerable) { + obj.__defineNonEnumerable(EMBER_META_PROPERTY); + } else { + Object.defineProperty(obj, '__ember_meta__', META_DESC); + } + obj.__ember_meta__ = ret; + + return ret; +} diff --git a/packages/ember-metal/lib/meta_listeners.js b/packages/ember-metal/lib/meta_listeners.js new file mode 100644 index 00000000000..736dda52585 --- /dev/null +++ b/packages/ember-metal/lib/meta_listeners.js @@ -0,0 +1,154 @@ +/* + When we render a rich template hierarchy, the set of events that + *might* happen tends to be much larger than the set of events that + actually happen. This implies that we should make listener creation & + destruction cheap, even at the cost of making event dispatch more + expensive. + + Thus we store a new listener with a single push and no new + allocations, without even bothering to do deduplication -- we can + save that for dispatch time, if an event actually happens. + */ + +/* listener flags */ +export var ONCE = 1; +export var SUSPENDED = 2; + +export var protoMethods = { + + addToListeners(eventName, target, method, flags) { + if (!this._listeners) { + this._listeners = []; + } + this._listeners.push(eventName, target, method, flags); + }, + + _finalizeListeners() { + if (this._listenersFinalized) { return; } + if (!this._listeners) { this._listeners = []; } + let pointer = this.parent; + while (pointer) { + let listeners = pointer._listeners; + if (listeners) { + this._listeners = this._listeners.concat(listeners); + } + if (pointer._listenersFinalized) { break; } + pointer = pointer.parent; + } + this._listenersFinalized = true; + }, + + removeFromListeners(eventName, target, method, didRemove) { + let pointer = this; + while (pointer) { + let listeners = pointer._listeners; + if (listeners) { + for (let index = listeners.length - 4; index >= 0; index -= 4) { + if (listeners[index] === eventName && (!method || (listeners[index + 1] === target && listeners[index + 2] === method))) { + if (pointer === this) { + // we are modifying our own list, so we edit directly + if (typeof didRemove === 'function') { + didRemove(eventName, target, listeners[index + 2]); + } + listeners.splice(index, 4); + } else { + // we are trying to remove an inherited listener, so we do + // just-in-time copying to detach our own listeners from + // our inheritance chain. + this._finalizeListeners(); + return this.removeFromListeners(eventName, target, method); + } + } + } + } + if (pointer._listenersFinalized) { break; } + pointer = pointer.parent; + } + }, + + matchingListeners(eventName) { + let pointer = this; + let result = []; + while (pointer) { + let listeners = pointer._listeners; + if (listeners) { + for (let index = 0; index < listeners.length - 3; index += 4) { + if (listeners[index] === eventName) { + pushUniqueListener(result, listeners, index); + } + } + } + if (pointer._listenersFinalized) { break; } + pointer = pointer.parent; + } + let sus = this._suspendedListeners; + if (sus) { + for (let susIndex = 0; susIndex < sus.length - 2; susIndex += 3) { + if (eventName === sus[susIndex]) { + for (let resultIndex = 0; resultIndex < result.length - 2; resultIndex += 3) { + if (result[resultIndex] === sus[susIndex + 1] && result[resultIndex + 1] === sus[susIndex + 2]) { + result[resultIndex + 2] |= SUSPENDED; + } + } + } + } + } + return result; + }, + + suspendListeners(eventNames, target, method, callback) { + let sus = this._suspendedListeners; + if (!sus) { + sus = this._suspendedListeners = []; + } + for (let i = 0; i < eventNames.length; i++) { + sus.push(eventNames[i], target, method); + } + try { + return callback.call(target); + } finally { + if (sus.length === eventNames.length) { + this._suspendedListeners = undefined; + } else { + for (let i = sus.length - 3; i >= 0; i -= 3) { + if (sus[i + 1] === target && sus[i + 2] === method && eventNames.indexOf(sus[i]) !== -1) { + sus.splice(i, 3); + } + } + } + } + }, + + watchedEvents() { + let pointer = this; + let names = {}; + while (pointer) { + let listeners = pointer._listeners; + if (listeners) { + for (let index = 0; index < listeners.length - 3; index += 4) { + names[listeners[index]] = true; + } + } + if (pointer._listenersFinalized) { break; } + pointer = pointer.parent; + } + return Object.keys(names); + }, + + _initializeListeners() { + this._listeners = undefined; + this._listenersFinalized = undefined; + this._suspendedListeners = undefined; + } +}; + +function pushUniqueListener(destination, source, index) { + let target = source[index + 1]; + let method = source[index + 2]; + for (let destinationIndex = 0; destinationIndex < destination.length - 2; destinationIndex += 3) { + if (destination[destinationIndex] === target && destination[destinationIndex + 1] === method) { + return; + } + } + destination.push(target, method, source[index + 3]); +} diff --git a/packages/ember-metal/lib/mixin.js b/packages/ember-metal/lib/mixin.js index b7807b25cf7..bafe40de2cd 100644 --- a/packages/ember-metal/lib/mixin.js +++ b/packages/ember-metal/lib/mixin.js @@ -10,14 +10,15 @@ import Ember from 'ember-metal/core'; // warn, assert, wrap, et; import merge from 'ember-metal/merge'; +import EmptyObject from 'ember-metal/empty_object'; import { get } from 'ember-metal/property_get'; import { set, trySet } from 'ember-metal/property_set'; import { guidFor, - meta as metaFor, wrap, makeArray } from 'ember-metal/utils'; +import { meta as metaFor } from 'ember-metal/meta'; import expandProperties from 'ember-metal/expand_properties'; import { Descriptor, @@ -74,14 +75,7 @@ superFunction.call(primer, 1, 2); superFunction.call(primer, 1, 2, 3); function mixinsMeta(obj) { - var m = metaFor(obj, true); - var ret = m.mixins; - if (!ret) { - ret = m.mixins = {}; - } else if (!m.hasOwnProperty('mixins')) { - ret = m.mixins = Object.create(ret); - } - return ret; + return metaFor(obj, true).writableMixins(); } function isMethod(obj) { @@ -316,13 +310,7 @@ var IS_BINDING = /^.+Binding$/; function detectBinding(obj, key, value, m) { if (IS_BINDING.test(key)) { - var bindings = m.bindings; - if (!bindings) { - bindings = m.bindings = {}; - } else if (!m.hasOwnProperty('bindings')) { - bindings = m.bindings = Object.create(m.bindings); - } - bindings[key] = value; + m.writableBindings()[key] = value; } } @@ -345,7 +333,7 @@ function connectStreamBinding(obj, key, stream) { stream.subscribe(onNotify); if (obj._streamBindingSubscriptions === undefined) { - obj._streamBindingSubscriptions = Object.create(null); + obj._streamBindingSubscriptions = new EmptyObject(); } obj._streamBindingSubscriptions[key] = onNotify; @@ -353,7 +341,7 @@ function connectStreamBinding(obj, key, stream) { function connectBindings(obj, m) { // TODO Mixin.apply(instance) should disconnect binding if exists - var bindings = m.bindings; + var bindings = m.readableBindings(); var key, binding, to; if (bindings) { for (key in bindings) { @@ -374,7 +362,7 @@ function connectBindings(obj, m) { } } // mark as applied - m.bindings = {}; + m.clearBindings(); } } @@ -667,12 +655,9 @@ function _detect(curMixin, targetMixin, seen) { MixinPrototype.detect = function(obj) { if (!obj) { return false; } if (obj instanceof Mixin) { return _detect(obj, this, {}); } - var m = obj['__ember_meta__']; - var mixins = m && m.mixins; - if (mixins) { - return !!mixins[guidFor(this)]; - } - return false; + var m = obj.__ember_meta__; + if (!m) { return false; } + return !!m.peekMixins(guidFor(this)); }; MixinPrototype.without = function(...args) { @@ -712,7 +697,7 @@ MixinPrototype.keys = function() { // TODO: Make Ember.mixin Mixin.mixins = function(obj) { var m = obj['__ember_meta__']; - var mixins = m && m.mixins; + var mixins = m && m.readableMixins(); var ret = []; if (!mixins) { return ret; } diff --git a/packages/ember-metal/lib/properties.js b/packages/ember-metal/lib/properties.js index d5f7600ae11..3fd5f3f705e 100644 --- a/packages/ember-metal/lib/properties.js +++ b/packages/ember-metal/lib/properties.js @@ -4,7 +4,7 @@ import Ember from 'ember-metal/core'; import isEnabled from 'ember-metal/features'; -import { meta as metaFor } from 'ember-metal/utils'; +import { meta as metaFor } from 'ember-metal/meta'; import { overrideChains } from 'ember-metal/property_events'; // .......................................................... // DESCRIPTOR @@ -34,7 +34,7 @@ export function MANDATORY_SETTER_FUNCTION(name) { export function DEFAULT_GETTER_FUNCTION(name) { return function GETTER_FUNCTION() { var meta = this['__ember_meta__']; - return meta && meta.values[name]; + return meta && meta.peekValues(name); }; } @@ -89,7 +89,7 @@ export function defineProperty(obj, keyName, desc, data, meta) { if (!meta) { meta = metaFor(obj); } - var watchEntry = meta.watching[keyName]; + var watchEntry = meta.peekWatching(keyName); possibleDesc = obj[keyName]; existingDesc = (possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor) ? possibleDesc : undefined; @@ -122,7 +122,7 @@ export function defineProperty(obj, keyName, desc, data, meta) { if (isEnabled('mandatory-setter')) { if (watching) { - meta.values[keyName] = data; + meta.writableValues()[keyName] = data; Object.defineProperty(obj, keyName, { configurable: true, enumerable: true, diff --git a/packages/ember-metal/lib/property_events.js b/packages/ember-metal/lib/property_events.js index f46505f4f50..68a7caf1e33 100644 --- a/packages/ember-metal/lib/property_events.js +++ b/packages/ember-metal/lib/property_events.js @@ -36,7 +36,7 @@ var deferred = 0; */ function propertyWillChange(obj, keyName) { var m = obj['__ember_meta__']; - var watching = (m && m.watching[keyName] > 0) || keyName === 'length'; + var watching = (m && m.peekWatching(keyName) > 0) || keyName === 'length'; var proto = m && m.proto; var possibleDesc = obj[keyName]; var desc = (possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor) ? possibleDesc : undefined; @@ -76,7 +76,7 @@ function propertyWillChange(obj, keyName) { */ function propertyDidChange(obj, keyName) { var m = obj['__ember_meta__']; - var watching = (m && m.watching[keyName] > 0) || keyName === 'length'; + var watching = (m && m.peekWatching(keyName) > 0) || keyName === 'length'; var proto = m && m.proto; var possibleDesc = obj[keyName]; var desc = (possibleDesc !== null && typeof possibleDesc === 'object' && possibleDesc.isDescriptor) ? possibleDesc : undefined; @@ -98,7 +98,7 @@ function propertyDidChange(obj, keyName) { return; } - if (m && m.deps && m.deps[keyName]) { + if (m && m.readableDeps(keyName)) { dependentKeysDidChange(obj, keyName, m); } @@ -112,7 +112,7 @@ function dependentKeysWillChange(obj, depKey, meta) { if (obj.isDestroying) { return; } var deps; - if (meta && meta.deps && (deps = meta.deps[depKey])) { + if (meta && (deps = meta.readableDeps(depKey))) { var seen = WILL_SEEN; var top = !seen; @@ -133,7 +133,7 @@ function dependentKeysDidChange(obj, depKey, meta) { if (obj.isDestroying) { return; } var deps; - if (meta && meta.deps && (deps = meta.deps[depKey])) { + if (meta && (deps = meta.readableDeps(depKey))) { var seen = DID_SEEN; var top = !seen; @@ -191,26 +191,24 @@ function iterDeps(method, obj, deps, depKey, seen, meta) { } function chainsWillChange(obj, keyName, m) { - if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { - return; + let c = m.readableChainWatchers(); + if (c) { + c.notify(keyName, false, propertyWillChange); } - - m.chainWatchers.notify(keyName, false, propertyWillChange); } function chainsDidChange(obj, keyName, m) { - if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { - return; + let c = m.readableChainWatchers(); + if (c) { + c.notify(keyName, true, propertyDidChange); } - - m.chainWatchers.notify(keyName, true, propertyDidChange); } function overrideChains(obj, keyName, m) { - if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { - return; + let c = m.readableChainWatchers(); + if (c) { + c.revalidate(keyName); } - m.chainWatchers.revalidate(keyName); } /** diff --git a/packages/ember-metal/lib/property_get.js b/packages/ember-metal/lib/property_get.js index a96fdcc7892..26533b0f17a 100644 --- a/packages/ember-metal/lib/property_get.js +++ b/packages/ember-metal/lib/property_get.js @@ -69,8 +69,8 @@ export function get(obj, keyName) { return desc.get(obj, keyName); } else { if (isEnabled('mandatory-setter')) { - if (meta && meta.watching[keyName] > 0) { - ret = meta.values[keyName]; + if (meta && meta.peekWatching(keyName) > 0) { + ret = meta.peekValues(keyName); } else { ret = obj[keyName]; } diff --git a/packages/ember-metal/lib/property_set.js b/packages/ember-metal/lib/property_set.js index 7275e4ec106..16d591324ed 100644 --- a/packages/ember-metal/lib/property_set.js +++ b/packages/ember-metal/lib/property_set.js @@ -63,10 +63,10 @@ export function set(obj, keyName, value, tolerant) { // `setUnknownProperty` method exists on the object if (isUnknown && 'function' === typeof obj.setUnknownProperty) { obj.setUnknownProperty(keyName, value); - } else if (meta && meta.watching[keyName] > 0) { + } else if (meta && meta.peekWatching(keyName) > 0) { if (meta.proto !== obj) { if (isEnabled('mandatory-setter')) { - currentValue = meta.values[keyName]; + currentValue = meta.peekValues(keyName); } else { currentValue = obj[keyName]; } @@ -81,7 +81,7 @@ export function set(obj, keyName, value, tolerant) { ) { defineProperty(obj, keyName, null, value); // setup mandatory setter } else { - meta.values[keyName] = value; + meta.writableValues()[keyName] = value; } } else { obj[keyName] = value; diff --git a/packages/ember-metal/lib/streams/stream.js b/packages/ember-metal/lib/streams/stream.js index 372a31b0e47..57ee12656ef 100644 --- a/packages/ember-metal/lib/streams/stream.js +++ b/packages/ember-metal/lib/streams/stream.js @@ -2,6 +2,7 @@ import Ember from 'ember-metal/core'; import { getFirstKey, getTailPath } from 'ember-metal/path_cache'; import { addObserver, removeObserver } from 'ember-metal/observer'; import { isStream } from 'ember-metal/streams/utils'; +import EmptyObject from 'ember-metal/empty_object'; import Subscriber from 'ember-metal/streams/subscriber'; import Dependency from 'ember-metal/streams/dependency'; @@ -51,7 +52,7 @@ Stream.prototype = { getKey(key) { if (this.children === undefined) { - this.children = Object.create(null); + this.children = new EmptyObject(); } var keyStream = this.children[key]; @@ -69,7 +70,7 @@ Stream.prototype = { var tailPath = getTailPath(path); if (this.children === undefined) { - this.children = Object.create(null); + this.children = new EmptyObject(); } var keyStream = this.children[firstKey]; diff --git a/packages/ember-metal/lib/utils.js b/packages/ember-metal/lib/utils.js index ac9f5f79109..86f6a17095c 100644 --- a/packages/ember-metal/lib/utils.js +++ b/packages/ember-metal/lib/utils.js @@ -3,8 +3,6 @@ // 'REMOVE_USE_STRICT: true'; -import isEnabled from 'ember-metal/features'; - /** @module ember-metal */ @@ -139,18 +137,6 @@ var nullDescriptor = { value: null }; -var META_DESC = { - writable: true, - configurable: true, - enumerable: false, - value: null -}; - -export var EMBER_META_PROPERTY = { - name: '__ember_meta__', - descriptor: META_DESC -}; - export var GUID_KEY_PROPERTY = { name: GUID_KEY, descriptor: nullDescriptor @@ -280,88 +266,6 @@ export function guidFor(obj) { } } -// .......................................................... -// META -// -function Meta(obj) { - this.watching = {}; - this.cache = undefined; - this.source = obj; - this.deps = undefined; - this.listeners = undefined; - this.mixins = undefined; - this.bindings = undefined; - this.chains = undefined; - this.chainWatchers = undefined; - this.values = undefined; - this.proto = undefined; -} - -// Placeholder for non-writable metas. -var EMPTY_META = new Meta(null); - -if (isEnabled('mandatory-setter')) { - EMPTY_META.values = {}; -} - -/** - Retrieves the meta hash for an object. If `writable` is true ensures the - hash is writable for this object as well. - - The meta object contains information about computed property descriptors as - well as any watched properties and other information. You generally will - not access this information directly but instead work with higher level - methods that manipulate this hash indirectly. - - @method meta - @for Ember - @private - - @param {Object} obj The object to retrieve meta for - @param {Boolean} [writable=true] Pass `false` if you do not intend to modify - the meta hash, allowing the method to avoid making an unnecessary copy. - @return {Object} the meta hash for an object -*/ -function meta(obj, writable) { - var ret = obj.__ember_meta__; - if (writable === false) { - return ret || EMPTY_META; - } - - if (!ret) { - if (obj.__defineNonEnumerable) { - obj.__defineNonEnumerable(EMBER_META_PROPERTY); - } else { - Object.defineProperty(obj, '__ember_meta__', META_DESC); - } - - ret = new Meta(obj); - - if (isEnabled('mandatory-setter')) { - ret.values = {}; - } - - obj.__ember_meta__ = ret; - } else if (ret.source !== obj) { - if (obj.__defineNonEnumerable) { - obj.__defineNonEnumerable(EMBER_META_PROPERTY); - } else { - Object.defineProperty(obj, '__ember_meta__', META_DESC); - } - - ret = Object.create(ret); - ret.watching = Object.create(ret.watching); - ret.cache = undefined; - ret.source = obj; - - if (isEnabled('mandatory-setter')) { - ret.values = Object.create(ret.values); - } - - obj['__ember_meta__'] = ret; - } - return ret; -} /** @@ -593,9 +497,6 @@ export function applyStr(t, m, a) { export { GUID_KEY, - META_DESC, - EMPTY_META, - meta, makeArray, canInvoke }; diff --git a/packages/ember-metal/lib/watch_key.js b/packages/ember-metal/lib/watch_key.js index 473b49989a8..803ea869f92 100644 --- a/packages/ember-metal/lib/watch_key.js +++ b/packages/ember-metal/lib/watch_key.js @@ -1,7 +1,7 @@ import isEnabled from 'ember-metal/features'; import { meta as metaFor -} from 'ember-metal/utils'; +} from 'ember-metal/meta'; import { MANDATORY_SETTER_FUNCTION, DEFAULT_GETTER_FUNCTION @@ -12,7 +12,7 @@ export function watchKey(obj, keyName, meta) { if (keyName === 'length' && Array.isArray(obj)) { return; } var m = meta || metaFor(obj); - var watching = m.watching; + var watching = m.writableWatching(); // activate watching first time if (!watching[keyName]) { @@ -48,7 +48,7 @@ if (isEnabled('mandatory-setter')) { // this x in Y deopts, so keeping it in this function is better; if (configurable && isWritable && hasValue && keyName in obj) { - m.values[keyName] = obj[keyName]; + m.writableValues()[keyName] = obj[keyName]; Object.defineProperty(obj, keyName, { configurable: true, enumerable: Object.prototype.propertyIsEnumerable.call(obj, keyName), @@ -65,7 +65,7 @@ if (isEnabled('mandatory-setter')) { export function unwatchKey(obj, keyName, meta) { var m = meta || metaFor(obj); - var watching = m.watching; + var watching = m.writableWatching(); if (watching[keyName] === 1) { watching[keyName] = 0; @@ -91,7 +91,7 @@ export function unwatchKey(obj, keyName, meta) { enumerable: true, value: val }); - delete m.values[keyName]; + delete m.writableValues()[keyName]; }, get: DEFAULT_GETTER_FUNCTION(keyName) }); diff --git a/packages/ember-metal/lib/watch_path.js b/packages/ember-metal/lib/watch_path.js index a5d67dc4f10..d6f6d8a75f5 100644 --- a/packages/ember-metal/lib/watch_path.js +++ b/packages/ember-metal/lib/watch_path.js @@ -1,20 +1,17 @@ import { meta as metaFor -} from 'ember-metal/utils'; +} from 'ember-metal/meta'; import { ChainNode } from 'ember-metal/chains'; // get the chains for the current object. If the current object has // chains inherited from the proto they will be cloned and reconfigured for // the current object. function chainsFor(obj, meta) { - var m = meta || metaFor(obj); - var ret = m.chains; - if (!ret) { - ret = m.chains = new ChainNode(null, null, obj); - } else if (ret.value() !== obj) { - ret = m.chains = ret.copy(obj); - } - return ret; + return (meta || metaFor(obj)).writableChains(makeChainNode); +} + +function makeChainNode(obj) { + return new ChainNode(null, null, obj); } export function watchPath(obj, keyPath, meta) { @@ -22,7 +19,7 @@ export function watchPath(obj, keyPath, meta) { if (keyPath === 'length' && Array.isArray(obj)) { return; } var m = meta || metaFor(obj); - var watching = m.watching; + var watching = m.writableWatching(); if (!watching[keyPath]) { // activate watching first time watching[keyPath] = 1; @@ -34,7 +31,7 @@ export function watchPath(obj, keyPath, meta) { export function unwatchPath(obj, keyPath, meta) { var m = meta || metaFor(obj); - var watching = m.watching; + var watching = m.writableWatching(); if (watching[keyPath] === 1) { watching[keyPath] = 0; diff --git a/packages/ember-metal/lib/watching.js b/packages/ember-metal/lib/watching.js index ed21b727245..301df33ab00 100644 --- a/packages/ember-metal/lib/watching.js +++ b/packages/ember-metal/lib/watching.js @@ -46,7 +46,7 @@ export { watch }; export function isWatching(obj, key) { var meta = obj['__ember_meta__']; - return (meta && meta.watching[key]) > 0; + return (meta && meta.peekWatching(key)) > 0; } watch.flushPending = flushPendingChains; @@ -81,7 +81,7 @@ export function destroy(obj) { if (meta) { obj['__ember_meta__'] = null; // remove chainWatchers to remove circular references that would prevent GC - node = meta.chains; + node = meta.readableChains(); if (node) { NODE_STACK.push(node); // process tree diff --git a/packages/ember-metal/tests/accessors/mandatory_setters_test.js b/packages/ember-metal/tests/accessors/mandatory_setters_test.js index 993cbf4372d..bf3a12e55ff 100644 --- a/packages/ember-metal/tests/accessors/mandatory_setters_test.js +++ b/packages/ember-metal/tests/accessors/mandatory_setters_test.js @@ -2,14 +2,14 @@ import isEnabled from 'ember-metal/features'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; import { watch } from 'ember-metal/watching'; -import { meta as metaFor } from 'ember-metal/utils'; +import { meta as metaFor } from 'ember-metal/meta'; QUnit.module('mandatory-setters'); function hasMandatorySetter(object, property) { var meta = metaFor(object); - - return property in meta.values; + let values = meta.readableValues(); + return values && property in values; } if (isEnabled('mandatory-setter')) { @@ -114,7 +114,7 @@ if (isEnabled('mandatory-setter')) { }); watch(obj, 'someProp'); - ok(!('someProp' in meta.values), 'blastix'); + ok(!('someProp' in meta.readableValues()), 'blastix'); }); QUnit.test('sets up mandatory-setter if property comes from prototype', function() { @@ -131,7 +131,7 @@ if (isEnabled('mandatory-setter')) { watch(obj2, 'someProp'); var meta = metaFor(obj2); - ok(('someProp' in meta.values), 'mandatory setter has been setup'); + ok(('someProp' in meta.readableValues()), 'mandatory setter has been setup'); expectAssertion(function() { obj2.someProp = 'foo-bar'; diff --git a/packages/ember-metal/tests/alias_test.js b/packages/ember-metal/tests/alias_test.js index 4efe77b35b6..e432314dcc5 100644 --- a/packages/ember-metal/tests/alias_test.js +++ b/packages/ember-metal/tests/alias_test.js @@ -2,7 +2,7 @@ import alias from 'ember-metal/alias'; import { defineProperty } from 'ember-metal/properties'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; -import { meta } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import { isWatching } from 'ember-metal/watching'; import { addObserver, removeObserver } from 'ember-metal/observer'; @@ -37,9 +37,9 @@ QUnit.test('basic lifecycle', function() { defineProperty(obj, 'bar', alias('foo.faz')); var m = meta(obj); addObserver(obj, 'bar', incrementCount); - equal(m.deps['foo.faz'].bar, 1); + equal(m.readableDeps('foo.faz').bar, 1); removeObserver(obj, 'bar', incrementCount); - equal(m.deps['foo.faz'].bar, 0); + equal(m.readableDeps('foo.faz').bar, 0); }); QUnit.test('begins watching alt key as soon as alias is watched', function() { diff --git a/packages/ember-metal/tests/chains_test.js b/packages/ember-metal/tests/chains_test.js index 774b62c1472..ec3518078bd 100644 --- a/packages/ember-metal/tests/chains_test.js +++ b/packages/ember-metal/tests/chains_test.js @@ -14,8 +14,7 @@ QUnit.test('finishChains should properly copy chains from prototypes to instance var childObj = Object.create(obj); finishChains(childObj); - - ok(obj['__ember_meta__'].chains !== childObj['__ember_meta__'].chains, 'The chains object is copied'); + ok(obj['__ember_meta__'].readableChains() !== childObj['__ember_meta__'].readableChains(), 'The chains object is copied'); }); diff --git a/packages/ember-metal/tests/events_test.js b/packages/ember-metal/tests/events_test.js index d53334fa804..68e507b8ffc 100644 --- a/packages/ember-metal/tests/events_test.js +++ b/packages/ember-metal/tests/events_test.js @@ -1,5 +1,5 @@ import { Mixin } from 'ember-metal/mixin'; -import { meta } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import { on, @@ -179,7 +179,6 @@ QUnit.test('calling removeListener without method should remove all listeners', addListener(obj, 'event!', F2); equal(hasListeners(obj, 'event!'), true, 'has listeners'); - removeListener(obj, 'event!'); equal(hasListeners(obj, 'event!'), false, 'has no more listeners'); @@ -205,7 +204,7 @@ QUnit.test('while suspended, it should not be possible to add a duplicate listen suspendListener(obj, 'event!', target, target.method, callback); equal(target.count, 1, 'should invoke'); - equal(meta(obj).listeners['event!'].length, 3, 'a duplicate listener wasn\'t added'); + equal(meta(obj).matchingListeners('event!').length, 3, 'a duplicate listener wasn\'t added'); // now test suspendListeners... @@ -214,7 +213,7 @@ QUnit.test('while suspended, it should not be possible to add a duplicate listen suspendListeners(obj, ['event!'], target, target.method, callback); equal(target.count, 2, 'should have invoked again'); - equal(meta(obj).listeners['event!'].length, 3, 'a duplicate listener wasn\'t added'); + equal(meta(obj).matchingListeners('event!').length, 3, 'a duplicate listener wasn\'t added'); }); QUnit.test('a listener can be added as part of a mixin', function() { diff --git a/packages/ember-metal/tests/meta_test.js b/packages/ember-metal/tests/meta_test.js new file mode 100644 index 00000000000..125b30f4ef6 --- /dev/null +++ b/packages/ember-metal/tests/meta_test.js @@ -0,0 +1,98 @@ +import { + meta +} from 'ember-metal/meta'; + +QUnit.module('Ember.meta'); + +QUnit.test('should return the same hash for an object', function() { + var obj = {}; + + meta(obj).foo = 'bar'; + + equal(meta(obj).foo, 'bar', 'returns same hash with multiple calls to Ember.meta()'); +}); + +QUnit.test('meta is not enumerable', function () { + var proto, obj, props, prop; + proto = { foo: 'bar' }; + meta(proto); + obj = Object.create(proto); + meta(obj); + obj.bar = 'baz'; + props = []; + for (prop in obj) { + props.push(prop); + } + deepEqual(props.sort(), ['bar', 'foo']); + if (typeof JSON !== 'undefined' && 'stringify' in JSON) { + try { + JSON.stringify(obj); + } catch (e) { + ok(false, 'meta should not fail JSON.stringify'); + } + } +}); + +QUnit.test('meta is not enumerable', function () { + var proto, obj, props, prop; + proto = { foo: 'bar' }; + meta(proto); + obj = Object.create(proto); + meta(obj); + obj.bar = 'baz'; + props = []; + for (prop in obj) { + props.push(prop); + } + deepEqual(props.sort(), ['bar', 'foo']); + if (typeof JSON !== 'undefined' && 'stringify' in JSON) { + try { + JSON.stringify(obj); + } catch (e) { + ok(false, 'meta should not fail JSON.stringify'); + } + } +}); + +QUnit.test('meta.listeners basics', function(assert) { + let t = {}; + let m = meta({}); + m.addToListeners('hello', t, 'm', 0); + let matching = m.matchingListeners('hello'); + assert.equal(matching.length, 3); + assert.equal(matching[0], t); + m.removeFromListeners('hello', t, 'm'); + matching = m.matchingListeners('hello'); + assert.equal(matching.length, 0); +}); + +QUnit.test('meta.listeners inheritance', function(assert) { + let target = {}; + let parent = {}; + let parentMeta = meta(parent); + parentMeta.addToListeners('hello', target, 'm', 0); + + let child = Object.create(parent); + let m = meta(child); + + let matching = m.matchingListeners('hello'); + assert.equal(matching.length, 3); + assert.equal(matching[0], target); + assert.equal(matching[1], 'm'); + assert.equal(matching[2], 0); + m.removeFromListeners('hello', target, 'm'); + matching = m.matchingListeners('hello'); + assert.equal(matching.length, 0); + matching = parentMeta.matchingListeners('hello'); + assert.equal(matching.length, 3); +}); + +QUnit.test('meta.listeners deduplication', function(assert) { + let t = {}; + let m = meta({}); + m.addToListeners('hello', t, 'm', 0); + m.addToListeners('hello', t, 'm', 0); + let matching = m.matchingListeners('hello'); + assert.equal(matching.length, 3); + assert.equal(matching[0], t); +}); diff --git a/packages/ember-metal/tests/utils/meta_test.js b/packages/ember-metal/tests/utils/meta_test.js deleted file mode 100644 index 49982786100..00000000000 --- a/packages/ember-metal/tests/utils/meta_test.js +++ /dev/null @@ -1,57 +0,0 @@ -import { - meta -} from 'ember-metal/utils'; - -QUnit.module('Ember.meta'); - -QUnit.test('should return the same hash for an object', function() { - var obj = {}; - - meta(obj).foo = 'bar'; - - equal(meta(obj).foo, 'bar', 'returns same hash with multiple calls to Ember.meta()'); -}); - -QUnit.module('Ember.meta enumerable'); - -QUnit.test('meta is not enumerable', function () { - var proto, obj, props, prop; - proto = { foo: 'bar' }; - meta(proto); - obj = Object.create(proto); - meta(obj); - obj.bar = 'baz'; - props = []; - for (prop in obj) { - props.push(prop); - } - deepEqual(props.sort(), ['bar', 'foo']); - if (typeof JSON !== 'undefined' && 'stringify' in JSON) { - try { - JSON.stringify(obj); - } catch (e) { - ok(false, 'meta should not fail JSON.stringify'); - } - } -}); - -QUnit.test('meta is not enumerable', function () { - var proto, obj, props, prop; - proto = { foo: 'bar' }; - meta(proto); - obj = Object.create(proto); - meta(obj); - obj.bar = 'baz'; - props = []; - for (prop in obj) { - props.push(prop); - } - deepEqual(props.sort(), ['bar', 'foo']); - if (typeof JSON !== 'undefined' && 'stringify' in JSON) { - try { - JSON.stringify(obj); - } catch (e) { - ok(false, 'meta should not fail JSON.stringify'); - } - } -}); diff --git a/packages/ember-metal/tests/watching/watch_test.js b/packages/ember-metal/tests/watching/watch_test.js index 910a9d8e9c6..7926003a6d8 100644 --- a/packages/ember-metal/tests/watching/watch_test.js +++ b/packages/ember-metal/tests/watching/watch_test.js @@ -203,15 +203,15 @@ QUnit.test('when watching a global object, destroy should remove chain watchers watch(obj, 'Global.foo'); var meta_Global = Ember.meta(Global); - var chainNode = Ember.meta(obj).chains._chains.Global._chains.foo; + var chainNode = Ember.meta(obj).readableChains()._chains.Global._chains.foo; - equal(meta_Global.watching.foo, 1, 'should be watching foo'); - equal(meta_Global.chainWatchers.has('foo', chainNode), true, 'should have chain watcher'); + equal(meta_Global.peekWatching('foo'), 1, 'should be watching foo'); + equal(meta_Global.readableChainWatchers().has('foo', chainNode), true, 'should have chain watcher'); destroy(obj); - equal(meta_Global.watching.foo, 0, 'should not be watching foo'); - equal(meta_Global.chainWatchers.has('foo', chainNode), false, 'should not have chain watcher'); + equal(meta_Global.peekWatching('foo'), 0, 'should not be watching foo'); + equal(meta_Global.readableChainWatchers().has('foo', chainNode), false, 'should not have chain watcher'); lookup['Global'] = Global = null; // reset }); @@ -225,15 +225,15 @@ QUnit.test('when watching another object, destroy should remove chain watchers f watch(objA, 'b.foo'); var meta_objB = Ember.meta(objB); - var chainNode = Ember.meta(objA).chains._chains.b._chains.foo; + var chainNode = Ember.meta(objA).readableChains()._chains.b._chains.foo; - equal(meta_objB.watching.foo, 1, 'should be watching foo'); - equal(meta_objB.chainWatchers.has('foo', chainNode), true, 'should have chain watcher'); + equal(meta_objB.peekWatching('foo'), 1, 'should be watching foo'); + equal(meta_objB.readableChainWatchers().has('foo', chainNode), true, 'should have chain watcher'); destroy(objA); - equal(meta_objB.watching.foo, 0, 'should not be watching foo'); - equal(meta_objB.chainWatchers.has('foo', chainNode), false, 'should not have chain watcher'); + equal(meta_objB.peekWatching('foo'), 0, 'should not be watching foo'); + equal(meta_objB.readableChainWatchers().has('foo', chainNode), false, 'should not have chain watcher'); }); // TESTS for length property diff --git a/packages/ember-routing-htmlbars/lib/keywords/render.js b/packages/ember-routing-htmlbars/lib/keywords/render.js index c08a00bcd57..142d38ae5d6 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/render.js +++ b/packages/ember-routing-htmlbars/lib/keywords/render.js @@ -1,5 +1,6 @@ import Ember from 'ember-metal/core'; // assert import { get } from 'ember-metal/property_get'; +import EmptyObject from 'ember-metal/empty_object'; import EmberError from 'ember-metal/error'; import { isStream, read } from 'ember-metal/streams/utils'; import { camelize } from 'ember-runtime/system/string'; @@ -186,7 +187,7 @@ function childOutletState(name, env) { if (!selectedOutletState) { return; } var matched = selectedOutletState.outlets[name]; if (matched) { - var childState = Object.create(null); + var childState = new EmptyObject(); childState[matched.render.outlet] = matched; matched.wasUsed = true; return childState; diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js index 1c24660e344..b3899b5fef9 100644 --- a/packages/ember-routing/lib/system/router.js +++ b/packages/ember-routing/lib/system/router.js @@ -4,6 +4,7 @@ import EmberError from 'ember-metal/error'; import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; import { defineProperty } from 'ember-metal/properties'; +import EmptyObject from 'ember-metal/empty_object'; import { computed } from 'ember-metal/computed'; import merge from 'ember-metal/merge'; import run from 'ember-metal/run_loop'; @@ -101,7 +102,7 @@ var EmberRouter = EmberObject.extend(Evented, { init() { this._activeViews = {}; - this._qpCache = Object.create(null); + this._qpCache = new EmptyObject(); this._resetQueuedQueryParameterChanges(); }, @@ -463,7 +464,7 @@ var EmberRouter = EmberObject.extend(Evented, { }, _getHandlerFunction() { - var seen = Object.create(null); + var seen = new EmptyObject(); var container = this.container; var DefaultRoute = container.lookupFactory('route:basic'); @@ -1048,7 +1049,7 @@ function appendLiveRoute(liveRoutes, defaultParentState, renderOptions) { var target; var myState = { render: renderOptions, - outlets: Object.create(null) + outlets: new EmptyObject() }; if (renderOptions.into) { target = findLiveRoute(liveRoutes, renderOptions.into); @@ -1082,7 +1083,7 @@ function appendOrphan(liveRoutes, into, myState) { render: { name: '__ember_orphans__' }, - outlets: Object.create(null) + outlets: new EmptyObject() }; } liveRoutes.outlets.__ember_orphans__.outlets[into] = myState; diff --git a/packages/ember-runtime/lib/mixins/-proxy.js b/packages/ember-runtime/lib/mixins/-proxy.js index 85dde327351..eb3a9e93d72 100644 --- a/packages/ember-runtime/lib/mixins/-proxy.js +++ b/packages/ember-runtime/lib/mixins/-proxy.js @@ -6,7 +6,7 @@ import Ember from 'ember-metal/core'; // Ember.assert import { get } from 'ember-metal/property_get'; import { set } from 'ember-metal/property_set'; -import { meta } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import { addObserver, removeObserver, diff --git a/packages/ember-runtime/lib/system/core_object.js b/packages/ember-runtime/lib/system/core_object.js index debffee5561..0b705914b7e 100644 --- a/packages/ember-runtime/lib/system/core_object.js +++ b/packages/ember-runtime/lib/system/core_object.js @@ -26,9 +26,9 @@ import { generateGuid, GUID_KEY_PROPERTY, NEXT_SUPER_PROPERTY, - meta, makeArray } from 'ember-metal/utils'; +import { meta } from 'ember-metal/meta'; import { finishChains } from 'ember-metal/chains'; import { sendEvent } from 'ember-metal/events'; import { @@ -109,13 +109,7 @@ function makeCtor() { var value = properties[keyName]; if (IS_BINDING.test(keyName)) { - var bindings = m.bindings; - if (!bindings) { - bindings = m.bindings = {}; - } else if (!m.hasOwnProperty('bindings')) { - bindings = m.bindings = Object.create(m.bindings); - } - bindings[keyName] = value; + m.writableBindings()[keyName] = value; } var possibleDesc = this[keyName]; @@ -864,7 +858,7 @@ CoreObject.reopen({ didDefineProperty(proto, key, value) { if (hasCachedComputedProperties === false) { return; } if (value instanceof Ember.ComputedProperty) { - var cache = Ember.meta(this.constructor).cache; + var cache = Ember.meta(this.constructor).readableCache(); if (cache && cache._computedProperties !== undefined) { cache._computedProperties = undefined; diff --git a/packages/ember-views/lib/views/component.js b/packages/ember-views/lib/views/component.js index 7cf2539b34a..5767947860f 100644 --- a/packages/ember-views/lib/views/component.js +++ b/packages/ember-views/lib/views/component.js @@ -171,7 +171,7 @@ var Component = View.extend(TargetActionSupport, ComponentTemplateDeprecation, { _template: computed('templateName', { get() { - if (get(this, '_deprecatedFlagForBlockProvided')) { + if (this._deprecatedFlagForBlockProvided) { return true; } var templateName = get(this, 'templateName'); diff --git a/packages/ember-views/lib/views/text_field.js b/packages/ember-views/lib/views/text_field.js index 1a403ac72eb..0fc539b6827 100644 --- a/packages/ember-views/lib/views/text_field.js +++ b/packages/ember-views/lib/views/text_field.js @@ -6,9 +6,10 @@ import { computed } from 'ember-metal/computed'; import environment from 'ember-metal/environment'; import Component from 'ember-views/views/component'; import TextSupport from 'ember-views/mixins/text_support'; +import EmptyObject from 'ember-metal/empty_object'; var inputTypeTestElement; -var inputTypes = Object.create(null); +var inputTypes = new EmptyObject(); function canSetTypeOfInput(type) { if (type in inputTypes) { return inputTypes[type]; diff --git a/packages/ember/tests/routing/query_params_test.js b/packages/ember/tests/routing/query_params_test.js index ce42493a938..02508274021 100644 --- a/packages/ember/tests/routing/query_params_test.js +++ b/packages/ember/tests/routing/query_params_test.js @@ -1811,7 +1811,7 @@ if (isEnabled('ember-routing-route-configured-query-params')) { App.OtherRoute = Ember.Route.extend({ model(p, trans) { var m = Ember.meta(trans.params.application); - ok(!m.watching.woot, 'A meta object isn\'t constructed for this params POJO'); + ok(!m.peekWatching('woot'), 'A meta object isn\'t constructed for this params POJO'); } }); @@ -2960,7 +2960,7 @@ if (isEnabled('ember-routing-route-configured-query-params')) { App.OtherRoute = Ember.Route.extend({ model(p, trans) { var m = Ember.meta(trans.params.application); - ok(!m.watching.woot, 'A meta object isn\'t constructed for this params POJO'); + ok(!m.peekWatching('woot'), 'A meta object isn\'t constructed for this params POJO'); } }); @@ -3102,5 +3102,3 @@ QUnit.test('handle routes names that clash with Object.prototype properties', fu var controller = container.lookup('controller:constructor'); equal(get(controller, 'foo'), '999'); }); - -