
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:
 
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 .
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 , false ); 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 usandobase::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 ( pthreadno POSIX ouCreateThread()no Windows). Implementado na classebase::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 debase/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".