如何捕获嵌套在 Shadow DOM 的“#shadow 根(开放)”元素中的 iframe?

How to capture iframe nested within Shadow DOM's "#shadow-root (open)" element?

提问人:Verminous 提问时间:10/28/2022 更新时间:10/28/2022 访问量:2491

问:

  • 我需要在 中注入一个元素,然后单击此 iframe 中的 。<iframe><button>
  • 这存在于不受我控制的页面(例如 somewebpage.com)中。<iframe>
  • 的内容与主文档的正文/DOM 结构属于同一域。<iframe>
  • 为此,我需要根据以下 V3 清单的配置使用 Chrome 扩展程序中的内容脚本文件:"content_scripts"
"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
            ]

扩展的内容脚本文件包含的代码应检测 iframe,侦听事件,然后在检测到 iframe 后将元素注入 iframe。"foreground.js"clickclick

页面完全加载后,当我检查这个页面上的元素时,它基本上看起来像这样:

<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>

我尝试了很多不同的方法,但没有一个能够找到.控制台上的错误消息返回 as 和 as 。<iframe>getElementByIdnullgetElementsByTagNameundefined

作为一种标准方法,我尝试了一些变化,例如: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
        });
    }

我还尝试了一些更高级的解决方案,例如:

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
}

来源:
在 javascript 中获取嵌套在 Frame 中的 iframe 中的元素值?

在我看来,这个问题仍然存在,可能是因为它是“影子 DOM”元素的子元素,这使得查找方法与标准不同。<iframe><iframe>

在意识到问题可能在于“Shadow DOM”元素的子元素之后,我从另一个问题中尝试了这种方法:<iframe>

this.windows = this.shadowRoot.getElementsByTagName('app-window')

来源:
来自 Shadow DOM 中的 GetElementById

甚至是这个:
(尽管我怀疑对于我的用例来说可能有点矫枉过正)

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

来源:
如何从影子根目录中选择元素标签

我再次得到相同或错误。 但我认为,也许我现在走在正确的轨道上,因为我知道它嵌套在“Shadow DOM”元素中。nullundefined<iframe>

也许有一种方法可以绕过或尊重“影子 DOM”并实现这一目标,但老实说,我没有想法。<iframe>

谢谢

javascript iframe google-chrome-extension shadow-dom

评论


答:

2赞 wOxxOm 10/28/2022 #1
  1. shadowRoot 没有 .getElementsByTagName
  2. ShadowDOM 帧不会在全局或(顺便说一句,这是一回事)windowframes
  3. customElements.define 在内容脚本中不起作用(由于“世界隔离”)。

你可以使用 querySelector + contentDocument:

const el = document.querySelector('macroponent').shadowRoot.querySelector('iframe');
const elInner = el.contentDocument.querySelector('div');

请注意,您不需要 or 此处,因为 iframe 是同源的,因此可以从主文档的内容脚本直接访问它,如上所示。all_framesmatch_origin_as_fallback

一个陷阱是,新式网站会动态生成页面内容,这通常取决于网络响应,这可能会在所有内容脚本运行后很长时间内发生。在这种情况下,您可以使用 MutationObserver 来检测它,或者只是在 setInterval 中定期重新检查。

评论

0赞 Verminous 10/31/2022
嘿@wOxxOm谢谢,这奏效了!我用了第一行。不确定第二行应该做什么。我在第一行下面使用了这个:el.contentDocument.getElementById("myButton").addEventListener("click", funtion() {...