-1

I have wondering a question, how do timers work? Sure after that I towarded to read whatwg spec and I came across on the following text:

If global is a Window object, wait until global's associated Document has been fully active for a further milliseconds milliseconds (not necessarily consecutively).

Well, how can I reproduce this case? And how I can track when document will be fully active?


By the way I've made interesting example by that case:

let iframe = document.createElement("iframe");
document.body.append(iframe);
void setTimeout(() => {
  let windowIframe = frames[0];
  windowIframe.frameElement.remove();
  let id = windowIframe.setTimeout(() => console.log("hello"));
  console.log(id);
})

If you run it at your local machine, you will see 0 in the console and the code with "hello" will never printed. What do you think where this behaviour came from specification?

MaximPro
  • 563
  • 8
  • 21

2 Answers2

1

And how I can track when document will be fully active?

There's already an answer here regarding what is means for a document to be fully active: Detecting when a document is "fully active"

Well, how can I reproduce this case?

There is some extensive discussion here: https://github.com/whatwg/html/issues/8002

If you run it at your local machine, you will see 0 in the console

For entries put in the map of active timers by the timer initialization steps, i.e., by setTimeout() and setInterval(), the keys are numbers.

Furthermore:

A browsing context is an environment in which Document objects are presented to the user. A tab or window in a web browser typically contains a browsing context, as does an iframe or frames in a frameset.

A Document's browsing context is the browsing context whose session history contains the Document, if any such browsing context exists and has not been discarded, and null otherwise.

A Document does not necessarily have a non-null browsing context. In particular, data mining tools are likely to never instantiate browsing contexts. A Document created using an API such as createDocument() never has a non-null browsing context. And the Document originally created for an iframe element, which has since been removed from the document, has no associated browsing context, since that browsing context was discarded.

A Document d is said to be fully active when d's browsing context is non-null, d's browsing context's active document is d, and either d's browsing context is a top-level browsing context, or d's browsing context's container document is fully active.

Swiffy
  • 4,401
  • 2
  • 23
  • 49
  • Sure when we are talking about tracking the `fully active` document - just take the definition of it. But the problem is that I want to see how the `fully active` document is tracked through js code. In fact not `fully active` document is document when it is not viewed to user and there is no in the DOM (simplistically). `There is some extensive discussion here: https://github.com/whatwg/html/issues/800` - I think you did a mistake, I don't see "extensive discussion" in there. – MaximPro Jun 19 '22 at 16:14
  • When you're talking about values that is returned by setTimeout/setInterval, I know that they return values of the number type. The most interesting thing is that if you will try to shedule setTimeout on the not `fully active` document, you won't see callback invocation also the value that will be returned is 0. What about your "futhermore" - I've read it. In conclusion you've not written new information for me and this is sad. – MaximPro Jun 19 '22 at 16:14
  • @MaximPro Whops, my bad with the Github link., fixed it. The value setTimeout returns is just its numeric key in the window's active timers map. This map can't be viewed [without tricks](https://stackoverflow.com/questions/858619/viewing-all-the-timeouts-intervals-in-javascript). I don't think it is that interesting that the callback won't fire - as I understand, you are removing it from the iframe context's global's map of active timers before it has chance to fire. It is fully active though. – Swiffy Jun 19 '22 at 17:45
  • As I wrote if I invoke setTimeout/setInterval on the not `fully active` document I'll get `0`, this id won't be returned if you sheduled timer on the not `fully active` document. Your link onto tricks is trying to create a map, but the map that has ids among these we have invalid timer id is not correct. – MaximPro Jun 19 '22 at 18:24
  • The returned id has very little to do here. If it's 0, it just means that it's the first timer that was created. If it's 4, then it's the fifth timer that was created. If you are able to call setTimeout, there is no doubt that it means that you are able to set a timeout. This is like asking if you can call some program's function, if you haven't started the program yet. Obviously the setTimeout can't call it's callback, if you remove the initiating/active script, or remove the global map of timers, just like any program can't call their callback, if you close the program before that happens. – Swiffy Jun 19 '22 at 18:34
  • Unfortunately you're not right, because when you try to shedule setTimeout on the saved window object that also is not active, it won't be run. If you open blank tab and write in the console `setTimeout(() => {})` you'll also get `1` as first id of timers. Oppsosite if you will do the call for the not active window object, you get returned value equal to `0` and no matter what how much calls you do by setTimeout. – MaximPro Jun 20 '22 at 00:03
  • There is following text in the steps of the timers algorithm: ` 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 is proving my words above. – MaximPro Jun 20 '22 at 03:34
1

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:

  1. Let task be a task that runs the following substeps:
    1. If id does not exist in global's map of active timers, then abort these steps.
    2. 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.

rhelminen
  • 678
  • 1
  • 5
  • 15
  • Interesting answer. I was interested by the spec words: `If global is a Window object, wait until global's associated Document has been fully active` - hence there is may be case with not "fully active" document. If it is not that means it is spec bug or this is a future that browsers did not implement yet. P.S Although your answer doesn't answer to my question I see that you have the same thoughs as I. I like it :) – MaximPro Jun 25 '22 at 01:36
  • Yeah, I think the problem there is that `global` can't be a `Window` object, since `windowIframes`'s `window` is `null`. Therefore, it can't have an "associated Document" either. `windowIframe` itself is a `Window` object with an associated document, which kind of makes it confusing... "A browsing context's active document is its active window's associated Document." — [WHATWG](https://html.spec.whatwg.org/multipage/browsers.html#active-document), and since `windowIframe` doesn't have an active window anymore, the document can't be active either, even though you can access it in the console. – rhelminen Jun 25 '22 at 12:14
  • 1
    I think the problem is that we store WindowProxy shell, but window object inside [[Window]] has been removed. That's why I think firefox throw an error and firefox gives us id equal to 0. – MaximPro Jun 25 '22 at 17:09