Chrome n'est pas seulement un navigateur, mais aussi un bon framework



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:

 #include "base/command_line.h" 

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

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


All Articles