Skip to content
14 changes: 14 additions & 0 deletions packages/ember-metal/lib/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import EmptyObject from 'ember-metal/empty_object';
*/
let members = {
cache: ownMap,
weak: ownMap,
watching: inheritedMap,
mixins: inheritedMap,
bindings: inheritedMap,
Expand All @@ -53,6 +54,7 @@ function Meta(obj, parentMeta) {
this._deps = undefined;
this._chainWatchers = undefined;
this._chains = undefined;
this._weak = undefined;
// used only internally
this.source = obj;

Expand Down Expand Up @@ -148,6 +150,18 @@ Meta.prototype._getInherited = function(key) {
}
};

Meta.prototype.getWeak = function(symbol) {
var weak = this.readableWeak();
if (weak) {
return weak[symbol];
}
};

Meta.prototype.setWeak = function(symbol, value) {
var weak = this.writableWeak();
return weak[symbol] = value;
};

Meta.prototype._findInherited = function(key, subkey) {
let pointer = this;
while (pointer !== undefined) {
Expand Down
74 changes: 74 additions & 0 deletions packages/ember-metal/lib/weak_map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { assert } from 'ember-metal/debug';
import { GUID_KEY } from 'ember-metal/utils';
import { meta } from 'ember-metal/meta';

var id = 0;
function UNDEFINED() {}

/*
* @public
* @class Ember.WeakMap
*
* Weak relationship from Map -> Key, but not Key to Map.
*
* Key must be a non null object
*/
export default function WeakMap() {
this._id = GUID_KEY + (id++);
}

/*
* @method get
* @param key {Object}
* @return {*} stored value
*/
WeakMap.prototype.get = function(obj) {
var map = meta(obj).readableWeak();
if (map) {
if (map[this._id] === UNDEFINED) {
return undefined;
}

return map[this._id];
}
};

/*
* @method set
* @param key {Object}
* @param value {Any}
* @return {Any} stored value
*/
WeakMap.prototype.set = function(obj, value) {
assert('Uncaught TypeError: Invalid value used as weak map key', obj && (typeof obj === 'object' || typeof obj === 'function'));

if (value === undefined) {
value = UNDEFINED;
}

meta(obj).writableWeak()[this._id] = value;
return this;
};

/*
* @method has
* @param key {Object}
* @return {Boolean} if the key exists
*/
WeakMap.prototype.has = function(obj) {
var map = meta(obj).readableWeak();

return (map && map[this._id] !== undefined);
};

/*
* @method delete
* @param key {Object}
*/
WeakMap.prototype.delete = function(obj) {
if (this.has(obj)) {
delete meta(obj).writableWeak()[this._id];
}

return this;
};
90 changes: 90 additions & 0 deletions packages/ember-metal/tests/weak_map_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import WeakMap from 'ember-metal/weak_map';

QUnit.module('Ember.WeakMap');

QUnit.test('has weakMap like qualities', function(assert) {
var map = new WeakMap();
var map2 = new WeakMap();

var a = {};
var b = {};
var c = {};

equal(map.get(a), undefined);
equal(map.get(b), undefined);
equal(map.get(c), undefined);

equal(map2.get(a), undefined);
equal(map2.get(b), undefined);
equal(map2.get(c), undefined);

equal(map.set(a, 1), map, 'map.set should return itself');
equal(map.get(a), 1);
equal(map.set(b, undefined), map);
equal(map.set(a, 2), map);
equal(map.get(a), 2);
equal(map.set(b, undefined), map);

equal(map2.get(a), undefined);
equal(map2.get(b), undefined);
equal(map2.get(c), undefined);

equal(map.set(c, 1), map);
equal(map.get(c), 1);
equal(map.get(a), 2);
equal(map.get(b), undefined);

equal(map2.set(a, 3), map2);
equal(map2.set(b, 4), map2);
equal(map2.set(c, 5), map2);

equal(map2.get(a), 3);
equal(map2.get(b), 4);
equal(map2.get(c), 5);

equal(map.get(c), 1);
equal(map.get(a), 2);
equal(map.get(b), undefined);
});

QUnit.test('that error is thrown when using a primitive key', function(assert) {
var map = new WeakMap();

expectAssertion(function() {
map.set('a', 1);
}, /Uncaught TypeError: Invalid value used as weak map key/);

expectAssertion(function() {
map.set(1, 1);
}, /Uncaught TypeError: Invalid value used as weak map key/);

expectAssertion(function() {
map.set(true, 1);
}, /Uncaught TypeError: Invalid value used as weak map key/);

expectAssertion(function() {
map.set(null, 1);
}, /Uncaught TypeError: Invalid value used as weak map key/);

expectAssertion(function() {
map.set(undefined, 1);
}, /Uncaught TypeError: Invalid value used as weak map key/);
});

QUnit.test('that .has and .delete work as expected', function(assert) {
var map = new WeakMap();
var a = {};
var b = {};
var foo = { id: 1, name: 'My file', progress: 0 };

deepEqual(map.set(a, foo), map);
deepEqual(map.get(a), foo);
ok(map.has(a));
ok(!map.has(b));

deepEqual(map.delete(a), map);
ok(!map.has(a));

map.set(a, undefined);
ok(map.has(a));
});