This proposal is an early design sketch by Chrome's Web Apps & Capabilities (Fugu) team to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
This proposal introduces additional ways for web applications to introspect and control their windows, to enable critical window management functionality on the web platform.
The proposed enhancement would allow web applications to maximize, minimize, and restore their windows and introspect that window display state. Further, it allows applications to be notified when the window is repositioned, and control whether the window can be resized.
The window management permission will be required for the new JS APIs (window.maximize()/minimize()/restore()/setResizable(bool)).
The new CSS media features display-state
and resizable
are not gated behind a permission as they can follow the non-AWC API toggled states as well. Also window.onmove event won't be gated behind any permission and unlike other APIs, it will be available to normal browser window too (not just web apps).
Virtual Desktop Infrastructure (VDI) providers allow software running remotely on a host device to be displayed on a separate client device. For example, the VDI provider's cloud might run a text processor application, and present the user interface window within a client device’s desktop environment, as if that application were running locally.
Additional Explorations of Window Management contained suggestions for incremental improvements to existing window management APIs. That helped inform this explainer, and contains more background.
Window Controls Overlay offers new capabilities for web applications to control the area that would generally be occupied by the title bar in an installed web application running on a desktop environment. That specification also allows applications to define draggable regions or content, which is highly relevant to this proposal.
Demo glitch app that ties in proposals from this doc https://seamless-titlebar-example.glitch.me/
VDI web clients have limited abilities to integrate remote application windows with the local desktop environment, which creates suboptimal experiences for their users. Currently, they can only present full disjoint remote desktop environments (e.g. in a local fullscreen window), or present individual remote applications in separate local windows with titlebar window controls that are inoperative, redundant, and confusing for users.
Remote window presented within a local window
Several VDI clients, including Citrix and VMWare, have reported that their users strongly desire an integrated experience as their work is increasingly happening both on local client and remote host devices.
Unfortunately, the web platform offers no means for web applications to signal the user agent when users interact with remote application (or custom) window controls. Further, web applications cannot introspect the local window’s display state, and must poll for local window position changes. These platform gaps create disconnects between local and remote windows, and prevent web VDI clients from offering functionality expected by users.
These missing capabilities also prevent a broader set of web applications from offering compelling window management experiences for their users.
This proposal seeks to enable local web applications to convey a user's intended window control interactions with remote (or custom) window controls. Summary of the API proposals, which are generally gated by Window Management permission:
await window.minimize()/maximize()/restore()
changes the window display statedisplay-state
CSS media feature yields the current window display statewindow.onmove
event is fired when the window movesawait window.setResizable(bool)
sets whether the window is user-resizableresizable
CSS media feature yields whether the window is user-resizable
Use cases explored here also rely upon a parallel effort to standardize the existing CSS property -webkit-app-region/app-region
.
An open proposal to standardize the existing -webkit-app-region/app-region
CSS property is valuable for Window Controls Overlay and for VDI client use cases explored here. This property allows web apps to specify web content areas (e.g. custom or remote application title bars) that can be used to drag the window within the local desktop environment. (ref. Electron seamless titlebar tutorial):
<body>
<header id="titlebar">
<div id="drag-region"></div>
</header>
...
</body>
#titlebar #drag-region {
app-region: drag; /* The magic happens here. */
}
A new move
event on window
fires when the window’s position changes, in a pattern similar to the existing window resize
event, if the site has permission. When a user-agent determines that the window’s top left corner at coordinates (x, y) has moved (e.g. as a result of the user repositioning the browser window), run these steps:
- If the site doesn’t have the Window Management permission, abort these steps.
- Update the
window.screenX
andwindow.screenY
properties with the new coordinates. - Fire an event named
move
at thewindow
object.
Move
events will not be fired at background pages - when the page regains focus a single coalesced event will be fired (similar to how the resize
event works). This is to prevent cross-site tracking amongst tabs in the same window.
See related specification material for moveTo, resizeTo & resize.
window.addEventListener('move', () =>
console.log('New window position: ${window.screenX}, ${window.screenY}'));
This obviates the need for polling if a web application wants to save a user’s window placement and restore that on their next session, or change the relative placement of remote windows in response to changes of their local counterparts.
That second use case is particularly important if VDI implementations use a single composited framebuffer to capture remote application window content from a composited view of the remote desktop. In that case, local and remote window placements must be synchronized, so that remote window occlusions (their overlaps) correspond with their local counterparts.
In order to provide web applications the ability to integrate a remote application’s window controls with the local window manager, we propose adding new window.minimize()/maximize()/restore()
async APIs.
In addition the app needs to be able to 1) query the current window display state (window.displayState
property), and 2) receive window display state change events (new displaystatechange
event which fires when the window display state changes). This is so that the app can eg. toggle maximize/restore buttons.
Code example of manually rolling the buttons (ref. Electron seamless titlebar tutorial):
<header id="titlebar">
<div id="drag-region">
<div id="window-controls">
<div id="minimize-button"><img src="minimize.png" /></div>
<div id="maximize-button"><img src="maximize.png" /></div>
<div id="restore-button"><img src="restore.png" /></div>
<div id="close-button"><img src="close.png" /></div>
</div>
</div>
</header>
#window-controls {
/* We can't click on draggable regions for the window, make buttons
non-draggable. */
app-region: no-drag;
}
/* Toggle maximize/restore buttons. */
/* Hide restore button by default, show only in maximized state. */
#restore-button {
display: none !important;
}
/* Handle maximized state. */
@media (display-state: maximized) {
#titlebar {
width: 100%;
padding: 0;
}
#window-title {
margin-left: 12px;
}
#restore-button {
display: flex !important;
}
#maximize-button {
display: none;
}
}
/* Handle buttons. */
document.getElementById('minimize-button').addEventListener('click', event => {
await window.minimize();
});
document.getElementById('maximize-button').addEventListener('click', event => {
await window.maximize();
});
document.getElementById('restore-button').addEventListener('click', event => {
await window.restore();
});
document.getElementById('close-button').addEventListener('click', event => {
window.close();
});
/* Query the window display-state in JS. */
window.matchMedia("(display-state: maximized)").matches;
In addition VDI apps may request that the client window be made resizable or not, to match the behavior of the remote window, for that we would also add window.setResizable(bool)
async API function and we would get the value with a resizable
CSS media feature.
/* Handle resizable styles. */
@media (resizable: true) {
#class-name {
/*styles to be applied*/
}
}
// Make the window size fixed.
try {
await window.setResizable(false);
} catch (error) {
console.log('Setting a fixed size window failed: ', error);
}
New window.minimize()/maximize()/restore()
async APIs ask the user agent to perform the operation on the provided window object. They return a Promise which is resolved once the operation has succeeded, or is rejected if there was an error (lack of necessary permission for the API).
- If a properly-permissioned request matches the target state, it should just succeed idempotently (eg. maximize() when the window is already maximized). If the requested state cannot be achieved, the promise should be rejected (eg. maximize() a window that is closed, tabbed, cross-origin, on another virtual desktop/workspace, blocked by a modal dialog, etc.).
minimize()/maximize()/restore()
operations follow platform convention.- Snapped states aren’t typically exposed to nor initiated by client applications on desktop platforms, and each platform does them a little differently. They are out of scope here.
A new display-state
CSS media feature indicates the current display state of the window. Possible values are normal/minimized/maximized/fullscreen
. The property will always contain the value normal
if there was an error (e.g. a lack of permission).
A new window.setResizable(bool)
async API asks the user agent to set whether users can resize the provided window object. It returns a Promise which is successfully resolved, or is rejected if there was an error (lack of permission). This allows VDI client applications to request that local windows match the behavior of the remote window.
This API requests that the user agent prevent resize drag handles on top-level windows, and it has no effect on iframe windows. Maximize is prevented, but minimize and restore still work as expected. Not all resizes can be prevented (e.g. window being moved to another display with a size smaller than the window) and it’s ultimately the client app's responsibility to handle such cases.
While user-initiated attempts to enter fullscreen are prevented, applications can still utilize the Fullscreen API (i.e. Element.requestFullscreen()
) to request fullscreen programmatically. To prevent script-initiated fullscreen requests, developers can override Element.prototype.requestFullscreen
, set an HTTP fullscreen
permission-policy or use the FullscreenAllowed enterprise policy for managed sessions.
A new resizable
CSS media feature indicates whether the provided window object is user resizable.
window.minimize()/maximize()/restore()
async functions that return promises for async permission checks/prompts and for async window manager ops- PROs:
- functions perform well-understood actions
- similar to how
play()
andpause()
work on media elements (even though there’s a.paused
getter), orshow()
andclose()
on dialogs
- CONs:
- need multiple functions compared to
setWindowState
option
- need multiple functions compared to
- PROs:
window.setDisplayState(state)
async function (& similarwindow.setResizable(bool)
to set whether users can resize the window)- PROs:
- compared to
updateParams
simpler and better defined
- compared to
- CONs:
- less intuitive than the
minimize/maximize/restore
option
- less intuitive than the
- PROs:
window.updateParams({param: value, …})
async function- PROs:
- one function that could accommodate both setting the window
displayState
&resizable
- future extensible
- one function that could accommodate both setting the window
- CONs:
- design too generic
- doubtful that all future "params" (whatever those are) will have the same shape
- type system will already fight back if we try to have one that is a string (
displayState
) and one that is a boolean (resizable
)
- PROs:
display-state
(&resizable
) CSS media feature- PROs:
- Precedent of display-mode
- Allows for UI changes to be specified strictly in CSS (no need for JS logic)
- change event for free (don’t need to specify a separate
displaystatechange
event)
- CONs:
- Limited avenues for permission gating:
- Always yield
normal
fordisplay-state
,true
forresizable
without permission
- Always yield
- Limited avenues for permission gating:
- PROs:
- Sync
window.displayState
andwindow.resizable
attributes and adisplaystatechange
event onwindow
- PROs:
- Straightforward and convenient
- Alignment with existing attributes on the window object
- Ergonomic sync attribute access from event handlers
- CONs:
- Limited avenues for permission gating:
- Always yield
normal
fordisplayState
,true
forresizable
without permission - Do not fire
displaystatechange
without permission, or fire it immediately when a handler is added with a "permission missing" error.
- Always yield
- Increases the fingerprinting surface of the user agent
- Adds new attributes on a global interface
- Limited avenues for permission gating:
- PROs:
- Async
await window.getDisplayState()
yields the current window display state value (& similarawait window.getResizable()
yields whether the window is resizable)- PROs:
- Simple well-defined API shape with one purpose
- Supports permission gating
- CONs:
- Poor ergonomics for checking state from event handlers
- PROs:
- Async
await window.queryParam()
yields a dictionary with named param & value ({state: 'maximized'}
or{resizable: false}
)- PROs:
- Supports permission gating
- CONs:
- API too generic and accepting an arbitrary string
- Poor ergonomics for checking state from event handlers
- PROs:
- Async
await window.getDisplayState()
yields permission-gated access to a newDisplayState
interface- PROs:
- Precedent of
window.getScreenDetails()
,navigator.getBattery()
, andnavigator.requestMIDIAccess()
- Encapsulates permission-gated properties, methods, and event targets
- Provides live data that callers can cache a reference to
- Precedent of
- CONs:
- Minor added complexity for callers
- Puts
minimize()/maximize()/restore()
in a different place thanclose()
. (Although,close()
is already different because it's sync)
- PROs:
- Sync
window.displayState
interface access- PROs:
- Precedent of
navigator.geolocation
andnavigator.clipboard
- Encapsulates properties, methods, and event targets
- Provides live data that callers can cache a reference to
- Precedent of
- CONs:
- Does not offer easy permission gating
- Puts
minimize()/maximize()/restore()
in a different place thanclose()
. (Although,close()
is already different because it's sync)
- PROs:
An alternative to the JS API would be to extend app-region
CSS to support minimize/maximize/restore/close types. That lets a VDI simply set a region over each of the remote applications buttons that request the user agent to handle clicks in a particular way. Note: this is the old school windows hit testing approach (“I ask the window manager what is here where I clicked” - “minimize hit test”).
- PROs:
- might be simpler than the JS API
- CONs:
- might not work for all VDI’s use cases (user selecting a remote-app menu item like Window>Minimize would need a transient app-region and would be tricky; user pressing a remote-app-specific hotkey like Ctrl+M to minimize the remote window would be impossible)
Replacing window.setResizable() with manifest property and window.open() feature (static vs. dynamic resizable)
The window resizable property could be set statically during window creation (via either manifest property or window.open() feature), but this doesn’t cover all of the VDI clients use-cases as the property could change dynamically during window life-time (since they display remote windows).
JS APIs usage would be gated by:
- Secure origins with the Window Management permission
- Consuming a user gesture for
window.minimize()/maximize()/restore()
APIs:- To make
restore()
work with minimized windows (which can’t get a user gesture) we will extend the capability delegation API (i.e.postMessage()
consumes activation in one window and transfers it to another window permitting it torestore()
, even cross-origin)
- To make
window.minimize()/maximize()/restore()
are limited to:- Windows with ‘standalone’ or ‘minimal-ui’ display-mode, i.e. installed application windows, and origins running in their own window, whether installed or not, e.g. Chrome’s “Create shortcut…” with “Open as window” checked
- Popup windows created by script, i.e.
window.open()
window.onmove
event is allowed for origins in a tab, since they can already accesswindow.screenX|Y
, but it is only fired on foreground tabs, to prevent cross-site tracking amongst background tabs in the same window.
Here are the most prevalent concerns raised by this API. Malicious sites may wish to:
- minimize windows (abuse similar to pop-unders)
- restore minimized windows or maximize ‘normal’ windows (to attempt click-jacking, show undesirable content, or block display of or access to other windows)
- get window display state and resizable information (for fingerprinting)
- observe concurrent window display state changes across site boundaries (for cross-site identity joining)
It is valuable to also consider concerns raised by preexisting window control APIs, such as window.open()
, window.moveTo|resizeTo()
, window.close()
, window.screenX|screenY|outerWidth|outerHeight
, element.requestFullscreen()
, and others, as those likely apply to this API surface too. In general, the threats associated with this API functionality (especially restore) are the same as that of window management. That is, if an app can open new windows or move and resize its existing windows anywhere on the device’s screens, then the ability to maximize, minimize, and restore its windows presents relatively little additional concern. It is worth noting that web apps today can enter fullscreen (with a user gesture) where they are given total control over the display.
Additional Explorations of Window Placement on the Web - Helped inform API shape, contains more background.
Draggable region (Frameless Window | Electron) - Electron apps use -webkit-app-region CSS property to specify draggable regions.
Electron seamless titlebar tutorial
- Electron tutorial on replacing the native title bar with a custom hand rolled one.
- Creating a title bar and adding the window title, making it draggable, adding window control buttons, styling them, implementing logic behind them.
Many thanks for valuable contributions, feedback, and advice from many folks, especially: