Description
Consider this testcase:
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
var fn = () => {
iframe.remove();
console.log('error');
};
iframe.addEventListener('load', fn);
iframe.src = "https://www.seattlesymphony.org/~/media/files/notes/schubert-sonata-21-b-flat.pdf";
(where you can use any URL that has Content-Disposition: attachment
for the src). Per spec what should happen here is this:
- During the
appendChild
call https://html.spec.whatwg.org/multipage/iframe-embed-object.html#process-the-iframe-attributes runs and does the "Queue a task to run the iframe load event steps." bit. - The event listener is added.
- The load in the iframe starts.
- The queued task from step 1 runs, fires the load event, the iframe is removed and "error" is logged.
What happens in browsers instead is:
- Chrome and Safari: The load event fires synchronously under the
appendChild
call, as far as I can tell, so that the listener is added after the event has fired and hence the frame is never removed and "error" is never logged. The navigation does not fire a load event, because it hasContent-Disposition: attachment
and hence is not loaded in the iframe. - Firefox: The insertion starts an async
about:blank
load which would fire a load event async when it completes. The start of a new navigation cancels that load, so there is no load event fired at all. The frame is never removed and "error" is never logged. The navigation to the PDF does not fire a load event, because it hasContent-Disposition: attachment
and hence is not loaded in the iframe.
I don't have a good proposal for what the spec should say here... Currently the spec-compliant way for a page to do what the above script is trying to do is something like:
var iframe = document.createElement('iframe');
iframe.addEventListener("load", () => {
var fn = () => {
iframe.remove();
console.log('error');
};
iframe.addEventListener('load', fn);
iframe.src = "https://www.seattlesymphony.org/~/media/files/notes/schubert-sonata-21-b-flat.pdf";
}, { once: true });
document.body.appendChild(iframe);
which is rather annoying from an authoring perspective and hard for authors to think of.
That said, I expect that parser-triggered insertions likely do need the async load event thing in some way, though this testcase:
<iframe onload="console.log('sync-loaded')"></iframe>
<script>
document.querySelector("iframe").onload = () => { console.log('async-loaded'); }
</script>
shows "sync-loaded" in Chrome and Safari (and "async-loaded" in Firefox; per spec behavior could be either way depending on whether there's a packet boundary between the <iframe>
and the <script>
, afaict).