O Chromium não é apenas um navegador, mas também uma boa estrutura



A maioria das pessoas está acostumada ao fato de o Chromium ser um navegador e a base de outros navegadores. Até recentemente, eu também pensava assim, mas, estudando esse tópico por alguns meses, comecei a descobrir outro mundo maravilhoso. O Chromium é um enorme ecossistema no qual existe tudo: um sistema de dependência, um sistema de construção de plataforma cruzada e componentes para quase todas as ocasiões. Então, por que não tentar criar seus próprios aplicativos usando todo esse poder?

Sob kat, um pequeno guia sobre como começar a fazer isso.

Preparação do ambiente


No artigo que usarei o Ubuntu 18.04, o procedimento para outros sistemas operacionais pode ser encontrado na documentação:


As etapas a seguir requerem Git e Python. Se eles não estiverem instalados, eles deverão ser instalados usando o comando:

sudo apt install git python 

Definindo depot_tools


depot_tools é um kit de ferramentas de desenvolvimento do Chromium. Para instalá-lo, você deve executar:

 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 

E adicione o caminho à variável de ambiente PATH:

 export PATH="$PATH:/path/to/depot_tools" 

Importante: se o download de depot_tools para sua pasta pessoal, não use ~ na variável PATH , caso contrário, poderão ocorrer problemas. Você deve usar a variável $HOME :

 export PATH="$PATH:${HOME}/depot_tools" 

Recuperação de código


Primeiro você precisa criar uma pasta para a fonte. Por exemplo, no diretório inicial (são necessários aproximadamente 30 GB de espaço livre):

 mkdir ~/chromium && cd ~/chromium 

Depois disso, você pode fazer o download das fontes usando o utilitário de fetch em depot_tools :

 fetch --nohooks --no-history chromium 

Agora você pode tomar chá / café, pois o procedimento não é rápido. Para experimentos, nenhum histórico é necessário; portanto, o sinalizador --no-history é usado. A história será ainda mais longa.

Instalação de Dependências


Todas as fontes estão na pasta src , acesse:

 cd src 

Agora você precisa colocar todas as dependências usando o script:

 ./build/install-build-deps.sh 

E execute os ganchos:

 gclient runhooks 

Isso completa a preparação do ambiente.

Sistema de compilação


O Ninja é usado como o sistema principal de montagem do Chromium, e o utilitário GN é usado para gerar arquivos .ninja .

Para entender como usar essas ferramentas, proponho criar um exemplo de utilitário de teste. Para fazer isso, crie uma subpasta de example na pasta src :

 mkdir example 

Em seguida, na pasta src/example , crie o arquivo BUILD.gn , que contém:

 executable("example") { sources = [ "example.cc", ] } 

BUILD.gn consiste em um destino ( example executável) e uma lista de arquivos necessários para construir o destino.

A próxima etapa é criar o próprio arquivo example.cc . Para começar, proponho criar um aplicativo clássico "Hello world":

 #include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; } 

O código-fonte pode ser encontrado no GitHub .

Para que o GN aprenda sobre o novo projeto, no arquivo BUILD.gn localizado em src , adicione a linha "//example" na seção deps :

 ... group("gn_all") { testonly = true deps = [ ":gn_visibility", "//base:base_perftests", "//base:base_unittests", "//base/util:base_util_unittests", "//chrome/installer", "//chrome/updater", "//net:net_unittests", "//services:services_unittests", "//services/service_manager/public/cpp", "//skia:skia_unittests", "//sql:sql_unittests", "//third_party/flatbuffers:flatbuffers_unittests", "//tools/binary_size:binary_size_trybot_py", "//tools/ipc_fuzzer:ipc_fuzzer_all", "//tools/metrics:metrics_metadata", "//ui/base:ui_base_unittests", "//ui/gfx:gfx_unittests", "//url:url_unittests", # ↓↓↓↓↓↓↓↓ "//example", ] ... 

Agora você precisa retornar à pasta src e gerar o projeto usando o comando:

 gn gen out/Default 

O GN também permite que você prepare um projeto para um dos IDEs suportados:

  • eclipse
  • vs
  • vs2013
  • vs2015
  • vs2017
  • vs2019
  • xcode
  • qtcreator
  • json

Mais informações podem ser obtidas usando o comando:

 gn help gen 

Por exemplo, para trabalhar com o projeto de example no QtCreator, você precisa executar o comando:

 gn gen --ide=qtcreator --root-target=example out/Default 

Depois disso, você pode abrir o projeto no QtCreator:

 qtcreator out/Default/qtcreator_project/all.creator 

A etapa final é criar o projeto usando o Ninja:

 autoninja -C out/Default example 

Esta breve introdução ao sistema de montagem pode ser concluída.

O aplicativo pode ser iniciado usando o comando:

 ./out/Default/example 

E veja Olá, mundo. De fato, você pode escrever um artigo separado sobre o sistema de montagem no Chromium. Talvez não um.

Trabalhar com a linha de comando


Como primeiro exemplo de uso da base de código do Chromium como estrutura, sugiro que brinque com a linha de comando.

Tarefa: exiba todos os argumentos passados ​​para o aplicativo no estilo Chromium.
Para trabalhar com a linha de comando, você precisa incluir o arquivo de cabeçalho em example.cc:

 #include "base/command_line.h" 

E também não devemos esquecer de adicionar uma dependência no projeto base no BUILD.gn . BUILD.gn deve ficar assim:

 executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] } 

