📜 ⬆️ ⬇️

How to save memory on browser tabs, but not to lose their contents. Experience team Yandex. Browser

When browsers do not have enough memory, they unload the oldest tabs from it. This is annoying because clicking on this tab will force the page to reload. Today we will tell Habr's readers how the Yandex.Browser team solves this problem with the help of Hibernate technology.

Chromium-based browsers create a process for each tab. This approach has many advantages. This is security (isolation of sites from each other), and stability (the fall of one process does not pull the entire browser behind it), and the acceleration of work on modern processors with a large number of cores. But there is also a minus - a higher consumption of RAM than when using a single process for everything. If browsers didn’t do anything about it, their users would constantly see something like this:


')
The Chromium project fights memory consumption with background tabs by clearing various caches. This is not about the cache, which stores images of downloaded pages. There are no problems with him - he lives on a hard disk. In a modern browser, there is a lot of other cached information that is stored in RAM.

Chromium has also been working for quite some time to stop JS timers in background tabs. Otherwise, clearing the cache loses its meaning, because activities in the background tabs restore them. It is believed that if sites want to work in the background, then you need to use service worker, and not timers.

If these measures do not help, then all that remains is to unload the entire tab rendering process from memory. An open site simply ceases to exist. If you switch to the tab, it will start downloading from the network. If there was a pause video in the tab, it will start playing from the beginning. If the form has been filled out on the page, then the information entered may be lost. If a heavy JS application worked in the tab, then you will need to restart it.

The problem of unloading tabs is especially unpleasant in the absence of network access. Postponed tab with Habr for reading on board the aircraft? Be prepared that a useful article will turn into a pumpkin.

Browser developers understand that this extreme measure irritates users (just look at the search to assess the scale), so they apply it at the last moment. At this point, the computer is already slowing down due to lack of memory, users notice this and are looking for alternative ways to solve the problem, so, for example, The Great Suspender has more than 1.4 million users.

People want browsers and memory to save, and do not start to slow down. To do this, the tabs need to be unloaded not at the last moment, but a little earlier. And for this you need to stop losing the contents of the tabs, i.e. make the savings process invisible. But then what to save? The circle is closed. But the solution was found.

Hibernate in Yandex Browser


Many Habr's readers could already guess what to clear the memory, but it’s quite possible to save the state of a tab if you previously unload the state onto a hard disk. If you click a tab to restore a tab from the hard disk, the user will not notice anything.

Our team is involved in the development of the Chromium project, where it sends significant optimizing edits and new features . Back in 2015, we discussed with the colleagues from the project the idea of ​​maintaining the state of the tabs on the hard disk and even managed to make some improvements, but Chromium decided to freeze this trend. We decided differently and continued development in Yandex Browser. It took longer than planned, but it was worth it. Below we will talk about the technical stuffing of the Hibernate technology, but for now let's start with general logic.

Several times a minute, Yandex. The browser checks the amount of available memory, and if it is less than the threshold value of 600 megabytes, then Hibernate takes over. It all starts with the fact that the Browser finds the oldest (by usage) background tab. By the way, on average, the user has 7 tabs open, but 5% have more than 30 tabs.

Unloading any old tab from the memory is impossible - you can break something really important. For example, playing music or chatting in a web messenger. There are 28 such exceptions. If the tab does not fit at least one of them, the Browser proceeds to check the next one.

If a tab is found that meets the requirements, then the process of saving it begins.

Saving and restoring tabs in Hibernate


Any page can be divided into two large parts, related to the V8 (JS) and Blink engines (HTML / DOM). Consider a small example:

<html> <head> <script type="text/javascript"> function onLoad() { var div = document.createElement("div"); div.textContent = "Look ma, I can set div text"; document.body.appendChild(div); } </script> </head> <body onload="onLoad()"></body> </html> 


We have some DOM tree and a small script that just adds a div to the body. From the point of view of Blink, this page looks like this:



Let's look at the relationship between Blink and V8 on the example of HTMLBodyElement:



You may notice that Blink and V8 have different representations of the same entities and are closely related to each other. So we came to the original idea - to save the full state of V8, and for Blink to store only HTML attributes in the form of text. But this was a mistake, because we lost those DOM object states that were not stored in attributes. And also lost states that were not stored in the DOM. The solution to this problem was the complete preservation of Blink. But not everything is so simple.

