Get information about connected displays and position windows relative to those displays.
Window Management API
The Window Management API allows you to enumerate the displays connected to your machine and to place windows on specific screens.
Suggested use cases
Examples of sites that may use this API include:
- Multi-window graphics editors à la Gimp can place various editing tools in accurately positioned windows.
- Virtual trading desks can show market trends in multiple windows any of which can be viewed in fullscreen mode.
- Slideshow apps can show speaker notes on the internal primary screen and the presentation on an external projector.
How to use the Window Management API
The problem
The time-tested approach to controlling windows,
Window.open()
, is unfortunately
unaware of additional screens. While some aspects of this API seem a little archaic, such as its
windowFeatures
DOMString
parameter, it has nevertheless served us well over the years. To specify a window's
position, you can pass the
coordinates as left
and top
(or screenX
and screenY
respectively) and pass the desired
size as
width
and height
(or innerWidth
and innerHeight
respectively). For example, to open a
400×300 window at 50 pixels from the left and 50 pixels from the top, this is the code that you
could use:
const popup = window.open(
'https://example.com/',
'My Popup',
'left=50,top=50,width=400,height=300',
);
You can get information about the current screen by looking at the
window.screen
property, which
returns a Screen
object. This is the
output on my MacBook Pro 13″:
window.screen;
/* Output from my MacBook Pro 13″:
availHeight: 969
availLeft: 0
availTop: 25
availWidth: 1680
colorDepth: 30
height: 1050
isExtended: true
onchange: null
orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
pixelDepth: 30
width: 1680
*/
Like most people working in tech, I have had to adapt myself to the new work reality and set up my personal home office. Mine looks like on the photo below (if you are interested, you can read the full details about my setup). The iPad next to my MacBook is connected to the laptop via Sidecar, so whenever I need to, I can quickly turn the iPad into a second screen.
If I want to take advantage of the bigger screen, I can put the popup from the code sample above on to the second screen. I do it like this:
popup.moveTo(2500, 50);
This is a rough guess, since there is no way to know the dimensions of the second screen. The info
from window.screen
only covers the built-in screen, but not the iPad screen. The reported width
of the built-in screen was 1680
pixels, so moving to 2500
pixels might work to shift the
window over to the iPad, since I happen to know that it is located on the right of my MacBook. How
can I do this in the general case? Turns out, there is a better way than guessing. That way is the
Window Management API.
Feature detection
To check if the Window Management API is supported, use:
if ('getScreenDetails' in window) {
// The Window Management API is supported.
}
The window-management
permission
Before I can use the Window Management API, I must ask the user for permission to do so.
The window-management
permission can be queried with the
Permissions API like so:
let granted = false;
try {
const { state } = await navigator.permissions.query({ name: 'window-management' });
granted = state === 'granted';
} catch {
// Nothing.
}
While browsers with the old and the new permission name are in use, be sure to use defensive code when requesting permission, as in the example below.
async function getWindowManagementPermissionState() {
let state;
// The new permission name.
try {
({ state } = await navigator.permissions.query({
name: "window-management",
}));
} catch (err) {
return `${err.name}: ${err.message}`;
}
return state;
}
document.querySelector("button").addEventListener("click", async () => {
const state = await getWindowManagementPermissionState();
document.querySelector("pre").textContent = state;
});
The browser can choose to show the permission prompt dynamically on the first attempt to use any of the methods of the new API. Read on to learn more.
The window.screen.isExtended
property
To find out if more than one screen is connected to my device, I access the
window.screen.isExtended
property. It returns true
or false
. For my setup, it returns true
.
window.screen.isExtended;
// Returns `true` or `false`.
The getScreenDetails()
method
Now that I know that the current setup is multi-screen, I can obtain more information about the
second screen using Window.getScreenDetails()
. Calling this function will show a permission prompt that
asks me whether the site may open and place windows on my screen. The function returns a promise
that resolves with a ScreenDetailed
object. On my MacBook Pro 13 with a connected iPad,
this includes a screens
field with two ScreenDetailed
objects:
await window.getScreenDetails();
/* Output from my MacBook Pro 13″ with the iPad attached:
{
currentScreen: ScreenDetailed {left: 0, top: 0, isPrimary: true, isInternal: true, devicePixelRatio: 2, …}
oncurrentscreenchange: null
onscreenschange: null
screens: [{
// The MacBook Pro
availHeight: 969
availLeft: 0
availTop: 25
availWidth: 1680
colorDepth: 30
devicePixelRatio: 2
height: 1050
isExtended: true
isInternal: true
isPrimary: true
label: "Built-in Retina Display"
left: 0
onchange: null
orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
pixelDepth: 30
top: 0
width: 1680
},
{
// The iPad
availHeight: 999
availLeft: 1680
availTop: 25
availWidth: 1366
colorDepth: 24
devicePixelRatio: 2
height: 1024
isExtended: true
isInternal: false
isPrimary: false
label: "Sidecar Display (AirPlay)"
left: 1680
onchange: null
orientation: ScreenOrientation {angle: 0, type: "landscape-primary", onchange: null}
pixelDepth: 24
top: 0
width: 1366
}]
}
*/
Information about the connected screens is available in the screens
array. Note how the value of
left
for the iPad starts at 1680
, which is exactly the width
of the built-in display. This
allows me to determine exactly how the screens are arranged logically (next to each other, on top of
each other, etc.). There is also data now for each screen to show whether it is an isInternal
one
and whether it is an isPrimary
one. Note that the built-in screen
is not necessarily the primary screen.
The currentScreen
field is a live object corresponding to the current window.screen
. The object
is updated on cross-screen window placements or device changes.
The screenschange
event
The only thing missing now is a way to detect when my screen setup changes. A new event,
screenschange
, does exactly that: it fires whenever the screen constellation is modified. (Notice
that "screens" is plural in the event name.) This means the event fires whenever a new screen or an
existing screen is (physically or virtually in the case of Sidecar) plugged in or unplugged.
Note that you need to look up the new screen details asynchronously, the screenschange
event
itself does not provide this data. To look up the screen details, use the live object from a cached
Screens
interface.
const screenDetails = await window.getScreenDetails();
let cachedScreensLength = screenDetails.screens.length;
screenDetails.addEventListener('screenschange', (event) => {
if (screenDetails.screens.length !== cachedScreensLength) {
console.log(
`The screen count changed from ${cachedScreensLength} to ${screenDetails.screens.length}`,
);
cachedScreensLength = screenDetails.screens.length;
}
});
The currentscreenchange
event
If I am only interested in changes to the current screen (that is, the value of the live object
currentScreen
), I can listen for the currentscreenchange
event.
const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
const details = screenDetails.currentScreen;
console.log('The current screen has changed.', event, details);
});
The change
event
Finally, if I am only interested in changes to a concrete screen, I can listen to that screen's
change
event.
const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
console.log('The first screen has changed.', event, firstScreen);
});
New fullscreen options
Until now, you could request that elements be displayed in fullscreen mode via the aptly named
requestFullScreen()
method. The method takes an options
parameter where you can pass
FullscreenOptions
. So far,
its only property has been
navigationUI
.
The Window Management API adds a new screen
property that allows you to determine
which screen to start the fullscreen view on. For example, if you want to make the primary screen
fullscreen:
try {
const primaryScreen = (await getScreenDetails()).screens.filter((screen) => screen.isPrimary)[0];
await document.body.requestFullscreen({ screen: primaryScreen });
} catch (err) {
console.error(err.name, err.message);
}
Polyfill
It is not possible to polyfill the Window Management API, but you can shim its shape so you can code exclusively against the new API:
if (!('getScreenDetails' in window)) {
// Returning a one-element array with the current screen,
// noting that there might be more.
window.getScreenDetails = async () => [window.screen];
// Set to `false`, noting that this might be a lie.
window.screen.isExtended = false;
}
The other aspects of the API, that is, the various screen change events and the screen
property of
the FullscreenOptions
, would simply never fire or silently be ignored respectively by
non-supporting browsers.
Demo
If you are anything like me, you keep a close eye on the development of the various cryptocurrencies. (In reality I very much do not because I love this planet, but, for the sake of this article, just assume I did.) To keep track of the cryptocurrencies that I own, I have developed a web app that allows me to watch the markets in all life situations, such as from the comfort of my bed, where I have a decent single-screen setup.
This being about crypto, the markets can get hectic at any time. Should this happen, I can quickly move over to my desk where I have a multi-screen setup. I can click on any currency's window and quickly see the full details in a fullscreen view on the opposite screen. Below is a recent photo of me taken during the last YCY bloodbath. It caught me completely off-guard and left me with my hands on my face.
You can play with the demo embedded below, or see its source code on glitch.
Security and permissions
The Chrome team has designed and implemented the Window Management API using the core principles defined in Controlling Access to Powerful Web Platform Features, including user control, transparency, and ergonomics. The Window Management API exposes new information about the screens connected to a device, increasing the fingerprinting surface of users, especially those with multiple screens consistently connected to their devices. As one mitigation of this privacy concern, the exposed screen properties are limited to the minimum needed for common placement use cases. User permission is required for sites to get multi-screen information and place windows on other screens. While Chromium returns detailed screen labels, browsers are free to return less descriptive (or even empty labels).
User control
The user is in full control of the exposure of their setup. They can accept or decline the permission prompt, and revoke a previously granted permission via the site information feature in the browser.
Enterprise control
Chrome Enterprise users can control several aspects of the Window Management API as outlined in the relevant section of the Atomic Policy Groups settings.
Transparency
The fact whether the permission to use the Window Management API has been granted is exposed in the browser's site information and is also queryable via the Permissions API.
Permission persistence
The browser persists permission grants. The permission can be revoked via the browser's site information.
Feedback
The Chrome team wants to hear about your experiences with the Window Management API.
Tell us about the API design
Is there something about the API that does not work like you expected? Or are there missing methods or properties that you need to implement your idea? Have a question or comment on the security model?
- File a spec issue on the corresponding GitHub repo, or add your thoughts to an existing issue.
Report a problem with the implementation
Did you find a bug with Chrome's implementation? Or is the implementation different from the spec?
- File a bug at new.crbug.com. Be sure to include as much detail as you
can, simple instructions for reproducing, and enter
Blink>Screen>MultiScreen
in the Components box. Glitch works great for sharing quick and easy repros.
Show support for the API
Are you planning to use the Window Management API? Your public support helps the Chrome team to prioritize features and shows other browser vendors how critical it is to support them.
- Share how you plan to use it on the WICG Discourse thread.
- Send a tweet to @ChromiumDev using the hashtag
#WindowManagement
and let us know where and how you are using it. - Ask other browser vendors to implement the API.
Helpful links
- Spec draft
- Public explainer
- Window Management API demo | Window Management API demo source
- Chromium tracking bug
- ChromeStatus.com entry
- Blink Component:
Blink>Screen>MultiScreen
- TAG Review
- Intent to Experiment
Acknowledgements
The Window Management API spec was edited by Victor Costan, Joshua Bell, and Mike Wasserman. The API was implemented by Mike Wasserman and Adrienne Walker. This article was reviewed by Joe Medley, François Beaufort, and Kayce Basques. Thanks to Laura Torrent Puig for the photos.