Web Browser Engineering Blog

Share this post

Refactoring BlockLayout

browserbook.substack.com

Refactoring BlockLayout

Pavel Panchekha
Jan 10
4
Share this post

Refactoring BlockLayout

browserbook.substack.com

Earlier today, I pushed a major change to Chapter 5 of Web Browser Engineering, unifying the BlockLayout and InlineLayout classes. Since we published Chapter 5 almost two years ago, I wanted to explain the change and why Chris and I made it.

How Layout Works in WBE

Chapter 5 is about browser layout: building the layout tree, computing sizes and positions of the layout nodes, and using that information to paint the web page to the screen.

Until this change, the layout tree had two key types of nodes: BlockLayout and InlineLayout. BlockLayout nodes were used for things like <div> or <body>, elements that contain a sequence of vertically stacked blocks, while InlineLayout nodes were used for things like <p> or <h1>, which contain text laid out left to right in lines.

There are basically two key things a layout node needs to be able to do: construct its children, and compute its own size and position.

1
BlockLayout and InlineLayout construct their children differently (BlockLayout creates BlockLayouts and InlineLayouts; InlineLayouts create LineLayouts and TextLayouts) but compute their sizes and positions more or less identically.
2
So it was kind of a judgement call whether these should be one class, thereby sharing the size and position computation but with ugly conditionals for the tree construction, or two classes, avoiding the conditionals at the cost of some duplication.

When we were first writing Chapter 5, it looked like splitting the two classes lead to cleaner code overall, so we went with that.

Why Change?

First of all, since we wrote Chapter 5, we’ve written a lot more chapters, and that’s made the costs and duplication versus conditionals much clearer. Chapters 11 and 13 about the graphics pipeline, for example, required lots of changes to painting for BlockLayout and InlineLayout. Typically, those changes have to be duplicated across both classes (ugly!), and it’s pretty easy to forget change both in the same way, meaning that it’s easy to end up with rare bugs or to have to fix the same bug twice. That’s frustrating.

Second, I’ve taught the material in the book a couple of times, and I’ve found that it’s hard for students to keep in mind the distinction between BlockLayout and InlineLayout. It’s not that it’s hard to differentiate between paragraphs of text and a section containing paragraphs; it’s just that a paragraph is still a block, so if you’re implementing, say, background images, it seems natural to do that in BlockLayout, even if your test page uses a paragraph. That leads to false starts and confusions. It doesn’t help, by the way, that CSS has the concept of display: block and display: inline, and those don’t correspond to BlockLayout versus InlineLayout (the book doesn’t implement the display property).

Third, and ultimately what was decisive, is the ongoing work on invalidation in Chapter 16. Without writing a whole chapter here on what invalidation is and how it works, one important step is caching the layout tree instead of recreating it every time the browser does layout. This requires only re-creating a layout node if its dependencies change. But this creates a problem with the split between InlineLayout and BlockLayout: it is a node’s parent that creates the InlineLayout or BlockLayout, so the node’s parent needs to keep track of changes to its children to recreate them as the correct layout node type. This was very hard to explain, in what’s already going to prove a very complicated chapter, and would be avoided entirely by unifying the InlineLayout and BlockLayout classes.

Making the Change

I know many readers are following along with the book, implementing their own web browsers and sometimes even going way beyond the book. It’s up to you whether you want to update your browser with the unified BlockLayout, but you may want to, and if you do it’ll probably be easier to incorporate Chapter 16 when it is eventually published.

I think the change won’t be too bad to incorporate. I recommend doing it in a few steps.

First, track down where InlineLayouts are created in your code. Most likely, they are only created by BlockLayout’s layout method, inside the tree creation for loop. Remove that code; once we unify BlockLayout and InlineLayout, that for loop will only create BlockLayouts.

Second, copy all the InlineLayout methods into BlockLayout. That includes recurse, text, input, new_line, and get_font.

Third, verify that the paint method and the constructor are the same for InlineLayout or BlockLayout. It’s possible that they’re different—maybe only InlineLayout has backgrounds, or only BlockLayout supports visual effects. In that case, you’ll want to merge the two paint methods. It should be possible to support all the paint features that either supports.

At this point the only thing left unmerged should be the layout methods of the two classes. These two methods should have a lot of duplication—they should compute sizes and positions in the same way, and both should recursively call layout on their children—but they will have different tree creation code. You’ll want to use a conditional to unify the tree creation code, like this:

class BlockLayout:
    def layout(self, zoom):
        # ...
        mode = layout_mode(self.node)
        if mode == "block":
            # BlockLayout tree creation code
        else:
            # InlineLayout tree creation code
        # ...

The BlockLayout tree creation code here is a for loop, while the InlineLayout tree creation code calls new_line followed by recurse.

Once you’ve made these changes, verify that your browser works, that InlineLayout has no references in your code, and that all its methods have been copied into BlockLayout, and then delete the InlineLayout class.

Future Changes

As much as I would love to be “done” with the already-published chapters of WBE, we’ll probably need to continue pushing fixes or changing the code of earlier chapters as we work on later ones. Once we reach the end of the book, we’ll probably do a clean-up pass as well. So there are probably more book-wide changes coming up. So far those changes have been minor (changing something from a local variable to a field, for example) and we’ve pushed the changes without announcement, but if we need any more major changes we’ll put up a blog post.

But don’t let these changes keep you from hacking away on your own browser. One of the goals of this book is to give you ownership of a web browser. If you don’t like our changes, it’s your browser and your code! Or you may well have found your own workarounds to these problems, or written enough code on the old abstractions that it’s hard to port. That, too, is part of working on web browsers, and as good a reason as any to skip the updates. Happy hacking!

1

Both happen in the layout method in WBE. In a real browser, these two steps are often distinct—there may even be some intermediate steps—but I decided against making them separate steps in WBE because it would require going into the details of multi-phase layout when handling line breaking.

2

In Chapter 5, the computation of height is actually different for BlockLayout and InlineLayout, but by Chapter 7, when LineLayout is introduced, they become identical.

Share this post

Refactoring BlockLayout

browserbook.substack.com
Comments
TopNewCommunity

No posts

Ready for more?

© 2023 Pavel Panchekha and Chris Harrelson
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing