Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save Georgegriff/0f95ea606925d64d8b9b7f1273fff158 to your computer and use it in GitHub Desktop.
Save Georgegriff/0f95ea606925d64d8b9b7f1273fff158 to your computer and use it in GitHub Desktop.
Find a single element within a page, even one inside of many shadow roots
/**
* @author Georgegriff@ (George Griffiths)
* License Apache-2.0
*/
/**
* Finds the first matching element on the page that may be in a shadow root using a complex selector of n-depth
*
* Don't have to specify all shadow roots to button, tree is travered to find the correct element
*
* Example querySelectorDeep('downloads-item:nth-child(4) #remove');
*
* Example should work on chrome://downloads outputting the remove button inside of a download card component
*
* Example find first active download link element querySelectorDeep('#downloads-list .is-active a[href^="https://"]');
*
* Another example querySelectorDeep('#downloads-list div#title-area + a');
e.g.
*/
function querySelectorDeep(selector) {
// no need to do any special if selector matches something in light-dom
let lightElement = document.querySelector(selector);
if (lightElement) {
return lightElement;
}
// do best to support complex selectors and split the query
const splitSelector = selector.replace(/\s*([,>+~]+)\s*/g, '$1').split(' ');
let possibleElementsIndex = splitSelector.length - 1;
let possibleElements = collectAllElementsDeep(splitSelector[possibleElementsIndex]);
return possibleElements.find((element) => {
let position = possibleElementsIndex;
let parent = element;
let foundElement = false;
while (parent) {
const foundMatch = parent.matches(splitSelector[position]);
if (foundMatch && position === 0) {
foundElement = true;
break;
}
if (foundMatch) {
position--;
}
parent = findParentOrHost(parent);
}
return foundElement;
});
}
function findParentOrHost(element) {
const parentNode = element.parentNode;
return parentNode && parentNode.host ? parentNode.host : parentNode === document ? null : parentNode;
}
/**
* Finds all elements on the page, inclusive of those within shadow roots.
* @param {string=} selector Simple selector to filter the elements by. e.g. 'a', 'div.main'
* @return {!Array<string>} List of anchor hrefs.
* @author ebidel@ (Eric Bidelman)
* License Apache-2.0
*/
function collectAllElementsDeep(selector = null) {
const allElements = [];
const findAllElements = function(nodes) {
for (let i = 0, el; el = nodes[i]; ++i) {
allElements.push(el);
// If the element has a shadow root, dig deeper.
if (el.shadowRoot) {
findAllElements(el.shadowRoot.querySelectorAll('*'));
}
}
};
findAllElements(document.querySelectorAll('*'));
return selector ? allElements.filter(el => el.matches(selector)) : allElements;
}
// Paste this code into chrome://downloads console to try it out :)
console.log(querySelectorDeep('downloads-item:nth-child(4) #remove'));
console.log(querySelectorDeep('#downloads-list .is-active a[href^="https://"]'));
console.log(querySelectorDeep('#downloads-list div#title-area + a'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment