mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-07-09 20:09:36 +02:00
13744 lines
395 KiB
JavaScript
13744 lines
395 KiB
JavaScript
/*
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
|
|
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/>.
|
|
*/
|
|
const CHANGED = Symbol("changed");
|
|
|
|
// used in Element to setup once classList
|
|
const CLASS_LIST = Symbol("classList");
|
|
|
|
// used in Document to attach once customElements
|
|
const CUSTOM_ELEMENTS = Symbol("CustomElements");
|
|
|
|
// used in HTMLTemplateElement
|
|
const CONTENT = Symbol("content");
|
|
|
|
// used in Element for data attributes
|
|
const DATASET = Symbol("dataset");
|
|
|
|
// used in Document to attach the DocType
|
|
const DOCTYPE = Symbol("doctype");
|
|
|
|
// used in parser and Document to attach once a DOMParser
|
|
const DOM_PARSER = Symbol("DOMParser");
|
|
|
|
// used to reference an end node
|
|
const END = Symbol("end");
|
|
|
|
// used in Document to make the globalThis an event target
|
|
const EVENT_TARGET = Symbol("EventTarget");
|
|
|
|
// used to augment a created document defaultView
|
|
const GLOBALS = Symbol("globals");
|
|
|
|
// used in both Canvas and Document to provide images
|
|
const IMAGE = Symbol("image");
|
|
|
|
// used to define Document mime type
|
|
const MIME = Symbol("mime");
|
|
|
|
// used in Document to attach once MutationObserver
|
|
const MUTATION_OBSERVER = Symbol("MutationObserver");
|
|
|
|
// used to define next node reference
|
|
const NEXT = Symbol("next");
|
|
|
|
// used to define Attr owner elements
|
|
const OWNER_ELEMENT = Symbol("ownerElement");
|
|
|
|
// used to define previous node reference
|
|
const PREV = Symbol("prev");
|
|
|
|
// used to define various "private" properties
|
|
const PRIVATE = Symbol("private");
|
|
|
|
// used to define the CSSStyleSheet.sheet
|
|
const SHEET = Symbol("sheet");
|
|
|
|
// used to define start node reference
|
|
const START = Symbol("start");
|
|
|
|
// used to define special CSS style attribute
|
|
const STYLE = Symbol("style");
|
|
|
|
// used to upgrade Custom Elements
|
|
const UPGRADE = Symbol("upgrade");
|
|
|
|
// used to define generic values
|
|
const VALUE = Symbol("value");
|
|
|
|
// Adapted from https://github.com/mathiasbynens/he/blob/36afe179392226cf1b6ccdb16ebbb7a5a844d93a/src/he.js#L106-L134
|
|
var _a;
|
|
const decodeMap = new Map([
|
|
[0, 65533],
|
|
// C1 Unicode control character reference replacements
|
|
[128, 8364],
|
|
[130, 8218],
|
|
[131, 402],
|
|
[132, 8222],
|
|
[133, 8230],
|
|
[134, 8224],
|
|
[135, 8225],
|
|
[136, 710],
|
|
[137, 8240],
|
|
[138, 352],
|
|
[139, 8249],
|
|
[140, 338],
|
|
[142, 381],
|
|
[145, 8216],
|
|
[146, 8217],
|
|
[147, 8220],
|
|
[148, 8221],
|
|
[149, 8226],
|
|
[150, 8211],
|
|
[151, 8212],
|
|
[152, 732],
|
|
[153, 8482],
|
|
[154, 353],
|
|
[155, 8250],
|
|
[156, 339],
|
|
[158, 382],
|
|
[159, 376]
|
|
]);
|
|
/**
|
|
* Polyfill for `String.fromCodePoint`. It is used to create a string from a Unicode code point.
|
|
*/
|
|
const fromCodePoint =
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, n/no-unsupported-features/es-builtins
|
|
(_a = String.fromCodePoint) !== null && _a !== void 0
|
|
? _a
|
|
: (codePoint) => {
|
|
let output = "";
|
|
if (codePoint > 65535) {
|
|
codePoint -= 65536;
|
|
output += String.fromCharCode(((codePoint >>> 10) & 1023) | 55296);
|
|
codePoint = 56320 | (codePoint & 1023);
|
|
}
|
|
output += String.fromCharCode(codePoint);
|
|
return output;
|
|
};
|
|
/**
|
|
* Replace the given code point with a replacement character if it is a
|
|
* surrogate or is outside the valid range. Otherwise return the code
|
|
* point unchanged.
|
|
*/
|
|
function replaceCodePoint(codePoint) {
|
|
var _a;
|
|
if ((codePoint >= 55296 && codePoint <= 57343) || codePoint > 1114111) {
|
|
return 65533;
|
|
}
|
|
return (_a = decodeMap.get(codePoint)) !== null && _a !== void 0
|
|
? _a
|
|
: codePoint;
|
|
}
|
|
|
|
/*
|
|
* Shared base64 decode helper for generated decode data.
|
|
* Assumes global atob is available.
|
|
*/
|
|
function decodeBase64(input) {
|
|
const binary =
|
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
typeof atob === "function"
|
|
? // Browser (and Node >=16)
|
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
atob(input)
|
|
: // Older Node versions (<16)
|
|
// eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
typeof Buffer.from === "function"
|
|
? // eslint-disable-next-line n/no-unsupported-features/node-builtins
|
|
Buffer.from(input, "base64").toString("binary")
|
|
: // eslint-disable-next-line unicorn/no-new-buffer, n/no-deprecated-api
|
|
new Buffer(input, "base64").toString("binary");
|
|
const evenLength = binary.length & -2; // Round down to even length
|
|
const out = new Uint16Array(evenLength / 2);
|
|
for (let index = 0, outIndex = 0; index < evenLength; index += 2) {
|
|
const lo = binary.charCodeAt(index);
|
|
const hi = binary.charCodeAt(index + 1);
|
|
out[outIndex++] = lo | (hi << 8);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Generated using scripts/write-decode-map.ts
|
|
const htmlDecodeTree = /* #__PURE__ */ decodeBase64(
|
|
"QR08ALkAAgH6AYsDNQR2BO0EPgXZBQEGLAbdBxMISQrvCmQLfQurDKQNLw4fD4YPpA+6D/IPAAAAAAAAAAAAAAAAKhBMEY8TmxUWF2EYLBkxGuAa3RsJHDscWR8YIC8jSCSIJcMl6ie3Ku8rEC0CLjoupS7kLgAIRU1hYmNmZ2xtbm9wcnN0dVQAWgBeAGUAaQBzAHcAfgCBAIQAhwCSAJoAoACsALMAbABpAGcAO4DGAMZAUAA7gCYAJkBjAHUAdABlADuAwQDBQHIiZXZlAAJhAAFpeW0AcgByAGMAO4DCAMJAEGRyAADgNdgE3XIAYQB2AGUAO4DAAMBA8CFoYZFj4SFjcgBhZAAAoFMqAAFncIsAjgBvAG4ABGFmAADgNdg43fAlbHlGdW5jdGlvbgCgYSBpAG4AZwA7gMUAxUAAAWNzpACoAHIAAOA12Jzc6SFnbgCgVCJpAGwAZABlADuAwwDDQG0AbAA7gMQAxEAABGFjZWZvcnN1xQDYANoA7QDxAPYA+QD8AAABY3LJAM8AayNzbGFzaAAAoBYidgHTANUAAKDnKmUAZAAAoAYjeQARZIABY3J0AOAA5QDrAGEidXNlAACgNSLuI291bGxpcwCgLCFhAJJjcgAA4DXYBd1wAGYAAOA12Dnd5SF2ZdhiYwDyAOoAbSJwZXEAAKBOIgAHSE9hY2RlZmhpbG9yc3UXARoBHwE6AVIBVQFiAWQBZgGCAakB6QHtAfIBYwB5ACdkUABZADuAqQCpQIABY3B5ACUBKAE1AfUhdGUGYWmg0iJ0KGFsRGlmZmVyZW50aWFsRAAAoEUhbCJleXMAAKAtIQACYWVpb0EBRAFKAU0B8iFvbgxhZABpAGwAO4DHAMdAcgBjAAhhbiJpbnQAAKAwIm8AdAAKYQABZG5ZAV0BaSJsbGEAuGB0I2VyRG90ALdg8gA5AWkAp2NyImNsZQAAAkRNUFRwAXQBeQF9AW8AdAAAoJkiaSJudXMAAKCWIuwhdXMAoJUiaSJtZXMAAKCXIm8AAAFjc4cBlAFrKndpc2VDb250b3VySW50ZWdyYWwAAKAyImUjQ3VybHkAAAFEUZwBpAFvJXVibGVRdW90ZQAAoB0gdSJvdGUAAKAZIAACbG5wdbABtgHNAdgBbwBuAGWgNyIAoHQqgAFnaXQAvAHBAcUB8iJ1ZW50AKBhIm4AdAAAoC8i7yV1ckludGVncmFsAKAuIgABZnLRAdMBAKACIe8iZHVjdACgECJuLnRlckNsb2Nrd2lzZUNvbnRvdXJJbnRlZ3JhbAAAoDMi7yFzcwCgLypjAHIAAOA12J7ccABDoNMiYQBwAACgTSKABURKU1phY2VmaW9zAAsCEgIVAhgCGwIsAjQCOQI9AnMCfwNvoEUh9CJyYWhkAKARKWMAeQACZGMAeQAFZGMAeQAPZIABZ3JzACECJQIoAuchZXIAoCEgcgAAoKEhaAB2AACg5CoAAWF5MAIzAvIhb24OYRRkbAB0oAciYQCUY3IAAOA12AfdAAFhZkECawIAAWNtRQJnAvIjaXRpY2FsAAJBREdUUAJUAl8CYwJjInV0ZQC0YG8AdAFZAloC2WJiJGxlQWN1dGUA3WJyImF2ZQBgYGkibGRlANxi7yFuZACgxCJmJWVyZW50aWFsRAAAoEYhcAR9AgAAAAAAAIECjgIAABoDZgAA4DXYO91EoagAhQKJAm8AdAAAoNwgcSJ1YWwAAKBQIuIhbGUAA0NETFJVVpkCqAK1Au8C/wIRA28AbgB0AG8AdQByAEkAbgB0AGUAZwByAGEA7ADEAW8AdAKvAgAAAACwAqhgbiNBcnJvdwAAoNMhAAFlb7kC0AJmAHQAgAFBUlQAwQLGAs0CciJyb3cAAKDQIekkZ2h0QXJyb3cAoNQhZQDlACsCbgBnAAABTFLWAugC5SFmdAABQVLcAuECciJyb3cAAKD4J+kkZ2h0QXJyb3cAoPon6SRnaHRBcnJvdwCg+SdpImdodAAAAUFU9gL7AnIicm93AACg0iFlAGUAAKCoInAAQQIGAwAAAAALA3Iicm93AACg0SFvJHduQXJyb3cAAKDVIWUlcnRpY2FsQmFyAACgJSJuAAADQUJMUlRhJAM2AzoDWgNxA3oDciJyb3cAAKGTIUJVLAMwA2EAcgAAoBMpcCNBcnJvdwAAoPUhciJldmUAEWPlIWZ00gJDAwAASwMAAFIDaSVnaHRWZWN0b3IAAKBQKWUkZVZlY3RvcgAAoF4p5SJjdG9yQqC9IWEAcgAAoFYpaSJnaHQA1AFiAwAAaQNlJGVWZWN0b3IAAKBfKeUiY3RvckKgwSFhAHIAAKBXKWUAZQBBoKQiciJyb3cAAKCnIXIAcgBvAPcAtAIAAWN0gwOHA3IA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YwAAoN8i4SFycnCgtiEAoD0pgKIqImJjZG9zACsVMBU6FT4VQRVyImNhcAAAoEgqAAFhdTQVNxVwAACgRipwAACgSipvAHQAAKCNInIAAKBFKgDgKiIA/gACYWxydksVURVuFXMVcgByAG2gtyEAoDwpeQCAAWV2dwBYFWUVaRVxAHACXxUAAAAAYxVyAGUA4wAXFXUA4wAZFWUAZQAAoM4iZSJkZ2UAAKDPImUAbgA7gKQApEBlI2Fycm93AAABbHJ7FX8V5SFmdACgtiFpImdodAAAoLchZQDkAG0VAAFjaYsVkRVvAG4AaQBuAPQAkwFuAHQAAKAxImwiY3R5AACgLSOACUFIYWJjZGVmaGlqbG9yc3R1d3oAuBW7Fb8V1RXgFegV+RUKFhUWHxZUFlcWZRbFFtsW7xb7FgUXChdyAPIAtAJhAHIAAKBlKQACZ2xyc8YVyhXOFdAV5yFlcgCgICDlIXRoAKA4IfIA9QxoAHagECAAoKMiawHZFd4VYSJyb3cAAKAPKWEA4wBfAgABYXnkFecV8iFvbg9hNGQAoUYhYW/tFfQVAAFnciEC8RVyAACgyiF0InNlcQAAoHcqgAFnbG0A/xUCFgUWO4CwALBAdABhALRjcCJ0eXYAAKCxKQABaXIOFhIW8yFodACgfykA4DXYId1hAHIAAAFschsWHRYAoMMhAKDCIYACYWVnc3YAKBauAjYWOhY+Fm0AAKHEIm9zLhY0Fm4AZABzoMQi9SFpdACgZiZhIm1tYQDdY2kAbgAAoPIiAKH3AGlvQxZRFmQAZQAAgfcAO29KFksW90BuI3RpbWVzAACgxyJuAPgAUBZjAHkAUmRjAG8CXhYAAAAAYhZyAG4AAKAeI28AcAAAoA0jgAJscHR1dwBuFnEWdRaSFp4W7CFhciRgZgAA4DXYVd0AotkCZW1wc30WhBaJFo0WcQBkoFAibwB0AACgUSJpIm51cwAAoDgi7CF1cwCgFCLxInVhcmUAoKEiYgBsAGUAYgBhAHIAdwBlAGQAZwDlANcAbgCAAWFkaAClFqoWtBZyAHIAbwD3APUMbwB3AG4AYQByAHIAbwB3APMA8xVhI3Jwb29uAAABbHK8FsAWZQBmAPQAHBZpAGcAaAD0AB4WYgHJFs8WawBhAHIAbwD3AJILbwLUFgAAAADYFnIAbgAAoB8jbwBwAACgDCOAAWNvdADhFukW7BYAAXJ55RboFgDgNdi53FVkbAAAoPYp8iFvaxFhAAFkcvMW9xZvAHQAAKDxImkA5qC/JVsSAAFhaP8WAhdyAPIANQNhAPIA1wvhIm5nbGUAoKYpAAFjaQ4XEBd5AF9k5yJyYXJyAKD/JwAJRGFjZGVmZ2xtbm9wcXJzdHV4MRc4F0YXWxcyBF4XaRd5F40XrBe0F78X2RcVGCEYLRg1GEAYAAFEbzUXgRZvAPQA+BUAAWNzPBdCF3UAdABlADuA6QDpQPQhZXIAoG4qAAJhaW95TRdQF1YXWhfyIW9uG2FyAGOgViI7gOoA6kDsIW9uAKBVIk1kbwB0ABdhAAFEcmIXZhdvAHQAAKBSIgDgNdgi3XKhmipuF3QXYQB2AGUAO4DoAOhAZKCWKm8AdAAAoJgqgKGZKmlscwCAF4UXhxfuInRlcnMAoOcjAKATIWSglSpvAHQAAKCXKoABYXBzAJMXlheiF2MAcgATYXQAeQBzogUinxcAAAAAoRdlAHQAAKAFInAAMaADIDMBqRerFwCgBCAAoAUgAAFnc7AXsRdLYXAAAKACIAABZ3C4F7sXbwBuABlhZgAA4DXYVt2AAWFscwDFF8sXzxdyAHOg1SJsAACg4yl1AHMAAKBxKmkAAKG1A2x21RfYF28AbgC1Y/VjAAJjc3V24BfoF/0XEBgAAWlv5BdWF3IAYwAAoFYiaQLuFwAAAADwF+0ADQThIW50AAFnbPUX+Rd0AHIAAKCWKuUhc3MAoJUqgAFhZWkAAxgGGAoYbABzAD1gcwB0AACgXyJ2AESgYSJEAACgeCrwImFyc2wAoOUpAAFEYRkYHRhvAHQAAKBTInIAcgAAoHEpgAFjZGkAJxgqGO0XcgAAoC8hbwD0AIwCAAFhaDEYMhi3YzuA8ADwQAABbXI5GD0YbAA7gOsA60BvAACgrCCAAWNpcABGGEgYSxhsACFgcwD0ACwEAAFlb08YVxhjAHQAYQB0AGkAbwDuABoEbgBlAG4AdABpAGEAbADlADME4Ql1GAAAgRgAAIMYiBgAAAAAoRilGAAAqhgAALsYvhjRGAAA1xgnGWwAbABpAG4AZwBkAG8AdABzAGUA8QBlF3kARGRtImFsZQAAoEAmgAFpbHIAjRiRGJ0Y7CFpZwCgA/tpApcYAAAAAJoYZwAAoAD7aQBnAACgBPsA4DXYI93sIWlnAKAB++whaWcA4GYAagCAAWFsdACvGLIYthh0AACgbSZpAGcAAKAC+24AcwAAoLElbwBmAJJh8AHCGAAAxhhmAADgNdhX3QABYWvJGMwYbADsAGsEdqDUIgCg2SphI3J0aW50AACgDSoAAWFv2hgiGQABY3PeGB8ZsQPnGP0YBRkSGRUZAAAdGbID7xjyGPQY9xj5GAAA+xg7gL0AvUAAoFMhO4C8ALxAAKBVIQCgWSEAoFshswEBGQAAAxkAoFQhAKBWIbQCCxkOGQAAAAAQGTuAvgC+QACgVyEAoFwhNQAAoFghtgEZGQAAGxkAoFohAKBdITgAAKBeIWwAAKBEIHcAbgAAoCIjYwByAADgNdi73IAIRWFiY2RlZmdpamxub3JzdHYARhlKGVoZXhlmGWkZkhmWGZkZnRmgGa0ZxhnLGc8Z4BkjGmygZyIAoIwqgAFjbXAAUBlTGVgZ9SF0ZfVhbQBhAOSgswM6FgCghipyImV2ZQAfYQABaXliGWUZcgBjAB1hM2RvAHQAIWGAoWUibHFzAMYEcBl6GfGhZSLOBAAAdhlsAGEAbgD0AN8EgKF+KmNkbACBGYQZjBljAACgqSpvAHQAb6CAKmyggioAoIQqZeDbIgD+cwAAoJQqcgAA4DXYJN3noGsirATtIWVsAKA3IWMAeQBTZIChdyJFYWoApxmpGasZAKCSKgCgpSoAoKQqAAJFYWVztBm2Gb0ZwhkAoGkicABwoIoq8iFveACgiipxoIgq8aCIKrUZaQBtAACg5yJwAGYAAOA12FjdYQB2AOUAYwIAAWNp0xnWGXIAAKAKIW0AAKFzImVs3BneGQCgjioAoJAqAIM+ADtjZGxxco0E6xn0GfgZ/BkBGgABY2nvGfEZAKCnKnIAAKB6Km8AdAAAoNci0CFhcgCglSl1ImVzdAAAoHwqgAJhZGVscwAKGvQZFhrVBCAa8AEPGgAAFBpwAHIAbwD4AFkZcgAAoHgpcQAAAWxxxAQbGmwAZQBzAPMASRlpAO0A5AQAAWVuJxouGnIjdG5lcXEAAOBpIgD+xQAsGgAFQWFiY2Vma29zeUAaQxpmGmoabRqDGocalhrCGtMacgDyAMwCAAJpbG1yShpOGlAaVBpyAHMA8ABxD2YAvWBpAGwA9AASBQABZHJYGlsaYwB5AEpkAKGUIWN3YBpkGmkAcgAAoEgpAKCtIWEAcgAAoA8h6SFyYyVhgAFhbHIAcxp7Gn8a8iF0c3WgZSZpAHQAAKBlJuwhaXAAoCYg4yFvbgCguSJyAADgNdgl3XMAAAFld4wakRphInJvdwAAoCUpYSJyb3cAAKAmKYACYW1vcHIAnxqjGqcauhq+GnIAcgAAoP8h9CFodACgOyJrAAABbHKsGrMaZSRmdGFycm93AACgqSHpJGdodGFycm93AKCqIWYAAOA12Fnd4iFhcgCgFSCAAWNsdADIGswa0BpyAADgNdi93GEAcwDoAGka8iFvaydhAAFicNca2xr1IWxsAKBDIOghZW4AoBAg4Qr2GgAA/RoAAAgbExsaGwAAIRs7GwAAAAA+G2IbmRuVG6sbAACyG80b0htjAHUAdABlADuA7QDtQAChYyBpeQEbBhtyAGMAO4DuAO5AOGQAAWN4CxsNG3kANWRjAGwAO4ChAKFAAAFmcssCFhsA4DXYJt1yAGEAdgBlADuA7ADsQIChSCFpbm8AJxsyGzYbAAFpbisbLxtuAHQAAKAMKnQAAKAtIuYhaW4AoNwpdABhAACgKSHsIWlnM2GAAWFvcABDG1sbXhuAAWNndABJG0sbWRtyACthgAFlbHAAcQVRG1UbaQBuAOUAyAVhAHIA9AByBWgAMWFmAACgtyJlAGQAtWEAoggiY2ZvdGkbbRt1G3kb4SFyZQCgBSFpAG4AdKAeImkAZQAAoN0pZABvAPQAWxsAoisiY2VscIEbhRuPG5QbYQBsAACguiIAAWdyiRuNG2UAcgDzACMQ4wCCG2EicmhrAACgFyryIW9kAKA8KgACY2dwdJ8boRukG6gbeQBRZG8AbgAvYWYAAOA12FrdYQC5Y3UAZQBzAHQAO4C/AL9AAAFjabUbuRtyAADgNdi+3G4AAKIIIkVkc3bCG8QbyBvQAwCg+SJvAHQAAKD1Inag9CIAoPMiaaBiIOwhZGUpYesB1hsAANkbYwB5AFZkbAA7gO8A70AAA2NmbW9zdeYb7hvyG/Ub+hsFHAABaXnqG+0bcgBjADVhOWRyAADgNdgn3eEhdGg3YnAAZgAA4DXYW93jAf8bAAADHHIAAOA12L/c8iFjeVhk6yFjeVRkAARhY2ZnaGpvcxUcGhwiHCYcKhwtHDAcNRzwIXBhdqC6A/BjAAFleR4cIRzkIWls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ZmdobG1vcHJzdHV3CiwNLBEsHSwnLDEsQCxLLFIsYix6LIQsjyzLLOgs7Sz/LAotcgDyAAkDYQByAACgYykAAWNyFSwbLHUAdABlADuA+gD6QPIACQ1yAOMBIywAACUseQBeZHYAZQBtYQABaXkrLDAscgBjADuA+wD7QENkgAFhYmgANyw6LD0scgDyANEO7CFhY3FhYQDyAOAOAAFpckQsSCzzIWh0AKB+KQDgNdgy3XIAYQB2AGUAO4D5APlAYQFWLF8scgAAAWxyWixcLACgvyEAoL4hbABrAACggCUAAWN0Zix2LG8CbCwAAAAAcyxyAG4AZaAcI3IAAKAcI28AcAAAoA8jcgBpAACg+CUAAWFsfiyBLGMAcgBrYTuAqACoQAABZ3CILIssbwBuAHNhZgAA4DXYZt0AA2FkaGxzdZksniynLLgsuyzFLHIAcgBvAPcACQ1vAHcAbgBhAHIAcgBvAPcA2A5hI3Jwb29uAAABbHKvLLMsZQBmAPQAWyxpAGcAaAD0AF0sdQDzAKYOaQAAocUDaGzBLMIs0mNvAG4AxWPwI2Fycm93cwCgyCGAAWNpdADRLOEs5CxvAtcsAAAAAN4scgBuAGWgHSNyAACgHSNvAHAAAKAOI24AZwBvYXIAaQAAoPklYwByAADgNdjK3IABZGlyAPMs9yz6LG8AdAAAoPAi7CFkZWlhaQBmoLUlAKC0JQABYW0DLQYtcgDyAMosbAA7gPwA/EDhIm5nbGUAoKcpgAdBQkRhY2RlZmxub3Byc3oAJy0qLTAtNC2bLZ0toS2/LcMtxy3TLdgt3C3gLfwtcgDyABADYQByAHag6CoAoOkqYQBzAOgA/gIAAW5yOC08LechcnQAoJwpgANla25wcnN0AJkpSC1NLVQtXi1iLYItYQBwAHAA4QAaHG8AdABoAGkAbgDnAKEXgAFoaXIAoSmzJFotbwBwAPQAdCVooJUh7wD4JgABaXVmLWotZwBtAOEAuygAAWJwbi14LXMjZXRuZXEAceCKIgD+AODLKgD+cyNldG5lcQBx4IsiAP4A4MwqAP4AAWhyhi2KLWUAdADhABIraSNhbmdsZQAAAWxyki2WLeUhZnQAoLIiaSJnaHQAAKCzInkAMmThIXNoAKCiIoABZWxyAKcttC24LWKiKCKuLQAAAACyLWEAcgAAoLsicQAAoFoi7CFpcACg7iIAAWJ0vC1eD2EA8gBfD3IAAOA12DPddAByAOkAlS1zAHUAAAFicM0t0C0A4IIi0iAA4IMi0iBwAGYAAOA12GfdcgBvAPAAWQt0AHIA6QCaLQABY3XkLegtcgAA4DXYy9wAAWJw7C30LW4AAAFFZXUt8S0A4IoiAP5uAAABRWV/LfktAOCLIgD+6SJnemFnAKCaKYADY2Vmb3BycwANLhAuJS4pLiMuLi40LukhcmN1YQABZGkULiEuAAFiZxguHC5hAHIAAKBfKmUAcaAnIgCgWSLlIXJwAKAYIXIAAOA12DTdcABmAADgNdho3WWgQCJhAHQA6ABqD2MAcgAA4DXYzNzjCuQRUC4AAFQuAABYLmIuAAAAAGMubS5wLnQuAAAAAIguki4AAJouJxIqEnQAcgDpAB0ScgAA4DXYNd0AAUFhWy5eLnIA8gDnAnIA8gCTB75jAAFBYWYuaS5yAPIA4AJyAPIAjAdhAPAAeh5pAHMAAKD7IoABZHB0APgReS6DLgABZmx9LoAuAOA12GnddQDzAP8RaQBtAOUABBIAAUFhiy6OLnIA8gDuAnIA8gCaBwABY3GVLgoScgAA4DXYzdwAAXB0nS6hLmwAdQDzACUScgDpACASAARhY2VmaW9zdbEuvC7ELsguzC7PLtQu2S5jAAABdXm2LrsudABlADuA/QD9QE9kAAFpecAuwy5yAGMAd2FLZG4AO4ClAKVAcgAA4DXYNt1jAHkAV2RwAGYAAOA12GrdYwByAADgNdjO3AABY23dLt8ueQBOZGwAO4D/AP9AAAVhY2RlZmhpb3N38y73Lv8uAi8MLxAvEy8YLx0vIi9jInV0ZQB6YQABYXn7Lv4u8iFvbn5hN2RvAHQAfGEAAWV0Bi8KL3QAcgDmAB8QYQC2Y3IAAOA12DfdYwB5ADZk5yJyYXJyAKDdIXAAZgAA4DXYa91jAHIAAOA12M/cAAFqbiYvKC8AoA0gagAAoAwg"
|
|
);
|
|
|
|
// Generated using scripts/write-decode-map.ts
|
|
const xmlDecodeTree = /* #__PURE__ */ decodeBase64(
|
|
"AAJhZ2xxBwARABMAFQBtAg0AAAAAAA8AcAAmYG8AcwAnYHQAPmB0ADxg9SFvdCJg"
|
|
);
|
|
|
|
/**
|
|
* Bit flags & masks for the binary trie encoding used for entity decoding.
|
|
*
|
|
* Bit layout (16 bits total):
|
|
* 15..14 VALUE_LENGTH (+1 encoding; 0 => no value)
|
|
* 13 FLAG13. If valueLength>0: semicolon required flag (implicit ';').
|
|
* If valueLength==0: compact run flag.
|
|
* 12..7 BRANCH_LENGTH Branch length (0 => single branch in 6..0 if jumpOffset==char) OR run length (when compact run)
|
|
* 6..0 JUMP_TABLE Jump offset (jump table) OR single-branch char code OR first run char
|
|
*/
|
|
var BinTrieFlags;
|
|
(function (BinTrieFlags) {
|
|
BinTrieFlags[(BinTrieFlags["VALUE_LENGTH"] = 49152)] = "VALUE_LENGTH";
|
|
BinTrieFlags[(BinTrieFlags["FLAG13"] = 8192)] = "FLAG13";
|
|
BinTrieFlags[(BinTrieFlags["BRANCH_LENGTH"] = 8064)] = "BRANCH_LENGTH";
|
|
BinTrieFlags[(BinTrieFlags["JUMP_TABLE"] = 127)] = "JUMP_TABLE";
|
|
})(BinTrieFlags || (BinTrieFlags = {}));
|
|
|
|
var CharCodes$1;
|
|
(function (CharCodes) {
|
|
CharCodes[(CharCodes["NUM"] = 35)] = "NUM";
|
|
CharCodes[(CharCodes["SEMI"] = 59)] = "SEMI";
|
|
CharCodes[(CharCodes["EQUALS"] = 61)] = "EQUALS";
|
|
CharCodes[(CharCodes["ZERO"] = 48)] = "ZERO";
|
|
CharCodes[(CharCodes["NINE"] = 57)] = "NINE";
|
|
CharCodes[(CharCodes["LOWER_A"] = 97)] = "LOWER_A";
|
|
CharCodes[(CharCodes["LOWER_F"] = 102)] = "LOWER_F";
|
|
CharCodes[(CharCodes["LOWER_X"] = 120)] = "LOWER_X";
|
|
CharCodes[(CharCodes["LOWER_Z"] = 122)] = "LOWER_Z";
|
|
CharCodes[(CharCodes["UPPER_A"] = 65)] = "UPPER_A";
|
|
CharCodes[(CharCodes["UPPER_F"] = 70)] = "UPPER_F";
|
|
CharCodes[(CharCodes["UPPER_Z"] = 90)] = "UPPER_Z";
|
|
})(CharCodes$1 || (CharCodes$1 = {}));
|
|
/** Bit that needs to be set to convert an upper case ASCII character to lower case */
|
|
const TO_LOWER_BIT = 32;
|
|
function isNumber(code) {
|
|
return code >= CharCodes$1.ZERO && code <= CharCodes$1.NINE;
|
|
}
|
|
function isHexadecimalCharacter(code) {
|
|
return (
|
|
(code >= CharCodes$1.UPPER_A && code <= CharCodes$1.UPPER_F) ||
|
|
(code >= CharCodes$1.LOWER_A && code <= CharCodes$1.LOWER_F)
|
|
);
|
|
}
|
|
function isAsciiAlphaNumeric(code) {
|
|
return (
|
|
(code >= CharCodes$1.UPPER_A && code <= CharCodes$1.UPPER_Z) ||
|
|
(code >= CharCodes$1.LOWER_A && code <= CharCodes$1.LOWER_Z) ||
|
|
isNumber(code)
|
|
);
|
|
}
|
|
/**
|
|
* Checks if the given character is a valid end character for an entity in an attribute.
|
|
*
|
|
* Attribute values that aren't terminated properly aren't parsed, and shouldn't lead to a parser error.
|
|
* See the example in https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
|
|
*/
|
|
function isEntityInAttributeInvalidEnd(code) {
|
|
return code === CharCodes$1.EQUALS || isAsciiAlphaNumeric(code);
|
|
}
|
|
var EntityDecoderState;
|
|
(function (EntityDecoderState) {
|
|
EntityDecoderState[(EntityDecoderState["EntityStart"] = 0)] = "EntityStart";
|
|
EntityDecoderState[(EntityDecoderState["NumericStart"] = 1)] = "NumericStart";
|
|
EntityDecoderState[(EntityDecoderState["NumericDecimal"] = 2)] =
|
|
"NumericDecimal";
|
|
EntityDecoderState[(EntityDecoderState["NumericHex"] = 3)] = "NumericHex";
|
|
EntityDecoderState[(EntityDecoderState["NamedEntity"] = 4)] = "NamedEntity";
|
|
})(EntityDecoderState || (EntityDecoderState = {}));
|
|
var DecodingMode;
|
|
(function (DecodingMode) {
|
|
/** Entities in text nodes that can end with any character. */
|
|
DecodingMode[(DecodingMode["Legacy"] = 0)] = "Legacy";
|
|
/** Only allow entities terminated with a semicolon. */
|
|
DecodingMode[(DecodingMode["Strict"] = 1)] = "Strict";
|
|
/** Entities in attributes have limitations on ending characters. */
|
|
DecodingMode[(DecodingMode["Attribute"] = 2)] = "Attribute";
|
|
})(DecodingMode || (DecodingMode = {}));
|
|
/**
|
|
* Token decoder with support of writing partial entities.
|
|
*/
|
|
class EntityDecoder {
|
|
constructor(
|
|
/** The tree used to decode entities. */
|
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: False positive
|
|
decodeTree,
|
|
/**
|
|
* The function that is called when a codepoint is decoded.
|
|
*
|
|
* For multi-byte named entities, this will be called multiple times,
|
|
* with the second codepoint, and the same `consumed` value.
|
|
*
|
|
* @param codepoint The decoded codepoint.
|
|
* @param consumed The number of bytes consumed by the decoder.
|
|
*/
|
|
emitCodePoint,
|
|
/** An object that is used to produce errors. */
|
|
errors
|
|
) {
|
|
this.decodeTree = decodeTree;
|
|
this.emitCodePoint = emitCodePoint;
|
|
this.errors = errors;
|
|
/** The current state of the decoder. */
|
|
this.state = EntityDecoderState.EntityStart;
|
|
/** Characters that were consumed while parsing an entity. */
|
|
this.consumed = 1;
|
|
/**
|
|
* The result of the entity.
|
|
*
|
|
* Either the result index of a numeric entity, or the codepoint of a
|
|
* numeric entity.
|
|
*/
|
|
this.result = 0;
|
|
/** The current index in the decode tree. */
|
|
this.treeIndex = 0;
|
|
/** The number of characters that were consumed in excess. */
|
|
this.excess = 1;
|
|
/** The mode in which the decoder is operating. */
|
|
this.decodeMode = DecodingMode.Strict;
|
|
/** The number of characters that have been consumed in the current run. */
|
|
this.runConsumed = 0;
|
|
}
|
|
/** Resets the instance to make it reusable. */
|
|
startEntity(decodeMode) {
|
|
this.decodeMode = decodeMode;
|
|
this.state = EntityDecoderState.EntityStart;
|
|
this.result = 0;
|
|
this.treeIndex = 0;
|
|
this.excess = 1;
|
|
this.consumed = 1;
|
|
this.runConsumed = 0;
|
|
}
|
|
/**
|
|
* Write an entity to the decoder. This can be called multiple times with partial entities.
|
|
* If the entity is incomplete, the decoder will return -1.
|
|
*
|
|
* Mirrors the implementation of `getDecoder`, but with the ability to stop decoding if the
|
|
* entity is incomplete, and resume when the next string is written.
|
|
*
|
|
* @param input The string containing the entity (or a continuation of the entity).
|
|
* @param offset The offset at which the entity begins. Should be 0 if this is not the first call.
|
|
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
*/
|
|
write(input, offset) {
|
|
switch (this.state) {
|
|
case EntityDecoderState.EntityStart: {
|
|
if (input.charCodeAt(offset) === CharCodes$1.NUM) {
|
|
this.state = EntityDecoderState.NumericStart;
|
|
this.consumed += 1;
|
|
return this.stateNumericStart(input, offset + 1);
|
|
}
|
|
this.state = EntityDecoderState.NamedEntity;
|
|
return this.stateNamedEntity(input, offset);
|
|
}
|
|
case EntityDecoderState.NumericStart: {
|
|
return this.stateNumericStart(input, offset);
|
|
}
|
|
case EntityDecoderState.NumericDecimal: {
|
|
return this.stateNumericDecimal(input, offset);
|
|
}
|
|
case EntityDecoderState.NumericHex: {
|
|
return this.stateNumericHex(input, offset);
|
|
}
|
|
case EntityDecoderState.NamedEntity: {
|
|
return this.stateNamedEntity(input, offset);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Switches between the numeric decimal and hexadecimal states.
|
|
*
|
|
* Equivalent to the `Numeric character reference state` in the HTML spec.
|
|
*
|
|
* @param input The string containing the entity (or a continuation of the entity).
|
|
* @param offset The current offset.
|
|
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
*/
|
|
stateNumericStart(input, offset) {
|
|
if (offset >= input.length) {
|
|
return -1;
|
|
}
|
|
if ((input.charCodeAt(offset) | TO_LOWER_BIT) === CharCodes$1.LOWER_X) {
|
|
this.state = EntityDecoderState.NumericHex;
|
|
this.consumed += 1;
|
|
return this.stateNumericHex(input, offset + 1);
|
|
}
|
|
this.state = EntityDecoderState.NumericDecimal;
|
|
return this.stateNumericDecimal(input, offset);
|
|
}
|
|
/**
|
|
* Parses a hexadecimal numeric entity.
|
|
*
|
|
* Equivalent to the `Hexademical character reference state` in the HTML spec.
|
|
*
|
|
* @param input The string containing the entity (or a continuation of the entity).
|
|
* @param offset The current offset.
|
|
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
*/
|
|
stateNumericHex(input, offset) {
|
|
while (offset < input.length) {
|
|
const char = input.charCodeAt(offset);
|
|
if (isNumber(char) || isHexadecimalCharacter(char)) {
|
|
// Convert hex digit to value (0-15); 'a'/'A' -> 10.
|
|
const digit =
|
|
char <= CharCodes$1.NINE
|
|
? char - CharCodes$1.ZERO
|
|
: (char | TO_LOWER_BIT) - CharCodes$1.LOWER_A + 10;
|
|
this.result = this.result * 16 + digit;
|
|
this.consumed++;
|
|
offset++;
|
|
} else {
|
|
return this.emitNumericEntity(char, 3);
|
|
}
|
|
}
|
|
return -1; // Incomplete entity
|
|
}
|
|
/**
|
|
* Parses a decimal numeric entity.
|
|
*
|
|
* Equivalent to the `Decimal character reference state` in the HTML spec.
|
|
*
|
|
* @param input The string containing the entity (or a continuation of the entity).
|
|
* @param offset The current offset.
|
|
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
*/
|
|
stateNumericDecimal(input, offset) {
|
|
while (offset < input.length) {
|
|
const char = input.charCodeAt(offset);
|
|
if (isNumber(char)) {
|
|
this.result = this.result * 10 + (char - CharCodes$1.ZERO);
|
|
this.consumed++;
|
|
offset++;
|
|
} else {
|
|
return this.emitNumericEntity(char, 2);
|
|
}
|
|
}
|
|
return -1; // Incomplete entity
|
|
}
|
|
/**
|
|
* Validate and emit a numeric entity.
|
|
*
|
|
* Implements the logic from the `Hexademical character reference start
|
|
* state` and `Numeric character reference end state` in the HTML spec.
|
|
*
|
|
* @param lastCp The last code point of the entity. Used to see if the
|
|
* entity was terminated with a semicolon.
|
|
* @param expectedLength The minimum number of characters that should be
|
|
* consumed. Used to validate that at least one digit
|
|
* was consumed.
|
|
* @returns The number of characters that were consumed.
|
|
*/
|
|
emitNumericEntity(lastCp, expectedLength) {
|
|
var _a;
|
|
// Ensure we consumed at least one digit.
|
|
if (this.consumed <= expectedLength) {
|
|
(_a = this.errors) === null || _a === void 0
|
|
? void 0
|
|
: _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
|
return 0;
|
|
}
|
|
// Figure out if this is a legit end of the entity
|
|
if (lastCp === CharCodes$1.SEMI) {
|
|
this.consumed += 1;
|
|
} else if (this.decodeMode === DecodingMode.Strict) {
|
|
return 0;
|
|
}
|
|
this.emitCodePoint(replaceCodePoint(this.result), this.consumed);
|
|
if (this.errors) {
|
|
if (lastCp !== CharCodes$1.SEMI) {
|
|
this.errors.missingSemicolonAfterCharacterReference();
|
|
}
|
|
this.errors.validateNumericCharacterReference(this.result);
|
|
}
|
|
return this.consumed;
|
|
}
|
|
/**
|
|
* Parses a named entity.
|
|
*
|
|
* Equivalent to the `Named character reference state` in the HTML spec.
|
|
*
|
|
* @param input The string containing the entity (or a continuation of the entity).
|
|
* @param offset The current offset.
|
|
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
|
|
*/
|
|
stateNamedEntity(input, offset) {
|
|
const { decodeTree } = this;
|
|
let current = decodeTree[this.treeIndex];
|
|
// The length is the number of bytes of the value, including the current byte.
|
|
let valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
while (offset < input.length) {
|
|
// Handle compact runs (possibly inline): valueLength == 0 and SEMI_REQUIRED bit set.
|
|
if (valueLength === 0 && (current & BinTrieFlags.FLAG13) !== 0) {
|
|
const runLength =
|
|
(current & BinTrieFlags.BRANCH_LENGTH) >> 7; /* 2..63 */
|
|
// If we are starting a run, check the first char.
|
|
if (this.runConsumed === 0) {
|
|
const firstChar = current & BinTrieFlags.JUMP_TABLE;
|
|
if (input.charCodeAt(offset) !== firstChar) {
|
|
return this.result === 0 ? 0 : this.emitNotTerminatedNamedEntity();
|
|
}
|
|
offset++;
|
|
this.excess++;
|
|
this.runConsumed++;
|
|
}
|
|
// Check remaining characters in the run.
|
|
while (this.runConsumed < runLength) {
|
|
if (offset >= input.length) {
|
|
return -1;
|
|
}
|
|
const charIndexInPacked = this.runConsumed - 1;
|
|
const packedWord =
|
|
decodeTree[this.treeIndex + 1 + (charIndexInPacked >> 1)];
|
|
const expectedChar =
|
|
charIndexInPacked % 2 === 0
|
|
? packedWord & 0xff
|
|
: (packedWord >> 8) & 0xff;
|
|
if (input.charCodeAt(offset) !== expectedChar) {
|
|
this.runConsumed = 0;
|
|
return this.result === 0 ? 0 : this.emitNotTerminatedNamedEntity();
|
|
}
|
|
offset++;
|
|
this.excess++;
|
|
this.runConsumed++;
|
|
}
|
|
this.runConsumed = 0;
|
|
this.treeIndex += 1 + (runLength >> 1);
|
|
current = decodeTree[this.treeIndex];
|
|
valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
}
|
|
if (offset >= input.length) break;
|
|
const char = input.charCodeAt(offset);
|
|
/*
|
|
* Implicit semicolon handling for nodes that require a semicolon but
|
|
* don't have an explicit ';' branch stored in the trie. If we have
|
|
* a value on the current node, it requires a semicolon, and the
|
|
* current input character is a semicolon, emit the entity using the
|
|
* current node (without descending further).
|
|
*/
|
|
if (
|
|
char === CharCodes$1.SEMI &&
|
|
valueLength !== 0 &&
|
|
(current & BinTrieFlags.FLAG13) !== 0
|
|
) {
|
|
return this.emitNamedEntityData(
|
|
this.treeIndex,
|
|
valueLength,
|
|
this.consumed + this.excess
|
|
);
|
|
}
|
|
this.treeIndex = determineBranch(
|
|
decodeTree,
|
|
current,
|
|
this.treeIndex + Math.max(1, valueLength),
|
|
char
|
|
);
|
|
if (this.treeIndex < 0) {
|
|
return this.result === 0 ||
|
|
// If we are parsing an attribute
|
|
(this.decodeMode === DecodingMode.Attribute &&
|
|
// We shouldn't have consumed any characters after the entity,
|
|
(valueLength === 0 ||
|
|
// And there should be no invalid characters.
|
|
isEntityInAttributeInvalidEnd(char)))
|
|
? 0
|
|
: this.emitNotTerminatedNamedEntity();
|
|
}
|
|
current = decodeTree[this.treeIndex];
|
|
valueLength = (current & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
// If the branch is a value, store it and continue
|
|
if (valueLength !== 0) {
|
|
// If the entity is terminated by a semicolon, we are done.
|
|
if (char === CharCodes$1.SEMI) {
|
|
return this.emitNamedEntityData(
|
|
this.treeIndex,
|
|
valueLength,
|
|
this.consumed + this.excess
|
|
);
|
|
}
|
|
// If we encounter a non-terminated (legacy) entity while parsing strictly, then ignore it.
|
|
if (
|
|
this.decodeMode !== DecodingMode.Strict &&
|
|
(current & BinTrieFlags.FLAG13) === 0
|
|
) {
|
|
this.result = this.treeIndex;
|
|
this.consumed += this.excess;
|
|
this.excess = 0;
|
|
}
|
|
}
|
|
// Increment offset & excess for next iteration
|
|
offset++;
|
|
this.excess++;
|
|
}
|
|
return -1;
|
|
}
|
|
/**
|
|
* Emit a named entity that was not terminated with a semicolon.
|
|
*
|
|
* @returns The number of characters consumed.
|
|
*/
|
|
emitNotTerminatedNamedEntity() {
|
|
var _a;
|
|
const { result, decodeTree } = this;
|
|
const valueLength = (decodeTree[result] & BinTrieFlags.VALUE_LENGTH) >> 14;
|
|
this.emitNamedEntityData(result, valueLength, this.consumed);
|
|
(_a = this.errors) === null || _a === void 0
|
|
? void 0
|
|
: _a.missingSemicolonAfterCharacterReference();
|
|
return this.consumed;
|
|
}
|
|
/**
|
|
* Emit a named entity.
|
|
*
|
|
* @param result The index of the entity in the decode tree.
|
|
* @param valueLength The number of bytes in the entity.
|
|
* @param consumed The number of characters consumed.
|
|
*
|
|
* @returns The number of characters consumed.
|
|
*/
|
|
emitNamedEntityData(result, valueLength, consumed) {
|
|
const { decodeTree } = this;
|
|
this.emitCodePoint(
|
|
valueLength === 1
|
|
? decodeTree[result] &
|
|
~(BinTrieFlags.VALUE_LENGTH | BinTrieFlags.FLAG13)
|
|
: decodeTree[result + 1],
|
|
consumed
|
|
);
|
|
if (valueLength === 3) {
|
|
// For multi-byte values, we need to emit the second byte.
|
|
this.emitCodePoint(decodeTree[result + 2], consumed);
|
|
}
|
|
return consumed;
|
|
}
|
|
/**
|
|
* Signal to the parser that the end of the input was reached.
|
|
*
|
|
* Remaining data will be emitted and relevant errors will be produced.
|
|
*
|
|
* @returns The number of characters consumed.
|
|
*/
|
|
end() {
|
|
var _a;
|
|
switch (this.state) {
|
|
case EntityDecoderState.NamedEntity: {
|
|
// Emit a named entity if we have one.
|
|
return this.result !== 0 &&
|
|
(this.decodeMode !== DecodingMode.Attribute ||
|
|
this.result === this.treeIndex)
|
|
? this.emitNotTerminatedNamedEntity()
|
|
: 0;
|
|
}
|
|
// Otherwise, emit a numeric entity if we have one.
|
|
case EntityDecoderState.NumericDecimal: {
|
|
return this.emitNumericEntity(0, 2);
|
|
}
|
|
case EntityDecoderState.NumericHex: {
|
|
return this.emitNumericEntity(0, 3);
|
|
}
|
|
case EntityDecoderState.NumericStart: {
|
|
(_a = this.errors) === null || _a === void 0
|
|
? void 0
|
|
: _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
|
|
return 0;
|
|
}
|
|
case EntityDecoderState.EntityStart: {
|
|
// Return 0 if we have no entity.
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Determines the branch of the current node that is taken given the current
|
|
* character. This function is used to traverse the trie.
|
|
*
|
|
* @param decodeTree The trie.
|
|
* @param current The current node.
|
|
* @param nodeIdx The index right after the current node and its value.
|
|
* @param char The current character.
|
|
* @returns The index of the next node, or -1 if no branch is taken.
|
|
*/
|
|
function determineBranch(decodeTree, current, nodeIndex, char) {
|
|
const branchCount = (current & BinTrieFlags.BRANCH_LENGTH) >> 7;
|
|
const jumpOffset = current & BinTrieFlags.JUMP_TABLE;
|
|
// Case 1: Single branch encoded in jump offset
|
|
if (branchCount === 0) {
|
|
return jumpOffset !== 0 && char === jumpOffset ? nodeIndex : -1;
|
|
}
|
|
// Case 2: Multiple branches encoded in jump table
|
|
if (jumpOffset) {
|
|
const value = char - jumpOffset;
|
|
return value < 0 || value >= branchCount
|
|
? -1
|
|
: decodeTree[nodeIndex + value] - 1;
|
|
}
|
|
// Case 3: Multiple branches encoded in packed dictionary (two keys per uint16)
|
|
const packedKeySlots = (branchCount + 1) >> 1;
|
|
/*
|
|
* Treat packed keys as a virtual sorted array of length `branchCount`.
|
|
* Key(i) = low byte for even i, high byte for odd i in slot i>>1.
|
|
*/
|
|
let lo = 0;
|
|
let hi = branchCount - 1;
|
|
while (lo <= hi) {
|
|
const mid = (lo + hi) >>> 1;
|
|
const slot = mid >> 1;
|
|
const packed = decodeTree[nodeIndex + slot];
|
|
const midKey = (packed >> ((mid & 1) * 8)) & 0xff;
|
|
if (midKey < char) {
|
|
lo = mid + 1;
|
|
} else if (midKey > char) {
|
|
hi = mid - 1;
|
|
} else {
|
|
return decodeTree[nodeIndex + packedKeySlots + mid];
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
var CharCodes;
|
|
(function (CharCodes) {
|
|
CharCodes[(CharCodes["Tab"] = 9)] = "Tab";
|
|
CharCodes[(CharCodes["NewLine"] = 10)] = "NewLine";
|
|
CharCodes[(CharCodes["FormFeed"] = 12)] = "FormFeed";
|
|
CharCodes[(CharCodes["CarriageReturn"] = 13)] = "CarriageReturn";
|
|
CharCodes[(CharCodes["Space"] = 32)] = "Space";
|
|
CharCodes[(CharCodes["ExclamationMark"] = 33)] = "ExclamationMark";
|
|
CharCodes[(CharCodes["Number"] = 35)] = "Number";
|
|
CharCodes[(CharCodes["Amp"] = 38)] = "Amp";
|
|
CharCodes[(CharCodes["SingleQuote"] = 39)] = "SingleQuote";
|
|
CharCodes[(CharCodes["DoubleQuote"] = 34)] = "DoubleQuote";
|
|
CharCodes[(CharCodes["Dash"] = 45)] = "Dash";
|
|
CharCodes[(CharCodes["Slash"] = 47)] = "Slash";
|
|
CharCodes[(CharCodes["Zero"] = 48)] = "Zero";
|
|
CharCodes[(CharCodes["Nine"] = 57)] = "Nine";
|
|
CharCodes[(CharCodes["Semi"] = 59)] = "Semi";
|
|
CharCodes[(CharCodes["Lt"] = 60)] = "Lt";
|
|
CharCodes[(CharCodes["Eq"] = 61)] = "Eq";
|
|
CharCodes[(CharCodes["Gt"] = 62)] = "Gt";
|
|
CharCodes[(CharCodes["Questionmark"] = 63)] = "Questionmark";
|
|
CharCodes[(CharCodes["UpperA"] = 65)] = "UpperA";
|
|
CharCodes[(CharCodes["LowerA"] = 97)] = "LowerA";
|
|
CharCodes[(CharCodes["UpperF"] = 70)] = "UpperF";
|
|
CharCodes[(CharCodes["LowerF"] = 102)] = "LowerF";
|
|
CharCodes[(CharCodes["UpperZ"] = 90)] = "UpperZ";
|
|
CharCodes[(CharCodes["LowerZ"] = 122)] = "LowerZ";
|
|
CharCodes[(CharCodes["LowerX"] = 120)] = "LowerX";
|
|
CharCodes[(CharCodes["OpeningSquareBracket"] = 91)] = "OpeningSquareBracket";
|
|
})(CharCodes || (CharCodes = {}));
|
|
/** All the states the tokenizer can be in. */
|
|
var State;
|
|
(function (State) {
|
|
State[(State["Text"] = 1)] = "Text";
|
|
State[(State["BeforeTagName"] = 2)] = "BeforeTagName";
|
|
State[(State["InTagName"] = 3)] = "InTagName";
|
|
State[(State["InSelfClosingTag"] = 4)] = "InSelfClosingTag";
|
|
State[(State["BeforeClosingTagName"] = 5)] = "BeforeClosingTagName";
|
|
State[(State["InClosingTagName"] = 6)] = "InClosingTagName";
|
|
State[(State["AfterClosingTagName"] = 7)] = "AfterClosingTagName";
|
|
// Attributes
|
|
State[(State["BeforeAttributeName"] = 8)] = "BeforeAttributeName";
|
|
State[(State["InAttributeName"] = 9)] = "InAttributeName";
|
|
State[(State["AfterAttributeName"] = 10)] = "AfterAttributeName";
|
|
State[(State["BeforeAttributeValue"] = 11)] = "BeforeAttributeValue";
|
|
State[(State["InAttributeValueDq"] = 12)] = "InAttributeValueDq";
|
|
State[(State["InAttributeValueSq"] = 13)] = "InAttributeValueSq";
|
|
State[(State["InAttributeValueNq"] = 14)] = "InAttributeValueNq";
|
|
// Declarations
|
|
State[(State["BeforeDeclaration"] = 15)] = "BeforeDeclaration";
|
|
State[(State["InDeclaration"] = 16)] = "InDeclaration";
|
|
// Processing instructions
|
|
State[(State["InProcessingInstruction"] = 17)] = "InProcessingInstruction";
|
|
// Comments & CDATA
|
|
State[(State["BeforeComment"] = 18)] = "BeforeComment";
|
|
State[(State["CDATASequence"] = 19)] = "CDATASequence";
|
|
State[(State["InSpecialComment"] = 20)] = "InSpecialComment";
|
|
State[(State["InCommentLike"] = 21)] = "InCommentLike";
|
|
// Special tags
|
|
State[(State["BeforeSpecialS"] = 22)] = "BeforeSpecialS";
|
|
State[(State["BeforeSpecialT"] = 23)] = "BeforeSpecialT";
|
|
State[(State["SpecialStartSequence"] = 24)] = "SpecialStartSequence";
|
|
State[(State["InSpecialTag"] = 25)] = "InSpecialTag";
|
|
State[(State["InEntity"] = 26)] = "InEntity";
|
|
})(State || (State = {}));
|
|
function isWhitespace$1(c) {
|
|
return (
|
|
c === CharCodes.Space ||
|
|
c === CharCodes.NewLine ||
|
|
c === CharCodes.Tab ||
|
|
c === CharCodes.FormFeed ||
|
|
c === CharCodes.CarriageReturn
|
|
);
|
|
}
|
|
function isEndOfTagSection(c) {
|
|
return c === CharCodes.Slash || c === CharCodes.Gt || isWhitespace$1(c);
|
|
}
|
|
function isASCIIAlpha(c) {
|
|
return (
|
|
(c >= CharCodes.LowerA && c <= CharCodes.LowerZ) ||
|
|
(c >= CharCodes.UpperA && c <= CharCodes.UpperZ)
|
|
);
|
|
}
|
|
var QuoteType;
|
|
(function (QuoteType) {
|
|
QuoteType[(QuoteType["NoValue"] = 0)] = "NoValue";
|
|
QuoteType[(QuoteType["Unquoted"] = 1)] = "Unquoted";
|
|
QuoteType[(QuoteType["Single"] = 2)] = "Single";
|
|
QuoteType[(QuoteType["Double"] = 3)] = "Double";
|
|
})(QuoteType || (QuoteType = {}));
|
|
/**
|
|
* Sequences used to match longer strings.
|
|
*
|
|
* We don't have `Script`, `Style`, or `Title` here. Instead, we re-use the *End
|
|
* sequences with an increased offset.
|
|
*/
|
|
const Sequences = {
|
|
Cdata: new Uint8Array([0x43, 0x44, 0x41, 0x54, 0x41, 0x5b]), // CDATA[
|
|
CdataEnd: new Uint8Array([0x5d, 0x5d, 0x3e]), // ]]>
|
|
CommentEnd: new Uint8Array([0x2d, 0x2d, 0x3e]), // `-->`
|
|
ScriptEnd: new Uint8Array([0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74]), // `</script`
|
|
StyleEnd: new Uint8Array([0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65]), // `</style`
|
|
TitleEnd: new Uint8Array([0x3c, 0x2f, 0x74, 0x69, 0x74, 0x6c, 0x65]), // `</title`
|
|
TextareaEnd: new Uint8Array([
|
|
0x3c, 0x2f, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61
|
|
]), // `</textarea`
|
|
XmpEnd: new Uint8Array([0x3c, 0x2f, 0x78, 0x6d, 0x70]) // `</xmp`
|
|
};
|
|
class Tokenizer {
|
|
constructor({ xmlMode = false, decodeEntities = true }, cbs) {
|
|
this.cbs = cbs;
|
|
/** The current state the tokenizer is in. */
|
|
this.state = State.Text;
|
|
/** The read buffer. */
|
|
this.buffer = "";
|
|
/** The beginning of the section that is currently being read. */
|
|
this.sectionStart = 0;
|
|
/** The index within the buffer that we are currently looking at. */
|
|
this.index = 0;
|
|
/** The start of the last entity. */
|
|
this.entityStart = 0;
|
|
/** Some behavior, eg. when decoding entities, is done while we are in another state. This keeps track of the other state type. */
|
|
this.baseState = State.Text;
|
|
/** For special parsing behavior inside of script and style tags. */
|
|
this.isSpecial = false;
|
|
/** Indicates whether the tokenizer has been paused. */
|
|
this.running = true;
|
|
/** The offset of the current buffer. */
|
|
this.offset = 0;
|
|
this.currentSequence = undefined;
|
|
this.sequenceIndex = 0;
|
|
this.xmlMode = xmlMode;
|
|
this.decodeEntities = decodeEntities;
|
|
this.entityDecoder = new EntityDecoder(
|
|
xmlMode ? xmlDecodeTree : htmlDecodeTree,
|
|
(cp, consumed) => this.emitCodePoint(cp, consumed)
|
|
);
|
|
}
|
|
reset() {
|
|
this.state = State.Text;
|
|
this.buffer = "";
|
|
this.sectionStart = 0;
|
|
this.index = 0;
|
|
this.baseState = State.Text;
|
|
this.currentSequence = undefined;
|
|
this.running = true;
|
|
this.offset = 0;
|
|
}
|
|
write(chunk) {
|
|
this.offset += this.buffer.length;
|
|
this.buffer = chunk;
|
|
this.parse();
|
|
}
|
|
end() {
|
|
if (this.running) this.finish();
|
|
}
|
|
pause() {
|
|
this.running = false;
|
|
}
|
|
resume() {
|
|
this.running = true;
|
|
if (this.index < this.buffer.length + this.offset) {
|
|
this.parse();
|
|
}
|
|
}
|
|
stateText(c) {
|
|
if (
|
|
c === CharCodes.Lt ||
|
|
(!this.decodeEntities && this.fastForwardTo(CharCodes.Lt))
|
|
) {
|
|
if (this.index > this.sectionStart) {
|
|
this.cbs.ontext(this.sectionStart, this.index);
|
|
}
|
|
this.state = State.BeforeTagName;
|
|
this.sectionStart = this.index;
|
|
} else if (this.decodeEntities && c === CharCodes.Amp) {
|
|
this.startEntity();
|
|
}
|
|
}
|
|
stateSpecialStartSequence(c) {
|
|
const isEnd = this.sequenceIndex === this.currentSequence.length;
|
|
const isMatch = isEnd
|
|
? // If we are at the end of the sequence, make sure the tag name has ended
|
|
isEndOfTagSection(c)
|
|
: // Otherwise, do a case-insensitive comparison
|
|
(c | 0x20) === this.currentSequence[this.sequenceIndex];
|
|
if (!isMatch) {
|
|
this.isSpecial = false;
|
|
} else if (!isEnd) {
|
|
this.sequenceIndex++;
|
|
return;
|
|
}
|
|
this.sequenceIndex = 0;
|
|
this.state = State.InTagName;
|
|
this.stateInTagName(c);
|
|
}
|
|
/** Look for an end tag. For <title> tags, also decode entities. */
|
|
stateInSpecialTag(c) {
|
|
if (this.sequenceIndex === this.currentSequence.length) {
|
|
if (c === CharCodes.Gt || isWhitespace$1(c)) {
|
|
const endOfText = this.index - this.currentSequence.length;
|
|
if (this.sectionStart < endOfText) {
|
|
// Spoof the index so that reported locations match up.
|
|
const actualIndex = this.index;
|
|
this.index = endOfText;
|
|
this.cbs.ontext(this.sectionStart, endOfText);
|
|
this.index = actualIndex;
|
|
}
|
|
this.isSpecial = false;
|
|
this.sectionStart = endOfText + 2; // Skip over the `</`
|
|
this.stateInClosingTagName(c);
|
|
return; // We are done; skip the rest of the function.
|
|
}
|
|
this.sequenceIndex = 0;
|
|
}
|
|
if ((c | 0x20) === this.currentSequence[this.sequenceIndex]) {
|
|
this.sequenceIndex += 1;
|
|
} else if (this.sequenceIndex === 0) {
|
|
if (this.currentSequence === Sequences.TitleEnd) {
|
|
// We have to parse entities in <title> tags.
|
|
if (this.decodeEntities && c === CharCodes.Amp) {
|
|
this.startEntity();
|
|
}
|
|
} else if (this.fastForwardTo(CharCodes.Lt)) {
|
|
// Outside of <title> tags, we can fast-forward.
|
|
this.sequenceIndex = 1;
|
|
}
|
|
} else {
|
|
// If we see a `<`, set the sequence index to 1; useful for eg. `<</script>`.
|
|
this.sequenceIndex = Number(c === CharCodes.Lt);
|
|
}
|
|
}
|
|
stateCDATASequence(c) {
|
|
if (c === Sequences.Cdata[this.sequenceIndex]) {
|
|
if (++this.sequenceIndex === Sequences.Cdata.length) {
|
|
this.state = State.InCommentLike;
|
|
this.currentSequence = Sequences.CdataEnd;
|
|
this.sequenceIndex = 0;
|
|
this.sectionStart = this.index + 1;
|
|
}
|
|
} else {
|
|
this.sequenceIndex = 0;
|
|
this.state = State.InDeclaration;
|
|
this.stateInDeclaration(c); // Reconsume the character
|
|
}
|
|
}
|
|
/**
|
|
* When we wait for one specific character, we can speed things up
|
|
* by skipping through the buffer until we find it.
|
|
*
|
|
* @returns Whether the character was found.
|
|
*/
|
|
fastForwardTo(c) {
|
|
while (++this.index < this.buffer.length + this.offset) {
|
|
if (this.buffer.charCodeAt(this.index - this.offset) === c) {
|
|
return true;
|
|
}
|
|
}
|
|
/*
|
|
* We increment the index at the end of the `parse` loop,
|
|
* so set it to `buffer.length - 1` here.
|
|
*
|
|
* TODO: Refactor `parse` to increment index before calling states.
|
|
*/
|
|
this.index = this.buffer.length + this.offset - 1;
|
|
return false;
|
|
}
|
|
/**
|
|
* Comments and CDATA end with `-->` and `]]>`.
|
|
*
|
|
* Their common qualities are:
|
|
* - Their end sequences have a distinct character they start with.
|
|
* - That character is then repeated, so we have to check multiple repeats.
|
|
* - All characters but the start character of the sequence can be skipped.
|
|
*/
|
|
stateInCommentLike(c) {
|
|
if (c === this.currentSequence[this.sequenceIndex]) {
|
|
if (++this.sequenceIndex === this.currentSequence.length) {
|
|
if (this.currentSequence === Sequences.CdataEnd) {
|
|
this.cbs.oncdata(this.sectionStart, this.index, 2);
|
|
} else {
|
|
this.cbs.oncomment(this.sectionStart, this.index, 2);
|
|
}
|
|
this.sequenceIndex = 0;
|
|
this.sectionStart = this.index + 1;
|
|
this.state = State.Text;
|
|
}
|
|
} else if (this.sequenceIndex === 0) {
|
|
// Fast-forward to the first character of the sequence
|
|
if (this.fastForwardTo(this.currentSequence[0])) {
|
|
this.sequenceIndex = 1;
|
|
}
|
|
} else if (c !== this.currentSequence[this.sequenceIndex - 1]) {
|
|
// Allow long sequences, eg. --->, ]]]>
|
|
this.sequenceIndex = 0;
|
|
}
|
|
}
|
|
/**
|
|
* HTML only allows ASCII alpha characters (a-z and A-Z) at the beginning of a tag name.
|
|
*
|
|
* XML allows a lot more characters here (@see https://www.w3.org/TR/REC-xml/#NT-NameStartChar).
|
|
* We allow anything that wouldn't end the tag.
|
|
*/
|
|
isTagStartChar(c) {
|
|
return this.xmlMode ? !isEndOfTagSection(c) : isASCIIAlpha(c);
|
|
}
|
|
startSpecial(sequence, offset) {
|
|
this.isSpecial = true;
|
|
this.currentSequence = sequence;
|
|
this.sequenceIndex = offset;
|
|
this.state = State.SpecialStartSequence;
|
|
}
|
|
stateBeforeTagName(c) {
|
|
if (c === CharCodes.ExclamationMark) {
|
|
this.state = State.BeforeDeclaration;
|
|
this.sectionStart = this.index + 1;
|
|
} else if (c === CharCodes.Questionmark) {
|
|
this.state = State.InProcessingInstruction;
|
|
this.sectionStart = this.index + 1;
|
|
} else if (this.isTagStartChar(c)) {
|
|
const lower = c | 0x20;
|
|
this.sectionStart = this.index;
|
|
if (this.xmlMode) {
|
|
this.state = State.InTagName;
|
|
} else if (lower === Sequences.ScriptEnd[2]) {
|
|
this.state = State.BeforeSpecialS;
|
|
} else if (
|
|
lower === Sequences.TitleEnd[2] ||
|
|
lower === Sequences.XmpEnd[2]
|
|
) {
|
|
this.state = State.BeforeSpecialT;
|
|
} else {
|
|
this.state = State.InTagName;
|
|
}
|
|
} else if (c === CharCodes.Slash) {
|
|
this.state = State.BeforeClosingTagName;
|
|
} else {
|
|
this.state = State.Text;
|
|
this.stateText(c);
|
|
}
|
|
}
|
|
stateInTagName(c) {
|
|
if (isEndOfTagSection(c)) {
|
|
this.cbs.onopentagname(this.sectionStart, this.index);
|
|
this.sectionStart = -1;
|
|
this.state = State.BeforeAttributeName;
|
|
this.stateBeforeAttributeName(c);
|
|
}
|
|
}
|
|
stateBeforeClosingTagName(c) {
|
|
if (isWhitespace$1(c));
|
|
else if (c === CharCodes.Gt) {
|
|
this.state = State.Text;
|
|
} else {
|
|
this.state = this.isTagStartChar(c)
|
|
? State.InClosingTagName
|
|
: State.InSpecialComment;
|
|
this.sectionStart = this.index;
|
|
}
|
|
}
|
|
stateInClosingTagName(c) {
|
|
if (c === CharCodes.Gt || isWhitespace$1(c)) {
|
|
this.cbs.onclosetag(this.sectionStart, this.index);
|
|
this.sectionStart = -1;
|
|
this.state = State.AfterClosingTagName;
|
|
this.stateAfterClosingTagName(c);
|
|
}
|
|
}
|
|
stateAfterClosingTagName(c) {
|
|
// Skip everything until ">"
|
|
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
|
this.state = State.Text;
|
|
this.sectionStart = this.index + 1;
|
|
}
|
|
}
|
|
stateBeforeAttributeName(c) {
|
|
if (c === CharCodes.Gt) {
|
|
this.cbs.onopentagend(this.index);
|
|
if (this.isSpecial) {
|
|
this.state = State.InSpecialTag;
|
|
this.sequenceIndex = 0;
|
|
} else {
|
|
this.state = State.Text;
|
|
}
|
|
this.sectionStart = this.index + 1;
|
|
} else if (c === CharCodes.Slash) {
|
|
this.state = State.InSelfClosingTag;
|
|
} else if (!isWhitespace$1(c)) {
|
|
this.state = State.InAttributeName;
|
|
this.sectionStart = this.index;
|
|
}
|
|
}
|
|
stateInSelfClosingTag(c) {
|
|
if (c === CharCodes.Gt) {
|
|
this.cbs.onselfclosingtag(this.index);
|
|
this.state = State.Text;
|
|
this.sectionStart = this.index + 1;
|
|
this.isSpecial = false; // Reset special state, in case of self-closing special tags
|
|
} else if (!isWhitespace$1(c)) {
|
|
this.state = State.BeforeAttributeName;
|
|
this.stateBeforeAttributeName(c);
|
|
}
|
|
}
|
|
stateInAttributeName(c) {
|
|
if (c === CharCodes.Eq || isEndOfTagSection(c)) {
|
|
this.cbs.onattribname(this.sectionStart, this.index);
|
|
this.sectionStart = this.index;
|
|
this.state = State.AfterAttributeName;
|
|
this.stateAfterAttributeName(c);
|
|
}
|
|
}
|
|
stateAfterAttributeName(c) {
|
|
if (c === CharCodes.Eq) {
|
|
this.state = State.BeforeAttributeValue;
|
|
} else if (c === CharCodes.Slash || c === CharCodes.Gt) {
|
|
this.cbs.onattribend(QuoteType.NoValue, this.sectionStart);
|
|
this.sectionStart = -1;
|
|
this.state = State.BeforeAttributeName;
|
|
this.stateBeforeAttributeName(c);
|
|
} else if (!isWhitespace$1(c)) {
|
|
this.cbs.onattribend(QuoteType.NoValue, this.sectionStart);
|
|
this.state = State.InAttributeName;
|
|
this.sectionStart = this.index;
|
|
}
|
|
}
|
|
stateBeforeAttributeValue(c) {
|
|
if (c === CharCodes.DoubleQuote) {
|
|
this.state = State.InAttributeValueDq;
|
|
this.sectionStart = this.index + 1;
|
|
} else if (c === CharCodes.SingleQuote) {
|
|
this.state = State.InAttributeValueSq;
|
|
this.sectionStart = this.index + 1;
|
|
} else if (!isWhitespace$1(c)) {
|
|
this.sectionStart = this.index;
|
|
this.state = State.InAttributeValueNq;
|
|
this.stateInAttributeValueNoQuotes(c); // Reconsume token
|
|
}
|
|
}
|
|
handleInAttributeValue(c, quote) {
|
|
if (c === quote || (!this.decodeEntities && this.fastForwardTo(quote))) {
|
|
this.cbs.onattribdata(this.sectionStart, this.index);
|
|
this.sectionStart = -1;
|
|
this.cbs.onattribend(
|
|
quote === CharCodes.DoubleQuote ? QuoteType.Double : QuoteType.Single,
|
|
this.index + 1
|
|
);
|
|
this.state = State.BeforeAttributeName;
|
|
} else if (this.decodeEntities && c === CharCodes.Amp) {
|
|
this.startEntity();
|
|
}
|
|
}
|
|
stateInAttributeValueDoubleQuotes(c) {
|
|
this.handleInAttributeValue(c, CharCodes.DoubleQuote);
|
|
}
|
|
stateInAttributeValueSingleQuotes(c) {
|
|
this.handleInAttributeValue(c, CharCodes.SingleQuote);
|
|
}
|
|
stateInAttributeValueNoQuotes(c) {
|
|
if (isWhitespace$1(c) || c === CharCodes.Gt) {
|
|
this.cbs.onattribdata(this.sectionStart, this.index);
|
|
this.sectionStart = -1;
|
|
this.cbs.onattribend(QuoteType.Unquoted, this.index);
|
|
this.state = State.BeforeAttributeName;
|
|
this.stateBeforeAttributeName(c);
|
|
} else if (this.decodeEntities && c === CharCodes.Amp) {
|
|
this.startEntity();
|
|
}
|
|
}
|
|
stateBeforeDeclaration(c) {
|
|
if (c === CharCodes.OpeningSquareBracket) {
|
|
this.state = State.CDATASequence;
|
|
this.sequenceIndex = 0;
|
|
} else {
|
|
this.state =
|
|
c === CharCodes.Dash ? State.BeforeComment : State.InDeclaration;
|
|
}
|
|
}
|
|
stateInDeclaration(c) {
|
|
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
|
this.cbs.ondeclaration(this.sectionStart, this.index);
|
|
this.state = State.Text;
|
|
this.sectionStart = this.index + 1;
|
|
}
|
|
}
|
|
stateInProcessingInstruction(c) {
|
|
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
|
this.cbs.onprocessinginstruction(this.sectionStart, this.index);
|
|
this.state = State.Text;
|
|
this.sectionStart = this.index + 1;
|
|
}
|
|
}
|
|
stateBeforeComment(c) {
|
|
if (c === CharCodes.Dash) {
|
|
this.state = State.InCommentLike;
|
|
this.currentSequence = Sequences.CommentEnd;
|
|
// Allow short comments (eg. <!-->)
|
|
this.sequenceIndex = 2;
|
|
this.sectionStart = this.index + 1;
|
|
} else {
|
|
this.state = State.InDeclaration;
|
|
}
|
|
}
|
|
stateInSpecialComment(c) {
|
|
if (c === CharCodes.Gt || this.fastForwardTo(CharCodes.Gt)) {
|
|
this.cbs.oncomment(this.sectionStart, this.index, 0);
|
|
this.state = State.Text;
|
|
this.sectionStart = this.index + 1;
|
|
}
|
|
}
|
|
stateBeforeSpecialS(c) {
|
|
const lower = c | 0x20;
|
|
if (lower === Sequences.ScriptEnd[3]) {
|
|
this.startSpecial(Sequences.ScriptEnd, 4);
|
|
} else if (lower === Sequences.StyleEnd[3]) {
|
|
this.startSpecial(Sequences.StyleEnd, 4);
|
|
} else {
|
|
this.state = State.InTagName;
|
|
this.stateInTagName(c); // Consume the token again
|
|
}
|
|
}
|
|
stateBeforeSpecialT(c) {
|
|
const lower = c | 0x20;
|
|
switch (lower) {
|
|
case Sequences.TitleEnd[3]: {
|
|
this.startSpecial(Sequences.TitleEnd, 4);
|
|
break;
|
|
}
|
|
case Sequences.TextareaEnd[3]: {
|
|
this.startSpecial(Sequences.TextareaEnd, 4);
|
|
break;
|
|
}
|
|
case Sequences.XmpEnd[3]: {
|
|
this.startSpecial(Sequences.XmpEnd, 4);
|
|
break;
|
|
}
|
|
default: {
|
|
this.state = State.InTagName;
|
|
this.stateInTagName(c); // Consume the token again
|
|
}
|
|
}
|
|
}
|
|
startEntity() {
|
|
this.baseState = this.state;
|
|
this.state = State.InEntity;
|
|
this.entityStart = this.index;
|
|
this.entityDecoder.startEntity(
|
|
this.xmlMode
|
|
? DecodingMode.Strict
|
|
: this.baseState === State.Text || this.baseState === State.InSpecialTag
|
|
? DecodingMode.Legacy
|
|
: DecodingMode.Attribute
|
|
);
|
|
}
|
|
stateInEntity() {
|
|
const indexInBuffer = this.index - this.offset;
|
|
const length = this.entityDecoder.write(this.buffer, indexInBuffer);
|
|
// If `length` is positive, we are done with the entity.
|
|
if (length >= 0) {
|
|
this.state = this.baseState;
|
|
if (length === 0) {
|
|
this.index -= 1;
|
|
}
|
|
} else {
|
|
if (
|
|
indexInBuffer < this.buffer.length &&
|
|
this.buffer.charCodeAt(indexInBuffer) === CharCodes.Amp
|
|
) {
|
|
this.state = this.baseState;
|
|
this.index -= 1;
|
|
return;
|
|
}
|
|
// Mark buffer as consumed.
|
|
this.index = this.offset + this.buffer.length - 1;
|
|
}
|
|
}
|
|
/**
|
|
* Remove data that has already been consumed from the buffer.
|
|
*/
|
|
cleanup() {
|
|
// If we are inside of text or attributes, emit what we already have.
|
|
if (this.running && this.sectionStart !== this.index) {
|
|
if (
|
|
this.state === State.Text ||
|
|
(this.state === State.InSpecialTag && this.sequenceIndex === 0)
|
|
) {
|
|
this.cbs.ontext(this.sectionStart, this.index);
|
|
this.sectionStart = this.index;
|
|
} else if (
|
|
this.state === State.InAttributeValueDq ||
|
|
this.state === State.InAttributeValueSq ||
|
|
this.state === State.InAttributeValueNq
|
|
) {
|
|
this.cbs.onattribdata(this.sectionStart, this.index);
|
|
this.sectionStart = this.index;
|
|
}
|
|
}
|
|
}
|
|
shouldContinue() {
|
|
return this.index < this.buffer.length + this.offset && this.running;
|
|
}
|
|
/**
|
|
* Iterates through the buffer, calling the function corresponding to the current state.
|
|
*
|
|
* States that are more likely to be hit are higher up, as a performance improvement.
|
|
*/
|
|
parse() {
|
|
while (this.shouldContinue()) {
|
|
const c = this.buffer.charCodeAt(this.index - this.offset);
|
|
switch (this.state) {
|
|
case State.Text: {
|
|
this.stateText(c);
|
|
break;
|
|
}
|
|
case State.SpecialStartSequence: {
|
|
this.stateSpecialStartSequence(c);
|
|
break;
|
|
}
|
|
case State.InSpecialTag: {
|
|
this.stateInSpecialTag(c);
|
|
break;
|
|
}
|
|
case State.CDATASequence: {
|
|
this.stateCDATASequence(c);
|
|
break;
|
|
}
|
|
case State.InAttributeValueDq: {
|
|
this.stateInAttributeValueDoubleQuotes(c);
|
|
break;
|
|
}
|
|
case State.InAttributeName: {
|
|
this.stateInAttributeName(c);
|
|
break;
|
|
}
|
|
case State.InCommentLike: {
|
|
this.stateInCommentLike(c);
|
|
break;
|
|
}
|
|
case State.InSpecialComment: {
|
|
this.stateInSpecialComment(c);
|
|
break;
|
|
}
|
|
case State.BeforeAttributeName: {
|
|
this.stateBeforeAttributeName(c);
|
|
break;
|
|
}
|
|
case State.InTagName: {
|
|
this.stateInTagName(c);
|
|
break;
|
|
}
|
|
case State.InClosingTagName: {
|
|
this.stateInClosingTagName(c);
|
|
break;
|
|
}
|
|
case State.BeforeTagName: {
|
|
this.stateBeforeTagName(c);
|
|
break;
|
|
}
|
|
case State.AfterAttributeName: {
|
|
this.stateAfterAttributeName(c);
|
|
break;
|
|
}
|
|
case State.InAttributeValueSq: {
|
|
this.stateInAttributeValueSingleQuotes(c);
|
|
break;
|
|
}
|
|
case State.BeforeAttributeValue: {
|
|
this.stateBeforeAttributeValue(c);
|
|
break;
|
|
}
|
|
case State.BeforeClosingTagName: {
|
|
this.stateBeforeClosingTagName(c);
|
|
break;
|
|
}
|
|
case State.AfterClosingTagName: {
|
|
this.stateAfterClosingTagName(c);
|
|
break;
|
|
}
|
|
case State.BeforeSpecialS: {
|
|
this.stateBeforeSpecialS(c);
|
|
break;
|
|
}
|
|
case State.BeforeSpecialT: {
|
|
this.stateBeforeSpecialT(c);
|
|
break;
|
|
}
|
|
case State.InAttributeValueNq: {
|
|
this.stateInAttributeValueNoQuotes(c);
|
|
break;
|
|
}
|
|
case State.InSelfClosingTag: {
|
|
this.stateInSelfClosingTag(c);
|
|
break;
|
|
}
|
|
case State.InDeclaration: {
|
|
this.stateInDeclaration(c);
|
|
break;
|
|
}
|
|
case State.BeforeDeclaration: {
|
|
this.stateBeforeDeclaration(c);
|
|
break;
|
|
}
|
|
case State.BeforeComment: {
|
|
this.stateBeforeComment(c);
|
|
break;
|
|
}
|
|
case State.InProcessingInstruction: {
|
|
this.stateInProcessingInstruction(c);
|
|
break;
|
|
}
|
|
case State.InEntity: {
|
|
this.stateInEntity();
|
|
break;
|
|
}
|
|
}
|
|
this.index++;
|
|
}
|
|
this.cleanup();
|
|
}
|
|
finish() {
|
|
if (this.state === State.InEntity) {
|
|
this.entityDecoder.end();
|
|
this.state = this.baseState;
|
|
}
|
|
this.handleTrailingData();
|
|
this.cbs.onend();
|
|
}
|
|
/** Handle any trailing data. */
|
|
handleTrailingData() {
|
|
const endIndex = this.buffer.length + this.offset;
|
|
// If there is no remaining data, we are done.
|
|
if (this.sectionStart >= endIndex) {
|
|
return;
|
|
}
|
|
if (this.state === State.InCommentLike) {
|
|
if (this.currentSequence === Sequences.CdataEnd) {
|
|
this.cbs.oncdata(this.sectionStart, endIndex, 0);
|
|
} else {
|
|
this.cbs.oncomment(this.sectionStart, endIndex, 0);
|
|
}
|
|
} else if (
|
|
this.state === State.InTagName ||
|
|
this.state === State.BeforeAttributeName ||
|
|
this.state === State.BeforeAttributeValue ||
|
|
this.state === State.AfterAttributeName ||
|
|
this.state === State.InAttributeName ||
|
|
this.state === State.InAttributeValueSq ||
|
|
this.state === State.InAttributeValueDq ||
|
|
this.state === State.InAttributeValueNq ||
|
|
this.state === State.InClosingTagName
|
|
);
|
|
else {
|
|
this.cbs.ontext(this.sectionStart, endIndex);
|
|
}
|
|
}
|
|
emitCodePoint(cp, consumed) {
|
|
if (
|
|
this.baseState !== State.Text &&
|
|
this.baseState !== State.InSpecialTag
|
|
) {
|
|
if (this.sectionStart < this.entityStart) {
|
|
this.cbs.onattribdata(this.sectionStart, this.entityStart);
|
|
}
|
|
this.sectionStart = this.entityStart + consumed;
|
|
this.index = this.sectionStart - 1;
|
|
this.cbs.onattribentity(cp);
|
|
} else {
|
|
if (this.sectionStart < this.entityStart) {
|
|
this.cbs.ontext(this.sectionStart, this.entityStart);
|
|
}
|
|
this.sectionStart = this.entityStart + consumed;
|
|
this.index = this.sectionStart - 1;
|
|
this.cbs.ontextentity(cp, this.sectionStart);
|
|
}
|
|
}
|
|
}
|
|
|
|
const formTags = new Set([
|
|
"input",
|
|
"option",
|
|
"optgroup",
|
|
"select",
|
|
"button",
|
|
"datalist",
|
|
"textarea"
|
|
]);
|
|
const pTag = new Set(["p"]);
|
|
const tableSectionTags = new Set(["thead", "tbody"]);
|
|
const ddtTags = new Set(["dd", "dt"]);
|
|
const rtpTags = new Set(["rt", "rp"]);
|
|
const openImpliesClose = new Map([
|
|
["tr", new Set(["tr", "th", "td"])],
|
|
["th", new Set(["th"])],
|
|
["td", new Set(["thead", "th", "td"])],
|
|
["body", new Set(["head", "link", "script"])],
|
|
["li", new Set(["li"])],
|
|
["p", pTag],
|
|
["h1", pTag],
|
|
["h2", pTag],
|
|
["h3", pTag],
|
|
["h4", pTag],
|
|
["h5", pTag],
|
|
["h6", pTag],
|
|
["select", formTags],
|
|
["input", formTags],
|
|
["output", formTags],
|
|
["button", formTags],
|
|
["datalist", formTags],
|
|
["textarea", formTags],
|
|
["option", new Set(["option"])],
|
|
["optgroup", new Set(["optgroup", "option"])],
|
|
["dd", ddtTags],
|
|
["dt", ddtTags],
|
|
["address", pTag],
|
|
["article", pTag],
|
|
["aside", pTag],
|
|
["blockquote", pTag],
|
|
["details", pTag],
|
|
["div", pTag],
|
|
["dl", pTag],
|
|
["fieldset", pTag],
|
|
["figcaption", pTag],
|
|
["figure", pTag],
|
|
["footer", pTag],
|
|
["form", pTag],
|
|
["header", pTag],
|
|
["hr", pTag],
|
|
["main", pTag],
|
|
["nav", pTag],
|
|
["ol", pTag],
|
|
["pre", pTag],
|
|
["section", pTag],
|
|
["table", pTag],
|
|
["ul", pTag],
|
|
["rt", rtpTags],
|
|
["rp", rtpTags],
|
|
["tbody", tableSectionTags],
|
|
["tfoot", tableSectionTags]
|
|
]);
|
|
const voidElements$1 = new Set([
|
|
"area",
|
|
"base",
|
|
"basefont",
|
|
"br",
|
|
"col",
|
|
"command",
|
|
"embed",
|
|
"frame",
|
|
"hr",
|
|
"img",
|
|
"input",
|
|
"isindex",
|
|
"keygen",
|
|
"link",
|
|
"meta",
|
|
"param",
|
|
"source",
|
|
"track",
|
|
"wbr"
|
|
]);
|
|
const foreignContextElements = new Set(["math", "svg"]);
|
|
const htmlIntegrationElements = new Set([
|
|
"mi",
|
|
"mo",
|
|
"mn",
|
|
"ms",
|
|
"mtext",
|
|
"annotation-xml",
|
|
"foreignobject",
|
|
"desc",
|
|
"title"
|
|
]);
|
|
const reNameEnd = /\s|\//;
|
|
let Parser$1 = class Parser {
|
|
constructor(cbs, options = {}) {
|
|
var _a, _b, _c, _d, _e, _f;
|
|
this.options = options;
|
|
/** The start index of the last event. */
|
|
this.startIndex = 0;
|
|
/** The end index of the last event. */
|
|
this.endIndex = 0;
|
|
/**
|
|
* Store the start index of the current open tag,
|
|
* so we can update the start index for attributes.
|
|
*/
|
|
this.openTagStart = 0;
|
|
this.tagname = "";
|
|
this.attribname = "";
|
|
this.attribvalue = "";
|
|
this.attribs = null;
|
|
this.stack = [];
|
|
this.buffers = [];
|
|
this.bufferOffset = 0;
|
|
/** The index of the last written buffer. Used when resuming after a `pause()`. */
|
|
this.writeIndex = 0;
|
|
/** Indicates whether the parser has finished running / `.end` has been called. */
|
|
this.ended = false;
|
|
this.cbs = cbs !== null && cbs !== void 0 ? cbs : {};
|
|
this.htmlMode = !this.options.xmlMode;
|
|
this.lowerCaseTagNames =
|
|
(_a = options.lowerCaseTags) !== null && _a !== void 0
|
|
? _a
|
|
: this.htmlMode;
|
|
this.lowerCaseAttributeNames =
|
|
(_b = options.lowerCaseAttributeNames) !== null && _b !== void 0
|
|
? _b
|
|
: this.htmlMode;
|
|
this.recognizeSelfClosing =
|
|
(_c = options.recognizeSelfClosing) !== null && _c !== void 0
|
|
? _c
|
|
: !this.htmlMode;
|
|
this.tokenizer = new (
|
|
(_d = options.Tokenizer) !== null && _d !== void 0 ? _d : Tokenizer
|
|
)(this.options, this);
|
|
this.foreignContext = [!this.htmlMode];
|
|
(_f = (_e = this.cbs).onparserinit) === null || _f === void 0
|
|
? void 0
|
|
: _f.call(_e, this);
|
|
}
|
|
// Tokenizer event handlers
|
|
/** @internal */
|
|
ontext(start, endIndex) {
|
|
var _a, _b;
|
|
const data = this.getSlice(start, endIndex);
|
|
this.endIndex = endIndex - 1;
|
|
(_b = (_a = this.cbs).ontext) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, data);
|
|
this.startIndex = endIndex;
|
|
}
|
|
/** @internal */
|
|
ontextentity(cp, endIndex) {
|
|
var _a, _b;
|
|
this.endIndex = endIndex - 1;
|
|
(_b = (_a = this.cbs).ontext) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, fromCodePoint(cp));
|
|
this.startIndex = endIndex;
|
|
}
|
|
/**
|
|
* Checks if the current tag is a void element. Override this if you want
|
|
* to specify your own additional void elements.
|
|
*/
|
|
isVoidElement(name) {
|
|
return this.htmlMode && voidElements$1.has(name);
|
|
}
|
|
/** @internal */
|
|
onopentagname(start, endIndex) {
|
|
this.endIndex = endIndex;
|
|
let name = this.getSlice(start, endIndex);
|
|
if (this.lowerCaseTagNames) {
|
|
name = name.toLowerCase();
|
|
}
|
|
this.emitOpenTag(name);
|
|
}
|
|
emitOpenTag(name) {
|
|
var _a, _b, _c, _d;
|
|
this.openTagStart = this.startIndex;
|
|
this.tagname = name;
|
|
const impliesClose = this.htmlMode && openImpliesClose.get(name);
|
|
if (impliesClose) {
|
|
while (this.stack.length > 0 && impliesClose.has(this.stack[0])) {
|
|
const element = this.stack.shift();
|
|
(_b = (_a = this.cbs).onclosetag) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, element, true);
|
|
}
|
|
}
|
|
if (!this.isVoidElement(name)) {
|
|
this.stack.unshift(name);
|
|
if (this.htmlMode) {
|
|
if (foreignContextElements.has(name)) {
|
|
this.foreignContext.unshift(true);
|
|
} else if (htmlIntegrationElements.has(name)) {
|
|
this.foreignContext.unshift(false);
|
|
}
|
|
}
|
|
}
|
|
(_d = (_c = this.cbs).onopentagname) === null || _d === void 0
|
|
? void 0
|
|
: _d.call(_c, name);
|
|
if (this.cbs.onopentag) this.attribs = {};
|
|
}
|
|
endOpenTag(isImplied) {
|
|
var _a, _b;
|
|
this.startIndex = this.openTagStart;
|
|
if (this.attribs) {
|
|
(_b = (_a = this.cbs).onopentag) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, this.tagname, this.attribs, isImplied);
|
|
this.attribs = null;
|
|
}
|
|
if (this.cbs.onclosetag && this.isVoidElement(this.tagname)) {
|
|
this.cbs.onclosetag(this.tagname, true);
|
|
}
|
|
this.tagname = "";
|
|
}
|
|
/** @internal */
|
|
onopentagend(endIndex) {
|
|
this.endIndex = endIndex;
|
|
this.endOpenTag(false);
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
onclosetag(start, endIndex) {
|
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
this.endIndex = endIndex;
|
|
let name = this.getSlice(start, endIndex);
|
|
if (this.lowerCaseTagNames) {
|
|
name = name.toLowerCase();
|
|
}
|
|
if (
|
|
this.htmlMode &&
|
|
(foreignContextElements.has(name) || htmlIntegrationElements.has(name))
|
|
) {
|
|
this.foreignContext.shift();
|
|
}
|
|
if (!this.isVoidElement(name)) {
|
|
const pos = this.stack.indexOf(name);
|
|
if (pos !== -1) {
|
|
for (let index = 0; index <= pos; index++) {
|
|
const element = this.stack.shift();
|
|
// We know the stack has sufficient elements.
|
|
(_b = (_a = this.cbs).onclosetag) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, element, index !== pos);
|
|
}
|
|
} else if (this.htmlMode && name === "p") {
|
|
// Implicit open before close
|
|
this.emitOpenTag("p");
|
|
this.closeCurrentTag(true);
|
|
}
|
|
} else if (this.htmlMode && name === "br") {
|
|
// We can't use `emitOpenTag` for implicit open, as `br` would be implicitly closed.
|
|
(_d = (_c = this.cbs).onopentagname) === null || _d === void 0
|
|
? void 0
|
|
: _d.call(_c, "br");
|
|
(_f = (_e = this.cbs).onopentag) === null || _f === void 0
|
|
? void 0
|
|
: _f.call(_e, "br", {}, true);
|
|
(_h = (_g = this.cbs).onclosetag) === null || _h === void 0
|
|
? void 0
|
|
: _h.call(_g, "br", false);
|
|
}
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
onselfclosingtag(endIndex) {
|
|
this.endIndex = endIndex;
|
|
if (this.recognizeSelfClosing || this.foreignContext[0]) {
|
|
this.closeCurrentTag(false);
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
} else {
|
|
// Ignore the fact that the tag is self-closing.
|
|
this.onopentagend(endIndex);
|
|
}
|
|
}
|
|
closeCurrentTag(isOpenImplied) {
|
|
var _a, _b;
|
|
const name = this.tagname;
|
|
this.endOpenTag(isOpenImplied);
|
|
// Self-closing tags will be on the top of the stack
|
|
if (this.stack[0] === name) {
|
|
// If the opening tag isn't implied, the closing tag has to be implied.
|
|
(_b = (_a = this.cbs).onclosetag) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, name, !isOpenImplied);
|
|
this.stack.shift();
|
|
}
|
|
}
|
|
/** @internal */
|
|
onattribname(start, endIndex) {
|
|
this.startIndex = start;
|
|
const name = this.getSlice(start, endIndex);
|
|
this.attribname = this.lowerCaseAttributeNames ? name.toLowerCase() : name;
|
|
}
|
|
/** @internal */
|
|
onattribdata(start, endIndex) {
|
|
this.attribvalue += this.getSlice(start, endIndex);
|
|
}
|
|
/** @internal */
|
|
onattribentity(cp) {
|
|
this.attribvalue += fromCodePoint(cp);
|
|
}
|
|
/** @internal */
|
|
onattribend(quote, endIndex) {
|
|
var _a, _b;
|
|
this.endIndex = endIndex;
|
|
(_b = (_a = this.cbs).onattribute) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(
|
|
_a,
|
|
this.attribname,
|
|
this.attribvalue,
|
|
quote === QuoteType.Double
|
|
? '"'
|
|
: quote === QuoteType.Single
|
|
? "'"
|
|
: quote === QuoteType.NoValue
|
|
? undefined
|
|
: null
|
|
);
|
|
if (
|
|
this.attribs &&
|
|
!Object.prototype.hasOwnProperty.call(this.attribs, this.attribname)
|
|
) {
|
|
this.attribs[this.attribname] = this.attribvalue;
|
|
}
|
|
this.attribvalue = "";
|
|
}
|
|
getInstructionName(value) {
|
|
const index = value.search(reNameEnd);
|
|
let name = index < 0 ? value : value.substr(0, index);
|
|
if (this.lowerCaseTagNames) {
|
|
name = name.toLowerCase();
|
|
}
|
|
return name;
|
|
}
|
|
/** @internal */
|
|
ondeclaration(start, endIndex) {
|
|
this.endIndex = endIndex;
|
|
const value = this.getSlice(start, endIndex);
|
|
if (this.cbs.onprocessinginstruction) {
|
|
const name = this.getInstructionName(value);
|
|
this.cbs.onprocessinginstruction(`!${name}`, `!${value}`);
|
|
}
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
onprocessinginstruction(start, endIndex) {
|
|
this.endIndex = endIndex;
|
|
const value = this.getSlice(start, endIndex);
|
|
if (this.cbs.onprocessinginstruction) {
|
|
const name = this.getInstructionName(value);
|
|
this.cbs.onprocessinginstruction(`?${name}`, `?${value}`);
|
|
}
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
oncomment(start, endIndex, offset) {
|
|
var _a, _b, _c, _d;
|
|
this.endIndex = endIndex;
|
|
(_b = (_a = this.cbs).oncomment) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, this.getSlice(start, endIndex - offset));
|
|
(_d = (_c = this.cbs).oncommentend) === null || _d === void 0
|
|
? void 0
|
|
: _d.call(_c);
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
oncdata(start, endIndex, offset) {
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
this.endIndex = endIndex;
|
|
const value = this.getSlice(start, endIndex - offset);
|
|
if (!this.htmlMode || this.options.recognizeCDATA) {
|
|
(_b = (_a = this.cbs).oncdatastart) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a);
|
|
(_d = (_c = this.cbs).ontext) === null || _d === void 0
|
|
? void 0
|
|
: _d.call(_c, value);
|
|
(_f = (_e = this.cbs).oncdataend) === null || _f === void 0
|
|
? void 0
|
|
: _f.call(_e);
|
|
} else {
|
|
(_h = (_g = this.cbs).oncomment) === null || _h === void 0
|
|
? void 0
|
|
: _h.call(_g, `[CDATA[${value}]]`);
|
|
(_k = (_j = this.cbs).oncommentend) === null || _k === void 0
|
|
? void 0
|
|
: _k.call(_j);
|
|
}
|
|
// Set `startIndex` for next node
|
|
this.startIndex = endIndex + 1;
|
|
}
|
|
/** @internal */
|
|
onend() {
|
|
var _a, _b;
|
|
if (this.cbs.onclosetag) {
|
|
// Set the end index for all remaining tags
|
|
this.endIndex = this.startIndex;
|
|
for (let index = 0; index < this.stack.length; index++) {
|
|
this.cbs.onclosetag(this.stack[index], true);
|
|
}
|
|
}
|
|
(_b = (_a = this.cbs).onend) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a);
|
|
}
|
|
/**
|
|
* Resets the parser to a blank state, ready to parse a new HTML document
|
|
*/
|
|
reset() {
|
|
var _a, _b, _c, _d;
|
|
(_b = (_a = this.cbs).onreset) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a);
|
|
this.tokenizer.reset();
|
|
this.tagname = "";
|
|
this.attribname = "";
|
|
this.attribs = null;
|
|
this.stack.length = 0;
|
|
this.startIndex = 0;
|
|
this.endIndex = 0;
|
|
(_d = (_c = this.cbs).onparserinit) === null || _d === void 0
|
|
? void 0
|
|
: _d.call(_c, this);
|
|
this.buffers.length = 0;
|
|
this.foreignContext.length = 0;
|
|
this.foreignContext.unshift(!this.htmlMode);
|
|
this.bufferOffset = 0;
|
|
this.writeIndex = 0;
|
|
this.ended = false;
|
|
}
|
|
/**
|
|
* Resets the parser, then parses a complete document and
|
|
* pushes it to the handler.
|
|
*
|
|
* @param data Document to parse.
|
|
*/
|
|
parseComplete(data) {
|
|
this.reset();
|
|
this.end(data);
|
|
}
|
|
getSlice(start, end) {
|
|
while (start - this.bufferOffset >= this.buffers[0].length) {
|
|
this.shiftBuffer();
|
|
}
|
|
let slice = this.buffers[0].slice(
|
|
start - this.bufferOffset,
|
|
end - this.bufferOffset
|
|
);
|
|
while (end - this.bufferOffset > this.buffers[0].length) {
|
|
this.shiftBuffer();
|
|
slice += this.buffers[0].slice(0, end - this.bufferOffset);
|
|
}
|
|
return slice;
|
|
}
|
|
shiftBuffer() {
|
|
this.bufferOffset += this.buffers[0].length;
|
|
this.writeIndex--;
|
|
this.buffers.shift();
|
|
}
|
|
/**
|
|
* Parses a chunk of data and calls the corresponding callbacks.
|
|
*
|
|
* @param chunk Chunk to parse.
|
|
*/
|
|
write(chunk) {
|
|
var _a, _b;
|
|
if (this.ended) {
|
|
(_b = (_a = this.cbs).onerror) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, new Error(".write() after done!"));
|
|
return;
|
|
}
|
|
this.buffers.push(chunk);
|
|
if (this.tokenizer.running) {
|
|
this.tokenizer.write(chunk);
|
|
this.writeIndex++;
|
|
}
|
|
}
|
|
/**
|
|
* Parses the end of the buffer and clears the stack, calls onend.
|
|
*
|
|
* @param chunk Optional final chunk to parse.
|
|
*/
|
|
end(chunk) {
|
|
var _a, _b;
|
|
if (this.ended) {
|
|
(_b = (_a = this.cbs).onerror) === null || _b === void 0
|
|
? void 0
|
|
: _b.call(_a, new Error(".end() after done!"));
|
|
return;
|
|
}
|
|
if (chunk) this.write(chunk);
|
|
this.ended = true;
|
|
this.tokenizer.end();
|
|
}
|
|
/**
|
|
* Pauses parsing. The parser won't emit events until `resume` is called.
|
|
*/
|
|
pause() {
|
|
this.tokenizer.pause();
|
|
}
|
|
/**
|
|
* Resumes parsing after `pause` was called.
|
|
*/
|
|
resume() {
|
|
this.tokenizer.resume();
|
|
while (this.tokenizer.running && this.writeIndex < this.buffers.length) {
|
|
this.tokenizer.write(this.buffers[this.writeIndex++]);
|
|
}
|
|
if (this.ended) this.tokenizer.end();
|
|
}
|
|
/**
|
|
* Alias of `write`, for backwards compatibility.
|
|
*
|
|
* @param chunk Chunk to parse.
|
|
* @deprecated
|
|
*/
|
|
parseChunk(chunk) {
|
|
this.write(chunk);
|
|
}
|
|
/**
|
|
* Alias of `end`, for backwards compatibility.
|
|
*
|
|
* @param chunk Optional final chunk to parse.
|
|
* @deprecated
|
|
*/
|
|
done(chunk) {
|
|
this.end(chunk);
|
|
}
|
|
};
|
|
|
|
var HTMLParser2 = /*#__PURE__*/ Object.freeze({
|
|
__proto__: null,
|
|
Parser: Parser$1,
|
|
get QuoteType() {
|
|
return QuoteType;
|
|
},
|
|
Tokenizer: Tokenizer
|
|
});
|
|
|
|
// Internal
|
|
const NODE_END = -1;
|
|
|
|
// Node
|
|
const ELEMENT_NODE = 1;
|
|
const ATTRIBUTE_NODE = 2;
|
|
const TEXT_NODE = 3;
|
|
const CDATA_SECTION_NODE = 4;
|
|
const COMMENT_NODE = 8;
|
|
const DOCUMENT_NODE = 9;
|
|
const DOCUMENT_TYPE_NODE = 10;
|
|
const DOCUMENT_FRAGMENT_NODE = 11;
|
|
|
|
// Elements
|
|
const BLOCK_ELEMENTS = new Set([
|
|
"ARTICLE",
|
|
"ASIDE",
|
|
"BLOCKQUOTE",
|
|
"BODY",
|
|
"BR",
|
|
"BUTTON",
|
|
"CANVAS",
|
|
"CAPTION",
|
|
"COL",
|
|
"COLGROUP",
|
|
"DD",
|
|
"DIV",
|
|
"DL",
|
|
"DT",
|
|
"EMBED",
|
|
"FIELDSET",
|
|
"FIGCAPTION",
|
|
"FIGURE",
|
|
"FOOTER",
|
|
"FORM",
|
|
"H1",
|
|
"H2",
|
|
"H3",
|
|
"H4",
|
|
"H5",
|
|
"H6",
|
|
"LI",
|
|
"UL",
|
|
"OL",
|
|
"P"
|
|
]);
|
|
|
|
// TreeWalker
|
|
const SHOW_ALL = -1;
|
|
const SHOW_ELEMENT = 1;
|
|
const SHOW_TEXT = 4;
|
|
const SHOW_CDATA_SECTION = 8;
|
|
const SHOW_COMMENT = 128;
|
|
|
|
// Document position
|
|
const DOCUMENT_POSITION_DISCONNECTED = 0x01;
|
|
const DOCUMENT_POSITION_PRECEDING = 0x02;
|
|
const DOCUMENT_POSITION_FOLLOWING = 0x04;
|
|
const DOCUMENT_POSITION_CONTAINS = 0x08;
|
|
const DOCUMENT_POSITION_CONTAINED_BY = 0x10;
|
|
const DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
|
|
|
|
// SVG
|
|
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
|
|
const {
|
|
assign,
|
|
create: create$1,
|
|
defineProperties,
|
|
entries,
|
|
keys,
|
|
setPrototypeOf
|
|
} = Object;
|
|
|
|
const $String = String;
|
|
|
|
const getEnd = (node) => (node.nodeType === ELEMENT_NODE ? node[END] : node);
|
|
|
|
const ignoreCase = ({ ownerDocument }) => ownerDocument[MIME].ignoreCase;
|
|
|
|
const knownAdjacent = (prev, next) => {
|
|
prev[NEXT] = next;
|
|
next[PREV] = prev;
|
|
};
|
|
|
|
const knownBoundaries = (prev, current, next) => {
|
|
knownAdjacent(prev, current);
|
|
knownAdjacent(getEnd(current), next);
|
|
};
|
|
|
|
const knownSegment = (prev, start, end, next) => {
|
|
knownAdjacent(prev, start);
|
|
knownAdjacent(getEnd(end), next);
|
|
};
|
|
|
|
const knownSiblings = (prev, current, next) => {
|
|
knownAdjacent(prev, current);
|
|
knownAdjacent(current, next);
|
|
};
|
|
|
|
const localCase = ({ localName, ownerDocument }) => {
|
|
return ownerDocument[MIME].ignoreCase ? localName.toUpperCase() : localName;
|
|
};
|
|
|
|
const setAdjacent = (prev, next) => {
|
|
if (prev) prev[NEXT] = next;
|
|
if (next) next[PREV] = prev;
|
|
};
|
|
|
|
/**
|
|
* @param {import("../interface/document.js").Document} ownerDocument
|
|
* @param {string} html
|
|
* @return {import("../interface/document-fragment.js").DocumentFragment}
|
|
*/
|
|
const htmlToFragment = (ownerDocument, html) => {
|
|
const fragment = ownerDocument.createDocumentFragment();
|
|
|
|
const elem = ownerDocument.createElement("");
|
|
elem.innerHTML = html;
|
|
const { firstChild, lastChild } = elem;
|
|
|
|
if (firstChild) {
|
|
knownSegment(fragment, firstChild, lastChild, fragment[END]);
|
|
|
|
let child = firstChild;
|
|
do {
|
|
child.parentNode = fragment;
|
|
} while (child !== lastChild && (child = getEnd(child)[NEXT]));
|
|
}
|
|
|
|
return fragment;
|
|
};
|
|
|
|
const shadowRoots = new WeakMap();
|
|
|
|
let reactive = false;
|
|
|
|
const Classes = new WeakMap();
|
|
|
|
const customElements = new WeakMap();
|
|
|
|
const attributeChangedCallback$1 = (
|
|
element,
|
|
attributeName,
|
|
oldValue,
|
|
newValue
|
|
) => {
|
|
if (
|
|
reactive &&
|
|
customElements.has(element) &&
|
|
element.attributeChangedCallback &&
|
|
element.constructor.observedAttributes.includes(attributeName)
|
|
) {
|
|
element.attributeChangedCallback(attributeName, oldValue, newValue);
|
|
}
|
|
};
|
|
|
|
const createTrigger = (method, isConnected) => (element) => {
|
|
if (customElements.has(element)) {
|
|
const info = customElements.get(element);
|
|
if (info.connected !== isConnected && element.isConnected === isConnected) {
|
|
info.connected = isConnected;
|
|
if (method in element) element[method]();
|
|
}
|
|
}
|
|
};
|
|
|
|
const triggerConnected = createTrigger("connectedCallback", true);
|
|
const connectedCallback = (element) => {
|
|
if (reactive) {
|
|
triggerConnected(element);
|
|
if (shadowRoots.has(element)) element = shadowRoots.get(element).shadowRoot;
|
|
let { [NEXT]: next, [END]: end } = element;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE) triggerConnected(next);
|
|
next = next[NEXT];
|
|
}
|
|
}
|
|
};
|
|
|
|
const triggerDisconnected = createTrigger("disconnectedCallback", false);
|
|
const disconnectedCallback = (element) => {
|
|
if (reactive) {
|
|
triggerDisconnected(element);
|
|
if (shadowRoots.has(element)) element = shadowRoots.get(element).shadowRoot;
|
|
let { [NEXT]: next, [END]: end } = element;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE) triggerDisconnected(next);
|
|
next = next[NEXT];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.CustomElementRegistry
|
|
*/
|
|
class CustomElementRegistry {
|
|
/**
|
|
* @param {Document} ownerDocument
|
|
*/
|
|
constructor(ownerDocument) {
|
|
/**
|
|
* @private
|
|
*/
|
|
this.ownerDocument = ownerDocument;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.registry = new Map();
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.waiting = new Map();
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.active = false;
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName the custom element definition name
|
|
* @param {Function} Class the custom element **Class** definition
|
|
* @param {object?} options the optional object with an `extends` property
|
|
*/
|
|
define(localName, Class, options = {}) {
|
|
const { ownerDocument, registry, waiting } = this;
|
|
|
|
if (registry.has(localName))
|
|
throw new Error("unable to redefine " + localName);
|
|
|
|
if (Classes.has(Class))
|
|
throw new Error("unable to redefine the same class: " + Class);
|
|
|
|
this.active = reactive = true;
|
|
|
|
const { extends: extend } = options;
|
|
|
|
Classes.set(Class, {
|
|
ownerDocument,
|
|
options: { is: extend ? localName : "" },
|
|
localName: extend || localName
|
|
});
|
|
|
|
const check = extend
|
|
? (element) => {
|
|
return (
|
|
element.localName === extend &&
|
|
element.getAttribute("is") === localName
|
|
);
|
|
}
|
|
: (element) => element.localName === localName;
|
|
registry.set(localName, { Class, check });
|
|
if (waiting.has(localName)) {
|
|
for (const resolve of waiting.get(localName)) resolve(Class);
|
|
waiting.delete(localName);
|
|
}
|
|
ownerDocument
|
|
.querySelectorAll(extend ? `${extend}[is="${localName}"]` : localName)
|
|
.forEach(this.upgrade, this);
|
|
}
|
|
|
|
/**
|
|
* @param {Element} element
|
|
*/
|
|
upgrade(element) {
|
|
if (customElements.has(element)) return;
|
|
const { ownerDocument, registry } = this;
|
|
const ce = element.getAttribute("is") || element.localName;
|
|
if (registry.has(ce)) {
|
|
const { Class, check } = registry.get(ce);
|
|
if (check(element)) {
|
|
const { attributes, isConnected } = element;
|
|
for (const attr of attributes) element.removeAttributeNode(attr);
|
|
|
|
const values = entries(element);
|
|
for (const [key] of values) delete element[key];
|
|
|
|
setPrototypeOf(element, Class.prototype);
|
|
ownerDocument[UPGRADE] = { element, values };
|
|
new Class(ownerDocument, ce);
|
|
|
|
customElements.set(element, { connected: isConnected });
|
|
|
|
for (const attr of attributes) element.setAttributeNode(attr);
|
|
|
|
if (isConnected && element.connectedCallback)
|
|
element.connectedCallback();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName the custom element definition name
|
|
*/
|
|
whenDefined(localName) {
|
|
const { registry, waiting } = this;
|
|
return new Promise((resolve) => {
|
|
if (registry.has(localName)) resolve(registry.get(localName).Class);
|
|
else {
|
|
if (!waiting.has(localName)) waiting.set(localName, []);
|
|
waiting.get(localName).push(resolve);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {string} localName the custom element definition name
|
|
* @returns {Function?} the custom element **Class**, if any
|
|
*/
|
|
get(localName) {
|
|
const info = this.registry.get(localName);
|
|
return info && info.Class;
|
|
}
|
|
|
|
/**
|
|
* @param {Function} Class **Class** of custom element
|
|
* @returns {string?} found tag name or null
|
|
*/
|
|
getName(Class) {
|
|
if (Classes.has(Class)) {
|
|
const { localName } = Classes.get(Class);
|
|
return localName;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const { Parser } = HTMLParser2;
|
|
|
|
const append$2 = (self, node, active) => {
|
|
const end = self[END];
|
|
node.parentNode = self;
|
|
knownBoundaries(end[PREV], node, end);
|
|
if (active && node.nodeType === ELEMENT_NODE) connectedCallback(node);
|
|
return node;
|
|
};
|
|
|
|
const attribute = (element, end, attribute, value, active) => {
|
|
attribute[VALUE] = value;
|
|
attribute.ownerElement = element;
|
|
knownSiblings(end[PREV], attribute, end);
|
|
if (attribute.name === "class") element.className = value;
|
|
if (active) attributeChangedCallback$1(element, attribute.name, null, value);
|
|
};
|
|
|
|
const parseFromString = (document, isHTML, markupLanguage) => {
|
|
const { active, registry } = document[CUSTOM_ELEMENTS];
|
|
|
|
let node = document;
|
|
let ownerSVGElement = null;
|
|
let parsingCData = false;
|
|
|
|
const content = new Parser(
|
|
{
|
|
// <!DOCTYPE ...>
|
|
onprocessinginstruction(name, data) {
|
|
if (name.toLowerCase() === "!doctype")
|
|
document.doctype = data.slice(name.length).trim();
|
|
},
|
|
|
|
// <tagName>
|
|
onopentag(name, attributes) {
|
|
let create = true;
|
|
if (isHTML) {
|
|
if (ownerSVGElement) {
|
|
node = append$2(
|
|
node,
|
|
document.createElementNS(SVG_NAMESPACE, name),
|
|
active
|
|
);
|
|
node.ownerSVGElement = ownerSVGElement;
|
|
create = false;
|
|
} else if (name === "svg" || name === "SVG") {
|
|
ownerSVGElement = document.createElementNS(SVG_NAMESPACE, name);
|
|
node = append$2(node, ownerSVGElement, active);
|
|
create = false;
|
|
} else if (active) {
|
|
const ce = name.includes("-") ? name : attributes.is || "";
|
|
if (ce && registry.has(ce)) {
|
|
const { Class } = registry.get(ce);
|
|
node = append$2(node, new Class(), active);
|
|
delete attributes.is;
|
|
create = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (create) node = append$2(node, document.createElement(name), false);
|
|
|
|
let end = node[END];
|
|
for (const name of keys(attributes))
|
|
attribute(
|
|
node,
|
|
end,
|
|
document.createAttribute(name),
|
|
attributes[name],
|
|
active
|
|
);
|
|
},
|
|
|
|
// #text, #comment
|
|
oncomment(data) {
|
|
append$2(node, document.createComment(data), active);
|
|
},
|
|
ontext(text) {
|
|
if (parsingCData) {
|
|
append$2(node, document.createCDATASection(text), active);
|
|
} else {
|
|
append$2(node, document.createTextNode(text), active);
|
|
}
|
|
},
|
|
|
|
// #cdata
|
|
oncdatastart() {
|
|
parsingCData = true;
|
|
},
|
|
oncdataend() {
|
|
parsingCData = false;
|
|
},
|
|
|
|
// </tagName>
|
|
onclosetag() {
|
|
if (isHTML && node === ownerSVGElement) ownerSVGElement = null;
|
|
node = node.parentNode;
|
|
}
|
|
},
|
|
{
|
|
lowerCaseAttributeNames: false,
|
|
decodeEntities: true,
|
|
xmlMode: !isHTML
|
|
}
|
|
);
|
|
|
|
content.write(markupLanguage);
|
|
content.end();
|
|
|
|
return document;
|
|
};
|
|
|
|
const htmlClasses = new Map();
|
|
|
|
const registerHTMLClass = (names, Class) => {
|
|
for (const name of [].concat(names)) {
|
|
htmlClasses.set(name, Class);
|
|
htmlClasses.set(name.toUpperCase(), Class);
|
|
}
|
|
};
|
|
|
|
const loopSegment = ({ [NEXT]: next, [END]: end }, json) => {
|
|
while (next !== end) {
|
|
switch (next.nodeType) {
|
|
case ATTRIBUTE_NODE:
|
|
attrAsJSON(next, json);
|
|
break;
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
characterDataAsJSON(next, json);
|
|
break;
|
|
case ELEMENT_NODE:
|
|
elementAsJSON(next, json);
|
|
next = getEnd(next);
|
|
break;
|
|
case DOCUMENT_TYPE_NODE:
|
|
documentTypeAsJSON(next, json);
|
|
break;
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
const last = json.length - 1;
|
|
const value = json[last];
|
|
if (typeof value === "number" && value < 0) json[last] += NODE_END;
|
|
else json.push(NODE_END);
|
|
};
|
|
|
|
const attrAsJSON = (attr, json) => {
|
|
json.push(ATTRIBUTE_NODE, attr.name);
|
|
const value = attr[VALUE].trim();
|
|
if (value) json.push(value);
|
|
};
|
|
|
|
const characterDataAsJSON = (node, json) => {
|
|
const value = node[VALUE];
|
|
if (value.trim()) json.push(node.nodeType, value);
|
|
};
|
|
|
|
const nonElementAsJSON = (node, json) => {
|
|
json.push(node.nodeType);
|
|
loopSegment(node, json);
|
|
};
|
|
|
|
const documentTypeAsJSON = ({ name, publicId, systemId }, json) => {
|
|
json.push(DOCUMENT_TYPE_NODE, name);
|
|
if (publicId) json.push(publicId);
|
|
if (systemId) json.push(systemId);
|
|
};
|
|
|
|
const elementAsJSON = (element, json) => {
|
|
json.push(ELEMENT_NODE, element.localName);
|
|
loopSegment(element, json);
|
|
};
|
|
|
|
const createRecord = (
|
|
type,
|
|
target,
|
|
element,
|
|
addedNodes,
|
|
removedNodes,
|
|
attributeName,
|
|
oldValue
|
|
) => ({
|
|
type,
|
|
target,
|
|
addedNodes,
|
|
removedNodes,
|
|
attributeName,
|
|
oldValue,
|
|
previousSibling: element?.previousSibling || null,
|
|
nextSibling: element?.nextSibling || null
|
|
});
|
|
|
|
const queueAttribute = (
|
|
observer,
|
|
target,
|
|
attributeName,
|
|
attributeFilter,
|
|
attributeOldValue,
|
|
oldValue
|
|
) => {
|
|
if (!attributeFilter || attributeFilter.includes(attributeName)) {
|
|
const { callback, records, scheduled } = observer;
|
|
records.push(
|
|
createRecord(
|
|
"attributes",
|
|
target,
|
|
null,
|
|
[],
|
|
[],
|
|
attributeName,
|
|
attributeOldValue ? oldValue : void 0
|
|
)
|
|
);
|
|
if (!scheduled) {
|
|
observer.scheduled = true;
|
|
Promise.resolve().then(() => {
|
|
observer.scheduled = false;
|
|
callback(records.splice(0), observer);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
const attributeChangedCallback = (element, attributeName, oldValue) => {
|
|
const { ownerDocument } = element;
|
|
const { active, observers } = ownerDocument[MUTATION_OBSERVER];
|
|
if (active) {
|
|
for (const observer of observers) {
|
|
for (const [
|
|
target,
|
|
{ childList, subtree, attributes, attributeFilter, attributeOldValue }
|
|
] of observer.nodes) {
|
|
if (childList) {
|
|
if (
|
|
(subtree &&
|
|
(target === ownerDocument || target.contains(element))) ||
|
|
(!subtree && target.children.includes(element))
|
|
) {
|
|
queueAttribute(
|
|
observer,
|
|
element,
|
|
attributeName,
|
|
attributeFilter,
|
|
attributeOldValue,
|
|
oldValue
|
|
);
|
|
break;
|
|
}
|
|
} else if (attributes && target === element) {
|
|
queueAttribute(
|
|
observer,
|
|
element,
|
|
attributeName,
|
|
attributeFilter,
|
|
attributeOldValue,
|
|
oldValue
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const moCallback = (element, parentNode) => {
|
|
const { ownerDocument } = element;
|
|
const { active, observers } = ownerDocument[MUTATION_OBSERVER];
|
|
if (active) {
|
|
for (const observer of observers) {
|
|
for (const [
|
|
target,
|
|
{ subtree, childList, characterData }
|
|
] of observer.nodes) {
|
|
if (childList) {
|
|
if (
|
|
(parentNode &&
|
|
(target === parentNode ||
|
|
/* c8 ignore next */ (subtree &&
|
|
target.contains(parentNode)))) ||
|
|
(!parentNode &&
|
|
((subtree &&
|
|
(target === ownerDocument ||
|
|
/* c8 ignore next */ target.contains(element))) ||
|
|
(!subtree &&
|
|
target[characterData ? "childNodes" : "children"].includes(
|
|
element
|
|
))))
|
|
) {
|
|
const { callback, records, scheduled } = observer;
|
|
records.push(
|
|
createRecord(
|
|
"childList",
|
|
target,
|
|
element,
|
|
parentNode ? [] : [element],
|
|
parentNode ? [element] : []
|
|
)
|
|
);
|
|
if (!scheduled) {
|
|
observer.scheduled = true;
|
|
Promise.resolve().then(() => {
|
|
observer.scheduled = false;
|
|
callback(records.splice(0), observer);
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class MutationObserverClass {
|
|
constructor(ownerDocument) {
|
|
const observers = new Set();
|
|
this.observers = observers;
|
|
this.active = false;
|
|
|
|
/**
|
|
* @implements globalThis.MutationObserver
|
|
*/
|
|
this.class = class MutationObserver {
|
|
constructor(callback) {
|
|
/**
|
|
* @private
|
|
*/
|
|
this.callback = callback;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.nodes = new Map();
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.records = [];
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.scheduled = false;
|
|
}
|
|
|
|
disconnect() {
|
|
this.records.splice(0);
|
|
this.nodes.clear();
|
|
observers.delete(this);
|
|
ownerDocument[MUTATION_OBSERVER].active = !!observers.size;
|
|
}
|
|
|
|
/**
|
|
* @param {Element} target
|
|
* @param {MutationObserverInit} options
|
|
*/
|
|
observe(
|
|
target,
|
|
options = {
|
|
subtree: false,
|
|
childList: false,
|
|
attributes: false,
|
|
attributeFilter: null,
|
|
attributeOldValue: false,
|
|
characterData: false
|
|
// TODO: not implemented yet
|
|
// characterDataOldValue: false
|
|
}
|
|
) {
|
|
if ("attributeOldValue" in options || "attributeFilter" in options)
|
|
options.attributes = true;
|
|
// if ('characterDataOldValue' in options)
|
|
// options.characterData = true;
|
|
options.childList = !!options.childList;
|
|
options.subtree = !!options.subtree;
|
|
this.nodes.set(target, options);
|
|
observers.add(this);
|
|
ownerDocument[MUTATION_OBSERVER].active = true;
|
|
}
|
|
|
|
/**
|
|
* @returns {MutationRecord[]}
|
|
*/
|
|
takeRecords() {
|
|
return this.records.splice(0);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
const emptyAttributes = new Set([
|
|
"allowfullscreen",
|
|
"allowpaymentrequest",
|
|
"async",
|
|
"autofocus",
|
|
"autoplay",
|
|
"checked",
|
|
"class",
|
|
"contenteditable",
|
|
"controls",
|
|
"default",
|
|
"defer",
|
|
"disabled",
|
|
"draggable",
|
|
"formnovalidate",
|
|
"hidden",
|
|
"id",
|
|
"ismap",
|
|
"itemscope",
|
|
"loop",
|
|
"multiple",
|
|
"muted",
|
|
"nomodule",
|
|
"novalidate",
|
|
"open",
|
|
"playsinline",
|
|
"readonly",
|
|
"required",
|
|
"reversed",
|
|
"selected",
|
|
"style",
|
|
"truespeed"
|
|
]);
|
|
|
|
const setAttribute = (element, attribute) => {
|
|
const { [VALUE]: value, name } = attribute;
|
|
attribute.ownerElement = element;
|
|
knownSiblings(element, attribute, element[NEXT]);
|
|
if (name === "class") element.className = value;
|
|
attributeChangedCallback(element, name, null);
|
|
attributeChangedCallback$1(element, name, null, value);
|
|
};
|
|
|
|
const removeAttribute = (element, attribute) => {
|
|
const { [VALUE]: value, name } = attribute;
|
|
knownAdjacent(attribute[PREV], attribute[NEXT]);
|
|
attribute.ownerElement = attribute[PREV] = attribute[NEXT] = null;
|
|
if (name === "class") element[CLASS_LIST] = null;
|
|
attributeChangedCallback(element, name, value);
|
|
attributeChangedCallback$1(element, name, value, null);
|
|
};
|
|
|
|
const booleanAttribute = {
|
|
get(element, name) {
|
|
return element.hasAttribute(name);
|
|
},
|
|
set(element, name, value) {
|
|
if (value) element.setAttribute(name, "");
|
|
else element.removeAttribute(name);
|
|
}
|
|
};
|
|
|
|
const numericAttribute = {
|
|
get(element, name) {
|
|
return parseFloat(element.getAttribute(name) || 0);
|
|
},
|
|
set(element, name, value) {
|
|
element.setAttribute(name, value);
|
|
}
|
|
};
|
|
|
|
const stringAttribute = {
|
|
get(element, name) {
|
|
return element.getAttribute(name) || "";
|
|
},
|
|
set(element, name, value) {
|
|
element.setAttribute(name, value);
|
|
}
|
|
};
|
|
|
|
/* oddly enough, this apparently is not a thing
|
|
export const nullableAttribute = {
|
|
get(element, name) {
|
|
return element.getAttribute(name);
|
|
},
|
|
set(element, name, value) {
|
|
if (value === null)
|
|
element.removeAttribute(name);
|
|
else
|
|
element.setAttribute(name, value);
|
|
}
|
|
};
|
|
*/
|
|
|
|
// https://dom.spec.whatwg.org/#interface-eventtarget
|
|
|
|
const wm = new WeakMap();
|
|
|
|
function dispatch(event, listener) {
|
|
if (typeof listener === "function") listener.call(event.target, event);
|
|
else listener.handleEvent(event);
|
|
return event._stopImmediatePropagationFlag;
|
|
}
|
|
|
|
function invokeListeners({ currentTarget, target }) {
|
|
const map = wm.get(currentTarget);
|
|
if (map && map.has(this.type)) {
|
|
const listeners = map.get(this.type);
|
|
if (currentTarget === target) {
|
|
this.eventPhase = this.AT_TARGET;
|
|
} else {
|
|
this.eventPhase = this.BUBBLING_PHASE;
|
|
}
|
|
|
|
this.currentTarget = currentTarget;
|
|
this.target = target;
|
|
for (const [listener, options] of listeners) {
|
|
if (options && options.once) listeners.delete(listener);
|
|
if (dispatch(this, listener)) break;
|
|
}
|
|
delete this.currentTarget;
|
|
delete this.target;
|
|
return this.cancelBubble;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.EventTarget
|
|
*/
|
|
class DOMEventTarget {
|
|
constructor() {
|
|
wm.set(this, new Map());
|
|
}
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
_getParent() {
|
|
return null;
|
|
}
|
|
|
|
addEventListener(type, listener, options) {
|
|
const map = wm.get(this);
|
|
if (!map.has(type)) map.set(type, new Map());
|
|
map.get(type).set(listener, options);
|
|
}
|
|
|
|
removeEventListener(type, listener) {
|
|
const map = wm.get(this);
|
|
if (map.has(type)) {
|
|
const listeners = map.get(type);
|
|
if (listeners.delete(listener) && !listeners.size) map.delete(type);
|
|
}
|
|
}
|
|
|
|
dispatchEvent(event) {
|
|
let node = this;
|
|
event.eventPhase = event.CAPTURING_PHASE;
|
|
|
|
// intentionally simplified, specs imply way more code: https://dom.spec.whatwg.org/#event-path
|
|
while (node) {
|
|
if (node.dispatchEvent)
|
|
event._path.push({ currentTarget: node, target: this });
|
|
node = event.bubbles && node._getParent && node._getParent();
|
|
}
|
|
event._path.some(invokeListeners, event);
|
|
event._path = [];
|
|
event.eventPhase = event.NONE;
|
|
return !event.defaultPrevented;
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#interface-nodelist
|
|
|
|
/**
|
|
* @implements globalThis.NodeList
|
|
*/
|
|
class NodeList extends Array {
|
|
item(i) {
|
|
return i < this.length ? this[i] : null;
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#node
|
|
|
|
const getParentNodeCount = ({ parentNode }) => {
|
|
let count = 0;
|
|
while (parentNode) {
|
|
count++;
|
|
parentNode = parentNode.parentNode;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.Node
|
|
*/
|
|
let Node$1 = class Node extends DOMEventTarget {
|
|
static get ELEMENT_NODE() {
|
|
return ELEMENT_NODE;
|
|
}
|
|
static get ATTRIBUTE_NODE() {
|
|
return ATTRIBUTE_NODE;
|
|
}
|
|
static get TEXT_NODE() {
|
|
return TEXT_NODE;
|
|
}
|
|
static get CDATA_SECTION_NODE() {
|
|
return CDATA_SECTION_NODE;
|
|
}
|
|
static get COMMENT_NODE() {
|
|
return COMMENT_NODE;
|
|
}
|
|
static get DOCUMENT_NODE() {
|
|
return DOCUMENT_NODE;
|
|
}
|
|
static get DOCUMENT_FRAGMENT_NODE() {
|
|
return DOCUMENT_FRAGMENT_NODE;
|
|
}
|
|
static get DOCUMENT_TYPE_NODE() {
|
|
return DOCUMENT_TYPE_NODE;
|
|
}
|
|
|
|
constructor(ownerDocument, localName, nodeType) {
|
|
super();
|
|
this.ownerDocument = ownerDocument;
|
|
this.localName = localName;
|
|
this.nodeType = nodeType;
|
|
this.parentNode = null;
|
|
this[NEXT] = null;
|
|
this[PREV] = null;
|
|
}
|
|
|
|
get ELEMENT_NODE() {
|
|
return ELEMENT_NODE;
|
|
}
|
|
get ATTRIBUTE_NODE() {
|
|
return ATTRIBUTE_NODE;
|
|
}
|
|
get TEXT_NODE() {
|
|
return TEXT_NODE;
|
|
}
|
|
get CDATA_SECTION_NODE() {
|
|
return CDATA_SECTION_NODE;
|
|
}
|
|
get COMMENT_NODE() {
|
|
return COMMENT_NODE;
|
|
}
|
|
get DOCUMENT_NODE() {
|
|
return DOCUMENT_NODE;
|
|
}
|
|
get DOCUMENT_FRAGMENT_NODE() {
|
|
return DOCUMENT_FRAGMENT_NODE;
|
|
}
|
|
get DOCUMENT_TYPE_NODE() {
|
|
return DOCUMENT_TYPE_NODE;
|
|
}
|
|
|
|
get baseURI() {
|
|
const ownerDocument =
|
|
this.nodeType === DOCUMENT_NODE ? this : this.ownerDocument;
|
|
if (ownerDocument) {
|
|
const base = ownerDocument.querySelector("base");
|
|
if (base) return base.getAttribute("href");
|
|
|
|
const { location } = ownerDocument.defaultView;
|
|
if (location) return location.href;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
// mixin: node
|
|
get isConnected() {
|
|
return false;
|
|
}
|
|
get nodeName() {
|
|
return this.localName;
|
|
}
|
|
get parentElement() {
|
|
return null;
|
|
}
|
|
get previousSibling() {
|
|
return null;
|
|
}
|
|
get previousElementSibling() {
|
|
return null;
|
|
}
|
|
get nextSibling() {
|
|
return null;
|
|
}
|
|
get nextElementSibling() {
|
|
return null;
|
|
}
|
|
get childNodes() {
|
|
return new NodeList();
|
|
}
|
|
get firstChild() {
|
|
return null;
|
|
}
|
|
get lastChild() {
|
|
return null;
|
|
}
|
|
|
|
// default values
|
|
get nodeValue() {
|
|
return null;
|
|
}
|
|
set nodeValue(value) {}
|
|
get textContent() {
|
|
return null;
|
|
}
|
|
set textContent(value) {}
|
|
normalize() {}
|
|
cloneNode() {
|
|
return null;
|
|
}
|
|
contains() {
|
|
return false;
|
|
}
|
|
/**
|
|
* Inserts a node before a reference node as a child of this parent node.
|
|
* @param {Node} newNode The node to be inserted.
|
|
* @param {Node} referenceNode The node before which newNode is inserted. If this is null, then newNode is inserted at the end of node's child nodes.
|
|
* @returns The added child
|
|
*/
|
|
// eslint-disable-next-line no-unused-vars
|
|
insertBefore(newNode, referenceNode) {
|
|
return newNode;
|
|
}
|
|
/**
|
|
* Adds a node to the end of the list of children of this node.
|
|
* @param {Node} child The node to append to the given parent node.
|
|
* @returns The appended child.
|
|
*/
|
|
appendChild(child) {
|
|
return child;
|
|
}
|
|
/**
|
|
* Replaces a child node within this node
|
|
* @param {Node} newChild The new node to replace oldChild.
|
|
* @param {Node} oldChild The child to be replaced.
|
|
* @returns The replaced Node. This is the same node as oldChild.
|
|
*/
|
|
replaceChild(newChild, oldChild) {
|
|
return oldChild;
|
|
}
|
|
/**
|
|
* Removes a child node from the DOM.
|
|
* @param {Node} child A Node that is the child node to be removed from the DOM.
|
|
* @returns The removed node.
|
|
*/
|
|
removeChild(child) {
|
|
return child;
|
|
}
|
|
toString() {
|
|
return "";
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
hasChildNodes() {
|
|
return !!this.lastChild;
|
|
}
|
|
isSameNode(node) {
|
|
return this === node;
|
|
}
|
|
|
|
// TODO: attributes?
|
|
compareDocumentPosition(target) {
|
|
let result = 0;
|
|
if (this !== target) {
|
|
let self = getParentNodeCount(this);
|
|
let other = getParentNodeCount(target);
|
|
if (self < other) {
|
|
result += DOCUMENT_POSITION_FOLLOWING;
|
|
if (this.contains(target)) result += DOCUMENT_POSITION_CONTAINED_BY;
|
|
} else if (other < self) {
|
|
result += DOCUMENT_POSITION_PRECEDING;
|
|
if (target.contains(this)) result += DOCUMENT_POSITION_CONTAINS;
|
|
} else if (self && other) {
|
|
const { childNodes } = this.parentNode;
|
|
if (childNodes.indexOf(this) < childNodes.indexOf(target))
|
|
result += DOCUMENT_POSITION_FOLLOWING;
|
|
else result += DOCUMENT_POSITION_PRECEDING;
|
|
}
|
|
if (!self || !other) {
|
|
result += DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
|
|
result += DOCUMENT_POSITION_DISCONNECTED;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
isEqualNode(node) {
|
|
if (this === node) return true;
|
|
if (this.nodeType === node.nodeType) {
|
|
switch (this.nodeType) {
|
|
case DOCUMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE: {
|
|
const aNodes = this.childNodes;
|
|
const bNodes = node.childNodes;
|
|
return (
|
|
aNodes.length === bNodes.length &&
|
|
aNodes.every((node, i) => node.isEqualNode(bNodes[i]))
|
|
);
|
|
}
|
|
}
|
|
return this.toString() === node.toString();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
_getParent() {
|
|
return this.parentNode;
|
|
}
|
|
|
|
/**
|
|
* Calling it on an element inside a standard web page will return an HTMLDocument object representing the entire page (or <iframe>).
|
|
* Calling it on an element inside a shadow DOM will return the associated ShadowRoot.
|
|
* @return {ShadowRoot | HTMLDocument}
|
|
*/
|
|
getRootNode() {
|
|
let root = this;
|
|
while (root.parentNode) root = root.parentNode;
|
|
return root;
|
|
}
|
|
};
|
|
|
|
const { replace } = "";
|
|
|
|
// escape
|
|
const ca = /[<>&\xA0]/g;
|
|
|
|
const esca = {
|
|
"\xA0": " ",
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">"
|
|
};
|
|
|
|
const pe = (m) => esca[m];
|
|
|
|
/**
|
|
* Safely escape HTML entities such as `&`, `<`, `>` only.
|
|
* @param {string} es the input to safely escape
|
|
* @returns {string} the escaped input, and it **throws** an error if
|
|
* the input type is unexpected, except for boolean and numbers,
|
|
* converted as string.
|
|
*/
|
|
const escape = (es) => replace.call(es, ca, pe);
|
|
|
|
const QUOTE = /"/g;
|
|
|
|
/**
|
|
* @implements globalThis.Attr
|
|
*/
|
|
let Attr$1 = class Attr extends Node$1 {
|
|
constructor(ownerDocument, name, value = "") {
|
|
super(ownerDocument, name, ATTRIBUTE_NODE);
|
|
this.ownerElement = null;
|
|
this.name = $String(name);
|
|
this[VALUE] = $String(value);
|
|
this[CHANGED] = false;
|
|
}
|
|
|
|
get value() {
|
|
return this[VALUE];
|
|
}
|
|
set value(newValue) {
|
|
const { [VALUE]: oldValue, name, ownerElement } = this;
|
|
this[VALUE] = $String(newValue);
|
|
this[CHANGED] = true;
|
|
if (ownerElement) {
|
|
attributeChangedCallback(ownerElement, name, oldValue);
|
|
attributeChangedCallback$1(ownerElement, name, oldValue, this[VALUE]);
|
|
}
|
|
}
|
|
|
|
cloneNode() {
|
|
const { ownerDocument, name, [VALUE]: value } = this;
|
|
return new Attr(ownerDocument, name, value);
|
|
}
|
|
|
|
toString() {
|
|
const { name, [VALUE]: value } = this;
|
|
if (emptyAttributes.has(name) && !value) {
|
|
return ignoreCase(this) ? name : `${name}=""`;
|
|
}
|
|
const escapedValue = (ignoreCase(this) ? value : escape(value)).replace(
|
|
QUOTE,
|
|
"""
|
|
);
|
|
return `${name}="${escapedValue}"`;
|
|
}
|
|
|
|
toJSON() {
|
|
const json = [];
|
|
attrAsJSON(this, json);
|
|
return json;
|
|
}
|
|
};
|
|
|
|
const isConnected = ({ ownerDocument, parentNode }) => {
|
|
while (parentNode) {
|
|
if (parentNode === ownerDocument) return true;
|
|
parentNode = parentNode.parentNode || parentNode.host;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const parentElement = ({ parentNode }) => {
|
|
if (parentNode) {
|
|
switch (parentNode.nodeType) {
|
|
case DOCUMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
return null;
|
|
}
|
|
}
|
|
return parentNode;
|
|
};
|
|
|
|
const previousSibling = ({ [PREV]: prev }) => {
|
|
switch (prev ? prev.nodeType : 0) {
|
|
case NODE_END:
|
|
return prev[START];
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
return prev;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const nextSibling = (node) => {
|
|
const next = getEnd(node)[NEXT];
|
|
return next && (next.nodeType === NODE_END ? null : next);
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#nondocumenttypechildnode
|
|
// CharacterData, Element
|
|
|
|
const nextElementSibling$1 = (node) => {
|
|
let next = nextSibling(node);
|
|
while (next && next.nodeType !== ELEMENT_NODE) next = nextSibling(next);
|
|
return next;
|
|
};
|
|
|
|
const previousElementSibling = (node) => {
|
|
let prev = previousSibling(node);
|
|
while (prev && prev.nodeType !== ELEMENT_NODE) prev = previousSibling(prev);
|
|
return prev;
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#childnode
|
|
// CharacterData, DocumentType, Element
|
|
|
|
const asFragment = (ownerDocument, nodes) => {
|
|
const fragment = ownerDocument.createDocumentFragment();
|
|
fragment.append(...nodes);
|
|
return fragment;
|
|
};
|
|
|
|
const before = (node, nodes) => {
|
|
const { ownerDocument, parentNode } = node;
|
|
if (parentNode)
|
|
parentNode.insertBefore(asFragment(ownerDocument, nodes), node);
|
|
};
|
|
|
|
const after = (node, nodes) => {
|
|
const { ownerDocument, parentNode } = node;
|
|
if (parentNode)
|
|
parentNode.insertBefore(
|
|
asFragment(ownerDocument, nodes),
|
|
getEnd(node)[NEXT]
|
|
);
|
|
};
|
|
|
|
const replaceWith = (node, nodes) => {
|
|
const { ownerDocument, parentNode } = node;
|
|
if (parentNode) {
|
|
if (nodes.includes(node)) replaceWith(node, [(node = node.cloneNode())]);
|
|
parentNode.insertBefore(asFragment(ownerDocument, nodes), node);
|
|
node.remove();
|
|
}
|
|
};
|
|
|
|
const remove = (prev, current, next) => {
|
|
const { parentNode, nodeType } = current;
|
|
if (prev || next) {
|
|
setAdjacent(prev, next);
|
|
current[PREV] = null;
|
|
getEnd(current)[NEXT] = null;
|
|
}
|
|
if (parentNode) {
|
|
current.parentNode = null;
|
|
moCallback(current, parentNode);
|
|
if (nodeType === ELEMENT_NODE) disconnectedCallback(current);
|
|
}
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#interface-characterdata
|
|
|
|
/**
|
|
* @implements globalThis.CharacterData
|
|
*/
|
|
let CharacterData$1 = class CharacterData extends Node$1 {
|
|
constructor(ownerDocument, localName, nodeType, data) {
|
|
super(ownerDocument, localName, nodeType);
|
|
this[VALUE] = $String(data);
|
|
}
|
|
|
|
// <Mixins>
|
|
get isConnected() {
|
|
return isConnected(this);
|
|
}
|
|
get parentElement() {
|
|
return parentElement(this);
|
|
}
|
|
get previousSibling() {
|
|
return previousSibling(this);
|
|
}
|
|
get nextSibling() {
|
|
return nextSibling(this);
|
|
}
|
|
|
|
get previousElementSibling() {
|
|
return previousElementSibling(this);
|
|
}
|
|
get nextElementSibling() {
|
|
return nextElementSibling$1(this);
|
|
}
|
|
|
|
before(...nodes) {
|
|
before(this, nodes);
|
|
}
|
|
after(...nodes) {
|
|
after(this, nodes);
|
|
}
|
|
replaceWith(...nodes) {
|
|
replaceWith(this, nodes);
|
|
}
|
|
remove() {
|
|
remove(this[PREV], this, this[NEXT]);
|
|
}
|
|
// </Mixins>
|
|
|
|
// CharacterData only
|
|
/* c8 ignore start */
|
|
get data() {
|
|
return this[VALUE];
|
|
}
|
|
set data(value) {
|
|
this[VALUE] = $String(value);
|
|
moCallback(this, this.parentNode);
|
|
}
|
|
|
|
get nodeValue() {
|
|
return this.data;
|
|
}
|
|
set nodeValue(value) {
|
|
this.data = value;
|
|
}
|
|
|
|
get textContent() {
|
|
return this.data;
|
|
}
|
|
set textContent(value) {
|
|
this.data = value;
|
|
}
|
|
|
|
get length() {
|
|
return this.data.length;
|
|
}
|
|
|
|
substringData(offset, count) {
|
|
return this.data.substr(offset, count);
|
|
}
|
|
|
|
appendData(data) {
|
|
this.data += data;
|
|
}
|
|
|
|
insertData(offset, data) {
|
|
const { data: t } = this;
|
|
this.data = t.slice(0, offset) + data + t.slice(offset);
|
|
}
|
|
|
|
deleteData(offset, count) {
|
|
const { data: t } = this;
|
|
this.data = t.slice(0, offset) + t.slice(offset + count);
|
|
}
|
|
|
|
replaceData(offset, count, data) {
|
|
const { data: t } = this;
|
|
this.data = t.slice(0, offset) + data + t.slice(offset + count);
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
toJSON() {
|
|
const json = [];
|
|
characterDataAsJSON(this, json);
|
|
return json;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.CDATASection
|
|
*/
|
|
let CDATASection$1 = class CDATASection extends CharacterData$1 {
|
|
constructor(ownerDocument, data = "") {
|
|
super(ownerDocument, "#cdatasection", CDATA_SECTION_NODE, data);
|
|
}
|
|
|
|
cloneNode() {
|
|
const { ownerDocument, [VALUE]: data } = this;
|
|
return new CDATASection(ownerDocument, data);
|
|
}
|
|
|
|
toString() {
|
|
return `<![CDATA[${this[VALUE]}]]>`;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.Comment
|
|
*/
|
|
let Comment$2 = class Comment extends CharacterData$1 {
|
|
constructor(ownerDocument, data = "") {
|
|
super(ownerDocument, "#comment", COMMENT_NODE, data);
|
|
}
|
|
|
|
cloneNode() {
|
|
const { ownerDocument, [VALUE]: data } = this;
|
|
return new Comment(ownerDocument, data);
|
|
}
|
|
|
|
toString() {
|
|
return `<!--${this[VALUE]}-->`;
|
|
}
|
|
};
|
|
|
|
function trueFunc() {
|
|
return true;
|
|
}
|
|
function falseFunc() {
|
|
return false;
|
|
}
|
|
|
|
/** Discriminants for selector token kinds. */
|
|
var SelectorType;
|
|
(function (SelectorType) {
|
|
SelectorType["Attribute"] = "attribute";
|
|
SelectorType["Pseudo"] = "pseudo";
|
|
SelectorType["PseudoElement"] = "pseudo-element";
|
|
SelectorType["Tag"] = "tag";
|
|
SelectorType["Universal"] = "universal";
|
|
// Traversals
|
|
SelectorType["Adjacent"] = "adjacent";
|
|
SelectorType["Child"] = "child";
|
|
SelectorType["Descendant"] = "descendant";
|
|
SelectorType["Parent"] = "parent";
|
|
SelectorType["Sibling"] = "sibling";
|
|
SelectorType["ColumnCombinator"] = "column-combinator";
|
|
})(SelectorType || (SelectorType = {}));
|
|
/** Operators available for attribute selectors. */
|
|
var AttributeAction;
|
|
(function (AttributeAction) {
|
|
AttributeAction["Any"] = "any";
|
|
AttributeAction["Element"] = "element";
|
|
AttributeAction["End"] = "end";
|
|
AttributeAction["Equals"] = "equals";
|
|
AttributeAction["Exists"] = "exists";
|
|
AttributeAction["Hyphen"] = "hyphen";
|
|
AttributeAction["Not"] = "not";
|
|
AttributeAction["Start"] = "start";
|
|
})(AttributeAction || (AttributeAction = {}));
|
|
|
|
const reName = /^[^#\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\u00B0-\uFFFF-])+/;
|
|
const reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
|
|
var CharCode;
|
|
(function (CharCode) {
|
|
CharCode[(CharCode["LeftParenthesis"] = 40)] = "LeftParenthesis";
|
|
CharCode[(CharCode["RightParenthesis"] = 41)] = "RightParenthesis";
|
|
CharCode[(CharCode["LeftSquareBracket"] = 91)] = "LeftSquareBracket";
|
|
CharCode[(CharCode["RightSquareBracket"] = 93)] = "RightSquareBracket";
|
|
CharCode[(CharCode["Comma"] = 44)] = "Comma";
|
|
CharCode[(CharCode["Period"] = 46)] = "Period";
|
|
CharCode[(CharCode["Colon"] = 58)] = "Colon";
|
|
CharCode[(CharCode["SingleQuote"] = 39)] = "SingleQuote";
|
|
CharCode[(CharCode["DoubleQuote"] = 34)] = "DoubleQuote";
|
|
CharCode[(CharCode["Plus"] = 43)] = "Plus";
|
|
CharCode[(CharCode["Tilde"] = 126)] = "Tilde";
|
|
CharCode[(CharCode["QuestionMark"] = 63)] = "QuestionMark";
|
|
CharCode[(CharCode["ExclamationMark"] = 33)] = "ExclamationMark";
|
|
CharCode[(CharCode["Slash"] = 47)] = "Slash";
|
|
CharCode[(CharCode["Equal"] = 61)] = "Equal";
|
|
CharCode[(CharCode["Dollar"] = 36)] = "Dollar";
|
|
CharCode[(CharCode["Pipe"] = 124)] = "Pipe";
|
|
CharCode[(CharCode["Circumflex"] = 94)] = "Circumflex";
|
|
CharCode[(CharCode["Asterisk"] = 42)] = "Asterisk";
|
|
CharCode[(CharCode["GreaterThan"] = 62)] = "GreaterThan";
|
|
CharCode[(CharCode["LessThan"] = 60)] = "LessThan";
|
|
CharCode[(CharCode["Hash"] = 35)] = "Hash";
|
|
CharCode[(CharCode["LowerI"] = 105)] = "LowerI";
|
|
CharCode[(CharCode["LowerS"] = 115)] = "LowerS";
|
|
CharCode[(CharCode["BackSlash"] = 92)] = "BackSlash";
|
|
// Whitespace
|
|
CharCode[(CharCode["Space"] = 32)] = "Space";
|
|
CharCode[(CharCode["Tab"] = 9)] = "Tab";
|
|
CharCode[(CharCode["NewLine"] = 10)] = "NewLine";
|
|
CharCode[(CharCode["FormFeed"] = 12)] = "FormFeed";
|
|
CharCode[(CharCode["CarriageReturn"] = 13)] = "CarriageReturn";
|
|
})(CharCode || (CharCode = {}));
|
|
const actionTypes = new Map([
|
|
[CharCode.Tilde, AttributeAction.Element],
|
|
[CharCode.Circumflex, AttributeAction.Start],
|
|
[CharCode.Dollar, AttributeAction.End],
|
|
[CharCode.Asterisk, AttributeAction.Any],
|
|
[CharCode.ExclamationMark, AttributeAction.Not],
|
|
[CharCode.Pipe, AttributeAction.Hyphen]
|
|
]);
|
|
// Pseudos, whose data property is parsed as well.
|
|
const unpackPseudos = new Set([
|
|
"has",
|
|
"not",
|
|
"matches",
|
|
"is",
|
|
"where",
|
|
"host",
|
|
"host-context"
|
|
]);
|
|
/**
|
|
* Pseudo elements defined in CSS Level 1 and CSS Level 2 can be written with
|
|
* a single colon; eg. :before will turn into ::before.
|
|
* @see {@link https://www.w3.org/TR/2018/WD-selectors-4-20181121/#pseudo-element-syntax}
|
|
*/
|
|
const pseudosToPseudoElements = new Set([
|
|
"before",
|
|
"after",
|
|
"first-line",
|
|
"first-letter"
|
|
]);
|
|
/**
|
|
* Checks whether a specific selector is a traversal.
|
|
* This is useful eg. in swapping the order of elements that
|
|
* are not traversals.
|
|
* @param selector Selector to check.
|
|
*/
|
|
function isTraversal$1(selector) {
|
|
switch (selector.type) {
|
|
case SelectorType.Adjacent:
|
|
case SelectorType.Child:
|
|
case SelectorType.Descendant:
|
|
case SelectorType.Parent:
|
|
case SelectorType.Sibling:
|
|
case SelectorType.ColumnCombinator: {
|
|
return true;
|
|
}
|
|
case SelectorType.Attribute:
|
|
case SelectorType.Pseudo:
|
|
case SelectorType.PseudoElement:
|
|
case SelectorType.Tag:
|
|
case SelectorType.Universal: {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
const stripQuotesFromPseudos = new Set(["contains", "icontains"]);
|
|
// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
|
|
function funescape(_, escaped, escapedWhitespace) {
|
|
const high = Number.parseInt(escaped, 16) - 0x1_00_00;
|
|
// NaN means non-codepoint
|
|
return Number.isNaN(high) || escapedWhitespace
|
|
? escaped
|
|
: high < 0
|
|
? // BMP codepoint
|
|
String.fromCharCode(high + 0x1_00_00)
|
|
: // Supplemental Plane codepoint (surrogate pair)
|
|
String.fromCharCode((high >> 10) | 0xd8_00, (high & 0x3_ff) | 0xdc_00);
|
|
}
|
|
function unescapeCSS(cssString) {
|
|
return cssString.replace(reEscape, funescape);
|
|
}
|
|
function isQuote(c) {
|
|
return c === CharCode.SingleQuote || c === CharCode.DoubleQuote;
|
|
}
|
|
function isWhitespace(c) {
|
|
return (
|
|
c === CharCode.Space ||
|
|
c === CharCode.Tab ||
|
|
c === CharCode.NewLine ||
|
|
c === CharCode.FormFeed ||
|
|
c === CharCode.CarriageReturn
|
|
);
|
|
}
|
|
/**
|
|
* Parses `selector`.
|
|
* @param selector Selector to parse.
|
|
* @returns Returns a two-dimensional array.
|
|
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
|
|
* the second contains the relevant tokens for that selector.
|
|
*/
|
|
function parse$3(selector) {
|
|
const subselects = [];
|
|
const endIndex = parseSelector(subselects, `${selector}`, 0);
|
|
if (endIndex < selector.length) {
|
|
throw new Error(`Unmatched selector: ${selector.slice(endIndex)}`);
|
|
}
|
|
return subselects;
|
|
}
|
|
function parseSelector(subselects, selector, selectorIndex) {
|
|
let tokens = [];
|
|
function getName(offset) {
|
|
const match = selector.slice(selectorIndex + offset).match(reName);
|
|
if (!match) {
|
|
throw new Error(`Expected name, found ${selector.slice(selectorIndex)}`);
|
|
}
|
|
const [name] = match;
|
|
selectorIndex += offset + name.length;
|
|
return unescapeCSS(name);
|
|
}
|
|
function stripWhitespace(offset) {
|
|
selectorIndex += offset;
|
|
while (
|
|
selectorIndex < selector.length &&
|
|
isWhitespace(selector.charCodeAt(selectorIndex))
|
|
) {
|
|
selectorIndex++;
|
|
}
|
|
}
|
|
function readValueWithParenthesis() {
|
|
selectorIndex += 1;
|
|
const start = selectorIndex;
|
|
for (let counter = 1; selectorIndex < selector.length; selectorIndex++) {
|
|
switch (selector.charCodeAt(selectorIndex)) {
|
|
case CharCode.BackSlash: {
|
|
// Skip next character
|
|
selectorIndex += 1;
|
|
break;
|
|
}
|
|
case CharCode.LeftParenthesis: {
|
|
counter += 1;
|
|
break;
|
|
}
|
|
case CharCode.RightParenthesis: {
|
|
counter -= 1;
|
|
if (counter === 0) {
|
|
return unescapeCSS(selector.slice(start, selectorIndex++));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
throw new Error("Parenthesis not matched");
|
|
}
|
|
function ensureNotTraversal() {
|
|
if (tokens.length > 0 && isTraversal$1(tokens[tokens.length - 1])) {
|
|
throw new Error("Did not expect successive traversals.");
|
|
}
|
|
}
|
|
function addTraversal(type) {
|
|
if (
|
|
tokens.length > 0 &&
|
|
tokens[tokens.length - 1].type === SelectorType.Descendant
|
|
) {
|
|
tokens[tokens.length - 1].type = type;
|
|
return;
|
|
}
|
|
ensureNotTraversal();
|
|
tokens.push({ type });
|
|
}
|
|
function addSpecialAttribute(name, action) {
|
|
tokens.push({
|
|
type: SelectorType.Attribute,
|
|
name,
|
|
action,
|
|
value: getName(1),
|
|
namespace: null,
|
|
ignoreCase: "quirks"
|
|
});
|
|
}
|
|
/**
|
|
* We have finished parsing the current part of the selector.
|
|
*
|
|
* Remove descendant tokens at the end if they exist,
|
|
* and return the last index, so that parsing can be
|
|
* picked up from here.
|
|
*/
|
|
function finalizeSubselector() {
|
|
if (
|
|
tokens.length > 0 &&
|
|
tokens[tokens.length - 1].type === SelectorType.Descendant
|
|
) {
|
|
tokens.pop();
|
|
}
|
|
if (tokens.length === 0) {
|
|
throw new Error("Empty sub-selector");
|
|
}
|
|
subselects.push(tokens);
|
|
}
|
|
stripWhitespace(0);
|
|
if (selector.length === selectorIndex) {
|
|
return selectorIndex;
|
|
}
|
|
loop: while (selectorIndex < selector.length) {
|
|
const firstChar = selector.charCodeAt(selectorIndex);
|
|
switch (firstChar) {
|
|
// Whitespace
|
|
case CharCode.Space:
|
|
case CharCode.Tab:
|
|
case CharCode.NewLine:
|
|
case CharCode.FormFeed:
|
|
case CharCode.CarriageReturn: {
|
|
if (tokens.length === 0 || tokens[0].type !== SelectorType.Descendant) {
|
|
ensureNotTraversal();
|
|
tokens.push({ type: SelectorType.Descendant });
|
|
}
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
// Traversals
|
|
case CharCode.GreaterThan: {
|
|
addTraversal(SelectorType.Child);
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
case CharCode.LessThan: {
|
|
addTraversal(SelectorType.Parent);
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
case CharCode.Tilde: {
|
|
addTraversal(SelectorType.Sibling);
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
case CharCode.Plus: {
|
|
addTraversal(SelectorType.Adjacent);
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
// Special attribute selectors: .class, #id
|
|
case CharCode.Period: {
|
|
addSpecialAttribute("class", AttributeAction.Element);
|
|
break;
|
|
}
|
|
case CharCode.Hash: {
|
|
addSpecialAttribute("id", AttributeAction.Equals);
|
|
break;
|
|
}
|
|
case CharCode.LeftSquareBracket: {
|
|
stripWhitespace(1);
|
|
// Determine attribute name and namespace
|
|
let name;
|
|
let namespace = null;
|
|
if (selector.charCodeAt(selectorIndex) === CharCode.Pipe) {
|
|
// Equivalent to no namespace
|
|
name = getName(1);
|
|
} else if (selector.startsWith("*|", selectorIndex)) {
|
|
namespace = "*";
|
|
name = getName(2);
|
|
} else {
|
|
name = getName(0);
|
|
if (
|
|
selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
|
|
selector.charCodeAt(selectorIndex + 1) !== CharCode.Equal
|
|
) {
|
|
namespace = name;
|
|
name = getName(1);
|
|
}
|
|
}
|
|
stripWhitespace(0);
|
|
// Determine comparison operation
|
|
let action = AttributeAction.Exists;
|
|
const possibleAction = actionTypes.get(
|
|
selector.charCodeAt(selectorIndex)
|
|
);
|
|
if (possibleAction) {
|
|
action = possibleAction;
|
|
if (selector.charCodeAt(selectorIndex + 1) !== CharCode.Equal) {
|
|
throw new Error("Expected `=`");
|
|
}
|
|
stripWhitespace(2);
|
|
} else if (selector.charCodeAt(selectorIndex) === CharCode.Equal) {
|
|
action = AttributeAction.Equals;
|
|
stripWhitespace(1);
|
|
}
|
|
// Determine value
|
|
let value = "";
|
|
let ignoreCase = null;
|
|
if (action !== "exists") {
|
|
if (isQuote(selector.charCodeAt(selectorIndex))) {
|
|
const quote = selector.charCodeAt(selectorIndex);
|
|
selectorIndex += 1;
|
|
const sectionStart = selectorIndex;
|
|
while (
|
|
selectorIndex < selector.length &&
|
|
selector.charCodeAt(selectorIndex) !== quote
|
|
) {
|
|
selectorIndex +=
|
|
// Skip next character if it is escaped
|
|
selector.charCodeAt(selectorIndex) === CharCode.BackSlash
|
|
? 2
|
|
: 1;
|
|
}
|
|
if (selector.charCodeAt(selectorIndex) !== quote) {
|
|
throw new Error("Attribute value didn't end");
|
|
}
|
|
value = unescapeCSS(selector.slice(sectionStart, selectorIndex));
|
|
selectorIndex += 1;
|
|
} else {
|
|
const valueStart = selectorIndex;
|
|
while (
|
|
selectorIndex < selector.length &&
|
|
!isWhitespace(selector.charCodeAt(selectorIndex)) &&
|
|
selector.charCodeAt(selectorIndex) !== CharCode.RightSquareBracket
|
|
) {
|
|
selectorIndex +=
|
|
// Skip next character if it is escaped
|
|
selector.charCodeAt(selectorIndex) === CharCode.BackSlash
|
|
? 2
|
|
: 1;
|
|
}
|
|
value = unescapeCSS(selector.slice(valueStart, selectorIndex));
|
|
}
|
|
stripWhitespace(0);
|
|
// See if we have a force ignore flag
|
|
switch (selector.charCodeAt(selectorIndex) | 0x20) {
|
|
// If the forceIgnore flag is set (either `i` or `s`), use that value
|
|
case CharCode.LowerI: {
|
|
ignoreCase = true;
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
case CharCode.LowerS: {
|
|
ignoreCase = false;
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (
|
|
selector.charCodeAt(selectorIndex) !== CharCode.RightSquareBracket
|
|
) {
|
|
throw new Error("Attribute selector didn't terminate");
|
|
}
|
|
selectorIndex += 1;
|
|
const attributeSelector = {
|
|
type: SelectorType.Attribute,
|
|
name,
|
|
action,
|
|
value,
|
|
namespace,
|
|
ignoreCase
|
|
};
|
|
tokens.push(attributeSelector);
|
|
break;
|
|
}
|
|
case CharCode.Colon: {
|
|
if (selector.charCodeAt(selectorIndex + 1) === CharCode.Colon) {
|
|
tokens.push({
|
|
type: SelectorType.PseudoElement,
|
|
name: getName(2).toLowerCase(),
|
|
data:
|
|
selector.charCodeAt(selectorIndex) === CharCode.LeftParenthesis
|
|
? readValueWithParenthesis()
|
|
: null
|
|
});
|
|
break;
|
|
}
|
|
const name = getName(1).toLowerCase();
|
|
if (pseudosToPseudoElements.has(name)) {
|
|
tokens.push({
|
|
type: SelectorType.PseudoElement,
|
|
name,
|
|
data: null
|
|
});
|
|
break;
|
|
}
|
|
let data = null;
|
|
if (selector.charCodeAt(selectorIndex) === CharCode.LeftParenthesis) {
|
|
if (unpackPseudos.has(name)) {
|
|
if (isQuote(selector.charCodeAt(selectorIndex + 1))) {
|
|
throw new Error(`Pseudo-selector ${name} cannot be quoted`);
|
|
}
|
|
data = [];
|
|
selectorIndex = parseSelector(data, selector, selectorIndex + 1);
|
|
if (
|
|
selector.charCodeAt(selectorIndex) !== CharCode.RightParenthesis
|
|
) {
|
|
throw new Error(
|
|
`Missing closing parenthesis in :${name} (${selector})`
|
|
);
|
|
}
|
|
selectorIndex += 1;
|
|
} else {
|
|
data = readValueWithParenthesis();
|
|
if (stripQuotesFromPseudos.has(name)) {
|
|
const quot = data.charCodeAt(0);
|
|
if (quot === data.charCodeAt(data.length - 1) && isQuote(quot)) {
|
|
data = data.slice(1, -1);
|
|
}
|
|
}
|
|
data = unescapeCSS(data);
|
|
}
|
|
}
|
|
tokens.push({ type: SelectorType.Pseudo, name, data });
|
|
break;
|
|
}
|
|
case CharCode.Comma: {
|
|
finalizeSubselector();
|
|
tokens = [];
|
|
stripWhitespace(1);
|
|
break;
|
|
}
|
|
default: {
|
|
if (selector.startsWith("/*", selectorIndex)) {
|
|
const endIndex = selector.indexOf("*/", selectorIndex + 2);
|
|
if (endIndex === -1) {
|
|
throw new Error("Comment was not terminated");
|
|
}
|
|
selectorIndex = endIndex + 2;
|
|
// Remove leading whitespace
|
|
if (tokens.length === 0) {
|
|
stripWhitespace(0);
|
|
}
|
|
break;
|
|
}
|
|
let namespace = null;
|
|
let name;
|
|
if (firstChar === CharCode.Asterisk) {
|
|
selectorIndex += 1;
|
|
name = "*";
|
|
} else if (firstChar === CharCode.Pipe) {
|
|
name = "";
|
|
if (selector.charCodeAt(selectorIndex + 1) === CharCode.Pipe) {
|
|
addTraversal(SelectorType.ColumnCombinator);
|
|
stripWhitespace(2);
|
|
break;
|
|
}
|
|
} else if (reName.test(selector.slice(selectorIndex))) {
|
|
name = getName(0);
|
|
} else {
|
|
break loop;
|
|
}
|
|
if (
|
|
selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
|
|
selector.charCodeAt(selectorIndex + 1) !== CharCode.Pipe
|
|
) {
|
|
namespace = name;
|
|
if (selector.charCodeAt(selectorIndex + 1) === CharCode.Asterisk) {
|
|
name = "*";
|
|
selectorIndex += 2;
|
|
} else {
|
|
name = getName(1);
|
|
}
|
|
}
|
|
tokens.push(
|
|
name === "*"
|
|
? { type: SelectorType.Universal, namespace }
|
|
: { type: SelectorType.Tag, name, namespace }
|
|
);
|
|
}
|
|
}
|
|
}
|
|
finalizeSubselector();
|
|
return selectorIndex;
|
|
}
|
|
|
|
/** Types of elements found in htmlparser2's DOM */
|
|
var ElementType;
|
|
(function (ElementType) {
|
|
/** Type for the root element of a document */
|
|
ElementType["Root"] = "root";
|
|
/** Type for Text */
|
|
ElementType["Text"] = "text";
|
|
/** Type for <? ... ?> */
|
|
ElementType["Directive"] = "directive";
|
|
/** Type for <!-- ... --> */
|
|
ElementType["Comment"] = "comment";
|
|
/** Type for <script> tags */
|
|
ElementType["Script"] = "script";
|
|
/** Type for <style> tags */
|
|
ElementType["Style"] = "style";
|
|
/** Type for Any tag */
|
|
ElementType["Tag"] = "tag";
|
|
/** Type for <![CDATA[ ... ]]> */
|
|
ElementType["CDATA"] = "cdata";
|
|
/** Type for <!doctype ...> */
|
|
ElementType["Doctype"] = "doctype";
|
|
})(ElementType || (ElementType = {}));
|
|
/**
|
|
* Tests whether an element is a tag or not.
|
|
* @param element Element to test
|
|
* @param element.type Node type discriminator to check.
|
|
*/
|
|
function isTag$2(element) {
|
|
return (
|
|
element.type === ElementType.Tag ||
|
|
element.type === ElementType.Script ||
|
|
element.type === ElementType.Style
|
|
);
|
|
}
|
|
// Exports for backwards compatibility
|
|
/** Type for the root element of a document */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Root = ElementType.Root;
|
|
/** Type for Text */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Text$2 = ElementType.Text;
|
|
/** Type for <? ... ?> */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Directive = ElementType.Directive;
|
|
/** Type for <!-- ... --> */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Comment$1 = ElementType.Comment;
|
|
/** Type for <script> tags */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Script = ElementType.Script;
|
|
/** Type for <style> tags */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Style = ElementType.Style;
|
|
/** Type for Any tag */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Tag = ElementType.Tag;
|
|
/** Type for <![CDATA[ ... ]]> */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const CDATA = ElementType.CDATA;
|
|
/** Type for <!doctype ...> */
|
|
// eslint-disable-next-line prefer-destructuring
|
|
const Doctype = ElementType.Doctype;
|
|
|
|
/**
|
|
* Checks if `node` is an element node.
|
|
* @param node Node to check.
|
|
* @returns `true` if the node is an element node.
|
|
*/
|
|
function isTag$1(node) {
|
|
return isTag$2(node);
|
|
}
|
|
/**
|
|
* Checks if `node` is a CDATA node.
|
|
* @param node Node to check.
|
|
* @returns `true` if the node is a CDATA node.
|
|
*/
|
|
function isCDATA(node) {
|
|
return node.type === ElementType.CDATA;
|
|
}
|
|
/**
|
|
* Checks if `node` is a text node.
|
|
* @param node Node to check.
|
|
* @returns `true` if the node is a text node.
|
|
*/
|
|
function isText(node) {
|
|
return node.type === ElementType.Text;
|
|
}
|
|
/**
|
|
* Checks if `node` is a comment node.
|
|
* @param node Node to check.
|
|
* @returns `true` if the node is a comment node.
|
|
*/
|
|
function isComment(node) {
|
|
return node.type === ElementType.Comment;
|
|
}
|
|
/**
|
|
* Checks if `node` has children.
|
|
* @param node Node to check.
|
|
* @returns `true` if the node has children.
|
|
*/
|
|
function hasChildren(node) {
|
|
return Object.hasOwn(node, "children");
|
|
}
|
|
|
|
/**
|
|
* Search a node and its children for nodes passing a test function. If `node` is not an array, it will be wrapped in one.
|
|
*
|
|
* @category Querying
|
|
* @param test Function to test nodes on.
|
|
* @param node Node to search. Will be included in the result set if it matches.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes passing `test`.
|
|
*/
|
|
function filter(test, node, recurse = true, limit = Number.POSITIVE_INFINITY) {
|
|
return find(test, Array.isArray(node) ? node : [node], recurse, limit);
|
|
}
|
|
/**
|
|
* Search an array of nodes and their children for nodes passing a test function.
|
|
*
|
|
* @category Querying
|
|
* @param test Function to test nodes on.
|
|
* @param nodes Array of nodes to search.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes passing `test`.
|
|
*/
|
|
function find(test, nodes, recurse, limit) {
|
|
const result = [];
|
|
/** Stack of the arrays we are looking at. */
|
|
const nodeStack = [Array.isArray(nodes) ? nodes : [nodes]];
|
|
/** Stack of the indices within the arrays. */
|
|
const indexStack = [0];
|
|
for (;;) {
|
|
// First, check if the current array has any more elements to look at.
|
|
if (indexStack[0] >= nodeStack[0].length) {
|
|
// If we have no more arrays to look at, we are done.
|
|
if (indexStack.length === 1) {
|
|
return result;
|
|
}
|
|
// Otherwise, remove the current array from the stack.
|
|
nodeStack.shift();
|
|
indexStack.shift();
|
|
// Loop back to the start to continue with the next array.
|
|
continue;
|
|
}
|
|
const element = nodeStack[0][indexStack[0]++];
|
|
if (test(element)) {
|
|
result.push(element);
|
|
if (--limit <= 0) return result;
|
|
}
|
|
if (recurse && hasChildren(element) && element.children.length > 0) {
|
|
/*
|
|
* Add the children to the stack. We are depth-first, so this is
|
|
* the next array we look at.
|
|
*/
|
|
indexStack.unshift(0);
|
|
nodeStack.unshift(element.children);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Finds one element in a tree that passes a test.
|
|
*
|
|
* @category Querying
|
|
* @param test Function to test nodes on.
|
|
* @param nodes Node or array of nodes to search.
|
|
* @param recurse Also consider child nodes.
|
|
* @returns The first node that passes `test`.
|
|
*/
|
|
function findOne$2(test, nodes, recurse = true) {
|
|
const searchedNodes = Array.isArray(nodes) ? nodes : [nodes];
|
|
for (const node of searchedNodes) {
|
|
if (isTag$1(node) && test(node)) {
|
|
return node;
|
|
}
|
|
if (recurse && hasChildren(node) && node.children.length > 0) {
|
|
const found = findOne$2(test, node.children, true);
|
|
if (found) return found;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Checks if a tree of nodes contains at least one node passing a test.
|
|
*
|
|
* @category Querying
|
|
* @param test Function to test nodes on.
|
|
* @param nodes Array of nodes to search.
|
|
* @returns Whether a tree of nodes contains at least one node passing the test.
|
|
*/
|
|
function existsOne$1(test, nodes) {
|
|
return (Array.isArray(nodes) ? nodes : [nodes]).some(
|
|
(node) =>
|
|
(isTag$1(node) && test(node)) ||
|
|
(hasChildren(node) && existsOne$1(test, node.children))
|
|
);
|
|
}
|
|
/**
|
|
* Search an array of nodes and their children for elements passing a test function.
|
|
*
|
|
* Same as `find`, but limited to elements and with less options, leading to reduced complexity.
|
|
*
|
|
* @category Querying
|
|
* @param test Function to test nodes on.
|
|
* @param nodes Array of nodes to search.
|
|
* @returns All nodes passing `test`.
|
|
*/
|
|
function findAll$1(test, nodes) {
|
|
const result = [];
|
|
const nodeStack = [Array.isArray(nodes) ? nodes : [nodes]];
|
|
const indexStack = [0];
|
|
for (;;) {
|
|
if (indexStack[0] >= nodeStack[0].length) {
|
|
if (nodeStack.length === 1) {
|
|
return result;
|
|
}
|
|
// Otherwise, remove the current array from the stack.
|
|
nodeStack.shift();
|
|
indexStack.shift();
|
|
// Loop back to the start to continue with the next array.
|
|
continue;
|
|
}
|
|
const element = nodeStack[0][indexStack[0]++];
|
|
if (isTag$1(element) && test(element)) result.push(element);
|
|
if (hasChildren(element) && element.children.length > 0) {
|
|
indexStack.unshift(0);
|
|
nodeStack.unshift(element.children);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A map of functions to check nodes against.
|
|
*/
|
|
const Checks = {
|
|
tag_name(name) {
|
|
if (typeof name === "function") {
|
|
return (element) => isTag$1(element) && name(element.name);
|
|
}
|
|
if (name === "*") {
|
|
return isTag$1;
|
|
}
|
|
return (element) => isTag$1(element) && element.name === name;
|
|
},
|
|
tag_type(type) {
|
|
if (typeof type === "function") {
|
|
return (element) => type(element.type);
|
|
}
|
|
return (element) => element.type === type;
|
|
},
|
|
tag_contains(data) {
|
|
if (typeof data === "function") {
|
|
return (element) => isText(element) && data(element.data);
|
|
}
|
|
return (element) => isText(element) && element.data === data;
|
|
}
|
|
};
|
|
/**
|
|
* Returns a function to check whether a node has an attribute with a particular
|
|
* value.
|
|
*
|
|
* @param attrib Attribute to check.
|
|
* @param value Attribute value to look for.
|
|
* @returns A function to check whether the a node has an attribute with a
|
|
* particular value.
|
|
*/
|
|
function getAttribCheck(attrib, value) {
|
|
if (typeof value === "function") {
|
|
return (element) => isTag$1(element) && value(element.attribs[attrib]);
|
|
}
|
|
return (element) => isTag$1(element) && element.attribs[attrib] === value;
|
|
}
|
|
/**
|
|
* Returns a function that returns `true` if either of the input functions
|
|
* returns `true` for a node.
|
|
*
|
|
* @param a First function to combine.
|
|
* @param b Second function to combine.
|
|
* @returns A function taking a node and returning `true` if either of the input
|
|
* functions returns `true` for the node.
|
|
*/
|
|
function combineFuncs(a, b) {
|
|
return (element) => a(element) || b(element);
|
|
}
|
|
/**
|
|
* Returns a function that executes all checks in `options` and returns `true`
|
|
* if any of them match a node.
|
|
*
|
|
* @param options An object describing nodes to look for.
|
|
* @returns A function that executes all checks in `options` and returns `true`
|
|
* if any of them match a node.
|
|
*/
|
|
function compileTest(options) {
|
|
const funcs = Object.keys(options).map((key) => {
|
|
const value = options[key];
|
|
return Object.hasOwn(Checks, key)
|
|
? Checks[key](value)
|
|
: getAttribCheck(key, value);
|
|
});
|
|
return funcs.length === 0 ? null : funcs.reduce(combineFuncs);
|
|
}
|
|
/**
|
|
* Checks whether a node matches the description in `options`.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param options An object describing nodes to look for.
|
|
* @param node The element to test.
|
|
* @returns Whether the element matches the description in `options`.
|
|
*/
|
|
function testElement(options, node) {
|
|
const test = compileTest(options);
|
|
return test ? test(node) : true;
|
|
}
|
|
/**
|
|
* Returns all nodes that match `options`.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param options An object describing nodes to look for.
|
|
* @param nodes Nodes to search through.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes that match `options`.
|
|
*/
|
|
function getElements(
|
|
options,
|
|
nodes,
|
|
recurse,
|
|
limit = Number.POSITIVE_INFINITY
|
|
) {
|
|
const test = compileTest(options);
|
|
return test ? filter(test, nodes, recurse, limit) : [];
|
|
}
|
|
/**
|
|
* Returns the node with the supplied ID.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param id The unique ID attribute value to look for.
|
|
* @param nodes Nodes to search through.
|
|
* @param recurse Also consider child nodes.
|
|
* @returns The node with the supplied ID.
|
|
*/
|
|
function getElementById(id, nodes, recurse = true) {
|
|
if (!Array.isArray(nodes)) nodes = [nodes];
|
|
return findOne$2(getAttribCheck("id", id), nodes, recurse);
|
|
}
|
|
/**
|
|
* Returns all nodes with the supplied `tagName`.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param tagName Tag name to search for.
|
|
* @param nodes Nodes to search through.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes with the supplied `tagName`.
|
|
*/
|
|
function getElementsByTagName(
|
|
tagName,
|
|
nodes,
|
|
recurse = true,
|
|
limit = Number.POSITIVE_INFINITY
|
|
) {
|
|
return filter(Checks["tag_name"](tagName), nodes, recurse, limit);
|
|
}
|
|
/**
|
|
* Returns all nodes with the supplied `className`.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param className Class name to search for.
|
|
* @param nodes Nodes to search through.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes with the supplied `className`.
|
|
*/
|
|
function getElementsByClassName(
|
|
className,
|
|
nodes,
|
|
recurse = true,
|
|
limit = Number.POSITIVE_INFINITY
|
|
) {
|
|
return filter(getAttribCheck("class", className), nodes, recurse, limit);
|
|
}
|
|
/**
|
|
* Returns all nodes with the supplied `type`.
|
|
*
|
|
* @category Legacy Query Functions
|
|
* @param type Element type to look for.
|
|
* @param nodes Nodes to search through.
|
|
* @param recurse Also consider child nodes.
|
|
* @param limit Maximum number of nodes to return.
|
|
* @returns All nodes with the supplied `type`.
|
|
*/
|
|
function getElementsByTagType(
|
|
type,
|
|
nodes,
|
|
recurse = true,
|
|
limit = Number.POSITIVE_INFINITY
|
|
) {
|
|
return filter(Checks["tag_type"](type), nodes, recurse, limit);
|
|
}
|
|
|
|
const xmlCodeMap = new Map([
|
|
[34, """],
|
|
[38, "&"],
|
|
[39, "'"],
|
|
[60, "<"],
|
|
[62, ">"]
|
|
]);
|
|
// For compatibility with node < 4, we wrap `codePointAt`
|
|
/**
|
|
* Read a code point at a given index.
|
|
* @param input Input string to encode or decode.
|
|
* @param index Current read position in the input string.
|
|
*/
|
|
const getCodePoint =
|
|
typeof String.prototype.codePointAt === "function"
|
|
? (input, index) => input.codePointAt(index)
|
|
: // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
|
|
(c, index) =>
|
|
(c.charCodeAt(index) & 0xfc_00) === 0xd8_00
|
|
? (c.charCodeAt(index) - 0xd8_00) * 0x4_00 +
|
|
c.charCodeAt(index + 1) -
|
|
0xdc_00 +
|
|
0x1_00_00
|
|
: c.charCodeAt(index);
|
|
/**
|
|
* Bitset for ASCII characters that need to be escaped in XML.
|
|
*/
|
|
const XML_BITSET_VALUE = 0x50_00_00_c4; // 32..63 -> 34 ("),38 (&),39 ('),60 (<),62 (>)
|
|
/**
|
|
* Encodes all non-ASCII characters, as well as characters not valid in XML
|
|
* documents using XML entities. Uses a fast bitset scan instead of RegExp.
|
|
*
|
|
* If a character has no equivalent entity, a numeric hexadecimal reference
|
|
* (eg. `ü`) will be used.
|
|
* @param input Input string to encode or decode.
|
|
*/
|
|
function encodeXML(input) {
|
|
let out;
|
|
let last = 0;
|
|
const { length } = input;
|
|
for (let index = 0; index < length; index++) {
|
|
const char = input.charCodeAt(index);
|
|
// Check for ASCII chars that don't need escaping
|
|
if (
|
|
char < 0x80 &&
|
|
(((XML_BITSET_VALUE >>> char) & 1) === 0 || char >= 64 || char < 32)
|
|
) {
|
|
continue;
|
|
}
|
|
if (out === undefined) out = input.substring(0, index);
|
|
else if (last !== index) out += input.substring(last, index);
|
|
if (char < 64) {
|
|
// Known replacement
|
|
out += xmlCodeMap.get(char);
|
|
last = index + 1;
|
|
continue;
|
|
}
|
|
// Non-ASCII: encode as numeric entity (handle surrogate pair)
|
|
const cp = getCodePoint(input, index);
|
|
out += `&#x${cp.toString(16)};`;
|
|
if (cp !== char) index++; // Skip trailing surrogate
|
|
last = index + 1;
|
|
}
|
|
if (out === undefined) return input;
|
|
if (last < length) out += input.substr(last);
|
|
return out;
|
|
}
|
|
/**
|
|
* Creates a function that escapes all characters matched by the given regular
|
|
* expression using the given map of characters to escape to their entities.
|
|
* @param regex Regular expression to match characters to escape.
|
|
* @param map Map of characters to escape to their entities.
|
|
* @returns Function that escapes all characters matched by the given regular
|
|
* expression using the given map of characters to escape to their entities.
|
|
*/
|
|
function getEscaper(regex, map) {
|
|
return function escape(data) {
|
|
let match;
|
|
let lastIndex = 0;
|
|
let result = "";
|
|
while ((match = regex.exec(data))) {
|
|
if (lastIndex !== match.index) {
|
|
result += data.substring(lastIndex, match.index);
|
|
}
|
|
// We know that this character will be in the map.
|
|
result += map.get(match[0].charCodeAt(0));
|
|
// Every match will be of length 1
|
|
lastIndex = match.index + 1;
|
|
}
|
|
return result + data.substring(lastIndex);
|
|
};
|
|
}
|
|
/**
|
|
* Encodes all characters that have to be escaped in HTML attributes,
|
|
* following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
|
|
* @param data String to escape.
|
|
*/
|
|
const escapeAttribute = /* #__PURE__ */ getEscaper(
|
|
/["&\u00A0]/g,
|
|
new Map([
|
|
[34, """],
|
|
[38, "&"],
|
|
[160, " "]
|
|
])
|
|
);
|
|
/**
|
|
* Encodes all characters that have to be escaped in HTML text,
|
|
* following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
|
|
* @param data String to escape.
|
|
*/
|
|
const escapeText = /* #__PURE__ */ getEscaper(
|
|
/[&<>\u00A0]/g,
|
|
new Map([
|
|
[38, "&"],
|
|
[60, "<"],
|
|
[62, ">"],
|
|
[160, " "]
|
|
])
|
|
);
|
|
|
|
/**
|
|
* Mixed-case SVG and MathML element names recognized in foreign content.
|
|
*/
|
|
const elementNames = new Map(
|
|
[
|
|
"altGlyph",
|
|
"altGlyphDef",
|
|
"altGlyphItem",
|
|
"animateColor",
|
|
"animateMotion",
|
|
"animateTransform",
|
|
"clipPath",
|
|
"feBlend",
|
|
"feColorMatrix",
|
|
"feComponentTransfer",
|
|
"feComposite",
|
|
"feConvolveMatrix",
|
|
"feDiffuseLighting",
|
|
"feDisplacementMap",
|
|
"feDistantLight",
|
|
"feDropShadow",
|
|
"feFlood",
|
|
"feFuncA",
|
|
"feFuncB",
|
|
"feFuncG",
|
|
"feFuncR",
|
|
"feGaussianBlur",
|
|
"feImage",
|
|
"feMerge",
|
|
"feMergeNode",
|
|
"feMorphology",
|
|
"feOffset",
|
|
"fePointLight",
|
|
"feSpecularLighting",
|
|
"feSpotLight",
|
|
"feTile",
|
|
"feTurbulence",
|
|
"foreignObject",
|
|
"glyphRef",
|
|
"linearGradient",
|
|
"radialGradient",
|
|
"textPath"
|
|
].map((value) => [value.toLowerCase(), value])
|
|
);
|
|
/**
|
|
* Mixed-case SVG and MathML attribute names recognized in foreign content.
|
|
*/
|
|
const attributeNames = new Map(
|
|
[
|
|
"definitionURL",
|
|
"attributeName",
|
|
"attributeType",
|
|
"baseFrequency",
|
|
"baseProfile",
|
|
"calcMode",
|
|
"clipPathUnits",
|
|
"diffuseConstant",
|
|
"edgeMode",
|
|
"filterUnits",
|
|
"glyphRef",
|
|
"gradientTransform",
|
|
"gradientUnits",
|
|
"kernelMatrix",
|
|
"kernelUnitLength",
|
|
"keyPoints",
|
|
"keySplines",
|
|
"keyTimes",
|
|
"lengthAdjust",
|
|
"limitingConeAngle",
|
|
"markerHeight",
|
|
"markerUnits",
|
|
"markerWidth",
|
|
"maskContentUnits",
|
|
"maskUnits",
|
|
"numOctaves",
|
|
"pathLength",
|
|
"patternContentUnits",
|
|
"patternTransform",
|
|
"patternUnits",
|
|
"pointsAtX",
|
|
"pointsAtY",
|
|
"pointsAtZ",
|
|
"preserveAlpha",
|
|
"preserveAspectRatio",
|
|
"primitiveUnits",
|
|
"refX",
|
|
"refY",
|
|
"repeatCount",
|
|
"repeatDur",
|
|
"requiredExtensions",
|
|
"requiredFeatures",
|
|
"specularConstant",
|
|
"specularExponent",
|
|
"spreadMethod",
|
|
"startOffset",
|
|
"stdDeviation",
|
|
"stitchTiles",
|
|
"surfaceScale",
|
|
"systemLanguage",
|
|
"tableValues",
|
|
"targetX",
|
|
"targetY",
|
|
"textLength",
|
|
"viewBox",
|
|
"viewTarget",
|
|
"xChannelSelector",
|
|
"yChannelSelector",
|
|
"zoomAndPan"
|
|
].map((value) => [value.toLowerCase(), value])
|
|
);
|
|
|
|
/*
|
|
* Module dependencies
|
|
*/
|
|
const unencodedElements = new Set([
|
|
"style",
|
|
"script",
|
|
"xmp",
|
|
"iframe",
|
|
"noembed",
|
|
"noframes",
|
|
"plaintext",
|
|
"noscript"
|
|
]);
|
|
function replaceQuotes(value) {
|
|
return value.replace(/"/g, """);
|
|
}
|
|
/**
|
|
* Format attributes
|
|
* @param attributes Attribute map to serialize.
|
|
* @param options Options that control this operation.
|
|
*/
|
|
function formatAttributes(attributes, options) {
|
|
if (!attributes) return;
|
|
const encode =
|
|
(options.encodeEntities ?? options.decodeEntities) === false
|
|
? replaceQuotes
|
|
: !!options.xmlMode || options.encodeEntities !== "utf8"
|
|
? encodeXML
|
|
: escapeAttribute;
|
|
return Object.keys(attributes)
|
|
.map((key) => {
|
|
const value = attributes[key];
|
|
const normalizedValue = value == null ? "" : String(value);
|
|
if (options.xmlMode === "foreign") {
|
|
/* Fix up mixed-case attribute names */
|
|
key = attributeNames.get(key) ?? key;
|
|
}
|
|
if (!(options.emptyAttrs || options.xmlMode) && normalizedValue === "") {
|
|
return key;
|
|
}
|
|
return `${key}="${encode(normalizedValue)}"`;
|
|
})
|
|
.join(" ");
|
|
}
|
|
/**
|
|
* Self-enclosing tags
|
|
*/
|
|
const singleTag = new Set([
|
|
"area",
|
|
"base",
|
|
"basefont",
|
|
"br",
|
|
"col",
|
|
"command",
|
|
"embed",
|
|
"frame",
|
|
"hr",
|
|
"img",
|
|
"input",
|
|
"isindex",
|
|
"keygen",
|
|
"link",
|
|
"meta",
|
|
"param",
|
|
"source",
|
|
"track",
|
|
"wbr"
|
|
]);
|
|
/**
|
|
* Renders a DOM node or an array of DOM nodes to a string.
|
|
*
|
|
* Can be thought of as the equivalent of the `outerHTML` of the passed node(s).
|
|
* @param node Node to be rendered.
|
|
* @param options Changes serialization behavior
|
|
*/
|
|
function render(node, options = {}) {
|
|
const nodes = "length" in node ? node : [node];
|
|
let output = "";
|
|
let index = 0;
|
|
while (index < nodes.length) {
|
|
output += renderNode(nodes[index], options);
|
|
index++;
|
|
}
|
|
return output;
|
|
}
|
|
function renderNode(node, options) {
|
|
switch (node.type) {
|
|
case Root: {
|
|
return render(node.children, options);
|
|
}
|
|
// @ts-expect-error We don't use `Doctype` yet
|
|
case Doctype:
|
|
case Directive: {
|
|
return renderDirective(node);
|
|
}
|
|
case Comment$1: {
|
|
return renderComment(node);
|
|
}
|
|
case CDATA: {
|
|
return renderCdata(node);
|
|
}
|
|
case Script:
|
|
case Style:
|
|
case Tag: {
|
|
return renderTag(node, options);
|
|
}
|
|
case Text$2: {
|
|
return renderText(node, options);
|
|
}
|
|
}
|
|
}
|
|
const foreignModeIntegrationPoints = new Set([
|
|
"mi",
|
|
"mo",
|
|
"mn",
|
|
"ms",
|
|
"mtext",
|
|
"annotation-xml",
|
|
"foreignObject",
|
|
"desc",
|
|
"title"
|
|
]);
|
|
const foreignElements = new Set(["svg", "math"]);
|
|
function renderTag(element, options) {
|
|
// Handle SVG / MathML in HTML
|
|
if (options.xmlMode === "foreign") {
|
|
/* Fix up mixed-case element names */
|
|
element.name = elementNames.get(element.name) ?? element.name;
|
|
/* Exit foreign mode at integration points */
|
|
if (
|
|
element.parent &&
|
|
foreignModeIntegrationPoints.has(element.parent.name)
|
|
) {
|
|
options = { ...options, xmlMode: false };
|
|
}
|
|
}
|
|
if (!options.xmlMode && foreignElements.has(element.name)) {
|
|
options = { ...options, xmlMode: "foreign" };
|
|
}
|
|
let tag = `<${element.name}`;
|
|
const attribs = formatAttributes(element.attribs, options);
|
|
if (attribs) {
|
|
tag += ` ${attribs}`;
|
|
}
|
|
if (
|
|
element.children.length === 0 &&
|
|
(options.xmlMode
|
|
? // In XML mode or foreign mode, and user hasn't explicitly turned off self-closing tags
|
|
options.selfClosingTags !== false
|
|
: // User explicitly asked for self-closing tags, even in HTML mode
|
|
options.selfClosingTags && singleTag.has(element.name))
|
|
) {
|
|
if (!options.xmlMode) tag += " ";
|
|
tag += "/>";
|
|
} else {
|
|
tag += ">";
|
|
if (element.children.length > 0) {
|
|
tag += render(element.children, options);
|
|
}
|
|
if (!!options.xmlMode || !singleTag.has(element.name)) {
|
|
tag += `</${element.name}>`;
|
|
}
|
|
}
|
|
return tag;
|
|
}
|
|
function renderDirective(element) {
|
|
return `<${element.data}>`;
|
|
}
|
|
function renderText(element, options) {
|
|
let data = element.data || "";
|
|
// If entities weren't decoded, no need to encode them back
|
|
if (
|
|
(options.encodeEntities ?? options.decodeEntities) !== false &&
|
|
!(
|
|
!options.xmlMode &&
|
|
element.parent &&
|
|
unencodedElements.has(element.parent.name)
|
|
)
|
|
) {
|
|
data =
|
|
!!options.xmlMode || options.encodeEntities !== "utf8"
|
|
? encodeXML(data)
|
|
: escapeText(data);
|
|
}
|
|
return data;
|
|
}
|
|
function renderCdata(element) {
|
|
return `<![CDATA[${element.children[0].data}]]>`;
|
|
}
|
|
function renderComment(element) {
|
|
return `<!--${element.data}-->`;
|
|
}
|
|
|
|
/**
|
|
* @category Stringify
|
|
* @deprecated Use the `dom-serializer` module directly.
|
|
* @param node Node to get the outer HTML of.
|
|
* @param options Options for serialization.
|
|
* @returns `node`'s outer HTML.
|
|
*/
|
|
function getOuterHTML(node, options) {
|
|
return render(node, options);
|
|
}
|
|
/**
|
|
* @category Stringify
|
|
* @deprecated Use the `dom-serializer` module directly.
|
|
* @param node Node to get the inner HTML of.
|
|
* @param options Options for serialization.
|
|
* @returns `node`'s inner HTML.
|
|
*/
|
|
function getInnerHTML(node, options) {
|
|
return hasChildren(node)
|
|
? node.children.map((node) => getOuterHTML(node, options)).join("")
|
|
: "";
|
|
}
|
|
/**
|
|
* Get a node's inner text. Same as `textContent`, but inserts newlines for `<br>` tags. Ignores comments.
|
|
*
|
|
* @category Stringify
|
|
* @deprecated Use `textContent` instead.
|
|
* @param node Node to get the inner text of.
|
|
* @returns `node`'s inner text.
|
|
*/
|
|
function getText$1(node) {
|
|
if (Array.isArray(node)) return node.map(getText$1).join("");
|
|
if (isTag$1(node))
|
|
return node.name === "br" ? "\n" : getText$1(node.children);
|
|
if (isCDATA(node)) return getText$1(node.children);
|
|
if (isText(node)) return node.data;
|
|
return "";
|
|
}
|
|
/**
|
|
* Get a node's text content. Ignores comments.
|
|
*
|
|
* @category Stringify
|
|
* @param node Node to get the text content of.
|
|
* @returns `node`'s text content.
|
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent}
|
|
*/
|
|
function textContent(node) {
|
|
if (Array.isArray(node)) return node.map(textContent).join("");
|
|
if (hasChildren(node) && !isComment(node)) {
|
|
return textContent(node.children);
|
|
}
|
|
if (isText(node)) return node.data;
|
|
return "";
|
|
}
|
|
/**
|
|
* Get a node's inner text, ignoring `<script>` and `<style>` tags. Ignores comments.
|
|
*
|
|
* @category Stringify
|
|
* @param node Node to get the inner text of.
|
|
* @returns `node`'s inner text.
|
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/innerText}
|
|
*/
|
|
function innerText(node) {
|
|
if (Array.isArray(node)) return node.map(innerText).join("");
|
|
if (hasChildren(node) && (node.type === ElementType.Tag || isCDATA(node))) {
|
|
return innerText(node.children);
|
|
}
|
|
if (isText(node)) return node.data;
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Get the feed object from the root of a DOM tree.
|
|
*
|
|
* @category Feeds
|
|
* @param document The DOM to extract the feed from.
|
|
* @returns The feed.
|
|
*/
|
|
function getFeed(document) {
|
|
const feedRoot = getOneElement(isValidFeed, document);
|
|
return feedRoot
|
|
? feedRoot.name === "feed"
|
|
? getAtomFeed(feedRoot)
|
|
: getRssFeed(feedRoot)
|
|
: null;
|
|
}
|
|
/**
|
|
* Parse an Atom feed.
|
|
*
|
|
* @param feedRoot The root of the feed.
|
|
* @returns The parsed feed.
|
|
*/
|
|
function getAtomFeed(feedRoot) {
|
|
const childs = feedRoot.children;
|
|
const feed = {
|
|
type: "atom",
|
|
items: getElementsByTagName("entry", childs).map((item) => {
|
|
const { children } = item;
|
|
const entry = { media: getMediaElements(children) };
|
|
addConditionally(entry, "id", "id", children);
|
|
addConditionally(entry, "title", "title", children);
|
|
const href = getOneElement("link", children)?.attribs["href"];
|
|
if (href) {
|
|
entry.link = href;
|
|
}
|
|
const description =
|
|
fetch("summary", children) || fetch("content", children);
|
|
if (description) {
|
|
entry.description = description;
|
|
}
|
|
const pubDate = fetch("updated", children);
|
|
if (pubDate) {
|
|
entry.pubDate = new Date(pubDate);
|
|
}
|
|
return entry;
|
|
})
|
|
};
|
|
addConditionally(feed, "id", "id", childs);
|
|
addConditionally(feed, "title", "title", childs);
|
|
const href = getOneElement("link", childs)?.attribs["href"];
|
|
if (href) {
|
|
feed.link = href;
|
|
}
|
|
addConditionally(feed, "description", "subtitle", childs);
|
|
const updated = fetch("updated", childs);
|
|
if (updated) {
|
|
feed.updated = new Date(updated);
|
|
}
|
|
addConditionally(feed, "author", "email", childs, true);
|
|
return feed;
|
|
}
|
|
/**
|
|
* Parse a RSS feed.
|
|
*
|
|
* @param feedRoot The root of the feed.
|
|
* @returns The parsed feed.
|
|
*/
|
|
function getRssFeed(feedRoot) {
|
|
const childs = getOneElement("channel", feedRoot.children)?.children ?? [];
|
|
const feed = {
|
|
type: feedRoot.name.substr(0, 3),
|
|
id: "",
|
|
items: getElementsByTagName("item", feedRoot.children).map((item) => {
|
|
const { children } = item;
|
|
const entry = { media: getMediaElements(children) };
|
|
addConditionally(entry, "id", "guid", children);
|
|
addConditionally(entry, "title", "title", children);
|
|
addConditionally(entry, "link", "link", children);
|
|
addConditionally(entry, "description", "description", children);
|
|
const pubDate = fetch("pubDate", children) || fetch("dc:date", children);
|
|
if (pubDate) entry.pubDate = new Date(pubDate);
|
|
return entry;
|
|
})
|
|
};
|
|
addConditionally(feed, "title", "title", childs);
|
|
addConditionally(feed, "link", "link", childs);
|
|
addConditionally(feed, "description", "description", childs);
|
|
const updated = fetch("lastBuildDate", childs);
|
|
if (updated) {
|
|
feed.updated = new Date(updated);
|
|
}
|
|
addConditionally(feed, "author", "managingEditor", childs, true);
|
|
return feed;
|
|
}
|
|
const MEDIA_KEYS_STRING = ["url", "type", "lang"];
|
|
const MEDIA_KEYS_INT = [
|
|
"fileSize",
|
|
"bitrate",
|
|
"framerate",
|
|
"samplingrate",
|
|
"channels",
|
|
"duration",
|
|
"height",
|
|
"width"
|
|
];
|
|
/**
|
|
* Get all media elements of a feed item.
|
|
*
|
|
* @param where Nodes to search in.
|
|
* @returns Media elements.
|
|
*/
|
|
function getMediaElements(where) {
|
|
return getElementsByTagName("media:content", where).map((element) => {
|
|
const { attribs } = element;
|
|
const media = {
|
|
medium: attribs["medium"],
|
|
isDefault: !!attribs["isDefault"]
|
|
};
|
|
for (const attrib of MEDIA_KEYS_STRING) {
|
|
if (attribs[attrib]) {
|
|
media[attrib] = attribs[attrib];
|
|
}
|
|
}
|
|
for (const attrib of MEDIA_KEYS_INT) {
|
|
if (attribs[attrib]) {
|
|
media[attrib] = Number.parseInt(attribs[attrib], 10);
|
|
}
|
|
}
|
|
if (attribs["expression"]) {
|
|
media.expression = attribs["expression"];
|
|
}
|
|
return media;
|
|
});
|
|
}
|
|
/**
|
|
* Get one element by tag name.
|
|
*
|
|
* @param tagName Tag name to look for
|
|
* @param node Node to search in
|
|
* @returns The element or null
|
|
*/
|
|
function getOneElement(tagName, node) {
|
|
return getElementsByTagName(tagName, node, true, 1)[0];
|
|
}
|
|
/**
|
|
* Get the text content of an element with a certain tag name.
|
|
*
|
|
* @param tagName Tag name to look for.
|
|
* @param where Node to search in.
|
|
* @param recurse Whether to recurse into child nodes.
|
|
* @returns The text content of the element.
|
|
*/
|
|
function fetch(tagName, where, recurse = false) {
|
|
return textContent(getElementsByTagName(tagName, where, recurse, 1)).trim();
|
|
}
|
|
/**
|
|
* Adds a property to an object if it has a value.
|
|
*
|
|
* @param object Object to be extended.
|
|
* @param property Property name.
|
|
* @param tagName Tag name that contains the conditionally added property.
|
|
* @param where Element to search for the property.
|
|
* @param recurse Whether to recurse into child nodes.
|
|
*/
|
|
function addConditionally(object, property, tagName, where, recurse = false) {
|
|
const value = fetch(tagName, where, recurse);
|
|
if (value) object[property] = value;
|
|
}
|
|
/**
|
|
* Checks if an element is a feed root node.
|
|
*
|
|
* @param value The name of the element to check.
|
|
* @returns Whether an element is a feed root node.
|
|
*/
|
|
function isValidFeed(value) {
|
|
return value === "rss" || value === "feed" || value === "rdf:RDF";
|
|
}
|
|
|
|
/**
|
|
* Given an array of nodes, remove any member that is contained by another
|
|
* member.
|
|
*
|
|
* @category Helpers
|
|
* @param nodes Nodes to filter.
|
|
* @returns Remaining nodes that aren't contained by other nodes.
|
|
*/
|
|
function removeSubsets$1(nodes) {
|
|
let index = nodes.length;
|
|
/*
|
|
* Check if each node (or one of its ancestors) is already contained in the
|
|
* array.
|
|
*/
|
|
while (--index >= 0) {
|
|
const node = nodes[index];
|
|
/*
|
|
* Remove the node if it is not unique.
|
|
* We are going through the array from the end, so we only
|
|
* have to check nodes that preceed the node under consideration in the array.
|
|
*/
|
|
if (index > 0 && nodes.lastIndexOf(node, index - 1) >= 0) {
|
|
nodes.splice(index, 1);
|
|
continue;
|
|
}
|
|
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
if (nodes.includes(ancestor)) {
|
|
nodes.splice(index, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nodes;
|
|
}
|
|
/**
|
|
* @category Helpers
|
|
* @see {@link http://dom.spec.whatwg.org/#dom-node-comparedocumentposition}
|
|
*/
|
|
var DocumentPosition;
|
|
(function (DocumentPosition) {
|
|
DocumentPosition[(DocumentPosition["DISCONNECTED"] = 1)] = "DISCONNECTED";
|
|
DocumentPosition[(DocumentPosition["PRECEDING"] = 2)] = "PRECEDING";
|
|
DocumentPosition[(DocumentPosition["FOLLOWING"] = 4)] = "FOLLOWING";
|
|
DocumentPosition[(DocumentPosition["CONTAINS"] = 8)] = "CONTAINS";
|
|
DocumentPosition[(DocumentPosition["CONTAINED_BY"] = 16)] = "CONTAINED_BY";
|
|
})(DocumentPosition || (DocumentPosition = {}));
|
|
/**
|
|
* Compare the position of one node against another node in any other document,
|
|
* returning a bitmask with the values from {@link DocumentPosition}.
|
|
*
|
|
* Document order:
|
|
* > There is an ordering, document order, defined on all the nodes in the
|
|
* > document corresponding to the order in which the first character of the
|
|
* > XML representation of each node occurs in the XML representation of the
|
|
* > document after expansion of general entities. Thus, the document element
|
|
* > node will be the first node. Element nodes occur before their children.
|
|
* > Thus, document order orders element nodes in order of the occurrence of
|
|
* > their start-tag in the XML (after expansion of entities). The attribute
|
|
* > nodes of an element occur after the element and before its children. The
|
|
* > relative order of attribute nodes is implementation-dependent.
|
|
*
|
|
* Source:
|
|
* http://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-document-order
|
|
*
|
|
* @category Helpers
|
|
* @param nodeA The first node to use in the comparison
|
|
* @param nodeB The second node to use in the comparison
|
|
* @returns A bitmask describing the input nodes' relative position.
|
|
*
|
|
* See http://dom.spec.whatwg.org/#dom-node-comparedocumentposition for
|
|
* a description of these values.
|
|
*/
|
|
function compareDocumentPosition(nodeA, nodeB) {
|
|
const aParents = [];
|
|
const bParents = [];
|
|
if (nodeA === nodeB) {
|
|
return 0;
|
|
}
|
|
let current = hasChildren(nodeA) ? nodeA : nodeA.parent;
|
|
while (current) {
|
|
aParents.unshift(current);
|
|
current = current.parent;
|
|
}
|
|
current = hasChildren(nodeB) ? nodeB : nodeB.parent;
|
|
while (current) {
|
|
bParents.unshift(current);
|
|
current = current.parent;
|
|
}
|
|
const maxIndex = Math.min(aParents.length, bParents.length);
|
|
let index = 0;
|
|
while (index < maxIndex && aParents[index] === bParents[index]) {
|
|
index++;
|
|
}
|
|
if (index === 0) {
|
|
return DocumentPosition.DISCONNECTED;
|
|
}
|
|
const sharedParent = aParents[index - 1];
|
|
const siblings = sharedParent.children;
|
|
const aSibling = aParents[index];
|
|
const bSibling = bParents[index];
|
|
if (siblings.indexOf(aSibling) > siblings.indexOf(bSibling)) {
|
|
if (sharedParent === nodeB) {
|
|
return DocumentPosition.FOLLOWING | DocumentPosition.CONTAINED_BY;
|
|
}
|
|
return DocumentPosition.FOLLOWING;
|
|
}
|
|
if (sharedParent === nodeA) {
|
|
return DocumentPosition.PRECEDING | DocumentPosition.CONTAINS;
|
|
}
|
|
return DocumentPosition.PRECEDING;
|
|
}
|
|
/**
|
|
* Sort an array of nodes based on their relative position in the document,
|
|
* removing any duplicate nodes. If the array contains nodes that do not belong
|
|
* to the same document, sort order is unspecified.
|
|
*
|
|
* @category Helpers
|
|
* @param nodes Array of DOM nodes.
|
|
* @returns Collection of unique nodes, sorted in document order.
|
|
*/
|
|
function uniqueSort(nodes) {
|
|
nodes = nodes.filter(
|
|
(node, index, array) => !array.includes(node, index + 1)
|
|
);
|
|
nodes.sort((a, b) => {
|
|
const relative = compareDocumentPosition(a, b);
|
|
if (relative & DocumentPosition.PRECEDING) {
|
|
return -1;
|
|
}
|
|
if (relative & DocumentPosition.FOLLOWING) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
return nodes;
|
|
}
|
|
|
|
/**
|
|
* Remove an element from the dom
|
|
*
|
|
* @category Manipulation
|
|
* @param element The element to be removed.
|
|
*/
|
|
function removeElement(element) {
|
|
if (element.prev) element.prev.next = element.next;
|
|
if (element.next) element.next.prev = element.prev;
|
|
if (element.parent) {
|
|
const childs = element.parent.children;
|
|
const childsIndex = childs.lastIndexOf(element);
|
|
if (childsIndex !== -1) {
|
|
childs.splice(childsIndex, 1);
|
|
}
|
|
}
|
|
element.next = null;
|
|
element.prev = null;
|
|
element.parent = null;
|
|
}
|
|
/**
|
|
* Replace an element in the dom
|
|
*
|
|
* @category Manipulation
|
|
* @param element The element to be replaced.
|
|
* @param replacement The element to be added
|
|
*/
|
|
function replaceElement(element, replacement) {
|
|
replacement.prev = element.prev;
|
|
if (replacement.prev) {
|
|
replacement.prev.next = replacement;
|
|
}
|
|
replacement.next = element.next;
|
|
if (replacement.next) {
|
|
replacement.next.prev = replacement;
|
|
}
|
|
replacement.parent = element.parent;
|
|
if (replacement.parent) {
|
|
const { children } = replacement.parent;
|
|
const elementIndex = children.lastIndexOf(element);
|
|
if (elementIndex === -1) {
|
|
return;
|
|
}
|
|
children[elementIndex] = replacement;
|
|
element.parent = null;
|
|
}
|
|
}
|
|
/**
|
|
* Append a child to an element.
|
|
*
|
|
* @category Manipulation
|
|
* @param parent The element to append to.
|
|
* @param child The element to be added as a child.
|
|
*/
|
|
function appendChild(parent, child) {
|
|
removeElement(child);
|
|
child.next = null;
|
|
child.parent = parent;
|
|
if (parent.children.push(child) > 1) {
|
|
const sibling = parent.children[parent.children.length - 2];
|
|
sibling.next = child;
|
|
child.prev = sibling;
|
|
} else {
|
|
child.prev = null;
|
|
}
|
|
}
|
|
/**
|
|
* Append an element after another.
|
|
*
|
|
* @category Manipulation
|
|
* @param element The element to append after.
|
|
* @param next The element be added.
|
|
*/
|
|
function append$1(element, next) {
|
|
removeElement(next);
|
|
const { parent } = element;
|
|
const currentNext = element.next;
|
|
next.next = currentNext;
|
|
next.prev = element;
|
|
element.next = next;
|
|
next.parent = parent;
|
|
if (currentNext) {
|
|
currentNext.prev = next;
|
|
if (parent) {
|
|
const childs = parent.children;
|
|
childs.splice(childs.lastIndexOf(currentNext), 0, next);
|
|
}
|
|
} else if (parent) {
|
|
parent.children.push(next);
|
|
}
|
|
}
|
|
/**
|
|
* Prepend a child to an element.
|
|
*
|
|
* @category Manipulation
|
|
* @param parent The element to prepend before.
|
|
* @param child The element to be added as a child.
|
|
*/
|
|
function prependChild(parent, child) {
|
|
removeElement(child);
|
|
child.parent = parent;
|
|
child.prev = null;
|
|
if (parent.children.unshift(child) === 1) {
|
|
child.next = null;
|
|
} else {
|
|
const sibling = parent.children[1];
|
|
sibling.prev = child;
|
|
child.next = sibling;
|
|
}
|
|
}
|
|
/**
|
|
* Prepend an element before another.
|
|
*
|
|
* @category Manipulation
|
|
* @param element The element to prepend before.
|
|
* @param previous The element to be added.
|
|
*/
|
|
function prepend(element, previous) {
|
|
removeElement(previous);
|
|
const { parent } = element;
|
|
if (parent) {
|
|
const childs = parent.children;
|
|
childs.splice(childs.indexOf(element), 0, previous);
|
|
}
|
|
if (element.prev) {
|
|
element.prev.next = previous;
|
|
}
|
|
previous.parent = parent;
|
|
previous.prev = element.prev;
|
|
previous.next = element;
|
|
element.prev = previous;
|
|
}
|
|
|
|
/**
|
|
* Get a node's children.
|
|
*
|
|
* @category Traversal
|
|
* @param element Node to get the children of.
|
|
* @returns `element`'s children, or an empty array.
|
|
*/
|
|
function getChildren$1(element) {
|
|
return hasChildren(element) ? element.children : [];
|
|
}
|
|
function getParent$1(element) {
|
|
return element.parent || null;
|
|
}
|
|
/**
|
|
* Gets an elements siblings, including the element itself.
|
|
*
|
|
* Attempts to get the children through the element's parent first. If we don't
|
|
* have a parent (the element is a root node), we walk the element's `prev` &
|
|
* `next` to get all remaining nodes.
|
|
*
|
|
* @category Traversal
|
|
* @param element Element to get the siblings of.
|
|
* @returns `element`'s siblings, including `element`.
|
|
*/
|
|
function getSiblings$1(element) {
|
|
const parent = getParent$1(element);
|
|
if (parent != null) return getChildren$1(parent);
|
|
const siblings = [element];
|
|
let { prev, next } = element;
|
|
while (prev != null) {
|
|
siblings.unshift(prev);
|
|
({ prev } = prev);
|
|
}
|
|
while (next != null) {
|
|
siblings.push(next);
|
|
({ next } = next);
|
|
}
|
|
return siblings;
|
|
}
|
|
/**
|
|
* Gets an attribute from an element.
|
|
*
|
|
* @category Traversal
|
|
* @param element Element to check.
|
|
* @param name Attribute name to retrieve.
|
|
* @returns The element's attribute value, or `undefined`.
|
|
*/
|
|
function getAttributeValue$1(element, name) {
|
|
const { attribs } = element;
|
|
return attribs?.[name];
|
|
}
|
|
/**
|
|
* Checks whether an element has an attribute.
|
|
*
|
|
* @category Traversal
|
|
* @param element Element to check.
|
|
* @param name Attribute name to look for.
|
|
* @returns Returns whether `element` has the attribute `name`.
|
|
*/
|
|
function hasAttrib$1(element, name) {
|
|
const { attribs } = element;
|
|
return (
|
|
attribs != null && Object.hasOwn(attribs, name) && attribs[name] != null
|
|
);
|
|
}
|
|
/**
|
|
* Get the tag name of an element.
|
|
*
|
|
* @category Traversal
|
|
* @param element The element to get the name for.
|
|
* @returns The tag name of `element`.
|
|
*/
|
|
function getName$1(element) {
|
|
return element.name;
|
|
}
|
|
/**
|
|
* Returns the next element sibling of a node.
|
|
*
|
|
* @category Traversal
|
|
* @param element The element to get the next sibling of.
|
|
* @returns `element`'s next sibling that is a tag, or `null` if there is no next
|
|
* sibling.
|
|
*/
|
|
function nextElementSibling(element) {
|
|
let { next } = element;
|
|
while (next !== null && !isTag$1(next)) ({ next } = next);
|
|
return next;
|
|
}
|
|
/**
|
|
* Returns the previous element sibling of a node.
|
|
*
|
|
* @category Traversal
|
|
* @param element The element to get the previous sibling of.
|
|
* @returns `element`'s previous sibling that is a tag, or `null` if there is no
|
|
* previous sibling.
|
|
*/
|
|
// eslint-disable-next-line unicorn/prevent-abbreviations -- Keep public API name for backwards compatibility.
|
|
function prevElementSibling(element) {
|
|
let { prev } = element;
|
|
while (prev !== null && !isTag$1(prev)) ({ prev } = prev);
|
|
return prev;
|
|
}
|
|
|
|
var DomUtils = /*#__PURE__*/ Object.freeze({
|
|
__proto__: null,
|
|
get DocumentPosition() {
|
|
return DocumentPosition;
|
|
},
|
|
append: append$1,
|
|
appendChild: appendChild,
|
|
compareDocumentPosition: compareDocumentPosition,
|
|
existsOne: existsOne$1,
|
|
filter: filter,
|
|
find: find,
|
|
findAll: findAll$1,
|
|
findOne: findOne$2,
|
|
getAttributeValue: getAttributeValue$1,
|
|
getChildren: getChildren$1,
|
|
getElementById: getElementById,
|
|
getElements: getElements,
|
|
getElementsByClassName: getElementsByClassName,
|
|
getElementsByTagName: getElementsByTagName,
|
|
getElementsByTagType: getElementsByTagType,
|
|
getFeed: getFeed,
|
|
getInnerHTML: getInnerHTML,
|
|
getName: getName$1,
|
|
getOuterHTML: getOuterHTML,
|
|
getParent: getParent$1,
|
|
getSiblings: getSiblings$1,
|
|
getText: getText$1,
|
|
hasAttrib: hasAttrib$1,
|
|
innerText: innerText,
|
|
nextElementSibling: nextElementSibling,
|
|
prepend: prepend,
|
|
prependChild: prependChild,
|
|
prevElementSibling: prevElementSibling,
|
|
removeElement: removeElement,
|
|
removeSubsets: removeSubsets$1,
|
|
replaceElement: replaceElement,
|
|
testElement: testElement,
|
|
textContent: textContent,
|
|
uniqueSort: uniqueSort
|
|
});
|
|
|
|
/**
|
|
* All reserved characters in a regex, used for escaping.
|
|
*
|
|
* Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
|
|
* https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
|
|
*/
|
|
const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
|
|
const whitespaceRe = /\s/;
|
|
function escapeRegex(value) {
|
|
return value.replace(reChars, "\\$&");
|
|
}
|
|
/**
|
|
* Attributes that are case-insensitive in HTML.
|
|
* @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
|
|
*/
|
|
const caseInsensitiveAttributes = new Set([
|
|
"accept",
|
|
"accept-charset",
|
|
"align",
|
|
"alink",
|
|
"axis",
|
|
"bgcolor",
|
|
"charset",
|
|
"checked",
|
|
"clear",
|
|
"codetype",
|
|
"color",
|
|
"compact",
|
|
"declare",
|
|
"defer",
|
|
"dir",
|
|
"direction",
|
|
"disabled",
|
|
"enctype",
|
|
"face",
|
|
"frame",
|
|
"hreflang",
|
|
"http-equiv",
|
|
"lang",
|
|
"language",
|
|
"link",
|
|
"media",
|
|
"method",
|
|
"multiple",
|
|
"nohref",
|
|
"noresize",
|
|
"noshade",
|
|
"nowrap",
|
|
"readonly",
|
|
"rel",
|
|
"rev",
|
|
"rules",
|
|
"scope",
|
|
"scrolling",
|
|
"selected",
|
|
"shape",
|
|
"target",
|
|
"text",
|
|
"type",
|
|
"valign",
|
|
"valuetype",
|
|
"vlink"
|
|
]);
|
|
function shouldIgnoreCase(selector, options) {
|
|
return typeof selector.ignoreCase === "boolean"
|
|
? selector.ignoreCase
|
|
: selector.ignoreCase === "quirks"
|
|
? !!options.quirksMode
|
|
: !options.xmlMode && caseInsensitiveAttributes.has(selector.name);
|
|
}
|
|
/**
|
|
* Attribute selectors
|
|
*/
|
|
const attributeRules = {
|
|
equals(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name } = data;
|
|
let { value } = data;
|
|
if (shouldIgnoreCase(data, options)) {
|
|
value = value.toLowerCase();
|
|
return (element) => {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
attribute != null &&
|
|
attribute.length === value.length &&
|
|
attribute.toLowerCase() === value &&
|
|
next(element)
|
|
);
|
|
};
|
|
}
|
|
return (element) =>
|
|
adapter.getAttributeValue(element, name) === value && next(element);
|
|
},
|
|
hyphen(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name } = data;
|
|
let { value } = data;
|
|
const { length } = value;
|
|
if (shouldIgnoreCase(data, options)) {
|
|
value = value.toLowerCase();
|
|
return function hyphenIC(element) {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
attribute != null &&
|
|
(attribute.length === length || attribute.charAt(length) === "-") &&
|
|
attribute.substr(0, length).toLowerCase() === value &&
|
|
next(element)
|
|
);
|
|
};
|
|
}
|
|
return function hyphen(element) {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
attribute != null &&
|
|
(attribute.length === length || attribute.charAt(length) === "-") &&
|
|
attribute.substr(0, length) === value &&
|
|
next(element)
|
|
);
|
|
};
|
|
},
|
|
element(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name, value } = data;
|
|
if (whitespaceRe.test(value)) {
|
|
return falseFunc;
|
|
}
|
|
const regex = new RegExp(
|
|
`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`,
|
|
shouldIgnoreCase(data, options) ? "i" : ""
|
|
);
|
|
return function element(node) {
|
|
const attribute = adapter.getAttributeValue(node, name);
|
|
return (
|
|
attribute != null &&
|
|
attribute.length >= value.length &&
|
|
regex.test(attribute) &&
|
|
next(node)
|
|
);
|
|
};
|
|
},
|
|
exists(next, { name }, { adapter }) {
|
|
return (element) => adapter.hasAttrib(element, name) && next(element);
|
|
},
|
|
start(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name } = data;
|
|
let { value } = data;
|
|
const { length } = value;
|
|
if (length === 0) {
|
|
return falseFunc;
|
|
}
|
|
if (shouldIgnoreCase(data, options)) {
|
|
value = value.toLowerCase();
|
|
return (element) => {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
attribute != null &&
|
|
attribute.length >= length &&
|
|
attribute.substr(0, length).toLowerCase() === value &&
|
|
next(element)
|
|
);
|
|
};
|
|
}
|
|
return (element) =>
|
|
!!adapter.getAttributeValue(element, name)?.startsWith(value) &&
|
|
next(element);
|
|
},
|
|
end(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name } = data;
|
|
let { value } = data;
|
|
const length = -value.length;
|
|
if (length === 0) {
|
|
return falseFunc;
|
|
}
|
|
if (shouldIgnoreCase(data, options)) {
|
|
value = value.toLowerCase();
|
|
return (element) =>
|
|
adapter
|
|
.getAttributeValue(element, name)
|
|
?.substr(length)
|
|
.toLowerCase() === value && next(element);
|
|
}
|
|
return (element) =>
|
|
!!adapter.getAttributeValue(element, name)?.endsWith(value) &&
|
|
next(element);
|
|
},
|
|
any(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name, value } = data;
|
|
if (value === "") {
|
|
return falseFunc;
|
|
}
|
|
if (shouldIgnoreCase(data, options)) {
|
|
const regex = new RegExp(escapeRegex(value), "i");
|
|
return function anyIC(element) {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
attribute != null &&
|
|
attribute.length >= value.length &&
|
|
regex.test(attribute) &&
|
|
next(element)
|
|
);
|
|
};
|
|
}
|
|
return (element) =>
|
|
!!adapter.getAttributeValue(element, name)?.includes(value) &&
|
|
next(element);
|
|
},
|
|
not(next, data, options) {
|
|
const { adapter } = options;
|
|
const { name } = data;
|
|
let { value } = data;
|
|
if (value === "") {
|
|
return (element) =>
|
|
!!adapter.getAttributeValue(element, name) && next(element);
|
|
}
|
|
if (shouldIgnoreCase(data, options)) {
|
|
value = value.toLowerCase();
|
|
return (element) => {
|
|
const attribute = adapter.getAttributeValue(element, name);
|
|
return (
|
|
(attribute == null ||
|
|
attribute.length !== value.length ||
|
|
attribute.toLowerCase() !== value) &&
|
|
next(element)
|
|
);
|
|
};
|
|
}
|
|
return (element) =>
|
|
adapter.getAttributeValue(element, name) !== value && next(element);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Find all elements matching the query. If not in XML mode, the query will ignore
|
|
* the contents of `<template>` elements.
|
|
* @param query - Function that returns true if the element matches the query.
|
|
* @param nodes - Nodes to query. If a node is an element, its children will be queried.
|
|
* @param options - Options for querying the document.
|
|
* @returns All matching elements.
|
|
*/
|
|
/**
|
|
* Find the first element matching the query. If not in XML mode, the query will ignore
|
|
* the contents of `<template>` elements.
|
|
* @param query - Function that returns true if the element matches the query.
|
|
* @param nodes - Nodes to query. If a node is an element, its children will be queried.
|
|
* @param options - Options for querying the document.
|
|
* @returns The first matching element, or null if there was no match.
|
|
*/
|
|
function findOne$1(query, nodes, options) {
|
|
const { adapter, xmlMode = false } = options;
|
|
/** Stack of the arrays we are looking at. */
|
|
const nodeStack = [nodes];
|
|
/** Stack of the indices within the arrays. */
|
|
const indexStack = [0];
|
|
for (;;) {
|
|
// First, check if the current array has any more elements to look at.
|
|
if (indexStack[0] >= nodeStack[0].length) {
|
|
// If we have no more arrays to look at, we are done.
|
|
if (nodeStack.length === 1) {
|
|
return null;
|
|
}
|
|
nodeStack.shift();
|
|
indexStack.shift();
|
|
// Loop back to the start to continue with the next array.
|
|
continue;
|
|
}
|
|
const element = nodeStack[0][indexStack[0]++];
|
|
if (!adapter.isTag(element)) {
|
|
continue;
|
|
}
|
|
if (query(element)) {
|
|
return element;
|
|
}
|
|
if (xmlMode || adapter.getName(element) !== "template") {
|
|
/*
|
|
* Add the children to the stack. We are depth-first, so this is
|
|
* the next array we look at.
|
|
*/
|
|
const children = adapter.getChildren(element);
|
|
if (children.length > 0) {
|
|
nodeStack.unshift(children);
|
|
indexStack.unshift(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get all element siblings after the provided node.
|
|
* @param element Element candidate being tested.
|
|
* @param adapter Adapter implementation used for DOM operations.
|
|
*/
|
|
function getNextSiblings(element, adapter) {
|
|
const siblings = adapter.getSiblings(element);
|
|
if (siblings.length <= 1) {
|
|
return [];
|
|
}
|
|
const elementIndex = siblings.indexOf(element);
|
|
if (elementIndex === -1 || elementIndex === siblings.length - 1) {
|
|
return [];
|
|
}
|
|
return siblings.slice(elementIndex + 1).filter(adapter.isTag);
|
|
}
|
|
/**
|
|
* Get the parent element of a node.
|
|
* @param node Node to inspect.
|
|
* @param adapter Adapter implementation used for DOM operations.
|
|
*/
|
|
function getElementParent(node, adapter) {
|
|
const parent = adapter.getParent(node);
|
|
return parent != null && adapter.isTag(parent) ? parent : null;
|
|
}
|
|
|
|
/**
|
|
* Only text controls can be made read-only, since for other controls (such
|
|
* as checkboxes and buttons) there is no useful distinction between being
|
|
* read-only and being disabled.
|
|
* @see {@link https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly}
|
|
*/
|
|
const textControl =
|
|
"input:is([type=text i],[type=search i],[type=url i],[type=tel i],[type=email i],[type=password i],[type=date i],[type=month i],[type=week i],[type=time i],[type=datetime-local i],[type=number i])";
|
|
/**
|
|
* Aliases are pseudos that are expressed as selectors.
|
|
*/
|
|
const aliases = {
|
|
// Links
|
|
"any-link": ":is(a, area, link)[href]",
|
|
link: ":any-link:not(:visited)",
|
|
// Forms
|
|
// https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
|
|
disabled: `:is(
|
|
:is(button, input, select, textarea, optgroup, option)[disabled],
|
|
optgroup[disabled] > option,
|
|
fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)
|
|
)`,
|
|
enabled:
|
|
":is(button, input, select, textarea, optgroup, option, fieldset):not(:disabled)",
|
|
checked:
|
|
":is(:is(input[type=radio], input[type=checkbox])[checked], :selected)",
|
|
required: ":is(input, select, textarea)[required]",
|
|
optional: ":is(input, select, textarea):not([required])",
|
|
"read-only": `[readonly]:is(textarea, ${textControl})`,
|
|
"read-write": `:not([readonly]):is(textarea, ${textControl})`,
|
|
// JQuery extensions
|
|
/**
|
|
* `:selected` matches option elements that have the `selected` attribute,
|
|
* or are the first option element in a select element that does not have
|
|
* the `multiple` attribute and does not have any option elements with the
|
|
* `selected` attribute.
|
|
* @see https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-selectedness
|
|
*/
|
|
selected:
|
|
"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",
|
|
checkbox: "[type=checkbox]",
|
|
file: "[type=file]",
|
|
password: "[type=password]",
|
|
radio: "[type=radio]",
|
|
reset: "[type=reset]",
|
|
image: "[type=image]",
|
|
submit: "[type=submit]",
|
|
parent: ":not(:empty)",
|
|
header: ":is(h1, h2, h3, h4, h5, h6)",
|
|
button: ":is(button, input[type=button])",
|
|
input: ":is(input, textarea, select, button)",
|
|
text: "input:is(:not([type!='']), [type=text])"
|
|
};
|
|
|
|
/**
|
|
* Returns a function that checks if an elements index matches the given rule
|
|
* highly optimized to return the fastest solution.
|
|
* @param parsed A tuple [a, b], as returned by `parse`.
|
|
* @returns A highly optimized function that returns whether an index matches the nth-check.
|
|
* @example
|
|
*
|
|
* ```js
|
|
* const check = nthCheck.compile([2, 3]);
|
|
*
|
|
* check(0); // `false`
|
|
* check(1); // `false`
|
|
* check(2); // `true`
|
|
* check(3); // `false`
|
|
* check(4); // `true`
|
|
* check(5); // `false`
|
|
* check(6); // `true`
|
|
* ```
|
|
*/
|
|
function compile$1(parsed) {
|
|
const a = parsed[0];
|
|
// Subtract 1 from `b`, to convert from one- to zero-indexed.
|
|
const b = parsed[1] - 1;
|
|
/*
|
|
* When `b <= 0`, `a * n` won't be lead to any matches for `a < 0`.
|
|
* Besides, the specification states that no elements are
|
|
* matched when `a` and `b` are 0.
|
|
*
|
|
* `b < 0` here as we subtracted 1 from `b` above.
|
|
*/
|
|
if (b < 0 && a <= 0) return falseFunc;
|
|
// When `a` is in the range -1..1, it matches any element (so only `b` is checked).
|
|
if (a === -1) return (index) => index <= b;
|
|
if (a === 0) return (index) => index === b;
|
|
// When `b <= 0` and `a === 1`, they match any element.
|
|
if (a === 1) return b < 0 ? trueFunc : (index) => index >= b;
|
|
/*
|
|
* Otherwise, modulo can be used to check if there is a match.
|
|
*
|
|
* Modulo doesn't care about the sign, so let's use `a`s absolute value.
|
|
*/
|
|
const absA = Math.abs(a);
|
|
// Get `b mod a`, + a if this is negative.
|
|
const bModulo = ((b % absA) + absA) % absA;
|
|
return a > 1
|
|
? (index) => index >= b && index % absA === bModulo
|
|
: (index) => index <= b && index % absA === bModulo;
|
|
}
|
|
|
|
// Following http://www.w3.org/TR/css3-selectors/#nth-child-pseudo
|
|
// Whitespace as per https://www.w3.org/TR/selectors-3/#lex is " \t\r\n\f"
|
|
const whitespace = new Set([9, 10, 12, 13, 32]);
|
|
const ZERO = "0".charCodeAt(0);
|
|
const NINE = "9".charCodeAt(0);
|
|
/**
|
|
* Parses an expression.
|
|
* @param formula CSS nth-formula to parse.
|
|
* @throws {Error} An `Error` if parsing fails.
|
|
* @returns An array containing the integer step size and the integer offset of the nth rule.
|
|
* @example nthCheck.parse("2n+3"); // returns [2, 3]
|
|
*/
|
|
function parse$2(formula) {
|
|
formula = formula.trim().toLowerCase();
|
|
switch (formula) {
|
|
case "even": {
|
|
return [2, 0];
|
|
}
|
|
case "odd": {
|
|
return [2, 1];
|
|
}
|
|
}
|
|
// Parse [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]?
|
|
let index = 0;
|
|
let a = 0;
|
|
let sign = readSign();
|
|
let number = readNumber();
|
|
if (index < formula.length && formula.charAt(index) === "n") {
|
|
index++;
|
|
a = sign * (number ?? 1);
|
|
skipWhitespace();
|
|
if (index < formula.length) {
|
|
sign = readSign();
|
|
skipWhitespace();
|
|
number = readNumber();
|
|
} else {
|
|
sign = number = 0;
|
|
}
|
|
}
|
|
// Throw if there is anything else
|
|
if (number === null || index < formula.length) {
|
|
throw new Error(`n-th rule couldn't be parsed ('${formula}')`);
|
|
}
|
|
return [a, sign * number];
|
|
function readSign() {
|
|
switch (formula.charAt(index)) {
|
|
case "-": {
|
|
index++;
|
|
return -1;
|
|
}
|
|
case "+": {
|
|
index++;
|
|
break;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
function readNumber() {
|
|
const start = index;
|
|
let value = 0;
|
|
while (
|
|
index < formula.length &&
|
|
formula.charCodeAt(index) >= ZERO &&
|
|
formula.charCodeAt(index) <= NINE
|
|
) {
|
|
value = value * 10 + (formula.charCodeAt(index) - ZERO);
|
|
index++;
|
|
}
|
|
// Return `null` if we didn't read anything.
|
|
return index === start ? null : value;
|
|
}
|
|
function skipWhitespace() {
|
|
while (
|
|
index < formula.length &&
|
|
whitespace.has(formula.charCodeAt(index))
|
|
) {
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses and compiles a formula to a highly optimized function.
|
|
* Combination of {@link parse} and {@link compile}.
|
|
*
|
|
* If the formula doesn't match any elements,
|
|
* it returns [`boolbase`](https://github.com/fb55/boolbase)'s `falseFunc`.
|
|
* Otherwise, a function accepting an _index_ is returned, which returns
|
|
* whether or not the passed _index_ matches the formula.
|
|
*
|
|
* Note: The nth-rule starts counting at `1`, the returned function at `0`.
|
|
* @param formula The formula to compile.
|
|
* @example
|
|
* const check = nthCheck("2n+3");
|
|
*
|
|
* check(0); // `false`
|
|
* check(1); // `false`
|
|
* check(2); // `true`
|
|
* check(3); // `false`
|
|
* check(4); // `true`
|
|
* check(5); // `false`
|
|
* check(6); // `true`
|
|
*/
|
|
function nthCheck(formula) {
|
|
return compile$1(parse$2(formula));
|
|
}
|
|
|
|
/**
|
|
* Some selectors such as `:contains` and (non-relative) `:has` will only be
|
|
* able to match elements if their parents match the selector (as they contain
|
|
* a subset of the elements that the parent contains).
|
|
*
|
|
* This function wraps the given `matches` function in a function that caches
|
|
* the results of the parent elements, so that the `matches` function only
|
|
* needs to be called once for each subtree.
|
|
* @param next Matcher to run after this matcher succeeds.
|
|
* @param options Configuration object for cache behavior.
|
|
* @param options.adapter Adapter implementation used for DOM access.
|
|
* @param options.cacheResults Whether results should be memoized by input root.
|
|
* @param matches Compiled matcher function to wrap with caching.
|
|
*/
|
|
function cacheParentResults(next, { adapter, cacheResults }, matches) {
|
|
if (cacheResults === false || typeof WeakMap === "undefined") {
|
|
return (element) => next(element) && matches(element);
|
|
}
|
|
// Use a cache to avoid re-checking children of an element.
|
|
// @ts-expect-error `Node` is not extending object
|
|
const resultCache = new WeakMap();
|
|
function addResultToCache(element) {
|
|
const result = matches(element);
|
|
resultCache.set(element, result);
|
|
return result;
|
|
}
|
|
return function cachedMatcher(element) {
|
|
if (!next(element)) {
|
|
return false;
|
|
}
|
|
if (resultCache.has(element)) {
|
|
return resultCache.get(element) ?? false;
|
|
}
|
|
// Check all of the element's parents.
|
|
let node = element;
|
|
do {
|
|
const parent = getElementParent(node, adapter);
|
|
if (parent === null) {
|
|
return addResultToCache(element);
|
|
}
|
|
node = parent;
|
|
} while (!resultCache.has(node));
|
|
return resultCache.get(node) ? addResultToCache(element) : false;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a copy of options, omitting `context` and `rootFunc`.
|
|
*
|
|
* This is used when compiling nested selectors (e.g. inside `:is`, `:not`,
|
|
* `:nth-child(… of S)`) so that the parent compilation state doesn't leak.
|
|
*/
|
|
function copyOptions(options) {
|
|
// Omit context and rootFunc so parent compilation state doesn't leak.
|
|
const { context: _, rootFunc: __, ...copied } = options;
|
|
return copied;
|
|
}
|
|
|
|
/**
|
|
* RFC 4647 extended filtering with pre-split subtags.
|
|
* @param tag - Lowercased subtags of the element's language value.
|
|
* @param range - Lowercased subtags of the language range to match against.
|
|
*/
|
|
function extendedFilter(tag, range) {
|
|
if (range[0] !== "*" && range[0] !== tag[0]) return false;
|
|
let tagIndex = 1;
|
|
for (let rangeIndex = 1; rangeIndex < range.length; rangeIndex++) {
|
|
if (range[rangeIndex] === "*") continue;
|
|
// Skip non-singleton tag subtags until we find a match.
|
|
while (tagIndex < tag.length && tag[tagIndex] !== range[rangeIndex]) {
|
|
if (tag[tagIndex++].length <= 1) return false;
|
|
}
|
|
if (tagIndex >= tag.length) return false;
|
|
tagIndex++;
|
|
}
|
|
return true;
|
|
}
|
|
/** @see {@link https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo} */
|
|
const nthOfRegex = /^(.+?)\s+of\s+(.+)$/is;
|
|
function compileNth(reverse, ofType) {
|
|
return function nth(next, rule, options, context, compileToken) {
|
|
const { adapter, equals } = options;
|
|
const ofMatch = ofType ? null : rule.match(nthOfRegex);
|
|
const nthCheck$1 = nthCheck(ofMatch ? ofMatch[1].trim() : rule);
|
|
if (nthCheck$1 === falseFunc) return falseFunc;
|
|
const ofSelector =
|
|
ofMatch && compileToken
|
|
? compileToken(
|
|
parse$3(ofMatch[2].trim()),
|
|
copyOptions(options),
|
|
context
|
|
)
|
|
: undefined;
|
|
if (ofSelector === falseFunc) return falseFunc;
|
|
if (nthCheck$1 === trueFunc && !ofSelector) {
|
|
return (element) =>
|
|
getElementParent(element, adapter) !== null && next(element);
|
|
}
|
|
const shouldCount = ofSelector
|
|
? (_element, sibling) => ofSelector(sibling)
|
|
: ofType
|
|
? (element, sibling) =>
|
|
adapter.getName(sibling) === adapter.getName(element)
|
|
: trueFunc;
|
|
if (reverse) {
|
|
return function nthLast(element) {
|
|
if (ofSelector && !ofSelector(element)) return false;
|
|
const siblings = adapter.getSiblings(element);
|
|
let pos = 0;
|
|
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
const sibling = siblings[index];
|
|
if (equals(element, sibling)) break;
|
|
if (adapter.isTag(sibling) && shouldCount(element, sibling)) pos++;
|
|
}
|
|
return nthCheck$1(pos) && next(element);
|
|
};
|
|
}
|
|
return function nth(element) {
|
|
if (ofSelector && !ofSelector(element)) return false;
|
|
const siblings = adapter.getSiblings(element);
|
|
let pos = 0;
|
|
for (const sibling of siblings) {
|
|
if (equals(element, sibling)) break;
|
|
if (adapter.isTag(sibling) && shouldCount(element, sibling)) pos++;
|
|
}
|
|
return nthCheck$1(pos) && next(element);
|
|
};
|
|
};
|
|
}
|
|
/**
|
|
* Pre-compiled pseudo filters.
|
|
*/
|
|
const filters = {
|
|
contains(next, text, options) {
|
|
const { getText } = options.adapter;
|
|
return cacheParentResults(next, options, (element) =>
|
|
getText(element).includes(text)
|
|
);
|
|
},
|
|
icontains(next, text, options) {
|
|
const itext = text.toLowerCase();
|
|
const { getText } = options.adapter;
|
|
return cacheParentResults(next, options, (element) =>
|
|
getText(element).toLowerCase().includes(itext)
|
|
);
|
|
},
|
|
// Location specific methods
|
|
"nth-child": compileNth(false, false),
|
|
"nth-last-child": compileNth(true, false),
|
|
"nth-of-type": compileNth(false, true),
|
|
"nth-last-of-type": compileNth(true, true),
|
|
// TODO determine the actual root element
|
|
root(next, _rule, { adapter }) {
|
|
return (element) =>
|
|
getElementParent(element, adapter) === null && next(element);
|
|
},
|
|
scope(next, rule, options, context) {
|
|
const { equals } = options;
|
|
if (!context || context.length === 0) {
|
|
// Equivalent to :root
|
|
return filters["root"](next, rule, options);
|
|
}
|
|
if (context.length === 1) {
|
|
// NOTE: can't be unpacked, as :has uses this for side-effects
|
|
return (element) => equals(context[0], element) && next(element);
|
|
}
|
|
return (element) => context.includes(element) && next(element);
|
|
},
|
|
lang(next, code, { adapter }) {
|
|
const ranges = code
|
|
.split(",")
|
|
.map((r) => r.trim())
|
|
.filter((r) => r.length > 0)
|
|
.map((r) =>
|
|
r
|
|
.replace(/^['"]|['"]$/g, "")
|
|
.toLowerCase()
|
|
.split("-")
|
|
);
|
|
return function lang(element) {
|
|
let node = element;
|
|
while (node != null) {
|
|
const value =
|
|
adapter.getAttributeValue(node, "xml:lang") ??
|
|
adapter.getAttributeValue(node, "lang");
|
|
if (value != null) {
|
|
if (!value) {
|
|
return ranges.some((r) => r[0] === "") && next(element);
|
|
}
|
|
const tag = value.toLowerCase().split("-");
|
|
return ranges.some((r) => extendedFilter(tag, r)) && next(element);
|
|
}
|
|
const parent = adapter.getParent(node);
|
|
node = parent != null && adapter.isTag(parent) ? parent : null;
|
|
}
|
|
return ranges.some((r) => r[0] === "") && next(element);
|
|
};
|
|
},
|
|
hover: dynamicStatePseudo("isHovered"),
|
|
visited: dynamicStatePseudo("isVisited"),
|
|
active: dynamicStatePseudo("isActive")
|
|
};
|
|
/**
|
|
* Dynamic state pseudos. These depend on optional Adapter methods.
|
|
* @param name The name of the adapter method to call.
|
|
* @returns Pseudo for the `filters` object.
|
|
*/
|
|
function dynamicStatePseudo(name) {
|
|
return function dynamicPseudo(next, _rule, { adapter }) {
|
|
const filterFunction = adapter[name];
|
|
if (typeof filterFunction !== "function") {
|
|
return falseFunc;
|
|
}
|
|
return function active(element) {
|
|
return filterFunction(element) && next(element);
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* CSS limits the characters considered as whitespace to space, tab & line
|
|
* feed. We add carriage returns as htmlparser2 doesn't normalize them to
|
|
* line feeds.
|
|
* @see {@link https://www.w3.org/TR/css-text-3/#white-space}
|
|
*/
|
|
const isDocumentWhiteSpace = /^[ \t\r\n]*$/;
|
|
// While filters are precompiled, pseudos get called when they are needed
|
|
/** Runtime pseudo selector implementations. */
|
|
const pseudos = {
|
|
empty(element, { adapter }) {
|
|
const children = adapter.getChildren(element);
|
|
return (
|
|
// First, make sure the tag does not have any element children.
|
|
children.every((element) => !adapter.isTag(element)) &&
|
|
// Then, check that the text content is only whitespace.
|
|
children.every((element) =>
|
|
// FIXME: `getText` call is potentially expensive.
|
|
isDocumentWhiteSpace.test(adapter.getText(element))
|
|
)
|
|
);
|
|
},
|
|
"first-child"(element, { adapter, equals }) {
|
|
if (adapter.prevElementSibling) {
|
|
return adapter.prevElementSibling(element) == null;
|
|
}
|
|
const firstChild = adapter
|
|
.getSiblings(element)
|
|
.find((sibling) => adapter.isTag(sibling));
|
|
return firstChild != null && equals(element, firstChild);
|
|
},
|
|
"last-child"(element, { adapter, equals }) {
|
|
const siblings = adapter.getSiblings(element);
|
|
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
if (equals(element, siblings[index])) {
|
|
return true;
|
|
}
|
|
if (adapter.isTag(siblings[index])) {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
"first-of-type"(element, { adapter, equals }) {
|
|
const siblings = adapter.getSiblings(element);
|
|
const elementName = adapter.getName(element);
|
|
for (const currentSibling of siblings) {
|
|
if (equals(element, currentSibling)) {
|
|
return true;
|
|
}
|
|
if (
|
|
adapter.isTag(currentSibling) &&
|
|
adapter.getName(currentSibling) === elementName
|
|
) {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
"last-of-type"(element, { adapter, equals }) {
|
|
const siblings = adapter.getSiblings(element);
|
|
const elementName = adapter.getName(element);
|
|
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
const currentSibling = siblings[index];
|
|
if (equals(element, currentSibling)) {
|
|
return true;
|
|
}
|
|
if (
|
|
adapter.isTag(currentSibling) &&
|
|
adapter.getName(currentSibling) === elementName
|
|
) {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
"only-of-type"(element, { adapter, equals }) {
|
|
const elementName = adapter.getName(element);
|
|
return adapter
|
|
.getSiblings(element)
|
|
.every(
|
|
(sibling) =>
|
|
equals(element, sibling) ||
|
|
!adapter.isTag(sibling) ||
|
|
adapter.getName(sibling) !== elementName
|
|
);
|
|
},
|
|
"only-child"(element, { adapter, equals }) {
|
|
return adapter
|
|
.getSiblings(element)
|
|
.every((sibling) => equals(element, sibling) || !adapter.isTag(sibling));
|
|
}
|
|
};
|
|
/**
|
|
* Validate pseudo selector argument arity.
|
|
* @param pseudoClassCondition Pseudo-function implementation to wrap.
|
|
* @param name Name of the pseudo selector.
|
|
* @param subselect Subselector passed to the pseudo-function.
|
|
* @param argumentIndex Index of the argument parser to apply.
|
|
*/
|
|
function verifyPseudoArguments(
|
|
pseudoClassCondition,
|
|
name,
|
|
subselect,
|
|
argumentIndex
|
|
) {
|
|
if (subselect === null) {
|
|
if (pseudoClassCondition.length > argumentIndex) {
|
|
throw new Error(`Pseudo-class :${name} requires an argument`);
|
|
}
|
|
} else if (pseudoClassCondition.length === argumentIndex) {
|
|
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check whether a selector token performs traversal.
|
|
* @param token Selector token(s) to compile.
|
|
*/
|
|
function isTraversal(token) {
|
|
return token.type === "_flexibleDescendant" || isTraversal$1(token);
|
|
}
|
|
/**
|
|
* Sort the parts of the passed selector, as there is potential for
|
|
* optimization (some types of selectors are faster than others).
|
|
* @param array Selector to sort
|
|
*/
|
|
function sortRules(array) {
|
|
const ratings = array.map(getQuality);
|
|
for (let index = 1; index < array.length; index++) {
|
|
const procNew = ratings[index];
|
|
if (procNew < 0) {
|
|
continue;
|
|
}
|
|
// Use insertion sort to move the token to the correct position.
|
|
for (
|
|
let currentIndex = index;
|
|
currentIndex > 0 && procNew < ratings[currentIndex - 1];
|
|
currentIndex--
|
|
) {
|
|
const token = array[currentIndex];
|
|
array[currentIndex] = array[currentIndex - 1];
|
|
array[currentIndex - 1] = token;
|
|
ratings[currentIndex] = ratings[currentIndex - 1];
|
|
ratings[currentIndex - 1] = procNew;
|
|
}
|
|
}
|
|
}
|
|
function getAttributeQuality(token) {
|
|
switch (token.action) {
|
|
case AttributeAction.Exists: {
|
|
return 10;
|
|
}
|
|
case AttributeAction.Equals: {
|
|
// Prefer ID selectors (eg. #ID)
|
|
return token.name === "id" ? 9 : 8;
|
|
}
|
|
case AttributeAction.Not: {
|
|
return 7;
|
|
}
|
|
case AttributeAction.Start: {
|
|
return 6;
|
|
}
|
|
case AttributeAction.End: {
|
|
return 6;
|
|
}
|
|
case AttributeAction.Any: {
|
|
return 5;
|
|
}
|
|
case AttributeAction.Hyphen: {
|
|
return 4;
|
|
}
|
|
case AttributeAction.Element: {
|
|
return 3;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Determine the quality of the passed token. The higher the number, the
|
|
* faster the token is to execute.
|
|
* @param token Token to get the quality of.
|
|
* @returns The token's quality.
|
|
*/
|
|
function getQuality(token) {
|
|
switch (token.type) {
|
|
case SelectorType.Universal: {
|
|
return 50;
|
|
}
|
|
case SelectorType.Tag: {
|
|
return 30;
|
|
}
|
|
case SelectorType.Attribute: {
|
|
return Math.floor(
|
|
getAttributeQuality(token) /
|
|
// `ignoreCase` adds some overhead, half the result if applicable.
|
|
(token.ignoreCase ? 2 : 1)
|
|
);
|
|
}
|
|
case SelectorType.Pseudo: {
|
|
return token.data
|
|
? token.name === "has" ||
|
|
token.name === "contains" ||
|
|
token.name === "icontains"
|
|
? // Expensive in any case — run as late as possible.
|
|
0
|
|
: Array.isArray(token.data)
|
|
? // Eg. `:is`, `:not`
|
|
Math.max(
|
|
// If we have traversals, try to avoid executing this selector
|
|
0,
|
|
Math.min(
|
|
...token.data.map((d) => Math.min(...d.map(getQuality)))
|
|
)
|
|
)
|
|
: 2
|
|
: 3;
|
|
}
|
|
default: {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Check whether a token or nested token includes `:scope`.
|
|
* @param t Selector token under inspection.
|
|
*/
|
|
function includesScopePseudo(t) {
|
|
return (
|
|
t.type === SelectorType.Pseudo &&
|
|
(t.name === "scope" ||
|
|
(Array.isArray(t.data) &&
|
|
t.data.some((data) => data.some(includesScopePseudo))))
|
|
);
|
|
}
|
|
|
|
/** Used as a placeholder for :has. Will be replaced with the actual element. */
|
|
const PLACEHOLDER_ELEMENT = {};
|
|
/**
|
|
* Check if the selector has any properties that rely on the current element.
|
|
* If not, we can cache the result of the selector.
|
|
*
|
|
* We can't cache selectors that start with a traversal (e.g. `>`, `+`, `~`),
|
|
* or include a `:scope`.
|
|
* @param selector - The selector to check.
|
|
* @returns Whether the selector has any properties that rely on the current element.
|
|
*/
|
|
function hasDependsOnCurrentElement(selector) {
|
|
return selector.some(
|
|
(sel) =>
|
|
sel.length > 0 && (isTraversal(sel[0]) || sel.some(includesScopePseudo))
|
|
);
|
|
}
|
|
const is$1 = (next, token, options, context, compileToken) => {
|
|
const compiledToken = compileToken(token, copyOptions(options), context);
|
|
return compiledToken === trueFunc
|
|
? next
|
|
: compiledToken === falseFunc
|
|
? falseFunc
|
|
: (element) => compiledToken(element) && next(element);
|
|
};
|
|
/*
|
|
* :not, :has, :is, :matches and :where have to compile selectors
|
|
* doing this in src/pseudos.ts would lead to circular dependencies,
|
|
* so we add them here
|
|
*/
|
|
/** Pseudo selectors that compile nested selectors. */
|
|
const subselects = {
|
|
is: is$1,
|
|
/**
|
|
* `:matches` and `:where` are aliases for `:is`.
|
|
*/
|
|
matches: is$1,
|
|
where: is$1,
|
|
not(next, token, options, context, compileToken) {
|
|
const compiledToken = compileToken(token, copyOptions(options), context);
|
|
return compiledToken === falseFunc
|
|
? next
|
|
: compiledToken === trueFunc
|
|
? falseFunc
|
|
: (element) => !compiledToken(element) && next(element);
|
|
},
|
|
has(next, subselect, options, _context, compileToken) {
|
|
const { adapter } = options;
|
|
const copiedOptions = copyOptions(options);
|
|
copiedOptions.relativeSelector = true;
|
|
const context = subselect.some((s) => s.some(isTraversal))
|
|
? // Used as a placeholder. Will be replaced with the actual element.
|
|
[PLACEHOLDER_ELEMENT]
|
|
: undefined;
|
|
const skipCache = hasDependsOnCurrentElement(subselect);
|
|
const compiled = compileToken(subselect, copiedOptions, context);
|
|
if (compiled === falseFunc) {
|
|
return falseFunc;
|
|
}
|
|
// If `compiled` is `trueFunc`, we can skip this.
|
|
if (context && compiled !== trueFunc) {
|
|
return skipCache
|
|
? (element) => {
|
|
if (!next(element)) {
|
|
return false;
|
|
}
|
|
context[0] = element;
|
|
const childs = adapter.getChildren(element);
|
|
return (
|
|
findOne$1(
|
|
compiled,
|
|
compiled.shouldTestNextSiblings
|
|
? [...childs, ...getNextSiblings(element, adapter)]
|
|
: childs,
|
|
options
|
|
) !== null
|
|
);
|
|
}
|
|
: cacheParentResults(next, options, (element) => {
|
|
context[0] = element;
|
|
return (
|
|
findOne$1(compiled, adapter.getChildren(element), options) !==
|
|
null
|
|
);
|
|
});
|
|
}
|
|
const hasOne = (element) =>
|
|
findOne$1(compiled, adapter.getChildren(element), options) !== null;
|
|
return skipCache
|
|
? (element) => next(element) && hasOne(element)
|
|
: cacheParentResults(next, options, hasOne);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Pseudo selectors
|
|
*
|
|
* Pseudo selectors are available in three forms:
|
|
*
|
|
* 1. Filters are called when the selector is compiled and return a function
|
|
* that has to return either false, or the results of `next()`.
|
|
* 2. Pseudos are called on execution. They have to return a boolean.
|
|
* 3. Subselects work like filters, but have an embedded selector that will be run separately.
|
|
*
|
|
* Filters are great if you want to do some pre-processing, or change the call order
|
|
* of `next()` and your code.
|
|
* Pseudos should be used to implement simple checks.
|
|
*/
|
|
/**
|
|
* Compile a pseudo selector into an executable query function.
|
|
* @param next Matcher to run after this matcher succeeds.
|
|
* @param selector Selector used to match elements.
|
|
* @param options Options that control this operation.
|
|
* @param context Context nodes used to scope selector matching.
|
|
* @param compileToken Function used to compile nested selector tokens.
|
|
*/
|
|
function compilePseudoSelector(next, selector, options, context, compileToken) {
|
|
const { name, data } = selector;
|
|
if (Array.isArray(data)) {
|
|
if (!(name in subselects)) {
|
|
throw new Error(`Unknown pseudo-class :${name}(${data})`);
|
|
}
|
|
return subselects[name](next, data, options, context, compileToken);
|
|
}
|
|
const userPseudo = options.pseudos?.[name];
|
|
const stringPseudo =
|
|
typeof userPseudo === "string" ? userPseudo : aliases[name];
|
|
if (typeof stringPseudo === "string") {
|
|
if (data != null) {
|
|
throw new Error(`Pseudo ${name} doesn't have any arguments`);
|
|
}
|
|
// The alias has to be parsed here, to make sure options are respected.
|
|
const alias = parse$3(stringPseudo);
|
|
return subselects["is"](next, alias, options, context, compileToken);
|
|
}
|
|
if (typeof userPseudo === "function") {
|
|
verifyPseudoArguments(userPseudo, name, data, 1);
|
|
return (element) => userPseudo(element, data) && next(element);
|
|
}
|
|
if (name in filters) {
|
|
return filters[name](next, data, options, context, compileToken);
|
|
}
|
|
if (name in pseudos) {
|
|
const pseudo = pseudos[name];
|
|
verifyPseudoArguments(pseudo, name, data, 2);
|
|
return (element) => pseudo(element, options, data) && next(element);
|
|
}
|
|
throw new Error(`Unknown pseudo-class :${name}`);
|
|
}
|
|
|
|
/*
|
|
* All available rules
|
|
*/
|
|
/**
|
|
* Compile a single selector token.
|
|
* @param next Matcher to run after this matcher succeeds.
|
|
* @param selector Selector used to match elements.
|
|
* @param options Options that control this operation.
|
|
* @param context Context nodes used to scope selector matching.
|
|
* @param compileToken Function used to compile nested selector tokens.
|
|
* @param hasExpensiveSubselector Whether the selector contains expensive subselectors.
|
|
*/
|
|
function compileGeneralSelector(
|
|
next,
|
|
selector,
|
|
options,
|
|
context,
|
|
compileToken,
|
|
hasExpensiveSubselector
|
|
) {
|
|
const { adapter, equals, cacheResults } = options;
|
|
switch (selector.type) {
|
|
case SelectorType.PseudoElement: {
|
|
throw new Error("Pseudo-elements are not supported by css-select");
|
|
}
|
|
case SelectorType.ColumnCombinator: {
|
|
throw new Error("Column combinators are not yet supported by css-select");
|
|
}
|
|
case SelectorType.Attribute: {
|
|
if (selector.namespace != null) {
|
|
throw new Error(
|
|
"Namespaced attributes are not yet supported by css-select"
|
|
);
|
|
}
|
|
if (!options.xmlMode || options.lowerCaseAttributeNames) {
|
|
selector.name = selector.name.toLowerCase();
|
|
}
|
|
return attributeRules[selector.action](next, selector, options);
|
|
}
|
|
case SelectorType.Pseudo: {
|
|
return compilePseudoSelector(
|
|
next,
|
|
selector,
|
|
options,
|
|
context,
|
|
compileToken
|
|
);
|
|
}
|
|
// Tags
|
|
case SelectorType.Tag: {
|
|
if (selector.namespace != null) {
|
|
throw new Error(
|
|
"Namespaced tag names are not yet supported by css-select"
|
|
);
|
|
}
|
|
let { name } = selector;
|
|
if (!options.xmlMode || options.lowerCaseTags) {
|
|
name = name.toLowerCase();
|
|
}
|
|
return function tag(element) {
|
|
return adapter.getName(element) === name && next(element);
|
|
};
|
|
}
|
|
// Traversal
|
|
case SelectorType.Descendant: {
|
|
if (
|
|
!hasExpensiveSubselector ||
|
|
cacheResults === false ||
|
|
typeof WeakMap === "undefined"
|
|
) {
|
|
return function descendant(element) {
|
|
let current = element;
|
|
while ((current = getElementParent(current, adapter))) {
|
|
if (next(current)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
const resultCache = new WeakMap();
|
|
return function cachedDescendant(element) {
|
|
let current = element;
|
|
let result;
|
|
while ((current = getElementParent(current, adapter))) {
|
|
const cached = resultCache.get(current);
|
|
if (cached === undefined) {
|
|
result ??= { matches: false };
|
|
result.matches = next(current);
|
|
resultCache.set(current, result);
|
|
if (result.matches) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (result) {
|
|
result.matches = cached.matches;
|
|
}
|
|
return cached.matches;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
case "_flexibleDescendant": {
|
|
// Include element itself, only used while querying an array
|
|
return function flexibleDescendant(element) {
|
|
let current = element;
|
|
do {
|
|
if (next(current)) {
|
|
return true;
|
|
}
|
|
current = getElementParent(current, adapter);
|
|
} while (current);
|
|
return false;
|
|
};
|
|
}
|
|
case SelectorType.Parent: {
|
|
return function parent(element) {
|
|
return adapter
|
|
.getChildren(element)
|
|
.some((element) => adapter.isTag(element) && next(element));
|
|
};
|
|
}
|
|
case SelectorType.Child: {
|
|
return function child(element) {
|
|
const parent = getElementParent(element, adapter);
|
|
return parent !== null && next(parent);
|
|
};
|
|
}
|
|
case SelectorType.Sibling: {
|
|
return function sibling(element) {
|
|
const siblings = adapter.getSiblings(element);
|
|
for (const currentSibling of siblings) {
|
|
if (equals(element, currentSibling)) {
|
|
break;
|
|
}
|
|
if (adapter.isTag(currentSibling) && next(currentSibling)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
case SelectorType.Adjacent: {
|
|
if (adapter.prevElementSibling) {
|
|
return function adjacent(element) {
|
|
// biome-ignore lint/style/noNonNullAssertion: checked by if statement
|
|
const previous = adapter.prevElementSibling(element);
|
|
return previous != null && next(previous);
|
|
};
|
|
}
|
|
return function adjacent(element) {
|
|
const siblings = adapter.getSiblings(element);
|
|
let lastElement;
|
|
for (const currentSibling of siblings) {
|
|
if (equals(element, currentSibling)) {
|
|
break;
|
|
}
|
|
if (adapter.isTag(currentSibling)) {
|
|
lastElement = currentSibling;
|
|
}
|
|
}
|
|
return !!lastElement && next(lastElement);
|
|
};
|
|
}
|
|
case SelectorType.Universal: {
|
|
if (selector.namespace != null && selector.namespace !== "*") {
|
|
throw new Error(
|
|
"Namespaced universal selectors are not yet supported by css-select"
|
|
);
|
|
}
|
|
return next;
|
|
}
|
|
}
|
|
}
|
|
|
|
const DESCENDANT_TOKEN = { type: SelectorType.Descendant };
|
|
const FLEXIBLE_DESCENDANT_TOKEN = {
|
|
type: "_flexibleDescendant"
|
|
};
|
|
const SCOPE_TOKEN = {
|
|
type: SelectorType.Pseudo,
|
|
name: "scope",
|
|
data: null
|
|
};
|
|
/*
|
|
* CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector
|
|
* http://www.w3.org/TR/selectors4/#absolutizing
|
|
*/
|
|
function absolutize(token, { adapter }, context) {
|
|
// TODO Use better check if the context is a document
|
|
const hasContext = !!context?.every(
|
|
(element) =>
|
|
element === PLACEHOLDER_ELEMENT ||
|
|
(adapter.isTag(element) && getElementParent(element, adapter) !== null)
|
|
);
|
|
for (const t of token) {
|
|
if (
|
|
t.length > 0 &&
|
|
isTraversal(t[0]) &&
|
|
t[0].type !== SelectorType.Descendant
|
|
);
|
|
else if (hasContext && !t.some(includesScopePseudo)) {
|
|
t.unshift(DESCENDANT_TOKEN);
|
|
} else {
|
|
continue;
|
|
}
|
|
t.unshift(SCOPE_TOKEN);
|
|
}
|
|
}
|
|
/**
|
|
* Compile a parsed selector token into an executable query function.
|
|
* @param token Selector token(s) to compile.
|
|
* @param options Options that control this operation.
|
|
* @param compilationContext Compilation context for relative selector handling.
|
|
*/
|
|
function compileToken(token, options, compilationContext) {
|
|
for (const rules of token) {
|
|
sortRules(rules);
|
|
}
|
|
const { context = compilationContext, rootFunc: rootFunction = trueFunc } =
|
|
options;
|
|
const isArrayContext = Array.isArray(context);
|
|
const finalContext =
|
|
context && (Array.isArray(context) ? context : [context]);
|
|
// Check if the selector is relative
|
|
if (options.relativeSelector !== false) {
|
|
absolutize(token, options, finalContext);
|
|
} else if (token.some((t) => t.length > 0 && isTraversal(t[0]))) {
|
|
throw new Error(
|
|
"Relative selectors are not allowed when the `relativeSelector` option is disabled"
|
|
);
|
|
}
|
|
let shouldTestNextSiblings = false;
|
|
let query = falseFunc;
|
|
combineLoop: for (const rules of token) {
|
|
if (rules.length >= 2) {
|
|
const [first, second] = rules;
|
|
if (first.type !== SelectorType.Pseudo || first.name !== "scope");
|
|
else if (isArrayContext && second.type === SelectorType.Descendant) {
|
|
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
|
|
} else if (
|
|
second.type === SelectorType.Adjacent ||
|
|
second.type === SelectorType.Sibling
|
|
) {
|
|
shouldTestNextSiblings = true;
|
|
}
|
|
}
|
|
let next = rootFunction;
|
|
let hasExpensiveSubselector = false;
|
|
for (const rule of rules) {
|
|
next = compileGeneralSelector(
|
|
next,
|
|
rule,
|
|
options,
|
|
finalContext,
|
|
compileToken,
|
|
hasExpensiveSubselector
|
|
);
|
|
const quality = getQuality(rule);
|
|
if (quality === 0) {
|
|
hasExpensiveSubselector = true;
|
|
}
|
|
// If the sub-selector won't match any elements, skip it.
|
|
if (next === falseFunc) {
|
|
continue combineLoop;
|
|
}
|
|
}
|
|
// If we have a function that always returns true, we can stop here.
|
|
if (next === rootFunction) {
|
|
return rootFunction;
|
|
}
|
|
query = query === falseFunc ? next : or(query, next);
|
|
}
|
|
query.shouldTestNextSiblings = shouldTestNextSiblings;
|
|
return query;
|
|
}
|
|
function or(a, b) {
|
|
return (element) => a(element) || b(element);
|
|
}
|
|
|
|
const defaultEquals = (a, b) => a === b;
|
|
const defaultOptions = {
|
|
adapter: { ...DomUtils, isTag: isTag$1 },
|
|
equals: defaultEquals
|
|
};
|
|
function convertOptionFormats(options) {
|
|
/*
|
|
* We force one format of options to the other one.
|
|
*/
|
|
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
|
|
const finalOptions = options ?? defaultOptions;
|
|
// @ts-expect-error Same as above.
|
|
finalOptions.adapter ??= defaultOptions.adapter;
|
|
// @ts-expect-error `equals` does not exist on `Options`
|
|
finalOptions.equals ??= finalOptions.adapter?.equals ?? defaultEquals;
|
|
return finalOptions;
|
|
}
|
|
/**
|
|
* Compiles a selector to an executable function.
|
|
*
|
|
* The returned function checks if each passed node is an element. Use
|
|
* `_compileUnsafe` to skip this check.
|
|
* @param selector Selector to compile.
|
|
* @param options Compilation options.
|
|
* @param context Optional context for the selector.
|
|
*/
|
|
function compile(selector, options, context) {
|
|
const convertedOptions = convertOptionFormats(options);
|
|
const next = _compileUnsafe(selector, convertedOptions, context);
|
|
return next === falseFunc
|
|
? falseFunc
|
|
: (element) => convertedOptions.adapter.isTag(element) && next(element);
|
|
}
|
|
/**
|
|
* Like `compile`, but does not add a check if elements are tags.
|
|
* @param selector Selector used to match elements.
|
|
* @param options Options that control this operation.
|
|
* @param context Context nodes used to scope selector matching.
|
|
*/
|
|
function _compileUnsafe(selector, options, context) {
|
|
return compileToken(
|
|
typeof selector === "string" ? parse$3(selector) : selector,
|
|
convertOptionFormats(options),
|
|
context
|
|
);
|
|
}
|
|
/**
|
|
* Tests whether or not an element is matched by query.
|
|
* @template Node The generic Node type for the DOM adapter being used.
|
|
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
|
* @param element The element to test if it matches the query.
|
|
* @param query can be either a CSS selector string or a compiled query function.
|
|
* @param [options] options for querying the document.
|
|
* @see compile for supported selector queries.
|
|
* @returns Whether the element matches the query.
|
|
*/
|
|
function is(element, query, options) {
|
|
return (typeof query === "function" ? query : compile(query, options))(
|
|
element
|
|
);
|
|
}
|
|
|
|
const { isArray } = Array;
|
|
|
|
/* c8 ignore start */
|
|
const isTag = ({ nodeType }) => nodeType === ELEMENT_NODE;
|
|
|
|
const existsOne = (test, elements) =>
|
|
elements.some(
|
|
(element) =>
|
|
isTag(element) && (test(element) || existsOne(test, getChildren(element)))
|
|
);
|
|
|
|
const getAttributeValue = (element, name) =>
|
|
name === "class" ? element.classList.value : element.getAttribute(name);
|
|
|
|
const getChildren = ({ childNodes }) => childNodes;
|
|
|
|
const getName = (element) => {
|
|
const { localName } = element;
|
|
return ignoreCase(element) ? localName.toLowerCase() : localName;
|
|
};
|
|
|
|
const getParent = ({ parentNode }) => parentNode;
|
|
|
|
const getSiblings = (element) => {
|
|
const { parentNode } = element;
|
|
return parentNode ? getChildren(parentNode) : element;
|
|
};
|
|
|
|
const getText = (node) => {
|
|
if (isArray(node)) return node.map(getText).join("");
|
|
if (isTag(node)) return getText(getChildren(node));
|
|
if (node.nodeType === TEXT_NODE) return node.data;
|
|
return "";
|
|
};
|
|
|
|
const hasAttrib = (element, name) => element.hasAttribute(name);
|
|
|
|
const removeSubsets = (nodes) => {
|
|
let { length } = nodes;
|
|
while (length--) {
|
|
const node = nodes[length];
|
|
if (length && -1 < nodes.lastIndexOf(node, length - 1)) {
|
|
nodes.splice(length, 1);
|
|
continue;
|
|
}
|
|
for (
|
|
let { parentNode } = node;
|
|
parentNode;
|
|
parentNode = parentNode.parentNode
|
|
) {
|
|
if (nodes.includes(parentNode)) {
|
|
nodes.splice(length, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nodes;
|
|
};
|
|
|
|
const findAll = (test, nodes) => {
|
|
const matches = [];
|
|
for (const node of nodes) {
|
|
if (isTag(node)) {
|
|
if (test(node)) matches.push(node);
|
|
matches.push(...findAll(test, getChildren(node)));
|
|
}
|
|
}
|
|
return matches;
|
|
};
|
|
|
|
const findOne = (test, nodes) => {
|
|
for (let node of nodes)
|
|
if (test(node) || (node = findOne(test, getChildren(node)))) return node;
|
|
return null;
|
|
};
|
|
/* c8 ignore stop */
|
|
|
|
const adapter = {
|
|
isTag,
|
|
existsOne,
|
|
getAttributeValue,
|
|
getChildren,
|
|
getName,
|
|
getParent,
|
|
getSiblings,
|
|
getText,
|
|
hasAttrib,
|
|
removeSubsets,
|
|
findAll,
|
|
findOne
|
|
};
|
|
|
|
const prepareMatch = (element, selectors) =>
|
|
compile(selectors, {
|
|
context: selectors.includes(":scope") ? element : void 0,
|
|
xmlMode: !ignoreCase(element),
|
|
adapter
|
|
});
|
|
|
|
const matches = (element, selectors) =>
|
|
is(element, selectors, {
|
|
strict: true,
|
|
context: selectors.includes(":scope") ? element : void 0,
|
|
xmlMode: !ignoreCase(element),
|
|
adapter
|
|
});
|
|
|
|
/**
|
|
* @implements globalThis.Text
|
|
*/
|
|
let Text$1 = class Text extends CharacterData$1 {
|
|
constructor(ownerDocument, data = "") {
|
|
super(ownerDocument, "#text", TEXT_NODE, data);
|
|
}
|
|
|
|
get wholeText() {
|
|
const text = [];
|
|
let { previousSibling, nextSibling } = this;
|
|
while (previousSibling) {
|
|
if (previousSibling.nodeType === TEXT_NODE)
|
|
text.unshift(previousSibling[VALUE]);
|
|
else break;
|
|
previousSibling = previousSibling.previousSibling;
|
|
}
|
|
text.push(this[VALUE]);
|
|
while (nextSibling) {
|
|
if (nextSibling.nodeType === TEXT_NODE) text.push(nextSibling[VALUE]);
|
|
else break;
|
|
nextSibling = nextSibling.nextSibling;
|
|
}
|
|
return text.join("");
|
|
}
|
|
|
|
cloneNode() {
|
|
const { ownerDocument, [VALUE]: data } = this;
|
|
return new Text(ownerDocument, data);
|
|
}
|
|
|
|
toString() {
|
|
return escape(this[VALUE]);
|
|
}
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#interface-parentnode
|
|
// Document, DocumentFragment, Element
|
|
|
|
const isNode = (node) => node instanceof Node$1;
|
|
|
|
const insert = (parentNode, child, nodes) => {
|
|
const { ownerDocument } = parentNode;
|
|
for (const node of nodes)
|
|
parentNode.insertBefore(
|
|
isNode(node) ? node : new Text$1(ownerDocument, node),
|
|
child
|
|
);
|
|
};
|
|
|
|
/** @typedef { import('../interface/element.js').Element & {
|
|
[typeof NEXT]: NodeStruct,
|
|
[typeof PREV]: NodeStruct,
|
|
[typeof START]: NodeStruct,
|
|
nodeType: typeof ATTRIBUTE_NODE | typeof DOCUMENT_FRAGMENT_NODE | typeof ELEMENT_NODE | typeof TEXT_NODE | typeof NODE_END | typeof COMMENT_NODE | typeof CDATA_SECTION_NODE,
|
|
ownerDocument: Document,
|
|
parentNode: ParentNode,
|
|
}} NodeStruct */
|
|
|
|
class ParentNode extends Node$1 {
|
|
constructor(ownerDocument, localName, nodeType) {
|
|
super(ownerDocument, localName, nodeType);
|
|
this[PRIVATE] = null;
|
|
/** @type {NodeStruct} */
|
|
this[NEXT] = this[END] = {
|
|
[NEXT]: null,
|
|
[PREV]: this,
|
|
[START]: this,
|
|
nodeType: NODE_END,
|
|
ownerDocument: this.ownerDocument,
|
|
parentNode: null
|
|
};
|
|
}
|
|
|
|
get childNodes() {
|
|
const childNodes = new NodeList();
|
|
let { firstChild } = this;
|
|
while (firstChild) {
|
|
childNodes.push(firstChild);
|
|
firstChild = nextSibling(firstChild);
|
|
}
|
|
return childNodes;
|
|
}
|
|
|
|
get children() {
|
|
const children = new NodeList();
|
|
let { firstElementChild } = this;
|
|
while (firstElementChild) {
|
|
children.push(firstElementChild);
|
|
firstElementChild = nextElementSibling$1(firstElementChild);
|
|
}
|
|
return children;
|
|
}
|
|
|
|
/**
|
|
* @returns {NodeStruct | null}
|
|
*/
|
|
get firstChild() {
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next.nodeType === ATTRIBUTE_NODE) next = next[NEXT];
|
|
return next === end ? null : next;
|
|
}
|
|
|
|
/**
|
|
* @returns {NodeStruct | null}
|
|
*/
|
|
get firstElementChild() {
|
|
let { firstChild } = this;
|
|
while (firstChild) {
|
|
if (firstChild.nodeType === ELEMENT_NODE) return firstChild;
|
|
firstChild = nextSibling(firstChild);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
get lastChild() {
|
|
const prev = this[END][PREV];
|
|
switch (prev.nodeType) {
|
|
case NODE_END:
|
|
return prev[START];
|
|
case ATTRIBUTE_NODE:
|
|
return null;
|
|
}
|
|
return prev === this ? null : prev;
|
|
}
|
|
|
|
get lastElementChild() {
|
|
let { lastChild } = this;
|
|
while (lastChild) {
|
|
if (lastChild.nodeType === ELEMENT_NODE) return lastChild;
|
|
lastChild = previousSibling(lastChild);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
get childElementCount() {
|
|
return this.children.length;
|
|
}
|
|
|
|
prepend(...nodes) {
|
|
insert(this, this.firstChild, nodes);
|
|
}
|
|
|
|
append(...nodes) {
|
|
insert(this, this[END], nodes);
|
|
}
|
|
|
|
replaceChildren(...nodes) {
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end && next.nodeType === ATTRIBUTE_NODE) next = next[NEXT];
|
|
while (next !== end) {
|
|
const after = getEnd(next)[NEXT];
|
|
next.remove();
|
|
next = after;
|
|
}
|
|
if (nodes.length) insert(this, end, nodes);
|
|
}
|
|
|
|
getElementsByClassName(className) {
|
|
const elements = new NodeList();
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (
|
|
next.nodeType === ELEMENT_NODE &&
|
|
next.hasAttribute("class") &&
|
|
next.classList.has(className)
|
|
)
|
|
elements.push(next);
|
|
next = next[NEXT];
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
getElementsByTagName(tagName) {
|
|
const elements = new NodeList();
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (
|
|
next.nodeType === ELEMENT_NODE &&
|
|
(next.localName === tagName || localCase(next) === tagName)
|
|
)
|
|
elements.push(next);
|
|
next = next[NEXT];
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
querySelector(selectors) {
|
|
const matches = prepareMatch(this, selectors);
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE && matches(next)) return next;
|
|
next =
|
|
next.nodeType === ELEMENT_NODE && next.localName === "template"
|
|
? next[END]
|
|
: next[NEXT];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
querySelectorAll(selectors) {
|
|
const matches = prepareMatch(this, selectors);
|
|
const elements = new NodeList();
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE && matches(next)) elements.push(next);
|
|
next =
|
|
next.nodeType === ELEMENT_NODE && next.localName === "template"
|
|
? next[END]
|
|
: next[NEXT];
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
appendChild(node) {
|
|
return this.insertBefore(node, this[END]);
|
|
}
|
|
|
|
contains(node) {
|
|
let parentNode = node;
|
|
while (parentNode && parentNode !== this)
|
|
parentNode = parentNode.parentNode;
|
|
return parentNode === this;
|
|
}
|
|
|
|
insertBefore(node, before = null) {
|
|
if (node === before) return node;
|
|
if (node === this) throw new Error("unable to append a node to itself");
|
|
const next = before || this[END];
|
|
switch (node.nodeType) {
|
|
case ELEMENT_NODE:
|
|
node.remove();
|
|
node.parentNode = this;
|
|
knownBoundaries(next[PREV], node, next);
|
|
moCallback(node, null);
|
|
connectedCallback(node);
|
|
break;
|
|
case DOCUMENT_FRAGMENT_NODE: {
|
|
let { [PRIVATE]: parentNode, firstChild, lastChild } = node;
|
|
if (firstChild) {
|
|
knownSegment(next[PREV], firstChild, lastChild, next);
|
|
knownAdjacent(node, node[END]);
|
|
if (parentNode) parentNode.replaceChildren();
|
|
do {
|
|
firstChild.parentNode = this;
|
|
moCallback(firstChild, null);
|
|
if (firstChild.nodeType === ELEMENT_NODE)
|
|
connectedCallback(firstChild);
|
|
} while (
|
|
firstChild !== lastChild &&
|
|
(firstChild = nextSibling(firstChild))
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
node.remove();
|
|
/* eslint no-fallthrough:0 */
|
|
// this covers DOCUMENT_TYPE_NODE too
|
|
default:
|
|
node.parentNode = this;
|
|
knownSiblings(next[PREV], node, next);
|
|
moCallback(node, null);
|
|
break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
normalize() {
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
const { [NEXT]: $next, [PREV]: $prev, nodeType } = next;
|
|
if (nodeType === TEXT_NODE) {
|
|
if (!next[VALUE]) next.remove();
|
|
else if ($prev && $prev.nodeType === TEXT_NODE) {
|
|
$prev.textContent += next.textContent;
|
|
next.remove();
|
|
}
|
|
}
|
|
next = $next;
|
|
}
|
|
}
|
|
|
|
removeChild(node) {
|
|
if (node.parentNode !== this) throw new Error("node is not a child");
|
|
node.remove();
|
|
return node;
|
|
}
|
|
|
|
replaceChild(node, replaced) {
|
|
const next = getEnd(replaced)[NEXT];
|
|
replaced.remove();
|
|
this.insertBefore(node, next);
|
|
return replaced;
|
|
}
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#interface-nonelementparentnode
|
|
// Document, DocumentFragment
|
|
|
|
class NonElementParentNode extends ParentNode {
|
|
getElementById(id) {
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE && next.id === id) return next;
|
|
next = next[NEXT];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
cloneNode(deep) {
|
|
const { ownerDocument, constructor } = this;
|
|
const nonEPN = new constructor(ownerDocument);
|
|
if (deep) {
|
|
const { [END]: end } = nonEPN;
|
|
for (const node of this.childNodes)
|
|
nonEPN.insertBefore(node.cloneNode(deep), end);
|
|
}
|
|
return nonEPN;
|
|
}
|
|
|
|
toString() {
|
|
const { childNodes, localName } = this;
|
|
return `<${localName}>${childNodes.join("")}</${localName}>`;
|
|
}
|
|
|
|
toJSON() {
|
|
const json = [];
|
|
nonElementAsJSON(this, json);
|
|
return json;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.DocumentFragment
|
|
*/
|
|
let DocumentFragment$1 = class DocumentFragment extends NonElementParentNode {
|
|
constructor(ownerDocument) {
|
|
super(ownerDocument, "#document-fragment", DOCUMENT_FRAGMENT_NODE);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.DocumentType
|
|
*/
|
|
let DocumentType$1 = class DocumentType extends Node$1 {
|
|
constructor(ownerDocument, name, publicId = "", systemId = "") {
|
|
super(ownerDocument, "#document-type", DOCUMENT_TYPE_NODE);
|
|
this.name = name;
|
|
this.publicId = publicId;
|
|
this.systemId = systemId;
|
|
}
|
|
|
|
cloneNode() {
|
|
const { ownerDocument, name, publicId, systemId } = this;
|
|
return new DocumentType(ownerDocument, name, publicId, systemId);
|
|
}
|
|
|
|
toString() {
|
|
const { name, publicId, systemId } = this;
|
|
const hasPublic = 0 < publicId.length;
|
|
const str = [name];
|
|
if (hasPublic) str.push("PUBLIC", `"${publicId}"`);
|
|
if (systemId.length) {
|
|
if (!hasPublic) str.push("SYSTEM");
|
|
str.push(`"${systemId}"`);
|
|
}
|
|
return `<!DOCTYPE ${str.join(" ")}>`;
|
|
}
|
|
|
|
toJSON() {
|
|
const json = [];
|
|
documentTypeAsJSON(this, json);
|
|
return json;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @returns {String}
|
|
*/
|
|
const getInnerHtml = (node) => node.childNodes.join("");
|
|
|
|
/**
|
|
* @param {Node} node
|
|
* @param {String} html
|
|
*/
|
|
const setInnerHtml = (node, html) => {
|
|
const { ownerDocument } = node;
|
|
const { constructor } = ownerDocument;
|
|
const document = new constructor();
|
|
document[CUSTOM_ELEMENTS] = ownerDocument[CUSTOM_ELEMENTS];
|
|
const { childNodes } = parseFromString(document, ignoreCase(node), html);
|
|
|
|
node.replaceChildren(...childNodes.map(setOwnerDocument, ownerDocument));
|
|
};
|
|
|
|
function setOwnerDocument(node) {
|
|
node.ownerDocument = this;
|
|
switch (node.nodeType) {
|
|
case ELEMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
node.childNodes.forEach(setOwnerDocument, this);
|
|
break;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
var uhyphen = (camel) =>
|
|
camel
|
|
.replace(/(([A-Z0-9])([A-Z0-9][a-z]))|(([a-z0-9]+)([A-Z]))/g, "$2$5-$3$6")
|
|
.toLowerCase();
|
|
|
|
const refs$1 = new WeakMap();
|
|
|
|
const key = (name) => `data-${uhyphen(name)}`;
|
|
const prop = (name) =>
|
|
name.slice(5).replace(/-([a-z])/g, (_, $1) => $1.toUpperCase());
|
|
|
|
const handler$2 = {
|
|
get(dataset, name) {
|
|
if (name in dataset) return refs$1.get(dataset).getAttribute(key(name));
|
|
},
|
|
|
|
set(dataset, name, value) {
|
|
dataset[name] = value;
|
|
refs$1.get(dataset).setAttribute(key(name), value);
|
|
return true;
|
|
},
|
|
|
|
deleteProperty(dataset, name) {
|
|
if (name in dataset) refs$1.get(dataset).removeAttribute(key(name));
|
|
return delete dataset[name];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.DOMStringMap
|
|
*/
|
|
class DOMStringMap {
|
|
/**
|
|
* @param {Element} ref
|
|
*/
|
|
constructor(ref) {
|
|
for (const { name, value } of ref.attributes) {
|
|
if (/^data-/.test(name)) this[prop(name)] = value;
|
|
}
|
|
refs$1.set(this, ref);
|
|
return new Proxy(this, handler$2);
|
|
}
|
|
}
|
|
|
|
setPrototypeOf(DOMStringMap.prototype, null);
|
|
|
|
const { add } = Set.prototype;
|
|
const addTokens = (self, tokens) => {
|
|
for (const token of tokens) {
|
|
if (token) add.call(self, token);
|
|
}
|
|
};
|
|
|
|
const update = ({ [OWNER_ELEMENT]: ownerElement, value }) => {
|
|
const attribute = ownerElement.getAttributeNode("class");
|
|
if (attribute) attribute.value = value;
|
|
else
|
|
setAttribute(
|
|
ownerElement,
|
|
new Attr$1(ownerElement.ownerDocument, "class", value)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.DOMTokenList
|
|
*/
|
|
class DOMTokenList extends Set {
|
|
constructor(ownerElement) {
|
|
super();
|
|
this[OWNER_ELEMENT] = ownerElement;
|
|
const attribute = ownerElement.getAttributeNode("class");
|
|
if (attribute) addTokens(this, attribute.value.split(/\s+/));
|
|
}
|
|
|
|
get length() {
|
|
return this.size;
|
|
}
|
|
|
|
get value() {
|
|
return [...this].join(" ");
|
|
}
|
|
|
|
/**
|
|
* @param {...string} tokens
|
|
*/
|
|
add(...tokens) {
|
|
addTokens(this, tokens);
|
|
update(this);
|
|
}
|
|
|
|
/**
|
|
* @param {string} token
|
|
*/
|
|
contains(token) {
|
|
return this.has(token);
|
|
}
|
|
|
|
/**
|
|
* @param {...string} tokens
|
|
*/
|
|
remove(...tokens) {
|
|
for (const token of tokens) this.delete(token);
|
|
update(this);
|
|
}
|
|
|
|
/**
|
|
* @param {string} token
|
|
* @param {boolean?} force
|
|
*/
|
|
toggle(token, force) {
|
|
if (this.has(token)) {
|
|
if (force) return true;
|
|
this.delete(token);
|
|
update(this);
|
|
} else if (force || arguments.length === 1) {
|
|
super.add(token);
|
|
update(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param {string} token
|
|
* @param {string} newToken
|
|
*/
|
|
replace(token, newToken) {
|
|
if (this.has(token)) {
|
|
this.delete(token);
|
|
super.add(newToken);
|
|
update(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param {string} token
|
|
*/
|
|
supports() {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const refs = new WeakMap();
|
|
|
|
const getKeys = (style) => [...style.keys()].filter((key) => key !== PRIVATE);
|
|
|
|
const updateKeys = (style) => {
|
|
const attr = refs.get(style).getAttributeNode("style");
|
|
if (!attr || attr[CHANGED] || style.get(PRIVATE) !== attr) {
|
|
style.clear();
|
|
if (attr) {
|
|
style.set(PRIVATE, attr);
|
|
for (const rule of attr[VALUE].split(/\s*;\s*/)) {
|
|
let [key, ...rest] = rule.split(":");
|
|
if (rest.length > 0) {
|
|
key = key.trim();
|
|
const value = rest.join(":").trim();
|
|
if (key && value) style.set(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return attr;
|
|
};
|
|
|
|
const handler$1 = {
|
|
get(style, name) {
|
|
if (name in prototype) return style[name];
|
|
updateKeys(style);
|
|
if (name === "length") return getKeys(style).length;
|
|
if (/^\d+$/.test(name)) return getKeys(style)[name];
|
|
return style.get(uhyphen(name));
|
|
},
|
|
|
|
set(style, name, value) {
|
|
if (name === "cssText") style[name] = value;
|
|
else {
|
|
let attr = updateKeys(style);
|
|
if (value == null) style.delete(uhyphen(name));
|
|
else style.set(uhyphen(name), value);
|
|
if (!attr) {
|
|
const element = refs.get(style);
|
|
attr = element.ownerDocument.createAttribute("style");
|
|
element.setAttributeNode(attr);
|
|
style.set(PRIVATE, attr);
|
|
}
|
|
attr[CHANGED] = false;
|
|
attr[VALUE] = style.toString();
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.CSSStyleDeclaration
|
|
*/
|
|
let CSSStyleDeclaration$1 = class CSSStyleDeclaration extends Map {
|
|
constructor(element) {
|
|
super();
|
|
refs.set(this, element);
|
|
/* c8 ignore start */
|
|
return new Proxy(this, handler$1);
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
get cssText() {
|
|
return this.toString();
|
|
}
|
|
|
|
set cssText(value) {
|
|
refs.get(this).setAttribute("style", value);
|
|
}
|
|
|
|
getPropertyValue(name) {
|
|
const self = this[PRIVATE];
|
|
return handler$1.get(self, name);
|
|
}
|
|
|
|
setProperty(name, value) {
|
|
const self = this[PRIVATE];
|
|
handler$1.set(self, name, value);
|
|
}
|
|
|
|
removeProperty(name) {
|
|
const self = this[PRIVATE];
|
|
handler$1.set(self, name, null);
|
|
}
|
|
|
|
[Symbol.iterator]() {
|
|
const self = this[PRIVATE];
|
|
updateKeys(self);
|
|
const keys = getKeys(self);
|
|
const { length } = keys;
|
|
let i = 0;
|
|
return {
|
|
next() {
|
|
const done = i === length;
|
|
return { done, value: done ? null : keys[i++] };
|
|
}
|
|
};
|
|
}
|
|
|
|
get [PRIVATE]() {
|
|
return this;
|
|
}
|
|
|
|
toString() {
|
|
const self = this[PRIVATE];
|
|
updateKeys(self);
|
|
const cssText = [];
|
|
self.forEach(push, cssText);
|
|
return cssText.join(";");
|
|
}
|
|
};
|
|
|
|
const { prototype } = CSSStyleDeclaration$1;
|
|
|
|
function push(value, key) {
|
|
if (key !== PRIVATE) this.push(`${key}:${value}`);
|
|
}
|
|
|
|
// https://dom.spec.whatwg.org/#interface-event
|
|
|
|
/* c8 ignore start */
|
|
|
|
// Node 15 has Event but 14 and 12 don't
|
|
const BUBBLING_PHASE = 3;
|
|
const AT_TARGET = 2;
|
|
const CAPTURING_PHASE = 1;
|
|
const NONE = 0;
|
|
|
|
function getCurrentTarget(ev) {
|
|
return ev.currentTarget;
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.Event
|
|
*/
|
|
class GlobalEvent {
|
|
static get BUBBLING_PHASE() {
|
|
return BUBBLING_PHASE;
|
|
}
|
|
static get AT_TARGET() {
|
|
return AT_TARGET;
|
|
}
|
|
static get CAPTURING_PHASE() {
|
|
return CAPTURING_PHASE;
|
|
}
|
|
static get NONE() {
|
|
return NONE;
|
|
}
|
|
|
|
constructor(type, eventInitDict = {}) {
|
|
this.type = type;
|
|
this.bubbles = !!eventInitDict.bubbles;
|
|
this.cancelBubble = false;
|
|
this._stopImmediatePropagationFlag = false;
|
|
this.cancelable = !!eventInitDict.cancelable;
|
|
this.eventPhase = this.NONE;
|
|
this.timeStamp = Date.now();
|
|
this.defaultPrevented = false;
|
|
this.originalTarget = null;
|
|
this.returnValue = null;
|
|
this.srcElement = null;
|
|
this.target = null;
|
|
this._path = [];
|
|
}
|
|
|
|
get BUBBLING_PHASE() {
|
|
return BUBBLING_PHASE;
|
|
}
|
|
get AT_TARGET() {
|
|
return AT_TARGET;
|
|
}
|
|
get CAPTURING_PHASE() {
|
|
return CAPTURING_PHASE;
|
|
}
|
|
get NONE() {
|
|
return NONE;
|
|
}
|
|
|
|
preventDefault() {
|
|
this.defaultPrevented = true;
|
|
}
|
|
|
|
// simplified implementation, should be https://dom.spec.whatwg.org/#dom-event-composedpath
|
|
composedPath() {
|
|
return this._path.map(getCurrentTarget);
|
|
}
|
|
|
|
stopPropagation() {
|
|
this.cancelBubble = true;
|
|
}
|
|
|
|
stopImmediatePropagation() {
|
|
this.stopPropagation();
|
|
this._stopImmediatePropagationFlag = true;
|
|
}
|
|
}
|
|
|
|
/* c8 ignore stop */
|
|
|
|
/**
|
|
* @implements globalThis.NamedNodeMap
|
|
*/
|
|
class NamedNodeMap extends Array {
|
|
constructor(ownerElement) {
|
|
super();
|
|
this.ownerElement = ownerElement;
|
|
}
|
|
|
|
getNamedItem(name) {
|
|
return this.ownerElement.getAttributeNode(name);
|
|
}
|
|
|
|
setNamedItem(attr) {
|
|
this.ownerElement.setAttributeNode(attr);
|
|
this.unshift(attr);
|
|
}
|
|
|
|
removeNamedItem(name) {
|
|
const item = this.getNamedItem(name);
|
|
this.ownerElement.removeAttribute(name);
|
|
this.splice(this.indexOf(item), 1);
|
|
}
|
|
|
|
item(index) {
|
|
return index < this.length ? this[index] : null;
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
getNamedItemNS(_, name) {
|
|
return this.getNamedItem(name);
|
|
}
|
|
|
|
setNamedItemNS(_, attr) {
|
|
return this.setNamedItem(attr);
|
|
}
|
|
|
|
removeNamedItemNS(_, name) {
|
|
return this.removeNamedItem(name);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.ShadowRoot
|
|
*/
|
|
let ShadowRoot$1 = class ShadowRoot extends NonElementParentNode {
|
|
constructor(host) {
|
|
super(host.ownerDocument, "#shadow-root", DOCUMENT_FRAGMENT_NODE);
|
|
this.host = host;
|
|
}
|
|
|
|
get innerHTML() {
|
|
return getInnerHtml(this);
|
|
}
|
|
set innerHTML(html) {
|
|
setInnerHtml(this, html);
|
|
}
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#interface-element
|
|
|
|
// <utils>
|
|
const attributesHandler = {
|
|
get(target, key) {
|
|
return key in target
|
|
? target[key]
|
|
: target.find(({ name }) => name === key);
|
|
}
|
|
};
|
|
|
|
const create = (ownerDocument, element, localName) => {
|
|
if ("ownerSVGElement" in element) {
|
|
const svg = ownerDocument.createElementNS(SVG_NAMESPACE, localName);
|
|
svg.ownerSVGElement = element.ownerSVGElement;
|
|
return svg;
|
|
}
|
|
return ownerDocument.createElement(localName);
|
|
};
|
|
|
|
const isVoid = ({ localName, ownerDocument }) => {
|
|
return ownerDocument[MIME].voidElements.test(localName);
|
|
};
|
|
|
|
// </utils>
|
|
|
|
/**
|
|
* @implements globalThis.Element
|
|
*/
|
|
let Element$1 = class Element extends ParentNode {
|
|
constructor(ownerDocument, localName) {
|
|
super(ownerDocument, localName, ELEMENT_NODE);
|
|
this[CLASS_LIST] = null;
|
|
this[DATASET] = null;
|
|
this[STYLE] = null;
|
|
}
|
|
|
|
// <Mixins>
|
|
get isConnected() {
|
|
return isConnected(this);
|
|
}
|
|
get parentElement() {
|
|
return parentElement(this);
|
|
}
|
|
get previousSibling() {
|
|
return previousSibling(this);
|
|
}
|
|
get nextSibling() {
|
|
return nextSibling(this);
|
|
}
|
|
get namespaceURI() {
|
|
return "http://www.w3.org/1999/xhtml";
|
|
}
|
|
|
|
get previousElementSibling() {
|
|
return previousElementSibling(this);
|
|
}
|
|
get nextElementSibling() {
|
|
return nextElementSibling$1(this);
|
|
}
|
|
|
|
before(...nodes) {
|
|
before(this, nodes);
|
|
}
|
|
after(...nodes) {
|
|
after(this, nodes);
|
|
}
|
|
replaceWith(...nodes) {
|
|
replaceWith(this, nodes);
|
|
}
|
|
remove() {
|
|
remove(this[PREV], this, this[END][NEXT]);
|
|
}
|
|
// </Mixins>
|
|
|
|
// <specialGetters>
|
|
get id() {
|
|
return stringAttribute.get(this, "id");
|
|
}
|
|
set id(value) {
|
|
stringAttribute.set(this, "id", value);
|
|
}
|
|
|
|
get className() {
|
|
return this.classList.value;
|
|
}
|
|
set className(value) {
|
|
const { classList } = this;
|
|
classList.clear();
|
|
classList.add(...$String(value).split(/\s+/));
|
|
}
|
|
|
|
get nodeName() {
|
|
return localCase(this);
|
|
}
|
|
get tagName() {
|
|
return localCase(this);
|
|
}
|
|
|
|
get classList() {
|
|
return this[CLASS_LIST] || (this[CLASS_LIST] = new DOMTokenList(this));
|
|
}
|
|
|
|
get dataset() {
|
|
return this[DATASET] || (this[DATASET] = new DOMStringMap(this));
|
|
}
|
|
|
|
getBoundingClientRect() {
|
|
return {
|
|
x: 0,
|
|
y: 0,
|
|
bottom: 0,
|
|
height: 0,
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
width: 0
|
|
};
|
|
}
|
|
|
|
get nonce() {
|
|
return stringAttribute.get(this, "nonce");
|
|
}
|
|
set nonce(value) {
|
|
stringAttribute.set(this, "nonce", value);
|
|
}
|
|
|
|
get style() {
|
|
return this[STYLE] || (this[STYLE] = new CSSStyleDeclaration$1(this));
|
|
}
|
|
|
|
get tabIndex() {
|
|
return numericAttribute.get(this, "tabindex") || -1;
|
|
}
|
|
set tabIndex(value) {
|
|
numericAttribute.set(this, "tabindex", value);
|
|
}
|
|
|
|
get slot() {
|
|
return stringAttribute.get(this, "slot");
|
|
}
|
|
set slot(value) {
|
|
stringAttribute.set(this, "slot", value);
|
|
}
|
|
// </specialGetters>
|
|
|
|
// <contentRelated>
|
|
get innerText() {
|
|
const text = [];
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
if (next.nodeType === TEXT_NODE) {
|
|
text.push(next.textContent.replace(/\s+/g, " "));
|
|
} else if (
|
|
text.length &&
|
|
next[NEXT] != end &&
|
|
BLOCK_ELEMENTS.has(next.tagName)
|
|
) {
|
|
text.push("\n");
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
return text.join("");
|
|
}
|
|
|
|
/**
|
|
* @returns {String}
|
|
*/
|
|
get textContent() {
|
|
const text = [];
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
const nodeType = next.nodeType;
|
|
if (nodeType === TEXT_NODE || nodeType === CDATA_SECTION_NODE)
|
|
text.push(next.textContent);
|
|
next = next[NEXT];
|
|
}
|
|
return text.join("");
|
|
}
|
|
|
|
set textContent(text) {
|
|
this.replaceChildren();
|
|
if (text != null && text !== "")
|
|
this.appendChild(new Text$1(this.ownerDocument, text));
|
|
}
|
|
|
|
get innerHTML() {
|
|
return getInnerHtml(this);
|
|
}
|
|
set innerHTML(html) {
|
|
setInnerHtml(this, html);
|
|
}
|
|
|
|
get outerHTML() {
|
|
return this.toString();
|
|
}
|
|
set outerHTML(html) {
|
|
const template = this.ownerDocument.createElement("");
|
|
template.innerHTML = html;
|
|
this.replaceWith(...template.childNodes);
|
|
}
|
|
// </contentRelated>
|
|
|
|
// <attributes>
|
|
get attributes() {
|
|
const attributes = new NamedNodeMap(this);
|
|
let next = this[NEXT];
|
|
while (next.nodeType === ATTRIBUTE_NODE) {
|
|
attributes.push(next);
|
|
next = next[NEXT];
|
|
}
|
|
return new Proxy(attributes, attributesHandler);
|
|
}
|
|
|
|
focus() {
|
|
this.dispatchEvent(new GlobalEvent("focus"));
|
|
}
|
|
|
|
getAttribute(name) {
|
|
if (name === "class") return this.className;
|
|
const attribute = this.getAttributeNode(name);
|
|
return (
|
|
attribute &&
|
|
(ignoreCase(this) ? attribute.value : escape(attribute.value))
|
|
);
|
|
}
|
|
|
|
getAttributeNode(name) {
|
|
let next = this[NEXT];
|
|
while (next.nodeType === ATTRIBUTE_NODE) {
|
|
if (next.name === name) return next;
|
|
next = next[NEXT];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
getAttributeNames() {
|
|
const attributes = new NodeList();
|
|
let next = this[NEXT];
|
|
while (next.nodeType === ATTRIBUTE_NODE) {
|
|
attributes.push(next.name);
|
|
next = next[NEXT];
|
|
}
|
|
return attributes;
|
|
}
|
|
|
|
hasAttribute(name) {
|
|
return !!this.getAttributeNode(name);
|
|
}
|
|
hasAttributes() {
|
|
return this[NEXT].nodeType === ATTRIBUTE_NODE;
|
|
}
|
|
|
|
removeAttribute(name) {
|
|
if (name === "class" && this[CLASS_LIST]) this[CLASS_LIST].clear();
|
|
let next = this[NEXT];
|
|
while (next.nodeType === ATTRIBUTE_NODE) {
|
|
if (next.name === name) {
|
|
removeAttribute(this, next);
|
|
return;
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
}
|
|
|
|
removeAttributeNode(attribute) {
|
|
let next = this[NEXT];
|
|
while (next.nodeType === ATTRIBUTE_NODE) {
|
|
if (next === attribute) {
|
|
removeAttribute(this, next);
|
|
return;
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
}
|
|
|
|
setAttribute(name, value) {
|
|
if (name === "class") this.className = value;
|
|
else {
|
|
const attribute = this.getAttributeNode(name);
|
|
if (attribute) attribute.value = value;
|
|
else setAttribute(this, new Attr$1(this.ownerDocument, name, value));
|
|
}
|
|
}
|
|
|
|
setAttributeNode(attribute) {
|
|
const { name } = attribute;
|
|
const previously = this.getAttributeNode(name);
|
|
if (previously !== attribute) {
|
|
if (previously) this.removeAttributeNode(previously);
|
|
const { ownerElement } = attribute;
|
|
if (ownerElement) ownerElement.removeAttributeNode(attribute);
|
|
setAttribute(this, attribute);
|
|
}
|
|
return previously;
|
|
}
|
|
|
|
toggleAttribute(name, force) {
|
|
if (this.hasAttribute(name)) {
|
|
if (!force) {
|
|
this.removeAttribute(name);
|
|
return false;
|
|
}
|
|
return true;
|
|
} else if (force || arguments.length === 1) {
|
|
this.setAttribute(name, "");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
// </attributes>
|
|
|
|
// <ShadowDOM>
|
|
get shadowRoot() {
|
|
if (shadowRoots.has(this)) {
|
|
const { mode, shadowRoot } = shadowRoots.get(this);
|
|
if (mode === "open") return shadowRoot;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
attachShadow(init) {
|
|
if (shadowRoots.has(this)) throw new Error("operation not supported");
|
|
// TODO: shadowRoot should be likely a specialized class that extends DocumentFragment
|
|
// but until DSD is out, I am not sure I should spend time on this.
|
|
const shadowRoot = new ShadowRoot$1(this);
|
|
shadowRoots.set(this, {
|
|
mode: init.mode,
|
|
shadowRoot
|
|
});
|
|
return shadowRoot;
|
|
}
|
|
// </ShadowDOM>
|
|
|
|
// <selectors>
|
|
matches(selectors) {
|
|
return matches(this, selectors);
|
|
}
|
|
closest(selectors) {
|
|
let parentElement = this;
|
|
const matches = prepareMatch(parentElement, selectors);
|
|
while (parentElement && !matches(parentElement))
|
|
parentElement = parentElement.parentElement;
|
|
return parentElement;
|
|
}
|
|
// </selectors>
|
|
|
|
// <insertAdjacent>
|
|
insertAdjacentElement(position, element) {
|
|
const { parentElement } = this;
|
|
switch (position) {
|
|
case "beforebegin":
|
|
if (parentElement) {
|
|
parentElement.insertBefore(element, this);
|
|
break;
|
|
}
|
|
return null;
|
|
case "afterbegin":
|
|
this.insertBefore(element, this.firstChild);
|
|
break;
|
|
case "beforeend":
|
|
this.insertBefore(element, null);
|
|
break;
|
|
case "afterend":
|
|
if (parentElement) {
|
|
parentElement.insertBefore(element, this.nextSibling);
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
return element;
|
|
}
|
|
|
|
insertAdjacentHTML(position, html) {
|
|
this.insertAdjacentElement(
|
|
position,
|
|
htmlToFragment(this.ownerDocument, html)
|
|
);
|
|
}
|
|
|
|
insertAdjacentText(position, text) {
|
|
const node = this.ownerDocument.createTextNode(text);
|
|
this.insertAdjacentElement(position, node);
|
|
}
|
|
// </insertAdjacent>
|
|
|
|
cloneNode(deep = false) {
|
|
const { ownerDocument, localName } = this;
|
|
const addNext = (next) => {
|
|
next.parentNode = parentNode;
|
|
knownAdjacent($next, next);
|
|
$next = next;
|
|
};
|
|
const clone = create(ownerDocument, this, localName);
|
|
let parentNode = clone,
|
|
$next = clone;
|
|
let { [NEXT]: next, [END]: prev } = this;
|
|
while (next !== prev && (deep || next.nodeType === ATTRIBUTE_NODE)) {
|
|
switch (next.nodeType) {
|
|
case NODE_END:
|
|
knownAdjacent($next, parentNode[END]);
|
|
$next = parentNode[END];
|
|
parentNode = parentNode.parentNode;
|
|
break;
|
|
case ELEMENT_NODE: {
|
|
const node = create(ownerDocument, next, next.localName);
|
|
addNext(node);
|
|
parentNode = node;
|
|
break;
|
|
}
|
|
case ATTRIBUTE_NODE: {
|
|
const attr = next.cloneNode(deep);
|
|
attr.ownerElement = parentNode;
|
|
addNext(attr);
|
|
break;
|
|
}
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
addNext(next.cloneNode(deep));
|
|
break;
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
knownAdjacent($next, clone[END]);
|
|
return clone;
|
|
}
|
|
|
|
// <custom>
|
|
toString() {
|
|
const out = [];
|
|
const { [END]: end } = this;
|
|
let next = { [NEXT]: this };
|
|
let isOpened = false;
|
|
do {
|
|
next = next[NEXT];
|
|
switch (next.nodeType) {
|
|
case ATTRIBUTE_NODE: {
|
|
const attr = " " + next;
|
|
switch (attr) {
|
|
case " id":
|
|
case " class":
|
|
case " style":
|
|
break;
|
|
default:
|
|
out.push(attr);
|
|
}
|
|
break;
|
|
}
|
|
case NODE_END: {
|
|
const start = next[START];
|
|
if (isOpened) {
|
|
if ("ownerSVGElement" in start) out.push(" />");
|
|
else if (isVoid(start)) out.push(ignoreCase(start) ? ">" : " />");
|
|
else out.push(`></${start.localName}>`);
|
|
isOpened = false;
|
|
} else out.push(`</${start.localName}>`);
|
|
break;
|
|
}
|
|
case ELEMENT_NODE:
|
|
if (isOpened) out.push(">");
|
|
if (next.toString !== this.toString) {
|
|
out.push(next.toString());
|
|
next = next[END];
|
|
isOpened = false;
|
|
} else {
|
|
out.push(`<${next.localName}`);
|
|
isOpened = true;
|
|
}
|
|
break;
|
|
case TEXT_NODE:
|
|
case COMMENT_NODE:
|
|
case CDATA_SECTION_NODE:
|
|
out.push((isOpened ? ">" : "") + next);
|
|
isOpened = false;
|
|
break;
|
|
}
|
|
} while (next !== end);
|
|
return out.join("");
|
|
}
|
|
|
|
toJSON() {
|
|
const json = [];
|
|
elementAsJSON(this, json);
|
|
return json;
|
|
}
|
|
// </custom>
|
|
|
|
/* c8 ignore start */
|
|
getAttributeNS(_, name) {
|
|
return this.getAttribute(name);
|
|
}
|
|
getElementsByTagNameNS(_, name) {
|
|
return this.getElementsByTagName(name);
|
|
}
|
|
hasAttributeNS(_, name) {
|
|
return this.hasAttribute(name);
|
|
}
|
|
removeAttributeNS(_, name) {
|
|
this.removeAttribute(name);
|
|
}
|
|
setAttributeNS(_, name, value) {
|
|
this.setAttribute(name, value);
|
|
}
|
|
setAttributeNodeNS(attr) {
|
|
return this.setAttributeNode(attr);
|
|
}
|
|
/* c8 ignore stop */
|
|
};
|
|
|
|
const classNames = new WeakMap();
|
|
|
|
const handler = {
|
|
get(target, name) {
|
|
return target[name];
|
|
},
|
|
set(target, name, value) {
|
|
target[name] = value;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.SVGElement
|
|
*/
|
|
let SVGElement$1 = class SVGElement extends Element$1 {
|
|
constructor(ownerDocument, localName, ownerSVGElement = null) {
|
|
super(ownerDocument, localName);
|
|
this.ownerSVGElement = ownerSVGElement;
|
|
}
|
|
|
|
get className() {
|
|
if (!classNames.has(this))
|
|
classNames.set(this, new Proxy({ baseVal: "", animVal: "" }, handler));
|
|
return classNames.get(this);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
set className(value) {
|
|
const { classList } = this;
|
|
classList.clear();
|
|
classList.add(...$String(value).split(/\s+/));
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
get namespaceURI() {
|
|
return "http://www.w3.org/2000/svg";
|
|
}
|
|
|
|
getAttribute(name) {
|
|
return name === "class"
|
|
? [...this.classList].join(" ")
|
|
: super.getAttribute(name);
|
|
}
|
|
|
|
setAttribute(name, value) {
|
|
if (name === "class") this.className = value;
|
|
else if (name === "style") {
|
|
const { className } = this;
|
|
className.baseVal = className.animVal = value;
|
|
}
|
|
super.setAttribute(name, value);
|
|
}
|
|
};
|
|
|
|
/* c8 ignore start */
|
|
const illegalConstructor = () => {
|
|
throw new TypeError("Illegal constructor");
|
|
};
|
|
|
|
function Attr() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(Attr, Attr$1);
|
|
Attr.prototype = Attr$1.prototype;
|
|
|
|
function CDATASection() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(CDATASection, CDATASection$1);
|
|
CDATASection.prototype = CDATASection$1.prototype;
|
|
|
|
function CharacterData() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(CharacterData, CharacterData$1);
|
|
CharacterData.prototype = CharacterData$1.prototype;
|
|
|
|
function Comment() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(Comment, Comment$2);
|
|
Comment.prototype = Comment$2.prototype;
|
|
|
|
function DocumentFragment() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(DocumentFragment, DocumentFragment$1);
|
|
DocumentFragment.prototype = DocumentFragment$1.prototype;
|
|
|
|
function DocumentType() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(DocumentType, DocumentType$1);
|
|
DocumentType.prototype = DocumentType$1.prototype;
|
|
|
|
function Element() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(Element, Element$1);
|
|
Element.prototype = Element$1.prototype;
|
|
|
|
function Node() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(Node, Node$1);
|
|
Node.prototype = Node$1.prototype;
|
|
|
|
function ShadowRoot() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(ShadowRoot, ShadowRoot$1);
|
|
ShadowRoot.prototype = ShadowRoot$1.prototype;
|
|
|
|
function Text() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(Text, Text$1);
|
|
Text.prototype = Text$1.prototype;
|
|
|
|
function SVGElement() {
|
|
illegalConstructor();
|
|
}
|
|
setPrototypeOf(SVGElement, SVGElement$1);
|
|
SVGElement.prototype = SVGElement$1.prototype;
|
|
/* c8 ignore stop */
|
|
|
|
const Facades = {
|
|
Attr,
|
|
CDATASection,
|
|
CharacterData,
|
|
Comment,
|
|
DocumentFragment,
|
|
DocumentType,
|
|
Element,
|
|
Node,
|
|
ShadowRoot,
|
|
Text,
|
|
SVGElement
|
|
};
|
|
|
|
const Level0 = new WeakMap();
|
|
const level0 = {
|
|
get(element, name) {
|
|
return (Level0.has(element) && Level0.get(element)[name]) || null;
|
|
},
|
|
set(element, name, value) {
|
|
if (!Level0.has(element)) Level0.set(element, {});
|
|
const handlers = Level0.get(element);
|
|
const type = name.slice(2);
|
|
if (handlers[name])
|
|
element.removeEventListener(type, handlers[name], false);
|
|
if ((handlers[name] = value)) element.addEventListener(type, value, false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.HTMLElement
|
|
*/
|
|
class HTMLElement extends Element$1 {
|
|
static get observedAttributes() {
|
|
return [];
|
|
}
|
|
|
|
constructor(ownerDocument = null, localName = "") {
|
|
super(ownerDocument, localName);
|
|
|
|
const ownerLess = !ownerDocument;
|
|
let options;
|
|
|
|
if (ownerLess) {
|
|
const { constructor: Class } = this;
|
|
if (!Classes.has(Class))
|
|
throw new Error("unable to initialize this Custom Element");
|
|
({ ownerDocument, localName, options } = Classes.get(Class));
|
|
}
|
|
|
|
if (ownerDocument[UPGRADE]) {
|
|
const { element, values } = ownerDocument[UPGRADE];
|
|
ownerDocument[UPGRADE] = null;
|
|
for (const [key, value] of values) element[key] = value;
|
|
return element;
|
|
}
|
|
|
|
if (ownerLess) {
|
|
this.ownerDocument = this[END].ownerDocument = ownerDocument;
|
|
this.localName = localName;
|
|
customElements.set(this, { connected: false });
|
|
if (options.is) this.setAttribute("is", options.is);
|
|
}
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
|
|
/* TODO: what about these?
|
|
offsetHeight
|
|
offsetLeft
|
|
offsetParent
|
|
offsetTop
|
|
offsetWidth
|
|
*/
|
|
|
|
blur() {
|
|
this.dispatchEvent(new GlobalEvent("blur"));
|
|
}
|
|
click() {
|
|
const clickEvent = new GlobalEvent("click", {
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
clickEvent.button = 0;
|
|
|
|
this.dispatchEvent(clickEvent);
|
|
}
|
|
|
|
// Boolean getters
|
|
get accessKeyLabel() {
|
|
const { accessKey } = this;
|
|
return accessKey && `Alt+Shift+${accessKey}`;
|
|
}
|
|
get isContentEditable() {
|
|
return this.hasAttribute("contenteditable");
|
|
}
|
|
|
|
// Boolean Accessors
|
|
get contentEditable() {
|
|
return booleanAttribute.get(this, "contenteditable");
|
|
}
|
|
set contentEditable(value) {
|
|
booleanAttribute.set(this, "contenteditable", value);
|
|
}
|
|
get draggable() {
|
|
return booleanAttribute.get(this, "draggable");
|
|
}
|
|
set draggable(value) {
|
|
booleanAttribute.set(this, "draggable", value);
|
|
}
|
|
get hidden() {
|
|
return booleanAttribute.get(this, "hidden");
|
|
}
|
|
set hidden(value) {
|
|
booleanAttribute.set(this, "hidden", value);
|
|
}
|
|
get spellcheck() {
|
|
return booleanAttribute.get(this, "spellcheck");
|
|
}
|
|
set spellcheck(value) {
|
|
booleanAttribute.set(this, "spellcheck", value);
|
|
}
|
|
|
|
// String Accessors
|
|
get accessKey() {
|
|
return stringAttribute.get(this, "accesskey");
|
|
}
|
|
set accessKey(value) {
|
|
stringAttribute.set(this, "accesskey", value);
|
|
}
|
|
get dir() {
|
|
return stringAttribute.get(this, "dir");
|
|
}
|
|
set dir(value) {
|
|
stringAttribute.set(this, "dir", value);
|
|
}
|
|
get lang() {
|
|
return stringAttribute.get(this, "lang");
|
|
}
|
|
set lang(value) {
|
|
stringAttribute.set(this, "lang", value);
|
|
}
|
|
get title() {
|
|
return stringAttribute.get(this, "title");
|
|
}
|
|
set title(value) {
|
|
stringAttribute.set(this, "title", value);
|
|
}
|
|
|
|
// DOM Level 0
|
|
get onabort() {
|
|
return level0.get(this, "onabort");
|
|
}
|
|
set onabort(value) {
|
|
level0.set(this, "onabort", value);
|
|
}
|
|
|
|
get onblur() {
|
|
return level0.get(this, "onblur");
|
|
}
|
|
set onblur(value) {
|
|
level0.set(this, "onblur", value);
|
|
}
|
|
|
|
get oncancel() {
|
|
return level0.get(this, "oncancel");
|
|
}
|
|
set oncancel(value) {
|
|
level0.set(this, "oncancel", value);
|
|
}
|
|
|
|
get oncanplay() {
|
|
return level0.get(this, "oncanplay");
|
|
}
|
|
set oncanplay(value) {
|
|
level0.set(this, "oncanplay", value);
|
|
}
|
|
|
|
get oncanplaythrough() {
|
|
return level0.get(this, "oncanplaythrough");
|
|
}
|
|
set oncanplaythrough(value) {
|
|
level0.set(this, "oncanplaythrough", value);
|
|
}
|
|
|
|
get onchange() {
|
|
return level0.get(this, "onchange");
|
|
}
|
|
set onchange(value) {
|
|
level0.set(this, "onchange", value);
|
|
}
|
|
|
|
get onclick() {
|
|
return level0.get(this, "onclick");
|
|
}
|
|
set onclick(value) {
|
|
level0.set(this, "onclick", value);
|
|
}
|
|
|
|
get onclose() {
|
|
return level0.get(this, "onclose");
|
|
}
|
|
set onclose(value) {
|
|
level0.set(this, "onclose", value);
|
|
}
|
|
|
|
get oncontextmenu() {
|
|
return level0.get(this, "oncontextmenu");
|
|
}
|
|
set oncontextmenu(value) {
|
|
level0.set(this, "oncontextmenu", value);
|
|
}
|
|
|
|
get oncuechange() {
|
|
return level0.get(this, "oncuechange");
|
|
}
|
|
set oncuechange(value) {
|
|
level0.set(this, "oncuechange", value);
|
|
}
|
|
|
|
get ondblclick() {
|
|
return level0.get(this, "ondblclick");
|
|
}
|
|
set ondblclick(value) {
|
|
level0.set(this, "ondblclick", value);
|
|
}
|
|
|
|
get ondrag() {
|
|
return level0.get(this, "ondrag");
|
|
}
|
|
set ondrag(value) {
|
|
level0.set(this, "ondrag", value);
|
|
}
|
|
|
|
get ondragend() {
|
|
return level0.get(this, "ondragend");
|
|
}
|
|
set ondragend(value) {
|
|
level0.set(this, "ondragend", value);
|
|
}
|
|
|
|
get ondragenter() {
|
|
return level0.get(this, "ondragenter");
|
|
}
|
|
set ondragenter(value) {
|
|
level0.set(this, "ondragenter", value);
|
|
}
|
|
|
|
get ondragleave() {
|
|
return level0.get(this, "ondragleave");
|
|
}
|
|
set ondragleave(value) {
|
|
level0.set(this, "ondragleave", value);
|
|
}
|
|
|
|
get ondragover() {
|
|
return level0.get(this, "ondragover");
|
|
}
|
|
set ondragover(value) {
|
|
level0.set(this, "ondragover", value);
|
|
}
|
|
|
|
get ondragstart() {
|
|
return level0.get(this, "ondragstart");
|
|
}
|
|
set ondragstart(value) {
|
|
level0.set(this, "ondragstart", value);
|
|
}
|
|
|
|
get ondrop() {
|
|
return level0.get(this, "ondrop");
|
|
}
|
|
set ondrop(value) {
|
|
level0.set(this, "ondrop", value);
|
|
}
|
|
|
|
get ondurationchange() {
|
|
return level0.get(this, "ondurationchange");
|
|
}
|
|
set ondurationchange(value) {
|
|
level0.set(this, "ondurationchange", value);
|
|
}
|
|
|
|
get onemptied() {
|
|
return level0.get(this, "onemptied");
|
|
}
|
|
set onemptied(value) {
|
|
level0.set(this, "onemptied", value);
|
|
}
|
|
|
|
get onended() {
|
|
return level0.get(this, "onended");
|
|
}
|
|
set onended(value) {
|
|
level0.set(this, "onended", value);
|
|
}
|
|
|
|
get onerror() {
|
|
return level0.get(this, "onerror");
|
|
}
|
|
set onerror(value) {
|
|
level0.set(this, "onerror", value);
|
|
}
|
|
|
|
get onfocus() {
|
|
return level0.get(this, "onfocus");
|
|
}
|
|
set onfocus(value) {
|
|
level0.set(this, "onfocus", value);
|
|
}
|
|
|
|
get oninput() {
|
|
return level0.get(this, "oninput");
|
|
}
|
|
set oninput(value) {
|
|
level0.set(this, "oninput", value);
|
|
}
|
|
|
|
get oninvalid() {
|
|
return level0.get(this, "oninvalid");
|
|
}
|
|
set oninvalid(value) {
|
|
level0.set(this, "oninvalid", value);
|
|
}
|
|
|
|
get onkeydown() {
|
|
return level0.get(this, "onkeydown");
|
|
}
|
|
set onkeydown(value) {
|
|
level0.set(this, "onkeydown", value);
|
|
}
|
|
|
|
get onkeypress() {
|
|
return level0.get(this, "onkeypress");
|
|
}
|
|
set onkeypress(value) {
|
|
level0.set(this, "onkeypress", value);
|
|
}
|
|
|
|
get onkeyup() {
|
|
return level0.get(this, "onkeyup");
|
|
}
|
|
set onkeyup(value) {
|
|
level0.set(this, "onkeyup", value);
|
|
}
|
|
|
|
get onload() {
|
|
return level0.get(this, "onload");
|
|
}
|
|
set onload(value) {
|
|
level0.set(this, "onload", value);
|
|
}
|
|
|
|
get onloadeddata() {
|
|
return level0.get(this, "onloadeddata");
|
|
}
|
|
set onloadeddata(value) {
|
|
level0.set(this, "onloadeddata", value);
|
|
}
|
|
|
|
get onloadedmetadata() {
|
|
return level0.get(this, "onloadedmetadata");
|
|
}
|
|
set onloadedmetadata(value) {
|
|
level0.set(this, "onloadedmetadata", value);
|
|
}
|
|
|
|
get onloadstart() {
|
|
return level0.get(this, "onloadstart");
|
|
}
|
|
set onloadstart(value) {
|
|
level0.set(this, "onloadstart", value);
|
|
}
|
|
|
|
get onmousedown() {
|
|
return level0.get(this, "onmousedown");
|
|
}
|
|
set onmousedown(value) {
|
|
level0.set(this, "onmousedown", value);
|
|
}
|
|
|
|
get onmouseenter() {
|
|
return level0.get(this, "onmouseenter");
|
|
}
|
|
set onmouseenter(value) {
|
|
level0.set(this, "onmouseenter", value);
|
|
}
|
|
|
|
get onmouseleave() {
|
|
return level0.get(this, "onmouseleave");
|
|
}
|
|
set onmouseleave(value) {
|
|
level0.set(this, "onmouseleave", value);
|
|
}
|
|
|
|
get onmousemove() {
|
|
return level0.get(this, "onmousemove");
|
|
}
|
|
set onmousemove(value) {
|
|
level0.set(this, "onmousemove", value);
|
|
}
|
|
|
|
get onmouseout() {
|
|
return level0.get(this, "onmouseout");
|
|
}
|
|
set onmouseout(value) {
|
|
level0.set(this, "onmouseout", value);
|
|
}
|
|
|
|
get onmouseover() {
|
|
return level0.get(this, "onmouseover");
|
|
}
|
|
set onmouseover(value) {
|
|
level0.set(this, "onmouseover", value);
|
|
}
|
|
|
|
get onmouseup() {
|
|
return level0.get(this, "onmouseup");
|
|
}
|
|
set onmouseup(value) {
|
|
level0.set(this, "onmouseup", value);
|
|
}
|
|
|
|
get onmousewheel() {
|
|
return level0.get(this, "onmousewheel");
|
|
}
|
|
set onmousewheel(value) {
|
|
level0.set(this, "onmousewheel", value);
|
|
}
|
|
|
|
get onpause() {
|
|
return level0.get(this, "onpause");
|
|
}
|
|
set onpause(value) {
|
|
level0.set(this, "onpause", value);
|
|
}
|
|
|
|
get onplay() {
|
|
return level0.get(this, "onplay");
|
|
}
|
|
set onplay(value) {
|
|
level0.set(this, "onplay", value);
|
|
}
|
|
|
|
get onplaying() {
|
|
return level0.get(this, "onplaying");
|
|
}
|
|
set onplaying(value) {
|
|
level0.set(this, "onplaying", value);
|
|
}
|
|
|
|
get onprogress() {
|
|
return level0.get(this, "onprogress");
|
|
}
|
|
set onprogress(value) {
|
|
level0.set(this, "onprogress", value);
|
|
}
|
|
|
|
get onratechange() {
|
|
return level0.get(this, "onratechange");
|
|
}
|
|
set onratechange(value) {
|
|
level0.set(this, "onratechange", value);
|
|
}
|
|
|
|
get onreset() {
|
|
return level0.get(this, "onreset");
|
|
}
|
|
set onreset(value) {
|
|
level0.set(this, "onreset", value);
|
|
}
|
|
|
|
get onresize() {
|
|
return level0.get(this, "onresize");
|
|
}
|
|
set onresize(value) {
|
|
level0.set(this, "onresize", value);
|
|
}
|
|
|
|
get onscroll() {
|
|
return level0.get(this, "onscroll");
|
|
}
|
|
set onscroll(value) {
|
|
level0.set(this, "onscroll", value);
|
|
}
|
|
|
|
get onseeked() {
|
|
return level0.get(this, "onseeked");
|
|
}
|
|
set onseeked(value) {
|
|
level0.set(this, "onseeked", value);
|
|
}
|
|
|
|
get onseeking() {
|
|
return level0.get(this, "onseeking");
|
|
}
|
|
set onseeking(value) {
|
|
level0.set(this, "onseeking", value);
|
|
}
|
|
|
|
get onselect() {
|
|
return level0.get(this, "onselect");
|
|
}
|
|
set onselect(value) {
|
|
level0.set(this, "onselect", value);
|
|
}
|
|
|
|
get onshow() {
|
|
return level0.get(this, "onshow");
|
|
}
|
|
set onshow(value) {
|
|
level0.set(this, "onshow", value);
|
|
}
|
|
|
|
get onstalled() {
|
|
return level0.get(this, "onstalled");
|
|
}
|
|
set onstalled(value) {
|
|
level0.set(this, "onstalled", value);
|
|
}
|
|
|
|
get onsubmit() {
|
|
return level0.get(this, "onsubmit");
|
|
}
|
|
set onsubmit(value) {
|
|
level0.set(this, "onsubmit", value);
|
|
}
|
|
|
|
get onsuspend() {
|
|
return level0.get(this, "onsuspend");
|
|
}
|
|
set onsuspend(value) {
|
|
level0.set(this, "onsuspend", value);
|
|
}
|
|
|
|
get ontimeupdate() {
|
|
return level0.get(this, "ontimeupdate");
|
|
}
|
|
set ontimeupdate(value) {
|
|
level0.set(this, "ontimeupdate", value);
|
|
}
|
|
|
|
get ontoggle() {
|
|
return level0.get(this, "ontoggle");
|
|
}
|
|
set ontoggle(value) {
|
|
level0.set(this, "ontoggle", value);
|
|
}
|
|
|
|
get onvolumechange() {
|
|
return level0.get(this, "onvolumechange");
|
|
}
|
|
set onvolumechange(value) {
|
|
level0.set(this, "onvolumechange", value);
|
|
}
|
|
|
|
get onwaiting() {
|
|
return level0.get(this, "onwaiting");
|
|
}
|
|
set onwaiting(value) {
|
|
level0.set(this, "onwaiting", value);
|
|
}
|
|
|
|
get onauxclick() {
|
|
return level0.get(this, "onauxclick");
|
|
}
|
|
set onauxclick(value) {
|
|
level0.set(this, "onauxclick", value);
|
|
}
|
|
|
|
get ongotpointercapture() {
|
|
return level0.get(this, "ongotpointercapture");
|
|
}
|
|
set ongotpointercapture(value) {
|
|
level0.set(this, "ongotpointercapture", value);
|
|
}
|
|
|
|
get onlostpointercapture() {
|
|
return level0.get(this, "onlostpointercapture");
|
|
}
|
|
set onlostpointercapture(value) {
|
|
level0.set(this, "onlostpointercapture", value);
|
|
}
|
|
|
|
get onpointercancel() {
|
|
return level0.get(this, "onpointercancel");
|
|
}
|
|
set onpointercancel(value) {
|
|
level0.set(this, "onpointercancel", value);
|
|
}
|
|
|
|
get onpointerdown() {
|
|
return level0.get(this, "onpointerdown");
|
|
}
|
|
set onpointerdown(value) {
|
|
level0.set(this, "onpointerdown", value);
|
|
}
|
|
|
|
get onpointerenter() {
|
|
return level0.get(this, "onpointerenter");
|
|
}
|
|
set onpointerenter(value) {
|
|
level0.set(this, "onpointerenter", value);
|
|
}
|
|
|
|
get onpointerleave() {
|
|
return level0.get(this, "onpointerleave");
|
|
}
|
|
set onpointerleave(value) {
|
|
level0.set(this, "onpointerleave", value);
|
|
}
|
|
|
|
get onpointermove() {
|
|
return level0.get(this, "onpointermove");
|
|
}
|
|
set onpointermove(value) {
|
|
level0.set(this, "onpointermove", value);
|
|
}
|
|
|
|
get onpointerout() {
|
|
return level0.get(this, "onpointerout");
|
|
}
|
|
set onpointerout(value) {
|
|
level0.set(this, "onpointerout", value);
|
|
}
|
|
|
|
get onpointerover() {
|
|
return level0.get(this, "onpointerover");
|
|
}
|
|
set onpointerover(value) {
|
|
level0.set(this, "onpointerover", value);
|
|
}
|
|
|
|
get onpointerup() {
|
|
return level0.get(this, "onpointerup");
|
|
}
|
|
set onpointerup(value) {
|
|
level0.set(this, "onpointerup", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
const tagName$h = "template";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTemplateElement
|
|
*/
|
|
class HTMLTemplateElement extends HTMLElement {
|
|
constructor(ownerDocument) {
|
|
super(ownerDocument, tagName$h);
|
|
const content = this.ownerDocument.createDocumentFragment();
|
|
(this[CONTENT] = content)[PRIVATE] = this;
|
|
}
|
|
|
|
get content() {
|
|
if (this.hasChildNodes() && !this[CONTENT].hasChildNodes()) {
|
|
for (const node of this.childNodes)
|
|
this[CONTENT].appendChild(node.cloneNode(true));
|
|
}
|
|
return this[CONTENT];
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$h, HTMLTemplateElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLHtmlElement
|
|
*/
|
|
class HTMLHtmlElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "html") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const { toString } = HTMLElement.prototype;
|
|
|
|
class TextElement extends HTMLElement {
|
|
get innerHTML() {
|
|
return this.textContent;
|
|
}
|
|
set innerHTML(html) {
|
|
this.textContent = html;
|
|
}
|
|
|
|
toString() {
|
|
const outerHTML = toString.call(this.cloneNode());
|
|
return outerHTML.replace("><", () => `>${this.textContent}<`);
|
|
}
|
|
}
|
|
|
|
const tagName$g = "script";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLScriptElement
|
|
*/
|
|
class HTMLScriptElement extends TextElement {
|
|
constructor(ownerDocument, localName = tagName$g) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
get type() {
|
|
return stringAttribute.get(this, "type");
|
|
}
|
|
set type(value) {
|
|
stringAttribute.set(this, "type", value);
|
|
}
|
|
|
|
get src() {
|
|
return stringAttribute.get(this, "src");
|
|
}
|
|
set src(value) {
|
|
stringAttribute.set(this, "src", value);
|
|
}
|
|
|
|
get defer() {
|
|
return booleanAttribute.get(this, "defer");
|
|
}
|
|
|
|
set defer(value) {
|
|
booleanAttribute.set(this, "defer", value);
|
|
}
|
|
|
|
get crossOrigin() {
|
|
return stringAttribute.get(this, "crossorigin");
|
|
}
|
|
set crossOrigin(value) {
|
|
stringAttribute.set(this, "crossorigin", value);
|
|
}
|
|
|
|
get nomodule() {
|
|
return booleanAttribute.get(this, "nomodule");
|
|
}
|
|
set nomodule(value) {
|
|
booleanAttribute.set(this, "nomodule", value);
|
|
}
|
|
|
|
get referrerPolicy() {
|
|
return stringAttribute.get(this, "referrerpolicy");
|
|
}
|
|
set referrerPolicy(value) {
|
|
stringAttribute.set(this, "referrerpolicy", value);
|
|
}
|
|
|
|
get nonce() {
|
|
return stringAttribute.get(this, "nonce");
|
|
}
|
|
set nonce(value) {
|
|
stringAttribute.set(this, "nonce", value);
|
|
}
|
|
|
|
get async() {
|
|
return booleanAttribute.get(this, "async");
|
|
}
|
|
set async(value) {
|
|
booleanAttribute.set(this, "async", value);
|
|
}
|
|
|
|
get text() {
|
|
return this.textContent;
|
|
}
|
|
set text(content) {
|
|
this.textContent = content;
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$g, HTMLScriptElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLFrameElement
|
|
*/
|
|
class HTMLFrameElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "frame") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$f = "iframe";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLIFrameElement
|
|
*/
|
|
class HTMLIFrameElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$f) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get src() {
|
|
return stringAttribute.get(this, "src");
|
|
}
|
|
set src(value) {
|
|
stringAttribute.set(this, "src", value);
|
|
}
|
|
|
|
get srcdoc() {
|
|
return stringAttribute.get(this, "srcdoc");
|
|
}
|
|
set srcdoc(value) {
|
|
stringAttribute.set(this, "srcdoc", value);
|
|
}
|
|
|
|
get name() {
|
|
return stringAttribute.get(this, "name");
|
|
}
|
|
set name(value) {
|
|
stringAttribute.set(this, "name", value);
|
|
}
|
|
|
|
get allow() {
|
|
return stringAttribute.get(this, "allow");
|
|
}
|
|
set allow(value) {
|
|
stringAttribute.set(this, "allow", value);
|
|
}
|
|
|
|
get allowFullscreen() {
|
|
return booleanAttribute.get(this, "allowfullscreen");
|
|
}
|
|
set allowFullscreen(value) {
|
|
booleanAttribute.set(this, "allowfullscreen", value);
|
|
}
|
|
|
|
get referrerPolicy() {
|
|
return stringAttribute.get(this, "referrerpolicy");
|
|
}
|
|
set referrerPolicy(value) {
|
|
stringAttribute.set(this, "referrerpolicy", value);
|
|
}
|
|
|
|
get loading() {
|
|
return stringAttribute.get(this, "loading");
|
|
}
|
|
set loading(value) {
|
|
stringAttribute.set(this, "loading", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$f, HTMLIFrameElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLObjectElement
|
|
*/
|
|
class HTMLObjectElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "object") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLHeadElement
|
|
*/
|
|
class HTMLHeadElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "head") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLBodyElement
|
|
*/
|
|
class HTMLBodyElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "body") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
var lib = {};
|
|
|
|
var CSSStyleDeclaration = {};
|
|
|
|
var parse$1 = {};
|
|
|
|
var CSSStyleSheet = {};
|
|
|
|
var StyleSheet = {};
|
|
|
|
var hasRequiredStyleSheet;
|
|
|
|
function requireStyleSheet() {
|
|
if (hasRequiredStyleSheet) return StyleSheet;
|
|
hasRequiredStyleSheet = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#the-stylesheet-interface
|
|
*/
|
|
CSSOM.StyleSheet = function StyleSheet() {
|
|
this.parentStyleSheet = null;
|
|
};
|
|
|
|
//.CommonJS
|
|
StyleSheet.StyleSheet = CSSOM.StyleSheet;
|
|
///CommonJS
|
|
return StyleSheet;
|
|
}
|
|
|
|
var CSSStyleRule = {};
|
|
|
|
var CSSRule = {};
|
|
|
|
var hasRequiredCSSRule;
|
|
|
|
function requireCSSRule() {
|
|
if (hasRequiredCSSRule) return CSSRule;
|
|
hasRequiredCSSRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#the-cssrule-interface
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSRule
|
|
*/
|
|
CSSOM.CSSRule = function CSSRule() {
|
|
this.parentRule = null;
|
|
this.parentStyleSheet = null;
|
|
};
|
|
|
|
CSSOM.CSSRule.UNKNOWN_RULE = 0; // obsolete
|
|
CSSOM.CSSRule.STYLE_RULE = 1;
|
|
CSSOM.CSSRule.CHARSET_RULE = 2; // obsolete
|
|
CSSOM.CSSRule.IMPORT_RULE = 3;
|
|
CSSOM.CSSRule.MEDIA_RULE = 4;
|
|
CSSOM.CSSRule.FONT_FACE_RULE = 5;
|
|
CSSOM.CSSRule.PAGE_RULE = 6;
|
|
CSSOM.CSSRule.KEYFRAMES_RULE = 7;
|
|
CSSOM.CSSRule.KEYFRAME_RULE = 8;
|
|
CSSOM.CSSRule.MARGIN_RULE = 9;
|
|
CSSOM.CSSRule.NAMESPACE_RULE = 10;
|
|
CSSOM.CSSRule.COUNTER_STYLE_RULE = 11;
|
|
CSSOM.CSSRule.SUPPORTS_RULE = 12;
|
|
CSSOM.CSSRule.DOCUMENT_RULE = 13;
|
|
CSSOM.CSSRule.FONT_FEATURE_VALUES_RULE = 14;
|
|
CSSOM.CSSRule.VIEWPORT_RULE = 15;
|
|
CSSOM.CSSRule.REGION_STYLE_RULE = 16;
|
|
|
|
CSSOM.CSSRule.prototype = {
|
|
constructor: CSSOM.CSSRule
|
|
//FIXME
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSRule.CSSRule = CSSOM.CSSRule;
|
|
///CommonJS
|
|
return CSSRule;
|
|
}
|
|
|
|
var hasRequiredCSSStyleRule;
|
|
|
|
function requireCSSStyleRule() {
|
|
if (hasRequiredCSSStyleRule) return CSSStyleRule;
|
|
hasRequiredCSSStyleRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
|
|
CSSRule: requireCSSRule().CSSRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#cssstylerule
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleRule
|
|
*/
|
|
CSSOM.CSSStyleRule = function CSSStyleRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.selectorText = "";
|
|
this.style = new CSSOM.CSSStyleDeclaration();
|
|
this.style.parentRule = this;
|
|
};
|
|
|
|
CSSOM.CSSStyleRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSStyleRule.prototype.constructor = CSSOM.CSSStyleRule;
|
|
CSSOM.CSSStyleRule.prototype.type = 1;
|
|
|
|
Object.defineProperty(CSSOM.CSSStyleRule.prototype, "cssText", {
|
|
get: function () {
|
|
var text;
|
|
if (this.selectorText) {
|
|
text = this.selectorText + " {" + this.style.cssText + "}";
|
|
} else {
|
|
text = "";
|
|
}
|
|
return text;
|
|
},
|
|
set: function (cssText) {
|
|
var rule = CSSOM.CSSStyleRule.parse(cssText);
|
|
this.style = rule.style;
|
|
this.selectorText = rule.selectorText;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* NON-STANDARD
|
|
* lightweight version of parse.js.
|
|
* @param {string} ruleText
|
|
* @return CSSStyleRule
|
|
*/
|
|
CSSOM.CSSStyleRule.parse = function (ruleText) {
|
|
var i = 0;
|
|
var state = "selector";
|
|
var index;
|
|
var j = i;
|
|
var buffer = "";
|
|
|
|
var SIGNIFICANT_WHITESPACE = {
|
|
selector: true,
|
|
value: true
|
|
};
|
|
|
|
var styleRule = new CSSOM.CSSStyleRule();
|
|
var name,
|
|
priority = "";
|
|
|
|
for (var character; (character = ruleText.charAt(i)); i++) {
|
|
switch (character) {
|
|
case " ":
|
|
case "\t":
|
|
case "\r":
|
|
case "\n":
|
|
case "\f":
|
|
if (SIGNIFICANT_WHITESPACE[state]) {
|
|
// Squash 2 or more white-spaces in the row into 1
|
|
switch (ruleText.charAt(i - 1)) {
|
|
case " ":
|
|
case "\t":
|
|
case "\r":
|
|
case "\n":
|
|
case "\f":
|
|
break;
|
|
default:
|
|
buffer += " ";
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// String
|
|
case '"':
|
|
j = i + 1;
|
|
index = ruleText.indexOf('"', j) + 1;
|
|
if (!index) {
|
|
throw '" is missing';
|
|
}
|
|
buffer += ruleText.slice(i, index);
|
|
i = index - 1;
|
|
break;
|
|
|
|
case "'":
|
|
j = i + 1;
|
|
index = ruleText.indexOf("'", j) + 1;
|
|
if (!index) {
|
|
throw "' is missing";
|
|
}
|
|
buffer += ruleText.slice(i, index);
|
|
i = index - 1;
|
|
break;
|
|
|
|
// Comment
|
|
case "/":
|
|
if (ruleText.charAt(i + 1) === "*") {
|
|
i += 2;
|
|
index = ruleText.indexOf("*/", i);
|
|
if (index === -1) {
|
|
throw new SyntaxError("Missing */");
|
|
} else {
|
|
i = index + 1;
|
|
}
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "{":
|
|
if (state === "selector") {
|
|
styleRule.selectorText = buffer.trim();
|
|
buffer = "";
|
|
state = "name";
|
|
}
|
|
break;
|
|
|
|
case ":":
|
|
if (state === "name") {
|
|
name = buffer.trim();
|
|
buffer = "";
|
|
state = "value";
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "!":
|
|
if (state === "value" && ruleText.indexOf("!important", i) === i) {
|
|
priority = "important";
|
|
i += "important".length;
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case ";":
|
|
if (state === "value") {
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
buffer = "";
|
|
state = "name";
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "}":
|
|
if (state === "value") {
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
buffer = "";
|
|
} else if (state === "name") {
|
|
break;
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
state = "selector";
|
|
break;
|
|
|
|
default:
|
|
buffer += character;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return styleRule;
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSStyleRule.CSSStyleRule = CSSOM.CSSStyleRule;
|
|
///CommonJS
|
|
return CSSStyleRule;
|
|
}
|
|
|
|
var hasRequiredCSSStyleSheet;
|
|
|
|
function requireCSSStyleSheet() {
|
|
if (hasRequiredCSSStyleSheet) return CSSStyleSheet;
|
|
hasRequiredCSSStyleSheet = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
StyleSheet: requireStyleSheet().StyleSheet,
|
|
CSSStyleRule: requireCSSStyleRule().CSSStyleRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet
|
|
*/
|
|
CSSOM.CSSStyleSheet = function CSSStyleSheet() {
|
|
CSSOM.StyleSheet.call(this);
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSStyleSheet.prototype = new CSSOM.StyleSheet();
|
|
CSSOM.CSSStyleSheet.prototype.constructor = CSSOM.CSSStyleSheet;
|
|
|
|
/**
|
|
* Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade.
|
|
*
|
|
* sheet = new Sheet("body {margin: 0}")
|
|
* sheet.toString()
|
|
* -> "body{margin:0;}"
|
|
* sheet.insertRule("img {border: none}", 0)
|
|
* -> 0
|
|
* sheet.toString()
|
|
* -> "img{border:none;}body{margin:0;}"
|
|
*
|
|
* @param {string} rule
|
|
* @param {number} index
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-insertRule
|
|
* @return {number} The index within the style sheet's rule collection of the newly inserted rule.
|
|
*/
|
|
CSSOM.CSSStyleSheet.prototype.insertRule = function (rule, index) {
|
|
if (index < 0 || index > this.cssRules.length) {
|
|
throw new RangeError("INDEX_SIZE_ERR");
|
|
}
|
|
var cssRule = CSSOM.parse(rule).cssRules[0];
|
|
cssRule.parentStyleSheet = this;
|
|
this.cssRules.splice(index, 0, cssRule);
|
|
return index;
|
|
};
|
|
|
|
/**
|
|
* Used to delete a rule from the style sheet.
|
|
*
|
|
* sheet = new Sheet("img{border:none} body{margin:0}")
|
|
* sheet.toString()
|
|
* -> "img{border:none;}body{margin:0;}"
|
|
* sheet.deleteRule(0)
|
|
* sheet.toString()
|
|
* -> "body{margin:0;}"
|
|
*
|
|
* @param {number} index within the style sheet's rule list of the rule to remove.
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleSheet-deleteRule
|
|
*/
|
|
CSSOM.CSSStyleSheet.prototype.deleteRule = function (index) {
|
|
if (index < 0 || index >= this.cssRules.length) {
|
|
throw new RangeError("INDEX_SIZE_ERR");
|
|
}
|
|
this.cssRules.splice(index, 1);
|
|
};
|
|
|
|
/**
|
|
* NON-STANDARD
|
|
* @return {string} serialize stylesheet
|
|
*/
|
|
CSSOM.CSSStyleSheet.prototype.toString = function () {
|
|
var result = "";
|
|
var rules = this.cssRules;
|
|
for (var i = 0; i < rules.length; i++) {
|
|
result += rules[i].cssText + "\n";
|
|
}
|
|
return result;
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSStyleSheet.CSSStyleSheet = CSSOM.CSSStyleSheet;
|
|
CSSOM.parse = requireParse().parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleSheet.js
|
|
///CommonJS
|
|
return CSSStyleSheet;
|
|
}
|
|
|
|
var CSSImportRule = {};
|
|
|
|
var MediaList = {};
|
|
|
|
var hasRequiredMediaList;
|
|
|
|
function requireMediaList() {
|
|
if (hasRequiredMediaList) return MediaList;
|
|
hasRequiredMediaList = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#the-medialist-interface
|
|
*/
|
|
CSSOM.MediaList = function MediaList() {
|
|
this.length = 0;
|
|
};
|
|
|
|
CSSOM.MediaList.prototype = {
|
|
constructor: CSSOM.MediaList,
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
get mediaText() {
|
|
return Array.prototype.join.call(this, ", ");
|
|
},
|
|
|
|
/**
|
|
* @param {string} value
|
|
*/
|
|
set mediaText(value) {
|
|
var values = value.split(",");
|
|
var length = (this.length = values.length);
|
|
for (var i = 0; i < length; i++) {
|
|
this[i] = values[i].trim();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} medium
|
|
*/
|
|
appendMedium: function (medium) {
|
|
if (Array.prototype.indexOf.call(this, medium) === -1) {
|
|
this[this.length] = medium;
|
|
this.length++;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} medium
|
|
*/
|
|
deleteMedium: function (medium) {
|
|
var index = Array.prototype.indexOf.call(this, medium);
|
|
if (index !== -1) {
|
|
Array.prototype.splice.call(this, index, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
//.CommonJS
|
|
MediaList.MediaList = CSSOM.MediaList;
|
|
///CommonJS
|
|
return MediaList;
|
|
}
|
|
|
|
var hasRequiredCSSImportRule;
|
|
|
|
function requireCSSImportRule() {
|
|
if (hasRequiredCSSImportRule) return CSSImportRule;
|
|
hasRequiredCSSImportRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSStyleSheet: requireCSSStyleSheet().CSSStyleSheet,
|
|
MediaList: requireMediaList().MediaList
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#cssimportrule
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSImportRule
|
|
*/
|
|
CSSOM.CSSImportRule = function CSSImportRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.href = "";
|
|
this.media = new CSSOM.MediaList();
|
|
this.styleSheet = new CSSOM.CSSStyleSheet();
|
|
};
|
|
|
|
CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
|
|
CSSOM.CSSImportRule.prototype.type = 3;
|
|
|
|
Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
|
|
get: function () {
|
|
var mediaText = this.media.mediaText;
|
|
return (
|
|
"@import url(" +
|
|
this.href +
|
|
")" +
|
|
(mediaText ? " " + mediaText : "") +
|
|
";"
|
|
);
|
|
},
|
|
set: function (cssText) {
|
|
var i = 0;
|
|
|
|
/**
|
|
* @import url(partial.css) screen, handheld;
|
|
* || |
|
|
* after-import media
|
|
* |
|
|
* url
|
|
*/
|
|
var state = "";
|
|
|
|
var buffer = "";
|
|
var index;
|
|
for (var character; (character = cssText.charAt(i)); i++) {
|
|
switch (character) {
|
|
case " ":
|
|
case "\t":
|
|
case "\r":
|
|
case "\n":
|
|
case "\f":
|
|
if (state === "after-import") {
|
|
state = "url";
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "@":
|
|
if (!state && cssText.indexOf("@import", i) === i) {
|
|
state = "after-import";
|
|
i += "import".length;
|
|
buffer = "";
|
|
}
|
|
break;
|
|
|
|
case "u":
|
|
if (state === "url" && cssText.indexOf("url(", i) === i) {
|
|
index = cssText.indexOf(")", i + 1);
|
|
if (index === -1) {
|
|
throw i + ': ")" not found';
|
|
}
|
|
i += "url(".length;
|
|
var url = cssText.slice(i, index);
|
|
if (url[0] === url[url.length - 1]) {
|
|
if (url[0] === '"' || url[0] === "'") {
|
|
url = url.slice(1, -1);
|
|
}
|
|
}
|
|
this.href = url;
|
|
i = index;
|
|
state = "media";
|
|
}
|
|
break;
|
|
|
|
case '"':
|
|
if (state === "url") {
|
|
index = cssText.indexOf('"', i + 1);
|
|
if (!index) {
|
|
throw i + ": '\"' not found";
|
|
}
|
|
this.href = cssText.slice(i + 1, index);
|
|
i = index;
|
|
state = "media";
|
|
}
|
|
break;
|
|
|
|
case "'":
|
|
if (state === "url") {
|
|
index = cssText.indexOf("'", i + 1);
|
|
if (!index) {
|
|
throw i + ': "\'" not found';
|
|
}
|
|
this.href = cssText.slice(i + 1, index);
|
|
i = index;
|
|
state = "media";
|
|
}
|
|
break;
|
|
|
|
case ";":
|
|
if (state === "media") {
|
|
if (buffer) {
|
|
this.media.mediaText = buffer.trim();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (state === "media") {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSImportRule.CSSImportRule = CSSOM.CSSImportRule;
|
|
///CommonJS
|
|
return CSSImportRule;
|
|
}
|
|
|
|
var CSSGroupingRule = {};
|
|
|
|
var hasRequiredCSSGroupingRule;
|
|
|
|
function requireCSSGroupingRule() {
|
|
if (hasRequiredCSSGroupingRule) return CSSGroupingRule;
|
|
hasRequiredCSSGroupingRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see https://drafts.csswg.org/cssom/#the-cssgroupingrule-interface
|
|
*/
|
|
CSSOM.CSSGroupingRule = function CSSGroupingRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSGroupingRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSGroupingRule.prototype.constructor = CSSOM.CSSGroupingRule;
|
|
|
|
/**
|
|
* Used to insert a new CSS rule to a list of CSS rules.
|
|
*
|
|
* @example
|
|
* cssGroupingRule.cssText
|
|
* -> "body{margin:0;}"
|
|
* cssGroupingRule.insertRule("img{border:none;}", 1)
|
|
* -> 1
|
|
* cssGroupingRule.cssText
|
|
* -> "body{margin:0;}img{border:none;}"
|
|
*
|
|
* @param {string} rule
|
|
* @param {number} [index]
|
|
* @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-insertrule
|
|
* @return {number} The index within the grouping rule's collection of the newly inserted rule.
|
|
*/
|
|
CSSOM.CSSGroupingRule.prototype.insertRule = function insertRule(
|
|
rule,
|
|
index
|
|
) {
|
|
if (index < 0 || index > this.cssRules.length) {
|
|
throw new RangeError("INDEX_SIZE_ERR");
|
|
}
|
|
var cssRule = CSSOM.parse(rule).cssRules[0];
|
|
cssRule.parentRule = this;
|
|
this.cssRules.splice(index, 0, cssRule);
|
|
return index;
|
|
};
|
|
|
|
/**
|
|
* Used to delete a rule from the grouping rule.
|
|
*
|
|
* cssGroupingRule.cssText
|
|
* -> "img{border:none;}body{margin:0;}"
|
|
* cssGroupingRule.deleteRule(0)
|
|
* cssGroupingRule.cssText
|
|
* -> "body{margin:0;}"
|
|
*
|
|
* @param {number} index within the grouping rule's rule list of the rule to remove.
|
|
* @see https://www.w3.org/TR/cssom-1/#dom-cssgroupingrule-deleterule
|
|
*/
|
|
CSSOM.CSSGroupingRule.prototype.deleteRule = function deleteRule(index) {
|
|
if (index < 0 || index >= this.cssRules.length) {
|
|
throw new RangeError("INDEX_SIZE_ERR");
|
|
}
|
|
this.cssRules.splice(index, 1)[0].parentRule = null;
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSGroupingRule.CSSGroupingRule = CSSOM.CSSGroupingRule;
|
|
///CommonJS
|
|
return CSSGroupingRule;
|
|
}
|
|
|
|
var CSSMediaRule = {};
|
|
|
|
var CSSConditionRule = {};
|
|
|
|
var hasRequiredCSSConditionRule;
|
|
|
|
function requireCSSConditionRule() {
|
|
if (hasRequiredCSSConditionRule) return CSSConditionRule;
|
|
hasRequiredCSSConditionRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see https://www.w3.org/TR/css-conditional-3/#the-cssconditionrule-interface
|
|
*/
|
|
CSSOM.CSSConditionRule = function CSSConditionRule() {
|
|
CSSOM.CSSGroupingRule.call(this);
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
|
|
CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
|
|
CSSOM.CSSConditionRule.prototype.conditionText = "";
|
|
CSSOM.CSSConditionRule.prototype.cssText = "";
|
|
|
|
//.CommonJS
|
|
CSSConditionRule.CSSConditionRule = CSSOM.CSSConditionRule;
|
|
///CommonJS
|
|
return CSSConditionRule;
|
|
}
|
|
|
|
var hasRequiredCSSMediaRule;
|
|
|
|
function requireCSSMediaRule() {
|
|
if (hasRequiredCSSMediaRule) return CSSMediaRule;
|
|
hasRequiredCSSMediaRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
|
|
CSSConditionRule: requireCSSConditionRule().CSSConditionRule,
|
|
MediaList: requireMediaList().MediaList
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#cssmediarule
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSMediaRule
|
|
*/
|
|
CSSOM.CSSMediaRule = function CSSMediaRule() {
|
|
CSSOM.CSSConditionRule.call(this);
|
|
this.media = new CSSOM.MediaList();
|
|
};
|
|
|
|
CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
|
|
CSSOM.CSSMediaRule.prototype.constructor = CSSOM.CSSMediaRule;
|
|
CSSOM.CSSMediaRule.prototype.type = 4;
|
|
|
|
// https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
|
|
Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
|
|
conditionText: {
|
|
get: function () {
|
|
return this.media.mediaText;
|
|
},
|
|
set: function (value) {
|
|
this.media.mediaText = value;
|
|
},
|
|
configurable: true,
|
|
enumerable: true
|
|
},
|
|
cssText: {
|
|
get: function () {
|
|
var cssTexts = [];
|
|
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
cssTexts.push(this.cssRules[i].cssText);
|
|
}
|
|
return (
|
|
"@media " + this.media.mediaText + " {" + cssTexts.join("") + "}"
|
|
);
|
|
},
|
|
configurable: true,
|
|
enumerable: true
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSMediaRule.CSSMediaRule = CSSOM.CSSMediaRule;
|
|
///CommonJS
|
|
return CSSMediaRule;
|
|
}
|
|
|
|
var CSSSupportsRule = {};
|
|
|
|
var hasRequiredCSSSupportsRule;
|
|
|
|
function requireCSSSupportsRule() {
|
|
if (hasRequiredCSSSupportsRule) return CSSSupportsRule;
|
|
hasRequiredCSSSupportsRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
|
|
CSSConditionRule: requireCSSConditionRule().CSSConditionRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see https://drafts.csswg.org/css-conditional-3/#the-csssupportsrule-interface
|
|
*/
|
|
CSSOM.CSSSupportsRule = function CSSSupportsRule() {
|
|
CSSOM.CSSConditionRule.call(this);
|
|
};
|
|
|
|
CSSOM.CSSSupportsRule.prototype = new CSSOM.CSSConditionRule();
|
|
CSSOM.CSSSupportsRule.prototype.constructor = CSSOM.CSSSupportsRule;
|
|
CSSOM.CSSSupportsRule.prototype.type = 12;
|
|
|
|
Object.defineProperty(CSSOM.CSSSupportsRule.prototype, "cssText", {
|
|
get: function () {
|
|
var cssTexts = [];
|
|
|
|
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
cssTexts.push(this.cssRules[i].cssText);
|
|
}
|
|
|
|
return "@supports " + this.conditionText + " {" + cssTexts.join("") + "}";
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSSupportsRule.CSSSupportsRule = CSSOM.CSSSupportsRule;
|
|
///CommonJS
|
|
return CSSSupportsRule;
|
|
}
|
|
|
|
var CSSFontFaceRule = {};
|
|
|
|
var hasRequiredCSSFontFaceRule;
|
|
|
|
function requireCSSFontFaceRule() {
|
|
if (hasRequiredCSSFontFaceRule) return CSSFontFaceRule;
|
|
hasRequiredCSSFontFaceRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
|
|
CSSRule: requireCSSRule().CSSRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://dev.w3.org/csswg/cssom/#css-font-face-rule
|
|
*/
|
|
CSSOM.CSSFontFaceRule = function CSSFontFaceRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.style = new CSSOM.CSSStyleDeclaration();
|
|
this.style.parentRule = this;
|
|
};
|
|
|
|
CSSOM.CSSFontFaceRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSFontFaceRule.prototype.constructor = CSSOM.CSSFontFaceRule;
|
|
CSSOM.CSSFontFaceRule.prototype.type = 5;
|
|
//FIXME
|
|
//CSSOM.CSSFontFaceRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
|
|
//CSSOM.CSSFontFaceRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
|
|
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSFontFaceRule.cpp
|
|
Object.defineProperty(CSSOM.CSSFontFaceRule.prototype, "cssText", {
|
|
get: function () {
|
|
return "@font-face {" + this.style.cssText + "}";
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSFontFaceRule.CSSFontFaceRule = CSSOM.CSSFontFaceRule;
|
|
///CommonJS
|
|
return CSSFontFaceRule;
|
|
}
|
|
|
|
var CSSHostRule = {};
|
|
|
|
var hasRequiredCSSHostRule;
|
|
|
|
function requireCSSHostRule() {
|
|
if (hasRequiredCSSHostRule) return CSSHostRule;
|
|
hasRequiredCSSHostRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/shadow-dom/#host-at-rule
|
|
*/
|
|
CSSOM.CSSHostRule = function CSSHostRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSHostRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSHostRule.prototype.constructor = CSSOM.CSSHostRule;
|
|
CSSOM.CSSHostRule.prototype.type = 1001;
|
|
//FIXME
|
|
//CSSOM.CSSHostRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
|
|
//CSSOM.CSSHostRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
|
|
Object.defineProperty(CSSOM.CSSHostRule.prototype, "cssText", {
|
|
get: function () {
|
|
var cssTexts = [];
|
|
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
cssTexts.push(this.cssRules[i].cssText);
|
|
}
|
|
return "@host {" + cssTexts.join("") + "}";
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSHostRule.CSSHostRule = CSSOM.CSSHostRule;
|
|
///CommonJS
|
|
return CSSHostRule;
|
|
}
|
|
|
|
var CSSKeyframeRule = {};
|
|
|
|
var hasRequiredCSSKeyframeRule;
|
|
|
|
function requireCSSKeyframeRule() {
|
|
if (hasRequiredCSSKeyframeRule) return CSSKeyframeRule;
|
|
hasRequiredCSSKeyframeRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframeRule
|
|
*/
|
|
CSSOM.CSSKeyframeRule = function CSSKeyframeRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.keyText = "";
|
|
this.style = new CSSOM.CSSStyleDeclaration();
|
|
this.style.parentRule = this;
|
|
};
|
|
|
|
CSSOM.CSSKeyframeRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSKeyframeRule.prototype.constructor = CSSOM.CSSKeyframeRule;
|
|
CSSOM.CSSKeyframeRule.prototype.type = 8;
|
|
//FIXME
|
|
//CSSOM.CSSKeyframeRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
|
|
//CSSOM.CSSKeyframeRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
|
|
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframeRule.cpp
|
|
Object.defineProperty(CSSOM.CSSKeyframeRule.prototype, "cssText", {
|
|
get: function () {
|
|
return this.keyText + " {" + this.style.cssText + "} ";
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSKeyframeRule.CSSKeyframeRule = CSSOM.CSSKeyframeRule;
|
|
///CommonJS
|
|
return CSSKeyframeRule;
|
|
}
|
|
|
|
var CSSKeyframesRule = {};
|
|
|
|
var hasRequiredCSSKeyframesRule;
|
|
|
|
function requireCSSKeyframesRule() {
|
|
if (hasRequiredCSSKeyframesRule) return CSSKeyframesRule;
|
|
hasRequiredCSSKeyframesRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/css3-animations/#DOM-CSSKeyframesRule
|
|
*/
|
|
CSSOM.CSSKeyframesRule = function CSSKeyframesRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.name = "";
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSKeyframesRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSKeyframesRule.prototype.constructor = CSSOM.CSSKeyframesRule;
|
|
CSSOM.CSSKeyframesRule.prototype.type = 7;
|
|
//FIXME
|
|
//CSSOM.CSSKeyframesRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
|
|
//CSSOM.CSSKeyframesRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
|
|
// http://www.opensource.apple.com/source/WebCore/WebCore-955.66.1/css/WebKitCSSKeyframesRule.cpp
|
|
Object.defineProperty(CSSOM.CSSKeyframesRule.prototype, "cssText", {
|
|
get: function () {
|
|
var cssTexts = [];
|
|
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
cssTexts.push(" " + this.cssRules[i].cssText);
|
|
}
|
|
return (
|
|
"@" +
|
|
(this._vendorPrefix || "") +
|
|
"keyframes " +
|
|
this.name +
|
|
" { \n" +
|
|
cssTexts.join("\n") +
|
|
"\n}"
|
|
);
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSKeyframesRule.CSSKeyframesRule = CSSOM.CSSKeyframesRule;
|
|
///CommonJS
|
|
return CSSKeyframesRule;
|
|
}
|
|
|
|
var CSSValueExpression = {};
|
|
|
|
var CSSValue = {};
|
|
|
|
var hasRequiredCSSValue;
|
|
|
|
function requireCSSValue() {
|
|
if (hasRequiredCSSValue) return CSSValue;
|
|
hasRequiredCSSValue = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
|
|
*
|
|
* TODO: add if needed
|
|
*/
|
|
CSSOM.CSSValue = function CSSValue() {};
|
|
|
|
CSSOM.CSSValue.prototype = {
|
|
constructor: CSSOM.CSSValue,
|
|
|
|
// @see: http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSValue
|
|
set cssText(text) {
|
|
var name = this._getConstructorName();
|
|
|
|
throw new Error(
|
|
'DOMException: property "cssText" of "' +
|
|
name +
|
|
'" is readonly and can not be replaced with "' +
|
|
text +
|
|
'"!'
|
|
);
|
|
},
|
|
|
|
get cssText() {
|
|
var name = this._getConstructorName();
|
|
|
|
throw new Error('getter "cssText" of "' + name + '" is not implemented!');
|
|
},
|
|
|
|
_getConstructorName: function () {
|
|
var s = this.constructor.toString(),
|
|
c = s.match(/function\s([^\(]+)/),
|
|
name = c[1];
|
|
|
|
return name;
|
|
}
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSValue.CSSValue = CSSOM.CSSValue;
|
|
///CommonJS
|
|
return CSSValue;
|
|
}
|
|
|
|
var hasRequiredCSSValueExpression;
|
|
|
|
function requireCSSValueExpression() {
|
|
if (hasRequiredCSSValueExpression) return CSSValueExpression;
|
|
hasRequiredCSSValueExpression = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSValue: requireCSSValue().CSSValue
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://msdn.microsoft.com/en-us/library/ms537634(v=vs.85).aspx
|
|
*
|
|
*/
|
|
CSSOM.CSSValueExpression = function CSSValueExpression(token, idx) {
|
|
this._token = token;
|
|
this._idx = idx;
|
|
};
|
|
|
|
CSSOM.CSSValueExpression.prototype = new CSSOM.CSSValue();
|
|
CSSOM.CSSValueExpression.prototype.constructor = CSSOM.CSSValueExpression;
|
|
|
|
/**
|
|
* parse css expression() value
|
|
*
|
|
* @return {Object}
|
|
* - error:
|
|
* or
|
|
* - idx:
|
|
* - expression:
|
|
*
|
|
* Example:
|
|
*
|
|
* .selector {
|
|
* zoom: expression(documentElement.clientWidth > 1000 ? '1000px' : 'auto');
|
|
* }
|
|
*/
|
|
CSSOM.CSSValueExpression.prototype.parse = function () {
|
|
var token = this._token,
|
|
idx = this._idx;
|
|
|
|
var character = "",
|
|
expression = "",
|
|
error = "",
|
|
info,
|
|
paren = [];
|
|
|
|
for (; ; ++idx) {
|
|
character = token.charAt(idx);
|
|
|
|
// end of token
|
|
if (character === "") {
|
|
error = "css expression error: unfinished expression!";
|
|
break;
|
|
}
|
|
|
|
switch (character) {
|
|
case "(":
|
|
paren.push(character);
|
|
expression += character;
|
|
break;
|
|
|
|
case ")":
|
|
paren.pop(character);
|
|
expression += character;
|
|
break;
|
|
|
|
case "/":
|
|
if ((info = this._parseJSComment(token, idx))) {
|
|
// comment?
|
|
if (info.error) {
|
|
error = "css expression error: unfinished comment in expression!";
|
|
} else {
|
|
idx = info.idx;
|
|
// ignore the comment
|
|
}
|
|
} else if ((info = this._parseJSRexExp(token, idx))) {
|
|
// regexp
|
|
idx = info.idx;
|
|
expression += info.text;
|
|
} else {
|
|
// other
|
|
expression += character;
|
|
}
|
|
break;
|
|
|
|
case "'":
|
|
case '"':
|
|
info = this._parseJSString(token, idx, character);
|
|
if (info) {
|
|
// string
|
|
idx = info.idx;
|
|
expression += info.text;
|
|
} else {
|
|
expression += character;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
expression += character;
|
|
break;
|
|
}
|
|
|
|
if (error) {
|
|
break;
|
|
}
|
|
|
|
// end of expression
|
|
if (paren.length === 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var ret;
|
|
if (error) {
|
|
ret = {
|
|
error: error
|
|
};
|
|
} else {
|
|
ret = {
|
|
idx: idx,
|
|
expression: expression
|
|
};
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @return {Object|false}
|
|
* - idx:
|
|
* - text:
|
|
* or
|
|
* - error:
|
|
* or
|
|
* false
|
|
*
|
|
*/
|
|
CSSOM.CSSValueExpression.prototype._parseJSComment = function (token, idx) {
|
|
var nextChar = token.charAt(idx + 1),
|
|
text;
|
|
|
|
if (nextChar === "/" || nextChar === "*") {
|
|
var startIdx = idx,
|
|
endIdx,
|
|
commentEndChar;
|
|
|
|
if (nextChar === "/") {
|
|
// line comment
|
|
commentEndChar = "\n";
|
|
} else if (nextChar === "*") {
|
|
// block comment
|
|
commentEndChar = "*/";
|
|
}
|
|
|
|
endIdx = token.indexOf(commentEndChar, startIdx + 1 + 1);
|
|
if (endIdx !== -1) {
|
|
endIdx = endIdx + commentEndChar.length - 1;
|
|
text = token.substring(idx, endIdx + 1);
|
|
return {
|
|
idx: endIdx,
|
|
text: text
|
|
};
|
|
} else {
|
|
var error = "css expression error: unfinished comment in expression!";
|
|
return {
|
|
error: error
|
|
};
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @return {Object|false}
|
|
* - idx:
|
|
* - text:
|
|
* or
|
|
* false
|
|
*
|
|
*/
|
|
CSSOM.CSSValueExpression.prototype._parseJSString = function (
|
|
token,
|
|
idx,
|
|
sep
|
|
) {
|
|
var endIdx = this._findMatchedIdx(token, idx, sep),
|
|
text;
|
|
|
|
if (endIdx === -1) {
|
|
return false;
|
|
} else {
|
|
text = token.substring(idx, endIdx + sep.length);
|
|
|
|
return {
|
|
idx: endIdx,
|
|
text: text
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* parse regexp in css expression
|
|
*
|
|
* @return {Object|false}
|
|
* - idx:
|
|
* - regExp:
|
|
* or
|
|
* false
|
|
*/
|
|
|
|
/*
|
|
|
|
all legal RegExp
|
|
|
|
/a/
|
|
(/a/)
|
|
[/a/]
|
|
[12, /a/]
|
|
|
|
!/a/
|
|
|
|
+/a/
|
|
-/a/
|
|
* /a/
|
|
/ /a/
|
|
%/a/
|
|
|
|
===/a/
|
|
!==/a/
|
|
==/a/
|
|
!=/a/
|
|
>/a/
|
|
>=/a/
|
|
</a/
|
|
<=/a/
|
|
|
|
&/a/
|
|
|/a/
|
|
^/a/
|
|
~/a/
|
|
<</a/
|
|
>>/a/
|
|
>>>/a/
|
|
|
|
&&/a/
|
|
||/a/
|
|
?/a/
|
|
=/a/
|
|
,/a/
|
|
|
|
delete /a/
|
|
in /a/
|
|
instanceof /a/
|
|
new /a/
|
|
typeof /a/
|
|
void /a/
|
|
|
|
*/
|
|
CSSOM.CSSValueExpression.prototype._parseJSRexExp = function (token, idx) {
|
|
var before = token.substring(0, idx).replace(/\s+$/, ""),
|
|
legalRegx = [
|
|
/^$/,
|
|
/\($/,
|
|
/\[$/,
|
|
/\!$/,
|
|
/\+$/,
|
|
/\-$/,
|
|
/\*$/,
|
|
/\/\s+/,
|
|
/\%$/,
|
|
/\=$/,
|
|
/\>$/,
|
|
/<$/,
|
|
/\&$/,
|
|
/\|$/,
|
|
/\^$/,
|
|
/\~$/,
|
|
/\?$/,
|
|
/\,$/,
|
|
/delete$/,
|
|
/in$/,
|
|
/instanceof$/,
|
|
/new$/,
|
|
/typeof$/,
|
|
/void$/
|
|
];
|
|
|
|
var isLegal = legalRegx.some(function (reg) {
|
|
return reg.test(before);
|
|
});
|
|
|
|
if (!isLegal) {
|
|
return false;
|
|
} else {
|
|
var sep = "/";
|
|
|
|
// same logic as string
|
|
return this._parseJSString(token, idx, sep);
|
|
}
|
|
};
|
|
|
|
/**
|
|
*
|
|
* find next sep(same line) index in `token`
|
|
*
|
|
* @return {Number}
|
|
*
|
|
*/
|
|
CSSOM.CSSValueExpression.prototype._findMatchedIdx = function (
|
|
token,
|
|
idx,
|
|
sep
|
|
) {
|
|
var startIdx = idx,
|
|
endIdx;
|
|
|
|
var NOT_FOUND = -1;
|
|
|
|
while (true) {
|
|
endIdx = token.indexOf(sep, startIdx + 1);
|
|
|
|
if (endIdx === -1) {
|
|
// not found
|
|
endIdx = NOT_FOUND;
|
|
break;
|
|
} else {
|
|
var text = token.substring(idx + 1, endIdx),
|
|
matched = text.match(/\\+$/);
|
|
if (!matched || matched[0] % 2 === 0) {
|
|
// not escaped
|
|
break;
|
|
} else {
|
|
startIdx = endIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
// boundary must be in the same line(js sting or regexp)
|
|
var nextNewLineIdx = token.indexOf("\n", idx + 1);
|
|
if (nextNewLineIdx < endIdx) {
|
|
endIdx = NOT_FOUND;
|
|
}
|
|
|
|
return endIdx;
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSValueExpression.CSSValueExpression = CSSOM.CSSValueExpression;
|
|
///CommonJS
|
|
return CSSValueExpression;
|
|
}
|
|
|
|
var CSSDocumentRule = {};
|
|
|
|
var MatcherList = {};
|
|
|
|
var hasRequiredMatcherList;
|
|
|
|
function requireMatcherList() {
|
|
if (hasRequiredMatcherList) return MatcherList;
|
|
hasRequiredMatcherList = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see https://developer.mozilla.org/en/CSS/@-moz-document
|
|
*/
|
|
CSSOM.MatcherList = function MatcherList() {
|
|
this.length = 0;
|
|
};
|
|
|
|
CSSOM.MatcherList.prototype = {
|
|
constructor: CSSOM.MatcherList,
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
get matcherText() {
|
|
return Array.prototype.join.call(this, ", ");
|
|
},
|
|
|
|
/**
|
|
* @param {string} value
|
|
*/
|
|
set matcherText(value) {
|
|
// just a temporary solution, actually it may be wrong by just split the value with ',', because a url can include ','.
|
|
var values = value.split(",");
|
|
var length = (this.length = values.length);
|
|
for (var i = 0; i < length; i++) {
|
|
this[i] = values[i].trim();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} matcher
|
|
*/
|
|
appendMatcher: function (matcher) {
|
|
if (Array.prototype.indexOf.call(this, matcher) === -1) {
|
|
this[this.length] = matcher;
|
|
this.length++;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @param {string} matcher
|
|
*/
|
|
deleteMatcher: function (matcher) {
|
|
var index = Array.prototype.indexOf.call(this, matcher);
|
|
if (index !== -1) {
|
|
Array.prototype.splice.call(this, index, 1);
|
|
}
|
|
}
|
|
};
|
|
|
|
//.CommonJS
|
|
MatcherList.MatcherList = CSSOM.MatcherList;
|
|
///CommonJS
|
|
return MatcherList;
|
|
}
|
|
|
|
var hasRequiredCSSDocumentRule;
|
|
|
|
function requireCSSDocumentRule() {
|
|
if (hasRequiredCSSDocumentRule) return CSSDocumentRule;
|
|
hasRequiredCSSDocumentRule = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
MatcherList: requireMatcherList().MatcherList
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see https://developer.mozilla.org/en/CSS/@-moz-document
|
|
*/
|
|
CSSOM.CSSDocumentRule = function CSSDocumentRule() {
|
|
CSSOM.CSSRule.call(this);
|
|
this.matcher = new CSSOM.MatcherList();
|
|
this.cssRules = [];
|
|
};
|
|
|
|
CSSOM.CSSDocumentRule.prototype = new CSSOM.CSSRule();
|
|
CSSOM.CSSDocumentRule.prototype.constructor = CSSOM.CSSDocumentRule;
|
|
CSSOM.CSSDocumentRule.prototype.type = 10;
|
|
//FIXME
|
|
//CSSOM.CSSDocumentRule.prototype.insertRule = CSSStyleSheet.prototype.insertRule;
|
|
//CSSOM.CSSDocumentRule.prototype.deleteRule = CSSStyleSheet.prototype.deleteRule;
|
|
|
|
Object.defineProperty(CSSOM.CSSDocumentRule.prototype, "cssText", {
|
|
get: function () {
|
|
var cssTexts = [];
|
|
for (var i = 0, length = this.cssRules.length; i < length; i++) {
|
|
cssTexts.push(this.cssRules[i].cssText);
|
|
}
|
|
return (
|
|
"@-moz-document " +
|
|
this.matcher.matcherText +
|
|
" {" +
|
|
cssTexts.join("") +
|
|
"}"
|
|
);
|
|
}
|
|
});
|
|
|
|
//.CommonJS
|
|
CSSDocumentRule.CSSDocumentRule = CSSOM.CSSDocumentRule;
|
|
///CommonJS
|
|
return CSSDocumentRule;
|
|
}
|
|
|
|
var hasRequiredParse;
|
|
|
|
function requireParse() {
|
|
if (hasRequiredParse) return parse$1;
|
|
hasRequiredParse = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @param {string} token
|
|
*/
|
|
CSSOM.parse = function parse(token) {
|
|
var i = 0;
|
|
|
|
/**
|
|
"before-selector" or
|
|
"selector" or
|
|
"atRule" or
|
|
"atBlock" or
|
|
"conditionBlock" or
|
|
"before-name" or
|
|
"name" or
|
|
"before-value" or
|
|
"value"
|
|
*/
|
|
var state = "before-selector";
|
|
|
|
var index;
|
|
var buffer = "";
|
|
var valueParenthesisDepth = 0;
|
|
|
|
var SIGNIFICANT_WHITESPACE = {
|
|
selector: true,
|
|
value: true,
|
|
"value-parenthesis": true,
|
|
atRule: true,
|
|
"importRule-begin": true,
|
|
importRule: true,
|
|
atBlock: true,
|
|
conditionBlock: true,
|
|
"documentRule-begin": true
|
|
};
|
|
|
|
var styleSheet = new CSSOM.CSSStyleSheet();
|
|
|
|
// @type CSSStyleSheet|CSSMediaRule|CSSSupportsRule|CSSFontFaceRule|CSSKeyframesRule|CSSDocumentRule
|
|
var currentScope = styleSheet;
|
|
|
|
// @type CSSMediaRule|CSSSupportsRule|CSSKeyframesRule|CSSDocumentRule
|
|
var parentRule;
|
|
|
|
var ancestorRules = [];
|
|
var hasAncestors = false;
|
|
var prevScope;
|
|
|
|
var name,
|
|
priority = "",
|
|
styleRule,
|
|
mediaRule,
|
|
supportsRule,
|
|
importRule,
|
|
fontFaceRule,
|
|
keyframesRule,
|
|
documentRule,
|
|
hostRule;
|
|
|
|
var atKeyframesRegExp = /@(-(?:\w+-)+)?keyframes/g;
|
|
|
|
var parseError = function (message) {
|
|
var lines = token.substring(0, i).split("\n");
|
|
var lineCount = lines.length;
|
|
var charCount = lines.pop().length + 1;
|
|
var error = new Error(
|
|
message + " (line " + lineCount + ", char " + charCount + ")"
|
|
);
|
|
error.line = lineCount;
|
|
/* jshint sub : true */
|
|
error["char"] = charCount;
|
|
error.styleSheet = styleSheet;
|
|
throw error;
|
|
};
|
|
|
|
for (var character; (character = token.charAt(i)); i++) {
|
|
switch (character) {
|
|
case " ":
|
|
case "\t":
|
|
case "\r":
|
|
case "\n":
|
|
case "\f":
|
|
if (SIGNIFICANT_WHITESPACE[state]) {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
// String
|
|
case '"':
|
|
index = i + 1;
|
|
do {
|
|
index = token.indexOf('"', index) + 1;
|
|
if (!index) {
|
|
parseError('Unmatched "');
|
|
}
|
|
} while (token[index - 2] === "\\");
|
|
buffer += token.slice(i, index);
|
|
i = index - 1;
|
|
switch (state) {
|
|
case "before-value":
|
|
state = "value";
|
|
break;
|
|
case "importRule-begin":
|
|
state = "importRule";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "'":
|
|
index = i + 1;
|
|
do {
|
|
index = token.indexOf("'", index) + 1;
|
|
if (!index) {
|
|
parseError("Unmatched '");
|
|
}
|
|
} while (token[index - 2] === "\\");
|
|
buffer += token.slice(i, index);
|
|
i = index - 1;
|
|
switch (state) {
|
|
case "before-value":
|
|
state = "value";
|
|
break;
|
|
case "importRule-begin":
|
|
state = "importRule";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
// Comment
|
|
case "/":
|
|
if (token.charAt(i + 1) === "*") {
|
|
i += 2;
|
|
index = token.indexOf("*/", i);
|
|
if (index === -1) {
|
|
parseError("Missing */");
|
|
} else {
|
|
i = index + 1;
|
|
}
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
if (state === "importRule-begin") {
|
|
buffer += " ";
|
|
state = "importRule";
|
|
}
|
|
break;
|
|
|
|
// At-rule
|
|
case "@":
|
|
if (token.indexOf("@-moz-document", i) === i) {
|
|
state = "documentRule-begin";
|
|
documentRule = new CSSOM.CSSDocumentRule();
|
|
documentRule.__starts = i;
|
|
i += "-moz-document".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@media", i) === i) {
|
|
state = "atBlock";
|
|
mediaRule = new CSSOM.CSSMediaRule();
|
|
mediaRule.__starts = i;
|
|
i += "media".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@supports", i) === i) {
|
|
state = "conditionBlock";
|
|
supportsRule = new CSSOM.CSSSupportsRule();
|
|
supportsRule.__starts = i;
|
|
i += "supports".length;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@host", i) === i) {
|
|
state = "hostRule-begin";
|
|
i += "host".length;
|
|
hostRule = new CSSOM.CSSHostRule();
|
|
hostRule.__starts = i;
|
|
buffer = "";
|
|
break;
|
|
} else if (token.indexOf("@import", i) === i) {
|
|
state = "importRule-begin";
|
|
i += "import".length;
|
|
buffer += "@import";
|
|
break;
|
|
} else if (token.indexOf("@font-face", i) === i) {
|
|
state = "fontFaceRule-begin";
|
|
i += "font-face".length;
|
|
fontFaceRule = new CSSOM.CSSFontFaceRule();
|
|
fontFaceRule.__starts = i;
|
|
buffer = "";
|
|
break;
|
|
} else {
|
|
atKeyframesRegExp.lastIndex = i;
|
|
var matchKeyframes = atKeyframesRegExp.exec(token);
|
|
if (matchKeyframes && matchKeyframes.index === i) {
|
|
state = "keyframesRule-begin";
|
|
keyframesRule = new CSSOM.CSSKeyframesRule();
|
|
keyframesRule.__starts = i;
|
|
keyframesRule._vendorPrefix = matchKeyframes[1]; // Will come out as undefined if no prefix was found
|
|
i += matchKeyframes[0].length - 1;
|
|
buffer = "";
|
|
break;
|
|
} else if (state === "selector") {
|
|
state = "atRule";
|
|
}
|
|
}
|
|
buffer += character;
|
|
break;
|
|
|
|
case "{":
|
|
if (state === "selector" || state === "atRule") {
|
|
styleRule.selectorText = buffer.trim();
|
|
styleRule.style.__starts = i;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "atBlock") {
|
|
mediaRule.media.mediaText = buffer.trim();
|
|
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = mediaRule;
|
|
mediaRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "conditionBlock") {
|
|
supportsRule.conditionText = buffer.trim();
|
|
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = supportsRule;
|
|
supportsRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "hostRule-begin") {
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
}
|
|
|
|
currentScope = parentRule = hostRule;
|
|
hostRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
} else if (state === "fontFaceRule-begin") {
|
|
if (parentRule) {
|
|
fontFaceRule.parentRule = parentRule;
|
|
}
|
|
fontFaceRule.parentStyleSheet = styleSheet;
|
|
styleRule = fontFaceRule;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "keyframesRule-begin") {
|
|
keyframesRule.name = buffer.trim();
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
keyframesRule.parentRule = parentRule;
|
|
}
|
|
keyframesRule.parentStyleSheet = styleSheet;
|
|
currentScope = parentRule = keyframesRule;
|
|
buffer = "";
|
|
state = "keyframeRule-begin";
|
|
} else if (state === "keyframeRule-begin") {
|
|
styleRule = new CSSOM.CSSKeyframeRule();
|
|
styleRule.keyText = buffer.trim();
|
|
styleRule.__starts = i;
|
|
buffer = "";
|
|
state = "before-name";
|
|
} else if (state === "documentRule-begin") {
|
|
// FIXME: what if this '{' is in the url text of the match function?
|
|
documentRule.matcher.matcherText = buffer.trim();
|
|
if (parentRule) {
|
|
ancestorRules.push(parentRule);
|
|
documentRule.parentRule = parentRule;
|
|
}
|
|
currentScope = parentRule = documentRule;
|
|
documentRule.parentStyleSheet = styleSheet;
|
|
buffer = "";
|
|
state = "before-selector";
|
|
}
|
|
break;
|
|
|
|
case ":":
|
|
if (state === "name") {
|
|
name = buffer.trim();
|
|
buffer = "";
|
|
state = "before-value";
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case "(":
|
|
if (state === "value") {
|
|
// ie css expression mode
|
|
if (buffer.trim() === "expression") {
|
|
var info = new CSSOM.CSSValueExpression(token, i).parse();
|
|
|
|
if (info.error) {
|
|
parseError(info.error);
|
|
} else {
|
|
buffer += info.expression;
|
|
i = info.idx;
|
|
}
|
|
} else {
|
|
state = "value-parenthesis";
|
|
//always ensure this is reset to 1 on transition
|
|
//from value to value-parenthesis
|
|
valueParenthesisDepth = 1;
|
|
buffer += character;
|
|
}
|
|
} else if (state === "value-parenthesis") {
|
|
valueParenthesisDepth++;
|
|
buffer += character;
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case ")":
|
|
if (state === "value-parenthesis") {
|
|
valueParenthesisDepth--;
|
|
if (valueParenthesisDepth === 0) state = "value";
|
|
}
|
|
buffer += character;
|
|
break;
|
|
|
|
case "!":
|
|
if (state === "value" && token.indexOf("!important", i) === i) {
|
|
priority = "important";
|
|
i += "important".length;
|
|
} else {
|
|
buffer += character;
|
|
}
|
|
break;
|
|
|
|
case ";":
|
|
switch (state) {
|
|
case "value":
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
buffer = "";
|
|
state = "before-name";
|
|
break;
|
|
case "atRule":
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
case "importRule":
|
|
importRule = new CSSOM.CSSImportRule();
|
|
importRule.parentStyleSheet =
|
|
importRule.styleSheet.parentStyleSheet = styleSheet;
|
|
importRule.cssText = buffer + character;
|
|
styleSheet.cssRules.push(importRule);
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
default:
|
|
buffer += character;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case "}":
|
|
switch (state) {
|
|
case "value":
|
|
styleRule.style.setProperty(name, buffer.trim(), priority);
|
|
priority = "";
|
|
/* falls through */
|
|
case "before-name":
|
|
case "name":
|
|
styleRule.__ends = i + 1;
|
|
if (parentRule) {
|
|
styleRule.parentRule = parentRule;
|
|
}
|
|
styleRule.parentStyleSheet = styleSheet;
|
|
currentScope.cssRules.push(styleRule);
|
|
buffer = "";
|
|
if (currentScope.constructor === CSSOM.CSSKeyframesRule) {
|
|
state = "keyframeRule-begin";
|
|
} else {
|
|
state = "before-selector";
|
|
}
|
|
break;
|
|
case "keyframeRule-begin":
|
|
case "before-selector":
|
|
case "selector":
|
|
// End of media/supports/document rule.
|
|
if (!parentRule) {
|
|
parseError("Unexpected }");
|
|
}
|
|
|
|
// Handle rules nested in @media or @supports
|
|
hasAncestors = ancestorRules.length > 0;
|
|
|
|
while (ancestorRules.length > 0) {
|
|
parentRule = ancestorRules.pop();
|
|
|
|
if (
|
|
parentRule.constructor.name === "CSSMediaRule" ||
|
|
parentRule.constructor.name === "CSSSupportsRule"
|
|
) {
|
|
prevScope = currentScope;
|
|
currentScope = parentRule;
|
|
currentScope.cssRules.push(prevScope);
|
|
break;
|
|
}
|
|
|
|
if (ancestorRules.length === 0) {
|
|
hasAncestors = false;
|
|
}
|
|
}
|
|
|
|
if (!hasAncestors) {
|
|
currentScope.__ends = i + 1;
|
|
styleSheet.cssRules.push(currentScope);
|
|
currentScope = styleSheet;
|
|
parentRule = null;
|
|
}
|
|
|
|
buffer = "";
|
|
state = "before-selector";
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
switch (state) {
|
|
case "before-selector":
|
|
state = "selector";
|
|
styleRule = new CSSOM.CSSStyleRule();
|
|
styleRule.__starts = i;
|
|
break;
|
|
case "before-name":
|
|
state = "name";
|
|
break;
|
|
case "before-value":
|
|
state = "value";
|
|
break;
|
|
case "importRule-begin":
|
|
state = "importRule";
|
|
break;
|
|
}
|
|
buffer += character;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return styleSheet;
|
|
};
|
|
|
|
//.CommonJS
|
|
parse$1.parse = CSSOM.parse;
|
|
// The following modules cannot be included sooner due to the mutual dependency with parse.js
|
|
CSSOM.CSSStyleSheet = requireCSSStyleSheet().CSSStyleSheet;
|
|
CSSOM.CSSStyleRule = requireCSSStyleRule().CSSStyleRule;
|
|
CSSOM.CSSImportRule = requireCSSImportRule().CSSImportRule;
|
|
CSSOM.CSSGroupingRule = requireCSSGroupingRule().CSSGroupingRule;
|
|
CSSOM.CSSMediaRule = requireCSSMediaRule().CSSMediaRule;
|
|
CSSOM.CSSConditionRule = requireCSSConditionRule().CSSConditionRule;
|
|
CSSOM.CSSSupportsRule = requireCSSSupportsRule().CSSSupportsRule;
|
|
CSSOM.CSSFontFaceRule = requireCSSFontFaceRule().CSSFontFaceRule;
|
|
CSSOM.CSSHostRule = requireCSSHostRule().CSSHostRule;
|
|
CSSOM.CSSStyleDeclaration = requireCSSStyleDeclaration().CSSStyleDeclaration;
|
|
CSSOM.CSSKeyframeRule = requireCSSKeyframeRule().CSSKeyframeRule;
|
|
CSSOM.CSSKeyframesRule = requireCSSKeyframesRule().CSSKeyframesRule;
|
|
CSSOM.CSSValueExpression = requireCSSValueExpression().CSSValueExpression;
|
|
CSSOM.CSSDocumentRule = requireCSSDocumentRule().CSSDocumentRule;
|
|
///CommonJS
|
|
return parse$1;
|
|
}
|
|
|
|
var hasRequiredCSSStyleDeclaration;
|
|
|
|
function requireCSSStyleDeclaration() {
|
|
if (hasRequiredCSSStyleDeclaration) return CSSStyleDeclaration;
|
|
hasRequiredCSSStyleDeclaration = 1;
|
|
//.CommonJS
|
|
var CSSOM = {};
|
|
///CommonJS
|
|
|
|
/**
|
|
* @constructor
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration
|
|
*/
|
|
CSSOM.CSSStyleDeclaration = function CSSStyleDeclaration() {
|
|
this.length = 0;
|
|
this.parentRule = null;
|
|
|
|
// NON-STANDARD
|
|
this._importants = {};
|
|
};
|
|
|
|
CSSOM.CSSStyleDeclaration.prototype = {
|
|
constructor: CSSOM.CSSStyleDeclaration,
|
|
|
|
/**
|
|
*
|
|
* @param {string} name
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-getPropertyValue
|
|
* @return {string} the value of the property if it has been explicitly set for this declaration block.
|
|
* Returns the empty string if the property has not been set.
|
|
*/
|
|
getPropertyValue: function (name) {
|
|
return this[name] || "";
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {string} name
|
|
* @param {string} value
|
|
* @param {string} [priority=null] "important" or null
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-setProperty
|
|
*/
|
|
setProperty: function (name, value, priority) {
|
|
if (this[name]) {
|
|
// Property already exist. Overwrite it.
|
|
var index = Array.prototype.indexOf.call(this, name);
|
|
if (index < 0) {
|
|
this[this.length] = name;
|
|
this.length++;
|
|
}
|
|
} else {
|
|
// New property.
|
|
this[this.length] = name;
|
|
this.length++;
|
|
}
|
|
this[name] = value + "";
|
|
this._importants[name] = priority;
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {string} name
|
|
* @see http://www.w3.org/TR/DOM-Level-2-Style/css.html#CSS-CSSStyleDeclaration-removeProperty
|
|
* @return {string} the value of the property if it has been explicitly set for this declaration block.
|
|
* Returns the empty string if the property has not been set or the property name does not correspond to a known CSS property.
|
|
*/
|
|
removeProperty: function (name) {
|
|
if (!(name in this)) {
|
|
return "";
|
|
}
|
|
var index = Array.prototype.indexOf.call(this, name);
|
|
if (index < 0) {
|
|
return "";
|
|
}
|
|
var prevValue = this[name];
|
|
this[name] = "";
|
|
|
|
// That's what WebKit and Opera do
|
|
Array.prototype.splice.call(this, index, 1);
|
|
|
|
// That's what Firefox does
|
|
//this[index] = ""
|
|
|
|
return prevValue;
|
|
},
|
|
|
|
getPropertyCSSValue: function () {
|
|
//FIXME
|
|
},
|
|
|
|
/**
|
|
*
|
|
* @param {String} name
|
|
*/
|
|
getPropertyPriority: function (name) {
|
|
return this._importants[name] || "";
|
|
},
|
|
|
|
/**
|
|
* element.style.overflow = "auto"
|
|
* element.style.getPropertyShorthand("overflow-x")
|
|
* -> "overflow"
|
|
*/
|
|
getPropertyShorthand: function () {
|
|
//FIXME
|
|
},
|
|
|
|
isPropertyImplicit: function () {
|
|
//FIXME
|
|
},
|
|
|
|
// Doesn't work in IE < 9
|
|
get cssText() {
|
|
var properties = [];
|
|
for (var i = 0, length = this.length; i < length; ++i) {
|
|
var name = this[i];
|
|
var value = this.getPropertyValue(name);
|
|
var priority = this.getPropertyPriority(name);
|
|
if (priority) {
|
|
priority = " !" + priority;
|
|
}
|
|
properties[i] = name + ": " + value + priority + ";";
|
|
}
|
|
return properties.join(" ");
|
|
},
|
|
|
|
set cssText(text) {
|
|
var i, name;
|
|
for (i = this.length; i--; ) {
|
|
name = this[i];
|
|
this[name] = "";
|
|
}
|
|
Array.prototype.splice.call(this, 0, this.length);
|
|
this._importants = {};
|
|
|
|
var dummyRule = CSSOM.parse("#bogus{" + text + "}").cssRules[0].style;
|
|
var length = dummyRule.length;
|
|
for (i = 0; i < length; ++i) {
|
|
name = dummyRule[i];
|
|
this.setProperty(
|
|
dummyRule[i],
|
|
dummyRule.getPropertyValue(name),
|
|
dummyRule.getPropertyPriority(name)
|
|
);
|
|
}
|
|
}
|
|
};
|
|
|
|
//.CommonJS
|
|
CSSStyleDeclaration.CSSStyleDeclaration = CSSOM.CSSStyleDeclaration;
|
|
CSSOM.parse = requireParse().parse; // Cannot be included sooner due to the mutual dependency between parse.js and CSSStyleDeclaration.js
|
|
///CommonJS
|
|
return CSSStyleDeclaration;
|
|
}
|
|
|
|
var clone = {};
|
|
|
|
var hasRequiredClone;
|
|
|
|
function requireClone() {
|
|
if (hasRequiredClone) return clone;
|
|
hasRequiredClone = 1;
|
|
//.CommonJS
|
|
var CSSOM = {
|
|
CSSStyleSheet: requireCSSStyleSheet().CSSStyleSheet,
|
|
CSSRule: requireCSSRule().CSSRule,
|
|
CSSStyleRule: requireCSSStyleRule().CSSStyleRule,
|
|
CSSGroupingRule: requireCSSGroupingRule().CSSGroupingRule,
|
|
CSSConditionRule: requireCSSConditionRule().CSSConditionRule,
|
|
CSSMediaRule: requireCSSMediaRule().CSSMediaRule,
|
|
CSSSupportsRule: requireCSSSupportsRule().CSSSupportsRule,
|
|
CSSStyleDeclaration: requireCSSStyleDeclaration().CSSStyleDeclaration,
|
|
CSSKeyframeRule: requireCSSKeyframeRule().CSSKeyframeRule,
|
|
CSSKeyframesRule: requireCSSKeyframesRule().CSSKeyframesRule
|
|
};
|
|
///CommonJS
|
|
|
|
/**
|
|
* Produces a deep copy of stylesheet — the instance variables of stylesheet are copied recursively.
|
|
* @param {CSSStyleSheet|CSSOM.CSSStyleSheet} stylesheet
|
|
* @nosideeffects
|
|
* @return {CSSOM.CSSStyleSheet}
|
|
*/
|
|
CSSOM.clone = function clone(stylesheet) {
|
|
var cloned = new CSSOM.CSSStyleSheet();
|
|
|
|
var rules = stylesheet.cssRules;
|
|
if (!rules) {
|
|
return cloned;
|
|
}
|
|
|
|
for (var i = 0, rulesLength = rules.length; i < rulesLength; i++) {
|
|
var rule = rules[i];
|
|
var ruleClone = (cloned.cssRules[i] = new rule.constructor());
|
|
|
|
var style = rule.style;
|
|
if (style) {
|
|
var styleClone = (ruleClone.style = new CSSOM.CSSStyleDeclaration());
|
|
for (var j = 0, styleLength = style.length; j < styleLength; j++) {
|
|
var name = (styleClone[j] = style[j]);
|
|
styleClone[name] = style[name];
|
|
styleClone._importants[name] = style.getPropertyPriority(name);
|
|
}
|
|
styleClone.length = style.length;
|
|
}
|
|
|
|
if (rule.hasOwnProperty("keyText")) {
|
|
ruleClone.keyText = rule.keyText;
|
|
}
|
|
|
|
if (rule.hasOwnProperty("selectorText")) {
|
|
ruleClone.selectorText = rule.selectorText;
|
|
}
|
|
|
|
if (rule.hasOwnProperty("mediaText")) {
|
|
ruleClone.mediaText = rule.mediaText;
|
|
}
|
|
|
|
if (rule.hasOwnProperty("conditionText")) {
|
|
ruleClone.conditionText = rule.conditionText;
|
|
}
|
|
|
|
if (rule.hasOwnProperty("cssRules")) {
|
|
ruleClone.cssRules = clone(rule).cssRules;
|
|
}
|
|
}
|
|
|
|
return cloned;
|
|
};
|
|
|
|
//.CommonJS
|
|
clone.clone = CSSOM.clone;
|
|
///CommonJS
|
|
return clone;
|
|
}
|
|
|
|
var hasRequiredLib;
|
|
|
|
function requireLib() {
|
|
if (hasRequiredLib) return lib;
|
|
hasRequiredLib = 1;
|
|
|
|
lib.CSSStyleDeclaration = requireCSSStyleDeclaration().CSSStyleDeclaration;
|
|
lib.CSSRule = requireCSSRule().CSSRule;
|
|
lib.CSSGroupingRule = requireCSSGroupingRule().CSSGroupingRule;
|
|
lib.CSSConditionRule = requireCSSConditionRule().CSSConditionRule;
|
|
lib.CSSStyleRule = requireCSSStyleRule().CSSStyleRule;
|
|
lib.MediaList = requireMediaList().MediaList;
|
|
lib.CSSMediaRule = requireCSSMediaRule().CSSMediaRule;
|
|
lib.CSSSupportsRule = requireCSSSupportsRule().CSSSupportsRule;
|
|
lib.CSSImportRule = requireCSSImportRule().CSSImportRule;
|
|
lib.CSSFontFaceRule = requireCSSFontFaceRule().CSSFontFaceRule;
|
|
lib.CSSHostRule = requireCSSHostRule().CSSHostRule;
|
|
lib.StyleSheet = requireStyleSheet().StyleSheet;
|
|
lib.CSSStyleSheet = requireCSSStyleSheet().CSSStyleSheet;
|
|
lib.CSSKeyframesRule = requireCSSKeyframesRule().CSSKeyframesRule;
|
|
lib.CSSKeyframeRule = requireCSSKeyframeRule().CSSKeyframeRule;
|
|
lib.MatcherList = requireMatcherList().MatcherList;
|
|
lib.CSSDocumentRule = requireCSSDocumentRule().CSSDocumentRule;
|
|
lib.CSSValue = requireCSSValue().CSSValue;
|
|
lib.CSSValueExpression = requireCSSValueExpression().CSSValueExpression;
|
|
lib.parse = requireParse().parse;
|
|
lib.clone = requireClone().clone;
|
|
return lib;
|
|
}
|
|
|
|
var libExports = requireLib();
|
|
|
|
const tagName$e = "style";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLStyleElement
|
|
*/
|
|
class HTMLStyleElement extends TextElement {
|
|
constructor(ownerDocument, localName = tagName$e) {
|
|
super(ownerDocument, localName);
|
|
this[SHEET] = null;
|
|
}
|
|
|
|
get sheet() {
|
|
const sheet = this[SHEET];
|
|
if (sheet !== null) {
|
|
return sheet;
|
|
}
|
|
return (this[SHEET] = libExports.parse(this.textContent));
|
|
}
|
|
|
|
get innerHTML() {
|
|
return super.innerHTML || "";
|
|
}
|
|
set innerHTML(value) {
|
|
super.textContent = value;
|
|
this[SHEET] = null;
|
|
}
|
|
get innerText() {
|
|
return super.innerText || "";
|
|
}
|
|
set innerText(value) {
|
|
super.textContent = value;
|
|
this[SHEET] = null;
|
|
}
|
|
get textContent() {
|
|
return super.textContent || "";
|
|
}
|
|
set textContent(value) {
|
|
super.textContent = value;
|
|
this[SHEET] = null;
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$e, HTMLStyleElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTimeElement
|
|
*/
|
|
class HTMLTimeElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "time") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/**
|
|
* @type {string}
|
|
*/
|
|
get dateTime() {
|
|
return stringAttribute.get(this, "datetime");
|
|
}
|
|
set dateTime(value) {
|
|
stringAttribute.set(this, "datetime", value);
|
|
}
|
|
}
|
|
|
|
registerHTMLClass("time", HTMLTimeElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLFieldSetElement
|
|
*/
|
|
class HTMLFieldSetElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "fieldset") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLEmbedElement
|
|
*/
|
|
class HTMLEmbedElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "embed") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLHRElement
|
|
*/
|
|
class HTMLHRElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "hr") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLProgressElement
|
|
*/
|
|
class HTMLProgressElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "progress") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLParagraphElement
|
|
*/
|
|
class HTMLParagraphElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "p") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTableElement
|
|
*/
|
|
class HTMLTableElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "table") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLFrameSetElement
|
|
*/
|
|
class HTMLFrameSetElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "frameset") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLLIElement
|
|
*/
|
|
class HTMLLIElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "li") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLBaseElement
|
|
*/
|
|
class HTMLBaseElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "base") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDataListElement
|
|
*/
|
|
class HTMLDataListElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "datalist") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$d = "input";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLInputElement
|
|
*/
|
|
class HTMLInputElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$d) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get autofocus() {
|
|
return booleanAttribute.get(this, "autofocus") || -1;
|
|
}
|
|
set autofocus(value) {
|
|
booleanAttribute.set(this, "autofocus", value);
|
|
}
|
|
|
|
get disabled() {
|
|
return booleanAttribute.get(this, "disabled");
|
|
}
|
|
set disabled(value) {
|
|
booleanAttribute.set(this, "disabled", value);
|
|
}
|
|
|
|
get name() {
|
|
return this.getAttribute("name");
|
|
}
|
|
set name(value) {
|
|
this.setAttribute("name", value);
|
|
}
|
|
|
|
get placeholder() {
|
|
return this.getAttribute("placeholder");
|
|
}
|
|
set placeholder(value) {
|
|
this.setAttribute("placeholder", value);
|
|
}
|
|
|
|
get type() {
|
|
return this.getAttribute("type");
|
|
}
|
|
set type(value) {
|
|
this.setAttribute("type", value);
|
|
}
|
|
|
|
get value() {
|
|
return stringAttribute.get(this, "value");
|
|
}
|
|
set value(value) {
|
|
stringAttribute.set(this, "value", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$d, HTMLInputElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLParamElement
|
|
*/
|
|
class HTMLParamElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "param") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLMediaElement
|
|
*/
|
|
class HTMLMediaElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "media") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLAudioElement
|
|
*/
|
|
class HTMLAudioElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "audio") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$c = "h1";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLHeadingElement
|
|
*/
|
|
class HTMLHeadingElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$c) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(
|
|
[tagName$c, "h2", "h3", "h4", "h5", "h6"],
|
|
HTMLHeadingElement
|
|
);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDirectoryElement
|
|
*/
|
|
class HTMLDirectoryElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "dir") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLQuoteElement
|
|
*/
|
|
class HTMLQuoteElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "quote") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
class Canvas {
|
|
constructor(width, height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
getContext() {
|
|
return null;
|
|
}
|
|
toDataURL() {
|
|
return "";
|
|
}
|
|
}
|
|
var Canvas$1 = { createCanvas: (width, height) => new Canvas(width, height) };
|
|
|
|
const { createCanvas } = Canvas$1;
|
|
|
|
const tagName$b = "canvas";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLCanvasElement
|
|
*/
|
|
class HTMLCanvasElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$b) {
|
|
super(ownerDocument, localName);
|
|
this[IMAGE] = createCanvas(300, 150);
|
|
}
|
|
|
|
get width() {
|
|
return this[IMAGE].width;
|
|
}
|
|
|
|
set width(value) {
|
|
numericAttribute.set(this, "width", value);
|
|
this[IMAGE].width = value;
|
|
}
|
|
|
|
get height() {
|
|
return this[IMAGE].height;
|
|
}
|
|
|
|
set height(value) {
|
|
numericAttribute.set(this, "height", value);
|
|
this[IMAGE].height = value;
|
|
}
|
|
|
|
getContext(type) {
|
|
return this[IMAGE].getContext(type);
|
|
}
|
|
|
|
toDataURL(...args) {
|
|
return this[IMAGE].toDataURL(...args);
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$b, HTMLCanvasElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLLegendElement
|
|
*/
|
|
class HTMLLegendElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "legend") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$a = "option";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLOptionElement
|
|
*/
|
|
class HTMLOptionElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$a) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get value() {
|
|
return stringAttribute.get(this, "value");
|
|
}
|
|
set value(value) {
|
|
stringAttribute.set(this, "value", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
get selected() {
|
|
return booleanAttribute.get(this, "selected");
|
|
}
|
|
set selected(value) {
|
|
const option = this.parentElement?.querySelector("option[selected]");
|
|
if (option && option !== this) option.selected = false;
|
|
booleanAttribute.set(this, "selected", value);
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$a, HTMLOptionElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLSpanElement
|
|
*/
|
|
class HTMLSpanElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "span") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLMeterElement
|
|
*/
|
|
class HTMLMeterElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "meter") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLVideoElement
|
|
*/
|
|
class HTMLVideoElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "video") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTableCellElement
|
|
*/
|
|
class HTMLTableCellElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "td") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$9 = "title";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTitleElement
|
|
*/
|
|
class HTMLTitleElement extends TextElement {
|
|
constructor(ownerDocument, localName = tagName$9) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$9, HTMLTitleElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLOutputElement
|
|
*/
|
|
class HTMLOutputElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "output") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTableRowElement
|
|
*/
|
|
class HTMLTableRowElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "tr") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDataElement
|
|
*/
|
|
class HTMLDataElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "data") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLMenuElement
|
|
*/
|
|
class HTMLMenuElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "menu") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$8 = "select";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLSelectElement
|
|
*/
|
|
class HTMLSelectElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$8) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
get options() {
|
|
let children = new NodeList();
|
|
let { firstElementChild } = this;
|
|
while (firstElementChild) {
|
|
if (firstElementChild.tagName === "OPTGROUP")
|
|
children.push(...firstElementChild.children);
|
|
else children.push(firstElementChild);
|
|
firstElementChild = firstElementChild.nextElementSibling;
|
|
}
|
|
return children;
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get disabled() {
|
|
return booleanAttribute.get(this, "disabled");
|
|
}
|
|
set disabled(value) {
|
|
booleanAttribute.set(this, "disabled", value);
|
|
}
|
|
|
|
get name() {
|
|
return this.getAttribute("name");
|
|
}
|
|
set name(value) {
|
|
this.setAttribute("name", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
get value() {
|
|
return this.querySelector("option[selected]")?.value;
|
|
}
|
|
}
|
|
|
|
registerHTMLClass(tagName$8, HTMLSelectElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLBRElement
|
|
*/
|
|
class HTMLBRElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "br") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$7 = "button";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLButtonElement
|
|
*/
|
|
class HTMLButtonElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$7) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get disabled() {
|
|
return booleanAttribute.get(this, "disabled");
|
|
}
|
|
set disabled(value) {
|
|
booleanAttribute.set(this, "disabled", value);
|
|
}
|
|
|
|
get name() {
|
|
return this.getAttribute("name");
|
|
}
|
|
set name(value) {
|
|
this.setAttribute("name", value);
|
|
}
|
|
|
|
get type() {
|
|
return this.getAttribute("type");
|
|
}
|
|
set type(value) {
|
|
this.setAttribute("type", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$7, HTMLButtonElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLMapElement
|
|
*/
|
|
class HTMLMapElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "map") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLOptGroupElement
|
|
*/
|
|
class HTMLOptGroupElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "optgroup") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDListElement
|
|
*/
|
|
class HTMLDListElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "dl") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$6 = "textarea";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTextAreaElement
|
|
*/
|
|
class HTMLTextAreaElement extends TextElement {
|
|
constructor(ownerDocument, localName = tagName$6) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get disabled() {
|
|
return booleanAttribute.get(this, "disabled");
|
|
}
|
|
set disabled(value) {
|
|
booleanAttribute.set(this, "disabled", value);
|
|
}
|
|
|
|
get name() {
|
|
return this.getAttribute("name");
|
|
}
|
|
set name(value) {
|
|
this.setAttribute("name", value);
|
|
}
|
|
|
|
get placeholder() {
|
|
return this.getAttribute("placeholder");
|
|
}
|
|
set placeholder(value) {
|
|
this.setAttribute("placeholder", value);
|
|
}
|
|
|
|
get type() {
|
|
return this.getAttribute("type");
|
|
}
|
|
set type(value) {
|
|
this.setAttribute("type", value);
|
|
}
|
|
|
|
get value() {
|
|
return this.textContent;
|
|
}
|
|
set value(content) {
|
|
this.textContent = content;
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$6, HTMLTextAreaElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLFontElement
|
|
*/
|
|
class HTMLFontElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "font") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDivElement
|
|
*/
|
|
class HTMLDivElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "div") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$5 = "link";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLLinkElement
|
|
*/
|
|
class HTMLLinkElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$5) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */ // copy paste from img.src, already covered
|
|
get disabled() {
|
|
return booleanAttribute.get(this, "disabled");
|
|
}
|
|
set disabled(value) {
|
|
booleanAttribute.set(this, "disabled", value);
|
|
}
|
|
|
|
get href() {
|
|
return stringAttribute.get(this, "href").trim();
|
|
}
|
|
set href(value) {
|
|
stringAttribute.set(this, "href", value);
|
|
}
|
|
|
|
get hreflang() {
|
|
return stringAttribute.get(this, "hreflang");
|
|
}
|
|
set hreflang(value) {
|
|
stringAttribute.set(this, "hreflang", value);
|
|
}
|
|
|
|
get media() {
|
|
return stringAttribute.get(this, "media");
|
|
}
|
|
set media(value) {
|
|
stringAttribute.set(this, "media", value);
|
|
}
|
|
|
|
get rel() {
|
|
return stringAttribute.get(this, "rel");
|
|
}
|
|
set rel(value) {
|
|
stringAttribute.set(this, "rel", value);
|
|
}
|
|
|
|
get type() {
|
|
return stringAttribute.get(this, "type");
|
|
}
|
|
set type(value) {
|
|
stringAttribute.set(this, "type", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$5, HTMLLinkElement);
|
|
|
|
const tagName$4 = "slot";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLSlotElement
|
|
*/
|
|
class HTMLSlotElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$4) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get name() {
|
|
return this.getAttribute("name");
|
|
}
|
|
set name(value) {
|
|
this.setAttribute("name", value);
|
|
}
|
|
|
|
assign() {}
|
|
|
|
assignedNodes(options) {
|
|
const isNamedSlot = !!this.name;
|
|
const hostChildNodes = this.getRootNode().host?.childNodes ?? [];
|
|
let slottables;
|
|
|
|
if (isNamedSlot) {
|
|
slottables = [...hostChildNodes].filter(
|
|
(node) => node.slot === this.name
|
|
);
|
|
} else {
|
|
slottables = [...hostChildNodes].filter((node) => !node.slot);
|
|
}
|
|
|
|
if (options?.flatten) {
|
|
const result = [];
|
|
|
|
// Element and Text nodes are slottables. A slot can be a slottable.
|
|
for (let slottable of slottables) {
|
|
if (slottable.localName === "slot") {
|
|
result.push(...slottable.assignedNodes({ flatten: true }));
|
|
} else {
|
|
result.push(slottable);
|
|
}
|
|
}
|
|
|
|
slottables = result;
|
|
}
|
|
|
|
// If no assigned nodes are found, it returns the slot's fallback content.
|
|
return slottables.length ? slottables : [...this.childNodes];
|
|
}
|
|
|
|
assignedElements(options) {
|
|
const slottables = this.assignedNodes(options).filter(
|
|
(n) => n.nodeType === 1
|
|
);
|
|
|
|
// If no assigned elements are found, it returns the slot's fallback content.
|
|
return slottables.length ? slottables : [...this.children];
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$4, HTMLSlotElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLFormElement
|
|
*/
|
|
class HTMLFormElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "form") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$3 = "img";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLImageElement
|
|
*/
|
|
class HTMLImageElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$3) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get alt() {
|
|
return stringAttribute.get(this, "alt");
|
|
}
|
|
set alt(value) {
|
|
stringAttribute.set(this, "alt", value);
|
|
}
|
|
|
|
get sizes() {
|
|
return stringAttribute.get(this, "sizes");
|
|
}
|
|
set sizes(value) {
|
|
stringAttribute.set(this, "sizes", value);
|
|
}
|
|
|
|
get src() {
|
|
return stringAttribute.get(this, "src");
|
|
}
|
|
set src(value) {
|
|
stringAttribute.set(this, "src", value);
|
|
}
|
|
|
|
get srcset() {
|
|
return stringAttribute.get(this, "srcset");
|
|
}
|
|
set srcset(value) {
|
|
stringAttribute.set(this, "srcset", value);
|
|
}
|
|
|
|
get title() {
|
|
return stringAttribute.get(this, "title");
|
|
}
|
|
set title(value) {
|
|
stringAttribute.set(this, "title", value);
|
|
}
|
|
|
|
get width() {
|
|
return numericAttribute.get(this, "width");
|
|
}
|
|
set width(value) {
|
|
numericAttribute.set(this, "width", value);
|
|
}
|
|
|
|
get height() {
|
|
return numericAttribute.get(this, "height");
|
|
}
|
|
set height(value) {
|
|
numericAttribute.set(this, "height", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$3, HTMLImageElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLPreElement
|
|
*/
|
|
class HTMLPreElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "pre") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLUListElement
|
|
*/
|
|
class HTMLUListElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "ul") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$2 = "meta";
|
|
/**
|
|
* @implements globalThis.HTMLMetaElement
|
|
*/
|
|
class HTMLMetaElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$2) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get name() {
|
|
return stringAttribute.get(this, "name");
|
|
}
|
|
set name(value) {
|
|
stringAttribute.set(this, "name", value);
|
|
}
|
|
|
|
get httpEquiv() {
|
|
return stringAttribute.get(this, "http-equiv");
|
|
}
|
|
set httpEquiv(value) {
|
|
stringAttribute.set(this, "http-equiv", value);
|
|
}
|
|
|
|
get content() {
|
|
return stringAttribute.get(this, "content");
|
|
}
|
|
set content(value) {
|
|
stringAttribute.set(this, "content", value);
|
|
}
|
|
|
|
get charset() {
|
|
return stringAttribute.get(this, "charset");
|
|
}
|
|
set charset(value) {
|
|
stringAttribute.set(this, "charset", value);
|
|
}
|
|
|
|
get media() {
|
|
return stringAttribute.get(this, "media");
|
|
}
|
|
set media(value) {
|
|
stringAttribute.set(this, "media", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$2, HTMLMetaElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLPictureElement
|
|
*/
|
|
class HTMLPictureElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "picture") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLAreaElement
|
|
*/
|
|
class HTMLAreaElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "area") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLOListElement
|
|
*/
|
|
class HTMLOListElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "ol") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTableCaptionElement
|
|
*/
|
|
class HTMLTableCaptionElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "caption") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName$1 = "a";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLAnchorElement
|
|
*/
|
|
class HTMLAnchorElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName$1) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */ // copy paste from img.src, already covered
|
|
get href() {
|
|
return encodeURI(decodeURI(stringAttribute.get(this, "href"))).trim();
|
|
}
|
|
set href(value) {
|
|
stringAttribute.set(this, "href", decodeURI(value));
|
|
}
|
|
|
|
get download() {
|
|
return encodeURI(decodeURI(stringAttribute.get(this, "download")));
|
|
}
|
|
set download(value) {
|
|
stringAttribute.set(this, "download", decodeURI(value));
|
|
}
|
|
|
|
get target() {
|
|
return stringAttribute.get(this, "target");
|
|
}
|
|
set target(value) {
|
|
stringAttribute.set(this, "target", value);
|
|
}
|
|
|
|
get type() {
|
|
return stringAttribute.get(this, "type");
|
|
}
|
|
set type(value) {
|
|
stringAttribute.set(this, "type", value);
|
|
}
|
|
|
|
get rel() {
|
|
return stringAttribute.get(this, "rel");
|
|
}
|
|
set rel(value) {
|
|
stringAttribute.set(this, "rel", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName$1, HTMLAnchorElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLLabelElement
|
|
*/
|
|
class HTMLLabelElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "label") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLUnknownElement
|
|
*/
|
|
class HTMLUnknownElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "unknown") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLModElement
|
|
*/
|
|
class HTMLModElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "mod") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDetailsElement
|
|
*/
|
|
class HTMLDetailsElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "details") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const tagName = "source";
|
|
|
|
/**
|
|
* @implements globalThis.HTMLSourceElement
|
|
*/
|
|
class HTMLSourceElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = tagName) {
|
|
super(ownerDocument, localName);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
get src() {
|
|
return stringAttribute.get(this, "src");
|
|
}
|
|
set src(value) {
|
|
stringAttribute.set(this, "src", value);
|
|
}
|
|
|
|
get srcset() {
|
|
return stringAttribute.get(this, "srcset");
|
|
}
|
|
set srcset(value) {
|
|
stringAttribute.set(this, "srcset", value);
|
|
}
|
|
|
|
get sizes() {
|
|
return stringAttribute.get(this, "sizes");
|
|
}
|
|
set sizes(value) {
|
|
stringAttribute.set(this, "sizes", value);
|
|
}
|
|
|
|
get type() {
|
|
return stringAttribute.get(this, "type");
|
|
}
|
|
set type(value) {
|
|
stringAttribute.set(this, "type", value);
|
|
}
|
|
/* c8 ignore stop */
|
|
}
|
|
|
|
registerHTMLClass(tagName, HTMLSourceElement);
|
|
|
|
/**
|
|
* @implements globalThis.HTMLTrackElement
|
|
*/
|
|
class HTMLTrackElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "track") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.HTMLMarqueeElement
|
|
*/
|
|
class HTMLMarqueeElement extends HTMLElement {
|
|
constructor(ownerDocument, localName = "marquee") {
|
|
super(ownerDocument, localName);
|
|
}
|
|
}
|
|
|
|
const HTMLClasses = {
|
|
HTMLElement,
|
|
HTMLTemplateElement,
|
|
HTMLHtmlElement,
|
|
HTMLScriptElement,
|
|
HTMLFrameElement,
|
|
HTMLIFrameElement,
|
|
HTMLObjectElement,
|
|
HTMLHeadElement,
|
|
HTMLBodyElement,
|
|
HTMLStyleElement,
|
|
HTMLTimeElement,
|
|
HTMLFieldSetElement,
|
|
HTMLEmbedElement,
|
|
HTMLHRElement,
|
|
HTMLProgressElement,
|
|
HTMLParagraphElement,
|
|
HTMLTableElement,
|
|
HTMLFrameSetElement,
|
|
HTMLLIElement,
|
|
HTMLBaseElement,
|
|
HTMLDataListElement,
|
|
HTMLInputElement,
|
|
HTMLParamElement,
|
|
HTMLMediaElement,
|
|
HTMLAudioElement,
|
|
HTMLHeadingElement,
|
|
HTMLDirectoryElement,
|
|
HTMLQuoteElement,
|
|
HTMLCanvasElement,
|
|
HTMLLegendElement,
|
|
HTMLOptionElement,
|
|
HTMLSpanElement,
|
|
HTMLMeterElement,
|
|
HTMLVideoElement,
|
|
HTMLTableCellElement,
|
|
HTMLTitleElement,
|
|
HTMLOutputElement,
|
|
HTMLTableRowElement,
|
|
HTMLDataElement,
|
|
HTMLMenuElement,
|
|
HTMLSelectElement,
|
|
HTMLBRElement,
|
|
HTMLButtonElement,
|
|
HTMLMapElement,
|
|
HTMLOptGroupElement,
|
|
HTMLDListElement,
|
|
HTMLTextAreaElement,
|
|
HTMLFontElement,
|
|
HTMLDivElement,
|
|
HTMLLinkElement,
|
|
HTMLSlotElement,
|
|
HTMLFormElement,
|
|
HTMLImageElement,
|
|
HTMLPreElement,
|
|
HTMLUListElement,
|
|
HTMLMetaElement,
|
|
HTMLPictureElement,
|
|
HTMLAreaElement,
|
|
HTMLOListElement,
|
|
HTMLTableCaptionElement,
|
|
HTMLAnchorElement,
|
|
HTMLLabelElement,
|
|
HTMLUnknownElement,
|
|
HTMLModElement,
|
|
HTMLDetailsElement,
|
|
HTMLSourceElement,
|
|
HTMLTrackElement,
|
|
HTMLMarqueeElement
|
|
};
|
|
|
|
// TODO: ensure all these are text only
|
|
// /^(?:plaintext|script|style|textarea|title|xmp)$/i
|
|
|
|
const voidElements = { test: () => true };
|
|
const Mime = {
|
|
"text/html": {
|
|
docType: "<!DOCTYPE html>",
|
|
ignoreCase: true,
|
|
voidElements:
|
|
/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i
|
|
},
|
|
"image/svg+xml": {
|
|
docType: '<?xml version="1.0" encoding="utf-8"?>',
|
|
ignoreCase: false,
|
|
voidElements
|
|
},
|
|
"text/xml": {
|
|
docType: '<?xml version="1.0" encoding="utf-8"?>',
|
|
ignoreCase: false,
|
|
voidElements
|
|
},
|
|
"application/xml": {
|
|
docType: '<?xml version="1.0" encoding="utf-8"?>',
|
|
ignoreCase: false,
|
|
voidElements
|
|
},
|
|
"application/xhtml+xml": {
|
|
docType: '<?xml version="1.0" encoding="utf-8"?>',
|
|
ignoreCase: false,
|
|
voidElements
|
|
}
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#interface-customevent
|
|
|
|
/**
|
|
* @implements globalThis.CustomEvent
|
|
*/
|
|
class CustomEvent extends GlobalEvent {
|
|
constructor(type, eventInitDict = {}) {
|
|
super(type, eventInitDict);
|
|
this.detail = eventInitDict.detail;
|
|
}
|
|
}
|
|
|
|
/* c8 ignore stop */
|
|
|
|
// https://dom.spec.whatwg.org/#interface-customevent
|
|
|
|
/**
|
|
* @implements globalThis.InputEvent
|
|
*/
|
|
class InputEvent extends GlobalEvent {
|
|
constructor(type, inputEventInit = {}) {
|
|
super(type, inputEventInit);
|
|
this.inputType = inputEventInit.inputType;
|
|
this.data = inputEventInit.data;
|
|
this.dataTransfer = inputEventInit.dataTransfer;
|
|
this.isComposing = inputEventInit.isComposing || false;
|
|
this.ranges = inputEventInit.ranges;
|
|
}
|
|
}
|
|
/* c8 ignore stop */
|
|
|
|
const ImageClass = (ownerDocument) =>
|
|
/**
|
|
* @implements globalThis.Image
|
|
*/
|
|
class Image extends HTMLImageElement {
|
|
constructor(width, height) {
|
|
super(ownerDocument);
|
|
switch (arguments.length) {
|
|
case 1:
|
|
this.height = width;
|
|
this.width = width;
|
|
break;
|
|
case 2:
|
|
this.height = height;
|
|
this.width = width;
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// https://dom.spec.whatwg.org/#concept-live-range
|
|
|
|
const deleteContents = ({ [START]: start, [END]: end }, fragment = null) => {
|
|
setAdjacent(start[PREV], end[NEXT]);
|
|
do {
|
|
const after = getEnd(start);
|
|
const next = after === end ? after : after[NEXT];
|
|
if (fragment) fragment.insertBefore(start, fragment[END]);
|
|
else start.remove();
|
|
start = next;
|
|
} while (start !== end);
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.Range
|
|
*/
|
|
class Range {
|
|
constructor() {
|
|
this[START] = null;
|
|
this[END] = null;
|
|
this.commonAncestorContainer = null;
|
|
}
|
|
|
|
/* TODO: this is more complicated than it looks
|
|
setStart(node, offset) {
|
|
this[START] = node.childNodes[offset];
|
|
}
|
|
|
|
setEnd(node, offset) {
|
|
this[END] = getEnd(node.childNodes[offset]);
|
|
}
|
|
//*/
|
|
|
|
insertNode(newNode) {
|
|
this[END].parentNode.insertBefore(newNode, this[START]);
|
|
}
|
|
|
|
selectNode(node) {
|
|
this[START] = node;
|
|
this[END] = getEnd(node);
|
|
}
|
|
|
|
// TODO: SVG elements should then create contextual fragments
|
|
// that return SVG nodes
|
|
selectNodeContents(node) {
|
|
this.selectNode(node);
|
|
this.commonAncestorContainer = node;
|
|
}
|
|
|
|
surroundContents(parentNode) {
|
|
parentNode.replaceChildren(this.extractContents());
|
|
}
|
|
|
|
setStartBefore(node) {
|
|
this[START] = node;
|
|
}
|
|
|
|
setStartAfter(node) {
|
|
this[START] = node.nextSibling;
|
|
}
|
|
|
|
setEndBefore(node) {
|
|
this[END] = getEnd(node.previousSibling);
|
|
}
|
|
|
|
setEndAfter(node) {
|
|
this[END] = getEnd(node);
|
|
}
|
|
|
|
cloneContents() {
|
|
let { [START]: start, [END]: end } = this;
|
|
const fragment = start.ownerDocument.createDocumentFragment();
|
|
while (start !== end) {
|
|
fragment.insertBefore(start.cloneNode(true), fragment[END]);
|
|
start = getEnd(start);
|
|
if (start !== end) start = start[NEXT];
|
|
}
|
|
return fragment;
|
|
}
|
|
|
|
deleteContents() {
|
|
deleteContents(this);
|
|
}
|
|
|
|
extractContents() {
|
|
const fragment = this[START].ownerDocument.createDocumentFragment();
|
|
deleteContents(this, fragment);
|
|
return fragment;
|
|
}
|
|
|
|
createContextualFragment(html) {
|
|
const { commonAncestorContainer: doc } = this;
|
|
const isSVG = "ownerSVGElement" in doc;
|
|
const document = isSVG ? doc.ownerDocument : doc;
|
|
let content = htmlToFragment(document, html);
|
|
if (isSVG) {
|
|
const childNodes = [...content.childNodes];
|
|
content = document.createDocumentFragment();
|
|
Object.setPrototypeOf(content, SVGElement$1.prototype);
|
|
content.ownerSVGElement = document;
|
|
for (const child of childNodes) {
|
|
Object.setPrototypeOf(child, SVGElement$1.prototype);
|
|
child.ownerSVGElement = document;
|
|
content.appendChild(child);
|
|
}
|
|
} else this.selectNode(content);
|
|
return content;
|
|
}
|
|
|
|
cloneRange() {
|
|
const range = new Range();
|
|
range[START] = this[START];
|
|
range[END] = this[END];
|
|
return range;
|
|
}
|
|
}
|
|
|
|
const isOK = ({ nodeType }, mask) => {
|
|
switch (nodeType) {
|
|
case ELEMENT_NODE:
|
|
return mask & SHOW_ELEMENT;
|
|
case TEXT_NODE:
|
|
return mask & SHOW_TEXT;
|
|
case COMMENT_NODE:
|
|
return mask & SHOW_COMMENT;
|
|
case CDATA_SECTION_NODE:
|
|
return mask & SHOW_CDATA_SECTION;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.TreeWalker
|
|
*/
|
|
class TreeWalker {
|
|
constructor(root, whatToShow = SHOW_ALL) {
|
|
this.root = root;
|
|
this.currentNode = root;
|
|
this.whatToShow = whatToShow;
|
|
let { [NEXT]: next, [END]: end } = root;
|
|
if (root.nodeType === DOCUMENT_NODE) {
|
|
const { documentElement } = root;
|
|
next = documentElement;
|
|
end = documentElement[END];
|
|
}
|
|
const nodes = [];
|
|
while (next && next !== end) {
|
|
if (isOK(next, whatToShow)) nodes.push(next);
|
|
next = next[NEXT];
|
|
}
|
|
this[PRIVATE] = { i: 0, nodes };
|
|
}
|
|
|
|
nextNode() {
|
|
const $ = this[PRIVATE];
|
|
this.currentNode = $.i < $.nodes.length ? $.nodes[$.i++] : null;
|
|
return this.currentNode;
|
|
}
|
|
}
|
|
|
|
const query = (method, ownerDocument, selectors) => {
|
|
let { [NEXT]: next, [END]: end } = ownerDocument;
|
|
return method.call({ ownerDocument, [NEXT]: next, [END]: end }, selectors);
|
|
};
|
|
|
|
const globalExports = assign({}, Facades, HTMLClasses, {
|
|
CustomEvent,
|
|
Event: GlobalEvent,
|
|
EventTarget: DOMEventTarget,
|
|
InputEvent,
|
|
NamedNodeMap,
|
|
NodeList
|
|
});
|
|
|
|
const window = new WeakMap();
|
|
|
|
/**
|
|
* @implements globalThis.Document
|
|
*/
|
|
let Document$1 = class Document extends NonElementParentNode {
|
|
constructor(type) {
|
|
super(null, "#document", DOCUMENT_NODE);
|
|
this[CUSTOM_ELEMENTS] = { active: false, registry: null };
|
|
this[MUTATION_OBSERVER] = { active: false, class: null };
|
|
this[MIME] = Mime[type];
|
|
/** @type {DocumentType} */
|
|
this[DOCTYPE] = null;
|
|
this[DOM_PARSER] = null;
|
|
this[GLOBALS] = null;
|
|
this[IMAGE] = null;
|
|
this[UPGRADE] = null;
|
|
}
|
|
|
|
/**
|
|
* @type {globalThis.Document['defaultView']}
|
|
*/
|
|
get defaultView() {
|
|
if (!window.has(this))
|
|
window.set(
|
|
this,
|
|
new Proxy(globalThis, {
|
|
set: (target, name, value) => {
|
|
switch (name) {
|
|
case "addEventListener":
|
|
case "removeEventListener":
|
|
case "dispatchEvent":
|
|
this[EVENT_TARGET][name] = value;
|
|
break;
|
|
default:
|
|
target[name] = value;
|
|
break;
|
|
}
|
|
return true;
|
|
},
|
|
get: (globalThis, name) => {
|
|
switch (name) {
|
|
case "addEventListener":
|
|
case "removeEventListener":
|
|
case "dispatchEvent":
|
|
if (!this[EVENT_TARGET]) {
|
|
const et = (this[EVENT_TARGET] = new DOMEventTarget());
|
|
et.dispatchEvent = et.dispatchEvent.bind(et);
|
|
et.addEventListener = et.addEventListener.bind(et);
|
|
et.removeEventListener = et.removeEventListener.bind(et);
|
|
}
|
|
return this[EVENT_TARGET][name];
|
|
case "document":
|
|
return this;
|
|
/* c8 ignore start */
|
|
case "navigator":
|
|
return {
|
|
userAgent:
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36"
|
|
};
|
|
/* c8 ignore stop */
|
|
case "window":
|
|
return window.get(this);
|
|
case "customElements":
|
|
if (!this[CUSTOM_ELEMENTS].registry)
|
|
this[CUSTOM_ELEMENTS] = new CustomElementRegistry(this);
|
|
return this[CUSTOM_ELEMENTS];
|
|
case "performance":
|
|
return globalThis.performance;
|
|
case "DOMParser":
|
|
return this[DOM_PARSER];
|
|
case "Image":
|
|
if (!this[IMAGE]) this[IMAGE] = ImageClass(this);
|
|
return this[IMAGE];
|
|
case "MutationObserver":
|
|
if (!this[MUTATION_OBSERVER].class)
|
|
this[MUTATION_OBSERVER] = new MutationObserverClass(this);
|
|
return this[MUTATION_OBSERVER].class;
|
|
}
|
|
return (
|
|
(this[GLOBALS] && this[GLOBALS][name]) ||
|
|
globalExports[name] ||
|
|
globalThis[name]
|
|
);
|
|
}
|
|
})
|
|
);
|
|
return window.get(this);
|
|
}
|
|
|
|
get doctype() {
|
|
const docType = this[DOCTYPE];
|
|
if (docType) return docType;
|
|
const { firstChild } = this;
|
|
if (firstChild && firstChild.nodeType === DOCUMENT_TYPE_NODE)
|
|
return (this[DOCTYPE] = firstChild);
|
|
return null;
|
|
}
|
|
|
|
set doctype(value) {
|
|
if (
|
|
/^([a-z:]+)(\s+system|\s+public(\s+"([^"]+)")?)?(\s+"([^"]+)")?/i.test(
|
|
value
|
|
)
|
|
) {
|
|
const { $1: name, $4: publicId, $6: systemId } = RegExp;
|
|
this[DOCTYPE] = new DocumentType$1(this, name, publicId, systemId);
|
|
knownSiblings(this, this[DOCTYPE], this[NEXT]);
|
|
}
|
|
}
|
|
|
|
get documentElement() {
|
|
return this.firstElementChild;
|
|
}
|
|
|
|
get isConnected() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
_getParent() {
|
|
return this[EVENT_TARGET];
|
|
}
|
|
|
|
createAttribute(name) {
|
|
return new Attr$1(this, name);
|
|
}
|
|
createCDATASection(data) {
|
|
return new CDATASection$1(this, data);
|
|
}
|
|
createComment(textContent) {
|
|
return new Comment$2(this, textContent);
|
|
}
|
|
createDocumentFragment() {
|
|
return new DocumentFragment$1(this);
|
|
}
|
|
createDocumentType(name, publicId, systemId) {
|
|
return new DocumentType$1(this, name, publicId, systemId);
|
|
}
|
|
createElement(localName) {
|
|
return new Element$1(this, localName);
|
|
}
|
|
createRange() {
|
|
const range = new Range();
|
|
range.commonAncestorContainer = this;
|
|
return range;
|
|
}
|
|
createTextNode(textContent) {
|
|
return new Text$1(this, textContent);
|
|
}
|
|
createTreeWalker(root, whatToShow = -1) {
|
|
return new TreeWalker(root, whatToShow);
|
|
}
|
|
createNodeIterator(root, whatToShow = -1) {
|
|
return this.createTreeWalker(root, whatToShow);
|
|
}
|
|
|
|
createEvent(name) {
|
|
const event = create$1(
|
|
name === "Event" ? new GlobalEvent("") : new CustomEvent("")
|
|
);
|
|
event.initEvent = event.initCustomEvent = (
|
|
type,
|
|
canBubble = false,
|
|
cancelable = false,
|
|
detail
|
|
) => {
|
|
event.bubbles = !!canBubble;
|
|
|
|
defineProperties(event, {
|
|
type: { value: type },
|
|
canBubble: { value: canBubble },
|
|
cancelable: { value: cancelable },
|
|
detail: { value: detail }
|
|
});
|
|
};
|
|
return event;
|
|
}
|
|
|
|
cloneNode(deep = false) {
|
|
const {
|
|
constructor,
|
|
[CUSTOM_ELEMENTS]: customElements,
|
|
[DOCTYPE]: doctype
|
|
} = this;
|
|
const document = new constructor();
|
|
document[CUSTOM_ELEMENTS] = customElements;
|
|
if (deep) {
|
|
const end = document[END];
|
|
const { childNodes } = this;
|
|
for (let { length } = childNodes, i = 0; i < length; i++)
|
|
document.insertBefore(childNodes[i].cloneNode(true), end);
|
|
if (doctype) document[DOCTYPE] = childNodes[0];
|
|
}
|
|
return document;
|
|
}
|
|
|
|
importNode(externalNode) {
|
|
// important: keep the signature length as *one*
|
|
// or it would behave like old IE or Edge with polyfills
|
|
const deep = 1 < arguments.length && !!arguments[1];
|
|
const node = externalNode.cloneNode(deep);
|
|
const { [CUSTOM_ELEMENTS]: customElements } = this;
|
|
const { active } = customElements;
|
|
const upgrade = (element) => {
|
|
const { ownerDocument, nodeType } = element;
|
|
element.ownerDocument = this;
|
|
if (active && ownerDocument !== this && nodeType === ELEMENT_NODE)
|
|
customElements.upgrade(element);
|
|
};
|
|
upgrade(node);
|
|
if (deep) {
|
|
switch (node.nodeType) {
|
|
case ELEMENT_NODE:
|
|
case DOCUMENT_FRAGMENT_NODE: {
|
|
let { [NEXT]: next, [END]: end } = node;
|
|
while (next !== end) {
|
|
if (next.nodeType === ELEMENT_NODE) upgrade(next);
|
|
next = next[NEXT];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
toString() {
|
|
return this.childNodes.join("");
|
|
}
|
|
|
|
querySelector(selectors) {
|
|
return query(super.querySelector, this, selectors);
|
|
}
|
|
|
|
querySelectorAll(selectors) {
|
|
return query(super.querySelectorAll, this, selectors);
|
|
}
|
|
|
|
/* c8 ignore start */
|
|
getElementsByTagNameNS(_, name) {
|
|
return this.getElementsByTagName(name);
|
|
}
|
|
createAttributeNS(_, name) {
|
|
return this.createAttribute(name);
|
|
}
|
|
createElementNS(nsp, localName, options) {
|
|
return nsp === SVG_NAMESPACE
|
|
? new SVGElement$1(this, localName, null)
|
|
: this.createElement(localName, options);
|
|
}
|
|
/* c8 ignore stop */
|
|
};
|
|
|
|
setPrototypeOf(
|
|
(globalExports.Document = function Document() {
|
|
illegalConstructor();
|
|
}),
|
|
Document$1
|
|
).prototype = Document$1.prototype;
|
|
|
|
const createHTMLElement$1 = (ownerDocument, builtin, localName, options) => {
|
|
if (!builtin && htmlClasses.has(localName)) {
|
|
const Class = htmlClasses.get(localName);
|
|
return new Class(ownerDocument, localName);
|
|
}
|
|
const {
|
|
[CUSTOM_ELEMENTS]: { active, registry }
|
|
} = ownerDocument;
|
|
if (active) {
|
|
const ce = builtin ? options.is : localName;
|
|
if (registry.has(ce)) {
|
|
const { Class } = registry.get(ce);
|
|
const element = new Class(ownerDocument, localName);
|
|
customElements.set(element, { connected: false });
|
|
return element;
|
|
}
|
|
}
|
|
return new HTMLElement(ownerDocument, localName);
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.HTMLDocument
|
|
*/
|
|
class HTMLDocument extends Document$1 {
|
|
constructor() {
|
|
super("text/html");
|
|
}
|
|
|
|
get all() {
|
|
const nodeList = new NodeList();
|
|
let { [NEXT]: next, [END]: end } = this;
|
|
while (next !== end) {
|
|
switch (next.nodeType) {
|
|
case ELEMENT_NODE:
|
|
nodeList.push(next);
|
|
break;
|
|
}
|
|
next = next[NEXT];
|
|
}
|
|
return nodeList;
|
|
}
|
|
|
|
/**
|
|
* @type HTMLHeadElement
|
|
*/
|
|
get head() {
|
|
const { documentElement } = this;
|
|
let { firstElementChild } = documentElement;
|
|
if (!firstElementChild || firstElementChild.tagName !== "HEAD") {
|
|
firstElementChild = this.createElement("head");
|
|
documentElement.prepend(firstElementChild);
|
|
}
|
|
return firstElementChild;
|
|
}
|
|
|
|
/**
|
|
* @type HTMLBodyElement
|
|
*/
|
|
get body() {
|
|
const { head } = this;
|
|
let { nextElementSibling } = head;
|
|
if (!nextElementSibling || nextElementSibling.tagName !== "BODY") {
|
|
nextElementSibling = this.createElement("body");
|
|
head.after(nextElementSibling);
|
|
}
|
|
return nextElementSibling;
|
|
}
|
|
|
|
/**
|
|
* @type HTMLTitleElement
|
|
*/
|
|
get title() {
|
|
const { head } = this;
|
|
return head.getElementsByTagName("title").at(0)?.textContent || "";
|
|
}
|
|
|
|
set title(textContent) {
|
|
const { head } = this;
|
|
let title = head.getElementsByTagName("title").at(0);
|
|
if (title) title.textContent = textContent;
|
|
else {
|
|
head.insertBefore(
|
|
this.createElement("title"),
|
|
head.firstChild
|
|
).textContent = textContent;
|
|
}
|
|
}
|
|
|
|
createElement(localName, options) {
|
|
const builtin = !!(options && options.is);
|
|
const element = createHTMLElement$1(this, builtin, localName, options);
|
|
if (builtin) element.setAttribute("is", options.is);
|
|
return element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.Document
|
|
*/
|
|
class SVGDocument extends Document$1 {
|
|
constructor() {
|
|
super("image/svg+xml");
|
|
}
|
|
toString() {
|
|
return this[MIME].docType + super.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.XMLDocument
|
|
*/
|
|
class XMLDocument extends Document$1 {
|
|
constructor() {
|
|
super("text/xml");
|
|
}
|
|
toString() {
|
|
return this[MIME].docType + super.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @implements globalThis.DOMParser
|
|
*/
|
|
class DOMParser {
|
|
/** @typedef {{ "text/html": HTMLDocument, "image/svg+xml": SVGDocument, "text/xml": XMLDocument }} MimeToDoc */
|
|
/**
|
|
* @template {keyof MimeToDoc} MIME
|
|
* @param {string} markupLanguage
|
|
* @param {MIME} mimeType
|
|
* @returns {MimeToDoc[MIME]}
|
|
*/
|
|
parseFromString(markupLanguage, mimeType, globals = null) {
|
|
let isHTML = false,
|
|
document;
|
|
if (mimeType === "text/html") {
|
|
isHTML = true;
|
|
document = new HTMLDocument();
|
|
} else if (mimeType === "image/svg+xml") document = new SVGDocument();
|
|
else document = new XMLDocument();
|
|
document[DOM_PARSER] = DOMParser;
|
|
if (globals) document[GLOBALS] = globals;
|
|
if (isHTML && markupLanguage === "...")
|
|
markupLanguage = "<!doctype html><html><head></head><body></body></html>";
|
|
return markupLanguage
|
|
? parseFromString(document, isHTML, markupLanguage)
|
|
: document;
|
|
}
|
|
}
|
|
|
|
const { parse } = JSON;
|
|
|
|
const append = (parentNode, node, end) => {
|
|
node.parentNode = parentNode;
|
|
knownSiblings(end[PREV], node, end);
|
|
};
|
|
|
|
const createHTMLElement = (ownerDocument, localName) => {
|
|
if (htmlClasses.has(localName)) {
|
|
const Class = htmlClasses.get(localName);
|
|
return new Class(ownerDocument, localName);
|
|
}
|
|
return new HTMLElement(ownerDocument, localName);
|
|
};
|
|
|
|
/**
|
|
* @typedef {number|string} jsdonValue - either a node type or its content
|
|
*/
|
|
|
|
/**
|
|
* Given a stringified, or arrayfied DOM element, returns an HTMLDocument
|
|
* that represent the content of such string, or array.
|
|
* @param {string|jsdonValue[]} value
|
|
* @returns {HTMLDocument}
|
|
*/
|
|
const parseJSON = (value) => {
|
|
const array = typeof value === "string" ? parse(value) : value;
|
|
const { length } = array;
|
|
const document = new HTMLDocument();
|
|
let parentNode = document,
|
|
end = parentNode[END],
|
|
svg = false,
|
|
i = 0;
|
|
while (i < length) {
|
|
let nodeType = array[i++];
|
|
switch (nodeType) {
|
|
case ELEMENT_NODE: {
|
|
const localName = array[i++];
|
|
const isSVG = svg || localName === "svg" || localName === "SVG";
|
|
const element = isSVG
|
|
? new SVGElement$1(
|
|
document,
|
|
localName,
|
|
parentNode.ownerSVGElement || null
|
|
)
|
|
: createHTMLElement(document, localName);
|
|
knownBoundaries(end[PREV], element, end);
|
|
element.parentNode = parentNode;
|
|
parentNode = element;
|
|
end = parentNode[END];
|
|
svg = isSVG;
|
|
break;
|
|
}
|
|
case ATTRIBUTE_NODE: {
|
|
const name = array[i++];
|
|
const value = typeof array[i] === "string" ? array[i++] : "";
|
|
const attr = new Attr$1(document, name, value);
|
|
attr.ownerElement = parentNode;
|
|
knownSiblings(end[PREV], attr, end);
|
|
break;
|
|
}
|
|
case TEXT_NODE:
|
|
append(parentNode, new Text$1(document, array[i++]), end);
|
|
break;
|
|
case COMMENT_NODE:
|
|
append(parentNode, new Comment$2(document, array[i++]), end);
|
|
break;
|
|
case CDATA_SECTION_NODE:
|
|
append(parentNode, new CDATASection$1(document, array[i++]), end);
|
|
break;
|
|
case DOCUMENT_TYPE_NODE: {
|
|
const args = [document];
|
|
while (typeof array[i] === "string") args.push(array[i++]);
|
|
if (args.length === 3 && /\.dtd$/i.test(args[2])) args.splice(2, 0, "");
|
|
append(parentNode, new DocumentType$1(...args), end);
|
|
break;
|
|
}
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
parentNode = document.createDocumentFragment();
|
|
end = parentNode[END];
|
|
/* eslint no-fallthrough:0 */
|
|
case DOCUMENT_NODE:
|
|
break;
|
|
default:
|
|
do {
|
|
nodeType -= NODE_END;
|
|
if (svg && !parentNode.ownerSVGElement) svg = false;
|
|
parentNode = parentNode.parentNode || parentNode;
|
|
} while (nodeType < 0);
|
|
end = parentNode[END];
|
|
break;
|
|
}
|
|
}
|
|
switch (i && array[0]) {
|
|
case ELEMENT_NODE:
|
|
return document.firstElementChild;
|
|
case DOCUMENT_FRAGMENT_NODE:
|
|
return parentNode;
|
|
}
|
|
return document;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {Document|Element} node the Document or Element to serialize
|
|
* @returns {jsdonValue[]} the linear jsdon serialized array
|
|
*/
|
|
const toJSON = (node) => node.toJSON();
|
|
|
|
class NodeFilter {
|
|
static get SHOW_ALL() {
|
|
return SHOW_ALL;
|
|
}
|
|
static get SHOW_ELEMENT() {
|
|
return SHOW_ELEMENT;
|
|
}
|
|
static get SHOW_COMMENT() {
|
|
return SHOW_COMMENT;
|
|
}
|
|
static get SHOW_CDATA_SECTION() {
|
|
return SHOW_CDATA_SECTION;
|
|
}
|
|
static get SHOW_TEXT() {
|
|
return SHOW_TEXT;
|
|
}
|
|
}
|
|
|
|
const parseHTML = (html, globals = null) =>
|
|
new DOMParser().parseFromString(html, "text/html", globals).defaultView;
|
|
|
|
function Document() {
|
|
illegalConstructor();
|
|
}
|
|
|
|
setPrototypeOf(Document, Document$1).prototype = Document$1.prototype;
|
|
|
|
export {
|
|
Attr,
|
|
CDATASection,
|
|
CharacterData,
|
|
Comment,
|
|
CustomEvent,
|
|
DOMParser,
|
|
Document,
|
|
DocumentFragment,
|
|
DocumentType,
|
|
Element,
|
|
GlobalEvent as Event,
|
|
DOMEventTarget as EventTarget,
|
|
Facades,
|
|
HTMLAnchorElement,
|
|
HTMLAreaElement,
|
|
HTMLAudioElement,
|
|
HTMLBRElement,
|
|
HTMLBaseElement,
|
|
HTMLBodyElement,
|
|
HTMLButtonElement,
|
|
HTMLCanvasElement,
|
|
HTMLClasses,
|
|
HTMLDListElement,
|
|
HTMLDataElement,
|
|
HTMLDataListElement,
|
|
HTMLDetailsElement,
|
|
HTMLDirectoryElement,
|
|
HTMLDivElement,
|
|
HTMLElement,
|
|
HTMLEmbedElement,
|
|
HTMLFieldSetElement,
|
|
HTMLFontElement,
|
|
HTMLFormElement,
|
|
HTMLFrameElement,
|
|
HTMLFrameSetElement,
|
|
HTMLHRElement,
|
|
HTMLHeadElement,
|
|
HTMLHeadingElement,
|
|
HTMLHtmlElement,
|
|
HTMLIFrameElement,
|
|
HTMLImageElement,
|
|
HTMLInputElement,
|
|
HTMLLIElement,
|
|
HTMLLabelElement,
|
|
HTMLLegendElement,
|
|
HTMLLinkElement,
|
|
HTMLMapElement,
|
|
HTMLMarqueeElement,
|
|
HTMLMediaElement,
|
|
HTMLMenuElement,
|
|
HTMLMetaElement,
|
|
HTMLMeterElement,
|
|
HTMLModElement,
|
|
HTMLOListElement,
|
|
HTMLObjectElement,
|
|
HTMLOptGroupElement,
|
|
HTMLOptionElement,
|
|
HTMLOutputElement,
|
|
HTMLParagraphElement,
|
|
HTMLParamElement,
|
|
HTMLPictureElement,
|
|
HTMLPreElement,
|
|
HTMLProgressElement,
|
|
HTMLQuoteElement,
|
|
HTMLScriptElement,
|
|
HTMLSelectElement,
|
|
HTMLSlotElement,
|
|
HTMLSourceElement,
|
|
HTMLSpanElement,
|
|
HTMLStyleElement,
|
|
HTMLTableCaptionElement,
|
|
HTMLTableCellElement,
|
|
HTMLTableElement,
|
|
HTMLTableRowElement,
|
|
HTMLTemplateElement,
|
|
HTMLTextAreaElement,
|
|
HTMLTimeElement,
|
|
HTMLTitleElement,
|
|
HTMLTrackElement,
|
|
HTMLUListElement,
|
|
HTMLUnknownElement,
|
|
HTMLVideoElement,
|
|
InputEvent,
|
|
Node,
|
|
NodeFilter,
|
|
NodeList,
|
|
SVGElement,
|
|
ShadowRoot,
|
|
Text,
|
|
illegalConstructor,
|
|
parseHTML,
|
|
parseJSON,
|
|
toJSON
|
|
};
|