| /* |
| * Copyright (C) 2007-2018 Diego Perini |
| * All rights reserved. |
| * |
| * Caching/memoization module for NWMatcher |
| * |
| * Added capabilities: |
| * |
| * - Mutation Events are feature tested and used safely |
| * - handle caching different document types HTML/XML/SVG |
| * - store result sets for different selectors / contexts |
| * - simultaneously control mutation on multiple documents |
| * |
| */ |
| |
| (function(global) { |
| |
| // export the public API for CommonJS implementations, |
| // for headless JS engines or for standard web browsers |
| var Dom = |
| // as CommonJS/NodeJS module |
| typeof exports == 'object' ? exports : |
| // create or extend NW namespace |
| ((global.NW || (global.NW = { })) && |
| (global.NW.Dom || (global.NW.Dom = { }))), |
| |
| Contexts = { }, |
| Results = { }, |
| |
| isEnabled = false, |
| isExpired = true, |
| isPaused = false, |
| |
| context = global.document, |
| root = context.documentElement, |
| |
| // timing pauses |
| now = 0, |
| |
| // last time cache initialization was called |
| lastCalled = 0, |
| |
| // minimum time allowed between calls to the cache initialization |
| minCacheRest = 15, //ms |
| |
| mutationTest = |
| function(type, callback) { |
| var isSupported = false, |
| root = document.documentElement, |
| div = document.createElement('div'), |
| handler = function() { isSupported = true; }; |
| root.insertBefore(div, root.firstChild); |
| div.addEventListener(type, handler, true); |
| if (callback && callback.call) callback(div); |
| div.removeEventListener(type, handler, true); |
| root.removeChild(div); |
| return isSupported; |
| }, |
| |
| // check for Mutation Events, DOMAttrModified should be |
| // enough to ensure DOMNodeInserted/DOMNodeRemoved exist |
| HACKED_MUTATION_EVENTS = false, |
| |
| NATIVE_MUTATION_EVENTS = root.addEventListener ? |
| mutationTest('DOMAttrModified', function(e) { e.setAttribute('id', 'nw'); }) : false, |
| |
| loadResults = |
| function(selector, from, doc, root) { |
| if (isEnabled && !isPaused) { |
| if (!isExpired) { |
| if (Results[selector] && Contexts[selector] === from) { |
| return Results[selector]; |
| } |
| } else { |
| // pause caching while we are getting |
| // hammered by dom mutations (jdalton) |
| now = (new Date).getTime(); |
| if ((now - lastCalled) < minCacheRest) { |
| isPaused = isExpired = true; |
| setTimeout(function() { isPaused = false; }, minCacheRest); |
| } else setCache(true, doc); |
| lastCalled = now; |
| } |
| } |
| return undefined; |
| }, |
| |
| saveResults = |
| function(selector, from, doc, data) { |
| Contexts[selector] = from; |
| Results[selector] = data; |
| return; |
| }, |
| |
| /*-------------------------------- CACHING ---------------------------------*/ |
| |
| // invoked by mutation events to expire cached parts |
| mutationWrapper = |
| function(event) { |
| var d = event.target.ownerDocument || event.target; |
| stopMutation(d); |
| expireCache(d); |
| }, |
| |
| // append mutation events |
| startMutation = |
| function(d) { |
| if (!d.isCaching && d.addEventListener) { |
| // FireFox/Opera/Safari/KHTML have support for Mutation Events |
| d.addEventListener('DOMAttrModified', mutationWrapper, true); |
| d.addEventListener('DOMNodeInserted', mutationWrapper, true); |
| d.addEventListener('DOMNodeRemoved', mutationWrapper, true); |
| d.isCaching = true; |
| } |
| }, |
| |
| // remove mutation events |
| stopMutation = |
| function(d) { |
| if (d.isCaching && d.removeEventListener) { |
| d.removeEventListener('DOMAttrModified', mutationWrapper, true); |
| d.removeEventListener('DOMNodeInserted', mutationWrapper, true); |
| d.removeEventListener('DOMNodeRemoved', mutationWrapper, true); |
| d.isCaching = false; |
| } |
| }, |
| |
| // enable/disable context caching system |
| // @d optional document context (iframe, xml document) |
| // script loading context will be used as default context |
| setCache = |
| function(enable, d) { |
| if (!!enable) { |
| isExpired = false; |
| startMutation(d); |
| } else { |
| isExpired = true; |
| stopMutation(d); |
| } |
| isEnabled = !!enable; |
| }, |
| |
| // expire complete cache |
| // can be invoked by Mutation Events or |
| // programmatically by other code/scripts |
| // document context is mandatory no checks |
| expireCache = |
| function(d) { |
| isExpired = true; |
| Contexts = { }; |
| Results = { }; |
| }; |
| |
| if (!NATIVE_MUTATION_EVENTS && root.addEventListener && Element && Element.prototype) { |
| if (mutationTest('DOMNodeInserted', function(e) { e.appendChild(document.createElement('div')); }) && |
| mutationTest('DOMNodeRemoved', function(e) { e.removeChild(e.appendChild(document.createElement('div'))); })) { |
| HACKED_MUTATION_EVENTS = true; |
| Element.prototype._setAttribute = Element.prototype.setAttribute; |
| Element.prototype.setAttribute = |
| function(name, val) { |
| this._setAttribute(name, val); |
| mutationWrapper({ |
| target: this, |
| type: 'DOMAttrModified', |
| attrName: name, |
| attrValue: val }); |
| }; |
| } |
| } |
| |
| isEnabled = NATIVE_MUTATION_EVENTS || HACKED_MUTATION_EVENTS; |
| |
| /*------------------------------- PUBLIC API -------------------------------*/ |
| |
| // save results into cache |
| Dom.saveResults = saveResults; |
| |
| // load results from cache |
| Dom.loadResults = loadResults; |
| |
| // expire DOM tree cache |
| Dom.expireCache = expireCache; |
| |
| // enable/disable cache |
| Dom.setCache = setCache; |
| |
| })(this); |