/* * Copyright (C) 2007-2025 Diego Perini * All rights reserved. * * nwsapi.js - Fast CSS Selectors API Engine * * Author: Diego Perini * Version: 2.2.20 * Created: 20070722 * Release: 20250322 * * License: * https://javascript.nwbox.com/nwsapi/MIT-LICENSE * Download: * https://javascript.nwbox.com/nwsapi/nwsapi.js */ (function Export(global, factory) { 'use strict'; if (typeof module == 'object' && typeof exports == 'object') { module.exports = factory; } else if (typeof define == 'function' && define['amd']) { define(factory); } else { global.NW || (global.NW = { }); global.NW.Dom = factory(global, Export); } })(this, function Factory(global, Export) { /** * Generate a regex that matches a balanced set of parentheses. * Outermost parentheses are excluded so any amount of children can be handled. * See https://stackoverflow.com/a/35271017 for reference * * @param {number} depth * @return {string} */ function createMatchingParensRegex(depth) { var out = ('\\([^)(]*?(?:'.repeat(depth) + '\\([^)(]*?\\)' + '[^)(]*?)*?\\)'.repeat(depth)); // remove outermost escaped parens return out.slice(2, out.length - 2); } var version = 'nwsapi-2.2.20', doc = global.document, root = doc.documentElement, slice = Array.prototype.slice, HSP = '[\\x20\\t]', VSP = '[\\r\\n\\f]', WSP = '[\\x20\\t\\r\\n\\f]', CFG = { // extensions operators: '[~*^$|]=|=', combinators: '[\\x20\\t>+~](?=[^>+~])' }, NOT = { // not enclosed in double/single/parens/square double_enc: '(?=(?:[^"]*["][^"]*["])*[^"]*$)', single_enc: "(?=(?:[^']*['][^']*['])*[^']*$)", parens_enc: '(?![^\\x28]*\\x29)', square_enc: '(?![^\\x5b]*\\x5d)' }, REX = { // regular expressions HasEscapes: RegExp('\\\\'), HexNumbers: RegExp('^[0-9a-fA-F]'), EscOrQuote: RegExp('^\\\\|[\\x22\\x27]'), RegExpChar: RegExp('(?!\\\\)[\\\\^$.,*+?()[\\]{}|\\/]', 'g'), TrimSpaces: RegExp('^' + WSP + '+|' + WSP + '+$|' + VSP, 'g'), SplitGroup: RegExp('(\\([^)]*\\)|\\[[^[]*\\]|\\\\.|[^,])+', 'g'), CommaGroup: RegExp('(\\s*,\\s*)' + NOT.square_enc + NOT.parens_enc, 'g'), FixEscapes: RegExp('\\\\([0-9a-fA-F]{1,6}' + WSP + '?|.)|([\\x22\\x27])', 'g'), CombineWSP: RegExp('[\\n\\r\\f\\x20]+' + NOT.single_enc + NOT.double_enc, 'g'), TabCharWSP: RegExp('(\\x20?\\t+\\x20?)' + NOT.single_enc + NOT.double_enc, 'g'), PseudosWSP: RegExp('\\s+([-+])\\s+' + NOT.square_enc, 'g') }, STD = { combinator: RegExp('\\s?([>+~])\\s?', 'g'), apimethods: RegExp('^(?:\\w+|\\*)\\|'), namespaces: RegExp('(\\*|\\w+)\\|[\\w-]+') }, GROUPS = { // pseudo-classes requiring parameters linguistic: '(dir|lang)(?:\\x28\\s?([-\\w]{2,})\\s?\\x29)', logicalsel: '(is|where|matches|not|has)(?:\\x28\\s?(' + createMatchingParensRegex(3) + ')\\s?\\x29)', treestruct: '(nth(?:-last)?(?:-child|-of\\-type))(?:\\x28\\s?(even|odd|(?:[-+]?\\d*)(?:n\\s?[-+]?\\s?\\d*)?)\\s?\\x29)', // pseudo-classes not requiring parameters locationpc: '(any\\-link|link|visited|target)\\b', useraction: '(hover|active|focus\\-within|focus\\-visible|focus)\\b', structural: '(scope|root|empty|(?:(?:first|last|only)(?:-child|\\-of\\-type)))\\b', inputstate: '(enabled|disabled|read\\-only|read\\-write|placeholder\\-shown|default)\\b', inputvalue: '(checked|indeterminate|required|optional|valid|invalid|in\\-range|out\\-of\\-range)\\b', // pseudo-classes not requiring parameters and describing functional state rsrc_state: '(playing|paused|seeking|buffering|stalled|muted|volume-locked)\\b', disp_state: '(open|closed|modal|fullscreen|picture-in-picture)\\b', time_state: '(current|past|future)\\b', // pseudo-classes for parsing only selectors pseudo_nop: '(autofill|-webkit\\-autofill)\\b', // pseudo-elements starting with single colon (:) pseudo_sng: '(after|before|first\\-letter|first\\-line)\\b', // pseudo-elements starting with double colon (::) pseudo_dbl: ':(after|before|first\\-letter|first\\-line|selection|placeholder|-webkit-[-a-zA-Z0-9]{2,})\\b' }, Patterns = { // pseudo-classes treestruct: RegExp('^:(?:' + GROUPS.treestruct + ')(.*)', 'i'), structural: RegExp('^:(?:' + GROUPS.structural + ')(.*)', 'i'), linguistic: RegExp('^:(?:' + GROUPS.linguistic + ')(.*)', 'i'), useraction: RegExp('^:(?:' + GROUPS.useraction + ')(.*)', 'i'), inputstate: RegExp('^:(?:' + GROUPS.inputstate + ')(.*)', 'i'), inputvalue: RegExp('^:(?:' + GROUPS.inputvalue + ')(.*)', 'i'), rsrc_state: RegExp('^:(?:' + GROUPS.rsrc_state + ')(.*)', 'i'), disp_state: RegExp('^:(?:' + GROUPS.disp_state + ')(.*)', 'i'), time_state: RegExp('^:(?:' + GROUPS.time_state + ')(.*)', 'i'), locationpc: RegExp('^:(?:' + GROUPS.locationpc + ')(.*)', 'i'), logicalsel: RegExp('^:(?:' + GROUPS.logicalsel + ')(.*)', 'i'), pseudo_nop: RegExp('^:(?:' + GROUPS.pseudo_nop + ')(.*)', 'i'), pseudo_sng: RegExp('^:(?:' + GROUPS.pseudo_sng + ')(.*)', 'i'), pseudo_dbl: RegExp('^:(?:' + GROUPS.pseudo_dbl + ')(.*)', 'i'), // combinator symbols children: RegExp('^' + WSP + '?\\>' + WSP + '?(.*)'), adjacent: RegExp('^' + WSP + '?\\+' + WSP + '?(.*)'), relative: RegExp('^' + WSP + '?\\~' + WSP + '?(.*)'), ancestor: RegExp('^' + WSP + '+(.*)'), // universal & namespace universal: RegExp('^(\\*)(.*)'), namespace: RegExp('^(\\*|[\\w-]+)?\\|(.*)') }, // regexp to better aproximate detection of RTL languages (Arabic) RTL = RegExp('^(?:[\\u0627-\\u064a]|[\\u0591-\\u08ff]|[\\ufb1d-\\ufdfd]|[\\ufe70-\\ufefc])+$'), // emulate firefox error strings qsNotArgs = 'Not enough arguments', qsInvalid = ' is not a valid selector', // detect structural pseudo-classes in selectors reNthElem = RegExp('(:nth(?:-last)?-child)', 'i'), reNthType = RegExp('(:nth(?:-last)?-of-type)', 'i'), // placeholder for global regexp reOptimizer, reValidator, // special handling configuration flags Config = { IDS_DUPES: true, ANODELIST: false, LOGERRORS: true, USR_EVENT: true, VERBOSITY: true }, NAMESPACE, QUIRKS_MODE, HTML_DOCUMENT, ATTR_STD_OPS = { '=': 1, '^=': 1, '$=': 1, '|=': 1, '*=': 1, '~=': 1 }, HTML_TABLE = { 'accept': 1, 'accept-charset': 1, 'align': 1, 'alink': 1, 'axis': 1, 'bgcolor': 1, 'charset': 1, 'checked': 1, 'clear': 1, 'codetype': 1, 'color': 1, 'compact': 1, 'declare': 1, 'defer': 1, 'dir': 1, 'direction': 1, 'disabled': 1, 'enctype': 1, 'face': 1, 'frame': 1, 'hreflang': 1, 'http-equiv': 1, 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'method': 1, 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, 'readonly': 1, 'rel': 1, 'rev': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, 'shape': 1, 'target': 1, 'text': 1, 'type': 1, 'valign': 1, 'valuetype': 1, 'vlink': 1 }, Combinators = { }, Selectors = { }, Operators = { '=': { p1: '^', p2: '$', p3: 'true' }, '^=': { p1: '^', p2: '', p3: 'true' }, '$=': { p1: '', p2: '$', p3: 'true' }, '*=': { p1: '', p2: '', p3: 'true' }, '|=': { p1: '^', p2: '(-|$)', p3: 'true' }, '~=': { p1: '(^|\\s)', p2: '(\\s|$)', p3: 'true' } }, concatCall = function(nodes, callback) { var i = 0, l = nodes.length, list = Array(l); while (l > i) { if (false === callback(list[i] = nodes[i])) break; ++i; } return list; }, concatList = function(list, nodes) { var i = -1, l = nodes.length; while (l--) { list[list.length] = nodes[++i]; } return list; }, // only define the toNodeList helper if explicitly enabled in Config, // a safety measure for headless hosts missing feature/implementation toNodeList = Config.ANODELIST == false ? function(x) { return x; } : function() { // create a DocumentFragment var emptyNL = doc.createDocumentFragment().childNodes; // this is returned from a self-executing function so that // the DocumentFragment isn't repeatedly created return function(nodeArray) { // check if it is already a nodelist if (nodeArray instanceof global.NodeList) return nodeArray; // if it's a single element, wrap it in a classic array if (!Array.isArray(nodeArray)) nodeArray = [nodeArray]; // base an object on emptyNL var fakeNL = Object.create(emptyNL, { 'length': { value: nodeArray.length, enumerable: false }, 'item': { 'value': function(i) { return this[+i || 0]; }, enumerable: false } }); // copy the array elemnts nodeArray.forEach(function (v, i) { fakeNL[i] = v; }); // return an object pretending to be a NodeList. return fakeNL; }; }(), documentOrder = function(a, b) { if (!hasDupes && a === b) { hasDupes = true; return 0; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }, hasDupes = false, unique = function(nodes) { var i = 0, j = -1, l = nodes.length + 1, list = [ ]; while (--l) { if (nodes[i++] === nodes[i]) continue; list[++j] = nodes[i - 1]; } hasDupes = false; return list; }, switchContext = function(context, force) { var oldDoc = doc; doc = context.ownerDocument || context; if (force || oldDoc !== doc) { // force a new check for each document change // performed before the next select operation root = doc.documentElement; HTML_DOCUMENT = isHTML(doc); QUIRKS_MODE = HTML_DOCUMENT && doc.compatMode.indexOf('CSS') < 0; NAMESPACE = root && root.namespaceURI; Snapshot.doc = doc; Snapshot.root = root; } return (Snapshot.from = context); }, // convert single codepoint to UTF-16 encoding codePointToUTF16 = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\\ufffd'; } // javascript strings are UTF-16 encoded if (codePoint < 0x10000) { var lowHex = '000' + codePoint.toString(16); return '\\u' + lowHex.substr(lowHex.length - 4); } // supplementary high + low surrogates return '\\u' + (((codePoint - 0x10000) >> 0x0a) + 0xd800).toString(16) + '\\u' + (((codePoint - 0x10000) % 0x400) + 0xdc00).toString(16); }, // convert single codepoint to string stringFromCodePoint = function(codePoint) { // out of range, use replacement character if (codePoint < 1 || codePoint > 0x10ffff || (codePoint > 0xd7ff && codePoint < 0xe000)) { return '\ufffd'; } if (codePoint < 0x10000) { return String.fromCharCode(codePoint); } return String.fromCodePoint ? String.fromCodePoint(codePoint) : String.fromCharCode( ((codePoint - 0x10000) >> 0x0a) + 0xd800, ((codePoint - 0x10000) % 0x400) + 0xdc00); }, // convert escape sequence in a CSS string or identifier // to javascript string with javascript escape sequences convertEscapes = function(str) { return REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? '\\' + p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? codePointToUTF16(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, // convert escape sequence in a CSS string or identifier // to javascript string with characters representations unescapeIdentifier = function(str) { return REX.HasEscapes.test(str) ? str.replace(REX.FixEscapes, function(substring, p1, p2) { // unescaped " or ' return p2 ? p2 : // javascript strings are UTF-16 encoded REX.HexNumbers.test(p1) ? stringFromCodePoint(parseInt(p1, 16)) : // \' \" REX.EscOrQuote.test(p1) ? substring : // \g \h \. \# etc p1; } ) : str; }, method = { '#': 'getElementById', '*': 'getElementsByTagName', '|': 'getElementsByTagNameNS', '.': 'getElementsByClassName' }, compat = { '#': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byId(n, c); }; }, '*': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byTag(n, c); }; }, '|': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byTagNS(n, c); }; }, '.': function(c, n) { REX.HasEscapes.test(n) && (n = unescapeIdentifier(n)); return function(e, f) { return byClass(n, c); }; } }, // find duplicate ids using iterative walk byIdRaw = function(id, context) { var node = context, nodes = [ ], next = node.firstElementChild; while ((node = next)) { node.id == id && (nodes[nodes.length] = node); if ((next = node.firstElementChild || node.nextElementSibling)) continue; while (!next && (node = node.parentElement) && node !== context) { next = node.nextElementSibling; } } return nodes; }, // context agnostic getElementById byId = function(id, context) { var e, i, l, nodes, api = method['#']; // duplicates id allowed if (Config.IDS_DUPES === false) { if (api in context) { return (e = context[api](id)) ? [ e ] : none; } } else { if ('all' in context) { if ((e = context.all[id])) { if (e.nodeType == 1) return e.getAttribute('id') != id ? [ ] : [ e ]; else if (id == 'length') return (e = context[api](id)) ? [ e ] : none; for (i = 0, l = e.length, nodes = [ ]; l > i; ++i) { if (e[i].id == id) nodes[nodes.length] = e[i]; } return nodes && nodes.length ? nodes : [ nodes ]; } else return none; } } return byIdRaw(id, context); }, // wrapped up namespaced TagName api calls byTagNS = function(context, tag) { return byTag(tag, context); }, // context agnostic getElementsByTagName byTag = function(tag, context) { var e, nodes, api = method['*']; // DOCUMENT_NODE (9) & ELEMENT_NODE (1) if (api in context) { return slice.call(context[api](tag)); } else { tag = tag.toLowerCase(); // DOCUMENT_FRAGMENT_NODE (11) if ((e = context.firstElementChild)) { if (!(e.nextElementSibling || tag == '*' || e.localName == tag)) { return slice.call(e[api](tag)); } else { nodes = [ ]; do { if (tag == '*' || e.localName == tag) nodes[nodes.length] = e; concatList(nodes, e[api](tag)); } while ((e = e.nextElementSibling)); } } else nodes = none; } return !Config.ANODELIST ? nodes : nodes instanceof global.NodeList ? nodes : toNodeList(nodes); }, // context agnostic getElementsByClassName byClass = function(cls, context) { var e, nodes, api = method['.'], reCls; // DOCUMENT_NODE (9) & ELEMENT_NODE (1) if (api in context) { return slice.call(context[api](cls)); } else { // DOCUMENT_FRAGMENT_NODE (11) if ((e = context.firstElementChild)) { reCls = RegExp('(^|\\s)' + cls + '(\\s|$)', QUIRKS_MODE ? 'i' : ''); if (!(e.nextElementSibling || reCls.test(e.className))) { return slice.call(e[api](cls)); } else { nodes = [ ]; do { if (reCls.test(e.className)) nodes[nodes.length] = e; concatList(nodes, e[api](cls)); } while ((e = e.nextElementSibling)); } } else nodes = none; } return !Config.ANODELIST ? nodes : nodes instanceof global.NodeList ? nodes : toNodeList(nodes); }, // namespace aware hasAttribute // helper for XML/XHTML documents hasAttributeNS = function(e, name) { var i, l, attr = e.getAttributeNames(); name = RegExp(':?' + name + '$', HTML_DOCUMENT ? 'i' : ''); for (i = 0, l = attr.length; l > i; ++i) { if (name.test(attr[i])) return true; } return false; }, // fast resolver for the :nth-child() and :nth-last-child() pseudo-classes nthElement = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l; if (parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0) { parents[i = l] = parent; l = 0; nodes[i] = Array(); e = parent && parent.firstElementChild || element; while (e) { nodes[i][l] = e; if (e === element) j = l; e = e.nextElementSibling; ++l; } set = i; idx = 0; len = l; if (l < 2) return l; } else { l = nodes[i].length; set = i; } } if (element !== nodes[i][j] && element !== nodes[i][j = 0]) { for (j = 0, e = nodes[i], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // fast resolver for the :nth-of-type() and :nth-last-of-type() pseudo-classes nthOfType = (function() { var idx = 0, len = 0, set = 0, parent = undefined, parents = Array(), nodes = Array(); return function(element, dir) { // ensure caches are emptied after each run, invoking with dir = 2 if (dir == 2) { idx = 0; len = 0; set = 0; nodes.length = 0; parents.length = 0; parent = undefined; return -1; } var e, i, j, k, l, name = element.localName; if (nodes[set] && nodes[set][name] && parent === element.parentElement) { i = set; j = idx; l = len; } else { l = parents.length; parent = element.parentElement; for (i = -1, j = 0, k = l - 1; l > j; ++j, --k) { if (parents[j] === parent) { i = j; break; } if (parents[k] === parent) { i = k; break; } } if (i < 0 || !nodes[i][name]) { parents[i = l] = parent; nodes[i] || (nodes[i] = Object()); l = 0; nodes[i][name] = Array(); e = parent && parent.firstElementChild || element; while (e) { if (e === element) j = l; if (e.localName == name) { nodes[i][name][l] = e; ++l; } e = e.nextElementSibling; } set = i; idx = j; len = l; if (l < 2) return l; } else { l = nodes[i][name].length; set = i; } } if (element !== nodes[i][name][j] && element !== nodes[i][name][j = 0]) { for (j = 0, e = nodes[i][name], k = l - 1; l > j; ++j, --k) { if (e[j] === element) { break; } if (e[k] === element) { j = k; break; } } } idx = j + 1; len = l; return dir ? l - j : idx; }; })(), // check if the document type is HTML isHTML = function(node) { var doc = node.ownerDocument || node; return doc.nodeType == 9 && // contentType not in IE <= 11 'contentType' in doc ? doc.contentType.indexOf('/html') > 0 : doc.createElement('DiV').localName == 'div'; }, // return node if node is focusable // or false if node isn't focusable isFocusable = function(node) { var doc = node.ownerDocument; if (node.contentDocument&&node.localName== 'iframe') { return false; } if (doc.hasFocus() && node === doc.activeElement) { if (node.type || node.href || typeof node.tabIndex == 'number') { return node; } } return false; }, // check if node content is editable isContentEditable = function(node) { var attrValue = 'inherit'; if (node.hasAttribute('contenteditable')) { attrValue = node.getAttribute('contenteditable'); } switch (attrValue) { case '': case 'plaintext-only': case 'true': return true; case 'false': return false; default: if (node.parentNode && node.parentNode.nodeType === 1) { return isContentEditable(node.parentNode); } return false; } }, // check media resources is playing isPlaying = function(media) { // for