Skip to content

Commit

Permalink
Merge pull request #12990 from mmun/weak-map
Browse files Browse the repository at this point in the history
[BUGFIX beta] Add private WeakMap
  • Loading branch information
krisselden committed Feb 20, 2016
2 parents 3b8e875 + bce8ed3 commit b00c076
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/ember-metal/lib/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import EmptyObject from 'ember-metal/empty_object';
*/
let members = {
cache: ownMap,
weak: ownMap,
watching: inheritedMap,
mixins: inheritedMap,
bindings: inheritedMap,
Expand All @@ -46,6 +47,7 @@ const META_FIELD = '__ember_meta__';

function Meta(obj, parentMeta) {
this._cache = undefined;
this._weak = undefined;
this._watching = undefined;
this._mixins = undefined;
this._bindings = undefined;
Expand Down
101 changes: 101 additions & 0 deletions packages/ember-metal/lib/weak_map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { assert } from 'ember-metal/debug';
import { GUID_KEY } from 'ember-metal/utils';
import {
peekMeta,
meta as metaFor
} from 'ember-metal/meta';

let id = 0;
function UNDEFINED() {}

/*
* @private
* @class Ember.WeakMap
*
* A partial polyfill for [WeakMap](http://www.ecma-international.org/ecma-262/6.0/#sec-weakmap-objects).
*
* There is a small but important caveat. This implementation assumes that the
* weak map will live longer (in the sense of garbage collection) than all of its
* keys, otherwise it is possible to leak the values stored in the weak map. In
* practice, most use cases satisfy this limitation which is why it is included
* in ember-metal.
*/
export default function WeakMap() {
assert(
'Invoking the WeakMap constructor with arguments is not supported at this time',
arguments.length === 0
);

this._id = GUID_KEY + (id++);
}

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

return map[this._id];
}
}
};

/*
* @method set
* @param key {Object | Function}
* @param value {Any}
* @return {WeakMap} the weak map
*/
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;
}

metaFor(obj).writableWeak()[this._id] = value;

return this;
};

/*
* @method has
* @param key {Object | Function}
* @return {boolean} if the key exists
*/
WeakMap.prototype.has = function(obj) {
let meta = peekMeta(obj);
if (meta) {
let map = meta.readableWeak();
if (map) {
return map[this._id] !== undefined;
}
}

return false;
};

/*
* @method delete
* @param key {Object | Function}
* @return {boolean} if the key was deleted
*/
WeakMap.prototype.delete = function(obj) {
if (this.has(obj)) {
delete metaFor(obj).writableWeak()[this._id];
return true;
} else {
return false;
}
};
98 changes: 98 additions & 0 deletions packages/ember-metal/tests/weak_map_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import WeakMap from 'ember-metal/weak_map';

QUnit.module('Ember.WeakMap');

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

let a = {};
let b = {};
let c = {};

assert.strictEqual(map.get(a), undefined);
assert.strictEqual(map.get(b), undefined);
assert.strictEqual(map.get(c), undefined);

assert.strictEqual(map2.get(a), undefined);
assert.strictEqual(map2.get(b), undefined);
assert.strictEqual(map2.get(c), undefined);

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

assert.strictEqual(map2.get(a), undefined);
assert.strictEqual(map2.get(b), undefined);
assert.strictEqual(map2.get(c), undefined);

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

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

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

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

QUnit.test('invoking the WeakMap constructor with arguments is not supported at this time', function(assert) {
expectAssertion(function() {
new WeakMap([[{}, 1]]);
}, /Invoking the WeakMap constructor with arguments is not supported at this time/);
});

QUnit.test('that error is thrown when using a primitive key', function(assert) {
let 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) {
let map = new WeakMap();
let a = {};
let b = {};
let foo = { id: 1, name: 'My file', progress: 0 };

assert.strictEqual(map.set(a, foo), map);
assert.strictEqual(map.get(a), foo);
assert.strictEqual(map.has(a), true);
assert.strictEqual(map.has(b), false);
assert.strictEqual(map.delete(a), true);
assert.strictEqual(map.has(a), false);
assert.strictEqual(map.delete(a), false);
assert.strictEqual(map.set(a, undefined), map);
assert.strictEqual(map.has(a), true);
assert.strictEqual(map.delete(a), true);
assert.strictEqual(map.delete(a), false);
assert.strictEqual(map.has(a), false);
});

0 comments on commit b00c076

Please sign in to comment.