fliphub-monorepo
Version:
the builder of builders
380 lines (363 loc) • 14 kB
JavaScript
module.exports = function() {
/*
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) {
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 < l) && (!takingTooLong); i++) {
if (_indexes.indexOf(wrapped[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 < l ? i : 0 // reset wrapped so we can make sure that we pick things back up
_doCheckCallback(_f)
}
})(wraped)
_doCheckCallback(f)
return Observer
})()
var Notifier = function Notifier(watching) {
var _listeners = [], _acceptLists = [], _updates = [], _updater = false, properties = [], values = []
var self = this
Object.defineProperty(self, '_watching', {
enumerable: true,
get: (function(watched) {
return function() {
return watched
}
})(watching),
})
var wrapProperty = function wrapProperty(object, prop) {
var propType = typeof(object[prop]), descriptor = Object.getOwnPropertyDescriptor(object, prop)
if ((prop === 'getNotifier') || isAccessorDescriptor(descriptor) || (!descriptor.enumerable)) {
return false
}
if ((object instanceof Array) && isNumeric(prop)) {
var idx = properties.length
properties[idx] = prop
values[idx] = object[prop]
return true
}
(function(idx, prop) {
properties[idx] = prop
values[idx] = object[prop]
function getter() {
return values[getter.info.idx]
}
function setter(value) {
if (!sameValue(values[setter.info.idx], value)) {
Object.getNotifier(object).queueUpdate(object, prop, 'update', values[setter.info.idx])
values[setter.info.idx] = value
}
}
getter.info = setter.info = {
idx,
}
Object.defineProperty(object, prop, {
get: getter,
set: setter,
})
})(properties.length, prop)
return true
}
self._checkPropertyListing = function _checkPropertyListing(dontQueueUpdates) {
var object = self._watching, keys = Object.keys(object), i = 0, l = keys.length
var newKeys = [], oldKeys = properties.slice(0), updates = []
var prop, queueUpdates = !dontQueueUpdates, propType, value, idx, aLength
if (object instanceof Array) {
aLength = self._oldLength// object.length;
// aLength = object.length;
}
for (i = 0; i < l; i++) {
prop = keys[i]
value = object[prop]
propType = typeof(value)
if ((idx = properties.indexOf(prop)) === -1) {
if (wrapProperty(object, prop) && queueUpdates) {
self.queueUpdate(object, prop, 'add', null, object[prop])
}
} else {
if (!(object instanceof Array) || (isNumeric(prop))) {
if (values[idx] !== value) {
if (queueUpdates) {
self.queueUpdate(object, prop, 'update', values[idx], value)
}
values[idx] = value
}
}
oldKeys.splice(oldKeys.indexOf(prop), 1)
}
}
if (object instanceof Array && object.length !== aLength) {
if (queueUpdates) {
self.queueUpdate(object, 'length', 'update', aLength, object)
}
self._oldLength = object.length
}
if (queueUpdates) {
l = oldKeys.length
for (i = 0; i < l; i++) {
idx = properties.indexOf(oldKeys[i])
self.queueUpdate(object, oldKeys[i], 'delete', values[idx])
properties.splice(idx,1)
values.splice(idx,1)
for (var i = idx; i < properties.length; i++) {
if (!(properties[i] in object))
continue
var getter = Object.getOwnPropertyDescriptor(object,properties[i]).get
if (!getter)
continue
var info = getter.info
info.idx = i
}
};
}
}
self.addListener = function Notifier_addListener(callback, accept) {
var idx = _listeners.indexOf(callback)
if (idx === -1) {
_listeners.push(callback)
_acceptLists.push(accept)
}
else {
_acceptLists[idx] = accept
}
}
self.removeListener = function Notifier_removeListener(callback) {
var idx = _listeners.indexOf(callback)
if (idx > -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,
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 < l; i++) {
update = updates[i]
_updates.push(update)
}
if (_updater) {
_clearCheckCallback(_updater)
}
_updater = _doCheckCallback(function() {
_updater = false
self.deliverChangeRecords()
})
}
self.deliverChangeRecords = function Notifier_deliverChangeRecords() {
var i = 0, l = _listeners.length,
// keepRunning = true, removed as it seems the actual implementation doesn't do this
// In response to BUG #5
retval
for (i = 0; i < l; i++) {
if (_listeners[i]) {
var currentUpdates
if (_acceptLists[i]) {
currentUpdates = []
for (var j = 0, updatesLength = _updates.length; j < updatesLength; j++) {
if (_acceptLists[i].indexOf(_updates[j].type) !== -1) {
currentUpdates.push(_updates[j])
}
}
}
else {
currentUpdates = _updates
}
if (currentUpdates.length) {
if (_listeners[i] === console.log) {
console.log(currentUpdates)
} else {
_listeners[i](currentUpdates)
}
}
}
}
_updates = []
}
self.notify = function Notifier_notify(changeRecord) {
if (typeof changeRecord !== 'object' || typeof changeRecord.type !== 'string') {
throw new TypeError('Invalid changeRecord with non-string \'type\' property')
}
changeRecord.object = watching
self.queueUpdates([changeRecord])
}
self._checkPropertyListing(true)
}
var _notifiers = [], _indexes = [], __observers = []
extend.getNotifier = function Object_getNotifier(O) {
var idx = _indexes.indexOf(O), notifier = idx > -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)) {
var __observer = new Observer(O, callback, accept)
__observers.push(__observer)
}
}
extend.unobserve = function Object_unobserve(O, callback) {
validateArguments(O, callback)
var idx = _indexes.indexOf(O),
notifier = idx > -1 ? _notifiers[idx] : false
console.log('unobserving...', {notifier, _indexes, idx})
if (!notifier) {
return
}
notifier.removeListener(callback)
if (notifier.listeners().length === 0) {
_indexes.splice(idx, 1)
_notifiers.splice(idx, 1)
}
}
extend.unobserveAll = function Object_unobserve(callback) {
console.log('unobserving all...', _indexes, _notifiers)
_notifiers.forEach(notifier => {
notifier.removeListener(callback)
})
}
})(Object, this)
}}