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();
        //...