Web Browser Anatomy
Tags: browser, browserengine, javascript, javascriptengine
Published: 2021-05-05 09:00:35


Web Browser is a big and sophisticated application, built from multiple components. It obligated to satisfy different boring standards, to facilitate developers with stable contracts. You might know these contracts as HTML, CSS, and JavaScript.

Any valid code or markup will be recognized and processed by one of the browser modules. The browser glues together all its modules with the Browser Object Model (BOM) API, aka [Web API](https://developer.mozilla.org/en-US/docs/Web/API). This is something that empowers JavaScript to operate on HTML and CSS. We will get back to it in more detail later.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7j929vsc42e4dlb2rjn.jpg)

A good example of Web API is [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or its predecessor the [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest), both are used to communicate with a remote server over HTTP. Yet another useful tool from Web API toolbox is the [File System API](https://developer.mozilla.org/en-US/docs/Web/API/File_and_Directory_Entries_API), allowing communication with the underlying file system.

## The Browser Engine

### Rendering

Let's slightly touch the browser rendering process to get ourselves familiar with the main actors.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mklwiueebfgism82g0m8.png)

Once the browser receives HTTP Response with HTML file, it'll try to break it down into a _Document Object Model_ ([DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)) Tree, download if necessary CSS and break it into _CSS Object Model_ ([CSSOM](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model)) Tree.

After receiving following index.html

```html
<!DOCTYPE html>
<html>
  <head>
    <meta name="Val likes potatoes" content="width=device-width, initial-scale=1.0">
    <link href="/css/example.css" rel="stylesheet">
  </head>
  <body>
    <div>I like <span>potatoes</span></div>
  </body>
</html>
```

And download referenced example.css

```css
body {
  font-size: 5vw;
} 
span {
  color: brown;
}
```

As an intermediate step browser will produce these two trees 🌳🌳

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k8kpzzso9c1ryfhw41v3.png)

After it is done, it'll try to merge those trees and finalize results in another tree, known as a _Render-Tree_. This tree represents all nodes needed to be rendered, basically everything that have to be shown on the screen.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k013wf956hrk2n51vp8f.png)

The last two steps in the rendering process are _Layout_ and _Paint_. _Layout_ computes the exact position and size of each object, calculates the geometry based on the _Render-Tree_. Ultimately _Paint_ step receives the _Final Render-Tree_ and renders the pixels to the screen.

The _Web API_, we spoke about before, provide a mechanism for _DOM_ and _CSSOM_ manipulation. Which reflects on a render-tree. Hence what you see on the screen.

### Notes

_Browser Engine_ provides an API for interaction and state-mutation.  
It is important to note, that these APIs controlled and implemented by the browser. They might be accessed and consumed through the JavaScript code. However, nor JavaScript neither JavaScript Engine has any direct relation to it.

There's no generic browser platform, each browser comes with its own Browser Engine, e.g. Trident for IE, [Blink](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/) for Chrome, [Gecko](https://github.com/mozilla/gecko-dev) for Firefox, etc.  
So next time you find a dodgy styling behavior in IE, you know whom to blame 😈.

## The JavaScript Engine

The JavaScript code you've written, have never ever been executed in its original shape... 🙀

### Code processing

As we know, the first thing that hits the browser is HTML file which presumably contains a JavaScript script reference. The Browser engine starts producing the forest of trees and at the same time [_HTML Parser_ finds the](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element) `<script ...>` tag. Eventually, code is downloaded (alternatively pulled from the cache or service worker) and passed to the JavaScript Engine as a UTF-16 byte stream. The entrance point to the JavaScript Engine is a _Byte-Stream Decoder_.  
`0076 0061 0072 0020 0078 0020 003d 0020 0033 003b` stream got decoded and became something like `var x = 3;`. Next decoder searches for familiar pieces, pre-defined tokens. `var` is [one of the tokens](https://www-archive.mozilla.org/js/language/js20-1999-02-18/tokens).

Now is a time for another tree 🌳. _Parser_ and _Pre-Parser_ take all the tokens from the _Byte-Stream Decoder_, analyze them and produces _Abstract Syntax Tree_ (AST). _The Parser_ has a higher priority, it determines and processes the hot path, which is needed immediately to make the page work, _Pre-Parser_ is processing rest, including behavior that will be triggered by user interactions or with some other events.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptmz3a1d5omqioaf0zwz.png)

So far we have a tree representation of our code (AST), which we need to compile into machine code. The process of compilation and optimization takes place in _the compiler pipeline_. The pipeline highly depends on the JavaScript engine, thus absolutely different for all browsers.  
The most straight-forward approach to produce machine code is to produce an intermediate byte-code first with a help of the interpreter, this code can be observed and analyzed during execution, optimized and re-compiled several times into the machine code. Machine code is processor-specific, therefore optimization is done with respect to processor type and architecture.

Machine code is kina final product and ends processing cycle, however, through page lifetime, the optimization process can force re-compilation to produce a more efficient version of it.

Whenever JavaScript standard, aka ECMA Script, comes up with a new version or things like WebAssembly, asm.js appear, the browser engine compiling pipeline is the place where the most of work is have to be done.

### V8 Compilation Pipeline

V8 is Google's JavaScript engine, it is fast and powerful. V8 usage is not limited to the Chrome browser. NodeJS is using it as a part of its runtime. And recently released Microsoft Edge replaced its own implementation in favor of Google V8. And it is not only replacement Microsoft Edge did, but it also found a place for Google's Browser Engine Blink.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n86b1b2j6sh6l8q1z4lo.jpg)

Let's zoom in into the compilation pipeline and identify main components.

The compilation pipeline consists of two main pieces working side by side together and constantly interfering with each other. The interpreter called [Ignition](https://docs.google.com/document/d/11T2CRex9hXxoJwbYqVQ32yIPMh0uouUZLdyrtmMoL44/edit?usp=sharing) and [TurboFan](https://docs.google.com/document/d/1l-oZOW3uU4kSAHccaMuUMl_RCwuQC526s0hcNVeAM1E/edit) JIT compiler.

_The_ _TurboFan_ is optimizing compiler built on top of the ["Sea of nodes" graph concept](https://darksi.de/d.sea-of-nodes/). It came as a replacement for long-living _Crankshaft_. _The Crankshaft_ was good, but didn't scale well to fully support modern JavaScript (`try-catch`, `for-of`, `async/await,` etc). It was defaulting to de-optimization (performance cliffs, de-optimization loops). And it was tightly coupled to _the Full-Codegen_. Check out diagrams below 👇. _The Full-Codegen_ was also a compiler (yup, two compilers working together), comparatively fast and simple. It kicked in first and produced machine code directly, so the browser had something to execute while _Crankshaft_ was doing its job.

_The_ _Ignition_ interpreter that eventually squeezed out the _Full-Codegen_ compiler and reduced memory usage, parsing overhead and provided the ability to significantly reduce pipeline complexity. _Ignition_ is  compiling to a concise byte-code, rather than machine code, since it is more concise it allows more eager compilation and it makes byte-code a source of truth for optimization and de-optimization

Today V8 Compilation Pipeline looks like this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ow0ld6j3042wkfxj6yq.png)

This is the final product we have as of 2017. Through the years JavaScript was quickly evolving, becoming more complicated and feature-rich language, and so did V8.

In its first iteration back in 2008 it looked like this.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3h7o0n42qbvf2pmv62m.png)

After major re-factoring in 2010, with the introduction of mentioned previously _Crankshaft_ and _Full-Codegen_, the compile pipeline looked as following.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bqbq93uepnr5ojpsaszl.png)

