Programação Por Equipe DevBras

Capa do artigo: WebView2 e CefSharp no app desktop: limite a navegação e planeje a atualização

WebView2 e CefSharp no app desktop: limite a navegação e planeje a atualização

Embutir um navegador no programa traz o Chromium inteiro. Restrinja os endereços, isole a ponte com o código nativo, sirva arquivos locais e saiba quem atualiza o motor.

O Estoque Fácil era um programa Windows de 40 MB que abria em dois segundos. Para ganhar um painel com gráficos, a equipe embutiu um navegador e carregou a página do painel. Ficou bonito. Seis meses depois, o instalador tinha passado de 200 MB, o Gerenciador de Tarefas mostrava seis processos do programa, e um cliente descobriu que, colando um endereço qualquer na caixa de busca do painel, abria o site que quisesse dentro do sistema — com acesso à ponte que gravava arquivos no disco.

A resposta curta: um controle de navegador embutido, como WebView2 ou CefSharp, não é um componente leve, é o Chromium inteiro. Trate a página como conteúdo não confiável: liste os endereços permitidos, bloqueie o resto, exponha ao JavaScript só as mensagens que você precisa e sirva suas telas a partir de arquivos locais. E decida desde o começo quem mantém o motor atualizado.

O que você herda ao embutir um navegador

O navegador embutido traz o motor de renderização, o motor de JavaScript, a pilha de rede e o modelo de vários processos do Chromium: um processo principal, processos de renderização, GPU e utilitários. É por isso que o Estoque Fácil passou a ter seis processos e algumas centenas de megabytes a mais de memória com o painel aberto.

Herda também a superfície de ataque. Navegadores recebem correções de segurança com frequência, e um navegador embutido desatualizado é um navegador vulnerável rodando com as permissões do seu programa. Se a página carregada vem da internet ou de um endereço que o usuário digita, qualquer falha naquele site ou no motor alcança o seu processo.

E herda a ponte. O valor de embutir um navegador costuma ser deixar a página conversar com o programa: abrir um arquivo, imprimir, ler o banco local. Essa ponte é exatamente o que transforma "abrir um site qualquer" de incômodo em incidente.

WebView2 ou CefSharp: quem atualiza o motor

O WebView2 da Microsoft usa o motor do Edge. No modo Evergreen, o runtime é compartilhado pelo sistema e atualizado automaticamente pela Microsoft; no Windows 11 ele já vem instalado. Seu instalador fica pequeno e as correções chegam sem você publicar versão nova. No modo Fixed Version, você distribui uma versão específica junto com o programa, e atualizar passa a ser trabalho seu.

O CefSharp, baseado no Chromium Embedded Framework, embute o Chromium no pacote do seu programa. O número de versão do pacote acompanha a versão do Chromium. Isso dá controle total sobre o motor, mas cada correção de segurança do Chromium exige que você atualize o pacote, teste e distribua uma nova versão para todos os clientes. Programa com CefSharp que não é atualizado há um ano carrega um ano de falhas conhecidas.

Nenhuma das duas escolhas é errada. A errada é escolher sem saber de quem é a obrigação. O mesmo raciocínio vale para Electron, discutido em separar a janela Electron da interface.

Como embutir com o mínimo de risco

  1. Prefira servir suas telas de arquivos locais mapeados para um nome de host virtual, em vez de um servidor remoto.
  2. Liste os hosts permitidos e cancele qualquer navegação fora da lista.
  3. Intercepte novas janelas e abra links externos no navegador padrão do sistema.
  4. Use troca de mensagens em vez de expor objetos nativos inteiros ao JavaScript.
  5. Valide cada mensagem recebida: origem, ação permitida e parâmetros.
  6. Desligue DevTools e menus padrão na versão distribuída.
  7. Defina a política de atualização do motor e registre a versão em uso.
  8. Meça memória e processos com a tela aberta.

Exemplo com WebView2 no WinForms

using System.Diagnostics;
using Microsoft.Web.WebView2.Core;

public partial class FormPainel : Form
{
    private static readonly HashSet<string> HostsPermitidos = new() { "app.example" };

    private static bool Permitido(string endereco) =>
        Uri.TryCreate(endereco, UriKind.Absolute, out var u)
        && u.Scheme == Uri.UriSchemeHttps
        && HostsPermitidos.Contains(u.Host);

