-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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