javascripttypescriptiframereadystatedomcontentloaded

Why is document.body null with my readyState logic?


I'm trying to append an iframe element into document.body as soon as possible.

Error reports are coming in across all 4 major browsers (Chrome, Safari, FF, IE --various versions), we see document.body is null. I think this happens when my JS file is cached and loads quickly.

Here is the logic to insert the iframe:

private loadIFrame(): void {
  switch (document.readyState) {
    case 'uninitialized':
    case 'loading':
    case 'loaded':
      window.addEventListener('DOMContentLoaded',
        this.appendIFrame.bind(this)
      )
      break
    case 'interactive':
    case 'complete':
    default:
      this.appendIFrame()
  }
}

private appendIFrame(): void {
  if (document.getElementById('iframeId')) {
    return
  }

  let iFrame: HTMLIFrameElement = document.createElement('iframe')
  iFrame.src = document.location.protocol + this.ORIGIN + '/iframe.html'
  iFrame.id = 'iframeId'

  // document.body is null here
  document.body.appendChild(iFrame)
}

I'm having a hard time reproducing the issue in a clean environment, which leaves me guessing how this happens out in the wild.

I originally tried this rreadyState logic, but we saw document.body was undefined in IE when in the loading state.

private loadIFrame(): void {
  switch (document.readyState) {
    case 'uninitialized':
    case 'loading':
      window.addEventListener('DOMContentLoaded',
        this.appendIFrame.bind(this)
      )
      break
    case 'loaded':
    case 'interactive':
    case 'complete':
    default:
      this.appendIFrame()
  }
}

My current line of questioning....

  1. Is the issue the default case? Should I add the event listener there? I could modify the order of the cases so that the event listener is default?
  2. Is it possible body is null on the DOMContentLoaded event?
  3. Is it possible there are other values of document.readyState that are falling through?

Solution

  • I updated the logic, and I was able to almost eliminate the errors, but I am still seeing document.body is null when readyState is interactive.

    I'll be updating this post once I assess the specific browser scenarios that cause the issue.

    private loadIFrame(): void {
      switch (document.readyState) {
        case 'uninitialized':
        case 'loading':
        case 'loaded':
          document.addEventListener('DOMContentLoaded',
            this.appendIFrame.bind(this)
          )
          break
        case 'interactive':
        case 'complete':
        default:
          if(document.body) {
            this.appendIFrame()
          } else {
            window.addEventListener('load',
              this.appendIFrame.bind(this)
            )
          }
      }
    }