    private async void FormPainel_Load(object sender, EventArgs e)
    {
        await webView.EnsureCoreWebView2Async();
        var core = webView.CoreWebView2;

        core.SetVirtualHostNameToFolderMapping(
            "app.example",
            Path.Combine(AppContext.BaseDirectory, "painel"),
            CoreWebView2HostResourceAccessKind.Deny);

        core.Settings.AreDevToolsEnabled = false;
        core.Settings.AreDefaultContextMenusEnabled = false;
        core.Settings.AreHostObjectsAllowed = false;

        core.NavigationStarting += (s, a) =>
        {
            if (!Permitido(a.Uri)) a.Cancel = true;
        };

        core.NewWindowRequested += (s, a) =>
        {
            a.Handled = true;
            if (Uri.TryCreate(a.Uri, UriKind.Absolute, out var u) && u.Scheme == Uri.UriSchemeHttps)
                Process.Start(new ProcessStartInfo(u.AbsoluteUri) { UseShellExecute = true });
        };

        core.WebMessageReceived += AoReceberMensagem;
        webView.Source = new Uri("https://app.example/index.html");
    }

    private void AoReceberMensagem(object? sender, CoreWebView2WebMessageReceivedEventArgs a)
    {
        if (!Permitido(a.Source)) return;
        var acao = a.TryGetWebMessageAsString();
        switch (acao)
        {
            case "exportar-estoque":
                ExportarEstoqueParaPastaDoUsuario();
                break;
            default:
                return;
        }
    }
}

O SetVirtualHostNameToFolderMapping faz https://app.example/ servir os arquivos da pasta painel do programa, sem servidor e sem rede. O domínio .example é reservado para exemplos e não colide com nenhum site real. O NavigationStarting cancela qualquer endereço fora da lista, inclusive o colado na busca. Links externos vão para o navegador padrão, que o usuário mantém atualizado e onde a sessão dele já está.

A ponte é uma lista fechada de ações. A página manda window.chrome.webview.postMessage('exportar-estoque') e o programa decide o caminho do arquivo; a página nunca envia um caminho de disco. Com AreHostObjectsAllowed desligado, nenhum objeto .NET fica acessível ao JavaScript. No CefSharp, os equivalentes são o tratamento de navegação no request handler e o registro de objetos no repositório de objetos JavaScript, que merece a mesma lista fechada.

Login não é tarefa do navegador embutido

Se o painel precisa de login com Google, Microsoft ou outro provedor, não faça esse login dentro do controle embutido. Provedores bloqueiam ou desencorajam isso, e o usuário não tem como saber se está digitando a senha na página real. O caminho é abrir o navegador do sistema, como mostram login OAuth dentro do app não é a aba do Chrome e login no app MAUI.

Erros comuns

  • Carregar qualquer endereço que o usuário digita. O programa vira um navegador com acesso à sua ponte nativa.
  • Expor um objeto nativo inteiro ao JavaScript. Qualquer script na página chama qualquer método, inclusive os que gravam no disco.
  • Aceitar caminho de arquivo vindo da página. Uma página comprometida lê ou sobrescreve o que quiser.
  • Distribuir CefSharp sem plano de atualização. Meses de falhas conhecidas do Chromium rodando nos clientes.
  • Deixar DevTools ativo na versão final. Qualquer usuário inspeciona e chama a ponte à mão.
  • Ignorar memória e processos. O programa leve passa a disputar recursos em máquinas modestas.

Checklist

  • As telas próprias são servidas de arquivos locais por host virtual.
  • Há uma lista de hosts permitidos e a navegação fora dela é cancelada.
  • Novas janelas e links externos abrem no navegador do sistema.
  • A ponte aceita só uma lista fechada de mensagens, conferindo a origem.
  • Nenhum objeto nativo é exposto inteiro ao JavaScript.
  • DevTools e menus padrão estão desligados na versão distribuída.
  • Está definido quem atualiza o motor e a versão em uso é registrada.
  • Memória e número de processos foram medidos com a tela aberta.

Abra o seu programa, vá até a tela com o navegador embutido e tente fazê-lo carregar um endereço externo qualquer: ele abre? Se abre, que métodos nativos aquela página consegue chamar?

Avalie este artigo

Referência: https://cefsharp.github.io/