Chromium tidak hanya peramban, tetapi juga kerangka kerja yang baik



Sebagian besar orang terbiasa dengan fakta bahwa Chromium adalah peramban dan dasar bagi peramban lain. Sampai baru-baru ini, saya juga berpikir begitu, tetapi, mempelajari topik ini selama beberapa bulan, saya mulai menemukan dunia lain yang indah. Chromium adalah ekosistem besar di mana ada segalanya: sistem ketergantungan, sistem pembangunan lintas platform, dan komponen untuk hampir semua kesempatan. Jadi mengapa tidak mencoba membuat aplikasi sendiri menggunakan semua kekuatan ini?

Di bawah kat, panduan kecil tentang cara mulai melakukan ini.

Persiapan lingkungan


Dalam artikel saya akan menggunakan Ubuntu 18.04, prosedur untuk OS lain dapat ditemukan dalam dokumentasi:


Langkah-langkah berikut membutuhkan Git dan Python. Jika mereka tidak diinstal, maka mereka harus diinstal menggunakan perintah:

sudo apt install git python 

Pengaturan depot_tools


depot_tools adalah toolkit pengembangan Chromium. Untuk menginstalnya, Anda harus melakukan:

 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 

Dan tambahkan path ke variabel lingkungan PATH:

 export PATH="$PATH:/path/to/depot_tools" 

Penting: jika depot_tools diunduh ke folder rumah Anda, jangan gunakan ~ dalam variabel PATH , jika tidak, masalah dapat terjadi. Anda harus menggunakan variabel $HOME :

 export PATH="$PATH:${HOME}/depot_tools" 

Pengambilan kode


Pertama, Anda perlu membuat folder untuk sumbernya. Misalnya, di direktori home (dibutuhkan ruang kosong sekitar 30 GB):

 mkdir ~/chromium && cd ~/chromium 

Setelah itu, Anda dapat mengunduh sumber menggunakan utilitas fetch dari depot_tools :

 fetch --nohooks --no-history chromium 

Sekarang Anda bisa minum teh / kopi, karena prosedurnya tidak cepat. Untuk percobaan, tidak diperlukan riwayat, sehingga bendera --no-history digunakan. Ceritanya akan lebih panjang.

Instalasi Ketergantungan


Semua sumber ada di folder src , buka:

 cd src 

Sekarang Anda harus meletakkan semua dependensi menggunakan skrip:

 ./build/install-build-deps.sh 

Dan jalankan hooks:

 gclient runhooks 

Ini melengkapi persiapan lingkungan.

Membangun sistem


Ninja digunakan sebagai sistem perakitan utama untuk Chromium, dan utilitas GN digunakan untuk menghasilkan file .ninja .

Untuk memahami cara menggunakan alat ini, saya mengusulkan untuk membuat contoh utilitas pengujian. Untuk melakukan ini, buat example subfolder di folder src :

 mkdir example 

Kemudian, di folder src/example , buat file BUILD.gn , yang berisi:

 executable("example") { sources = [ "example.cc", ] } 

BUILD.gn terdiri dari target ( example executable) dan daftar file yang diperlukan untuk membangun target.

Langkah selanjutnya adalah membuat file example.cc itu sendiri. Untuk memulai, saya mengusulkan untuk membuat aplikasi klasik "Hello world":

 #include <iostream> int main(int argc, char **argv) { std::cout << "Hello world" << std::endl; return 0; } 

Kode sumber dapat ditemukan di GitHub .

Agar GN dapat mempelajari tentang proyek baru, dalam file BUILD.gn terletak di src , tambahkan baris "//example" di bagian 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", ] ... 

Sekarang Anda perlu kembali ke folder src dan menghasilkan proyek menggunakan perintah:

 gn gen out/Default 

GN juga memungkinkan Anda menyiapkan proyek untuk salah satu IDE yang didukung:

  • gerhana
  • vs.
  • vs2013
  • vs2015
  • vs2017
  • vs2019
  • xcode
  • qtcreator
  • json

Informasi lebih lanjut dapat diperoleh dengan menggunakan perintah:

 gn help gen 

Misalnya, untuk bekerja dengan proyek example di QtCreator, Anda perlu menjalankan perintah:

 gn gen --ide=qtcreator --root-target=example out/Default 

Setelah itu, Anda dapat membuka proyek di QtCreator:

 qtcreator out/Default/qtcreator_project/all.creator 

