/// /// /// /// /* © Microsoft. All rights reserved. This library is supported for use in Windows Tailored Apps only. Build: 6.2.8093.0 Version: 0.5 */ (function (global, undefined) { /* See comment for data-win-options attribute grammar for context. Syntactic grammar for the value of the data-win-bind attribute. BindDeclarations: BindDeclaration BindDeclarations ; BindDeclaration BindDeclaration: DestinationPropertyName : SourcePropertyName DestinationPropertyName : SourcePropertyName ActionName DestinationPropertyName: IdentifierExpression SourcePropertyName: IdentifierExpression ActionName: IdentifierExpression Value: NumberLiteral StringLiteral AccessExpression: [ Value ] . Identifier AccessExpressions: AccessExpression AccessExpressions AccessExpression IdentifierExpression: Identifier Identifier AccessExpressions */ var lexer = WinJS.UI._optionsLexer; var tokenType = lexer.tokenType; var BindingInterpreter = WinJS.Class.derive(WinJS.UI._optionsParser._BaseInterpreter, function (tokens, context) { this._initialize(tokens, context); }, { _error: function (message) { var error = new Error("Invalid binding, expected to be :;. " + message); error.name = "WinJS.Binding.ParseError"; throw error; }, _evaluateActionName: function () { if (this._current.type === tokenType.identifier) { return this._evaluateIdentifierExpression(); } return; }, _evaluateValue: function () { switch (this._current.type) { case tokenType.stringLiteral: case tokenType.numberLiteral: var value = this._current.value; this._read(); return value; default: this._unexpectedToken(tokenType.stringLiteral, tokenType.numberLiteral); return; } }, _readBindDeclarations: function () { var bindings = []; while (true) { switch (this._current.type) { case tokenType.identifier: bindings.push(this._readBindDeclaration()); break; case tokenType.semicolon: this._read(); break; case tokenType.eof: return bindings; default: this._unexpectedToken(tokenType.identifier, tokenType.semicolon, tokenType.eof); return; } } }, _readBindDeclaration: function () { var dest = this._readDestinationPropertyName(); this._read(tokenType.colon); var src = this._readSourcePropertyName(); var action = this._evaluateActionName(); return { destination: dest, source: src, action: action }; }, _readDestinationPropertyName: function () { return this._readIdentifierExpression(); }, _readSourcePropertyName: function () { return this._readIdentifierExpression(); }, run: function () { return this._readBindDeclarations(); } } ); function parser(text, context) { msWriteProfilerMark("BindingParser:parse:S"); var tokens = lexer(text); var interpreter = new BindingInterpreter(tokens, context || {}); var res = interpreter.run(); msWriteProfilerMark("BindingParser:parse:E"); return res; } WinJS.Namespace.define("WinJS.Binding", { _bindingParser: parser }); })(this); (function (WinJS, undefined) { var mixin = { _listeners: null, _updatedValues: null, _backingData: null, _initObservable: function(data) { this._backingData = data || {}; this._listeners = {}; }, _getObservable: function () { return this; }, _cancel: function (name) { var pending = this._updatedValues && this._updatedValues[name]; if (pending) { // If the work started, we tag the job as finished, which will avoid // adding more work to the queue, and then cancel the promise if // present // pending.finished = true; if (pending.promise) { pending.promise.cancel(); // cleanup if the promise didn't correctly cleanup // if (this._updatedValues[name]) { this._updatedValues[name].promise = null; delete this._updatedValues[name]; } }; } }, _notifyListeners: function (name, newValue, oldValue) { var listeners = this._listeners[name]; if (listeners) { var that = this; that._updatedValues = that._updatedValues || {}; // Handle the case where we are updating a value that is currently updating // var pending = that._updatedValues[name]; if (pending) { // If the work hasn't started, we just update the new target value // if (!pending.started) { pending.newValue = newValue; return pending.promise; } else { that._cancel(name); } } // Starting new work, we cache the work description and queue up to do the notifications // var cap = that._updatedValues[name] = { newValue: newValue, oldValue: oldValue, started: false }; // Binding guarantees async notification, so we do timeout() // cap.promise = WinJS.Promise.timeout(). then(function() { cap.started = true; var join; listeners.forEach(function (l) { if (!cap.finished) { var value = l(cap.newValue, cap.oldValue); // We sniff for promise return values to avoid creating a complex // join later if not needed // if (typeof value === "object" && typeof value.then === "function") { join = join || []; join.push(value); } } }); function cleanup() { cap.finished = true; if (cap === that._updatedValues[name]) { delete that._updatedValues[name]; } } if (join) { return WinJS.Promise.join(join).then(cleanup, cleanup).then(function() { return cap.newValue; }); } else { cleanup(); } }); return cap.promise; } return WinJS.Promise.as(); }, // UNDONE: maybe "listen" for the name? bind: function (name, action) { /// /// action will be invoked when the value of the property specified by name may have changed. /// It is not guaranteed that action will be called only when a value has actually changed, /// nor is it guaranteed that action will be called for every value change. The implementation /// of bind will coalesce change notificaiton such that multiple updates to a property /// value may result in only a single call to action. /// /// /// Name of property to listen to change notification for. /// /// /// Function to invoke asynchronously when the property specified by name may have changed. /// /// /// this object is returned (enabling fluent calling style) /// var listeners = this._listeners[name] = this._listeners[name] || []; var found = false; for (var i = 0, l = listeners.length; i < l; i++) { if (listeners[i] === action) { found = true; break; } } if (!found) { listeners.push(action); // UNDONE: this will notify all listeners when any listener is added, that's bad, // however we need to use _notifyListeners to make sure that the notification is // async and cancelable (i.e. fast calls to bind/unbind should cancel even the // initial notification // this._notifyListeners(name, unwrap(this.getProperty(name))); } return this; }, unbind: function (name, action) { /// /// Removes one or more listeners from the notification list for a given property. /// /// /// Name of property to stop listening for change notifications. If name is omitted, all listeners /// for all events are removed. /// /// /// Function to remove from the listener list for the specified property, if omitted all listeners /// are removed for the specific property. /// /// /// this object is returned (enabling fluent calling style) /// if (name && action) { // this assumes we rarely have more than one // listener, so we optimize to not do a lot of // array manipulation, although it means we // may do some extra GC churn in the other cases... // var listeners = this._listeners[name]; var removed = false; if (listeners) { var nl; for (var i=0,l=listeners.length; i /// Retrieves a property value by name, /// /// /// Name of property to retrieve /// /// /// value of the property as an observable object. /// return as(this._backingData[name]); }, setProperty: function (name, value) { /// /// Updates a property value and notifies any listeners. /// /// /// Name of property to update /// /// /// Value to update the property to. /// /// /// this object is returned (enabling fluent calling style) /// this.updateProperty(name, value); return this; }, addProperty: function (name, value) { /// /// Adds a property with change notificaiton to this object, including a ES5 property definition /// /// /// Name of property to add /// /// /// Value to update the property to. /// /// /// this object is returned (enabling fluent calling style) /// // we could walk Object.keys to more deterministically determine this, // however in the normal case this avoids a bunch of string compares // if (!this[name]) { Object.defineProperty(this, name, { get: function () { return this.getProperty(name); }, set: function (value) { this.setProperty(name, value); }, enumerable: true, configurable: true } ); } return this.setProperty(name, value); }, updateProperty: function (name, value) { /// /// Updates a property value and notifies any listeners. /// /// /// Name of property to update /// /// /// Value to update the property to. /// /// /// Promise is returned which will complete when the notifications for /// this property change have been processed. In the case of coalescing /// the promise may be canceled, or the value of the promise may be updated. /// The value of the promise will be the new value of the property for /// which the notificaitons have been completed. /// var oldValue = this._backingData[name]; var newValue = unwrap(value); if (oldValue !== newValue) { this._backingData[name] = newValue; // UNDONE: what is the expecation of this promise? Right now // this will complete when the listeners are notified, even // if a new value is used. The only time this promise will fail // (cancel) will be if we start notifying and then have to // cancel in the middle of processing it. That's a pretty // subtle contract. // return this._notifyListeners(name, newValue, oldValue); } return WinJS.Promise.as(); }, removeProperty: function (name) { /// /// Removes a property value /// /// /// Name of property to remove /// /// /// this object is returned (enabling fluent calling style) /// var oldValue = this._backingData[name]; var value; // capture "undefined" delete this._backingData[name]; delete this[name]; this._notifyListeners(name, value, oldValue); return this; } }; var bind = function (observable, bindingDescriptor) { /// /// Binds to one or more properties on the observable object or child values /// of that object. /// /// /// Object to bind to /// /// /// Object literal containing the binding declarations. This is of the form: /// { propertyName: (function | bindingDeclaration), ... } /// /// So, for example, you can bind to a nested member of an object: /// bind(someObject, { address: { street: function(v) { ... } } }); /// /// /// Object is returned which contains at least a field "cancel" which is /// a function that will remove all bindings associated with this bind /// request. /// observable = WinJS.Binding.as(observable); // UNDONE: this makes each binding fairly expensive... we have // + 2 closures (cancelSimple & cancelComplex) // + 1 closure per nested binding (propChanged) // + 2 object slots (complexLast, simpleLast) // + 1 object (complexLast) // // We should profile and determine if creating a constructor+prototype // could lighten this up // var complexLast = {}; var simpleLast = null; function cancelSimple() { if (simpleLast) { simpleLast.forEach(function (e) { e.source.unbind(e.prop, e.listener); }); } simpleLast = null; }; function cancelComplex(k) { if (complexLast[k]) { complexLast[k].complexBind.cancel(); delete complexLast[k]; } }; Object.keys(bindingDescriptor).forEach(function (k) { var listener = bindingDescriptor[k]; if (listener instanceof Function) { simpleLast = simpleLast || []; simpleLast.push({source:observable,prop:k,listener:listener}); observable.bind(k, listener); } else { var propChanged = function (v) { cancelComplex(k); complexBind = bind(as(v), listener); complexLast[k] = {source:v,complexBind:complexBind}; }; observable.bind(k, propChanged); } }); return { cancel: function () { cancelSimple(); Object.keys(complexLast).forEach(function (k) { cancelComplex(k); }); } } }; var ObservableProxy = WinJS.Class.mix(function (data) { this._initObservable(data); Object.defineProperties(this, expandProperties(data)); }, mixin); var expandProperties = function(shape) { /// /// Given an object produce an object which has properties which /// are instrumented for binding. This is meant to be used in /// conjunction with the binding mixin. /// /// /// Specification for the bindable object. /// /// /// Object with a set of properties all of which are wired for binding /// var props = {}; while (shape && shape !== Object.prototype) { Object.keys(shape).forEach(function (k) { props[k] = { get: function () { return this.getProperty(k); }, set: function (value) { this.setProperty(k, value); }, enumerable: true, configurable: true // enables delete }; }); shape = Object.getPrototypeOf(shape); } return props; }; var define = function (data) { /// /// Creates a new constructor function which supports observability and /// the set of properties defined in data. /// /// /// Object to use as the pattern for defining the set of properties, for example: /// var MyPointClass = define({x:0,y:0}); /// /// /// Constructor function with 1 optional argument that is the initial state of /// the properties. /// // Common unsupported types, we just coerce to be an empty record // if (!data || typeof(data) !== "object" || (data instanceof Date) || (data instanceof Array)) { if (WinJS.validation) { throw "Unsupported data type"; } else { return; } } return WinJS.Class.mix( function(init) { /// /// Creates a new observable object. /// /// /// Initial values for properties /// // UNDONE: how should we get the defaults from "data"... by copy? // this._initObservable(init || Object.create(data)); }, WinJS.Binding.mixin, WinJS.Binding.expandProperties(data) ); }; var as = function (data) { /// /// Either creates an observable proxy for data, returns an existing proxy, or /// returns data in the case that data directly supports observability. /// /// /// Object to provide observability for. /// /// /// Observable object /// if (!data) { return data; } var type = typeof data; if (type === "object" && !(data instanceof Date) && !(data instanceof Array)) { if (data._getObservable) { return data._getObservable(); } var observable = new ObservableProxy(data); observable.backingData = data; Object.defineProperty( data, "_getObservable", { value: function() { return observable; }, enumerable: false, writable: false } ); return observable; } else { return data; } }; var unwrap = function (data) { /// /// If data is an observable proxy, the original object is returned, otherwise data is returned. /// /// /// Object to retrieve the original value for. /// /// /// If data is an observable proxy, the original object is returned, otherwise data is returned. /// if (data && data.backingData) return data.backingData; else return data; }; WinJS.Namespace.defineWithParent(WinJS, "Binding", { // must use long form because mixin has "get" and "set" as members, so the define // method thinks it's a property mixin: {value: mixin, enumerable:true, writable:true, configurable:true}, expandProperties: expandProperties, define: define, as: as, unwrap: unwrap, bind: bind }); })(WinJS); (function (WinJS, undefined) { WinJS.Namespace.defineWithParent(WinJS, "Binding", { Template: WinJS.Class.define( function (element, options) { /// /// Creates a Template which allows for a reusable declarative binding element. /// /// /// DOM Element to convert to a template. /// /// /// If the href is supplied, the template is loaded from that URI using the FragmentLoader, and /// the content of element is ignored. /// msWriteProfilerMark("Template:new:S"); this.element = element; var that = this; if (element) { element.renderItem = function (item, recycled) { // we only enable element cache when we are trying // to recycle. Otherwise our element cache would // grow unbounded. // if (that.enableRecycling && !that.bindingCache.elements) { that.bindingCache.elements = {}; } if (that.enableRecycling && recycled && recycled.msOriginalTemplate === that) { // If we are asked to recycle, we cleanup any old work no matter what // if (recycled.msRendererPromise) { recycled.msRendererPromise.cancel(); } var cacheEntry = that.bindingCache.elements[recycled.id]; var okToReuse = true; if (cacheEntry) { cacheEntry.bindings.forEach(function (v) { v(); }); cacheEntry.bindings = []; okToReuse = !cacheEntry.nocache; } // If our cache indicates that we hit a non-cancelable thing, then we are // in an unknown state, so we actually can't recycle the tree. We have // cleaned up what we can, but at this point we need to reset and create // a new tree. // if (okToReuse) { // Element recycling requires that there be no other content in "recycled" other than this // templates' output. // recycled.msRendererPromise = WinJS.Binding.processAll(recycled, item.data, true, that.bindingCache); return recycled; } } var d = document.createElement("div"); d.msOriginalTemplate = that; d.msRendererPromise = that.render(item.data, d); return d; }; } if (options) { this.href = options.href; this.enableRecycling = !!options.enableRecycling; if (options.processTimeout) { this.processTimeout = options.processTimeout; } } if (!this.href) { this.element.style.display = "none"; } this.bindingCache = { expressions: {} }; msWriteProfilerMark("Template:new:E"); }, { processTimeout: 0, render: function (dataContext, container) { /// /// Binds values from the dataContext to elements which are descendents of rootElement /// which have the declarative binding attributes specified (data-win-bind and data-win-bindsource). /// /// /// Object to use for default data binding. Each element (or sub tree) may contain /// a data-win-bindsource attribute which will override the dataContext. /// /// /// Element to add this rendered template to. If omited a new DIV is created. /// /// /// Promise which will be completed after binding has finished, the value will be /// either container or the created DIV. /// msWriteProfilerMark("Template:render:S"); var d = container; var tempParent; if (!d) { d = document.createElement("div"); tempParent = document.createElement("div"); tempParent.style.display = "none"; document.body.appendChild(tempParent); tempParent.appendChild(d); } WinJS.Utilities.addClass(d, "win-template"); WinJS.Utilities.addClass(d, "win-loading"); var that = this; function done() { if (tempParent) { tempParent.removeChild(d); document.body.removeChild(tempParent); } WinJS.Utilities.removeClass(d, "win-loading"); msWriteProfilerMark("Template:render:E"); return d; }; var initial = d.children.length; return WinJS.UI.Fragments.cloneTo(that.href || that.element, undefined, d). then(function () { var work; // If no existing children, we can do the faster path of just calling // on the root element... // if (initial === 0) { work = function (f,a,b,c) { return f(d,a,b,c); }; } // We only grab the newly added nodes (always at the end) // and in the common case of only adding a single new element // we avoid the "join" overhead // else { var all = d.children; if (all.length === initial + 1) { work = function (f,a,b,c) { return f(all[initial],a,b,c); }; } else { // we have to capture the elements first, in case // doing the work affects the children order/content // var elements = []; for (var i=initial, l=all.length; i /// Renders a template based on a URI. /// /// /// URI to load the template from. /// /// /// Object to use for default data binding. Each element (or sub tree) may contain /// a data-win-bindsource attribute which will override the dataContext. /// /// /// Element to add this rendered template to. If omited a new DIV is created. /// /// /// Promise which will be completed after binding has finished, the value will be /// either container or the created DIV. /// return new WinJS.Binding.Template(null, { href:href }).render(dataContext, container); }} }); })(WinJS); (function (WinJS, undefined) { var uid = 0; function buildContext(element, baseElement, skipRoot) { var context; while (element && !context) { if (element.getAttribute) { context = element.getAttribute("data-win-bindsource"); } if (element === baseElement || (skipRoot && element.parentNode === baseElement)) { break; } element = element.parentNode; } return context; }; function inContainer(baseElement, control, start) { if (control && control.constructor.isDeclarativeControlContainer) { return true; } if (start !== baseElement && start.parentNode && start.parentNode !== baseElement) { start = start.parentNode; return inContainer(baseElement, WinJS.UI.getControl(start), start); } return false; }; function actionOneBinding(bind, ref, source, e, pend, cacheEntry) { var action = bind.action; if (action) { action = action.winControl || action["data-win-control"] || action; } if (action instanceof Function) { var result = action(source, bind.source, e, bind.destination); if (cacheEntry) { if (result && result.cancel) { cacheEntry.bindings.push(function() { result.cancel(); }); } else { // notify the cache that we encountered an uncancellable thing // cacheEntry.nocache = true; } } } else if (action && action.render) { pend.count++; // notify the cache that we encountered an uncancellable thing // if (cacheEntry) { cacheEntry.nocache = true; } // UNDONE: should nested templates bind to the source property? // action.render(getValue(source, bind.source), e). then(function() { return WinJS.UI.processAll(e); }).then(function() { pend.checkComplete(); }); } }; function sourceOneBinding(bind, ref, source, e, pend, cacheEntry) { var bindable; if (source._getObservable) { bindable = source._getObservable(); } if (bindable) { var first = true; pend.count++; var bindResult; // declarative binding must use a weak ref to the target element // function bindingAction(v) { var found = WinJS.Utilities._getWeakRefElement(ref); if (found) { nestedSet(found, bind.destination, v); } else if (bindResult) { bindResult.cancel(); } if (first) { pend.checkComplete(); first = false; } } bindResult = bindWorker(bindable, bind.source, bindingAction); if (cacheEntry) { cacheEntry.bindings.push(function() { bindResult.cancel(); }); } } else { nestedSet(e, bind.destination, getValue(source,bind.source)); } }; function calcBinding(bindingStr, bindingCache) { if (bindingCache) { var declBindCache = bindingCache.expressions[bindingStr]; var declBind; if (!declBindCache) { declBind = WinJS.Binding._bindingParser(bindingStr); bindingCache.expressions[bindingStr] = declBind; } if (!declBind) { declBind = declBindCache; } return declBind; } else { return WinJS.Binding._bindingParser(bindingStr); } } function declarativeBindImpl(rootElement, dataContext, skipRoot, bindingCache, c, e, p) { msWriteProfilerMark("Binding:processAll:S"); var pend = { count: 0, checkComplete: function checkComplete() { this.count--; if (this.count == 0) { msWriteProfilerMark("Binding:processAll:E"); c(); } } }; var baseElement = (rootElement || document.body); var attr = "data-win-bind" var elements = baseElement.querySelectorAll("[" + attr + "]"); var neg; if (!skipRoot && baseElement.getAttribute(attr)) { neg = baseElement; } pend.count++; for (var i=(neg?-1:0),l=elements.length; i /// Binds values from the dataContext to elements which are descendents of rootElement /// which have the declarative binding attributes specified (data-win-bind and data-win-bindsource). /// /// /// Object to use for default data binding. Each element (or sub tree) may contain /// a data-win-bindsource attribute which will override the dataContext. /// /// /// Element to start traversing for elements to bind to. If omited, the entire document /// is searched. /// /// /// Determines if rootElement should be bound, or only it's children /// /// /// Cached binding data /// /// /// Promise which will complete when each item that contains binding declarations has /// been processed and the update has started. /// return new WinJS.Promise(function(c,e,p) { declarativeBindImpl(rootElement, dataContext, skipRoot, bindingCache, c, e, p); }); } function converter(convert) { /// /// Creates a default binding action for a binding between a source /// property and a destination property with a provided converter function /// which is executed on the value of the source property. /// /// /// Conversion which operates over the result of the source property /// to produce a value which is set to the destination property /// /// /// Binding action /// return function(source, sourceProperties, dest, destProperties) { return bindWorker(source, sourceProperties, function(v) { nestedSet(dest, destProperties, convert(v)); }); }; } function getValue(obj, path) { if (path) { for (var i = 0, len = path.length; i < len; i++) { obj = obj[path[i]]; } } return obj; } function nestedSet(dest, destProperties, v) { for (var i = 0, len = (destProperties.length - 1); i < len; i++) { dest = dest[destProperties[i]]; } dest[destProperties[destProperties.length - 1]] = v; } function defaultBind(source, sourceProperties, dest, destProperties) { /// /// Creates a one-way binding between the source property and /// the destination property /// /// /// Source object /// /// /// Path on source object to source property /// /// /// Destination object /// /// /// Path on destination object to destination property /// /// /// Object with a cancel method which is used by binding for coalescing bindings /// return bindWorker(source, sourceProperties, function(v) { nestedSet(dest, destProperties, v); }); } function bindWorker(bindable, sourceProperties, func) { if (sourceProperties.length > 1) { // @TODO, Better names maybe? // var s = sourceProperties; var r = {}; var c = r; for (var i=0,l=s.length-1; i /// Sets the destination property to the value of the source property /// /// /// Source object /// /// /// Path on source object to source property /// /// /// Destination object /// /// /// Path on destination object to destination property /// /// /// Object with a cancel method which is used by binding for coalescing bindings /// for (var i = 0, len = (destProperties.length - 1); i < len; i++) { dest = dest[destProperties[i]]; } dest[destProperties[destProperties.length - 1]] = getValue(source, sourceProperties); return { cancel: noop }; } WinJS.Namespace.defineWithParent(WinJS, "Binding", { processAll: declarativeBind, oneTime: oneTime, defaultBind: defaultBind, converter: converter }); })(WinJS); (function (global, undefined) { var U = WinJS.Utilities; // Defaults var SWEEP_PERIOD = 500; var TIMEOUT = 1000; var table = {}; var cleanupToken; function cleanup() { if (U._DOMWeakRefTable_sweepPeriod === 0) { // If we're using post cleanupToken = 0; // indicate that cleanup has run } var keys = Object.keys(table); var time = Date.now() - U._DOMWeakRefTable_timeout; var i, len; for (i = 0, len = keys.length; i < len; i++) { var id = keys[i]; if (table[id].time < time) { delete table[id]; } } unscheduleCleanupIfNeeded(); }; function scheduleCleanupIfNeeded() { if ((global.Debug && U._DOMWeakRefTable_noTimeoutUnderDebugger) || cleanupToken) { return; } var period = U._DOMWeakRefTable_sweepPeriod; if (period === 0) { msQueueCallback(cleanup); cleanupToken = 1; } else { cleanupToken = setInterval(cleanup, U._DOMWeakRefTable_sweepPeriod); } }; function unscheduleCleanupIfNeeded() { if (global.Debug && U._DOMWeakRefTable_noTimeoutUnderDebugger) { return; } var period = U._DOMWeakRefTable_sweepPeriod; if (period === 0) { // if we're using post if (!cleanupToken) { // and there isn't already one scheduled if (Object.keys(table).length !== 0) { // and there are items in the table msQueueCallback(cleanup); // schedule another call to cleanup cleanupToken = 1; // and protect against overscheduling } } } else if (cleanupToken) { if (Object.keys(table).length === 0) { clearInterval(cleanupToken); cleanupToken = 0; } } }; function createWeakRef(element, id) { table[id] = { element: element, time: Date.now() }; scheduleCleanupIfNeeded(); return id; } function getWeakRefElement(id) { var element = document.getElementById(id); if (element) { delete table[id]; unscheduleCleanupIfNeeded(); } else { var entry = table[id]; if (entry) { entry.time = Date.now(); element = entry.element; } } return element; } WinJS.Namespace.defineWithParent(WinJS, "Utilities", { _DOMWeakRefTable_noTimeoutUnderDebugger: true, _DOMWeakRefTable_sweepPeriod: SWEEP_PERIOD, _DOMWeakRefTable_timeout: TIMEOUT, _DOMWeakRefTable_tableSize: { get: function () { return Object.keys(table).length; } }, _createWeakRef: createWeakRef, _getWeakRefElement: getWeakRefElement }); }(this));