Como gerenciar várias telas com a API Window Management

Receber informações sobre telas conectadas e posicionar janelas em relação a elas.

API Window Management

A API Window Management permite enumerar as telas conectadas à máquina e colocar janelas em telas específicas.

Casos de uso sugeridos

Exemplos de sites que podem usar essa API:

  • Editores gráficos com várias janelas, como o Gimp, podem colocar várias ferramentas de edição em janelas posicionadas com precisão.
  • As mesas de negociação virtuais podem mostrar tendências de mercado em várias janelas, que podem ser visualizadas no modo de tela cheia.
  • Os apps de apresentação de slides podem mostrar anotações do apresentador na tela principal interna e a apresentação em um projetor externo.

Como usar a API Window Management

O problema

A abordagem testada pelo tempo para controlar janelas, Window.open(), infelizmente, não reconhece telas adicionais. Embora alguns aspectos dessa API pareçam um pouco arcaicos, como o parâmetro windowFeatures DOMString, ela foi muito útil ao longo dos anos. Para especificar a posição de uma janela, passe as coordenadas como left e top (ou screenX e screenY, respectivamente) e transmita o tamanho desejado como width e height (ou innerWidth e innerHeight, respectivamente). Por exemplo, para abrir uma janela de 400×300 a 50 pixels da esquerda e 50 pixels da parte de cima, use este código:

const popup = window.open(
  'https://example.com/',
  'My Popup',
  'left=50,top=50,width=400,height=300',
);

Para saber mais sobre a tela atual, consulte a propriedade window.screen, que retorna um objeto Screen. Esta é a saída do meu 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
*/

Como a maioria das pessoas que trabalham com tecnologia, tive que me adaptar à nova realidade do trabalho e montar meu escritório em casa. O meu é parecido com a foto abaixo (se tiver interesse, leia os detalhes completos sobre minha configuração). O iPad ao lado do meu MacBook está conectado ao laptop pelo Sidecar. Assim, sempre que preciso, posso transformar rapidamente o iPad em uma segunda tela.

Banco escolar em duas cadeiras. Em cima do banco da escola estão caixas de sapatos que apoiam um laptop e dois iPads ao redor.
Uma configuração de várias telas.

Se eu quiser aproveitar a tela maior, posso colocar o pop-up da amostra de código acima na segunda tela. Eu faço assim:

popup.moveTo(2500, 50);

Essa é uma estimativa aproximada, já que não há como saber as dimensões da segunda tela. As informações de window.screen cobrem apenas a tela integrada, e não a tela do iPad. O width informado da tela integrada era de 1680 pixels. Portanto, mudar para 2500 pixels pode funcionar para mover a janela para o iPad, já que eu sei que ela está localizada à direita do meu MacBook. Como posso fazer isso no caso geral? Na verdade, há uma maneira melhor de fazer isso. Dessa forma, a API Window Management.

Detecção de recursos

Para verificar se a API Window Management é compatível, use:

if ('getScreenDetails' in window) {
  // The Window Management API is supported.
}

A permissão window-management

Antes de usar a API Window Management, preciso pedir permissão ao usuário. A permissão window-management pode ser consultada com a API Permissions, assim:

let granted = false;
try {
  const { state } = await navigator.permissions.query({ name: 'window-management' });
  granted = state === 'granted';
} catch {
  // Nothing.
}

Enquanto os navegadores com o nome de permissão antigo e novo estiverem em uso, use o código de defesa ao solicitar permissão, como no exemplo abaixo.

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;
});

O navegador pode escolher mostrar o comando de permissão dinamicamente na primeira tentativa de usar qualquer um dos métodos da nova API. Continue lendo para saber mais.

A propriedade window.screen.isExtended

Para descobrir se mais de uma tela está conectada ao meu dispositivo, acesse a propriedade window.screen.isExtended. Ele retorna true ou false. Na minha configuração, ele retorna true.

window.screen.isExtended;
// Returns `true` or `false`.

O método getScreenDetails()

Agora que sei que a configuração atual é para várias telas, posso acessar mais informações sobre a segunda tela usando Window.getScreenDetails(). Chamar essa função vai mostrar uma solicitação de permissão perguntando se o site pode abrir e colocar janelas na tela. A função retorna uma promessa que é resolvida com um objeto ScreenDetailed. No meu MacBook Pro 13 com um iPad conectado, isso inclui um campo screens com dois objetos ScreenDetailed:

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
  }]
}
*/

As informações sobre as telas conectadas estão disponíveis na matriz screens. Observe como o valor de left para o iPad começa em 1680, que é exatamente o width da tela integrada. Isso permite que eu determine exatamente como as telas são organizadas de forma lógica (uma ao lado da outra, uma em cima da outra etc.). Agora há dados para cada tela para mostrar se ela é isInternal ou isPrimary. A tela integrada não é necessariamente a tela principal.

O campo currentScreen é um objeto ativo correspondente ao window.screen atual. O objeto é atualizado em posicionamentos de janela em várias telas ou mudanças de dispositivo.

O evento screenschange

A única coisa que falta agora é uma maneira de detectar quando a configuração da tela muda. Um novo evento, screenschange, faz exatamente isso: ele é disparado sempre que a constelação de telas é modificada. Observe que "telas" está no plural no nome do evento. Isso significa que o evento é acionado sempre que uma nova tela ou uma tela existente é conectada ou desconectada (fisicamente ou virtualmente, no caso do Sidecar).

É necessário procurar os detalhes da nova tela de forma assíncrona. O evento screenschange não fornece esses dados. Para consultar os detalhes da tela, use o objeto em tempo real de uma interface Screens em cache.

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;
  }
});