Langkah terakhir adalah membangun proyek menggunakan Ninja:

 autoninja -C out/Default example 

Pengantar singkat tentang sistem perakitan ini dapat diselesaikan.

Aplikasi dapat diluncurkan menggunakan perintah:

 ./out/Default/example 

Dan lihat Hello world. Bahkan, Anda dapat menulis artikel terpisah tentang sistem perakitan di Chromium. Mungkin bukan satu.

Bekerja dengan baris perintah


Sebagai contoh pertama menggunakan basis kode Chromium sebagai kerangka kerja, saya sarankan bermain-main dengan baris perintah.

Tugas: menampilkan semua argumen yang diteruskan ke aplikasi dalam gaya Chromium.
Untuk bekerja dengan baris perintah, Anda harus menyertakan file header di example.cc:

 #include "base/command_line.h" 

Dan juga kita tidak boleh lupa untuk menambahkan ketergantungan pada proyek base di BUILD.gn . BUILD.gn akan terlihat seperti ini:

 executable("example") { sources = [ "example.cc", ] deps = [ "//base", ] } 

Sekarang semua yang Anda butuhkan akan terhubung ke example .

Untuk bekerja dengan baris perintah, Chromium menyediakan base::CommandLine tunggal base::CommandLine . Untuk mendapatkan tautan ke sana, Anda perlu menggunakan base::CommandLine::ForCurrentProcess metode statis base::CommandLine::ForCurrentProcess , tetapi pertama-tama Anda perlu menginisialisasi menggunakan base::CommandLine::Init Metode base::CommandLine::Init :

 base::CommandLine::Init(argc, argv); auto *cmd_line = base::CommandLine::ForCurrentProcess(); 

Semua argumen yang diteruskan ke aplikasi pada baris perintah dan dimulai dengan a - dikembalikan sebagai base::SwitchMap (pada dasarnya map<string, string> ) menggunakan metode GetSwitches . Semua argumen lain dikembalikan sebagai base::StringVector (pada dasarnya vectr<strig> ). Pengetahuan ini cukup untuk mengimplementasikan kode untuk tugas:

 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; } 

Versi lengkap dapat ditemukan di GitHub .

Untuk membangun dan menjalankan aplikasi, Anda perlu menjalankan:

 autoninja -C out/Default example ./out/Default/example arg1 --sw1=val1 --sw2 arg2 

Layar akan menampilkan:

 Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2 

Jaringan


Sebagai contoh kedua dan terakhir untuk hari ini, saya mengusulkan untuk bekerja dengan bagian jaringan Chromium.

Tugas: menampilkan konten URL yang diteruskan sebagai argumen .

Subsistem Jaringan Chromium


Subsistem jaringan cukup besar dan kompleks. Titik masuk untuk permintaan HTTP, HTTPS, FTP dan sumber daya data lainnya adalah URLRequest , yang sudah menentukan klien mana yang akan digunakan. Diagram yang disederhanakan terlihat seperti ini:



Versi lengkap dapat ditemukan di dokumentasi .

Untuk membuat URLRequest Anda harus menggunakan URLRequestContext . Membuat konteks adalah operasi yang agak rumit, oleh karena itu disarankan untuk menggunakan URLRequestContextBuilder . Ini akan menginisialisasi semua variabel yang diperlukan dengan nilai default, tetapi, jika diinginkan, mereka dapat diubah sendiri, misalnya:

 net::URLRequestContextBuilder context_builder; context_builder.DisableHttpCache(); context_builder.SetSpdyAndQuicEnabled(true /* http2 */, false /* quic */); context_builder.SetCookieStore(nullptr); 

Multithreading


