Como funciona o elemento HTML geolocation
Pedir a localização do usuário sempre foi um dos piores fluxos da web: se a permissão for negada uma vez, o usuário fica trancado do lado de fora sem saber como voltar. O elemento geolocation resolve isso com atributos HTML, menos JavaScript e um diálogo de permissão do qual dá para se recuperar.
O elemento HTML <geolocation> faz exatamente o que o nome sugere: é um elemento dedicado a obter a localização do usuário, uma única vez ou continuamente. As opções são definidas por atributos HTML em vez de JavaScript, mas o JavaScript continua sendo necessário.
Ainda assim, o <geolocation> exige menos linhas de JavaScript e oferece um tratamento de erros e permissões bem superior. Curiosidade: ele começou como um elemento <permission> para todos os fins, mas hoje é dedicado só à geolocalização.
Menos código é sempre bom, mas o ponto central aqui é o prompt de permissão. Com a envelhecida Geolocation API do JavaScript, negar a permissão em qualquer momento pode trancar o usuário do lado de fora, sem forma de recuperação a não ser mudar manualmente as permissões do navegador ou do sistema operacional.
Isso pode ser uma experiência dolorosa, especialmente para usuários menos familiarizados com tecnologia. Além disso, do lado de quem desenvolve, é preciso envolver a Permissions API.
Já o elemento <geolocation> oferece:
- Melhor tratamento de erros e, quando aplicável, orientação para recuperação
- Prompt de permissão controlado pelo usuário, mesmo que ele já tenha negado antes
- Localização automática, caso o usuário já tenha concedido a permissão anteriormente
- Um estado concedido que pode ser estilizado, via pseudoclasse CSS
:granted
O <geolocation> é melhor em todos os aspectos, exceto suporte dos navegadores (ele exige Chrome 144+). Por isso, o que vem a seguir explica como usá-lo ao lado da antiga atual Geolocation API do JavaScript, para que o usuário tenha a melhor experiência quando o novo elemento estiver disponível.
Pedindo a localização do usuário com <geolocation>
O elemento <geolocation> aceita vários atributos que correspondem, em boa parte, às opções dos métodos getCurrentPosition() e watchPosition() da envelhecida Geolocation API.
Primeiro, o atributo accuracymode aceita dois valores: approximate (o padrão) e precise.
O accuracymode=approximate equivale à opção enableHighAccuracy: false da Geolocation API (também o padrão), enquanto accuracymode=precise equivale a enableHighAccuracy: true, que devolve uma localização mais precisa se o dispositivo conseguir obtê-la.
O atributo booleano autolocate tenta obter a localização do usuário automaticamente, presumindo que ele já tenha concedido a permissão ao site anteriormente.
O atributo booleano watch equivale a chamar o método watchPosition() em vez do getCurrentPosition(). O getCurrentPosition() busca a localização uma vez só, enquanto o watchPosition() acompanha a localização ao longo do tempo.
Assim como accuracymode=precise e enableHighAccuracy: true, isso drena a bateria mais rápido.
O atributo de handler de evento onlocation pode ser usado para executar JavaScript sempre que dados de localização ou informação de erro forem passados ao navegador.
O que o <geolocation> não oferece é a possibilidade de especificar o timeout (quanto tempo o navegador deve esperar por uma resposta) ou o maximumAge (quão antiga pode ser uma localização em cache). No lugar disso, o navegador cuida desses valores como achar melhor.
Isso, na prática, é uma coisa boa: escolher os valores certos caso a caso é uma complexidade da qual simplesmente não se precisa.
Na prática, o uso do <geolocation> fica assim:
<!-- Localiza o usuário com precisão e o acompanha --><geolocation accuracymode="precise" autolocate watch> <!-- Renderize isto quando <geolocation> não tiver suporte --> <button id="fallbackButton">Use precise location</button></geolocation>Isso, claro, sem contar o lado JavaScript do <geolocation> nem o fallback para a Geolocation API. Se você quiser apenas o código completo, o artigo original traz a versão com getCurrentPosition() e a versão com watchPosition().
A geolocalização não funciona em contextos insecure, então, ainda que a lógica esteja correta, as demonstrações no CodePen não funcionam de fato. De todo modo, elas são templates de lógica bruta, não demonstrações completas.
Agora, ao JavaScript de tudo isso, começando pelo código que fica do outro lado daquela marcação <geolocation>.
Lidando com os dados de <geolocation> em JavaScript
Primeiro, é preciso garantir que o <geolocation> tem suporte, com if ("HTMLGeolocationElement" in window). Se tiver, o elemento é selecionado com const geoElement = document.querySelector("geolocation").
Infelizmente, a partir daí é preciso entrar na parte mais complexa do <geolocation>: a validade dele. A propriedade isValid retorna true ou false, e a propriedade invalidReason retorna "" (string vazia) ou um valor enumerado indicando o motivo.
Esses motivos, na maioria, vêm de descuido de quem desenvolve, então os usuários não deveriam topar com esses “bloqueadores”. Mas, se topassem, o botão do <geolocation> ficaria desabilitado.
Estes são os cenários em que isso pode acontecer (os bloqueadores estão em ordem de severidade e o invalidReason retorna apenas o mais severo):
illegal_subframe: o elemento<geolocation>está dentro de um<fencedframe>ou de um<iframe>insecure (o segundo caso é o motivo de as demonstrações no CodePen não funcionarem)unsuccessful_registration: a página tem mais de três elementos<geolocation>recently_attached: o elemento<geolocation>foi anexado ao DOM há muito pouco tempo (esse bloqueador expira bem rápido)intersection_changed: o elemento<geolocation>está se movendointersection_out_of_viewport_or_clipped: o elemento<geolocation>não está inteiramente dentro da viewportintersection_occluded_or_distorted: algo está obstruindo o elemento<geolocation>style_invalid: o elemento<geolocation>está estilizado de um jeito que não é permitido (mais sobre isso adiante)
Como se vê, esses bloqueadores são permanentemente evitáveis, desde que sejam identificados e corrigidos. Vale olhar melhor como o invalidReason reporta cada um deles.
Como descreve o comentário no JavaScript abaixo, o isValid sempre retorna false no início, enquanto o invalidReason retorna recently_attached. Na prática, isso desabilita o botão do <geolocation> por uma fração de segundo, para evitar clickjacking. A menos, claro, que se aplique um bloqueador de severidade maior.
A linha abaixo não é realmente necessária: ela está aqui apenas para mostrar como o <geolocation> nunca será válido no carregamento da página.
/* No começo, isValid === false e invalidReason === "recently_attached"a menos que isValid === false e invalidReason === "um motivo de severidade maior" */console.warn(`isValid: ${geoElement.isValid}, invalidReason: ${geoElement.invalidReason}`);Se o <geolocation> passar a ser inválido por um motivo mais permanente, o isValid obviamente continuará false, então não dá para usar o event listener validationstatuschange aqui.
Mas é possível envolver a linha em um setTimeout(), como abaixo. Isso vai registrar um novo bloqueador no console ou registrar que isValid é true (caso em que invalidReason será uma string vazia).
/* Depois de ~300ms, quando <geolocation> não está mais "recém-anexado" (ao DOM)ou isValid === true e invalidReason === "" (string vazia)ou isValid === false e invalidReason === "o motivo de maior severidade" */setTimeout(() => { console.log(`isValid: ${geoElement.isValid}, invalidReason: ${geoElement.invalidReason}`);}, 300);Se o status de validação mudar depois, então dá para usar o event listener validationstatuschange (de novo, como abaixo). Note que, se não houver bloqueadores persistentes, ele vai disparar quase imediatamente, quando o invalidReason sair de recently_attached para uma string vazia.
/* Se a validade de <geolocation> mudar */geoElement.addEventListener("validationstatuschange", () => { if (geoElement.isValid) { /* <geolocation> é válido (se não houver bloqueadores, ele fica válido assim que deixa de estar "recém-anexado") */ } else { console.error(`<geolocation> invalid: ${geoElement.invalidReason}`); }});Agora que já dá para depurar o status de validação e corrigir de forma permanente qualquer bloqueador persistente, vamos falar do status de permissão.
Há algumas propriedades e eventos para trabalhar:
initialPermissionStatus: propriedade que retornadenied,grantedouprompt(ou seja, nenhum dos dois), com base no status da permissão no primeiro carregamento da páginapermissionStatus: o status atual da permissãopromptaction: evento disparado quando o usuário nega ou concede a permissão pelo diálogo de permissão do<geolocation>promptdismiss: dispara quando o usuário fecha o diálogo, caso em que opermissionStatuspermanece inalterado
/* Determina o status inicial da permissão */if (geoElement.initialPermissionStatus === "denied") { /* O usuário negou a permissão anteriormente */} else if (geoElement.initialPermissionStatus === "granted") { /* O usuário concedeu a permissão anteriormente */} else if (geoElement.initialPermissionStatus === "prompt") { /* O usuário ainda não fez uma escolha */}
/* Se o usuário negar ou conceder a permissão */geoElement.addEventListener("promptaction", () => { if (geoElement.permissionStatus === "denied") { /* O usuário negou a permissão */ } else if (geoElement.permissionStatus === "granted") { /* O usuário concedeu a permissão */ }});
/* Se o usuário fechar o diálogo */geoElement.addEventListener("promptdismiss", () => { if (geoElement.permissionStatus === "denied") { /* O status da permissão continuou negado */ } else if (geoElement.permissionStatus === "granted") { /* O status da permissão continuou concedido */ } else if (geoElement.permissionStatus === "prompt") { /* O status da permissão continuou como prompt */ }});Na prática, não é nada óbvio para que tudo isso serviria. Se o usuário negou a permissão antes, por exemplo, o próprio diálogo de permissão do <geolocation> já permite que ele se recupere disso automaticamente:
É o contrário do que acontece na Geolocation API mais antiga, que dificilmente ajuda o usuário a se recuperar. Naquele caso, é preciso ler o status da permissão, gerenciar o estado do componente conforme ele e fornecer instruções de recuperação para que o usuário conceda o acesso manualmente. O <geolocation> cuida de tudo isso.
O que realmente vai ser necessário é o novo evento location, disparado sempre que o navegador nos passa dados de localização (geoElement.position, no caso) ou informação de erro (geoElement.error).
Depois, dentro do callback do event listener, presumindo que geoElement.position seja truthy, dá para acessar position.coords e position.timestamp, sintetizando os dados de localização assim:
/* Se o navegador passar dados de localização ou informação de erro */geoElement.addEventListener("location", () => { /* Se forem dados de localização */ if (geoElement.position) { /* Sintetiza os dados */ const { latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed } = geoElement.position.coords;
const timestamp = geoElement.position.timestamp; } else if (geoElement.error) { /* Se for informação de erro */ }});Já se geoElement.error for truthy, dá para acessar error.message (útil para registrar no console) e error.code, muito mais adequado para tratamento de erros.
São três os códigos de erro possíveis, cada um com uma constante associada, de modo que não é preciso decorar o que cada código representa. Então 1 representa PERMISSION_DENIED, 2 representa POSITION_UNAVAILABLE e, por fim, 3 representa TIMEOUT. A avaliação fica assim:
/* Se o navegador passar dados de localização ou informação de erro */geoElement.addEventListener("location", () => { if (geoElement.position) { /* Se forem dados de localização */ } else if (geoElement.error) { /* Se for informação de erro */ console.error(`<geolocation> error: ${geoElement.error.message}`);
if (geoElement.error.code === geoElement.error.PERMISSION_DENIED) { /* Sem HTTPS ou com má configuração no servidor */ } else if (geoElement.error.code === geoElement.error.POSITION_UNAVAILABLE) { /* Sem fonte de localização (satélite de GPS ou rede Wi-Fi/torre de celular próxima) */ } else if (geoElement.error.code === geoElement.error.TIMEOUT) { /* A detecção da fonte de localização ou o hardware demorou demais */ } }});O PERMISSION_DENIED significa que o site não está sendo servido por HTTPS (o navegador negou a permissão) ou que há algum tipo de má configuração no servidor (o servidor negou a permissão). Esses dois erros são corrigíveis em definitivo e não deveriam acontecer em produção.
A única forma de negar a permissão, pelo que se sabe, é bloquear o acesso à localização nas configurações do navegador, clicar no botão do <geolocation> e escolher continuar negando. Esse cenário é improvável e, de todo modo, não justifica uma mensagem de erro.
Não existe mecanismo de negação para bloqueios em nível de sistema operacional, justamente para não dar a impressão de que o navegador pode impor um. Ou seja: provavelmente não é preciso fazer nada para o PERMISSION_DENIED.
E, para deixar claro: como o <geolocation> é invocado pelo usuário, ele nunca define por conta própria o status da permissão como negado (de novo, pelo que se sabe).
O POSITION_UNAVAILABLE significa que o dispositivo não consegue detectar uma fonte de localização (redes Wi-Fi e torres de celular próximas, além de satélites de GPS) para determinar onde está.
Usar uma VPN ou visitar o site por um navegador embutido em app também pode causar esse erro, o que faz dele o mais complicado de comunicar ao usuário.
O TIMEOUT significa que a detecção da fonte de localização ou o hardware demorou demais e que o usuário deve tentar de novo.
Como comunicar os erros ao usuário é totalmente uma escolha sua.
Fallback para a Geolocation API do JavaScript
Pronto para o segundo round? Agora é a mesma coisa, mas com a envelhecida Geolocation API, que tem suporte em todos os navegadores e é meio dor de cabeça.
O ponto de partida é este:
/* Se <geolocation> tiver suporte */if ("HTMLGeolocationElement" in window) { /* O que vimos na seção anterior */} else { /* O que interessa agora (o fallback) */}Dentro daquele bloco else, que roda quando o <geolocation> não tem suporte, o começo é selecionar o botão de fallback (const fallbackButton = document.querySelector("#fallbackButton")). Se você se lembra, ele está dentro do <geolocation>, justamente para ser ignorado quando o <geolocation> tiver suporte.
Depois disso, é criada uma função (updateState()) que gerencia o estado do componente e fornece instruções de recuperação.
Como argumentos, entram o state em string (granted, prompt ou denied, correspondendo ao permissionStatus) e, opcionalmente, statusMessage, usada para comunicar mensagens de status ao usuário.
Não há como presumir nada sobre o seu componente, então a forma de comunicar o statusMessage e de expandir a updateState() fica a seu critério.
Se state === "denied", o botão é desabilitado com fallbackButton.disabled = true e é fornecida alguma instrução de recuperação, que deve ser passada como segundo argumento da função. O motivo de desabilitar o botão é que essa Geolocation API não é invocada pelo usuário.
Para proteger o usuário de requisições em massa, o navegador pode suprimir as requisições e mandar a API direto para a cadeia, sem passar pelo início, disparando PERMISSION_DENIED.
Além disso, se a API for tornada invocável pelo usuário (como aqui), o próprio usuário pode martelar o botão e basicamente se autobloquear. Desabilitar o botão resolve isso.
Se state === "granted" ou state === "prompt", o botão pode ser habilitado (fallbackButton.disabled = false).
O código JavaScript mostrado antes para o elemento <geolocation> funciona independentemente de o elemento ter ou não o atributo watch. Já com essa Geolocation API mais antiga, há duas coisas extras a resolver quando se quer acompanhar a localização do usuário continuamente.
A primeira é o ID do watcher, retornado pelo método watchPosition(). Conhecer esse ID permite limpar o watcher antes de registrar um novo, o que é obrigatório por questões de performance.
Então, por ora, let watcherID = null. Em seguida, é criada a função que tenta obter a localização (getLocation()).
A primeira coisa que ela faz, presumindo que watcherID !== null (ou seja, que ele já foi definido antes), é limpar o watcher com navigator.geolocation.clearWatch(watcherID) e voltar watcherID = null:
/* Uma função para obter a localização */const getLocation = () => { /* Se um watcher já tiver sido registrado */ if (watcherID !== null) { /* Cancela o registro dele */ navigator.geolocation.clearWatch(watcherID); watcherID = null; }}Depois, o botão é desabilitado com fallbackButton.disabled = true para, de novo, evitar cliques em série. Se você quiser embutir algum tipo de indicador de carregamento, fique à vontade, mas o <geolocation> não faz isso.
Em seguida vem a chamada de navigator.geolocation.watchPosition(), definindo watcherID com o ID retornado. Esse método tem três parâmetros: sucesso, erro e opções.
watcherID = navigator.geolocation.watchPosition( (position) => { /* Sucesso */ }, (error) => { /* Erro */ }, { /* Opções */ });Na função de callback de sucesso, os dados de localização são sintetizados como antes e depois é chamado updateState("granted").
Na função de callback de erro (opcional, mas altamente recomendada), o watcher é limpo e watcherID = null de novo, mas o resto segue a mesma lógica de tratamento de erros que o <geolocation> usa. A diferença é que aqui existem mais circunstâncias em que os erros podem acontecer.
Por exemplo: como watchPosition() e getCurrentPosition() não são necessariamente invocados pelo usuário, há mais cenários em que ele consegue negar o acesso, disparando o erro PERMISSION_DENIED.
Nesse caso, o certo é chamar updateState("denied", "Permission denied (try this or that)"), com uma mensagem de status útil e instruções claras de recuperação.
De forma parecida, o POSITION_UNAVAILABLE também pode ser disparado por um bloqueio em nível de sistema operacional, já que não todos os navegadores capturam isso no diálogo de permissão. Aqui o que se busca é updateState("prompt", "Position unavailable (try this or that)").
Por último, outro cenário que não acontece com o <geolocation>, mas acontece com essa Geolocation API: se o navegador pedir ao usuário para conceder a permissão em nível de sistema operacional e ele cancelar, isso pode disparar o erro TIMEOUT.
De todo modo, chame updateState("prompt", "Request timed out (try this or that)"), mais uma vez ajustando ao seu gosto.
Como se vê, o reporte de erros não é dos melhores. Às vezes o erro não é identificado corretamente e, mesmo quando é, ele pode ocorrer por vários motivos, o que dificulta comunicar uma mensagem de status útil e instruções claras de recuperação.
O <geolocation> lida melhor com isso: os erros são identificados corretamente e a natureza do elemento garante que certos erros nunca aconteçam. Mas por que categorias de erro? Por que não dizer exatamente o que deu errado?
O motivo é dificultar o fingerprinting. E, ainda que dê para fazer o trabalho extra de identificar o problema exato, simplesmente dizer o que aconteceu e o que o usuário deve fazer em seguida já é perfeitamente suficiente.
Claro, o error.message diz mais que o error.code, mas as mensagens podem ser um pouco vagas e diferem em cada navegador, então não é possível lê-las nem exibi-las de forma confiável.
De todo modo, o terceiro parâmetro (opcional) espera um objeto onde é possível definir:
enableHighAccuracy:trueoufalsetimeout:20000(20 segundos) é razoável seenableHighAccuracy: true, e3000a5000nos outros casosmaximumAge:0para navegação curva a curva,5000a10000(5 a 10 segundos) para rastreamento ao vivo,300000a600000(5 a 10 minutos) para atualizações frequentes (previsão do tempo, por exemplo)
Seria melhor que o navegador escolhesse o timeout e o maximumAge, especialmente considerando o impacto que eles têm no erro TIMEOUT e na bateria do dispositivo. E é exatamente isso que o <geolocation> faz.
/* Tenta obter a localização e armazena o ID retornado */watcherID = navigator.geolocation.watchPosition( /* Se forem passados dados de localização (como antes) */ (position) => { /* Sintetiza os dados (de novo, como antes) */ const { latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed } = position.coords;
const timestamp = position.timestamp;
/* E atualiza o estado */ updateState("granted"); },
/* Se for passada informação de erro (sim, como antes) */ (error) => { console.error(`Geolocation error: ${error.message}`);
/* De novo, cancela o registro do watcher (se necessário) */ if (watcherID !== null) { navigator.geolocation.clearWatch(watcherID); watcherID = null; }
if (error.code === error.PERMISSION_DENIED) { /* Sem HTTPS, má configuração no servidor ou o usuário negou o acesso */ updateState("denied", "Permission denied (try this or that)"); } else if (error.code === error.POSITION_UNAVAILABLE) { /* Sem fonte de localização (satélite de GPS ou rede Wi-Fi/torre de celular próxima) ou sem acesso em nível de sistema operacional */ updateState("prompt", "Position unavailable (try this or that)"); } else if (error.code === error.TIMEOUT) { /* A detecção da fonte de localização ou o hardware demorou demais, ou o usuário cancelou a requisição */ updateState("prompt", "Request timed out (try this or that)"); } }, { enableHighAccuracy: true, timeout: 20000 /* 20 segundos porque enableHighAccuracy: true */, maximumAge: 0 /* 0 segundo porque estamos exigindo alta precisão */ });Depois disso é preciso consultar o status da permissão usando a Permissions API (navigator.permissions.query({ name: "geolocation" }).then((permissionStatus) => { /* ... */ })) e executar toda a lógica citada com base nele.
Se permissionStatus.state === "granted", é chamado getLocation(), que é essencialmente o que o atributo autolocate faz. Se você não quiser a localização automática, basta apagar essa parte.
Se for qualquer outro valor, o estado inicial é determinado chamando updateState(permissionStatus.state).
/* Consulta o status da permissão */navigator.permissions.query({ name: "geolocation" }).then((permissionStatus) => { /* Equivalente ao atributo autolocate */ if (permissionStatus.state === "granted") { getLocation(); } else { /* Reage de acordo */ updateState(permissionStatus.state); }});Por fim, faça o botão responder a cliques:
/* Se o usuário clicar no botão de fallback */fallbackButton.addEventListener("click", () => { getLocation();});Se a intenção for obter a localização do usuário apenas uma vez (em vez de acompanhá-la continuamente), estas são as modificações necessárias:
- Remover tudo relacionado ao
watcherID - Trocar
watchPosition()porgetCurrentPosition() - Fornecer outras configurações para o parâmetro de opções
Note que o evento change, que poderia ajudar a atualizar o estado sempre que o usuário mudasse o status da permissão em nível de navegador, não dispara no Safari. E, de todo modo, o comportamento padrão dos navegadores é pedir ao usuário que recarregue a página, então vale ficar com isso.
Mais uma coisa. Seria interessante que o botão dissesse “Atualizar localização precisa” e “Obtendo localização precisa…” em certos momentos, mas o <geolocation> não faz isso, então o fallback aqui também não faz. Isso fica a seu critério.
Estilizando o elemento <geolocation>
O <geolocation> tem uma pseudoclasse :granted. Jogando um :not(:granted) na jogada, dá para mirar também nos estados de prompt e negado:
geolocation { &:granted { /* Permissão concedida */ }
&:not(:granted) { /* Permissão não concedida */ }}Com a Geolocation API, dá para alternar classes conforme necessário para obter o mesmo efeito. Mas, honestamente, nunca houve grande necessidade de estilizar um botão desses dessa forma.
O desejável seria conseguir mudar o ícone e o texto (talvez isso caiba na função updateState()), e é justamente uma das coisas que o elemento <geolocation> proíbe categoricamente.
Antes de entrar nisso, vale um resumo das regras do <geolocation> declaradas no explainer do Google e no da Mozilla:
- Precisa haver contraste de cor suficiente
- O canal alpha precisa resolver em
1(ou seja, sem transparência) - As larguras, alturas e tamanhos de fonte mínimos e máximos precisam ser respeitados
- Margens negativas e offsets de outline também não são permitidos
- Efeitos de distorção são proibidos, incluindo gradientes lineares
Algumas dessas regras funcionam como guardrails (por exemplo, é fisicamente impossível estilizar a altura acima de 50px), enquanto outras fazem o invalidReason retornar style_invalid e desabilitam o botão do <geolocation>.
Algumas das regras parecem ter mudado (ou estão com bug no momento). O <geolocation> também não respeita prefers-color-scheme, mas é um recurso em desenvolvimento, então não vale comentar muito sobre isso. O que dá para registrar é o que funciona e o que não funciona:
- Desabilitar o botão por causa de um estilo é ruim (guardrails são melhores, ainda que nenhuma das duas soluções agrade)
- O ícone e o texto forçados são ruins, assim como não poder usar gradientes ou
corner-shape - Com o resto dá para conviver, porque normalmente o design já ficaria dentro daqueles guardrails
- O texto ser localizado em vários idiomas é muito bacana, mas é uma primeira vez em padrões web e incomoda que exista só para esses botões de permissão
Impedir que autores enganem usuários para arrancar a localização deles é claramente o caminho certo. Mas seria melhor se o <geolocation> fosse totalmente estilizável e o diálogo de permissão fosse ultra claro sobre aquilo que o usuário está aceitando.
Mesmo sendo possível estilizar background, cor, borda, border radius e box shadow do jeito desejado (como na imagem abaixo, que é quase tudo que se pediria), isso soa como um passo atrás, na direção dos controles não estilizáveis.
Ainda assim, a abordagem tem ótimas intenções, então é animador ver para onde ela vai (e, francamente, poder jogar a envelhecida Geolocation API no lixo).
E agora? O que mais?
Quando todos os navegadores tiverem suporte ao <geolocation>, a experiência do usuário e a de quem desenvolve vão melhorar drasticamente.
E tem mais!
O que antes era o elemento <permission> hoje é o elemento <geolocation>, o elemento <install> (que facilita a instalação de Progressive Web Apps), o elemento <usermedia> (que facilita o acesso à câmera e/ou ao microfone do usuário) e os elementos <camera> e <microphone> (que facilitam o acesso a cada um deles individualmente).
Todos eles estão em fase de testes no Chrome, como parte de uma iniciativa maior para melhorar o processo e a experiência de pedir permissões, o que é excelente.
Se o assunto é elemento nativo com estilização controlada, vale ver também como usar e estilizar Dialog, que passa pelo mesmo tipo de negociação entre padrão do navegador e liberdade de design.