无法使用 JavaScript 访问自定义 HTML 标记内的 DOM 元素

jwa*_*980 5 html javascript iframe dom

我有几个后端 Salesforce (SF) 页面,它们具有很长的下拉列表(例如最多 1,000 个选项),并且我想在 Chrome 书签中使用 JS 代码将过滤器框添加到页面上的任意 SELECT 中(代码如下)。我认为问题是因为我想要访问的节点位于名为 的自定义 HTML 元素内force-aloha-page

\n\n

就我而言,我想要访问的第一个元素是自定义元素内的 IFRAME。(这不是一个跨站点安全问题,因为即使来源不好,JS 仍然会获得 IFRAME,只是其中没有任何内容。)

\n\n

例如,我可以检查代码并查看自定义 HTML 元素、查看 iframe,并且可以在页面上查看 iframe 的内容。如果我只是将节点转储到控制台,它会显示 JS 无法访问的 DOM 元素:

\n\n
\n
    \n
  • document.getElementsByTagName("FORCE-ALOHA-PAGE")[0]
  • \n
  • <force-aloha-page data-data-rendering-service-uid=\xe2\x80\x8b"203" data-aura-rendered-by=\xe2\x80\x8b"505:\xe2\x80\x8b0" force-alohapage_alohapage-host>\xe2\x80\x8b<div force-alohapage_alohapage class=\xe2\x80\x8b"iframe-parent slds-template_iframe slds-card">\xe2\x80\x8b<iframe force-alohapage_alohapage height=\xe2\x80\x8b"100%" width=\xe2\x80\x8b"100%" scrolling=\xe2\x80\x8b"yes" allowtransparency=\xe2\x80\x8b"true" name=\xe2\x80\x8b"vfFrameId_1569557364522" title=\xe2\x80\x8b"Page Configuration" allowfullscreen=\xe2\x80\x8b"true" lang=\xe2\x80\x8b"en-US" allow=\xe2\x80\x8b"geolocation *;\xe2\x80\x8b microphone *;\xe2\x80\x8b camera *">\xe2\x80\x8b\xe2\x80\xa6\xe2\x80\x8b</iframe>\xe2\x80\x8b</div>\xe2\x80\x8b</force-aloha-page>\xe2\x80\x8b
  • \n
\n
\n\n

我可以看到元素内部的div和。但是,如果我尝试访问 iframe,就会发生这种情况:iframeforce-aloha-page

