2019-11-21 14:43:30 -08:00
|
|
|
// Copyright (c) Microsoft Corporation.
|
|
|
|
// Licensed under the MIT license.
|
|
|
|
|
2019-11-22 16:21:30 -08:00
|
|
|
import { SelectorEngine, SelectorRoot } from './selectorEngine';
|
2019-11-22 15:36:17 -08:00
|
|
|
import { Utils } from './utils';
|
|
|
|
|
|
|
|
type ParsedSelector = { engine: SelectorEngine, selector: string }[];
|
|
|
|
|
|
|
|
export class Injected {
|
|
|
|
readonly utils: Utils;
|
|
|
|
readonly engines: Map<string, SelectorEngine>;
|
|
|
|
|
|
|
|
constructor(engines: SelectorEngine[]) {
|
|
|
|
this.utils = new Utils();
|
|
|
|
this.engines = new Map();
|
|
|
|
for (const engine of engines)
|
|
|
|
this.engines.set(engine.name, engine);
|
2019-11-21 14:43:30 -08:00
|
|
|
}
|
|
|
|
|
2019-11-22 16:21:30 -08:00
|
|
|
querySelector(selector: string, root: SelectorRoot): Element | undefined {
|
2019-11-22 15:36:17 -08:00
|
|
|
const parsed = this._parseSelector(selector);
|
|
|
|
let element = root;
|
|
|
|
for (const { engine, selector } of parsed) {
|
2019-11-22 16:21:30 -08:00
|
|
|
const next = engine.query((element as Element).shadowRoot || element, selector);
|
2019-11-22 15:36:17 -08:00
|
|
|
if (!next)
|
|
|
|
return;
|
|
|
|
element = next;
|
2019-11-21 14:43:30 -08:00
|
|
|
}
|
2019-11-22 16:21:30 -08:00
|
|
|
return element as Element;
|
2019-11-21 14:43:30 -08:00
|
|
|
}
|
2019-11-22 15:36:17 -08:00
|
|
|
|
2019-11-22 16:21:30 -08:00
|
|
|
querySelectorAll(selector: string, root: SelectorRoot): Element[] {
|
2019-11-22 15:36:17 -08:00
|
|
|
const parsed = this._parseSelector(selector);
|
2019-11-22 16:21:30 -08:00
|
|
|
let set = new Set<SelectorRoot>([ root ]);
|
2019-11-22 15:36:17 -08:00
|
|
|
for (const { engine, selector } of parsed) {
|
|
|
|
const newSet = new Set<Element>();
|
|
|
|
for (const prev of set) {
|
2019-11-22 16:21:30 -08:00
|
|
|
for (const next of engine.queryAll((prev as Element).shadowRoot || prev, selector)) {
|
2019-11-22 15:36:17 -08:00
|
|
|
if (newSet.has(next))
|
|
|
|
continue;
|
|
|
|
newSet.add(next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
set = newSet;
|
|
|
|
}
|
2019-11-22 16:21:30 -08:00
|
|
|
return Array.from(set) as Element[];
|
2019-11-22 15:36:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
private _parseSelector(selector: string): ParsedSelector {
|
|
|
|
let index = 0;
|
|
|
|
let quote: string | undefined;
|
|
|
|
let start = 0;
|
|
|
|
const result: ParsedSelector = [];
|
|
|
|
const append = () => {
|
|
|
|
const part = selector.substring(start, index);
|
|
|
|
const eqIndex = part.indexOf('=');
|
|
|
|
if (eqIndex === -1)
|
|
|
|
throw new Error(`Cannot parse selector ${selector}`);
|
|
|
|
const name = part.substring(0, eqIndex).trim();
|
|
|
|
const body = part.substring(eqIndex + 1);
|
|
|
|
const engine = this.engines.get(name.toLowerCase());
|
|
|
|
if (!engine)
|
|
|
|
throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);
|
|
|
|
result.push({ engine, selector: body });
|
|
|
|
};
|
|
|
|
while (index < selector.length) {
|
|
|
|
const c = selector[index];
|
|
|
|
if (c === '\\' && index + 1 < selector.length) {
|
|
|
|
index += 2;
|
|
|
|
} else if (c === quote) {
|
|
|
|
quote = undefined;
|
|
|
|
index++;
|
|
|
|
} else if (!quote && c === '>' && selector[index + 1] === '>') {
|
|
|
|
append();
|
|
|
|
index += 2;
|
|
|
|
start = index;
|
|
|
|
} else {
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
append();
|
|
|
|
return result;
|
|
|
|
}
|
2019-11-21 14:43:30 -08:00
|
|
|
}
|
|
|
|
|
2019-11-22 15:36:17 -08:00
|
|
|
export default Injected;
|