electron-devtools-vendor
Version:
<div align="center"> <h2>electron-devtools-vendor</h2> <img alt="MIT" src="https://img.shields.io/github/license/BlackHole1/electron-devtools-vendor?color=9cf&style=flat-square"> <img alt="GitHub repo size" src="https://img.shields.io/github/r
276 lines (239 loc) • 12.3 kB
JavaScript
Modules.set('utils', function() {
var utils = {
// Return a function that calls func with the given context.
bind: function(func, context) {
return function() {
return func.apply(context, arguments);
};
},
isObject: function(target) {
return typeof target == 'object' && target !== null;
},
isArray: function(object) {
return Object.prototype.toString.call(object) == '[object Array]';
},
// Return a clone of the given object.
// Note: the subproperties aren't cloned (shallow clone).
clone: function(object) {
if (!this.isObject(object)) return object;
if (this.isArray(object)) return object.slice();
var newObject = {};
for (var prop in object) {
newObject[prop] = object[prop];
}
return newObject;
},
// Extend the destination object with the properties of the source object.
// Returns the destination object.
extend: function(destination, source) {
for (var prop in source) {
destination[prop] = source[prop];
}
return destination;
},
each: function(object, iterator, context) {
if (this.isArray(object)) {
for (var i=0; i<object.length; i++) {
iterator.call(context, object[i], i, object);
}
} else {
for (var prop in object) {
if (object.hasOwnProperty(prop)) {
iterator.call(context, object[prop], prop, object);
}
}
}
// return the object for chaining
return object;
},
// Note: uses WatchJS dependency.
watchOnce: function(object, property, callback, recursionLevel) {
watch(object, property, function onceHandler(prop, action, newValue, oldValue) {
// by doing the unwatch before calling the callback (instead that doing it after),
// is possible for the callback to set the property again without ending up in an
// infinite loop.
unwatch(object, property, onceHandler);
callback(prop, action, newValue, oldValue);
}, recursionLevel);
},
// Call the callback every time the property of the object is setted, passing to it
// such setted value.
// Note: uses WatchJS dependency.
onSetted: function(object, property, callback) {
var handler = function (prop, action, newValue, oldValue) {
if (action == 'set') { callback(newValue); }
};
watch(object, property, handler, 0);
return [object, property, handler]; // usable to stop watching via stopOnSetted
},
// Like onSetted, but the callback is called only the first time the property is setted.
onceSetted: function(object, property, callback) {
var handler = function(prop, action, newValue, oldValue) {
if (action == 'set') { callback(newValue); }
};
this.watchOnce(object, property, handler, 0);
return [object, property, handler]; // usable to stop watching via stopOnSetted
},
// Monitor setting of object[property] and of its subproperties, including those added later.
// Detects also deletions of subproperties via delete.
// The recursion level is specified by 'recursionLevel' (like in WatchJS):
// undefined => complete recursion, 0 => no recursion (just level 0), n>0 => from level 0 to level n)
// Note: uses WatchJS dependency.
onSettedDeep: function(object, property, onChange, recursionLevel) {
var handler = function(prop, action, change, oldValue) {
if (action == "set" || action == "differentattr") {
onChange();
}
};
watch(object, property, handler, recursionLevel, true);
return [object, property, handler]; // usable to stop watching via stopOnSetted
},
// watcher is an array [object, property, (internal) handler] as returned by the on setted functions
// Note: uses WatchJS dependency.
stopOnSetted: function(watcher) {
unwatchOne.apply(this, watcher);
},
// Watch set of properties by using timers, without adding getters/setters to the watched object and
// thus without causing possible side effects; needed when watching DOM objects properties to prevent
// the browser from stopping recognizing the changes itself.
// TODO: this creates a timer for each call, refactor to use some global timer or less expensive way.
stealthOnSetted: function(object, property, callback) {
var newValue = object[property];
return setInterval(function() {
var oldValue = newValue;
newValue = object[property];
if (newValue !== oldValue) callback(newValue);
}, 50);
},
stopStealthOnSetted: function(watcher) {
clearInterval(watcher);
},
// Like onSetted, but calls the callback every time object[property] is setted to a non
// undefined value and also immediately if it's already non-undefined.
onDefined: function(object, property, callback) {
if (object[property] !== undefined) callback(object[property]);
this.onSetted(object, property, function(newValue) {
if (newValue !== undefined) callback(newValue);
});
},
// Like onDefined, but calls the callback just once.
// Note: uses WatchJS dependency.
onceDefined: function(object, property, callback) {
if (object[property] !== undefined) {
callback(object[property]);
return;
}
watch(object, property, function handler(prop, action, newValue, oldValue) {
if (newValue !== undefined) {
unwatch(object, property, handler);
callback(newValue);
}
}, 0);
},
// Listen for changes to the object property and calls the callback when that happens.
// Note: the callback is immediately called upon start if the property already has a non-undefined value.
// - recursionLevel is an integer that specified the recursion level to reach, e.g.
// 0 is "no recursion", 1 is "monitor also the properties of property" and so on.
// Note: an undefined recursionLevel means "complete recursion", but keep sure to not use that
// with objects that could contain circular references or the function will end up in an infinite loop.
// - property may also be of the form "prop1.prop2...", stating the path to follow to reach the
// sub-property to monitor.
// - Possible options:
// - stealth: if true, uses the stealth on setted function to monitor changes; this
// will cause the recursionLevel to be 0 since not supported by the stealth monitoring.
monitorProperty: function(object, property, recursionLevel, propertyChanged, options) {
options = options || {};
var me = this;
var onSettedFunc = options.stealth? this.stealthOnSetted : this.onSettedDeep;
var stopOnSettedFunc = options.stealth? this.stopStealthOnSetted : this.stopOnSetted;
var watchers = [];
var monitorFragment = function(object, propertyFragments, index) {
var currentProperty = propertyFragments[index];
var currentRecursionLevel = (index == propertyFragments.length-1) ? recursionLevel : 0; // used only in last fragment
var onFragmentChange = function() {
// TODO: remove old sub setters (if any)
if (index == propertyFragments.length - 1) {
// the final target has changed
propertyChanged();
} else if (me.isObject(object[currentProperty])) {
// remove the watchers of the old object and of its subproperties
for (var i=index; i<propertyFragments.length; i++) {
if (watchers[i]) stopOnSettedFunc(watchers[i]);
}
// monitor the next fragment
monitorFragment(object[currentProperty], propertyFragments, index+1);
}
}
// watch current fragment
watchers[index] = onSettedFunc(object, currentProperty, onFragmentChange, recursionLevel);
if (object[currentProperty] !== undefined) { onFragmentChange(); }
}
monitorFragment(object, property.split('.'), 0);
},
// Replace the 'functionName' function property of object
// with the one returned by the 'patcher' function.
// The patcher function is called with the original function as argument.
patchFunction: function(object, functionName, patcher, options) {
options = options || {};
var originalFunction = object[functionName];
object[functionName] = patcher(originalFunction);
if (options.preserveArity) {
var arity = originalFunction.length;
Object.defineProperty(object[functionName], 'length', {
configurable: true,
enumerable: true,
value: arity,
writable: true,
});
}
// When calling onString on the patched function, call the originalFunction onString.
// This is needed to allow an user of the originalFunction to manipulate its
// original string representation and not that of the patcher function.
// NOTE: if the original function is undefined, use the string representation of the empty function.
var emptyFunction = function() {};
object[functionName].toString = function() {
return originalFunction ? originalFunction.toString.apply(originalFunction, arguments)
: emptyFunction.toString.apply(emptyFunction, arguments);
}
},
// Like patchFunction, but waits for the function to be defined if is undefined
// when calling this.
patchFunctionLater: function(object, functionName, patcher) {
if (object[functionName] === undefined) {
this.onceDefined(object, functionName, this.bind(function() {
this.patchFunction(object, functionName, patcher);
}, this));
} else {
this.patchFunction(object, functionName, patcher);
}
},
// String utility functions
// Based on http://stackoverflow.com/a/498995
trim: function(target) {
return target.replace(/^\s+|\s+$/g, '');
},
// Based on http://stackoverflow.com/a/1981366
removeMultipleSpaces: function(target) {
return target.replace(/\s{2,}/g, ' ');
},
// Return the simplified version of target, i.e. a trimmed string with multiple spaces removed
simplify: function(target) {
return this.removeMultipleSpaces(this.trim(target));
},
// Based on http://stackoverflow.com/a/646643
startsWith: function(target, str) {
return target.slice(0, str.length) == str;
},
endsWith: function(target, str) {
return target.slice(-str.length) == str;
},
// remove first and last char
removeBorders: function(target) {
target = target.substring(1); // first
target = target.substring(0, target.length-1); // last
return target;
}
// end String utility functions
};
return utils;
});