Tumpukan jaringan Chromium dirancang untuk bekerja di lingkungan multi-utas, sehingga Anda tidak dapat melewati topik ini. Objek dasar untuk bekerja dengan multithreading di Chromium adalah:

  • Tugas - tugas untuk dieksekusi, di Chromium itu adalah fungsi dari tipe base::Callback , yang dapat dibuat menggunakan base::Bind .
  • Antrian tugas - antrian tugas untuk dieksekusi.
  • Fisik thread - pembungkus lintas platform di atas sistem operasi thread ( pthread pada POSIX atau CreateThread() pada Windows). Diterapkan dalam base::PlatformThread kelas base::PlatformThread , jangan gunakan secara langsung.
  • base :: Utas - utas nyata yang memproses pesan dari antrian tugas khusus tanpa henti; Tidak disarankan untuk membuatnya secara langsung.
  • Thread pool - pool thread dengan antrian tugas umum. Diimplementasikan dalam base::ThreadPool kelas base::ThreadPool . Sebagai aturan, buat satu instance. Tugas dikirim ke sana menggunakan fungsi dari base/task/post_task.h .
  • Urutan atau Urutan virtual - utas virtual yang menggunakan utas nyata dan dapat beralih di antara mereka.
  • Pelari tugas - antarmuka untuk mengatur tugas, diimplementasikan di base::TaskRunner .
  • Pelari tugas berurutan - antarmuka untuk mengatur tugas, yang memastikan bahwa tugas akan dieksekusi dalam urutan yang sama dengan saat mereka tiba. Diimplementasikan dalam base::SequencedTaskRunner kelas base::SequencedTaskRunner .
  • Pelari tugas utas tunggal - mirip dengan yang sebelumnya, tetapi menjamin bahwa semua tugas akan dilakukan dalam satu utas OS. Diterapkan dalam base::SingleThreadTaskRunner .

Implementasi


Beberapa komponen Chromium memerlukan kehadiran base::AtExitManager - ini adalah kelas yang memungkinkan Anda untuk mendaftarkan operasi yang harus dilakukan ketika aplikasi berakhir. Menggunakannya sangat sederhana, Anda perlu membuat objek di stack:

 base::AtExitManager exit_manager; 

Ketika exit_manager keluar dari ruang lingkup, semua panggilan balik yang terdaftar akan dieksekusi.

Sekarang Anda perlu menjaga ketersediaan semua komponen multithreading yang diperlukan untuk subsistem jaringan. Untuk melakukan ini, buat kumpulan Thread pool , Message loop dengan tipe TYPE_IO untuk memproses pesan jaringan, dan Run loop - loop program utama:

 base::ThreadPool::CreateAndStartWithDefaultParams("downloader"); base::MessageLoop msg_loop(base::MessageLoop::TYPE_IO); base::RunLoop run_loop; 

Selanjutnya, gunakan Context builder untuk membuat Context :

 auto ctx = net::URLRequestContextBuilder().Build(); 

Untuk mengirim permintaan, Anda harus membuat objek URLRequest menggunakan metode CreateRequest dari objek ctx . Parameter berikut dilewatkan:

  • URL, string dengan tipe GURL;
  • prioritas;
  • delegasikan yang menangani acara.

Delegasi adalah kelas yang mengimplementasikan antarmuka net::URLRequest::Delegate . Untuk tugas ini, mungkin terlihat seperti ini:

 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_; }; 

Semua logika utama ada di event OnResponseStarted : isi respons dikurangi sampai kesalahan terjadi atau tidak ada yang dibaca. Karena setelah membaca respons yang Anda butuhkan untuk menyelesaikan aplikasi, delegasi harus memiliki akses ke fungsi yang akan mengganggu Run loop utama, dalam hal ini panggilan balik dari tipe base::Closure digunakan.

Sekarang semuanya siap untuk mengirim permintaan:

 MyDelegate delegate(run_loop.QuitClosure()); auto req = ctx->CreateRequest(GURL(args[0]), net::RequestPriority::DEFAULT_PRIORITY, &delegate); req->Start(); 

Untuk permintaan untuk mulai memproses, Anda perlu menjalankan Run loop :

 run_loop.Run(); 

Versi lengkap dapat ditemukan di GitHub .

Untuk membangun dan menjalankan aplikasi, Anda perlu menjalankan:

 autoninja -C out/Default example out/Default/example "https://example.com/" 

Terakhir


Bahkan, di Chromium Anda dapat menemukan banyak kubus dan batu bata yang berguna untuk membangun aplikasi. Ini terus berkembang, yang, di satu sisi, merupakan nilai tambah, dan di sisi lain, perubahan reguler pada API tidak membuat Anda rileks. Misalnya, dalam rilis terbaru, base::TaskScheduler berubah menjadi base::ThreadPool , untungnya, tanpa mengubah API.

PS Kami mencari programmer C ++ terkemuka di tim kami! Jika Anda merasakan kekuatan dalam diri Anda, maka keinginan kami dijelaskan di sini: team.mail.ru/vacancy/4641/ . Ada juga tombol "Tanggapi".

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


All Articles