O evento currentscreenchange

Se eu tiver interesse apenas nas mudanças na tela atual (ou seja, no valor do objeto currentScreen em tempo real), posso detectar o evento currentscreenchange.

const screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('currentscreenchange', async (event) => {
  const details = screenDetails.currentScreen;
  console.log('The current screen has changed.', event, details);
});

O evento change

Por fim, se eu tiver interesse apenas em mudanças em uma tela específica, posso detectar o evento change dessa tela.

const firstScreen = (await window.getScreenDetails())[0];
firstScreen.addEventListener('change', async (event) => {
  console.log('The first screen has changed.', event, firstScreen);
});

Novas opções de tela cheia

Até agora, você podia solicitar que os elementos fossem exibidos no modo de tela cheia usando o método requestFullScreen() denominado corretamente. O método usa um parâmetro options em que você pode transmitir FullscreenOptions. Até agora, a única propriedade foi navigationUI. A API Window Management adiciona uma nova propriedade screen, que permite determinar em qual tela iniciar a visualização em tela cheia. Por exemplo, se você quiser colocar a tela principal em tela cheia:

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

Não é possível aplicar o polyfill à API Window Management, mas é possível alterar a forma dela para codificar exclusivamente com base na nova 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;
}

Os outros aspectos da API, ou seja, os vários eventos de mudança de tela e a propriedade screen do FullscreenOptions, simplesmente nunca seriam acionados ou ignorados silenciosamente por navegadores que não oferecem suporte.

Demonstração

Se você é como eu, acompanha de perto o desenvolvimento das várias criptomoedas. Na verdade, não, porque eu amo esse planeta, mas, para fins deste artigo, apenas suponha que eu amei. Para acompanhar as criptomoedas que tenho, desenvolvi um app da Web que me permite monitorar os mercados em todas as situações da vida, como no conforto da minha cama, onde tenho uma configuração decente de tela única.

Tela de TV gigantesca no final de uma cama com as pernas da autora parcialmente visíveis. Na tela, uma mesa de negociação de criptomoedas falsa.
Relaxar e observar os mercados.

Por falar nisso, os mercados podem ficar agitados a qualquer momento. Se isso acontecer, posso rapidamente me mudar para minha mesa, onde tenho uma configuração de várias telas. posso clicar na janela de qualquer moeda e ver rapidamente os detalhes completos em uma tela inteira na tela oposta. Confira abaixo uma foto minha recentemente tirada no último Banho de sangue do YCY. Isso me pegou completamente desprevenido e me deixou com as mãos no rosto.

O autor com as mãos no rosto em pânico olhando para a mesa de negociação de criptomoedas falsa.
Panicky, testemunhando o banho de sangue da YCY.

Você pode testar a demonstração incorporada abaixo ou conferir o código-fonte dela no glitch.

Segurança e permissões

A equipe do Chrome projetou e implementou a API Window Management usando os princípios básicos definidos em Como controlar o acesso a recursos avançados da plataforma da Web, incluindo controle do usuário, transparência e ergonomia. A API Window Management expõe novas informações sobre as telas conectadas a um dispositivo, aumentando a superfície de técnicas de impressão digital dos usuários, especialmente aqueles com várias telas sempre conectadas aos dispositivos. Como uma mitigação dessa preocupação com a privacidade, as propriedades de tela expostas são limitadas ao mínimo necessário para casos de uso de posicionamento comuns. É necessária a permissão do usuário para que os sites recebam informações de multitelas e posicionem janelas em outras telas. Embora o Chromium retorne rótulos de tela detalhados, os navegadores podem retornar rótulos menos descritivos (ou até mesmo rótulos vazios).

Controle do usuário

O usuário tem controle total sobre a exposição da configuração. Eles podem aceitar ou recusar a permissão e revogar uma permissão concedida anteriormente pelo recurso de informações do site no navegador.

Controle empresarial

Os usuários do Chrome Enterprise podem controlar vários aspectos da API Window Management, conforme descrito na seção relevante das configurações de Grupos de políticas atômicas.

Transparência

A permissão para usar a API Window Management foi concedida e está exposta nas informações do site do navegador. Ela também pode ser consultada pela API Permissions.

Persistência de permissões

O navegador mantém as permissões concedidas. A permissão pode ser revogada pelas informações do site do navegador.

Feedback

A equipe do Chrome quer saber sobre sua experiência com a API Window Management.

Conte sobre o design da API

Há algo na API que não funciona como esperado? Ou há métodos ou propriedades ausentes que você precisa para implementar sua ideia? Tem dúvidas ou comentários sobre o modelo de segurança?

  • Envie um problema de especificação no repositório do GitHub correspondente ou adicione sua opinião a um problema existente.

Informar um problema com a implementação

Você encontrou um bug na implementação do Chrome? Ou a implementação é diferente da especificação?

  • Registre um bug em new.crbug.com. Inclua o máximo de detalhes possível, instruções simples para reprodução e insira Blink>Screen>MultiScreen na caixa Components. O Glitch é ótimo para compartilhar reprosagens rápidas e fáceis.

Mostrar suporte à API

Você planeja usar a API Window Management? Seu apoio público ajuda a equipe do Chrome a priorizar recursos e mostra a outros fornecedores de navegadores como é essencial oferecer suporte a eles.

Links úteis

Agradecimentos

A especificação da API Window Management foi editada por Victor Costan, Joshua Bell e Mike Wasserman. A API foi implementada por Mike Wasserman e Adrienne Walker. Este artigo foi revisado por Joe Medley, François Beaufort e Kayce Basques. Obrigada a Laura Torrent Puig pelas fotos.