Issue
- I need to inject an element inside an
<iframe>
, after a<button>
within this iframe is clicked. - This
<iframe>
exists within a page (e. g. somewebpage.com) that is not under my control. - The
<iframe>
's content belongs to the same domain as the main document's body/DOM structure. - To achieve this I need to use a Content Script file from a Chrome Extension as per the below V3 manifest's
"content_scripts"
configuration:
"content_scripts": [
{
"type": "module", // I added this items "type", "run_at", "all_frames"
"run_at": "document_end", // and "match_origin_as_fallback" hoping they
"all_frames": true, // would help.
"match_origin_as_fallback": true,
"js": [
"foreground.js" // File containing the pure JS code
],
"matches": [
"https://somewebpage.com/*" // The page where I want to inject the element
]
The extension's content script "foreground.js"
file contains the code that should detect the iframe, listen to the click
event, and then inject the element within the iframe after click
is detected.
After the page is fully loaded, when I inspect the elements on this page, it basically looks like this:
<body>
<macroponent>
#shadow-root (open) <!-- Shadow DOM element. I believe this is what is my main obstacle -->
<div>
<sn-canvas-appshell-root>
<sn-canvas-appshell-layout>
<sn-polaris-layout>
<iframe id="myIframe"> <!-- to access button I need to find the iframe 1st -->
<button id="myButton">TAKE</button> <!-- I need to get that click event -->
</iframe>
</sn-polaris-layout>
</sn-canvas-appshell-root>
</sn-canvas-appshell-root>
</div>
</macroponent>
</body>
I have tried a lot of different approaches and none were able to find the <iframe>
. The error messages on the console are returning the getElementById
as null
and getElementsByTagName
as undefined
.
As a standard method I tried this with some variations like window.onload
:
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
let iframe = document.getElementsByTagName('iframe')[0];
iframe.contentDocument.getElementById("myButton").addEventListener("click", function () {
alert("Hurray!");
// Injected element code here
});
}
I have also tried some more advanced solutions like this:
function searchFrame(id) { // id = the id of the wanted (i)frame
var result = null, // Stores the result
search = function (iframes) { // Recursively called function
var n; // General loop counter
for (n = 0; n < iframes.length; n++) { // Iterate through all passed windows in (i)frames
if (iframes[n].frameElement.id === id) { // Check the id of the (i)frame
result = iframes[n]; // If found the wanted id, store the window to result
}
if (!result && iframes[n].frames.length > 0) { // Check if result not found and current window has (i)frames
search(iframes[n].frames); // Call search again, pass the windows in current window
}
}
};
search(window.top.frames); // Start searching from the topmost window
return result; // Returns the wanted window if found, null otherwise
}
Source:
Get element value inside iframe which is nested inside Frame in javascript?
In my opinion, this issue persists perhaps because the <iframe>
is a child to a "Shadow DOM" element, which makes the method of finding the <iframe>
different from the standard.
After realizing that probably the issue is that the <iframe>
is a child to a "Shadow DOM" element, I tried this approach from another question:
this.windows = this.shadowRoot.getElementsByTagName('app-window')
Source:
GetElementById from within Shadow DOM
Or even this one:
(although I suspect it might be a bit overkill for my use case)
customElements.define("my-component", class extends HTMLElement {
constructor() {
super().attachShadow({mode:"open"}).innerHTML = `<slot></slot>`;
}
})
const shadowDive = (
el,
selector,
match = (el, root) => {
console.warn('match', el, root);
},
root = el.shadowRoot || el
) => {
root.querySelector(selector) && match(root.querySelector(selector), root);
[...root.querySelectorAll("*")].map(el => shadowDive(el, selector, match));
}
shadowDive(document.body, "content"); // note optional parameters
Source:
How to select element tag from shadow root
And again I am getting the same null
or undefined
errors.
But I think that perhaps I am on the right track now that I know that the <iframe>
is nested within a "Shadow DOM" element.
Perhaps there is a way to bypass or respect the "Shadow DOM" and get to that <iframe>
but to be honest I am out of ideas.
Thank you
Solution
- A shadowRoot doesn't have
getElementsByTagName
. - ShadowDOM frames aren't exposed in the global
window
orframes
(it's the same thing BTW) - customElements.define doesn't work inside content scripts (due to "world isolation").
You can use querySelector + contentDocument:
const el = document.querySelector('macroponent').shadowRoot.querySelector('iframe');
const elInner = el.contentDocument.querySelector('div');
Note that you don't need all_frames
or match_origin_as_fallback
here because the iframe is same-origin, hence it's directly accessible from the main document's content script as shown above.
One pitfall is that modern sites generate the contents of the page dynamically, often depending on a network response, which can happen long time after all your content scripts ran. In this case you can use MutationObserver to detect it or simply re-check periodically inside setInterval.
Answered By - wOxxOm Answer Checked By - Candace Johnson (PHPFixing Volunteer)
0 Comments:
Post a Comment
Note: Only a member of this blog may post a comment.