A JavaScript popup, represented in a shadow root or alternatively in a shadow DOM, contains an element which I want to remove, so to remove it, I have tried the folliwing commands in my web browser’s console.
Hi @bendqh1, you can’t query for elements under a shadow root from “outside”, you need to know the host element in advance; then you can query that element’s shadow DOM like e.g.:
Note that the shadow root might also have been attached in closed mode though, in which case you won’t be able to access its contents at all.
PS: As for your last snippet, I suppose you might iterate over all elements in the document like this and check if they have a shadow root attached; and if so, query that shadow root with your selector.
It apears to me that in this particular case, the shadowRoot is open:
This code didn’t work:
const popupElement = document.querySelector('.spotimWrapper') // Select Wrapper
const myButton = popupElement.shadowRoot.querySelector('.Button__button--11-4-12'); // Select element in shadowRoot inside the wrapper
myButton.remove();
PS: As for your last snippet, I suppose you might iterate over all elements in the document like this and check if they have a shadow root attached; and if so, query that shadow root with your selector.
Based on my interpretation of that, I have checked the following code that didn’t work.
document.querySelectorAll('shadowRoot').forEach((element) => {
if (element.classList.contains('Button__button--11-4-12')) {
element.remove();
}
});
Well that’s not the host of the shadow root; the host element would be the one with the cryptic tag name ow-j3eolsu49kg beneath. Since this looks very much generated and likely to change in the future, you might use a more robust selector though… e.g. from the parent element:
Ah no that won’t work. I meant something like this:
document.querySelectorAll('*').forEach(element => {
// No shadow root? Continue.
if (!element.shadowRoot) {
return
}
// Now query the *shadow root* for your button element...
const button = element.shadowRoot.querySelector('.Button__button--11-4-12')
// ... and remove it if it's there.
if (button) {
button.remove()
}
})
Yes, I did that on purpose becaue I assumed there is indeed a shadow root attached.
Anyway, why then will the other two codes (that do have this shadow root test) won’t work?
I just want to select all button elements in all (opened) shadow root elements, anywhere, in this case.
Not every element on the page necessarily has a shadow root attached. This however…
document.querySelectorAll('*')
… will give you all elements on the page.
If you’re trying to query the shadow root of an element, and the element just doesn’t have shadow root attached, you’ll get an error. Hence the need for a prior check.
let shadowRoot = document.querySelector('#hostElementId').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
buttonElement.remove();
}
let shadowRoot = document.querySelector('.spotimWrapper').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
buttonElement.remove();
}
Uncaught TypeError: Cannot read properties of null (reading ‘querySelector’)
at :2:32
let shadowRoot = document.querySelector('ow-c73itcdk6u').shadowRoot;
let buttonElement = shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
buttonElement.remove();
}
The error “Cannot read properties of null (reading ‘querySelector’)” means that the document.querySelector('.spotimWrapper') and document.querySelector('ow-c73itcdk6u') didn’t return any element. This could be because the selectors are not correct or the elements might not have been rendered yet when the code ran.
Ensure that the element hosting the shadow root (shadow host) is correctly selected. Then access its shadow root.
Using the browser’s developer tools, Inspect the shadow host (the element that contains the shadow root) and note down its unique attributes or class names. Once you’ve identified the shadow host, use its attributes or class names in the querySelector to get a reference to it. Use the .shadowRoot property on the shadow host to access its shadow root. then use the shadow root reference to query for the button and remove it.
let shadowHost = document.querySelector('shadowHostSelector');
if (shadowHost && shadowHost.shadowRoot) {
let buttonElement = shadowHost.shadowRoot.querySelector('.Button__button--11-4-12');
if (buttonElement) {
buttonElement.remove();
}
}
Also check if the shadow root is added to the DOM dynamically, if yes, you need to modify the entire process.
I have double checked the selector names and they were correct.
The element was already loaded, and I have actually done all code test from browser console (i.e not from a user script manager).
Sadly the above code didn’t work as well.
Also check if the shadow root is added to the DOM dynamically, if yes, you need to modify the entire process.
I think that’s spot on !
Indeed, the entire wrapper element and it’s shadow root that “both” contain the button are dynamically added to the DOM (i.e they do not appear after I refresh the page).
So, it seems that I do indeed need to modify the enitre process but how should I start go about doing that?
The dynamically loaded elements within a shadow DOM are not easily accessible through conventional DOM querying methods. I will give you two logical solutions that can be broken down into potential pathways:
1. Macro Recorder Solution:
Given the challenges in programmatically accessing elements within a shadow DOM, especially when elements are dynamically loaded, a Macro Recorder such as Jitbit may provide a more straightforward solution. Here’s how you could proceed:
Install a Macro Recorder tool.
Manually perform the actions of navigating to the button and clicking it while the Macro Recorder is running to capture these actions.
Save this recorded macro, and run it whenever you need to automate the clicking of the button.
This approach essentially bypasses the need to interact with the DOM programmatically, and instead, relies on replicating user interactions to achieve the desired outcome.
2. Programmatic Solution:
A more sophisticated approach would be needed to handle the dynamically loaded shadow DOM elements if a programmatic solution is preferred. Here’s a step-by-step approach:
Step 1: Identify when the shadow DOM (along with the button) is loaded into the DOM. This could be done by monitoring mutations to the DOM using a MutationObserver.
Step 2: Once the shadow DOM is loaded, access the shadow root of the host element that encapsulates the shadow DOM.
Step 3: Utilize the shadowRoot.querySelector() method to target and interact with the button within the shadow DOM.
//Let's say shadowHostSelector is the selector for the host element of the shadow DOM
let shadowHost = document.querySelector('shadowHostSelector');
// Create an instance of MutationObserver
let observer = new MutationObserver((mutationsList, observer) => {
for(let mutation of mutationsList) {
if (mutation.type === 'childList' && shadowHost.shadowRoot) {
let buttonElement = shadowHost.shadowRoot.querySelector('.Button__button--11-4-12');
if(buttonElement) {
buttonElement.click(); // Or buttonElement.remove(); to remove the button
observer.disconnect(); // Stop observing once the button is found and clicked
}
}
}
});
// Start observing the document with configured parameters
observer.observe(document, { childList: true, subtree: true });
This programmatic approach uses a MutationObserver to wait for the shadow DOM to be loaded, then accesses and interacts with the button within the shadow DOM.
Either of these pathways could provide a viable solution to your problem.
Access the Shadow Host Element : First, you need to access the shadow host element. This is the element to which the shadow root is attached. You can do this using standard DOM methods like querySelector or by selecting the element by its ID, class, or any other selector.
Access the Shadow Root : Next, you’ll access the shadow root using the shadowRoot property.
const shadowRoot = shadowHost.shadowRoot;
Select Elements within the Shadow Root : Now that you have the shadow root, you can use the querySelector or other DOM methods to select elements within it, just like you would with the main DOM. The only difference is that you’re selecting elements within the shadow DOM.
No meaning to be annoying but without an HTML example such as the one I gave in the above linked thread I didn’t succeed to undersand your comment. You may want to expand based on the HTML there.