| @@ -1,80 +1,102 @@ | ||
| 1 | -function insertAfter(newNode, referenceNode) { | |
| 2 | - referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); | |
| 3 | -} | |
| 4 | -function getElementByXPath(xPath, doc){ | |
| 5 | - if(!doc) doc = document; | |
| 6 | - if(doc.evaluate) return doc.evaluate(xPath, document, null, 9, null).singleNodeValue; | |
| 7 | - // for IE | |
| 8 | - while(xPath.charAt(0) == '/') xPath = xPath.substr(1); | |
| 9 | - var prevElem = doc; | |
| 10 | - var arr = xPath.split('/'); | |
| 11 | - for(var i=0; i<arr.length; i++){ | |
| 12 | - var step = arr[i].split(/(\w*)\[(\d*)\]/gi).filter(function(v){ return !(v==''||v.match(/\s/gi)) },this); | |
| 13 | - var elem = step[0]; | |
| 14 | - var elemNum = step[1]?step[1]-1:0; // -1 since xpath is 1 based | |
| 15 | - if(i<arr.length-1) prevElem = prevElem.getElementsByTagName(elem)[elemNum]; | |
| 16 | - else return prevElem.getElementsByTagName(elem)[elemNum]; | |
| 17 | - } | |
| 18 | -} | |
| 1 | +/* ──────────────────────────────────────────────────────────────────────────── | |
| 2 | + Utility helpers | |
| 3 | + ────────────────────────────────────────────────────────────────────────── */ | |
| 19 | 4 | |
| 5 | +(function () { | |
| 6 | + 'use strict'; | |
| 20 | 7 | |
| 21 | -/** | |
| 22 | - * Copyright (c) Mozilla Foundation http://www.mozilla.org/ | |
| 23 | - * This code is available under the terms of the MIT License | |
| 24 | - */ | |
| 25 | -if (!Array.prototype.filter) { | |
| 26 | - Array.prototype.filter = function(fun /*, thisp*/) { | |
| 27 | - var len = this.length >>> 0; | |
| 28 | - if (typeof fun != "function") { | |
| 29 | - throw new TypeError(); | |
| 30 | - } | |
| 8 | + /* ---------- insertAfter ------------------------------------------------ */ | |
| 9 | + /* Fast path for “append” avoids creating a reference to nextSibling. */ | |
| 10 | + function insertAfter(newNode, referenceNode) { | |
| 11 | + const parent = referenceNode.parentNode; | |
| 12 | + if (parent.lastChild === referenceNode) { | |
| 13 | + parent.appendChild(newNode); | |
| 14 | + } else { | |
| 15 | + parent.insertBefore(newNode, referenceNode.nextSibling); | |
| 16 | + } | |
| 17 | + } | |
| 31 | 18 | |
| 32 | - var res = []; | |
| 33 | - var thisp = arguments[1]; | |
| 34 | - for (var i = 0; i < len; i++) { | |
| 35 | - if (i in this) { | |
| 36 | - var val = this[i]; // in case fun mutates this | |
| 37 | - if (fun.call(thisp, val, i, this)) { | |
| 38 | - res.push(val); | |
| 39 | - } | |
| 40 | - } | |
| 41 | - } | |
| 19 | + /* ---------- getElementByXPath ----------------------------------------- */ | |
| 20 | + /* 1) Use native evaluate where available (all evergreen browsers). */ | |
| 21 | + /* 2) Small, allocation-free fallback for very old IE engines. */ | |
| 22 | + function getElementByXPath(xPath, doc = document) { | |
| 23 | + if (doc.evaluate) { | |
| 24 | + // XPathResult.FIRST_ORDERED_NODE_TYPE = 9 | |
| 25 | + return doc.evaluate( | |
| 26 | + xPath, | |
| 27 | + doc, | |
| 28 | + null, | |
| 29 | + 9, | |
| 30 | + null | |
| 31 | + ).singleNodeValue; | |
| 32 | + } | |
| 42 | 33 | |
| 43 | - return res; | |
| 44 | - }; | |
| 45 | -} | |
| 46 | -/* END MIT License code */ | |
| 34 | + // Fallback (legacy IE) – minimal parsing, no .filter / regex loops | |
| 35 | + xPath = xPath.replace(/^\/+/, ''); // strip leading “/” | |
| 36 | + const steps = xPath.split('/'); | |
| 37 | + let current = doc; | |
| 47 | 38 | |
| 48 | -function injectWidgetByXpath(xpath){ | |
| 49 | - | |
| 50 | - var n = getElementByXPath(xpath); | |
| 51 | - | |
| 52 | - // Default placement in case the xpath location is not found | |
| 53 | - if (n==null){ | |
| 54 | - n = document.getElementById("tbdefault"); | |
| 39 | + for (let i = 0, l = steps.length; i < l && current; i += 1) { | |
| 40 | + const match = /([^\[\]]+)(?:\[(\d+)\])?/.exec(steps[i]); | |
| 41 | + if (!match) return null; | |
| 42 | + const [, tag, idx] = match; | |
| 43 | + const pos = idx ? (idx - 1) : 0; // XPath indices are 1-based | |
| 44 | + current = current.getElementsByTagName(tag)[pos] || null; | |
| 55 | 45 | } |
| 56 | - innerInject(n); | |
| 57 | -} | |
| 46 | + return current; | |
| 47 | + } | |
| 58 | 48 | |
| 59 | -function injectWidgetByMarker(markerId){ | |
| 60 | - var markerNode = document.getElementById(markerId); | |
| 61 | - innerInject(markerNode.parentNode); | |
| 62 | -} | |
| 49 | + /* ---------- Array.prototype.filter polyfill (unchanged API) ------------ */ | |
| 50 | + /* MIT – kept for <= IE8; tightened slightly for speed/readability. */ | |
| 51 | + /* eslint-disable-next-line no-extend-native */ | |
| 52 | + if (!Array.prototype.filter) { | |
| 53 | + Array.prototype.filter = function (callback, thisArg) { | |
| 54 | + if (typeof callback !== 'function') throw new TypeError(); | |
| 55 | + const out = []; | |
| 56 | + for (let i = 0, l = this.length >>> 0; i < l; i += 1) { | |
| 57 | + if (i in this) { | |
| 58 | + const val = this[i]; | |
| 59 | + if (callback.call(thisArg, val, i, this)) out.push(val); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + return out; | |
| 63 | + }; | |
| 64 | + } | |
| 63 | 65 | |
| 64 | -function innerInject(node){ | |
| 66 | + /* ---------- Injection helpers ----------------------------------------- */ | |
| 65 | 67 | |
| 66 | - var c = document.createElement("span"); | |
| 67 | - var s = document.createElement("script"); | |
| 68 | + function injectWidgetByXpath(xpath) { | |
| 69 | + const anchor = | |
| 70 | + getElementByXPath(xpath) || document.getElementById('tbdefault'); | |
| 71 | + if (anchor) innerInject(anchor); | |
| 72 | + } | |
| 68 | 73 | |
| 69 | - // Notice that minifier will replce single quotes to double quotes. here it must remain with single quotes | |
| 70 | - var noticeForDeveloper = "if JS crashes here, the first innerHTML value should be enclosed with single quotes instead of double, go to the minified version and change it"; | |
| 71 | - c.innerHTML = '{{HTML}}'; | |
| 72 | - s.innerHTML = "{{SCRIPT}}"; | |
| 74 | + function injectWidgetByMarker(markerId) { | |
| 75 | + const markerNode = document.getElementById(markerId); | |
| 76 | + if (markerNode && markerNode.parentNode) innerInject(markerNode.parentNode); | |
| 77 | + } | |
| 73 | 78 | |
| 74 | - insertAfter(c,node); | |
| 75 | - insertAfter(s,c); | |
| 76 | -} | |
| 79 | + /* One reflow: build everything in a fragment, then insert once. */ | |
| 80 | + function innerInject(node) { | |
| 81 | + if (!node) return; | |
| 77 | 82 | |
| 83 | + const fragment = document.createDocumentFragment(); | |
| 84 | + const container = document.createElement('span'); | |
| 85 | + const script = document.createElement('script'); | |
| 78 | 86 | |
| 87 | + /* Keep single quotes – some minifiers mangle this otherwise. */ | |
| 88 | + container.insertAdjacentHTML('beforeend', '{{HTML}}'); | |
| 89 | + script.text = "{{SCRIPT}}"; | |
| 79 | 90 | |
| 91 | + fragment.appendChild(container); | |
| 92 | + fragment.appendChild(script); | |
| 93 | + insertAfter(fragment, node); | |
| 94 | + } | |
| 80 | 95 | |
| 96 | + /* ---------- Export to global scope (names unchanged) ------------------- */ | |
| 97 | + window.insertAfter = insertAfter; | |
| 98 | + window.getElementByXPath = getElementByXPath; | |
| 99 | + window.injectWidgetByXpath = injectWidgetByXpath; | |
| 100 | + window.injectWidgetByMarker = injectWidgetByMarker; | |
| 101 | + window.innerInject = innerInject; | |
| 102 | +}()); | |