First you need to collect information about objects Blink. Therefore, at the time of saving V8, we not only stop JS and make its impression, but also collect in memory references to DOM objects and other auxiliary objects available for JS. We also go through all the objects that can be reached from the Document objects, the root elements of each page frame. So we collect information about everything that is important to keep. It remains the most difficult - to learn how to save.

If you count all the Blink classes that represent the DOM tree, as well as different HTML5 APIs (for example, canvas, media, geolocation), you get thousands of classes. It is almost impossible to write with your hands the logic of saving all classes. But the worst thing is that even if you do this, it will be impossible to maintain, because we regularly add new versions of Chromium, which introduce unexpected changes to any class.

Our browser for all platforms is built using clang. To solve the problem of preserving Blink classes, we created a clang plugin that builds an AST (abstract syntax tree) for classes. For example, this code:

Class code
 class Bar : public foo_namespace::Foo { struct BarInternal { int int_field_; float float_field_; } bar_internal_field_; std::string string_field_; }; 


Turns into this XML:

The result of the plugin in XML
 <class> <name>bar_namespace::Bar::BarInternal</name> <is_union>false</is_union> <is_abstract>false</is_abstract> <decl_source_file>src/bar.h</decl_source_file> <base_class_names></base_class_names> <fields> <field> <name>int_field_</name> <type> <builtin> <is_const>0</is_const> <name>int</name> </builtin> </type> </field> <field> <name>float_field_</name> <type> <builtin> <is_const>0</is_const> <name>float</name> </builtin> </type> </field> </class> <class> <name>bar_namespace::Bar</name> <is_union>false</is_union> <is_abstract>false</is_abstract> <decl_source_file>src/bar.h</decl_source_file> <base_class_names> <class_name>foo_namespace::Foo</class_name> </base_class_names> <fields> <field> <name>bar_internal_field_</name> <type> <class> <is_const>0</is_const> <name>bar_namespace::Bar::BarInternal</name> </class> </type> </field> <field> <name>string_field_</name> <type> <class> <is_const>0</is_const> <name>std::string</name> </class> </type> </field> </fields> </class> 


Further, other scripts written by us generate C ++ code from this information for saving and restoring classes, which falls into the Yandex.Browser assembly.

C ++ save code obtained by the script from XML
 void serialize_bar_namespace_Bar_BarInternal( WriteVisitor* writer, Bar::BarInternal* instance) { writer->WriteBuiltin<size_t>(instance->int_vector_field_.size()); for (auto& item : instance->int_vector_field_) { writer->WriteBuiltin<int>(item); } writer->WriteBuiltin<float>(instance->float_field_); } void serialize_bar_namespace_Bar(WriteVisitor* writer, Bar* instance) { serialize_foo_namespace_Foo(writer, instance); serialize_bar_namespace_Bar_BarInternal( writer, &instance->bar_internal_field_); writer->WriteString(instance->string_field_); } 


In total, we generate code for approximately 1000 Blink classes. For example, we learned to save such a complex class as Canvas. It can draw from JS-code, set a lot of properties, set the parameters of brushes for drawing and so on. We save all these properties, parameters and the picture itself.

After successfully encrypting and saving all data to the hard disk, the tab process is unloaded from memory until the user returns to this tab. In the interface, as before, it does not stand out.

Tab recovery is not instantaneous, but significantly faster than booting from the network. Nevertheless, we went on a tricky move, so as not to annoy users with flashes of a white screen. We show a screenshot of the page created during the save phase. This helps smooth the transition. The rest of the recovery process is similar to normal navigation, with the only difference being that the Browser does not make a network request. It recreates the frame structure and DOM trees in them, and then replaces the V8 state.

We recorded a video with a visual demonstration of how Hibernate unloads and restores tab clicks while preserving the progress in the JS game, entered in the text and position of the video:


Results


In the near future, Hibernate technology will be available to all users of Yandex. Browser for Windows. We also plan to start experimenting with it in alpha for Android. With its help, the Browser saves memory more efficiently than before. For example, users with a large number of open tabs Hibernate on average saves more than 330 megabytes of memory and does not lose the information in the tabs, which remains available in one click for any network condition. We understand that it would be useful for webmasters to take into account the unloading of background tabs, so we plan to support the Page Lifecycle API .

Hibernate is not our only solution to save resources. We are not the first year working to ensure that the browser adapts to the resources available in the system. For example, on weak devices, the Browser switches to the simplified mode, and when the laptop is disconnected from the power source, it reduces power consumption. Saving resources is a big and complex story, to which we will definitely return to Habré.

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


All Articles