Skip to content

Commit

Permalink
fix(text selector): do not match text inside <head> (#2413)
Browse files Browse the repository at this point in the history
We already skip <script> and <style> tags because they are not
the page content. Similar reasoning applies to <head> that has
content that is never rendered on the page.
  • Loading branch information
dgozman committed May 29, 2020
1 parent 084d5ff commit 8e4a1e7
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 4 deletions.
25 changes: 21 additions & 4 deletions src/injected/textSelectorEngine.ts
Expand Up @@ -80,9 +80,24 @@ function createMatcher(selector: string): Matcher {
return text => text.toLowerCase().includes(selector);
}

// Skips <head>, <script> and <style> elements and all their children.
const nodeFilter: NodeFilter = {
acceptNode: node => {
return node.nodeName === 'HEAD' || node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE' ?
NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
}
};

// If we are querying inside a filtered element, nodeFilter is never called, so we need a separate check.
function isFilteredNode(root: SelectorRoot, document: Document) {
return root.nodeName === 'SCRIPT' || root.nodeName === 'STYLE' || document.head && document.head.contains(root);
}

function queryInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean): Element | undefined {
const document = root instanceof Document ? root : root.ownerDocument!;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
if (isFilteredNode(root, document))
return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, nodeFilter);
const shadowRoots: ShadowRoot[] = [];
if (shadow && (root as Element).shadowRoot)
shadowRoots.push((root as Element).shadowRoot!);
Expand All @@ -94,7 +109,7 @@ function queryInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean): E

const textParent = (node && node.nodeType === Node.TEXT_NODE) ? node.parentElement : null;
if (lastTextParent && textParent !== lastTextParent) {
if (lastTextParent.nodeName !== 'SCRIPT' && lastTextParent.nodeName !== 'STYLE' && matcher(lastText))
if (matcher(lastText))
return lastTextParent;
lastText = '';
}
Expand Down Expand Up @@ -122,7 +137,9 @@ function queryInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean): E

function queryAllInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean, result: Element[]) {
const document = root instanceof Document ? root : root.ownerDocument!;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
if (isFilteredNode(root, document))
return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, nodeFilter);
const shadowRoots: ShadowRoot[] = [];
if (shadow && (root as Element).shadowRoot)
shadowRoots.push((root as Element).shadowRoot!);
Expand All @@ -134,7 +151,7 @@ function queryAllInternal(root: SelectorRoot, matcher: Matcher, shadow: boolean,

const textParent = (node && node.nodeType === Node.TEXT_NODE) ? node.parentElement : null;
if (lastTextParent && textParent !== lastTextParent) {
if (lastTextParent.nodeName !== 'SCRIPT' && lastTextParent.nodeName !== 'STYLE' && matcher(lastText))
if (matcher(lastText))
result.push(lastTextParent);
lastText = '';
}
Expand Down
26 changes: 26 additions & 0 deletions test/queryselector.spec.js
Expand Up @@ -551,6 +551,32 @@ describe('text selector', () => {
expect(await page.$(`text="with"`)).toBe(null);
});

it('should skip head, script and style', async({page}) => {
await page.setContent(`
<head>
<title>title</title>
<script>var script</script>
<style>.style {}</style>
</head>
<body>
<script>var script</script>
<style>.style {}</style>
<div>title script style</div>
</body>`);
const head = await page.$('head');
const title = await page.$('title');
const script = await page.$('body script');
const style = await page.$('body style');
for (const text of ['title', 'script', 'style']) {
expect(await page.$eval(`text=${text}`, e => e.nodeName)).toBe('DIV');
expect(await page.$$eval(`text=${text}`, els => els.map(e => e.nodeName).join('|'))).toBe('DIV');
for (const root of [head, title, script, style]) {
expect(await root.$(`text=${text}`)).toBe(null);
expect(await root.$$eval(`text=${text}`, els => els.length)).toBe(0);
}
}
});

it('should match input[type=button|submit]', async({page}) => {
await page.setContent(`<input type="submit" value="hello"><input type="button" value="world">`);
expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe('<input type="submit" value="hello">');
Expand Down

0 comments on commit 8e4a1e7

Please sign in to comment.