简体   繁体   中英

Cannot access DOM elements inside custom HTML tag with JavaScript

I have several backend Salesforce (SF) pages with very long dropdown lists (like up to 1,000 options), and I want to use JS code in a Chrome bookmark to add a filter box to an arbitrary SELECT on the page (code below). The problem, I think, is because the nodes I want to access are inside a custom HTML element called force-aloha-page .

In my case, the first element I want to access is an IFRAME inside the custom element. (This is not a cross-site security issue as, even with a bad origin, JS will still get the IFRAME, just not anything in it.)

For example, I can inspect the code and see the custom HTML element, see the iframe, and I can see the content of the iframe on the page. If I just dump the node into the console, it shows the DOM elements that the JS cannot reach:

  • document.getElementsByTagName("FORCE-ALOHA-PAGE")[0]
  • <force-aloha-page data-data-rendering-service-uid="203" data-aura-rendered-by="505:0" force-alohapage_alohapage-host><div force-alohapage_alohapage class="iframe-parent slds-template_iframe slds-card"><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></div></force-aloha-page>

I can see the div and the iframe inside the force-aloha-page element. But, if I try to access the iframe, this happens:

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

The JS can see the custom element:

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

But nothing inside it:

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

Even though the above "node dump" worked, this doesn't:

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

It is possible that I might have a whole other issue once I get to the iframe, but I have to get past that custom tag first.

What I've Tried

I created the JS bookmark in Chrome which works on SELECTs in the main page and inside IFRAMEs with good XSS. But does not work on SELECTs inside custom tags. This is the test page that I used when I was writing the bookmark code.

testpage1.html

<html>
<body>
<select>
    <option value="1">1</option>
    <option value="10">10</option>
    <option value="2">2</option>
    <option value="20">20</option>
</select>
<br /><br />
<select>
    <option value="Apples">Apples</option>
    <option value="Berries">Berries</option>
    <option value="Candies">Candies</option>
    <option value="Danishes">Danishes</option>
</select>
<br /><br />
<iframe src="testpage2.html"></iframe>
</body>
</html>

testpage2.html

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

Bookmark code (expanded out for better readability):

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

I my JS code, I also tried creating a recursive function that crawls through all child nodes, but, as the console example I posted above shows, JS returns 0 child nodes for the custom element.

The force-aloha-page element is probably a Web component , which might explain why you can't access the DOM inside, since it is a Shadow DOM .

Try to access it by using the shadowRoot property, like below:

 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]);
 <force-aloha-page></force-aloha-page>

The snippet below should work.

 // Query all iframes in the DOM var iframesNodes = document.querySelectorAll("iframe"); // Transfrom nodeList into an array var iframes = Array.prototype.slice.call(iframesNodes); console.log(iframes); // Loop through all iframes iframes.map(function(iframe){ // Get the document of the current iframe var innerDoc = iframe.contentDocument || iframe.contentWindow.document; /*... Then do what ever you want with the iframe document... */ innerDoc.body.style.backgroundColor = "#ff0000" });
 <exampletag> <iframe src="/" /> </exampletag> <iframe src="/" />

If you want to use custom tags, you should read this: https://www.smashingmagazine.com/2014/03/introduction-to-custom-elements/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM