
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 .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 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".