/* Tested against Chromium build with Object.observe and acts EXACTLY the same, though Chromium build is MUCH faster Trying to stay as close to the spec as possible, this is a work in progress, feel free to comment/update Specification: http://wiki.ecmascript.org/doku.php?id=harmony:observe Built using parts of: https://github.com/tvcutsem/harmony-reflect/blob/master/examples/observer.js Limits so far; Built using polling... Will update again with polling/getter&setters to make things better at some point TODO: Add support for Object.prototype.watch -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch */ if(!Object.observe){ (function(extend, global){ "use strict"; var isCallable = (function(toString){ var s = toString.call(toString), u = typeof u; return typeof global.alert === "object" ? function isCallable(f){ return s === toString.call(f) || (!!f && typeof f.toString == u && typeof f.valueOf == u && /^\s*\bfunction\b/.test("" + f)); }: function isCallable(f){ return s === toString.call(f); } ; })(extend.prototype.toString); // isNode & isElement from http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object //Returns true if it is a DOM node var isNode = function isNode(o){ return ( typeof Node === "object" ? o instanceof Node : o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName==="string" ); } //Returns true if it is a DOM element var isElement = function isElement(o){ return ( typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string" ); } var _isImmediateSupported = (function(){ return !!global.setImmediate; })(); var _doCheckCallback = (function(){ if(_isImmediateSupported){ return function _doCheckCallback(f){ return setImmediate(f); }; }else{ return function _doCheckCallback(f){ return setTimeout(f, 10); }; } })(); var _clearCheckCallback = (function(){ if(_isImmediateSupported){ return function _clearCheckCallback(id){ clearImmediate(id); }; }else{ return function _clearCheckCallback(id){ clearTimeout(id); }; } })(); var isNumeric=function isNumeric(n){ return !isNaN(parseFloat(n)) && isFinite(n); }; var sameValue = function sameValue(x, y){ if(x===y){ return x !== 0 || 1 / x === 1 / y; } return x !== x && y !== y; }; var isAccessorDescriptor = function isAccessorDescriptor(desc){ if (typeof(desc) === 'undefined'){ return false; } return ('get' in desc || 'set' in desc); }; var isDataDescriptor = function isDataDescriptor(desc){ if (typeof(desc) === 'undefined'){ return false; } return ('value' in desc || 'writable' in desc); }; var validateArguments = function validateArguments(O, callback, accept){ if(typeof(O)!=='object'){ // Throw Error throw new TypeError("Object.observeObject called on non-object"); } if(isCallable(callback)===false){ // Throw Error throw new TypeError("Object.observeObject: Expecting function"); } if(Object.isFrozen(callback)===true){ // Throw Error throw new TypeError("Object.observeObject: Expecting unfrozen function"); } if (accept !== undefined) { if (!Array.isArray(accept)) { throw new TypeError("Object.observeObject: Expecting acceptList in the form of an array"); } } }; var Observer = (function Observer(){ var wraped = []; var Observer = function Observer(O, callback, accept){ validateArguments(O, callback, accept); if (!accept) { accept = ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"]; } Object.getNotifier(O).addListener(callback, accept); if(wraped.indexOf(O)===-1){ wraped.push(O); }else{ Object.getNotifier(O)._checkPropertyListing(); } }; Observer.prototype.deliverChangeRecords = function Observer_deliverChangeRecords(O){ Object.getNotifier(O).deliverChangeRecords(); }; wraped.lastScanned = 0; var f = (function f(wrapped){ return function _f(){ var i = 0, l = wrapped.length, startTime = new Date(), takingTooLong=false; for(i=wrapped.lastScanned; (i -1){ Object.getNotifier(wrapped[i])._checkPropertyListing(); takingTooLong=((new Date())-startTime)>100; // make sure we don't take more than 100 milliseconds to scan all objects }else{ wrapped.splice(i, 1); i--; l--; } } wrapped.lastScanned=i-1){ _listeners.splice(idx, 1); _acceptLists.splice(idx, 1); } }; self.listeners = function Notifier_listeners(){ return _listeners; }; self.queueUpdate = function Notifier_queueUpdate(what, prop, type, was){ this.queueUpdates([{ type: type, object: what, name: prop, oldValue: was }]); }; self.queueUpdates = function Notifier_queueUpdates(updates){ var self = this, i = 0, l = updates.length||0, update; for(i=0; i-1?_notifiers[idx]:false; if(!notifier){ idx = _indexes.length; _indexes[idx] = O; notifier = _notifiers[idx] = new Notifier(O); } return notifier; }; extend.observe = function Object_observe(O, callback, accept){ // For Bug 4, can't observe DOM elements tested against canry implementation and matches if(!isElement(O)){ return new Observer(O, callback, accept); } }; extend.unobserve = function Object_unobserve(O, callback){ validateArguments(O, callback); var idx = _indexes.indexOf(O), notifier = idx>-1?_notifiers[idx]:false; if (!notifier){ return; } notifier.removeListener(callback); if (notifier.listeners().length === 0){ _indexes.splice(idx, 1); _notifiers.splice(idx, 1); } }; })(Object, this); }