Using the Window Management API
===============================

This guide explains how to use the Window Management API. The example code seen below is taken from our Multi-window learning environment example (see the source code).

Feature detection
-----------------

You can feature detect the Window Management API by checking for the existence of `getScreenDetails` in the current `window` instance. For example, you might want to provide a button to open a multi-window display if the API is supported, or a different experience such as creating links to the different pages if it isn't:


```js
if ("getScreenDetails" in window) {
  // The Window Management API is supported
  createButton();
} else {
  // The Window Management API is not supported
  createLinks(sites);
}
```

Basic usage
-----------

The core of the Windows Management API is the `Window.getScreenDetails()` method, which returns an object containing details of all the screens available to the user's system:


```js
const screenDetails = await window.getScreenDetails();

// Return the number of screens
const noOfScreens = screenDetails.screens.length;
```


When `getScreenDetails()` is invoked, the user will be asked for permission to manage windows on all their displays (the status of this permission can be checked using `Permissions.query()` to query `window-management`). If the user grants permission, a `ScreenDetails` object is returned. This object contains the following properties:

* `screens`: An array of `ScreenDetailed` objects, each one containing detailed information about a separate screen available to the system (see below). This array is also useful for determining the number of available screens, via `screens.length`.
* `currentScreen`: A single `ScreenDetailed` object containing detailed information about the screen that the current browser window is displayed in.

`ScreenDetailed` objects inherit the properties of the `Screen` interface, and contain useful information for placing windows on specific screens.

**Note:** You can gate functionality based on whether the user has more than one screen available using the `Window.screen.isExtended` property. This returns `true` if the device has multiple screens, and `false` if not.

### Opening windows

You'll still need to use `Window.open()` to open and manage windows, but the above provides you with better information for doing so in a multi-screen environment. For example, a utility function might look like so:


```js
// Array to hold references to the currently open windows
let windowRefs = [];

// ...

function openWindow(left, top, width, height, url) {
  const windowFeatures = `left=${left},top=${top},width=${width},height=${height}`;
  const windowRef = window.open(
    url,
    "\_blank", // needed for it to open in a new window
    windowFeatures,
  );

  if (windowRef === null) {
    // If the browser is blocking new windows, close any windows that were
    // able to open and display instructions to help the user fix this problem
    closeAllWindows();
    popoverElem.showPopover();
  } else {
    // Store a reference to the window in the windowRefs array
    windowRefs.push(windowRef);
  }
}
```


You would then invoke this function and open windows on specific screens like this:


```js
const screen1 = screenDetails.screens[0];
const screen2 = screenDetails.screens[1];
// Windows will be a third the width and the full height of the screen
// The available width of screen1, minus 3 times the horizontal browser chrome
// width, divided by 3
const windowWidth = Math.floor((screen1.availWidth - 3 \* WINDOW\_CHROME\_X) / 3);
// The available height of screen1, minus the vertical browser chrome width
const windowHeight = Math.floor(screen1.availHeight - WINDOW\_CHROME\_Y);

// Open a window a third of the width and the entire height of the primary screen
openWindow(
  screen1.availLeft,
  screen1.availTop,
  windowWidth,
  windowHeight,
  sites[1].url,
);

// ...
```

### Closing all windows

After opening each window, we add a reference to the `windowRefs` array. This allows you to, for example, close them all when one window is closed:


```js
function closeAllWindows() {
  // Loop through all window refs and close each one
  windowRefs.forEach((windowRef) => {
    windowRef.close();
  });
  windowRefs = [];
}

// Check whether one of our popup windows has been closed
// If so, close them all

closeMonitor = setInterval(checkWindowClose, 250);

function checkWindowClose() {
  if (windowRefs.some((windowRef) => windowRef.closed)) {
    closeAllWindows();
    clearInterval(closeMonitor);
  }
}
```


**Note:** In our experiments, the `setInterval()` polling method shown above seemed to work best for detecting window closure in the case of multiple windows. Using events such as `beforeunload`, `pagehide`, or `visibilitychange` proved unreliable because, when opening multiple windows at the same time, the rapid shift in focus/visibility seemed to fire the handler function prematurely.

**Note:** One concern with the above example is that it uses constant values to represent the size of the Chrome window UI portions in the calculations — `WINDOW_CHROME_X` and `WINDOW_CHROME_Y` — to get the window size calculations correct. To create precisely-sized windows on other future implementations of the API, you'd need to keep a small library of browser chrome sizes, and employ browser detection to discover which browser is rendering your app and choose the correct size for calculations. Or you can rely on less precise window sizes.

### Handling browser popup blockers

In modern browsers, a separate user gesture event is required for each `Window.open()` call, for security purposes. This prevents sites from spamming users with lots of windows. However, this poses an issue for multi-window applications. To work around this limitation, you can design your applications to:

* Open no more than one new window at once.
* Reuse existing windows to display different pages.
* Advise users on how to update their browser settings to allow multiple windows.

In our demo application, we have gone for the third option. Our `openWindow()` function contains the following section:


```js
// ...

if (windowRef === null) {
  // If the browser is blocking new windows, close any windows that were
  // able to open and display instructions to help the user fix this problem
  closeAllWindows();
  popoverElem.showPopover();
} else {
  // Store a reference to the window in the windowRefs array
  windowRefs.push(windowRef);
}

// ...
```


If the browser blocks a new window, the resulting `windowRef` will be `null`. In this case we run our `closeAllWindows()` function to close any windows that *did* manage to open before the blocking started, and show a popover element that explains how to disable the popup blocker.

Simple single-window per display case
-------------------------------------

If you want to open a single window on each available display that is the full width and height of the display, you could use a pattern like this:


```js
// Open a window on each screen of the device
for (const screen of screenDetails.screens) {
  openWindow(
    screen.availLeft,
    screen.availTop,
    screen.availWidth,
    screen.availHeight,
    url,
  );
}
```

Window management events
------------------------

The Window Management API provides some events for responding to changes in the available screens:

The `ScreenDetails` `screenschange` event

Fired when screens are connected to or disconnected from the system.

The `ScreenDetails` `currentscreenchange` event

Fired when the window's current screen changes in some way.

The `Screen` `change` event

Fired on a specific screen when it changes in some way.

So for example, you could use the `screenschange` event to detect when the available screens have changed (perhaps when a screen is plugged in or unplugged), report the change, close all windows, and update window arrangements to suit the new configuration:


```js
screenDetails.addEventListener("screenschange", () => {
  // If the new number of screens is different to the old number of screens,
  // report the difference
  if (screenDetails.screens.length !== noOfScreens) {
    console.log(
      `The screen count changed from ${noOfScreens} to ${screenDetails.screens.length}`,
    );
  }

  // If the windows are open, close them and then open them again
  // So that they fit with the new screen configuration
  if (windowRefs.length > 0) {
    closeAllWindows();
    openWindows();
  }
});
```

requestFullscreen() screen option
---------------------------------

The Window Management API adds a new `screen` option to the `requestFullscreen()` method that allows you to specify on which screen you want to put the element in fullscreen mode. For example, if you want to make it fullscreen on the primary OS screen:


```js
try {
  const primaryScreen = (await getScreenDetails()).screens.find(
    (screen) => screen.isPrimary,
  );
  await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
  console.error(err.name, err.message);
}
```