blob: b3b40dd3015ed8ecae78f527d7c9e40c3d1cdc8e [file] [log] [blame]
/*
* 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);