Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use WeakMap for storing Glimmer metadata #119

Merged
merged 3 commits into from
Mar 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/broccoli/build-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function includeTests(jsTree) {

return new Rollup(testsRoot, {
rollup: {
format: 'es',
format: 'iife',
entry: ['tests.js'],
dest: 'assets/tests.js',
plugins: [
Expand Down
55 changes: 42 additions & 13 deletions packages/@glimmer/component/src/tracked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,20 +294,48 @@ export interface Interceptors {
[key: string]: boolean;
}

let META = Symbol("ember-object");
/**
* A shared WeakMap for tracking an object's Meta instance, so any metadata
* will be garbage collected automatically with the associated object.
*/
const META_MAP = new WeakMap();

/**
* Returns the Meta instance for an object. If no existing Meta is found,
* creates a new instance and returns it. An object's Meta inherits from any
* existing Meta in its prototype chain.
*/
export function metaFor(obj: any): Meta {
let meta = obj[META];
if (meta && hasOwnProperty(obj, META)) {
return meta;
}

return obj[META] = new Meta(meta);
// Return the Meta for this object if we already have it.
let meta = META_MAP.get(obj);
if (meta) { return meta; }

// Otherwise, we need to walk the object's prototype chain to until we find a
// parent Meta to inherit from. If we reach the end of the chain and have not
// found a Meta, there is nothing to inherit.
let protoMeta = findPrototypeMeta(obj);
meta = new Meta(protoMeta);

// Save the object's Meta and return it.
META_MAP.set(obj, meta);
return meta;
}

let hOP = Object.prototype.hasOwnProperty;
function hasOwnProperty(obj: any, key: symbol) {
return hOP.call(obj, key);
const getPrototypeOf = Object.getPrototypeOf;

// Finds the nearest Meta instance in an object's prototype chain. Returns null
// if the end of the prototype chain is reached without finding a Meta.
function findPrototypeMeta(obj): Meta | null {
let meta = null;
let proto = obj;

while (!meta) {
proto = getPrototypeOf(proto);
if (!proto) { return meta; }
meta = META_MAP.get(proto);
}

return meta;
}

const EPOCH = DirtyableTag.create();
Expand All @@ -319,10 +347,11 @@ export function setPropertyDidChange(cb: () => void) {
}

export function hasTag(obj: any, key: string): boolean {
let meta = obj[META] as Meta;
let meta = META_MAP.get(obj);

if (!obj[META]) { return false; }
if (!meta.trackedProperties[key]) { return false; }
if (!meta || !meta.trackedProperties[key]) {
return false;
}

return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,46 @@ test('can request a tag for non-objects and get a CONSTANT_TAG', (assert) => {
assert.ok(tagForProperty('hello world', 'foo').validate(snapshot));
});

test('can request a tag from a frozen objects', assert => {
test('can request a tag from a frozen POJO', assert => {
let obj = Object.freeze({
firstName: 'Toran'
});

assert.strictEqual(obj.firstName, 'Toran');

let tag = tagForProperty(obj, 'firstName');
let snapshot = tag.value();
assert.ok(tag.validate(snapshot), 'tag should be valid to start');
snapshot = tag.value();
assert.strictEqual(tag.validate(snapshot), true, 'tag is still valid');

unrelatedBump(tag, snapshot);
});

test('can request a tag from a frozen class instance', assert => {
class TrackedPerson {
@tracked firstName = 'Toran';
lastName = 'Billups';
}

let obj = Object.freeze(new TrackedPerson());
assert.strictEqual(obj.firstName, 'Toran');
assert.strictEqual(obj.lastName, 'Billups');

// Explicitly annotated tracked properties
let tag = tagForProperty(obj, 'firstName');
let snapshot = tag.value();
assert.ok(tag.validate(snapshot), 'tag should be valid to start');
snapshot = tag.value();
assert.strictEqual(tag.validate(snapshot), true, 'tag is still valid');

// Non-tracked data properties
tag = tagForProperty(obj, 'lastName');
snapshot = tag.value();
assert.ok(tag.validate(snapshot), 'tag should be valid to start');
snapshot = tag.value();
assert.strictEqual(tag.validate(snapshot), true, 'tag is still valid');

unrelatedBump(tag, snapshot);
});

Expand Down