
La plupart des gens sont habitués au fait que Chromium est à la fois un navigateur et la base d'autres navigateurs. Jusqu'à récemment, je le pensais aussi, mais en étudiant ce sujet pendant quelques mois, j'ai commencé à découvrir un autre monde merveilleux. Le chrome est un immense écosystÚme dans lequel il y a tout: un systÚme de dépendance, un systÚme de construction multiplateforme et des composants pour presque toutes les occasions. Alors pourquoi ne pas essayer de créer vos propres applications en utilisant toute cette puissance?
Sous kat, un petit guide sur la façon de commencer à le faire.
Préparation de l'environnement
Dans l'article que j'utiliserai Ubuntu 18.04, la procĂ©dure pour les autres OS peut ĂȘtre trouvĂ©e dans la documentation:
Les Ă©tapes suivantes nĂ©cessitent Git et Python. S'ils ne sont pas installĂ©s, ils doivent ĂȘtre installĂ©s Ă l'aide de la commande:
sudo apt install git python
Définition de depot_tools
depot_tools
est une boßte à outils de développement Chromium. Pour l'installer, vous devez effectuer:
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
Et ajoutez le chemin d'accĂšs Ă la variable d'environnement PATH:
export PATH="$PATH:/path/to/depot_tools"
Important: si
depot_tools
été téléchargé dans votre dossier personnel, n'utilisez pas
~
dans la variable
PATH
, sinon des problĂšmes pourraient survenir. Vous devez utiliser la variable
$HOME
:
export PATH="$PATH:${HOME}/depot_tools"
Récupération de code
Vous devez d'abord créer un dossier pour la source. Par exemple, dans le répertoire personnel (environ 30 Go d'espace libre sont nécessaires):
mkdir ~/chromium && cd ~/chromium
AprÚs cela, vous pouvez télécharger les sources à l'aide de l'utilitaire de
depot_tools
partir de
depot_tools
:
fetch --nohooks --no-history chromium
Vous pouvez maintenant opter pour du thé / café, car la procédure n'est pas rapide. Pour les expériences, aucun historique n'est nécessaire, donc l'indicateur
--no-history
est utilisé. L'histoire sera encore plus longue.
Installation de dépendance
Toutes les sources sont dans le dossier
src
, allez-y:
cd src
Maintenant, vous devez mettre toutes les dépendances à l'aide du script:
./build/install-build-deps.sh
Et exécutez les crochets:
gclient runhooks
Ceci termine la préparation de l'environnement.
SystĂšme de construction
Ninja est utilisé comme systÚme d'assemblage principal pour Chromium, et l'utilitaire
GN est utilisé pour générer des fichiers
.ninja
.
Pour comprendre comment utiliser ces outils, je propose de créer un exemple d'utilitaire de test. Pour ce faire, créez un
example
sous-dossier dans le dossier
src
:
mkdir example
Ensuite, dans le dossier
src/example
, créez le fichier
BUILD.gn
, qui contient:
executable("example") { sources = [ "example.cc", ] }
BUILD.gn
compose d'une cible (
example
exécutable) et d'une liste de fichiers nécessaires à la construction de la cible.
L'étape suivante consiste à créer le fichier
example.cc
lui-mĂȘme. Pour commencer, je propose de faire une application classique "Hello world":
#include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; }
Le code source peut ĂȘtre trouvĂ© sur
GitHub .
Pour que GN découvre le nouveau projet, dans le fichier
BUILD.gn
situé dans
src
, ajoutez la ligne
"//example"
dans la section
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", ] ...
Maintenant, vous devez retourner dans le dossier
src
et générer le projet à l'aide de la commande:
gn gen out/Default
GN vous permet également de préparer un projet pour l'un des IDE pris en charge:
- éclipse
- vs
- vs2013
- vs2015
- vs2017
- vs2019
- xcode
- qtcreator
- json
Plus d'informations peuvent ĂȘtre obtenues en utilisant la commande:
gn help gen
Par exemple, pour travailler avec l'
example
projet dans
QtCreator, vous devez exécuter la commande:
gn gen --ide=qtcreator --root-target=example out/Default
AprĂšs cela, vous pouvez ouvrir le projet dans QtCreator:
qtcreator out/Default/qtcreator_project/all.creator
La derniÚre étape consiste à construire le projet à l'aide de Ninja:
autoninja -C out/Default example
Cette brĂšve introduction au systĂšme d'assemblage peut ĂȘtre complĂ©tĂ©e.
L'application peut ĂȘtre lancĂ©e Ă l'aide de la commande:
./out/Default/example
Et voyez Hello world. En fait, vous pouvez Ă©crire un article sĂ©parĂ© sur le systĂšme d'assemblage dans Chromium. Peut-ĂȘtre pas un.
Travailler avec la ligne de commande
Comme premier exemple d'utilisation de la base de code Chromium comme framework, je suggĂšre de jouer avec la ligne de commande.
TĂąche:
afficher tous les arguments passĂ©s Ă l'application dans le style Chromium.Pour travailler avec la ligne de commande, vous devez inclure le fichier d'en-tĂȘte dans example.cc:
Et nous ne devons pas non plus oublier d'ajouter une dépendance au projet de
base
dans
BUILD.gn
.
BUILD.gn
devrait ressembler Ă ceci:
executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] }
Maintenant, tout ce dont vous avez besoin sera connecté à l'
example
.
Pour travailler avec la ligne de commande, Chromium fournit un singleton
base::CommandLine
. Pour obtenir un lien vers celui-ci, vous devez utiliser la
base::CommandLine::ForCurrentProcess
statique
base::CommandLine::ForCurrentProcess
, mais vous devez d'abord l'initialiser à l'aide de la méthode
base::CommandLine::Init
:
base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess();
Tous les arguments passés à l'application sur la ligne de commande et commençant par un
-
renvoyés en tant que
base::SwitchMap
(essentiellement
map<string, string>
) à l'aide de la méthode
GetSwitches
. Tous les autres arguments sont renvoyés en tant que
base::StringVector
(essentiellement
vectr<strig>
). Cette connaissance est suffisante pour implémenter le code de la tùche:
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 version complĂšte peut ĂȘtre trouvĂ©e sur
GitHub .
Pour créer et exécuter l'application, vous devez exécuter:
autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2
L'écran affichera:
Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2
Réseautage
Comme deuxiÚme et dernier exemple pour aujourd'hui, je propose de travailler avec la partie réseau de Chromium.
TĂąche:
afficher le contenu de l'URL passée en argument .
Sous-systÚme de réseau Chrome
Le sous-systÚme réseau est assez grand et complexe. Le point d'entrée pour les demandes HTTP, HTTPS, FTP et autres ressources de données est
URLRequest
, qui détermine déjà quel client utiliser. Un diagramme simplifié ressemble à ceci:
La version complÚte se trouve dans la documentation .Pour créer une
URLRequest
vous devez utiliser une
URLRequestContext
. La création d'un contexte est une opération assez compliquée, il est donc recommandé d'utiliser
URLRequestContextBuilder
. Il initialisera toutes les variables nĂ©cessaires avec des valeurs par dĂ©faut, mais, si vous le souhaitez, elles peuvent ĂȘtre modifiĂ©es pour les leurs, par exemple:
net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true , false ); context_builder.SetCookieStore(nullptr);
Multithreading
La pile réseau Chromium est conçue pour fonctionner dans un environnement multi-thread, vous ne pouvez donc pas ignorer cette rubrique. Les objets de base pour travailler avec le multithreading dans Chromium sont:
- Tùche - une tùche à exécuter, dans Chromium, c'est une fonction de type
base::Callback
, qui peut ĂȘtre créée en utilisant base::Bind
.
- File d'attente des tùches - la file d'attente des tùches à exécuter.
pthread
physique - un wrapper multiplateforme sur le thread du systĂšme d'exploitation ( pthread
sur POSIX ou CreateThread()
sur Windows). Implémenté dans la classe base::PlatformThread
, ne l'utilisez pas directement.
- base :: Thread - un vrai thread qui traite les messages d'une file d'attente de tùches dédiée à l'infini; Il n'est pas recommandé de les créer directement.
- Pool de threads - pool de threads avec une file d'attente de tùches commune. Implémenté dans la classe
base::ThreadPool
. En rÚgle générale, créez une instance. Les tùches lui sont envoyées à l'aide des fonctions de base/task/post_task.h
.
- Séquence ou thread virtuel - un thread virtuel qui utilise de vrais threads et peut basculer entre eux.
base::TaskRunner
tùches - une interface pour définir des tùches, implémentée dans la base::TaskRunner
.
- Gestionnaire de tĂąches sĂ©quencĂ© - une interface pour dĂ©finir des tĂąches, qui garantit que les tĂąches seront exĂ©cutĂ©es dans le mĂȘme ordre dans lequel elles sont arrivĂ©es. ImplĂ©mentĂ© dans la classe
base::SequencedTaskRunner
.
- Exécuteur de tùches à un seul thread - similaire au précédent, mais garantit que toutes les tùches seront exécutées dans un seul thread du systÚme d'exploitation. Implémenté dans la
base::SingleThreadTaskRunner
.
Implémentation
Certains composants Chromium nécessitent la présence de
base::AtExitManager
- il s'agit d'une classe qui vous permet d'enregistrer les opĂ©rations qui doivent ĂȘtre effectuĂ©es Ă la fin de l'application. Son utilisation est trĂšs simple, vous devez crĂ©er un objet sur la pile:
base::AtExitManager exit_manager;
Lorsque
exit_manager
sort de la portée, tous les rappels enregistrés seront exécutés.
Vous devez maintenant vous assurer de la disponibilité de tous les composants multithreads nécessaires pour le sous-systÚme réseau. Pour ce faire, créez un
Thread pool
, une
Message loop
de type
TYPE_IO
pour le traitement des messages réseau et une
Message loop
d'
TYPE_IO
- la boucle du programme principal:
base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop;
Ensuite, utilisez le
Context builder
pour créer un
Context
:
auto ctx = net::URLRequestContextBuilder().Build();
Pour envoyer une demande, vous devez créer un objet
URLRequest
à l'aide de la méthode
CreateRequest
de l'objet
ctx
. Les paramĂštres suivants sont transmis:
- URL, chaĂźne de type GURL;
- priorité;
- délégué qui gÚre les événements.
Un délégué est une classe qui implémente l'interface
net::URLRequest::Delegate
. Pour cette tĂąche, cela peut ressembler Ă ceci:
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_; };
Toute la logique principale est dans le
OnResponseStarted
événement
OnResponseStarted
: le contenu de la rĂ©ponse est soustrait jusqu'Ă ce qu'une erreur se produise ou qu'il n'y ait rien Ă lire. Ătant donnĂ© qu'aprĂšs avoir lu la rĂ©ponse, vous devez terminer l'application, le dĂ©lĂ©guĂ© doit avoir accĂšs Ă la fonction qui interrompra la
Run loop
principale, dans ce cas, un rappel du type
base::Closure
est utilisé.
Maintenant, tout est prĂȘt pour envoyer la demande:
MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start();
Pour que la demande démarre le traitement, exécutez la
Run loop
:
run_loop.Run();
La version complĂšte peut ĂȘtre trouvĂ©e sur
GitHub .
Pour créer et exécuter l'application, vous devez exécuter:
autoninja -C out/Default example out/Default/example "https://example.com/"
Finale
En fait, dans Chromium, vous pouvez trouver de nombreux cubes et briques utiles à partir desquels vous pouvez créer des applications. Il est en constante évolution, ce qui, d'une part, est un plus, et d'autre part, les changements réguliers de l'API ne vous permettent pas de vous détendre. Par exemple, dans la derniÚre version,
base::TaskScheduler
s'est transformé en
base::ThreadPool
, heureusement, sans changer l'API.
PS Nous recherchons un programmeur C ++ leader dans notre Ă©quipe! Si vous ressentez la force en vous-mĂȘme, nos souhaits sont dĂ©crits ici:
team.mail.ru/vacancy/4641/ . Il y a également un bouton «Répondre».