Bug: "document.visibilityState" of the formerly active tab is not set to "hidden" after creating or switching between tabs
Status: RESOLVED
Priority: --
Resolution: FIXED
Created: 2024-01-25 19:42:06+00:00
Resolved: 2024-02-08 07:20:22+00:00

I noticed that while investigating a new [Puppeteer unit test for context activation](https://github.com/puppeteer/puppeteer/blob/f67b205776d70b914730750ba311bdb7aee9edcc/test/src/page.spec.ts#L2230). Here the `document.visibilityState` is used to check if a document is `visible` or `hidden`. And that check fails because it always returns `visible`.

This can be easily reproduced in the [WebDriver BiDi test client](https://juliandescottes.github.io/bidi-web-client/web/) by running the following code in the DevTool's console:

```
async function getVisibilityState(context) {
  const res = await sendCommand("script.evaluate", {
    expression: "document.visibilityState;",
    target: context,
    awaitPromise: false,
  });
  return res.result.result;
}

function sleep(time = 1000) {
  return new Promise(resolve => window.setTimeout(resolve, time));
}

(async function () {
  await sendCommand("session.new", { capabilities: {} });
  let res = await sendCommand("browsingContext.getTree", {});
  let context = res.result.contexts[0].context;

  const delay = 0;  // fails
  // const delay = 325; // passes

  await sendCommand("browsingContext.navigate", {
    context,
    url: "http://example.org",
    wait: "complete",
  });

  console.log("before opening the tab");
  console.log(await getVisibilityState({ context }));

  let tab1 = await sendCommand("browsingContext.create", {
    type: "tab"
  });

  console.log("after opening the tab");
  console.log(await getVisibilityState({ context }));
  console.log(await getVisibilityState({
    context: tab1.result.context
  }));
  await sleep(delay);

  console.log("after sleep");
  console.log(await getVisibilityState({ context }));
  console.log(await getVisibilityState({
    context: tab1.result.context
  }));
  await sleep(delay);

  await sendCommand("browsingContext.navigate", {
    context: tab1.result.context,
    url: "http://mozilla.org",
    wait: "complete",
  });

  console.log("after navigate in tab");
  console.log(await getVisibilityState({ context }));
  console.log(await getVisibilityState({
    context: tab1.result.context
  }));
  await sleep(delay);

  await sendCommand("browsingContext.activate", { context });

  console.log("after switching the tab");
  console.log(await getVisibilityState({ context }));
  console.log(await getVisibilityState({
    context: tab1.result.context
  }));
  await sleep(delay);

  console.log("after sleep");
  console.log(await getVisibilityState({ context }));
  console.log(await getVisibilityState({
    context: tab1.result.context
  }));

  await sendCommand("browsingContext.close", {
    context: tab1.result.context
  });
})()
```

As it can be seen the visibility state shows `visible` for the original tab and the newly opened one. Adding an additional delay makes it work. So it means that we have some race condition here, or maybe we have to explicitly wait until the `visibilityState` of the document in the content process has been updated?