Search code examples
javascripthtmliframe

querySelector null in iframe when readyState is complete


I have an iframe that I append to the body. I want to get an element inside the iframe. Nothing difficult, I just add a querySelector like this iframe.contentWindow.document.querySelector('#test'). To make it works, I have to wait the iframe to be loaded. Hence, I just add an eventListener load on the iframe and it works like a charme.

However, what I was wondering is why at first glance, the element that I tried to get is null whereas the readyState is complete. Indeed, the document has finished loading and parsed when readyState is interactive. So I make the assumption that when it is complete, it has been done and the element should be in the document. In addition, I have no script, no image, no stylesheet. See MDN doc

So why the querySelector is null when readyState is complete? (see the reproducible example in JSFiddle, does not work in snippet)

const srcdoc = `
<!DOCTYPE html>
<html>

<head>
  <title>Test</title>
</head>

<body>
  <div id="test"></div>
</body>
</html>
`;

const iframe = document.createElement('iframe');

iframe.addEventListener('DOMContentLoaded', () => {
  console.log('DOMContentLoaded', iframe.contentWindow.document.readyState);
  console.log('DOMContentLoaded', iframe.contentWindow.document.querySelector('#test'));
});

iframe.addEventListener('load', () => {
  console.log('onLoad', iframe.contentWindow.document.readyState);
  console.log('onLoad', iframe.contentWindow.document.querySelector('#test'));
});

iframe.srcdoc = srcdoc;

document.body.appendChild(iframe);

console.log('init', iframe.contentWindow.document.readyState);
console.log('init', iframe.contentWindow.document.querySelector('#test'));

Solution

  • As you've probably figured out, these two statements are executed before the new document has been loaded into the iframe.

    console.log('init', iframe.contentWindow.document.readyState);
    console.log('init', iframe.contentWindow.document.querySelector('#test'));
    

    The reason the readyState is already complete is the iframe has a content document when it is created, well before the new content -- with the test element -- has been loaded.

    You can demonstrate this by adding this line:

        console.log('init', iframe.contentWindow.document);
    

    The iframe's document at that point is about:blank.