Pavel Karpovich is a Lead Software Engineer at Godel who recently took part in a tech meetup in Wroclaw. He expanded on his topic, “To the loop and back. In and out” where he spoke about the zany world of browser engines, exploring the twists and turns of the event loop and how it manages to keep up with the frantic pace of modern web applications. He also ran a discussion, where he answered some tough questions about Event Loop and Rendering Pipeline.
What are the two core concepts of browser engines?
The two core concepts are Event Loop and Rendering Pipeline, which I will go into detail a bit later on. There are two core engines in the browser that are responsible for rendering HTML and CSS and executing JS. There are a lot of browsers, and they all use different engines under the hood, but in this example, I will talk about Google Chrome according to the last statistics is an industry-standard I will use it for my presentation. Chrome uses the V8 JS engine and Blink browser engine. In general, all browsers use the same approach so there is not much difference in general and you can apply the received information in your development no matter what you use.
Can you explain more about JS engine, specifically V8?
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others. The most crucial part of the understanding of how JS works is the event loop. JS is a single-threaded language so everything written in JS is executed in only one single thread. But at the same time, JS is also an asynchronous language. So how exactly does JavaScript execute your code, and what allows it to process multiple operations in one thread.
What does V8 include?
It’s heap and stack. A heap is an unordered space where objects are allocated and stack is execution contexts. Every time you create a function, it creates a new execution context including params and variables and places it into the stack. There is nothing more in the V8, and of course, nothing related to asynchronous execution.
You can see this is how the call stack looks. One thread -> one stack -> one frame per time. So, when we talk about JS as a single-threaded language, it means that it has only one call stack and all operations are performed only into this stack.
How do event loops operate?
So the event loop runs continuously, monitoring the call stack and the callback queue. When the call stack is empty, the event loop looks at the callback queue for any callbacks that need to be processed.
- When a JavaScript program is run, an initial stack frame is created and executed.
- As the program runs, functions are called, creating new stack frames that are added to the call stack.
- If a function contains asynchronous code (such as a setTimeout function), the code is not executed immediately. Instead, the function is added to the callback queue.
In our case with setTimout, it will be processed with Web API first. You can see that the engine doesn’t stop executing the code and creates execution frame in the call stack for the second console.log().
Now the call stack is empty but there is still timeout function executing by Web API. After that it will be placed in the callback queue: When the call stack is empty, the event loop checks the callback queue for any callbacks. If there are callbacks in the queue, the event loop adds the corresponding function to the call stack, where it is executed. Once the function is completed, its stack frame is removed from the call stack and the event loop checks the callback queue again. This process continues until there are no more callbacks in the queue.
It’s worth noting that the event loop has some nuances in its behaviour. For example, when the event loop is processing callbacks, it will not stop to process user input or redraw the screen, so the user interface may become unresponsive. In order to avoid this, it’s important to use asynchronous code wisely and not block the main thread of execution. Additionally, the event loop has different priority levels for different types of callbacks, so it’s possible for certain callbacks to be processed before others even if they were added to the queue later.
What role do browser engines play?
This topic is important because rendering engines play a crucial role in how users interact with web content. By understanding how they work, we can optimise our web applications and deliver a better user experience. To begin with, let’s define what a rendering engine is.
In simple terms, a rendering engine is a software component that takes HTML, CSS, and JavaScript code and converts it into a visual representation that users can see and interact with. The rendering engine is responsible for parsing the code, laying out the content, and painting the pixels that make up the final output.
There are several different rendering engines in use today, including Blink (used by Chrome), Gecko (used by Firefox), WebKit (used by Safari), and Trident (used by Internet Explorer). Each engine has its own strengths and weaknesses, and understanding how they work can help developers optimize their code for a particular browser. Let’s take a closer look at how a rendering engine works.
What is the process of a browser engine?
The process begins when the browser receives an HTML file from the server. The engine starts by parsing the HTML code and building a Document Object Model (DOM) tree.
The DOM tree represents the structure of the HTML document, with each element of the document represented as a node in the tree. It’s worth noting that this process stops when the browser sees the script tag in the HTML because it starts to parse JS and executes it. It happens because JS can change HTML which will affect on the DOM for example by the document.write() method.
Once the DOM tree has been built, the engine moves on to processing the CSS code. This involves building a CSS Object Model (CSSOM) that represents the styles applied to each element in the document. The engine then combines the DOM tree and the CSSOM to create a render tree, which represents the final layout of the content on the page.
Next, the engine calculates the layout of each element in the render tree. This involves determining the size and position of each element, as well as how it should flow with other elements on the page. Once the layout has been calculated, the engine paints the pixels that make up the final output. This involves creating a bitmap image of the page that can be displayed on the user’s screen.
What are the stages of a rendering pipeline?
Understanding how browser processes HTML and CSS, you can apply these knowledges to manage the pipeline. For example:
- If you change a “layout” property, so that’s one that changes an element’s geometry, like its width, height, or its position with left or top, the browser will have to check all the other elements and “reflow” the page. It’s also related to JS getters of the width of elements.
- If you changed a “paint only” property, like a background image, text color, or shadows, in other words one that does not affect the layout of the page, then the browser skips layout, but it will still do paint.
- If you changed a “paint only” property, such as a background image, text colour or shadows, in other words one that does not affect the layout of the page. The browser will skip the layout, but it will still do the paint.
- If you change a property that requires neither layout or paint and the browser jumps to do just compositing. The final version is the cheapest and most desirable for high pressure points in an app’s lifecycle, like animations or scrolling.
Conclusion
These are very simple and base concepts will help you to understand how your code works and help you to jungle with that like performance, bugs with asynchronous code, and code execution. It can also show you possible approaches to increase frame rate or how to not overload stack to not freeze the user interface.