Skip to content
Merged
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
122 changes: 100 additions & 22 deletions packages/ember-metal/lib/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
import { EmptyObject, lookupDescriptor, symbol } from 'ember-utils';
import isEnabled from './features';
import { protoMethods as listenerMethods } from './meta_listeners';
import { runInDebug } from './debug';

let counters = {
peekCalls: 0,
peekParentCalls: 0,
peekPrototypeWalks: 0,
setCalls: 0,
deleteCalls: 0,
metaCalls: 0,
metaInstantiated: 0
};

/**
@module ember-metal
Expand Down Expand Up @@ -53,7 +64,9 @@ if (isEnabled('ember-glimmer-detect-backtracking-rerender') ||
let memberNames = Object.keys(members);
const META_FIELD = '__ember_meta__';

function Meta(obj, parentMeta) {
export function Meta(obj, parentMeta) {
runInDebug(() => counters.metaInstantiated++);

this._cache = undefined;
this._weak = undefined;
this._watching = undefined;
Expand Down Expand Up @@ -352,20 +365,87 @@ if (isEnabled('mandatory-setter')) {
};
}

const HAS_NATIVE_WEAKMAP = (function() {
// detect if `WeakMap` is even present
let hasWeakMap = typeof WeakMap === 'function';
if (!hasWeakMap) { return false; }

let instance = new WeakMap();
// use `Object`'s `.toString` directly to prevent us from detecting
// polyfills as native weakmaps
return Object.prototype.toString.call(instance) === '[object WeakMap]';
Copy link
Member

@stefanpenner stefanpenner Sep 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe core-js's would actually match this. As I believe it patches toString to doit...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stefanpenner - It patches the Object.prototype.toString? That seems somewhat aggressive...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like lodash has been through this one already:

Quoting (just before _.isNative throws when it detects core-js):

 * **Note:** This method can't reliably detect native functions in the presence
 * of the core-js package because core-js circumvents this kind of detection.
 * Despite multiple requests, the core-js maintainer has made it clear: any
 * attempt to fix the detection will be obstructed. As a result, we're left
 * with little choice but to throw an error. Unfortunately, this also affects
 * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
 * which rely on core-js.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c, that is most likely a good way to detect.

})();

let setMeta, peekMeta, deleteMeta;

// choose the one appropriate for given platform
let setMeta = function(obj, meta) {
// if `null` already, just set it to the new value
// otherwise define property first
if (obj[META_FIELD] !== null) {
if (obj.__defineNonEnumerable) {
obj.__defineNonEnumerable(EMBER_META_PROPERTY);
} else {
Object.defineProperty(obj, META_FIELD, META_DESC);
if (HAS_NATIVE_WEAKMAP) {
let getPrototypeOf = Object.getPrototypeOf;
let metaStore = new WeakMap();

setMeta = function WeakMap_setMeta(obj, meta) {
runInDebug(() => counters.setCalls++);
metaStore.set(obj, meta);
};

peekMeta = function WeakMap_peekMeta(obj) {
runInDebug(() => counters.peekCalls++);

return metaStore.get(obj);
};

peekMeta = function WeakMap_peekParentMeta(obj) {
let pointer = obj;
let meta;
while (pointer) {
meta = metaStore.get(pointer);
// jshint loopfunc:true
runInDebug(() => counters.peekCalls++);
// stop if we find a `null` value, since
// that means the meta was deleted
// any other truthy value is a "real" meta
if (meta === null || meta) {
return meta;
}

pointer = getPrototypeOf(pointer);
runInDebug(() => counters.peakPrototypeWalks++);
}
}
};

obj[META_FIELD] = meta;
};
deleteMeta = function WeakMap_deleteMeta(obj) {
runInDebug(() => counters.deleteCalls++);

// set value to `null` so that we can detect
// a deleted meta in peekMeta later
metaStore.set(obj, null);
};
} else {
setMeta = function Fallback_setMeta(obj, meta) {
// if `null` already, just set it to the new value
// otherwise define property first
if (obj[META_FIELD] !== null) {
if (obj.__defineNonEnumerable) {
obj.__defineNonEnumerable(EMBER_META_PROPERTY);
} else {
Object.defineProperty(obj, META_FIELD, META_DESC);
}
}

obj[META_FIELD] = meta;
};

peekMeta = function Fallback_peekMeta(obj) {
return obj[META_FIELD];
};

deleteMeta = function Fallback_deleteMeta(obj) {
if (typeof obj[META_FIELD] !== 'object') {
return;
}
obj[META_FIELD] = null;
};
}

/**
Retrieves the meta hash for an object. If `writable` is true ensures the
Expand All @@ -386,6 +466,8 @@ let setMeta = function(obj, meta) {
@return {Object} the meta hash for an object
*/
export function meta(obj) {
runInDebug(() => counters.metaCalls++);

let maybeMeta = peekMeta(obj);
let parent;

Expand All @@ -402,13 +484,9 @@ export function meta(obj) {
return newMeta;
}

export function peekMeta(obj) {
return obj[META_FIELD];
}

export function deleteMeta(obj) {
if (typeof obj[META_FIELD] !== 'object') {
return;
}
obj[META_FIELD] = null;
}
export {
peekMeta,
setMeta,
deleteMeta,
counters
};