Time was passing, JavaScript became more complicated and demanding, so did the compiler pipeline. Throwback to 2014.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0oj2ez1mcjophzaalx60.png)

Finally, in 2016 V8 Code Compilation Pipeline became this...

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p0aks78zvnii289txvc2.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8osqtlvxzvdksv4y8vp3.jpeg)

In 2017 V8 team threw away the Baseline layer and Crankshaft. And sculpt the pipeline how we know it today (2020).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ak7gg51x0zaurj47rt5g.png)

### Runtime

Now let's focus on the code execution runtime concept. This concept is shared across all browsers with JavaScript support and any other JavaScript Runtime environment, such as NodeJS.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kx6h76vzd5a3eqkz21m.png)

The execution of code in JavaScript is limited to a single thread. The execution sequence and memory allocation is controlled by the JavaScript Engine. Objects are allocated in large memory regions called _Heap_, execution sequence controlled by _Stack of Frames_ or just _Stack_. _The Stack_ is directly correlated with the single-threaded nature of JavaScript (one stack === one thread). By the way, whatever you see in the exception stack trace, comes directly from the Stack of Frames. Stack has a limit and you can easily exploit touch it with infinite recursion, just saying 🙃.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0e0w25dbci4whj29z3nl.png)

One thread means blocking behavior, for that reason JavaScript has a concurrency model built-in, callbacks, promises, asynchronous operations, etc.  
The implementation is quite different from Java, C++ or C#. It is built on top of the infinite message loop, _the Event Loop_. Each message consists of the event and the callback function. _The Event Loop_ polls oldest messages from _the Queue_. Message becomes a Frame and Frame transitioned to _the Stack_. This process occurs only when _Stack_ is empty. _The Event Loop_ constantly monitors _the_ _Stack_ for that reason.  
Whenever an event happens and there is an event handler associated with the event, the message is added to _the Queue_. The association is stored in the _Event Table_, which also pushes messages into _the_ _Queue_.

Do you remember WebAPI? WebAPI exposes a large set of pre-defined hooks for callback function registration, such as `onClick` and `setInterval`. DOM interactions, Fetch API, Geo-location API and many more, all these APIs provide event-to-handler mappings compatible with _Event Table_.

"Don't try this at home" section again😈. With the help of the Queue and WebAPI, we can make infinite recursion work on the level above avoiding stack overflow exploit. All we need is to re-route functions to the Queue, instead of Stack. Wrap your recursive function call with `setInterval(..., 0);` and there you have it. The message goes to the Queue, instead of Stack. And it will be executed until the browser will terminate the process

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5pkmbqcn4allm9w38zo.gif)

## The End

All modern web browsers are different, they might or might not have different Browser and JavaScript engines. They have different internals and provide a slightly different experience. In order to stay compatible, they have to provide consistent behavior and implement all required standards.

A web browser is an extremely sophisticated evolving orchestra of modules, each and every module functioning independently. They have different responsibilities and peruse unrelated aims. However, as an experienced conductor, the web browser makes all the parts work together.

Knowing environment internals is useful, having a good understanding will help to improve and boost performance in the critical parts of the front-end application and overall rendering process. More optimized code or markup ➡️less work for browser ➡️faster and more responsive web site.