import EventEmitter from "../events.js"; import HookEvent from "../hook.js"; class ElementApi extends EventEmitter { constructor(ctx) { super(); this.ctx = ctx; this.window = ctx.window; this.Audio = this.window.Audio; this.Element = this.window.Element; this.elemProto = this.Element ? this.Element.prototype : {}; this.innerHTML = ctx.nativeMethods.getOwnPropertyDescriptor(this.elemProto, 'innerHTML'); this.outerHTML = ctx.nativeMethods.getOwnPropertyDescriptor(this.elemProto, 'outerHTML'); this.setAttribute = this.elemProto.setAttribute; this.getAttribute = this.elemProto.getAttribute; this.removeAttribute = this.elemProto.removeAttribute; this.hasAttribute = this.elemProto.hasAttribute; this.querySelector = this.elemProto.querySelector; this.querySelectorAll = this.elemProto.querySelectorAll; this.insertAdjacentHTML = this.elemProto.insertAdjacentHTML; this.insertAdjacentText = this.elemProto.insertAdjacentText; }; overrideQuerySelector() { this.ctx.override(this.elemProto, 'querySelector', (target, that, args) => { if (!args.length) return target.apply(that, args); let [ selectors ] = args; const event = new HookEvent({ selectors }, target, that); this.emit('querySelector', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.selectors); }); }; overrideAttribute() { this.ctx.override(this.elemProto, 'getAttribute', (target, that, args) => { if (!args.length) return target.apply(that, args); let [ name ] = args; const event = new HookEvent({ name }, target, that); this.emit('getAttribute', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.name); }); this.ctx.override(this.elemProto, 'setAttribute', (target, that, args) => { if (2 > args.length) return target.apply(that, args); let [ name, value ] = args; const event = new HookEvent({ name, value }, target, that); this.emit('setAttribute', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.name, event.data.value); }); this.ctx.override(this.elemProto, 'hasAttribute', (target, that, args) => { if (!args.length) return target.apply(that, args); let [ name ] = args; const event = new HookEvent({ name }, target, that); this.emit('hasAttribute', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.name); }); this.ctx.override(this.elemProto, 'removeAttribute', (target, that, args) => { if (!args.length) return target.apply(that, args); let [ name ] = args; const event = new HookEvent({ name }, target, that); this.emit('removeAttribute', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.name); }); }; overrideAudio() { this.ctx.override(this.window, 'Audio', (target, that, args) => { if (!args.length) return new target(...args); let [ url ] = args; const event = new HookEvent({ url }, target, that); this.emit('audio', event); if (event.intercepted) return event.returnValue; return new event.target(event.data.url); }, true); }; overrideHtml() { this.hookProperty(this.Element, 'innerHTML', { get: (target, that) => { const event = new HookEvent({ value: target.call(that) }, target, that); this.emit('getInnerHTML', event); if (event.intercepted) return event.returnValue; return event.data.value; }, set: (target, that, [ val ]) => { const event = new HookEvent({ value: val }, target, that); this.emit('setInnerHTML', event); if (event.intercepted) return event.returnValue; target.call(that, event.data.value); }, }); this.hookProperty(this.Element, 'outerHTML', { get: (target, that) => { const event = new HookEvent({ value: target.call(that) }, target, that); this.emit('getOuterHTML', event); if (event.intercepted) return event.returnValue; return event.data.value; }, set: (target, that, [ val ]) => { const event = new HookEvent({ value: val }, target, that); this.emit('setOuterHTML', event); if (event.intercepted) return event.returnValue; target.call(that, event.data.value); }, }); }; overrideInsertAdjacentHTML() { this.ctx.override(this.elemProto, 'insertAdjacentHTML', (target, that, args) => { if (2 > args.length) return target.apply(that, args); let [ position, html ] = args; const event = new HookEvent({ position, html }, target, that); this.emit('insertAdjacentHTML', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.position, event.data.html); }); }; overrideInsertAdjacentText() { this.ctx.override(this.elemProto, 'insertAdjacentText', (target, that, args) => { if (2 > args.length) return target.apply(that, args); let [ position, text ] = args; const event = new HookEvent({ position, text }, target, that); this.emit('insertAdjacentText', event); if (event.intercepted) return event.returnValue; return event.target.call(event.that, event.data.position, event.data.text); }); }; hookProperty(element, prop, handler) { if (!element || !prop in element) return false; if (this.ctx.nativeMethods.isArray(element)) { for (const elem of element) { this.hookProperty(elem, prop, handler); }; return true; }; const proto = element.prototype; this.ctx.overrideDescriptor(proto, prop, handler); return true; }; }; export default ElementApi;