\n\n
\n
    \n
  • document.getElementsByTagName(\'IFRAME\')
  • \n
  • 0
  • \n
  • document.querySelectorAll("iframe")
  • \n
  • 0
  • \n
\n
\n\n

JS可以看到自定义元素:

\n\n
\n
    \n
  • document.getElementsByTagName("FORCE-ALOHA-PAGE").length
  • \n
  • 1
  • \n
\n
\n\n

但里面什么也没有:

\n\n
\n
    \n
  • document.getElementsByTagName("FORCE-ALOHA-PAGE")[0].childNodes.length
  • \n
  • 0
  • \n
\n
\n\n

即使上面的“节点转储”有效,但这不起作用:

\n\n
\n
    \n
  • document.getElementsByTagName("FORCE-ALOHA-PAGE")[0].innerHTML
  • \n
  • ""
  • \n
\n
\n\n

一旦我到达 iframe,我可能会遇到一个完整的其他问题,但我必须首先克服该自定义标记。

\n\n

我尝试过的

\n\n

我在 Chrome 中创建了 JS 书签,它适用于主页中的 SELECT 和具有良好 XSS 的 IFRAME 内部。但不适用于自定义标签内的 SELECT。这是我在编写书签代码时使用的测试页。

\n\n

测试页1.html

\n\n
<html>\n<body>\n<select>\n    <option value="1">1</option>\n    <option value="10">10</option>\n    <option value="2">2</option>\n    <option value="20">20</option>\n</select>\n<br /><br />\n<select>\n    <option value="Apples">Apples</option>\n    <option value="Berries">Berries</option>\n    <option value="Candies">Candies</option>\n    <option value="Danishes">Danishes</option>\n</select>\n<br /><br />\n<iframe src="testpage2.html"></iframe>\n</body>\n</html>\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试页2.html

\n\n
<html>\n<body>\n<select>\n    <option value="3">3</option>\n    <option value="30">30</option>\n    <option value="4">4</option>\n    <option value="40">40</option>\n</select>\n<br /><br />\n<select onchange="selectChange(this)">\n    <option value="Eclaires">Eclaires</option>\n    <option value="Frozen Custard">Frozen Custard</option>\n    <option value="Grapes">Grapes</option>\n    <option value="Heath Bar">Heath Bar</option>\n</select>\n<script type="text/javascript">\n    function selectChange(el) {\n        console.log("Select value: " + el.value);\n    }\n</script>\n</body>\n</html>\n
Run Code Online (Sandbox Code Playgroud)\n\n

书签代码(为了更好的可读性而展开):

\n\n
javascript:(function(){\n    //return an array of all selects in the main page or in iframes\n    var selects=function(d){\n        var a=[],\n            s=d.getElementsByTagName(\'SELECT\'),\n            b=d.getElementsByTagName(\'IFRAME\');\n        for(var i=0;i<s.length;i++)\n            a.push(s[i]);\n        for(var i=0;i<b.length;i++){\n            try{\n                a=a.concat(selects(b[i].contentWindow.document));\n            }catch(e){\n                console.log(e);\n            }\n        }\n        return a;\n    },\n    //makes the SELECT border blink and scrolls it into view\n    blink=function(els,i){\n        if(i>=els.length)return;\n        var el=els[i],s=el.style,t=200,\n            nb=\'3px solid blue\',eb=s.border+\'\';\n        el.scrollIntoView();\n        s.border=nb;\n        setTimeout(function(){s.border=eb;},t);\n        setTimeout(function(){s.border=nb;},t*2);\n        setTimeout(function(){\n            s.border=eb;\n            if(confirm("This one?")){\n                filter(el);\n            }else{\n                blink(els,i+1);\n            }\n        },t*3);\n    },\n    //helper for creating options on a select\n    opt=function(v,t,p){\n        var y=document.createElement(\'OPTION\');\n        y.value=v;\n        y.text=t;\n        p.appendChild(y);\n    },\n    //creates the new filter input and adds it to the page\n    filter=function(el){\n        console.log(\'Filtering...\');\n        var d=document,c=d.createElement(\'INPUT\'),o=[];\n        c.type=\'text\';\n        c.placeholder=\'Filter list\';\n        c.style.width=el.style.width;\n        c.style.display=\'block\';\n        el.parentNode.insertBefore(c,el);\n        //filters the option list when something is typed\n        c.onkeyup=function(ev){\n            var j=c.value+\'\',h=el.options,x=0;\n            if(o.length==0){\n                for(var e=0;e<h.length; e++){\n                    with(h[e]){\n                        o.push({\'v\':value,\'t\':text});\n                    }\n                }\n            }\n            for(var g=h.length-1;g>=0;g--)el.remove(g);\n            for(var i=0;i<o.length; i++){\n                if(j.length==0){\n                    opt(o[i].v,o[i].t,el);\n                }else{\n                    if(match(o[i].t,j)){\n                        if(x==0) opt(\'\',\'\',el);\n                        opt(o[i].v,o[i].t,el);\n                        x++;\n                    }\n                }\n            }\n            if(x>0) el.options[0].text=\'<\'+x+\' Match(es) Found>\';\n        };\n    },\n    //determines if the option text matches the filter criteria, with wildcard support\n    match=function(a,b){\n        a=(a+\'\').toLowerCase();\n        b=(b+\'\').toLowerCase();\n        if(b.indexOf(\'*\')<0){\n            return a.indexOf(b)>=0;\n        }else{\n            var r=\'.*\',c=b.split(\'*\');\n            for(var i=0;i<c.length;i++){\n                r+=\'.*\'+(c[i].length>0?\'(\'+c[i]+\')\':\'\');\n            }\n            r+=\'.*\';\n            return (new RegExp(r)).test(a);\n        }\n    },\n    d=document,s=selects(d),v=[];\n    //gets only SELECTs that are visible on the page\n    for(var i=0;i<s.length;i++){\n        if (window.getComputedStyle(s[i]).display !== \'none\') \n            v.push(s[i]);\n    }\n    console.log(\'SELECTs: \'+s.length);\n    console.log(\'Visible SELECTs: \'+v.length);\n    //Begin.\n    blink(v,0);\n})();\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我的 JS 代码中,我还尝试创建一个递归函数来爬行所有子节点,但是,正如我上面发布的控制台示例所示,JS 为自定义元素返回 0 个子节点。

\n

Twi*_*her 5

force-aloha-page元素可能是一个Web 组件,这可能解释了为什么您无法访问内部的 DOM,因为它是Shadow DOM

尝试使用属性来访问它shadowRoot,如下所示:

class ForceAlohaPage extends HTMLElement {
    
    constructor() {
        super();
        this.attachShadow({
            mode: 'open'
        }).innerHTML = '<iframe force-alohapage_alohapage height="100%" width="100%" scrolling="yes" allowtransparency="true" name="vfFrameId_1569557364522" title="Page Configuration" allowfullscreen="true" lang="en-US" allow="geolocation *; microphone *; camera *"></iframe>';
    }
}

customElements.define("force-aloha-page", ForceAlohaPage);

console.log(document.getElementsByTagName('iframe').length);

console.log(document.getElementsByTagName("force-aloha-page")[0].childNodes.length);

console.log(document.getElementsByTagName("force-aloha-page")[0].innerHTML);

console.log(document.getElementsByTagName('force-aloha-page')[0].shadowRoot.childNodes[0]);
Run Code Online (Sandbox Code Playgroud)
<force-aloha-page></force-aloha-page>
Run Code Online (Sandbox Code Playgroud)