So let me see if I get what you're asking... First off, the reason that setTimeout()
's callback isn't executing is that before even calling setTimeout()
, you remove the iframe containing windowIframe
. To see the output of console.log("hello")
, you would need to delete the iframe after calling console.log()
, so something like this:
let iframe = document.createElement("iframe");
document.body.append(iframe);
void setTimeout(() => {
let windowIframe = frames[0];
let id = windowIframe.setTimeout(() => {
console.log("hello");
windowIframe.frameElement.remove();
});
console.log(id);
})
// OUTPUT:
// 1
// hello
...but I guess already knew that. As to why you can use setTimeout()
on an element whose window
context is gone, that's an interesting question, and I'm not sure why it should be possible, since setTimeout()
won't be able to execute on it. Actually, you can't in Firefox (which kind of proves my point): you will get an NS_ERROR_NOT_INITIALIZED
error, so I would assume that this is actually an implementation bug by Chrome. As you alluded to before, the spec quite clearly states:
...let id be an implementation-defined integer that is greater than zero and does not already exist in global's map of active timers.
This to me sounds like the ID should never be 0, and Chrome has incorrectly implemented the spec. An error should probably be raised somewhere along the way, and the function execution should stop as it does in Firefox. But since Chrome doesn't raise an error (and even gives the incorrect ID of 0), they're probably trying to execute the function anyway, but something prevents the callback from being executed. Let's investigate:
If you inspect windowIframe
in the Chrome console after removing its frameElement
, you will see something like this:
> windowIframe
<- Window {window: null, self: null, document: document, name: '', location: Location, …}
I think the relevant part of the spec is this:
- Let task be a task that runs the following substeps:
- If id does not exist in global's map of active timers, then abort these steps.
- If handler is a Function, then invoke handler given arguments with the callback this value set to thisArg. If this throws an exception, catch it, and report the exception.
— WHATWG spec - Timer initialization steps
If I understand this correctly, your element doesn't have a global anymore since you removed the frameElement
and now both windowIframe
's window
and self
are null. Therefore, step 1 fails because "global's map of active timers" can't exist without a global and step 2, which would execute the callback function console.log("hello")
, never gets gets called since the failure of step 1 aborts the execution of the subsequent steps.