Agora tudo o que você precisa estará conectado ao example .

Para trabalhar com a linha de comando, o Chromium fornece uma base::CommandLine singleton base::CommandLine . Para obter um link para ele, você precisa usar o método estático base::CommandLine::ForCurrentProcess , mas primeiro você precisa inicializá-lo usando o método base::CommandLine::Init :

 base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess(); 

Todos os argumentos passados ​​para o aplicativo na linha de comando e começando com a - retornados como base::SwitchMap (essencialmente map<string, string> ) usando o método GetSwitches . Todos os outros argumentos são retornados como base::StringVector (essencialmente, vectr<strig> ). Este conhecimento é suficiente para implementar o código da tarefa:

 for (const auto &sw : cmd_line->GetSwitches()) { std::cout << "Switch " << sw.first << ": " << sw.second << std::endl; } for (const auto &arg: cmd_line->GetArgs()) { std::cout << "Arg " << arg << std::endl; } 

A versão completa pode ser encontrada no GitHub .

Para criar e executar o aplicativo, você precisa executar:

 autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2 

A tela exibirá:

 Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2 

Trabalho em rede


Como segundo e último exemplo de hoje, proponho trabalhar com a parte da rede do Chromium.

Tarefa: exibe o conteúdo da URL passada como argumento .

Subsistema de rede Chromium


O subsistema de rede é bastante grande e complexo. O ponto de entrada para solicitações de HTTP, HTTPS, FTP e outros recursos de dados é URLRequest , que já determina qual cliente usar. Um diagrama simplificado é assim:



A versão completa pode ser encontrada na documentação .

Para criar um URLRequest você deve usar um URLRequestContext . Criar um contexto é uma operação bastante complicada, portanto, é recomendável usar URLRequestContextBuilder . Inicializará todas as variáveis ​​necessárias com valores padrão, mas, se desejado, elas poderão ser alteradas por conta própria, por exemplo:

 net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true /* http2 */, false /* quic */); context_builder.SetCookieStore(nullptr); 

Multithreading


A pilha de rede Chromium foi projetada para funcionar em um ambiente multithread, portanto você não pode pular este tópico. Os objetos básicos para trabalhar com multithreading no Chromium são:

  • Tarefa - uma tarefa a ser executada, no Chromium é uma função do tipo base::Callback , que pode ser criada usando base::Bind .
  • Fila de tarefas - a fila de tarefas a ser executada.
  • Encadeamento físico - um wrapper de plataforma cruzada sobre o encadeamento do sistema operacional ( pthread no POSIX ou CreateThread() no Windows). Implementado na classe base::PlatformThread , não use diretamente.
  • base :: Thread - um thread real que processa mensagens de uma fila de tarefas dedicada sem parar; Não é recomendável criá-los diretamente.
  • Pool de threads - pool de threads com uma fila de tarefas comum. Implementado na classe base::ThreadPool . Como regra, crie uma instância. As tarefas são enviadas usando funções de base/task/post_task.h .
  • Sequência ou thread virtual - um segmento virtual que usa threads reais e pode alternar entre eles.
  • Task runner - uma interface para definir tarefas, implementada na base::TaskRunner .
  • Executor de tarefas sequenciado - uma interface para definir tarefas, o que garante que as tarefas sejam executadas na mesma ordem em que chegaram. Implementado na classe base::SequencedTaskRunner .
  • Executor de tarefas de thread único - semelhante ao anterior, mas garante que todas as tarefas serão executadas em um thread do SO. Implementado na base::SingleThreadTaskRunner .

