Browser Engine
All this time I have been thinking how browsers can be so bloated. I tried multiple alternatives, Firefox, Chrome, Midori, Palemoon, Luakit, Surf, Netsurf, Dillo, etc. Some of them had less resource consumption but were still heavy, others were lighter but missed support for a big part of the web.
With this in mind, I started a simple experiment to understand a bit better how a browser works. After searching for a bit I found a nice tutorial for the base of a browser engine by a mozilla engineer, Matt Brubeck.
At the same time I was reading and checking the Clean Architecture of Uncle Bob, I didn't implement it per se, but at least tried to achieve some of the isolation of the main business logic he proposes.
With all of this in mind I started by checking the browser engine main parts.
Main Architecture
Here we can see a diagram of the pipeline created with the modules I grabbed from the tutorial page:
With the main modules of the engine defined I started the implementation.
Implementation
Communication and Configuration
Since I wanted to enforce a bigger level of isolation between each of the logic elements, I started by defining a common way of communication between the multiple parts. I'm used to GNU/Linux and the command line so I selected that each module will output text in json format and pass it to the next module using the pipe mechanism. Something like:
parsers | style | box_layot | block_layout | painting | render
I will use the lua programming language to implement the code and since lua has a structure called table that can represent arrays and dictionaries with any types, I can make a generic converter of a table to json which takes care of all the needed formatting of output and input for the communication. Something like:
Since pipes will be used to pass the results of one module to the other, our delivery mechanism will be the stdin/stdout.
We may want to pass some configuration to the logic, to that we will use command line parameters has the configuration provider
To join all this with the main logic and abstract the logic from the rest we implemented operations which realize the following steps:
- Ask the configuration provider for settings.
- According to the configuration read the input using a delivery mechanism(StdIO) with the specified formatter(JSON/Text)
- Pass the input read data to the defined wanted action
- According to the configuration send the result as output using a delivery mechanism(StdOut) with the specified formatter(JSON/Text)
Here we can see a operation implementation that will activate the html parser while reading regular text from stdin and sending json formatted text to stdout. In this case I provided the input and output configuration with two tables, but I could use the command line configuration provider to fetch them, or get optional configurations and use this as defaults.
local Operation = require "operation.operation"
local Parser = require "html.parser"
local input_config = {
{type = "std"}
}
local output_config = {
{type = "std", format = "json"}
}
local parse_html = function(self, input_data)
local parser = Parser.new(input_data)
return parser:parse()
end
local operation = Operation.new(input_config, output_config, parse_html)
operation:run()
Main Implementation
Here we describe what each module does:
- HTML Parser - Parses HTML source code and produces a tree of DOM nodes.
- CSS Parser - Parses CSS source code and produces a list of styling rules. Here I used the CSS2 standard as base but didn't have most rules implemented. Right now I implemented support for color, margin, padding, borders and sizes in pixels.
- Style Tree - Merges the DOM tree and the styling rules, creating a tree of the DOM nodes with style.
- Box Layout - By using the style tree adds the information about position layout to be used in each node. Right now I implemented it with support for the anonymous and block layout only.
- Block Layout - According to the box layout and style(margin, padding, size) defined for each of the nodes calculates the coordinates were to put each node, resulting in the block tree.
- Painting - Transforms the block tree in a list of draw commands.
After the painting module generates a list of drawing command I processed them using a simple implementation with Love2D. Here we can see a simple html and css file and the interface rendered using love2D.
This was the point I wanted to achieve in order to understand a bit better how browsers work. There is still tons of stuff missing so if you are interested I invite you to continue my experimentation :) .
Repository
If you want to check the code go to the repository
Other
If you want to receive updates when there are new games or development stories join the mailing list