dom-audit-logger
by Jason Miller
CSS
html, body {
font: 14px/1.2 system-ui;
}
html {
margin-bottom: 40vh;
}
iframe {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 40vh;
z-index: 99;
border: none;
background: #eee;
box-shadow: 0 0 5px #000;
}
JavaScript
const logFrame = document.createElement('iframe');
document.body.appendChild(logFrame);
setTimeout(() => {
const doc = logFrame.contentWindow.document;
doc.body.style.padding = 0;
const log = text => {
const line = doc.createElement('pre');
line.style = 'padding:4px;margin:0;box-shadow:0 -1px 0 #999;';
line.textContent = text;
doc.body.append(line);
return line;
}
const logger = new DOMAuditLogger();
logger.on('log', entry => {
const text = logger.formatLog(entry);
log(text);
});
window.logger = logger;
const btn = document.createElement('button');
const list = document.createElement('ul');
btn.textContent = '+ Add'
btn.onclick = () => {
const item = document.createElement('li');
item.textContent = `Item ${list.childNodes.length}`;
item.onclick = () => item.remove();
list.append(item);
};
document.body.append(btn, list);
});
class DOMAuditLogger {
#nextId = 0;
#logs = [];
#handlers = new Map();
#cleanup = [];
constructor() {
this.observeSetter(Element.prototype, 'innerHTML');
this.observeSetter(Element.prototype, 'className');
this.observeSetter(Element.prototype, 'id');
this.observeSetter(CharacterData.prototype, 'data');
this.observeSetter(Node.prototype, 'nodeValue');
this.observeSetter(Node.prototype, 'textContent');
const logInsert = this.logInsert.bind(this);
this.observeMethod(Element.prototype, 'append', logInsert);
this.observeMethod(Element.prototype, 'appendChild', logInsert);
this.observeMethod(Element.prototype, 'insertBefore', logInsert);
this.observeMethod(Element.prototype, 'removeChild', (_, child) => this.logRemove(child));
this.observeMethod(Element.prototype, 'remove', this.logRemove.bind(this));
this.observeMethod(CharacterData.prototype, 'remove', this.logRemove.bind(this));
}
destroy() {
this.clear();
//...