Implementação


Alguns componentes do Chromium exigem a presença de base::AtExitManager - essa é uma classe que permite registrar operações que devem ser executadas quando o aplicativo termina. Usá-lo é muito simples, você precisa criar um objeto na pilha:

 base::AtExitManager exit_manager; 

Quando exit_manager sai do escopo, todos os retornos de chamada registrados serão executados.

Agora você precisa cuidar da disponibilidade de todos os componentes multithreading necessários para o subsistema de rede. Para fazer isso, crie um Thread pool , um Message loop com o tipo TYPE_IO para processar mensagens de rede e um Run loop - o loop principal do programa:

 base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop; 

Em seguida, use o Context builder para criar um Context :

 auto ctx = net::URLRequestContextBuilder().Build(); 

Para enviar uma solicitação, você deve criar um objeto URLRequest usando o método CreateRequest do objeto ctx . Os seguintes parâmetros são passados:

  • URL, sequência com o tipo GURL;
  • prioridade;
  • delegado que lida com eventos.

Um delegado é uma classe que implementa a interface net::URLRequest::Delegate . Para esta tarefa, pode ser assim:

 class MyDelegate : public net::URLRequest::Delegate { public: explicit MyDelegate(base::Closure quit_closure) : quit_closure_(std::move(quit_closure)), buf_(base::MakeRefCounted<net::IOBuffer>(BUF_SZ)) {} void OnReceivedRedirect(net::URLRequest *request, const net::RedirectInfo &redirect_info, bool *defer_redirect) override { std::cerr << "redirect to " << redirect_info.new_url << std::endl; } void OnAuthRequired(net::URLRequest* request, const net::AuthChallengeInfo& auth_info) override { std::cerr << "auth req" << std::endl; } void OnCertificateRequested(net::URLRequest *request, net::SSLCertRequestInfo *cert_request_info) override { std::cerr << "cert req" << std::endl; } void OnSSLCertificateError(net::URLRequest* request, int net_error, const net::SSLInfo& ssl_info, bool fatal) override { std::cerr << "cert err" << std::endl; } void OnResponseStarted(net::URLRequest *request, int net_error) override { std::cerr << "resp started" << std::endl; while (true) { auto n = request->Read(buf_.get(), BUF_SZ); std::cerr << "resp read " << n << std::endl; if (n == net::ERR_IO_PENDING) return; if (n <= 0) { OnReadCompleted(request, n); return; } std::cout << std::string(buf_->data(), n) << std::endl; } } void OnReadCompleted(net::URLRequest *request, int bytes_read) override { std::cerr << "completed" << std::endl; quit_closure_.Run(); } private: base::Closure quit_closure_; scoped_refptr<net::IOBuffer> buf_; }; 

Toda a lógica principal está no OnResponseStarted eventos OnResponseStarted : o conteúdo da resposta é subtraído até que ocorra um erro ou não haja nada para ler. Como após ler a resposta, é necessário concluir o aplicativo, o delegado deve ter acesso à função que interromperá o Run loop principal de Run loop , nesse caso, um retorno de chamada do tipo base::Closure é usado.

Agora tudo está pronto para enviar a solicitação:

 MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start(); 

Para que a solicitação inicie o processamento, você precisa executar o Run loop :

 run_loop.Run(); 

A versão completa pode ser encontrada no GitHub .

Para criar e executar o aplicativo, você precisa executar:

 autoninja -C out/Default example out/Default/example "https://example.com/" 

Final


De fato, no Chromium, você pode encontrar muitos cubos e tijolos úteis a partir dos quais é possível criar aplicativos. Ele está em constante evolução, o que, por um lado, é uma vantagem e, por outro lado, alterações regulares na API não permitem que você relaxe. Por exemplo, na versão mais recente, base::TaskScheduler transformou em base::ThreadPool , felizmente, sem alterar a API.

PS Estamos à procura de um programador C ++ líder em nossa equipe! Se você sentir a força em si mesmo, nossos desejos são descritos aqui: team.mail.ru/vacancy/4641/ . Há também um botão "Responder".

Source: https://habr.com/ru/post/pt455956/


All Articles