ultraviolet/src/client/dom/element.js
David Reed 561a1ee190
run ESLint
Several bugs were found (and fixed):
src/client/index.js:71
the following if() was equivalent to if (false in obj)

src/client/dom/element.js:207
the following if() was equivalent to if (!element || false in element)

src/client/rewrite/html.js:179
an undefined iterate() was referenced
2022-11-23 12:37:00 -05:00

224 lines
7.5 KiB
JavaScript

import EventEmitter from 'events';
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;