(injectedScript: InjectedScript, targetElement: Element, options: InternalOptions)
| 389 | } |
| 390 | |
| 391 | function cssFallback(injectedScript: InjectedScript, targetElement: Element, options: InternalOptions): SelectorToken[] { |
| 392 | const root: Node = options.root ?? targetElement.ownerDocument; |
| 393 | const tokens: string[] = []; |
| 394 | |
| 395 | function uniqueCSSSelector(prefix?: string): string | undefined { |
| 396 | const path = tokens.slice(); |
| 397 | if (prefix) |
| 398 | path.unshift(prefix); |
| 399 | const selector = path.join(' > '); |
| 400 | const parsedSelector = injectedScript.parseSelector(selector); |
| 401 | const node = injectedScript.querySelector(parsedSelector, root, false); |
| 402 | return node === targetElement ? selector : undefined; |
| 403 | } |
| 404 | |
| 405 | function makeStrict(selector: string): SelectorToken[] { |
| 406 | const token = { engine: 'css', selector, score: kCSSFallbackScore }; |
| 407 | const parsedSelector = injectedScript.parseSelector(selector); |
| 408 | const elements = injectedScript.querySelectorAll(parsedSelector, root); |
| 409 | if (elements.length === 1) |
| 410 | return [token]; |
| 411 | const nth = { engine: 'nth', selector: String(elements.indexOf(targetElement)), score: kNthScore }; |
| 412 | return [token, nth]; |
| 413 | } |
| 414 | |
| 415 | for (let element: Element | undefined = targetElement; element && element !== root; element = parentElementOrShadowHost(element)) { |
| 416 | let bestTokenForLevel: string = ''; |
| 417 | |
| 418 | // Element ID is the strongest signal, use it. |
| 419 | if (element.id && !options.noCSSId) { |
| 420 | const token = makeSelectorForId(element.id); |
| 421 | const selector = uniqueCSSSelector(token); |
| 422 | if (selector) |
| 423 | return makeStrict(selector); |
| 424 | bestTokenForLevel = token; |
| 425 | } |
| 426 | |
| 427 | const parent = element.parentNode as (Element | ShadowRoot); |
| 428 | |
| 429 | // Combine class names until unique. |
| 430 | const classes = [...element.classList].map(escapeClassName); |
| 431 | for (let i = 0; i < classes.length; ++i) { |
| 432 | const token = '.' + classes.slice(0, i + 1).join('.'); |
| 433 | const selector = uniqueCSSSelector(token); |
| 434 | if (selector) |
| 435 | return makeStrict(selector); |
| 436 | // Even if not unique, does this subset of classes uniquely identify node as a child? |
| 437 | if (!bestTokenForLevel && parent) { |
| 438 | const sameClassSiblings = parent.querySelectorAll(token); |
| 439 | if (sameClassSiblings.length === 1) |
| 440 | bestTokenForLevel = token; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | // Ordinal is the weakest signal. |
| 445 | if (parent) { |
| 446 | const siblings = [...parent.children]; |
| 447 | const nodeName = element.nodeName; |
| 448 | const sameTagSiblings = siblings.filter(sibling => sibling.nodeName === nodeName); |
no test coverage detected
searching dependent graphs…