ivya
Version:
Fork of Playwright's locator resolution
1,848 lines (1,842 loc) • 167 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
// src/cssTokenizer.ts
var between = function(num, first, last) {
return num >= first && num <= last;
};
function digit(code) {
return between(code, 48, 57);
}
function hexdigit(code) {
return digit(code) || between(code, 65, 70) || between(code, 97, 102);
}
function uppercaseletter(code) {
return between(code, 65, 90);
}
function lowercaseletter(code) {
return between(code, 97, 122);
}
function letter(code) {
return uppercaseletter(code) || lowercaseletter(code);
}
function nonascii(code) {
return code >= 128;
}
function namestartchar(code) {
return letter(code) || nonascii(code) || code === 95;
}
function namechar(code) {
return namestartchar(code) || digit(code) || code === 45;
}
function nonprintable(code) {
return between(code, 0, 8) || code === 11 || between(code, 14, 31) || code === 127;
}
function newline(code) {
return code === 10;
}
function whitespace(code) {
return newline(code) || code === 9 || code === 32;
}
var maximumallowedcodepoint = 1114111;
var InvalidCharacterError = class extends Error {
constructor(message) {
super(message);
this.name = "InvalidCharacterError";
}
};
function preprocess(str) {
const codepoints = [];
for (let i = 0; i < str.length; i++) {
let code = str.charCodeAt(i);
if (code === 13 && str.charCodeAt(i + 1) === 10) {
code = 10;
i++;
}
if (code === 13 || code === 12) {
code = 10;
}
if (code === 0) {
code = 65533;
}
if (between(code, 55296, 56319) && between(str.charCodeAt(i + 1), 56320, 57343)) {
const lead = code - 55296;
const trail = str.charCodeAt(i + 1) - 56320;
code = 2 ** 16 + lead * 2 ** 10 + trail;
i++;
}
codepoints.push(code);
}
return codepoints;
}
function stringFromCode(code) {
if (code <= 65535) {
return String.fromCharCode(code);
}
code -= 2 ** 16;
const lead = Math.floor(code / 2 ** 10) + 55296;
const trail = code % 2 ** 10 + 56320;
return String.fromCharCode(lead) + String.fromCharCode(trail);
}
function tokenize(str1) {
const str = preprocess(str1);
let i = -1;
const tokens = [];
let code;
let line = 0;
let column = 0;
let lastLineLength = 0;
const incrLineno = function() {
line += 1;
lastLineLength = column;
column = 0;
};
const locStart = { line, column };
const codepoint = function(i2) {
if (i2 >= str.length) {
return -1;
}
return str[i2];
};
const next = function(num) {
if (num === void 0) {
num = 1;
}
if (num > 3) {
throw "Spec Error: no more than three codepoints of lookahead.";
}
return codepoint(i + num);
};
const consume = function(num) {
if (num === void 0) {
num = 1;
}
i += num;
code = codepoint(i);
if (newline(code)) {
incrLineno();
} else {
column += num;
}
return true;
};
const reconsume = function() {
i -= 1;
if (newline(code)) {
line -= 1;
column = lastLineLength;
} else {
column -= 1;
}
locStart.line = line;
locStart.column = column;
return true;
};
const eof = function(codepoint2) {
if (codepoint2 === void 0) {
codepoint2 = code;
}
return codepoint2 === -1;
};
const donothing = function() {
};
const parseerror = function() {
};
const consumeAToken = function() {
consumeComments();
consume();
if (whitespace(code)) {
while (whitespace(next())) {
consume();
}
return new WhitespaceToken();
} else if (code === 34) {
return consumeAStringToken();
} else if (code === 35) {
if (namechar(next()) || areAValidEscape(next(1), next(2))) {
const token = new HashToken("");
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
token.type = "id";
}
token.value = consumeAName();
return token;
} else {
return new DelimToken(code);
}
} else if (code === 36) {
if (next() === 61) {
consume();
return new SuffixMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 39) {
return consumeAStringToken();
} else if (code === 40) {
return new OpenParenToken();
} else if (code === 41) {
return new CloseParenToken();
} else if (code === 42) {
if (next() === 61) {
consume();
return new SubstringMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 43) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else {
return new DelimToken(code);
}
} else if (code === 44) {
return new CommaToken();
} else if (code === 45) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else if (next(1) === 45 && next(2) === 62) {
consume(2);
return new CDCToken();
} else if (startsWithAnIdentifier()) {
reconsume();
return consumeAnIdentlikeToken();
} else {
return new DelimToken(code);
}
} else if (code === 46) {
if (startsWithANumber()) {
reconsume();
return consumeANumericToken();
} else {
return new DelimToken(code);
}
} else if (code === 58) {
return new ColonToken();
} else if (code === 59) {
return new SemicolonToken();
} else if (code === 60) {
if (next(1) === 33 && next(2) === 45 && next(3) === 45) {
consume(3);
return new CDOToken();
} else {
return new DelimToken(code);
}
} else if (code === 64) {
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
return new AtKeywordToken(consumeAName());
} else {
return new DelimToken(code);
}
} else if (code === 91) {
return new OpenSquareToken();
} else if (code === 92) {
if (startsWithAValidEscape()) {
reconsume();
return consumeAnIdentlikeToken();
} else {
parseerror();
return new DelimToken(code);
}
} else if (code === 93) {
return new CloseSquareToken();
} else if (code === 94) {
if (next() === 61) {
consume();
return new PrefixMatchToken();
} else {
return new DelimToken(code);
}
} else if (code === 123) {
return new OpenCurlyToken();
} else if (code === 124) {
if (next() === 61) {
consume();
return new DashMatchToken();
} else if (next() === 124) {
consume();
return new ColumnToken();
} else {
return new DelimToken(code);
}
} else if (code === 125) {
return new CloseCurlyToken();
} else if (code === 126) {
if (next() === 61) {
consume();
return new IncludeMatchToken();
} else {
return new DelimToken(code);
}
} else if (digit(code)) {
reconsume();
return consumeANumericToken();
} else if (namestartchar(code)) {
reconsume();
return consumeAnIdentlikeToken();
} else if (eof()) {
return new EOFToken();
} else {
return new DelimToken(code);
}
};
const consumeComments = function() {
while (next(1) === 47 && next(2) === 42) {
consume(2);
while (true) {
consume();
if (code === 42 && next() === 47) {
consume();
break;
} else if (eof()) {
parseerror();
return;
}
}
}
};
const consumeANumericToken = function() {
const num = consumeANumber();
if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
const token = new DimensionToken();
token.value = num.value;
token.repr = num.repr;
token.type = num.type;
token.unit = consumeAName();
return token;
} else if (next() === 37) {
consume();
const token = new PercentageToken();
token.value = num.value;
token.repr = num.repr;
return token;
} else {
const token = new NumberToken();
token.value = num.value;
token.repr = num.repr;
token.type = num.type;
return token;
}
};
const consumeAnIdentlikeToken = function() {
const str2 = consumeAName();
if (str2.toLowerCase() === "url" && next() === 40) {
consume();
while (whitespace(next(1)) && whitespace(next(2))) {
consume();
}
if (next() === 34 || next() === 39) {
return new FunctionToken(str2);
} else if (whitespace(next()) && (next(2) === 34 || next(2) === 39)) {
return new FunctionToken(str2);
} else {
return consumeAURLToken();
}
} else if (next() === 40) {
consume();
return new FunctionToken(str2);
} else {
return new IdentToken(str2);
}
};
const consumeAStringToken = function(endingCodePoint) {
if (endingCodePoint === void 0) {
endingCodePoint = code;
}
let string = "";
while (consume()) {
if (code === endingCodePoint || eof()) {
return new StringToken(string);
} else if (newline(code)) {
parseerror();
reconsume();
return new BadStringToken();
} else if (code === 92) {
if (eof(next())) {
donothing();
} else if (newline(next())) {
consume();
} else {
string += stringFromCode(consumeEscape());
}
} else {
string += stringFromCode(code);
}
}
throw new Error("Internal error");
};
const consumeAURLToken = function() {
const token = new URLToken("");
while (whitespace(next())) {
consume();
}
if (eof(next())) {
return token;
}
while (consume()) {
if (code === 41 || eof()) {
return token;
} else if (whitespace(code)) {
while (whitespace(next())) {
consume();
}
if (next() === 41 || eof(next())) {
consume();
return token;
} else {
consumeTheRemnantsOfABadURL();
return new BadURLToken();
}
} else if (code === 34 || code === 39 || code === 40 || nonprintable(code)) {
parseerror();
consumeTheRemnantsOfABadURL();
return new BadURLToken();
} else if (code === 92) {
if (startsWithAValidEscape()) {
token.value += stringFromCode(consumeEscape());
} else {
parseerror();
consumeTheRemnantsOfABadURL();
return new BadURLToken();
}
} else {
token.value += stringFromCode(code);
}
}
throw new Error("Internal error");
};
const consumeEscape = function() {
consume();
if (hexdigit(code)) {
const digits = [code];
for (let total = 0; total < 5; total++) {
if (hexdigit(next())) {
consume();
digits.push(code);
} else {
break;
}
}
if (whitespace(next())) {
consume();
}
let value = Number.parseInt(
digits.map((x) => {
return String.fromCharCode(x);
}).join(""),
16
);
if (value > maximumallowedcodepoint) {
value = 65533;
}
return value;
} else if (eof()) {
return 65533;
} else {
return code;
}
};
const areAValidEscape = function(c1, c2) {
if (c1 !== 92) {
return false;
}
if (newline(c2)) {
return false;
}
return true;
};
const startsWithAValidEscape = function() {
return areAValidEscape(code, next());
};
const wouldStartAnIdentifier = function(c1, c2, c3) {
if (c1 === 45) {
return namestartchar(c2) || c2 === 45 || areAValidEscape(c2, c3);
} else if (namestartchar(c1)) {
return true;
} else if (c1 === 92) {
return areAValidEscape(c1, c2);
} else {
return false;
}
};
const startsWithAnIdentifier = function() {
return wouldStartAnIdentifier(code, next(1), next(2));
};
const wouldStartANumber = function(c1, c2, c3) {
if (c1 === 43 || c1 === 45) {
if (digit(c2)) {
return true;
}
if (c2 === 46 && digit(c3)) {
return true;
}
return false;
} else if (c1 === 46) {
if (digit(c2)) {
return true;
}
return false;
} else if (digit(c1)) {
return true;
} else {
return false;
}
};
const startsWithANumber = function() {
return wouldStartANumber(code, next(1), next(2));
};
const consumeAName = function() {
let result = "";
while (consume()) {
if (namechar(code)) {
result += stringFromCode(code);
} else if (startsWithAValidEscape()) {
result += stringFromCode(consumeEscape());
} else {
reconsume();
return result;
}
}
throw new Error("Internal parse error");
};
const consumeANumber = function() {
let repr = "";
let type = "integer";
if (next() === 43 || next() === 45) {
consume();
repr += stringFromCode(code);
}
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
if (next(1) === 46 && digit(next(2))) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = "number";
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
}
const c1 = next(1);
const c2 = next(2);
const c3 = next(3);
if ((c1 === 69 || c1 === 101) && digit(c2)) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = "number";
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
} else if ((c1 === 69 || c1 === 101) && (c2 === 43 || c2 === 45) && digit(c3)) {
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
consume();
repr += stringFromCode(code);
type = "number";
while (digit(next())) {
consume();
repr += stringFromCode(code);
}
}
const value = convertAStringToANumber(repr);
return { type, value, repr };
};
const convertAStringToANumber = function(string) {
return +string;
};
const consumeTheRemnantsOfABadURL = function() {
while (consume()) {
if (code === 41 || eof()) {
return;
} else if (startsWithAValidEscape()) {
consumeEscape();
donothing();
} else {
donothing();
}
}
};
let iterationCount = 0;
while (!eof(next())) {
tokens.push(consumeAToken());
iterationCount++;
if (iterationCount > str.length * 2) {
throw new Error("I'm infinite-looping!");
}
}
return tokens;
}
var CSSParserToken = class {
constructor() {
__publicField(this, "tokenType", "");
__publicField(this, "value");
}
toJSON() {
return { token: this.tokenType };
}
toString() {
return this.tokenType;
}
toSource() {
return `${this}`;
}
};
var BadStringToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "BADSTRING");
}
};
var BadURLToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "BADURL");
}
};
var WhitespaceToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "WHITESPACE");
}
toString() {
return "WS";
}
toSource() {
return " ";
}
};
var CDOToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "CDO");
}
toSource() {
return "<!--";
}
};
var CDCToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "CDC");
}
toSource() {
return "-->";
}
};
var ColonToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", ":");
}
};
var SemicolonToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", ";");
}
};
var CommaToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", ",");
}
};
var GroupingToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "value", "");
__publicField(this, "mirror", "");
}
};
var OpenCurlyToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", "{");
this.value = "{";
this.mirror = "}";
}
};
var CloseCurlyToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", "}");
this.value = "}";
this.mirror = "{";
}
};
var OpenSquareToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", "[");
this.value = "[";
this.mirror = "]";
}
};
var CloseSquareToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", "]");
this.value = "]";
this.mirror = "[";
}
};
var OpenParenToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", "(");
this.value = "(";
this.mirror = ")";
}
};
var CloseParenToken = class extends GroupingToken {
constructor() {
super();
__publicField(this, "tokenType", ")");
this.value = ")";
this.mirror = "(";
}
};
var IncludeMatchToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "~=");
}
};
var DashMatchToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "|=");
}
};
var PrefixMatchToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "^=");
}
};
var SuffixMatchToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "$=");
}
};
var SubstringMatchToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "*=");
}
};
var ColumnToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "||");
}
};
var EOFToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "tokenType", "EOF");
}
toSource() {
return "";
}
};
var DelimToken = class extends CSSParserToken {
constructor(code) {
super();
__publicField(this, "tokenType", "DELIM");
__publicField(this, "value", "");
this.value = stringFromCode(code);
}
toString() {
return `DELIM(${this.value})`;
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
return json;
}
toSource() {
if (this.value === "\\") {
return "\\\n";
} else {
return this.value;
}
}
};
var StringValuedToken = class extends CSSParserToken {
constructor() {
super(...arguments);
__publicField(this, "value", "");
}
ASCIIMatch(str) {
return this.value.toLowerCase() === str.toLowerCase();
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
return json;
}
};
var IdentToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "IDENT");
this.value = val;
}
toString() {
return `IDENT(${this.value})`;
}
toSource() {
return escapeIdent(this.value);
}
};
var FunctionToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "FUNCTION");
__publicField(this, "mirror");
this.value = val;
this.mirror = ")";
}
toString() {
return `FUNCTION(${this.value})`;
}
toSource() {
return `${escapeIdent(this.value)}(`;
}
};
var AtKeywordToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "AT-KEYWORD");
this.value = val;
}
toString() {
return `AT(${this.value})`;
}
toSource() {
return `@${escapeIdent(this.value)}`;
}
};
var HashToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "HASH");
__publicField(this, "type");
this.value = val;
this.type = "unrestricted";
}
toString() {
return `HASH(${this.value})`;
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.type = this.type;
return json;
}
toSource() {
if (this.type === "id") {
return `#${escapeIdent(this.value)}`;
} else {
return `#${escapeHash(this.value)}`;
}
}
};
var StringToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "STRING");
this.value = val;
}
toString() {
return `"${escapeString(this.value)}"`;
}
};
var URLToken = class extends StringValuedToken {
constructor(val) {
super();
__publicField(this, "tokenType", "URL");
this.value = val;
}
toString() {
return `URL(${this.value})`;
}
toSource() {
return `url("${escapeString(this.value)}")`;
}
};
var NumberToken = class extends CSSParserToken {
constructor() {
super();
__publicField(this, "tokenType", "NUMBER");
__publicField(this, "type");
__publicField(this, "repr");
this.type = "integer";
this.repr = "";
}
toString() {
if (this.type === "integer") {
return `INT(${this.value})`;
}
return `NUMBER(${this.value})`;
}
toJSON() {
const json = super.toJSON();
json.value = this.value;
json.type = this.type;
json.repr = this.repr;
return json;
}
toSource() {
return this.repr;
}
};
var PercentageToken = class extends CSSParserToken {
constructor() {
super();
__publicField(this, "tokenType", "PERCENTAGE");
__publicField(this, "repr");
this.repr = "";
}
toString() {
return `PERCENTAGE(${this.value})`;
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.repr = this.repr;
return json;
}
toSource() {
return `${this.repr}%`;
}
};
var DimensionToken = class extends CSSParserToken {
constructor() {
super();
__publicField(this, "tokenType", "DIMENSION");
__publicField(this, "type");
__publicField(this, "repr");
__publicField(this, "unit");
this.type = "integer";
this.repr = "";
this.unit = "";
}
toString() {
return `DIM(${this.value},${this.unit})`;
}
toJSON() {
const json = this.constructor.prototype.constructor.prototype.toJSON.call(this);
json.value = this.value;
json.type = this.type;
json.repr = this.repr;
json.unit = this.unit;
return json;
}
toSource() {
const source = this.repr;
let unit = escapeIdent(this.unit);
if (unit[0].toLowerCase() === "e" && (unit[1] === "-" || between(unit.charCodeAt(1), 48, 57))) {
unit = `\\65 ${unit.slice(1, unit.length)}`;
}
return source + unit;
}
};
function escapeIdent(string) {
string = `${string}`;
let result = "";
const firstcode = string.charCodeAt(0);
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0) {
throw new InvalidCharacterError(
"Invalid character: the input contains U+0000."
);
}
if (between(code, 1, 31) || code === 127 || i === 0 && between(code, 48, 57) || i === 1 && between(code, 48, 57) && firstcode === 45) {
result += `\\${code.toString(16)} `;
} else if (code >= 128 || code === 45 || code === 95 || between(code, 48, 57) || between(code, 65, 90) || between(code, 97, 122)) {
result += string[i];
} else {
result += `\\${string[i]}`;
}
}
return result;
}
function escapeHash(string) {
string = `${string}`;
let result = "";
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0) {
throw new InvalidCharacterError(
"Invalid character: the input contains U+0000."
);
}
if (code >= 128 || code === 45 || code === 95 || between(code, 48, 57) || between(code, 65, 90) || between(code, 97, 122)) {
result += string[i];
} else {
result += `\\${code.toString(16)} `;
}
}
return result;
}
function escapeString(string) {
string = `${string}`;
let result = "";
for (let i = 0; i < string.length; i++) {
const code = string.charCodeAt(i);
if (code === 0) {
throw new InvalidCharacterError(
"Invalid character: the input contains U+0000."
);
}
if (between(code, 1, 31) || code === 127) {
result += `\\${code.toString(16)} `;
} else if (code === 34 || code === 92) {
result += `\\${string[i]}`;
} else {
result += string[i];
}
}
return result;
}
// src/cssParser.ts
var InvalidSelectorError = class extends Error {
};
function parseCSS(selector, customNames) {
let tokens;
try {
tokens = tokenize(selector);
if (!(tokens[tokens.length - 1] instanceof EOFToken)) {
tokens.push(new EOFToken());
}
} catch (e) {
const newMessage = `${e.message} while parsing selector "${selector}"`;
const index = (e.stack || "").indexOf(e.message);
if (index !== -1) {
e.stack = e.stack.substring(0, index) + newMessage + e.stack.substring(index + e.message.length);
}
e.message = newMessage;
throw e;
}
const unsupportedToken = tokens.find((token) => {
return token instanceof AtKeywordToken || token instanceof BadStringToken || token instanceof BadURLToken || token instanceof ColumnToken || token instanceof CDOToken || token instanceof CDCToken || token instanceof SemicolonToken || // TODO: Consider using these for something, e.g. to escape complex strings.
// For example :xpath{ (//div/bar[@attr="foo"])[2]/baz }
// Or this way :xpath( {complex-xpath-goes-here("hello")} )
token instanceof OpenCurlyToken || token instanceof CloseCurlyToken || // TODO: Consider treating these as strings?
token instanceof URLToken || token instanceof PercentageToken;
});
if (unsupportedToken) {
throw new InvalidSelectorError(
`Unsupported token "${unsupportedToken.toSource()}" while parsing selector "${selector}"`
);
}
let pos = 0;
const names = /* @__PURE__ */ new Set();
function unexpected() {
return new InvalidSelectorError(
`Unexpected token "${tokens[pos].toSource()}" while parsing selector "${selector}"`
);
}
function skipWhitespace() {
while (tokens[pos] instanceof WhitespaceToken) {
pos++;
}
}
function isIdent(p = pos) {
return tokens[p] instanceof IdentToken;
}
function isString(p = pos) {
return tokens[p] instanceof StringToken;
}
function isNumber(p = pos) {
return tokens[p] instanceof NumberToken;
}
function isComma(p = pos) {
return tokens[p] instanceof CommaToken;
}
function isOpenParen(p = pos) {
return tokens[p] instanceof OpenParenToken;
}
function isCloseParen(p = pos) {
return tokens[p] instanceof CloseParenToken;
}
function isFunction(p = pos) {
return tokens[p] instanceof FunctionToken;
}
function isStar(p = pos) {
return tokens[p] instanceof DelimToken && tokens[p].value === "*";
}
function isEOF(p = pos) {
return tokens[p] instanceof EOFToken;
}
function isClauseCombinator(p = pos) {
return tokens[p] instanceof DelimToken && [">", "+", "~"].includes(tokens[p].value);
}
function isSelectorClauseEnd(p = pos) {
return isComma(p) || isCloseParen(p) || isEOF(p) || isClauseCombinator(p) || tokens[p] instanceof WhitespaceToken;
}
function consumeFunctionArguments() {
const result2 = [consumeArgument()];
while (true) {
skipWhitespace();
if (!isComma()) {
break;
}
pos++;
result2.push(consumeArgument());
}
return result2;
}
function consumeArgument() {
skipWhitespace();
if (isNumber()) {
return tokens[pos++].value;
}
if (isString()) {
return tokens[pos++].value;
}
return consumeComplexSelector();
}
function consumeComplexSelector() {
const result2 = { simples: [] };
skipWhitespace();
if (isClauseCombinator()) {
result2.simples.push({
selector: { functions: [{ name: "scope", args: [] }] },
combinator: ""
});
} else {
result2.simples.push({ selector: consumeSimpleSelector(), combinator: "" });
}
while (true) {
skipWhitespace();
if (isClauseCombinator()) {
result2.simples[result2.simples.length - 1].combinator = tokens[pos++].value;
skipWhitespace();
} else if (isSelectorClauseEnd()) {
break;
}
result2.simples.push({ combinator: "", selector: consumeSimpleSelector() });
}
return result2;
}
function consumeSimpleSelector() {
let rawCSSString = "";
const functions = [];
while (!isSelectorClauseEnd()) {
if (isIdent() || isStar()) {
rawCSSString += tokens[pos++].toSource();
} else if (tokens[pos] instanceof HashToken) {
rawCSSString += tokens[pos++].toSource();
} else if (tokens[pos] instanceof DelimToken && tokens[pos].value === ".") {
pos++;
if (isIdent()) {
rawCSSString += `.${tokens[pos++].toSource()}`;
} else {
throw unexpected();
}
} else if (tokens[pos] instanceof ColonToken) {
pos++;
if (isIdent()) {
if (!customNames.has(tokens[pos].value.toLowerCase())) {
rawCSSString += `:${tokens[pos++].toSource()}`;
} else {
const name = tokens[pos++].value.toLowerCase();
functions.push({ name, args: [] });
names.add(name);
}
} else if (isFunction()) {
const name = tokens[pos++].value.toLowerCase();
if (!customNames.has(name)) {
rawCSSString += `:${name}(${consumeBuiltinFunctionArguments()})`;
} else {
functions.push({ name, args: consumeFunctionArguments() });
names.add(name);
}
skipWhitespace();
if (!isCloseParen()) {
throw unexpected();
}
pos++;
} else {
throw unexpected();
}
} else if (tokens[pos] instanceof OpenSquareToken) {
rawCSSString += "[";
pos++;
while (!(tokens[pos] instanceof CloseSquareToken) && !isEOF()) {
rawCSSString += tokens[pos++].toSource();
}
if (!(tokens[pos] instanceof CloseSquareToken)) {
throw unexpected();
}
rawCSSString += "]";
pos++;
} else {
throw unexpected();
}
}
if (!rawCSSString && !functions.length) {
throw unexpected();
}
return { css: rawCSSString || void 0, functions };
}
function consumeBuiltinFunctionArguments() {
let s = "";
let balance = 1;
while (!isEOF()) {
if (isOpenParen() || isFunction()) {
balance++;
}
if (isCloseParen()) {
balance--;
}
if (!balance) {
break;
}
s += tokens[pos++].toSource();
}
return s;
}
const result = consumeFunctionArguments();
if (!isEOF()) {
throw unexpected();
}
if (result.some((arg) => typeof arg !== "object" || !("simples" in arg))) {
throw new InvalidSelectorError(`Error while parsing selector "${selector}"`);
}
return { selector: result, names: Array.from(names) };
}
// src/selectorParser.ts
var kNestedSelectorNames = /* @__PURE__ */ new Set([
"internal:has",
"internal:has-not",
"internal:and",
"internal:or",
"internal:chain",
"left-of",
"right-of",
"above",
"below",
"near"
]);
var kNestedSelectorNamesWithDistance = /* @__PURE__ */ new Set([
"left-of",
"right-of",
"above",
"below",
"near"
]);
var customCSSNames = /* @__PURE__ */ new Set([
"not",
"is",
"where",
"has",
"scope",
"light",
"visible",
"text",
"text-matches",
"text-is",
"has-text",
"above",
"below",
"right-of",
"left-of",
"near",
"nth-match"
]);
function parseSelector(selector) {
const parsedStrings = parseSelectorString(selector);
const parts = [];
for (const part of parsedStrings.parts) {
if (part.name === "css" || part.name === "css:light") {
if (part.name === "css:light") {
part.body = `:light(${part.body})`;
}
const parsedCSS = parseCSS(part.body, customCSSNames);
parts.push({
name: "css",
body: parsedCSS.selector,
source: part.body
});
continue;
}
if (kNestedSelectorNames.has(part.name)) {
let innerSelector;
let distance;
try {
const unescaped = JSON.parse(`[${part.body}]`);
if (!Array.isArray(unescaped) || unescaped.length < 1 || unescaped.length > 2 || typeof unescaped[0] !== "string") {
throw new InvalidSelectorError(
`Malformed selector: ${part.name}=${part.body}`
);
}
innerSelector = unescaped[0];
if (unescaped.length === 2) {
if (typeof unescaped[1] !== "number" || !kNestedSelectorNamesWithDistance.has(part.name)) {
throw new InvalidSelectorError(
`Malformed selector: ${part.name}=${part.body}`
);
}
distance = unescaped[1];
}
} catch (e) {
throw new InvalidSelectorError(
`Malformed selector: ${part.name}=${part.body}`
);
}
const nested = {
name: part.name,
source: part.body,
body: { parsed: parseSelector(innerSelector), distance }
};
const lastFrame = [...nested.body.parsed.parts].reverse().find(
(part2) => part2.name === "internal:control" && part2.body === "enter-frame"
);
const lastFrameIndex = lastFrame ? nested.body.parsed.parts.indexOf(lastFrame) : -1;
if (lastFrameIndex !== -1 && selectorPartsEqual(
nested.body.parsed.parts.slice(0, lastFrameIndex + 1),
parts.slice(0, lastFrameIndex + 1)
)) {
nested.body.parsed.parts.splice(0, lastFrameIndex + 1);
}
parts.push(nested);
continue;
}
parts.push({ ...part, source: part.body });
}
if (kNestedSelectorNames.has(parts[0].name)) {
throw new InvalidSelectorError(`"${parts[0].name}" selector cannot be first`);
}
return {
capture: parsedStrings.capture,
parts
};
}
function selectorPartsEqual(list1, list2) {
return stringifySelector({ parts: list1 }) === stringifySelector({ parts: list2 });
}
function stringifySelector(selector, forceEngineName) {
if (typeof selector === "string") {
return selector;
}
return selector.parts.map((p, i) => {
let includeEngine = true;
if (!forceEngineName && i !== selector.capture) {
if (p.name === "css") {
includeEngine = false;
} else if (p.name === "xpath" && p.source.startsWith("//") || p.source.startsWith("..")) {
includeEngine = false;
}
}
const prefix = includeEngine ? `${p.name}=` : "";
return `${i === selector.capture ? "*" : ""}${prefix}${p.source}`;
}).join(" >> ");
}
function visitAllSelectorParts(selector, visitor) {
const visit = (selector2, nested) => {
for (const part of selector2.parts) {
visitor(part, nested);
if (kNestedSelectorNames.has(part.name)) {
visit(part.body.parsed, true);
}
}
};
visit(selector, false);
}
function parseSelectorString(selector) {
let index = 0;
let quote;
let start = 0;
const result = { parts: [] };
const append = () => {
const part = selector.substring(start, index).trim();
const eqIndex = part.indexOf("=");
let name;
let body;
if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[\w\-+:*]+$/)) {
name = part.substring(0, eqIndex).trim();
body = part.substring(eqIndex + 1);
} else if (part.length > 1 && part[0] === '"' && part[part.length - 1] === '"') {
name = "text";
body = part;
} else if (part.length > 1 && part[0] === "'" && part[part.length - 1] === "'") {
name = "text";
body = part;
} else if (/^\(*\/\//.test(part) || part.startsWith("..")) {
name = "xpath";
body = part;
} else {
name = "css";
body = part;
}
let capture = false;
if (name[0] === "*") {
capture = true;
name = name.substring(1);
}
result.parts.push({ name, body });
if (capture) {
if (result.capture !== void 0) {
throw new InvalidSelectorError(
`Only one of the selectors can capture using * modifier`
);
}
result.capture = result.parts.length - 1;
}
};
if (!selector.includes(">>")) {
index = selector.length;
append();
return result;
}
const shouldIgnoreTextSelectorQuote = () => {
const prefix = selector.substring(start, index);
const match = prefix.match(/^\s*text\s*=(.*)$/);
return !!match && !!match[1];
};
while (index < selector.length) {
const c = selector[index];
if (c === "\\" && index + 1 < selector.length) {
index += 2;
} else if (c === quote) {
quote = void 0;
index++;
} else if (!quote && (c === '"' || c === "'" || c === "`") && !shouldIgnoreTextSelectorQuote()) {
quote = c;
index++;
} else if (!quote && c === ">" && selector[index + 1] === ">") {
append();
index += 2;
start = index;
} else {
index++;
}
}
append();
return result;
}
function parseAttributeSelector(selector, allowUnquotedStrings) {
let wp = 0;
let EOL = selector.length === 0;
const next = () => selector[wp] || "";
const eat1 = () => {
const result2 = next();
++wp;
EOL = wp >= selector.length;
return result2;
};
const syntaxError = (stage) => {
if (EOL) {
throw new InvalidSelectorError(
`Unexpected end of selector while parsing selector \`${selector}\``
);
}
throw new InvalidSelectorError(
`Error while parsing selector \`${selector}\` - unexpected symbol "${next()}" at position ${wp}${stage ? ` during ${stage}` : ""}`
);
};
function skipSpaces() {
while (!EOL && /\s/.test(next())) {
eat1();
}
}
function isCSSNameChar(char) {
return char >= "\x80" || // non-ascii
char >= "0" && char <= "9" || // digit
char >= "A" && char <= "Z" || // uppercase letter
char >= "a" && char <= "z" || // lowercase letter
char >= "0" && char <= "9" || // digit
char === "_" || // "_"
char === "-";
}
function readIdentifier() {
let result2 = "";
skipSpaces();
while (!EOL && isCSSNameChar(next())) {
result2 += eat1();
}
return result2;
}
function readQuotedString(quote) {
let result2 = eat1();
if (result2 !== quote) {
syntaxError("parsing quoted string");
}
while (!EOL && next() !== quote) {
if (next() === "\\") {
eat1();
}
result2 += eat1();
}
if (next() !== quote) {
syntaxError("parsing quoted string");
}
result2 += eat1();
return result2;
}
function readRegularExpression() {
if (eat1() !== "/") {
syntaxError("parsing regular expression");
}
let source = "";
let inClass = false;
while (!EOL) {
if (next() === "\\") {
source += eat1();
if (EOL) {
syntaxError("parsing regular expression");
}
} else if (inClass && next() === "]") {
inClass = false;
} else if (!inClass && next() === "[") {
inClass = true;
} else if (!inClass && next() === "/") {
break;
}
source += eat1();
}
if (eat1() !== "/") {
syntaxError("parsing regular expression");
}
let flags = "";
while (!EOL && next().match(/[dgimsuy]/)) {
flags += eat1();
}
try {
return new RegExp(source, flags);
} catch (e) {
throw new InvalidSelectorError(
`Error while parsing selector \`${selector}\`: ${e.message}`
);
}
}
function readAttributeToken() {
let token = "";
skipSpaces();
if (next() === `'` || next() === `"`) {
token = readQuotedString(next()).slice(1, -1);
} else {
token = readIdentifier();
}
if (!token) {
syntaxError("parsing property path");
}
return token;
}
function readOperator() {
skipSpaces();
let op = "";
if (!EOL) {
op += eat1();
}
if (!EOL && op !== "=") {
op += eat1();
}
if (!["=", "*=", "^=", "$=", "|=", "~="].includes(op)) {
syntaxError("parsing operator");
}
return op;
}
function readAttribute() {
eat1();
const jsonPath = [];
jsonPath.push(readAttributeToken());
skipSpaces();
while (next() === ".") {
eat1();
jsonPath.push(readAttributeToken());
skipSpaces();
}
if (next() === "]") {
eat1();
return {
name: jsonPath.join("."),
jsonPath,
op: "<truthy>",
value: null,
caseSensitive: false
};
}
const operator = readOperator();
let value;
let caseSensitive = true;
skipSpaces();
if (next() === "/") {
if (operator !== "=") {
throw new InvalidSelectorError(
`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with regular expression`
);
}
value = readRegularExpression();
} else if (next() === `'` || next() === `"`) {
value = readQuotedString(next()).slice(1, -1);
skipSpaces();
if (next() === "i" || next() === "I") {
caseSensitive = false;
eat1();
} else if (next() === "s" || next() === "S") {
caseSensitive = true;
eat1();
}
} else {
value = "";
while (!EOL && (isCSSNameChar(next()) || next() === "+" || next() === ".")) {
value += eat1();
}
if (value === "true") {
value = true;
} else if (value === "false") {
value = false;
} else {
if (!allowUnquotedStrings) {
value = +value;
if (Number.isNaN(value)) {
syntaxError("parsing attribute value");
}
}
}
}
skipSpaces();
if (next() !== "]") {
syntaxError("parsing attribute value");
}
eat1();
if (operator !== "=" && typeof value !== "string") {
throw new InvalidSelectorError(
`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with non-string matching value - ${value}`
);
}
return {
name: jsonPath.join("."),
jsonPath,
op: operator,
value,
caseSensitive
};
}
const result = {
name: "",
attributes: []
};
result.name = readIdentifier();
skipSpaces();
while (next() === "[") {
result.attributes.push(readAttribute());
skipSpaces();
}
if (!EOL) {
syntaxError(void 0);
}
if (!result.name && !result.attributes.length) {
throw new InvalidSelectorError(
`Error while parsing selector \`${selector}\` - selector cannot be empty`
);
}
return result;
}
// src/stringUtils.ts
function escapeWithQuotes(text, char = "'") {
const stringified = JSON.stringify(text);
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
if (char === "'") {
return char + escapedText.replace(/'/g, "\\'") + char;
}
if (char === '"') {
return char + escapedText.replace(/"/g, '\\"') + char;
}
if (char === "`") {
return char + escapedText.replace(/`/g, "`") + char;
}
throw new Error("Invalid escape char");
}
function cssEscape(s) {
let result = "";
for (let i = 0; i < s.length; i++) {
result += cssEscapeOne(s, i);
}
return result;
}
function quoteCSSAttributeValue(text) {
return `"${cssEscape(text).replace(/\\ /g, " ")}"`;
}
function cssEscapeOne(s, i) {
const c = s.charCodeAt(i);
if (c === 0) {
return "\uFFFD";
}
if (c >= 1 && c <= 31 || c >= 48 && c <= 57 && (i === 0 || i === 1 && s.charCodeAt(0) === 45)) {
return `\\${c.toString(16)} `;
}
if (i === 0 && c === 45 && s.length === 1) {
return `\\${s.charAt(i)}`;
}
if (c >= 128 || c === 45 || c === 95 || c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122) {
return s.charAt(i);
}
return `\\${s.charAt(i)}`;
}
function normalizeWhiteSpace(text) {
const result = text.replace(/\u200B/g, "").trim().replace(/\s+/g, " ");
return result;
}
function normalizeEscapedRegexQuotes(source) {
return source.replace(/(^|[^\\])(\\\\)*\\(['"`])/g, "$1$2$3");
}
function escapeRegexForSelector(re) {
if (re.unicode || re.unicodeSets) {
return String(re);
}
return String(re).replace(/(^|[^\\])(\\\\)*(["'`])/g, "$1$2\\$3").replace(/>>/g, "\\>\\>");
}
function escapeForTextSelector(text, exact) {
if (typeof text !== "string") {
return escapeRegexForSelector(text);
}
return `${JSON.stringify(text)}${exact ? "s" : "i"}`;
}
function escapeForAttributeSelector(value, exact) {
if (typeof value !== "string") {
return escapeRegexForSelector(value);
}
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"${exact ? "s" : "i"}`;
}
function trimString(input, cap, suffix = "") {
if (input.length <= cap) {
return input;
}
const chars = [...input];
if (chars.length > cap) {
return chars.slice(0, cap - suffix.length).join("") + suffix;
}
return chars.join("");
}
function trimStringWithEllipsis(input, cap) {
return trimString(input, cap, "\u2026");
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// src/domUtils.ts
function parentElementOrShadowHost(element) {
if (element.parentElement) {
return element.parentElement;
}
if (!element.parentNode) {
return;
}
if (element.parentNode.nodeType === 11 && element.parentNode.host) {
return element.parentNode.host;
}
}
function enclosingShadowRootOrDocument(element) {
let node = element;
while (node.parentNode) {
node = node.parentNode;
}
if (node.nodeType === 11 || node.nodeType === 9) {
return node;
}
}
function enclosingShadowHost(element) {
while (element.parentElement) {
element = element.parentElement;
}
return parentElementOrShadowHost(element);
}
function closestCrossShadow(element, css) {
while (element) {
const closest = element.closest(css);
if (closest) {
return closest;
}
element = enclosingShadowHost(element);
}
}
function getElementComputedStyle(element, pseudo) {
return element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element, pseudo) : void 0;
}
function isElementStyleVisibilityVisible(element, style) {
style = style != null ? style : getElementComputedStyle(element);
if (!style) {
return true;
}
if (Element.prototype.checkVisibility && Ivya.options.browser !== "webkit") {
if (!element.checkVisibility()) {
return false;
}
} else {
const detailsOrSummary = element.closest("details,summary");
if (detailsOrSummary !== element && (detailsOrSummary == null ? void 0 : detailsOrSummary.nodeName) === "DETAILS" && !detailsOrSummary.open) {
return false;
}
}
if (style.visibility !== "visible") {
return false;
}
return true;
}
function isElementVisible(element) {
const style = getElementComputedStyle(element);
if (!style) {
return true;
}
if (style.display === "contents") {
for (let child = element.firstChild; child; child = child.nextSibling) {
if (child.nodeType === 1 && isElementVisible(child)) {
return true;
}
if (child.nodeType === 3 && isVisibleTextNode(child)) {
return true;
}
}
return false;
}
if (!isElementStyleVisibilityVisible(element, style)) {
return false;
}
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function isVisibleTextNode(node) {
const range = node.ownerDocument.createRange();
range.selectNode(node);
const rect = range.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function elementSafeTagName(element) {
if (element instanceof HTMLFormElement) {
return "FORM";
}
return element.tagName.toUpperCase();
}
// src/roleUtils.ts
function hasExplicitAccessibleName(e) {
return e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby");
}
var kAncestorPreventingLandmark = "article:not([role]), aside:not([role]), main:not([role]), nav:not([role]), section:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]";
var kGlobalAriaAttributes = /* @__PURE__ */ new Map([
["aria-atomic", void 0],
["aria-busy", void 0],
["aria-controls", void 0],
["aria-current", void 0],
["aria-describedby", void 0],
["aria-details", void 0],
// Global use deprecated in ARIA 1.2
// ['aria-disabled', undefined],
["aria-dropeffect", v