
La mayoría de las personas están acostumbradas al hecho de que Chromium es tanto un navegador como la base de otros navegadores. Hasta hace poco, también lo pensaba, pero al estudiar este tema durante un par de meses, comencé a descubrir otro mundo maravilloso. Chromium es un gran ecosistema en el que hay de todo: un sistema de dependencia, un sistema de construcción multiplataforma y componentes para casi todas las ocasiones. Entonces, ¿por qué no tratar de crear sus propias aplicaciones utilizando todo este poder?
Bajo kat, una pequeña guía sobre cómo comenzar a hacer esto.
Preparación del medio ambiente
En el artículo utilizaré Ubuntu 18.04, el procedimiento para otros sistemas operativos se puede encontrar en la documentación:
Los siguientes pasos requieren Git y Python. Si no están instalados, entonces deben instalarse usando el comando:
sudo apt install git python
Establecer depot_tools
depot_tools
es un kit de herramientas de desarrollo de Chromium. Para instalarlo, debe realizar:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
Y agregue la ruta a la variable de entorno PATH:
export PATH="$PATH:/path/to/depot_tools"
Importante: si se descargaron
depot_tools
en su carpeta de inicio, no use
~
en la variable
PATH
, de lo contrario pueden surgir problemas. Debe usar la variable
$HOME
:
export PATH="$PATH:${HOME}/depot_tools"
Obteniendo código
Primero necesitas crear una carpeta para la fuente. Por ejemplo, en el directorio de inicio (se necesitan unos 30 GB de espacio libre):
mkdir ~/chromium && cd ~/chromium
Después de eso, puede descargar las fuentes usando la utilidad
fetch
de
depot_tools
:
fetch --nohooks --no-history chromium
Ahora puede tomar té / café, ya que el procedimiento no es rápido. Para los experimentos, no se necesita historial, por lo que se usa el indicador
--no-history
. La historia será aún más larga.
Instalación de dependencia
Todas las fuentes están en la carpeta
src
, ve a ella:
cd src
Ahora necesita poner todas las dependencias usando el script:
./build/install-build-deps.sh
Y corre los ganchos:
gclient runhooks
Esto completa la preparación del medio ambiente.
Sistema de construcción
Ninja se usa como el sistema de ensamblaje principal para Chromium, y la utilidad
GN se usa para generar archivos
.ninja
.
Para entender cómo usar estas herramientas, propongo crear un ejemplo de utilidad de prueba. Para hacer esto, cree una subcarpeta de
example
en la carpeta
src
:
mkdir example
Luego, en la carpeta
src/example
, cree el archivo
BUILD.gn
, que contiene:
executable("example") { sources = [ "example.cc", ] }
BUILD.gn
consta de un objetivo (
example
ejecutable) y una lista de archivos que se necesitan para construir el objetivo.
El siguiente paso es crear el archivo
example.cc
sí. Para comenzar, propongo hacer una aplicación clásica "Hola mundo":
#include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; }
El código fuente se puede encontrar en
GitHub .
Para que GN aprenda sobre el nuevo proyecto, en el archivo
BUILD.gn
ubicado en
src
, agregue la línea
"//example"
en la sección
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", ] ...
Ahora necesita regresar a la carpeta
src
y generar el proyecto usando el comando:
gn gen out/Default
GN también le permite preparar un proyecto para uno de los IDE admitidos:
- eclipse
- vs
- vs2013
- vs2015
- vs2017
- vs2019
- xcode
- qtcreator
- json
Se puede obtener más información con el comando:
gn help gen
Por ejemplo, para trabajar con el proyecto de
example
en
QtCreator, debe ejecutar el comando:
gn gen --ide=qtcreator --root-target=example out/Default
Después de eso, puede abrir el proyecto en QtCreator:
qtcreator out/Default/qtcreator_project/all.creator
El paso final es construir el proyecto usando Ninja:
autoninja -C out/Default example
Esta breve introducción al sistema de ensamblaje se puede completar.
La aplicación se puede iniciar con el comando:
./out/Default/example
Y mira Hola mundo. De hecho, puede escribir un artículo separado sobre el sistema de ensamblaje en Chromium. Quizás no uno.
Trabaja con la línea de comando
Como primer ejemplo de uso de la base de código de Chromium como marco, sugiero jugar con la línea de comando.
Tarea:
muestra todos los argumentos pasados a la aplicación en el estilo Chromium.Para trabajar con la línea de comando, debe incluir el archivo de encabezado en example.cc:
Y tampoco debemos olvidar agregar una dependencia en el proyecto
base
en
BUILD.gn
.
BUILD.gn
debería verse así:
executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] }
Ahora todo lo que necesita estará conectado a un
example
.
Para trabajar con la línea de comando, Chromium proporciona una
base::CommandLine
singleton
base::CommandLine
. Para obtener un enlace, debe usar el método estático
base::CommandLine::ForCurrentProcess
, pero primero debe inicializarlo usando el método
base::CommandLine::Init
:
base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess();
Todos los argumentos pasados a la aplicación en la línea de comando y comenzando con a
-
devuelven como
base::SwitchMap
(esencialmente
map<string, string>
) usando el método
GetSwitches
. Todos los demás argumentos se devuelven como
base::StringVector
(esencialmente
vectr<strig>
). Este conocimiento es suficiente para implementar el código para la tarea:
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; }
La versión completa se puede encontrar en
GitHub .
Para compilar y ejecutar la aplicación, debe ejecutar:
autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2
La pantalla mostrará:
Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2
Redes
Como segundo y último ejemplo para hoy, propongo trabajar con la parte de la red de Chromium.
Tarea:
muestra el contenido de la URL pasada como argumento .
Subsistema de red Chromium
El subsistema de red es bastante grande y complejo. El punto de entrada para las solicitudes a HTTP, HTTPS, FTP y otros recursos de datos es
URLRequest
, que ya determina qué cliente usar. Un diagrama simplificado se ve así:
La versión completa se puede encontrar en la documentación .Para crear una
URLRequest
debe usar un
URLRequestContext
. Crear un contexto es una operación bastante complicada, por lo tanto, se recomienda utilizar
URLRequestContextBuilder
. Inicializará todas las variables necesarias con valores predeterminados, pero, si lo desea, se pueden cambiar a los suyos, por ejemplo:
net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true , false ); context_builder.SetCookieStore(nullptr);
Multithreading
La pila de red Chromium está diseñada para funcionar en un entorno multiproceso, por lo que no puede omitir este tema. Los objetos básicos para trabajar con subprocesos múltiples en Chromium son:
- Tarea: una tarea a ejecutar, en Chromium es una función de tipo
base::Callback
, que se puede crear usando base::Bind
.
- Cola de tareas: la cola de tareas a ejecutar.
pthread
físico: un contenedor multiplataforma sobre el subproceso del sistema operativo ( pthread
en POSIX o CreateThread()
en Windows). Implementado en la clase base::PlatformThread
, no lo use directamente.
- base :: Thread: un hilo real que procesa mensajes de una cola de tareas dedicada sin fin; No se recomienda crearlos directamente.
- Grupo de subprocesos: grupo de subprocesos con una cola de tareas común. Implementado en la clase
base::ThreadPool
. Como regla general, cree una instancia. Se le envían tareas utilizando funciones de base/task/post_task.h
.
- Secuencia o hilo virtual: un hilo virtual que usa hilos reales y puede cambiar entre ellos.
- Task runner: una interfaz para configurar tareas, implementada en la
base::TaskRunner
.
- Secuenciador de tareas: una interfaz para configurar tareas, que garantiza que las tareas se ejecutarán en el mismo orden en que llegaron. Implementado en la clase
base::SequencedTaskRunner
.
- Ejecutor de tareas de subproceso único: similar al anterior, pero garantiza que todas las tareas se realizarán en un subproceso del sistema operativo. Implementado en la
base::SingleThreadTaskRunner
.
Implementación
Algunos componentes de Chromium requieren la presencia de
base::AtExitManager
: esta es una clase que le permite registrar operaciones que deben realizarse cuando finaliza la aplicación. Usarlo es muy simple, necesitas crear un objeto en la pila:
base::AtExitManager exit_manager;
Cuando
exit_manager
queda fuera de alcance, se ejecutarán todas las devoluciones de llamada registradas.
Ahora debe cuidar la disponibilidad de todos los componentes de subprocesos múltiples necesarios para el subsistema de red. Para hacer esto, cree un
Thread pool
, un
Message loop
con el tipo
TYPE_IO
para procesar mensajes de red y un
Run loop
principal del programa:
base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop;
A continuación, use el
Context builder
para crear un
Context
:
auto ctx = net::URLRequestContextBuilder().Build();
Para enviar una solicitud, debe crear un objeto
URLRequest
utilizando el método
CreateRequest
del objeto
ctx
. Se pasan los siguientes parámetros:
- URL, cadena con tipo GURL;
- prioridad
- delegado que maneja eventos.
Un delegado es una clase que implementa la interfaz
net::URLRequest::Delegate
. Para esta tarea, puede verse así:
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 la lógica principal está en el
OnResponseStarted
eventos
OnResponseStarted
: el contenido de la respuesta se resta hasta que se produce un error o no hay nada que leer. Dado que después de leer la respuesta, debe completar la aplicación, el delegado debe tener acceso a la función que interrumpirá el
Run loop
principal, en este caso se utiliza una devolución de llamada del tipo
base::Closure
.
Ahora todo está listo para enviar la solicitud:
MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start();
Para que la solicitud comience a procesarse, ejecute el
Run loop
:
run_loop.Run();
La versión completa se puede encontrar en
GitHub .
Para compilar y ejecutar la aplicación, debe ejecutar:
autoninja -C out/Default example out/Default/example "https://example.com/"
Final
De hecho, en Chromium puedes encontrar muchos cubos y ladrillos útiles desde los que puedes construir aplicaciones. Está en constante evolución, lo que, por un lado, es una ventaja, y por otro lado, los cambios regulares en la API no te permiten relajarte. Por ejemplo, en la última versión,
base::TaskScheduler
convirtió en
base::ThreadPool
, afortunadamente, sin cambiar la API.
PD ¡Estamos buscando un programador líder en C ++ en nuestro equipo! Si siente la fuerza en sí mismo, nuestros deseos se describen aquí:
team.mail.ru/vacancy/4641/ . También hay un botón "Responder".