js/domreplay/loader.js
import Logger from './logger';
import RegistrySingleton from './registry';
// HTMLElements using this attribute will be ignored
export const domreplayIgnoreAttributeName = 'dom-replay-ignore';
/**
* Generator function that creates an iterator of
* elements by tag name
* @param {String} tagname - HTML tag name
* @yield {HTMLElement} Element
*/
function* elementByTagNameIterator(tagname) {
const elements = document.getElementsByTagName(tagname);
yield* Array.from(elements).map(element => element);
}
/**
* Initializes events on elements in the dom.
*/
const initializeEvents = () => {
Logger.debug('Initializing existing events.');
for (let tagname of RegistrySingleton.getTagnames()) {
let events = RegistrySingleton.getEventsByTagname(tagname);
for (let element of elementByTagNameIterator(tagname)) {
if (!element.hasAttribute(domreplayIgnoreAttributeName)) {
for (let event of events) {
Logger.debug(`Adding ${event.eventType} event listener to element`);
element.addEventListener(event.eventType, () => event._handler(element), false);
}
}
}
}
}
/**
* Flattens the node tree and yields only,
* HTML elements with tagnames that should,
* be tracked.
* @param {[type]} mutation - mutation object from the mutation observer.
* @param {Array[]} tagfilter - tagnames that should be tracked
* @yield {HTMLElement} yields trackable HTMLElements.
*/
function* getFlatElementIterator(mutation, tagfilter) {
const recursiveFlat = function* (node) {
if (node instanceof HTMLElement &&
tagfilter.includes(node.tagName.toLowerCase()) &&
!node.hasAttribute(domreplayIgnoreAttributeName)
) {
yield node;
} else if (node.childNodes.length > 0) {
for (let n of Array.from(node.childNodes)) {
yield* recursiveFlat(n, tagfilter);
}
}
}
for (let node of Array.from(mutation.addedNodes)) {
yield* recursiveFlat(node);
}
}
/**
* Initializes mutation observer,
* when ever a new element has been added, it will automaticly
* add event listener to the element.
*/
const initializeMutationObserver = () => {
Logger.debug('Initializing mutation observer.');
const analyzeElement = (element) => {
if (RegistrySingleton.getTagnames().includes(element.tagName.toLowerCase())) {
for (let event of RegistrySingleton.getEventsByTagname(element.tagName.toLowerCase())) {
Logger.debug(`mutationobserver is adding a ${event.eventType}-listener to element ${element.id}`);
element.addEventListener(event.eventType, () => event._handler(element), false);
}
}
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
for (let element of getFlatElementIterator(mutation, RegistrySingleton.getTagnames())) {
analyzeElement(element);
}
});
});
const config = {
childList: true,
subtree: true
};
observer.observe(document.body, config);
}
/**
* Initializes events on existing elements,
* and initializes the mutation observer when
* document is ready.
* @access private
* @param {Boolean} test - if true mutation observer is disabled.
*/
export const domloader = (test=false) => {
return new Promise(resolve => {
const time = setInterval(() => {
if (document.readyState !== 'complete') {
Logger.debug('Ready-state check failed!');
return;
}
clearInterval(time);
initializeEvents();
// Don't initialize mutation observer when testing.
if (!test) {
initializeMutationObserver();
}
Logger.debug('Done initializing events');
resolve();
}, 100);
});
}