📜 ⬆️ ⬇️

Chromium is not only a browser, but also a good framework



Most people are used to the fact that Chromium is both the browser and the foundation for other browsers. Until recently, I also thought so, but, studying this topic for a couple of months, I began to discover another wonderful world. Chromium is a huge ecosystem that has everything: a dependency system, a cross-platform build system, and components for almost all occasions. So why not try creating your applications using all this power?

Under the cut a little guide how to start doing it.

Environment preparation


In the article I will use Ubuntu 18.04, the procedure for other operating systems can be found in the documentation:
')

Git and Python are required for the next steps. If they are not installed, they must be installed using the command:

sudo apt install git python 

Install depot_tools


depot_tools is a toolkit for Chromium development. To install it you need to run:

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

And add the path to the PATH environment variable:

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

Important: if depot_tools were downloaded to the home folder, then do not use ~ in the PATH variable, otherwise problems may occur. You must use the $HOME variable:

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

Getting the code


First you need to create a folder for sources. For example, in the home directory (you need about 30 GB of free space):

 mkdir ~/chromium && cd ~/chromium 

After that, you can download the sources using the fetch utility from depot_tools :

 fetch --nohooks --no-history chromium 

Now you can go to drink tea / coffee, as the procedure is not fast. History is not needed for experiments, so the --no-history flag is used. With the story will be even longer.

Installing dependencies


All sources are in the src folder, go to it:

 cd src 

Now you need to put all the dependencies using the script:

 ./build/install-build-deps.sh 

And run the hooks:

 gclient runhooks 

This completes the preparation of the environment.

Assembly system


Ninja is used as the main build system of Chromium, and GN is used to generate .ninja files.

To understand how to use these tools, I suggest creating a test utility example. To do this, you need to create a subfolder example in the src folder:

 mkdir example 

Then in the src/example folder you need to create a BUILD.gn file that contains:

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

BUILD.gn consists of a target (an executable example file) and a list of files that are needed to build a target.

The next step is to create the file itself example.cc . To begin with, I propose to make the classic application “Hello world”:

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

Source code can be found on github .

In order for GN to find out about a new project, you need to add the line "//example" in the deps section in the BUILD.gn file that is located in src :

 ... 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", ] ... 

Now you need to go back to the src folder and generate the project using the command:

 gn gen out/Default 

GN also allows you to prepare a project for one of the supported IDEs:


More information can be obtained using the command:

 gn help gen 

For example, to work with the example project in QtCreator, you need to run the command:

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

After that, you can open the project in QtCreator:

 qtcreator out/Default/qtcreator_project/all.creator 

The final step is building the project with Ninja:

 autoninja -C out/Default example 

At this brief introduction to the assembly system can be completed.

The application can be launched using the command:

 ./out/Default/example 

And see Hello world. In fact, you can write a separate article about the build system in Chromium. Perhaps not one.

Work with the command line


As the first example of using the Chromium codebase as a framework, I suggest playing with the command line.

Task: display all the arguments passed to the application in the style of Chromium.
To work with the command line, you need to include a header file in example.cc:

 #include "base/command_line.h" 

And also it is necessary not to forget to add dependence on the project base in BUILD.gn . BUILD.gn should look like this:

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

Now everything you need will be connected to example .

To work with the command line, Chromium provides a base::CommandLine . To get a link to it, you must use the static base::CommandLine::ForCurrentProcess , but first you need to initialize it using the base::CommandLine::Init method:

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

All arguments passed to the application on the command line and starting with a character - returned as base::SwitchMap (essentially, map<string, string> ) using the GetSwitches method. All other arguments are returned in the form of base::StringVector (in fact, vectr<strig> ). This knowledge is enough to implement the code for the task:

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

The full version can be found on github .

To build and run the application you need to run:

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

The screen will display:

 Switch sw1: val1 Switch sw2: Arg arg1 Arg arg2 

Work with the network


As a second and last example for today, I suggest working with the network part of Chromium.

Task: display the contents of the URL passed as an argument .

Chromium network subsystem


The network subsystem is quite large and complex. The entry point for requests to HTTP, HTTPS, FTP, and other data resources is URLRequest , which already determines which client to use. A simplified diagram looks like this:



The full version can be found in the documentation .

To create a URLRequest you must use a URLRequestContext . Creating a context is a rather complicated operation, so it is recommended to use URLRequestContextBuilder . It initializes all necessary variables with default values, but, if you wish, you can change them for your own, for example:

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

Multithreading


The network stack of Chromium is designed to work in a multi-threaded environment, so you can not skip this topic. The basic objects for working with multithreading in Chromium are:


Implementation


Some Chromium components require base::AtExitManager - this is the class that allows you to register the operations that must be performed when the application terminates. Using it is very simple; you need to create an object on the stack:

 base::AtExitManager exit_manager; 

When exit_manager goes out of scope, all registered callbacks will be executed.

Now you need to take care of the presence of all the necessary components of multithreading for the network subsystem. To do this, you need to create a Thread pool , Message loop with the TYPE_IO type for processing network messages, and the Run loop is the main program loop:

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

Next you need to use Context builder to create a Context :

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

To send a request, you must use the CreateRequest method of the ctx object to create a URLRequest object. The following parameters are passed as parameters:


A delegate is a class that implements the net::URLRequest::Delegate interface. For this task, it might look like this:

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

All the main logic is in the OnResponseStarted event OnResponseStarted : the contents of the response are read until an error occurs or there is nothing to read. Since after reading the response, you need to terminate the application, the delegate must have access to the function that will interrupt the main Run loop , in this case, callback of type base::Closure .

Now everything is ready to send the request:

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

For the request to start processing, you need to run the Run loop :

 run_loop.Run(); 

The full version can be found on github .

To build and run the application you need to run:

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

The final


In fact, in Chromium you can find a lot of useful cubes and bricks from which you can build applications. It is constantly evolving, which, on the one hand, is a plus, and on the other hand, regular API changes do not allow to relax. For example, in the latest release, base::TaskScheduler turned into base::ThreadPool , thankfully, without changing the API.

PS We are looking for a leading C ++ programmer in our team! If you feel strong, our wishes are described here: team.mail.ru/vacancy/4641/ . There is also a button "Respond".

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


All Articles