123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- /*******************************************************************************
- ηMatrix - a browser extension to black/white list requests.
- Copyright (C) 2014-2019 Raymond Hill
- Copyright (C) 2019-2022 Alessio Vanni
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see {http://www.gnu.org/licenses/}.
- Home: https://gitlab.com/vannilla/ematrix
- uMatrix Home: https://github.com/gorhill/uBlock
- */
- /* global DOMTokenList */
- /* exported uDom */
- 'use strict';
- // It's just a silly, minimalist DOM framework: this allows me to not rely
- // on jQuery. jQuery contains way too much stuff than I need, and as per
- // Opera rules, I am not allowed to use a cut-down version of jQuery. So
- // the code here does *only* what I need, and nothing more, and with a lot
- // of assumption on passed parameters, etc. I grow it on a per-need-basis only.
- // ηMatrix: well, we are not bound by these rules, but not being
- // reliant on jQuery is not a bad thing.
- var uDom = (function () {
- let DOMList = function () {
- this.nodes = [];
- };
- Object.defineProperty(DOMList.prototype, 'length', {
- get: function() {
- return this.nodes.length;
- }
- });
- let DOMListFactory = function (selector, context) {
- let r = new DOMList();
- if (typeof selector === 'string') {
- selector = selector.trim();
- if (selector !== '') {
- return addSelectorToList(r, selector, context);
- }
- }
- if (selector instanceof Node) {
- return addNodeToList(r, selector);
- }
- if (selector instanceof NodeList) {
- return addNodeListToList(r, selector);
- }
- if (selector instanceof DOMList) {
- return addListToList(r, selector);
- }
- return r;
- };
- DOMListFactory.onLoad = function (callback) {
- window.addEventListener('load', callback);
- };
- DOMListFactory.nodeFromId = function (id) {
- return document.getElementById(id);
- };
- DOMListFactory.nodeFromSelector = function (selector) {
- return document.querySelector(selector);
- };
- let addNodeToList = function (list, node) {
- if (node) {
- list.nodes.push(node);
- }
- return list;
- };
- let addNodeListToList = function (list, nodelist) {
- if (nodelist) {
- let n = nodelist.length;
- for (let i=0; i<n; ++i) {
- list.nodes.push(nodelist[i]);
- }
- }
- return list;
- };
- let addListToList = function (list, other) {
- list.nodes = list.nodes.concat(other.nodes);
- return list;
- };
- let addSelectorToList = function (list, selector, context) {
- let p = context || document;
- let r = p.querySelectorAll(selector);
- let n = r.length;
- for (let i=0; i<n; ++i) {
- list.nodes.push(r[i]);
- }
- return list;
- };
- let nodeInNodeList = function (node, nodeList) {
- let i = nodeList.length;
- while (i--) {
- if (nodeList[i] === node) {
- return true;
- }
- }
- return false;
- };
- let doesMatchSelector = function (node, selector) {
- if (!node) {
- return false;
- }
- if (node.nodeType !== 1) {
- return false;
- }
- if (selector === undefined) {
- return true;
- }
- let parentNode = node.parentNode;
- if (!parentNode || !parentNode.setAttribute) {
- return false;
- }
- let doesMatch = false;
- parentNode.setAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO', '');
- let grandpaNode = parentNode.parentNode || document;
- let nl = grandpaNode
- .querySelectorAll('[uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO] > '
- + selector);
- let i = nl.length;
- while (doesMatch === false && i--) {
- doesMatch = nl[i] === node;
- }
- parentNode.removeAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO');
- return doesMatch;
- };
- DOMList.prototype.nodeAt = function (i) {
- return this.nodes[i] || null;
- };
- DOMList.prototype.at = function (i) {
- return addNodeToList(new DOMList(), this.nodes[i]);
- };
- DOMList.prototype.toArray = function () {
- return this.nodes.slice();
- };
- DOMList.prototype.pop = function () {
- return addNodeToList(new DOMList(), this.nodes.pop());
- };
- DOMList.prototype.forEach = function (fn) {
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- fn(this.at(i), i);
- }
- return this;
- };
- DOMList.prototype.subset = function (i, l) {
- let r = new DOMList();
- let n = (l !== undefined) ? l : this.nodes.length;
- let j = Math.min(i + n, this.nodes.length);
- if (i < j) {
- r.nodes = this.nodes.slice(i, j);
- }
- return r;
- };
- DOMList.prototype.first = function () {
- return this.subset(0, 1);
- };
- DOMList.prototype.next = function (selector) {
- let r = new DOMList();
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- let node = this.nodes[i];
- while (node.nextSibling !== null) {
- node = node.nextSibling;
- if (node.nodeType !== 1) {
- continue;
- }
- if (doesMatchSelector(node, selector) === false) {
- continue;
- }
- addNodeToList(r, node);
- break;
- }
- }
- return r;
- };
- DOMList.prototype.parent = function () {
- var r = new DOMList();
- if (this.nodes.length) {
- addNodeToList(r, this.nodes[0].parentNode);
- }
- return r;
- };
- DOMList.prototype.filter = function (filter) {
- let r = new DOMList();
- let filterFunc;
- if (typeof filter === 'string') {
- filterFunc = function () {
- return doesMatchSelector(this, filter);
- };
- } else if (typeof filter === 'function') {
- filterFunc = filter;
- } else {
- filterFunc = function (){
- return true;
- };
- }
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- let node = this.nodes[i];
- if (filterFunc.apply(node)) {
- addNodeToList(r, node);
- }
- }
- return r;
- };
- // TODO: Avoid possible duplicates
- DOMList.prototype.ancestors = function (selector) {
- let r = new DOMList();
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- let node = this.nodes[i].parentNode;
- while (node) {
- if (doesMatchSelector(node, selector)) {
- addNodeToList(r, node);
- }
- node = node.parentNode;
- }
- }
- return r;
- };
- DOMList.prototype.descendants = function (selector) {
- let r = new DOMList();
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- let nl = this.nodes[i].querySelectorAll(selector);
- addNodeListToList(r, nl);
- }
- return r;
- };
- DOMList.prototype.contents = function () {
- let r = new DOMList();
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- let cnodes = this.nodes[i].childNodes;
- let cn = cnodes.length;
- for (let ci=0; ci<cn; ++ci) {
- addNodeToList(r, cnodes.item(ci));
- }
- }
- return r;
- };
- DOMList.prototype.remove = function() {
- let i = this.nodes.length;
- while (i--) {
- let cn = this.nodes[i];
- let p = cn.parentNode;
- if (p) {
- p.removeChild(cn);
- }
- }
- return this;
- };
- DOMList.prototype.detach = DOMList.prototype.remove;
- DOMList.prototype.empty = function () {
- let i = this.nodes.length;
- while (i--) {
- let node = this.nodes[i];
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- }
- return this;
- };
- DOMList.prototype.append = function (selector, context) {
- let p = this.nodes[0];
- if (p) {
- let c = DOMListFactory(selector, context);
- let n = c.nodes.length;
- for (let i=0; i<n; ++i) {
- p.appendChild(c.nodes[i]);
- }
- }
- return this;
- };
- DOMList.prototype.prepend = function (selector, context) {
- let p = this.nodes[0];
- if (p) {
- let c = DOMListFactory(selector, context);
- let i = c.nodes.length;
- while (i--) {
- p.insertBefore(c.nodes[i], p.firstChild);
- }
- }
- return this;
- };
- DOMList.prototype.appendTo = function (selector, context) {
- let p = (selector instanceof DOMListFactory)
- ? selector
- : DOMListFactory(selector, context);
- let n = p.length;
- for (let i=0; i<n; ++i) {
- p.nodes[0].appendChild(this.nodes[i]);
- }
- return this;
- };
- DOMList.prototype.insertAfter = function (selector, context) {
- if (this.nodes.length === 0) {
- return this;
- }
- let p = this.nodes[0].parentNode;
- if (!p) {
- return this;
- }
- let c = DOMListFactory(selector, context);
- let n = c.nodes.length;
- for (let i=0; i<n; ++i) {
- p.appendChild(c.nodes[i]);
- }
- return this;
- };
- DOMList.prototype.insertBefore = function (selector, context) {
- if (this.nodes.length === 0) {
- return this;
- }
- let referenceNodes = DOMListFactory(selector, context);
- if (referenceNodes.nodes.length === 0) {
- return this;
- }
- let referenceNode = referenceNodes.nodes[0];
- let parentNode = referenceNode.parentNode;
- if (!parentNode) {
- return this;
- }
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- parentNode.insertBefore(this.nodes[i], referenceNode);
- }
- return this;
- };
- DOMList.prototype.clone = function (notDeep) {
- let r = new DOMList();
- let n = this.nodes.length;
- for (let i=0; i<n; ++i) {
- addNodeToList(r, this.nodes[i].cloneNode(!notDeep));
- }
- return r;
- };
- DOMList.prototype.nthOfType = function () {
- if (this.nodes.length === 0) {
- return 0;
- }
- let node = this.nodes[0];
- let tagName = node.tagName;
- let i = 1;
- while (node.previousElementSibling !== null) {
- node = node.previousElementSibling;
- if (typeof node.tagName !== 'string') {
- continue;
- }
- if (node.tagName !== tagName) {
- continue;
- }
- i++;
- }
- return i;
- };
- DOMList.prototype.attr = function (attr, value) {
- let i = this.nodes.length;
- if (value === undefined && typeof attr !== 'object') {
- return i ? this.nodes[0].getAttribute(attr) : undefined;
- }
- if (typeof attr === 'object') {
- let attrNames = Object.keys(attr);
- while (i--) {
- let node = this.nodes[i];
- let j = attrNames.length;
- while (j--) {
- node.setAttribute(attrNames[j], attr[attrName]);
- }
- }
- } else {
- while (i--) {
- this.nodes[i].setAttribute(attr, value);
- }
- }
- return this;
- };
- DOMList.prototype.prop = function (prop, value) {
- let i = this.nodes.length;
- if (value === undefined) {
- return i !== 0 ? this.nodes[0][prop] : undefined;
- }
- while (i--) {
- this.nodes[i][prop] = value;
- }
- return this;
- };
- DOMList.prototype.css = function (prop, value) {
- let i = this.nodes.length;
- if (value === undefined) {
- return i ? this.nodes[0].style[prop] : undefined;
- }
- if (value !== '') {
- while (i--) {
- this.nodes[i].style.setProperty(prop, value);
- }
- return this;
- }
- while (i--) {
- this.nodes[i].style.removeProperty(prop);
- }
- return this;
- };
- DOMList.prototype.val = function (value) {
- return this.prop('value', value);
- };
- DOMList.prototype.html = function (html) {
- let i = this.nodes.length;
- if (html === undefined) {
- return i ? this.nodes[0].innerHTML : '';
- }
- while (i--) {
- vAPI.insertHTML(this.nodes[i], html);
- }
- return this;
- };
- DOMList.prototype.text = function (text) {
- let i = this.nodes.length;
- if (text === undefined) {
- return i ? this.nodes[0].textContent : '';
- }
- while (i--) {
- this.nodes[i].textContent = text;
- }
- return this;
- };
- let toggleClass = function (node, className, targetState) {
- let tokenList = node.classList;
- if (tokenList instanceof DOMTokenList === false) {
- return;
- }
- let currentState = tokenList.contains(className);
- let newState = targetState;
- if (newState === undefined) {
- newState = !currentState;
- }
- if (newState === currentState) {
- return;
- }
- tokenList.toggle(className, newState);
- };
- DOMList.prototype.hasClass = function (className) {
- if (!this.nodes.length) {
- return false;
- }
- let tokenList = this.nodes[0].classList;
- return tokenList instanceof DOMTokenList
- && tokenList.contains(className);
- };
- DOMList.prototype.hasClassName = DOMList.prototype.hasClass;
- DOMList.prototype.addClass = function (className) {
- return this.toggleClass(className, true);
- };
- DOMList.prototype.removeClass = function (className) {
- if (className !== undefined) {
- return this.toggleClass(className, false);
- }
- let i = this.nodes.length;
- while (i--) {
- this.nodes[i].className = '';
- }
- return this;
- };
- DOMList.prototype.toggleClass = function (className, targetState) {
- if (className.indexOf(' ') !== -1) {
- return this.toggleClasses(className, targetState);
- }
- let i = this.nodes.length;
- while (i--) {
- toggleClass(this.nodes[i], className, targetState);
- }
- return this;
- };
- DOMList.prototype.toggleClasses = function (classNames, targetState) {
- let tokens = classNames.split(/\s+/);
- let i = this.nodes.length;
- while (i--) {
- let node = this.nodes[i];
- let j = tokens.length;
- while (j--) {
- toggleClass(node, tokens[j], targetState);
- }
- }
- return this;
- };
- let listenerEntries = [];
- let ListenerEntry = function (target, type, capture, callback) {
- this.target = target;
- this.type = type;
- this.capture = capture;
- this.callback = callback;
- target.addEventListener(type, callback, capture);
- };
- ListenerEntry.prototype.dispose = function () {
- this.target.removeEventListener(this.type, this.callback, this.capture);
- this.target = null;
- this.callback = null;
- };
- let makeEventHandler = function (selector, callback) {
- return function (event) {
- let dispatcher = event.currentTarget;
- if (!dispatcher
- || typeof dispatcher.querySelectorAll !== 'function') {
- return;
- }
- let receiver = event.target;
- if (nodeInNodeList(receiver, dispatcher.querySelectorAll(selector))) {
- callback.call(receiver, event);
- }
- };
- };
- DOMList.prototype.on = function (etype, selector, callback) {
- if (typeof selector === 'function') {
- callback = selector;
- selector = undefined;
- } else {
- callback = makeEventHandler(selector, callback);
- }
- let i = this.nodes.length;
- while (i--) {
- listenerEntries.push(new ListenerEntry(this.nodes[i],
- etype,
- selector !== undefined,
- callback));
- }
- return this;
- };
- // TODO: Won't work for delegated handlers. Need to figure
- // what needs to be done.
- DOMList.prototype.off = function (evtype, callback) {
- let i = this.nodes.length;
- while (i--) {
- this.nodes[i].removeEventListener(evtype, callback);
- }
- return this;
- };
- DOMList.prototype.trigger = function (etype) {
- let ev = new CustomEvent(etype);
- let i = this.nodes.length;
- while (i--) {
- this.nodes[i].dispatchEvent(ev);
- }
- return this;
- };
- // Cleanup
- let onBeforeUnload = function () {
- let entry;
- while ((entry = listenerEntries.pop())) {
- entry.dispose();
- }
- window.removeEventListener('beforeunload', onBeforeUnload);
- };
- window.addEventListener('beforeunload', onBeforeUnload);
- return DOMListFactory;
- })();
|