diff --git a/doc/TOOL_API.md b/doc/TOOL_API.md index b1d6887..ba5b50e 100644 --- a/doc/TOOL_API.md +++ b/doc/TOOL_API.md @@ -105,9 +105,10 @@ LocalStorage, sessionStorage, cookies, scripts, styleSheets and images. ### Config -|Name |Type |Desc | -|----------------|-------|------------------| -|hideErudaSetting|boolean|Hide Eruda Setting| +|Name |Type |Desc | +|----------------|-------|---------------------| +|hideErudaSetting|boolean|Hide Eruda Setting | +|observeElement |boolean|Auto Refresh Elements| ## Sources diff --git a/doc/UTIL_API.md b/doc/UTIL_API.md index 183dfbe..a49515a 100644 --- a/doc/UTIL_API.md +++ b/doc/UTIL_API.md @@ -450,6 +450,19 @@ logger.on('debug', function (argList) }); ``` +## SafeMutationObserver + +Safe MutationObserver, does nothing if MutationObserver is not supported. + +```javascript +var observer = new DomObserver(function (mutations) +{ + // Do something. +}); +observer.observe(document.htmlElement); +observer.disconnect(); +``` + ## Select Simple wrapper of querySelectorAll to make dom selection easier. @@ -711,6 +724,19 @@ Any nested objects or arrays will be copied by reference, not duplicated. clone({name: 'eustia'}); // -> {name: 'eustia'} ``` +## concat + +Concat multiple arrays into a single array. + +|Name |Type |Desc | +|------|-----|------------------| +|...arr|array|Arrays to concat | +|return|array|Concatenated array| + +```javascript +concat([1, 2], [3], [4, 5]); // -> [1, 2, 3, 4, 5] +``` + ## contain Check if the value is present in the list. @@ -887,7 +913,7 @@ if (browser.name === 'ie' && browser.version < 9) ## each -Iterates over elements of collection and invokes iteratee for each element. +Iterate over elements of collection and invokes iteratee for each element. |Name |Type |Desc | |--------|------------|------------------------------| @@ -993,6 +1019,24 @@ fileSize(1500000000); // -> '1.4G' fileSize(1500000000000); // -> '1.36T' ``` +## filter + +Iterates over elements of collection, returning an array of all the values that pass a truth test. + +|Name |Type |Desc | +|---------|--------|---------------------------------------| +|obj |array |Collection to iterate over | +|predicate|function|Function invoked per iteration | +|[ctx] |* |Predicate context | +|return |array |Array of all values that pass predicate| + +```javascript +filter([1, 2, 3, 4, 5], function (val) +{ + return val % 2 === 0; +}); // -> [2, 4] +``` + ## freeze Shortcut for Object.freeze. @@ -1106,6 +1150,20 @@ Check if value is classified as an arguments object. })(); ``` +## isArr + +Check if value is an `Array` object. + +|Name |Type |Desc | +|------|-------|----------------------------------| +|val |* |The value to check | +|return|boolean|True if value is an `Array` object| + +```javascript +isArr([]); // -> true +isArr({}); // -> false +``` + ## isArrLike Check if value is array-like. @@ -1239,6 +1297,19 @@ Check if keys and values in src are contained in obj. isMatch({a: 1, b: 2}, {a: 1}); // -> true ``` +## isMobile + +Check whether client is using a mobile browser using ua. + +|Name |Type |Desc | +|------------------------|-------|-------------------------------------| +|[ua=navigator.userAgent]|string |User agent | +|return |boolean|True if ua belongs to mobile browsers| + +```javascript +isMobile(navigator.userAgent); +``` + ## isNull Check if value is an Null. @@ -1252,6 +1323,21 @@ Check if value is an Null. isNull(null); // -> true ``` +## isNum + +Checks if value is classified as a Number primitive or object. + +|Name |Type |Desc | +|------|-------|-------------------------------------| +|value |* |Value to check | +|return|boolean|True if value is correctly classified| + +```javascript +isNum(5); // -> true +isNum(5.1); // -> true +isNum({}); // -> false +``` + ## isObj Check if value is the language type of Object. @@ -1268,6 +1354,19 @@ isObj({}); // -> true isObj([]); // -> true ``` +## isRegExp + +Check if value is a regular expression. + +|Name |Type |Desc | +|------|-------|-------------------------------------| +|val |* |Value to check | +|return|boolean|True if value is a regular expression| + +```javascript +isRegExp(/a/); // -> true +``` + ## isStr Check if value is a string primitive. @@ -1281,6 +1380,20 @@ Check if value is a string primitive. isStr('eris'); // -> true ``` +## isUndef + +Check if value is undefined. + +|Name |Type |Desc | +|------|-------|--------------------------| +|val |* |Value to check | +|return|boolean|True if value is undefined| + +```javascript +isUndef(void 0); // -> true +isUndef(null); // -> false +``` + ## kebabCase Convert string to "kebabCase". @@ -1416,6 +1529,23 @@ var localStorage = window.localStorage || memStorage; localStorage.setItem('test', 'eris'); ``` +## memoize + +Memoize a given function by caching the computed result. + +|Name |Type |Desc | +|--------|--------|------------------------------------| +|fn |function|Function to have its output memoized| +|[hashFn]|function|Function to create cache key | +|return |function|New memoized function | + +```javascript +var fibonacci = memoize(function(n) +{ + return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); +}); +``` + ## meta Document meta manipulation, turn name and content into key value pairs. diff --git a/eustia/SafeMutationObserver.js b/eustia/SafeMutationObserver.js new file mode 100644 index 0000000..ebe319d --- /dev/null +++ b/eustia/SafeMutationObserver.js @@ -0,0 +1,23 @@ +/* Safe MutationObserver, does nothing if MutationObserver is not supported. + * + * ```javascript + * var observer = new DomObserver(function (mutations) + * { + * // Do something. + * }); + * observer.observe(document.htmlElement); + * observer.disconnect(); + * ``` + */ + +exports = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + +if (!exports) +{ + exports = class MutationObserver { + constructor() {} + observe() {} + disconnect() {} + takeRecords() {} + }; +} diff --git a/src/Elements/Elements.js b/src/Elements/Elements.js index aef7647..691e802 100644 --- a/src/Elements/Elements.js +++ b/src/Elements/Elements.js @@ -18,6 +18,7 @@ export default class Elements extends Tool this._rmDefComputedStyle = true; this._highlightElement = false; this._selectElement = false; + this._observeElement = true; } init($el, parent) { @@ -40,9 +41,16 @@ export default class Elements extends Tool { super.show(); + if (this._observeElement) this._enableObserver(); if (!this._curEl) this._setEl(this._htmlEl); this._render(); } + hide() + { + this._disableObserver(); + + return super.hide(); + } set(e) { this._setEl(e); @@ -90,6 +98,7 @@ export default class Elements extends Tool util.evalCss.remove(this._style); this._select.disable(); this._highlight.destroy(); + this._disableObserver(); this.restoreEventTarget(); } _back() @@ -187,18 +196,17 @@ export default class Elements extends Tool this._render(); } - _toggleObserver(flag) + _enableObserver() { - let observer = this._observer; - - if (!observer) return; - - flag ? observer.observe(this._htmlEl, { + this._observer.observe(this._htmlEl, { attributes: true, childList: true, - characterData: true, subtree: true - }) : observer.disconnect(); + }); + } + _disableObserver() + { + this._observer.disconnect(); } _toggleHighlight() { @@ -291,11 +299,7 @@ export default class Elements extends Tool } _initObserver() { - let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; - - if (!MutationObserver) return; - - this._observer = new MutationObserver(mutations => + this._observer = new util.SafeMutationObserver(mutations => { util.each(mutations, mutation => this._handleMutation(mutation)); }); @@ -341,14 +345,16 @@ export default class Elements extends Tool })); if (cfg.get('overrideEventTarget')) this.overrideEventTarget(); - if (cfg.get('observeElement')) this._toggleObserver(true); + if (cfg.get('observeElement')) this._observeElement = false; cfg.on('change', (key, val) => { switch (key) { case 'overrideEventTarget': return val ? this.overrideEventTarget(): this.restoreEventTarget(); - case 'observeElement': return this._toggleObserver(val); + case 'observeElement': + this._observeElement = val; + return val ? this._enableObserver() :this._disableObserver(); } }); diff --git a/src/Resources/Resources.js b/src/Resources/Resources.js index d778333..2337ddd 100644 --- a/src/Resources/Resources.js +++ b/src/Resources/Resources.js @@ -17,6 +17,7 @@ export default class Resources extends Tool this._scriptData = []; this._stylesheetData = []; this._imageData = []; + this._observeElement = true; this._tpl = require('./Resources.hbs'); } init($el, parent) @@ -27,9 +28,8 @@ export default class Resources extends Tool this.refresh(); this._bindEvent(); + this._initObserver(); this._initCfg(); - - util.ready(() => this._observeScript()); } refresh() { @@ -44,8 +44,8 @@ export default class Resources extends Tool { super.destroy(); + this._disableObserver(); util.evalCss.remove(this._style); - this._unobserveScript(); } refreshScript() { @@ -173,9 +173,16 @@ export default class Resources extends Tool show() { super.show(); + if (this._observeElement) this._enableObserver(); return this.refresh(); } + hide() + { + this._disableObserver(); + + return super.hide(); + } _bindEvent() { let self = this, @@ -299,22 +306,28 @@ export default class Resources extends Tool let cfg = this.config = util.createCfg('resources'); cfg.set(util.defaults(cfg.get(), { - hideErudaSetting: true + hideErudaSetting: true, + observeElement: true })); if (cfg.get('hideErudaSetting')) this._hideErudaSetting = true; + if (!cfg.get('observeElement')) this._observeElement = false; cfg.on('change', (key, val) => { switch (key) { case 'hideErudaSetting': this._hideErudaSetting = val; return; + case 'observeElement': + this._observeElement = val; + return val ? this._enableObserver() : this._disableObserver(); } }); let settings = this._parent.get('settings'); settings.text('Resources') .switch(cfg, 'hideErudaSetting', 'Hide Eruda Setting') + .switch(cfg, 'observeElement', 'Auto Refresh Elements') .separator(); } _render() @@ -352,36 +365,63 @@ export default class Resources extends Tool this._lastHtml = html; this._$el.html(html); } - _observeScript() + _initObserver() { - let MutationObserver = window.MutationObserver || window.WebKitMutationObserver; - if (!MutationObserver) return; - - this._scriptObserver = new MutationObserver(mutations => + this._observer = new util.SafeMutationObserver(mutations => { - if (!this.active) return; - - mutations.forEach(mutation => + let needToRender = false; + util.each(mutations, mutation => { - mutation.addedNodes.forEach(node => - { - if (/^script$/i.test(node.tagName) && node.src !== '') - { - this._scriptData.push(node.src); - } - }); - - this._scriptData = util.unique(this._scriptData); - this._render(); - }); + if (this._handleMutation(mutation)) needToRender = true; + }); + if (needToRender) this._render(); }); - - this._scriptObserver.observe(document.head, { childList: true }); - this._scriptObserver.observe(document.body, { childList: true }); } - _unobserveScript() + _handleMutation(mutation) { - if (this._scriptObserver) this._scriptObserver.disconnect(); + if (util.isErudaEl(mutation.target)) return; + + let checkEl = el => + { + let tagName = getLowerCaseTagName(el); + switch (tagName) + { + case 'script': this.refreshScript(); return true; + case 'img': this.refreshImage(); return true; + case 'link': this.refreshStylesheet(); return true; + } + + return false; + } + + if (mutation.type === 'attributes') + { + if (checkEl(mutation.target)) return true; + } else if (mutation.type === 'childList') + { + if (checkEl(mutation.target)) return true; + let nodes = util.toArr(mutation.addedNodes); + nodes = util.concat(nodes, util.toArr(mutation.removedNodes)); + + for (let node of nodes) + { + if (checkEl(node)) return true; + } + } + + return false; + } + _enableObserver() + { + this._observer.observe(document.documentElement, { + attributes: true, + childList: true, + subtree: true + }); + } + _disableObserver() + { + this._observer.disconnect(); } } @@ -447,4 +487,10 @@ function delCookie(key) } } +function getLowerCaseTagName(el) +{ + if (!el.tagName) return ''; + return el.tagName.toLowerCase(); +} + let sliceStr = (str, len) => str.length < len ? str : str.slice(0, len) + '...'; diff --git a/src/lib/util.js b/src/lib/util.js index f011790..a1f18f0 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -359,6 +359,37 @@ module.exports = (function () return exports; })(); + /* ------------------------------ SafeMutationObserver ------------------------------ */ + + _.SafeMutationObserver = (function (exports) + { + /* Safe MutationObserver, does nothing if MutationObserver is not supported. + * + * ```javascript + * var observer = new DomObserver(function (mutations) + * { + * // Do something. + * }); + * observer.observe(document.htmlElement); + * observer.disconnect(); + * ``` + */ + + exports = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; + + if (!exports) + { + exports = class MutationObserver { + constructor() {} + observe() {} + disconnect() {} + takeRecords() {} + }; + } + + return exports; + })({}); + /* ------------------------------ noop ------------------------------ */ var noop = _.noop = (function () @@ -1343,14 +1374,14 @@ module.exports = (function () */ /* dependencies - * isNum has isFn + * isNum isFn */ var MAX_ARR_IDX = Math.pow(2, 53) - 1; function exports(val) { - if (!has(val, 'length')) return false; + if (!val) return false; var len = val.length; @@ -3913,6 +3944,47 @@ module.exports = (function () return exports; })({}); + /* ------------------------------ concat ------------------------------ */ + + _.concat = (function () + { + /* Concat multiple arrays into a single array. + * + * |Name |Type |Desc | + * |------|-----|------------------| + * |...arr|array|Arrays to concat | + * |return|array|Concatenated array| + * + * ```javascript + * concat([1, 2], [3], [4, 5]); // -> [1, 2, 3, 4, 5] + * ``` + */ + + /* module + * env: all + * test: all + */ + + /* dependencies + * toArr + */ + + function exports() + { + var args = toArr(arguments), + ret = []; + + for (var i = 0, len = args.length; i < len; i++) + { + ret = ret.concat(toArr(args[i])); + } + + return ret; + } + + return exports; + })(); + /* ------------------------------ some ------------------------------ */ var some = _.some = (function () @@ -5329,55 +5401,6 @@ module.exports = (function () return exports; })(); - /* ------------------------------ ready ------------------------------ */ - - _.ready = (function () - { - /* Invoke callback when dom is ready, similar to jQuery ready. - * - * |Name|Type |Desc | - * |----|--------|-----------------| - * |fn |function|Callback function| - * - * ```javascript - * ready(function () - * { - * // It's safe to manipulate dom here. - * }); - * ``` - */ - - /* module - * env: browser - * test: browser - */ - - var fns = [], - listener, - doc = document, - hack = doc.documentElement.doScroll, - domContentLoaded = 'DOMContentLoaded', - loaded = (hack ? /^loaded|^c/ : /^loaded|^i|^c/).test(doc.readyState); - - if (!loaded) - { - doc.addEventListener(domContentLoaded, listener = function () - { - doc.removeEventListener(domContentLoaded, listener); - loaded = 1; - /* eslint-disable no-cond-assign */ - while (listener = fns.shift()) listener(); - }); - } - - function exports(fn) - { - loaded ? setTimeout(fn, 0) : fns.push(fn) - } - - return exports; - })(); - /* ------------------------------ rtrim ------------------------------ */ var rtrim = _.rtrim = (function ()