Chromium no es solo un navegador, sino también un buen marco



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:

 #include "base/command_line.h" 

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 /* http2 */, false /* quic */); 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".

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


All Articles