
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 .
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 utilisantbase::Bind.
 
- File d'attente des tùches - la file d'attente des tùches à exécuter.
 
- pthreadphysique - un wrapper multiplateforme sur le thread du systÚme d'exploitation (- pthreadsur 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 debase/task/post_task.h.
 
- Séquence ou thread virtuel - un thread virtuel qui utilise de vrais threads et peut basculer entre eux.
 
- base::TaskRunnertù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».