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:
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:
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 ofScreenDetailed
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, viascreens.length
.currentScreen
: A singleScreenDetailed
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:
// 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:
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:
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:
// ...
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:
// 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:
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:
try {
const primaryScreen = (await getScreenDetails()).screens.find(
(screen) => screen.isPrimary,
);
await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
console.error(err.name, err.message);
}