<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Web Browser Engineering Blog]]></title><description><![CDATA[Blog for the Web Browser Engineering book]]></description><link>https://browserbook.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png</url><title>Web Browser Engineering Blog</title><link>https://browserbook.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 05 Jul 2026 11:28:34 GMT</lastBuildDate><atom:link href="https://browserbook.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Pavel Panchekha and Chris Harrelson]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[author@browser.engineering]]></webMaster><itunes:owner><itunes:email><![CDATA[author@browser.engineering]]></itunes:email><itunes:name><![CDATA[Pavel Panchekha]]></itunes:name></itunes:owner><itunes:author><![CDATA[Pavel Panchekha]]></itunes:author><googleplay:owner><![CDATA[author@browser.engineering]]></googleplay:owner><googleplay:email><![CDATA[author@browser.engineering]]></googleplay:email><googleplay:author><![CDATA[Pavel Panchekha]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Japan & Korea]]></title><description><![CDATA[Web Browser Engineering has now come out in Korean and Japanese.]]></description><link>https://browserbook.substack.com/p/japan-and-korea</link><guid isPermaLink="false">https://browserbook.substack.com/p/japan-and-korea</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 13 Mar 2026 16:48:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4VI0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em><a href="https://browser.engineering/">Web Browser Engineering</a></em> has now come out in Korean and Japanese. You can buy it directly from the publishers (<a href="https://www.hanbit.co.kr/store/books/look.php?p_code=B6818199506">Hanbit</a> for Korean, <a href="https://www.oreilly.co.jp/books/9784814401574/">O&#8217;Reilly</a> for Japanese) or from a number of resellers, from <a href="https://www.yes24.com/product/goods/153499985">Yes24</a> to <a href="https://www.amazon.co.jp/Web%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%83%AA%E3%83%B3%E3%82%B0%E2%80%95Chrome%E9%96%8B%E7%99%BA%E8%80%85%E3%81%9F%E3%81%A1%E3%81%8B%E3%82%89%E5%AD%A6%E3%81%B6%E3%80%81%E4%BD%9C%E3%81%A3%E3%81%A6%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6%E3%81%A8Web%E3%81%AE%E4%BB%95%E7%B5%84%E3%81%BF-Pavel-Panchekha/dp/4814401574">Amazon</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4VI0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4VI0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4VI0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg" width="1456" height="922" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:922,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4138830,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://browserbook.substack.com/i/190850739?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4VI0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4VI0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6243565-dc48-4f30-99fe-b04a42f8100d_4458x2822.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The books are really nicely made, with a super cute cover on the Korean one and and some fun animal typography (it&#8217;s O&#8217;Reilly) in the Japanese one:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vobG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vobG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vobG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vobG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vobG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vobG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg" width="1456" height="1093" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1093,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3957662,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://browserbook.substack.com/i/190850739?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vobG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 424w, https://substackcdn.com/image/fetch/$s_!vobG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 848w, https://substackcdn.com/image/fetch/$s_!vobG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!vobG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F452ec7a4-ce63-4d69-a532-ddfeef8c0f8e_4624x3472.jpeg 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The translators also surely did a great job, because they found a lot of small typos and mistakes in the English-language version of the book and were nice enough to file GitHub issues.</p><p>We&#8217;re thrilled to have readers in so many countries and, if you&#8217;re a publisher, we&#8217;re happy to work with you on more translations as well!</p>]]></content:encoded></item><item><title><![CDATA[Text changes to update dependencies]]></title><description><![CDATA[Hello all!]]></description><link>https://browserbook.substack.com/p/text-changes-to-update-dependencies</link><guid isPermaLink="false">https://browserbook.substack.com/p/text-changes-to-update-dependencies</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 05 Dec 2025 22:52:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello all!</p><p>I hope you&#8217;re enjoying <em><a href="https://browser.engineering/">Web Browser Engineering</a></em> and maybe even considering gifting a copy to a friend for the coming holidays. We have a <a href="https://www.hanbit.co.kr/store/books/look.php?p_code=B6818199506">new Korean edition</a> out in case you&#8217;d like one, and there&#8217;s more translations in the works.</p><p>Meanwhile, I did want to mention a few changes we made to the book to keep up with some of our dependencies changing or going obsolete.</p><p><strong>First, Skia.</strong> <a href="https://browser.engineering/visual-effects.html">Chapter 11 (on visual effects)</a> and every later chapter uses the Skia 2D graphics library for drawing page content and the browser UI. Until now, we&#8217;ve specifically required Skia version 87, which is very, very old; the most recent release is milestone 144. So we&#8217;ve updated the text to work with more recent versions.</p><p>The main change is in Chapter 15 <a href="https://browser.engineering/embeds.html#cb9">in the implementation of the CSS </a><code>image-rendering</code><a href="https://browser.engineering/embeds.html#cb9"> property</a>. Basically, in Skia 87, this used an API called <code>FilterQuality</code>, but more recent Skia versions instead use an API called <code>SamplingOptions</code>. It&#8217;s not a big change, and we have <a href="https://browser.engineering/porting.html">porting notes</a> in case you want to keep using Skia 87, but the big benefit is that while <code>FilterQuality</code> was an unstable API, <code>SamplingOptions</code> is stable so it&#8217;s guaranteed to work in future Skia versions as well. This means we can now recommend readers install the current Skia version instead of asking you to install an old, obsolete version.</p><p><strong>Second, </strong><code>playsound</code><strong>.</strong> This was a simple Python library to play audio, which we used in <a href="https://browser.engineering/accessibility.html">Chapter 14 (on accessibility)</a> to implement a really simple screen reader. But unfortunately, it looks like <code>playsound</code> doesn&#8217;t work on some combination of recent Python versions and recent macOS releases, and we&#8217;ve heard some complaints from users on other OSes as well.</p><p>Since browsers don&#8217;t typically include their own screen readers, and since playing audio is actually totally secondary to the main point of the chapter, we&#8217;ve decided to remove <code>playsound</code> from the main chapter text, and just recommend users print the screen reader text to the console. There&#8217;s a <a href="https://browser.engineering/accessibility.html#exercises">new exercise</a> that describes how to build a somewhat more sophisticated screen reader, including support for polite and assertive interruptions for live content.</p><p>As always, thank you all for reading, and do consider gifting <em>Web Browser Engineering</em> this holiday season. The book should be available on all of the typical sites like <a href="https://bookshop.org/p/books/web-browser-engineering-chris-harrelson/21588966">Bookshop.org</a>, <a href="https://www.barnesandnoble.com/w/web-browser-engineering-pavel-panchekha/1146050176">Barnes &amp; Noble</a>, and <a href="https://amzn.to/4hFTkC2">Amazon</a> (referral link). And if you&#8217;ve got some free time over the holidays, consider hacking a bit on your favorite open source web browser!</p>]]></content:encoded></item><item><title><![CDATA[A Note About Book Sales]]></title><description><![CDATA[Hello all!]]></description><link>https://browserbook.substack.com/p/a-note-about-book-sales</link><guid isPermaLink="false">https://browserbook.substack.com/p/a-note-about-book-sales</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 18 Jul 2025 22:19:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello all!</p><p><a href="https://browser.engineering/">Web Browser Engineering</a>, our book about how web browsers work (and how to write your own, from scratch) has been out for about half a year now, and we&#8217;ve just got the first number from the publisher on how many we&#8217;ve sold.</p><p>The initial print run for the book was 75 hardback books and 305 paperbacks. We&#8217;ve now busted through both, having sold 76 hardbacks and 518 paperbacks (and hopefully a few more by the time you get this!), so we&#8217;re now on our second printing!</p><p>As always, thank you all for reading and buying the book. If you&#8217;ve been holding off, or if you have a friend you think would like it, now&#8217;s a good time! The book should be available on all of the typical sites like <a href="https://bookshop.org/p/books/web-browser-engineering-chris-harrelson/21588966">Bookshop.org</a>, <a href="https://www.barnesandnoble.com/w/web-browser-engineering-pavel-panchekha/1146050176">Barnes &amp; Noble</a>, and <a href="https://amzn.to/4hFTkC2">Amazon</a> (referral link).</p>]]></content:encoded></item><item><title><![CDATA[A Thank You to Maintainers]]></title><description><![CDATA[I&#8217;d like to take a little break from advertising Web Browser Engineering (which is now available for purchase from most retailers) to thank the people who made the book possible: the maintainers of the languages and libraries we use in the book, a number of whom have graciously accepted patches, followed up on bug reports, or made releases to make]]></description><link>https://browserbook.substack.com/p/a-thank-you-to-maintainers</link><guid isPermaLink="false">https://browserbook.substack.com/p/a-thank-you-to-maintainers</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 04 Apr 2025 16:07:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I&#8217;d like to take a little break from advertising <em><a href="https://browser.engineering/">Web Browser Engineering</a></em> (which is now available for purchase from most retailers) to thank the people who made the book possible: the maintainers of the languages and libraries we use in the book, a number of whom have graciously accepted patches, followed up on bug reports, or made releases to make <em>Web Browser Engineering</em> better.</p><p><a href="https://github.com/amol-">Alessandro Molina</a> is the main developer behind <a href="https://github.com/amol-/dukpy">DukPy</a>, the Python bindings for the JavaScript interpreter we use in the book. Besides putting out new releases and making sure DukPy is available for modern Python versions, Alessandro very kindly accepted a patch improving one of the error messages we saw readers hitting. The underlying DukTape engine itself is maintained by <a href="https://github.com/svaarala">Sami Vaarala</a>.</p><p><a href="https://github.com/kyamagu">Kota Yamaguchi</a> and <a href="https://github.com/HinTak">Hin-Tak Leung</a> are the maintainers of <a href="https://github.com/kyamagu/skia-python">skia-python</a>, which the book uses for advanced graphics starting in Chapter 11. Maintaining <code>skia-python</code> is especially challenging, because Skia is a fast-moving project with a lot of breaking changes. Both helped determine which Skia version to target in <em>WBE</em>, and Hin-Tak made several special-purpose releases of the chosen version, m87, for newer Python versions. M87 is quite old and just building on modern Python and C++ needs patches, so I&#8217;m especially thankful for his expertise.</p><p><a href="https://github.com/a-hurst">Austin Hurst</a> seems to be the current maintainer of <a href="https://github.com/py-sdl/py-sdl2">PySDL2</a>, which the book uses as the base GUI library from Chapter 11 on. <a href="https://github.com/mcfletch">Mike C. Fletcher</a> maintains the PyOpenGL library, which <em>WBE</em> uses in Chapter 13 to do GPU-accelerated rendering. Both libraries have vast APIs and yet Chris and I had no troubles with either libraries, nor have any of our readers reported issues, which really speaks to their stewardship.</p><p>The playsound and gTTS libraries are used in Chapter 14 to demonstrate a basic screen reader. <a href="https://github.com/TaylorSMarks/playsound/commits/master/">Taylor S. Marks</a> wrote playsound, though I don&#8217;t know if he still maintains it. <a href="https://github.com/pndurette">Pierre Nicolas Durette</a> maintains gTTS, quite actively in fact, as new languages are added to Google&#8217;s TTS service.</p><p>And of course, the Python language and standard library, maintained by the <a href="https://www.python.org/psf-landing/">Python Software Foundation</a>, is what makes <em>WBE</em> possible at all. In particular it is extremely helpful that Python comes with a large and stable standard library, especially the ssl, tkinter, socket, and threading packages. Of course, those packages are in turn built on open-source libraries like <a href="https://core.tcl-lang.org/index.html">Tcl/Tk</a> and <a href="https://openssl-library.org/">OpenSSL</a>.</p><p>So, again, a thank you to all of you!</p><p>The story of the web and the story of open source technology are fundamentally intertwined, as we discuss in the <a href="https://browser.engineering/history.html">History chapter</a>. All browsers today are open source, and part of the reason for this is that they rely on a vast ecosystem of other open source projects: graphics packages, networking code, cryptography libraries, image and video formats, user interfaces libraries, and so so much more.</p><p>Moreover, it&#8217;s the availability of open-source servers (Apache, NGINX), programming languages (Perl, PHP, Python), and operating systems (Linux, BSD) that have enabled the web to grow so large, diverse, and widely used. And these open source projects are often organized using the web! So, to open source in general, without which not only would <em>WBE</em> not be possible, it would not even be worth writing: thank you!</p><p></p>]]></content:encoded></item><item><title><![CDATA[How to buy Web Browser Engineering]]></title><description><![CDATA[Web Browser Engineering is officially available for purchase!]]></description><link>https://browserbook.substack.com/p/how-to-buy-web-browser-engineering</link><guid isPermaLink="false">https://browserbook.substack.com/p/how-to-buy-web-browser-engineering</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Tue, 25 Mar 2025 00:07:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Web Browser Engineering</em> is officially available for purchase! Our publisher, of course, would most prefer you buy it on their website, here:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://global.oup.com/academic/product/web-browser-engineering-9780198913863&quot;,&quot;text&quot;:&quot;Buy from Oxford University Press&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863"><span>Buy from Oxford University Press</span></a></p><p>That said, the book is also available from <strong>plenty of other resellers</strong>. In the US, <a href="https://bookshop.org/p/books/web-browser-engineering-chris-harrelson/21588966">Bookshop.org</a>, <a href="https://www.barnesandnoble.com/w/web-browser-engineering-pavel-panchekha/1146050176">Barnes &amp; Noble</a>, and <a href="https://amzn.to/4hFTkC2">Amazon</a> all sell the book, and popular bookstores in other countries likely carry it as well. We&#8217;ve confirmed that some readers have been able to purchase the book (through Amazon) in Canada, for example, and Amazon currently advertises delivery in about a week.</p><p>If you&#8217;re new to the book, you can preview the full book <a href="https://browser.engineering/">on our website</a> and, if you like what you see, please order a copy. Or, if you&#8217;re already a fan, maybe forward this email to a friend who you think will like the book.</p>]]></content:encoded></item><item><title><![CDATA[WBE is Out in a Week]]></title><description><![CDATA[Hello all!]]></description><link>https://browserbook.substack.com/p/wbe-is-out-in-a-week</link><guid isPermaLink="false">https://browserbook.substack.com/p/wbe-is-out-in-a-week</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Wed, 05 Mar 2025 20:00:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello all! <em><a href="https://browser.engineering/">Web Browser Engineering</a></em> is out in US stores in a week! (At least I think so.) So if you&#8217;ve already pre-ordered, your copy should ship out soon.</p><p>But if you haven&#8217;t ordered a copy, now&#8217;s a good time. The best place to order one is <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863?cc=us&amp;lang=en&amp;">directly from the publisher</a>; the paperback is $50 in the US (plus shipping; &#163;28 in the UK).</p><p><em>Web Browser Engineering</em> teaches you how web browsers work, in the best way possible: by building your own. Some of my personal favorite bits are:</p><ul><li><p>Implementing error-correct HTML parsing in Chapter 4</p></li><li><p>Adding link navigation and multiple tabs in Chapter 7</p></li><li><p>Implementing JavaScript <code>click</code> and <code>submit</code> callbacks in Chapter 9</p></li><li><p>Adding a separate browser and main thread in Chapter 11</p></li><li><p>Using transforms for iframe rendering and hit-testing in Chapter 15</p></li></ul><p>We&#8217;ve written the book to make the code as transparent and simple as possible, and then we&#8217;ve listed fun exercises implementing real web browser features (like custom fonts, CORS, <code>z-index</code>, and <code>:hover</code>) that you&#8217;ll be able to do after reading the book.</p><p>Check out the <em><a href="https://browser.engineering/">Web Browser Engineering website</a></em> to read it online and, if you like what you see, please do <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863?cc=us&amp;lang=en&amp;">order a copy</a>.</p>]]></content:encoded></item><item><title><![CDATA[First Books Arrive]]></title><description><![CDATA[Chris and I got our copies of Web Browser Engineering yesterday:]]></description><link>https://browserbook.substack.com/p/first-books-arrive</link><guid isPermaLink="false">https://browserbook.substack.com/p/first-books-arrive</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Wed, 15 Jan 2025 22:27:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!-0Fm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chris and I got our copies of <em><a href="https://browser.engineering/">Web Browser Engineering</a></em> yesterday:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-0Fm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-0Fm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-0Fm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg" width="1456" height="1934" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1934,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:770985,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-0Fm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-0Fm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ed30a64-0155-47b0-99e9-2230e9c0ccc7_2499x3319.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The book feels great&#8212;lightweight, easy to read, and the cover looks even better than I expected. My understanding is that these will start shipping out to pre-orders sooner rather than later, I think first in the UK and then later in the US and elsewhere.</p><p>If you&#8217;ve already pre-ordered, thank you so much! The demand has been quite a bit more than I expected, and I&#8217;m thrilled to see so many people excited to learn about what goes on inside a web browser. If you haven&#8217;t yet, you can do so <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863?cc=us&amp;lang=en&amp;">on Oxford&#8217;s website</a> or on a variety of other book-buying sites like <a href="https://www.amazon.com/Web-Browser-Engineering-Pavel-Panchekha/dp/0198913869/ref=sr_1_1?crid=1KYKGZ3O3UA3Q&amp;dib=eyJ2IjoiMSJ9.dQD-l3BWxRRJNN0O3LuWDF6Y47te-QA0l8k_ntGz2JOmauleuljkha5KK3y-yCZOYz4OkcHI5To8jmqmwSfREyM1w1b6Lmp8vVnuqzgV8KYAYRBqPSTPf7CnQrfz330OKbo7KTAh1RvdgulRj8IkaEIi4XhyW5CCpCt09Muw0nqpToTp5mCX-OErf_WkK5cRYgStpEOqSO9wk0V9xYuSeW-XE_ZQHh60tN09xHf2wPs.qAhxTzOfEYK9KSPETEIbmCAqlLOLmITDekTaVU-dXz4&amp;dib_tag=se&amp;keywords=web+browser+engineering&amp;qid=1736900295&amp;sprefix=web+browser+engineerin%2Caps%2C149&amp;sr=8-1">Amazon</a>. And if you&#8217;re not sure if you&#8217;ll like it, the whole book is <a href="https://browser.engineering/">available online</a>, for free, and will continue to be.</p><p>Chris and I will now be shutting down the Patreon. The book is finished, and our Patreon supporters were a big part of how it happened. We appreciate the support over multiple years! Thank you Randy Naar, Min Lee, Zachary Tatlock, Jonas Treub, Alexandru Nedel, Adam Gutglick, Swav Rybak, Rishi Chopra, Yuanhang Xie, Shuhei Kagawa, Vitor Roriz, Maia X., Parker Henderson, Tiago Pereira, Liza Daly, Sangyeob Han, YongWoo Jeon, Jess, Martin Minkov, Peter Rushforth, Gowtham K, Ryo Ogawa, and JaviFML.</p>]]></content:encoded></item><item><title><![CDATA[Final Proofs Done!]]></title><description><![CDATA[Hello all&#8212;a quick update.]]></description><link>https://browserbook.substack.com/p/final-proofs-done</link><guid isPermaLink="false">https://browserbook.substack.com/p/final-proofs-done</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 22 Nov 2024 00:58:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello all&#8212;a quick update. We&#8217;ve received a fourth round of proofs from the publisher and it looks good, meaning the book will soon head to the printers and, from there, your homes. (At least&#8212;if you&#8217;ve <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863">preordered</a>. Please do!)</p><p>The proof process focused on a lot of things, like getting all the punctuation in the right place, making sure all the code blocks are correctly indented, and things like that. But one particularly tricky bit was making sure <a href="https://browser.engineering/visual-effects.html">Chapter 11</a>, where we talk about color mixing, makes sense both when the book is printed in black and white and when the book is viewed in full color online. This required modifying some figure captions and adding footnotes, where appropriate, reminding people to check the website for full-color images. The book will come out in print form with no color, a color version on Oxford University Press&#8217;s web page, and as always on <a href="https://browser.engineering">web.browser.engineering</a>.</p><p>As soon as we have a hard shipping date, I&#8217;ll publish an update for y&#8217;all. It&#8217;s been a long journey! Chris and I are extremely excited that soon we&#8217;ll hold <em>Web Browser Engineering</em> in our hands. Hopefully you are too.</p><p>And if you are extra-excited, please do preorder the book, either on the <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863">publisher&#8217;s web page</a> (preferred) or <a href="https://www.amazon.com/Web-Browser-Engineering-Pavel-Panchekha/dp/0198913869/ref=sr_1_1?crid=3JG5BOR4Z5T3L&amp;dib=eyJ2IjoiMSJ9.dQD-l3BWxRRJNN0O3LuWDH3TgLPr7XK_Cllnx8SLlemPNHroGSX8SrvZfDqkLvVEYz4OkcHI5To8jmqmwSfREz8kQUKbhMAXf4YXO1c9hYtmXvWQdsyYdzwLRCWeSdngitLOkVAYE6wGP3FVJR3USUK3tfBMPogWI9OkBUIx2kefpGqa_ENgFAgHl0vLBcbfTxD25lExd2si3Z0e5MTe9a-vcDVOAQQX_6P3IJHsbc0.QBEdpNIUHKO_3KQF2BIr_sg7aQ5XSFRcwStgkM0S0XI&amp;dib_tag=se&amp;keywords=Web+browser+engineering&amp;qid=1732210430&amp;sprefix=web+browser+engineering%2Caps%2C174&amp;sr=8-1">from Amazon</a>.</p>]]></content:encoded></item><item><title><![CDATA[Thank you for the HN front page!]]></title><description><![CDATA[Yesterday Web Browser Engineering hit #1 on the front page of Hacker News, the popular programming news site and stayed there for a full day. Thank you, both long-time readers and new subscribers! Part of the reason Chris & I wrote this book is to get a new generation interested in web browser engineering, and it&#8217;s always a thrill to see that basic thesis validated.]]></description><link>https://browserbook.substack.com/p/thank-you-for-the-hn-front-page</link><guid isPermaLink="false">https://browserbook.substack.com/p/thank-you-for-the-hn-front-page</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Thu, 17 Oct 2024 13:39:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Yesterday <em><a href="https://browser.engineering/">Web Browser Engineering</a></em> hit #1 on the front page of <a href="https://news.ycombinator.com/">Hacker News</a>, the popular programming news site and <a href="https://news.ycombinator.com/item?id=41846780">stayed there for a full day</a>. Thank you, both long-time readers and new subscribers! Part of the reason Chris &amp; I wrote this book is to get a new generation interested in web browser engineering, and it&#8217;s always a thrill to see that basic thesis validated.</p><p>I suppose while I&#8217;m here I might as well relay a funny story. The book&#8217;s website is hosted on a <a href="https://www.digitalocean.com/">Digital Ocean</a> droplet (supported by our wonderful <a href="https://www.patreon.com/browserengineering">Patreon</a>s) for $4/mo. Amazingly, it handled the traffic flood just fine, with CPU usage at around 10&#8211;20% when I checked on it. But our log parser <a href="https://goaccess.io/">GoAccess</a>, which we use to figure out how many visitors the site has, kept getting OOM-killed because the log was too big, so I was keeping track mostly by checking my network usage in the Digital Ocean dashboard.</p><p>Today traffic died down and I finally got the log parser running. It looks like about 22,000 people checked out the book, mostly people who&#8217;d never visited the site before. That&#8217;s a huge number of people getting excited about web browsers! Digging into the data a bit, most visitors of course only looked at the table of contents, but a big fraction started reading the first chapter and a sizable fraction of those presumably finished it and went on to the second.</p><p>So, again, hello new readers and a big thank you to long-time fans for supporting this work over the years!</p><p>Finally, a quick status update&#8212;we&#8217;re waiting for a second and hopefully final proof from the publishers, after which printing can start. I&#8217;m a little unclear on the exact timeline, but we still hope to have books sent out to people who pre-ordered before the end of the year.</p>]]></content:encoded></item><item><title><![CDATA[Pre-order Web Browser Engineering]]></title><description><![CDATA[We just got word from our publisher that you can now pre-order Web Browser Engineering from the Oxford University Press website.]]></description><link>https://browserbook.substack.com/p/pre-order-web-browser-engineering</link><guid isPermaLink="false">https://browserbook.substack.com/p/pre-order-web-browser-engineering</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 19 Jul 2024 16:38:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!a54T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>We just got word from our publisher that you can now <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863">pre-order </a><em><a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863">Web Browser Engineering</a></em> from the Oxford University Press website.</p><p>If you pre-order, you&#8217;ll likely get your copy before the end of the year, perhaps as a present for your wintertime holiday of choice. The book comes in hardback and paperback, beautifully typeset with a lovely cover:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!a54T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!a54T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 424w, https://substackcdn.com/image/fetch/$s_!a54T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 848w, https://substackcdn.com/image/fetch/$s_!a54T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!a54T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!a54T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg" width="261" height="391.1444141689373" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:550,&quot;width&quot;:367,&quot;resizeWidth&quot;:261,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The cover for Web Browser Engineering, from Oxford University Press&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The cover for Web Browser Engineering, from Oxford University Press" title="The cover for Web Browser Engineering, from Oxford University Press" srcset="https://substackcdn.com/image/fetch/$s_!a54T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 424w, https://substackcdn.com/image/fetch/$s_!a54T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 848w, https://substackcdn.com/image/fetch/$s_!a54T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!a54T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd721ac47-b072-4b81-bd04-3198878f0596_367x550.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Besides the general theme of connecting the world through software, the cover has some easter egg references to the web&#8217;s history and major players, from centering the globe on CERN to using Fira Sans for the title.</p><p>To celebrate our progress toward publication, we&#8217;ve also pushed several updates to the website which fix a number of bugs in the text. The most important is probably a fix to iframe scrolling in Chapter 15, where we were failing to trigger a redraw when an iframe scrolled. We&#8217;ve also fixed all of the JavaScript widgets on the site (especially in Safari). The text of the book is also much more consistent in terms of terminology and typography, largely thanks to the work of our amazing copyeditor <a href="https://www.richardhutchinson.uk/">Richard Hutchinson</a>.</p><p>Anyway, if you&#8217;d like to purchase a copy, please do pre-order, which helps promote the book. You can do so from <a href="https://global.oup.com/academic/product/web-browser-engineering-9780198913863">OUP&#8217;s website</a>, or through the numerous links on the book&#8217;s website.</p>]]></content:encoded></item><item><title><![CDATA[Rewriting Chapter 11]]></title><description><![CDATA[A few months ago (actually, quite a few now) Chris and I rewrote Chapter 11 of Web Browser Engineering, on visual effects, quite significantly.]]></description><link>https://browserbook.substack.com/p/rewriting-chapter-11</link><guid isPermaLink="false">https://browserbook.substack.com/p/rewriting-chapter-11</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Thu, 18 Apr 2024 14:50:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A few months ago (actually, quite a few now) Chris and I rewrote<a href="https://browser.engineering/visual-effects.html"> Chapter 11</a> of <em><a href="https://browser.engineering/">Web Browser Engineering</a></em>, on visual effects, quite significantly. The revised chapter focuses around a core theme: surfaces, and why the browser might want several of them. This (belated) changelog describes what we changed and why. The website shows the most recent version.</p><p><strong>Merging in Pixel pseudo-code</strong>: blending modes are kind of complicated, and it&#8217;s helpful to see their mathematical definitions. But of course a web browser doesn&#8217;t actually have those formulas in its code base; they instead live inside whatever 2D graphics library the browser uses, in our case Skia. In earlier drafts there were some sections, then, that had pseudo-code for the blending operations, but these sections stood out from the rest of the chapter. We&#8217;ve now integrated them a bit better and introduce them earlier, where we talk about drawing / blitting, as a way to explain how a surface is represented internally.</p><p><strong>Introducing scrolling earlier</strong>: a simple and compelling use case for compositing is scrolling: rasterizing the whole page to one surface, and then just offsetting that surface during drawing, makes scrolling much, much faster than re-rasterizing on every frame. Making this one of the first things in the chapter, instead of one of the last, justifies the up-front work to switch from Tk to Skia and, I hope, gets the reader asking &#8220;what else can surfaces do&#8221;.</p><p><strong>De-emphasizing atomicity</strong>: applying effects like opacity <em>atomically</em> means correctly grouping effects and applying them in the right order. In a previous draft of this chapter, we first introduced non-atomic effects, and then had to have a bunch of text to convince the reader of the value of atomicity. In the new draft, we take a new approach: introduce RGBA colors (which don&#8217;t need atomicity because they only apply to one drawing command) and then introduce atomicity at the same time we introduce opacity. RGBA makes it easier to explain atomicity, and now we can focus only on the correct approach instead of first introducing an incorrect one.</p><p>As part of this, we also fixed the compositing algorithm in <a href="https://browser.engineering/animations.html">Chapter 13</a> to handle effects atomically. This is a little tricky, and Chris and I debated for a while whether the added complexity is worth it, but ultimately we decided that always handling atomicity correctly made the book simpler&#8212;we wouldn&#8217;t have to explain the effects of doing it wrong.</p><p><strong>Renaming the effects</strong>: In a previous draft of the chapter, more or less all visual effects were applied via a single &#8220;Swiss army knife&#8221; drawing command called <code>SaveLayer</code>. That&#8217;s important for efficiency but the name was opaque and it made the chapter harder to read. We now introduce separate <code>Blend</code> and <code>Opacity</code> operations before merging them later in the chapter when we talk about optimization. I think this makes the chapter a bit easier to follow.</p><p><strong>Removing ClipRR</strong>: In previous drafts, the chapter introduced destination-in blending for clips, but then switches to using Skia&#8217;s native <code>ClipRR</code>. The native operation is faster, but it&#8217;s a bit weird to introduce blending and then not use it for anything significant. So in the latest draft, we remove this optimization from the chapter text. As a result, the book browser now uses blend operations for clipping, which I think better justifies their importance. It also provides another opportunity to talk about blend order and atomicity.</p><p>Anyway, I think the new chapter draft is shorter and clearer. As mentioned, the changes are <a href="https://browser.engineering/visual-effects.html">live on the site</a> and should make it into the <a href="https://browserbook.substack.com/p/publishing-web-browser-engineering">published book</a>. Please do recommend the book to anyone you think might appreciate it, and if you&#8217;re interested in updates, follow <a href="https://browserbook.substack.com/">this blog</a> and <a href="https://indieweb.social/@browserbook">Mastodon</a> (or <a href="https://twitter.com/BrowserBook">Twitter</a>, whatever it might be called) to hear about future updates, and participate in the <a href="https://browserbook.substack.com/p/conclusions#:~:text=participate%20in%20the-,Github%20Discussions,-with%20questions%20and">Github Discussions</a> with questions and answers for the community. And as always, a huge thank you to our readers and our supporters on <a href="https://patreon.com/browserengineering">Patreon</a>.</p>]]></content:encoded></item><item><title><![CDATA[Redoing the browser chrome]]></title><description><![CDATA[Over the past few months, as Chris and I worked to finalize a Web Browser Engineering manuscript for publication, we made several major changes to how the toy browser chrome is drawn.]]></description><link>https://browserbook.substack.com/p/redoing-the-browser-chrome</link><guid isPermaLink="false">https://browserbook.substack.com/p/redoing-the-browser-chrome</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Tue, 23 Jan 2024 15:56:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past few months, as Chris and I worked to finalize a <a href="https://browser.engineering/">Web Browser Engineering</a> manuscript for publication, we made several major changes to how the toy browser chrome is drawn. The changes simplify it a bit and make it more similar to how real web browsers work, at the cost of a bit more conceptual complexity. I want to explain the changes.</p><h2>The old way</h2><p>The browser chrome is a little complicated because it works one way before Chapter 11 and another way in Chapter 11 and onward, though there are similarities. Before Chapter 11, the browser chrome was drawn as part of the <code>Browser</code>&#8217;s <code>draw</code> method by directly invoking methods on the Tk <code>Canvas</code> object that the browser is drawn to. The arguments to those calls were just hard-coded numbers. This had the benefit of conceptual simplicity&#8212;the chrome is drawn &#8220;raw&#8221;, just method calls and constants.</p><p>After Chapter 11 switched to Skia that &#8220;raw&#8221;ness became hard to maintain, because the Skia API is more verbose, so instead the chrome code made calls to wrapper functions like <code>draw_line</code> and similar. One benefit of this old approach was that we didn&#8217;t need to define <code>DrawLine</code> and <code>DrawOutline</code> classes until much later in the book, since when the chrome was first implemented page content only contained rectangles and text.</p><p>But I didn&#8217;t like the <code>draw_line</code> functions&#8212;it felt weird to have a <code>DrawLine</code> class that calls a <code>draw_line</code> function&#8212;and also using &#8220;raw&#8221; APIs meant that the transition to Skia required a lot of quite tedious porting from one API to another. That porting was annoying enough that the book left it to the reader, which made it harder for readers to make it through Chapter 11. Ultimately, the &#8220;raw&#8221; API approach, while it had its benefits, just introduced needless complexity to the Skia port, and that wasn&#8217;t worth it.</p><h2>The new way</h2><p>The new way avoids all of these issues by first painting the browser chrome to a display list, which is then drawn to the <code>Canvas</code> or <code>Surface</code> just like page content. This does mean that we need the <code>DrawLine</code> and <code>DrawOutline</code> paint commands earlier, as soon as they&#8217;re used in the browser chrome, and it also means we have to accept some minor hits to aesthetics, like the back button now using the less-than character instead of a manually painted triangle to represent the back arrow. But using the same paint commands for page content and browser chrome means that nothing major has to be ported to Skia, and the <code>DrawLine</code> command is also useful for cursor drawing in Chapter 8.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>As we were doing this, we made another big change to the browser chrome: we added layout. You see, previously we gave exact pixel coordinates for the corner of every button and text field, and that worked fine because the browser chrome was basically static. But it caused problems across different operating systems, because different operating systems use different fonts of different sizes. The book asked readers to adjust the coordinates for their system, but actually observing some readers made it clear that that was difficult, and most readers weren&#8217;t learning much from doing it.</p><p>So now the browser chrome uses font measurements and metrics to determine the position and size of each browser chrome element. This means it&#8217;ll look right on every operating system from the start, and also reinforce layout reasoning, which is a big part of the book. One downside of this change is that hit testing for the browser chrome used to be trivial, since the size and position of each button and input field was hard-coded. After this change, the layout depends on font metrics, so we chose to compute the browser chrome layout in a layout phase and then retain that layout for later paint and hit-test phases. In fact, the browser chrome ends up structured very similarly to ordinary layout objects,<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> further emphasizing that browser chrome and page content are more alike than not.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a></p><h2>The advantages</h2><p>Basically, this change made Chapter 11, the port to Skia, a lot less laborious, and while it added a lot of conceptual complexity&#8212;a chrome display list, a Chrome class, a hit-testing method&#8212;a lot of that conceptual complexity is already familiar from layout objects and seeing it again might actually help readers review how layout and paint works and why both phases are needed.</p><p>The new changes are <a href="https://browser.engineering/chrome.html">live on the site</a>, so do check them out and let us know (by commenting below or by <a href="http://author@browser.engineering">emailing us</a>) whether you like them.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Before, the text cursor was also drawn &#8220;raw&#8221; up until Chapter 13. (Chapter 13 adds transforms, which require the cursor to be part of the display list so that it can be transformed properly.) Now, the text cursor is part of the display list from its introduction in Chapter 8, because there&#8217;s now already a <code>DrawLine</code> command at that point. This makes the code less buggy and removes a refactor later on.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Ordinary layout objects don&#8217;t have a hit-test method in our toy browser, so that&#8217;s one difference. I do think having each layout object type hit-test itself would be a clean design, but it would add a lot of duplicative code, so we decided against doing that.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>And this is in fact more like how modern browsers work. Chrome, for example, has a <a href="https://source.chromium.org/chromium/chromium/src/+/main:ui/">custom UI library</a> for implementing the browser chrome, much like our browser. Firefox goes a step further and today uses the web renderer directly, though historically it used this thing called XUL which was sort-of the web renderer but sort-of not.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Publishing Web Browser Engineering]]></title><description><![CDATA[Chris & I are really excited to announce that we&#8217;ve signed a contract with Oxford University Press to publish Web Browser Engineering in the US and UK sometime in 2024!]]></description><link>https://browserbook.substack.com/p/publishing-web-browser-engineering</link><guid isPermaLink="false">https://browserbook.substack.com/p/publishing-web-browser-engineering</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Sun, 22 Oct 2023 18:10:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chris &amp; I are really excited to announce that we&#8217;ve signed a contract with Oxford University Press to publish <em>Web Browser Engineering</em> in the US and UK sometime in 2024!</p><p><em>Web Browser Engineering</em> will continue to be available on <a href="https://browser.engineering/">the current website</a>, but Oxford will also be publishing it in book form, and it should ultimately be available from your favorite online and local book stores. We don&#8217;t yet have an exact date, pre-order page, pricing or anything like that, but we&#8217;ll make that all public as soon as we have it.</p><p>We also hope to have translations come out in other languages, though no firm promises on that front.</p><p>More details to come, and as always please do recommend the book to anyone you think might appreciate it, and if you&#8217;re interested in updates, follow <a href="https://browserbook.substack.com/">this blog</a> and <a href="https://indieweb.social/@browserbook">Mastodon</a> (or <a href="https://twitter.com/BrowserBook">Twitter</a>, whatever it might be called) to hear about future updates, and participate in the <a href="https://browserbook.substack.com/p/conclusions#:~:text=participate%20in%20the-,Github%20Discussions,-with%20questions%20and">Github Discussions</a> with questions and answers for the community. And as always, a huge thank you to our readers and our supporters on <a href="https://patreon.com/browserengineering">Patreon</a>.</p>]]></content:encoded></item><item><title><![CDATA[Adding a URL class]]></title><description><![CDATA[Hi all!]]></description><link>https://browserbook.substack.com/p/adding-a-url-class</link><guid isPermaLink="false">https://browserbook.substack.com/p/adding-a-url-class</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Wed, 13 Sep 2023 16:22:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi all! Now that Chris &amp; I have finished all of the chapters in the book, we&#8217;re going back through earlier chapters and trying to fix anything that turned out to be a bad idea. We&#8217;ll blog about these fixes when we can, especially because I think it can be helpful to see what code <em>didn&#8217;t</em>, but <em>could have</em>, made it into the book.</p><h2>How Chapter 1 was written</h2><p>Chapter 1 is pretty old&#8212;it was published in July 2020, just as the world was starting to first emerge from Covid lockdowns, and in fact a month before Chris joined the project! This means it was before we had any real sense of the tone or style of the book. For example, some of the &#8220;Go Further&#8221; blocks in that chapter were pretty strange by the standards of later chapters: a short sentence linking to a standards document, nothing more.</p><p>The presentation was also odd&#8212;it was breezier, less concerned with writing or organizing code. For example, most of the code was written as if <code>https://example.org/index.html</code> was the only URL you might be requesting, and it was up to the reader to actually generalize it to handle multiple URLs. That was confusing, and it was easy to get it wrong (for example, by not updating the <code>Host</code> header). In fact, that style wouldn&#8217;t last even a second chapter, because as the browser gets bigger and more complex, it becomes more and more important to know where each line of code lives and when it is executed.</p><p>So anyway, the point is that Chapter 1 tried out some stylistic ideas that didn&#8217;t quite work, and I was itching to rewrite it anyway.</p><h2>Why one big function</h2><p>Besides style, there was a key technical issues with Chapter 1. As originally published, Chapter 1 basically writes one big function: <code>request</code>.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> The issue with <code>request</code> is that it&#8217;s quite long, which makes it kind of annoying to talk about later in the book, when we need to add features (like cookies). The obvious choice would be to break it up into multiple steps:</p><ul><li><p><code>parse_url</code> to parse a URL and return pieces like the host and path</p></li><li><p><code>request_url</code> to send an HTTP request</p></li><li><p>Maybe <code>parse_response</code> to parse the resulting HTTP response</p></li></ul><p>In fact, this isn&#8217;t hypothetical, because it&#8217;s how I initially wrote this chapter (sadly, pre-<code>git</code>, so lost to the mists of time). However, this design was pretty verbose and over time I merged pieces. For example, while splitting the HTTP request and response is nice, the object passed between the two would be a live socket connection. That seemed dangerous: different functions open and close the socket, and errors can happen in either function. So in the <a href="https://github.com/browserengineering/book/commit/1c72af5ecfb892e2bdbe491c62121df2dac41543#diff-75491c2631c9db750db251fcb38d528dc2cc281128a04523264a8ac1e0906d96">first version of the chapter</a> that I committed, I had already combined the request and response logic into a single <code>request</code> function. So it was <code>request</code> and <code>parse</code>.</p><p>Of course I couldn&#8217;t really call the <code>parse_url</code> function, just <code>parse</code>. Browsers parse lots of things! But even <code>parse_url</code> was awkward! It would return a four-tuple, which I then had to pass to <code>request</code>. Since I didn&#8217;t want to use fancy Python features like <code>*args</code>, that meant every HTTP request would take up two lines (one to <code>parse</code> and unpack into variables, and another to call <code>request</code>). Plus those lines were just long and ugly. So eventually I just combined all of it into one big <code>request</code> function. This worked fine for the first chapter (where I could just walk through each piece of it step by step) and also for the next few, because we don&#8217;t modify the <code>request</code> function until Chapter 8, and because the most complex modifications don&#8217;t happen until Chapter 10.</p><h2>Issues with one big function</h2><p>That said, as we wrote more and more of the book, we started to dislike the one big function. First of all, over time, we changed Chapter 10 to focus more on browser-internal security policies (like CSP) and that meant adding functions like <code>url_origin</code>. These duplicate pieces of the URL parsing logic.</p><p>There was also a sense that much of the parsing code was eventually &#8220;organized&#8221; into classes like <code>CSSParser</code> and <code>HTMLParser</code>, but URL parsing ended up scattered across multiple functions called from multiple classes.</p><p>I initially tried a <code>Networking</code> class that could be used as a namespace for all the URL-focused classes. The issue with that was there wasn&#8217;t any state for the <code>Networking</code> class to represent until Chapter 10 (when cookies are added), which would make it weird to create the class in Chapter 1. (It would have only static methods, something we&#8217;ve avoided in the rest of the book.) And splitting parsing into its own function would reintroduce the issue that its return value was an ugly four-tuple.</p><p>Eventually I hit up a variation that would work: a class to represent URLs.</p><h2>A URL class</h2><p>Upon reconsideration, the theme of the book is browsers, not web protocols like HTTP. So while the first chapter does focus on networking, we need to think about networking from a browser-focused perspective, which to me means a procedure to convert URLs to resources. So the key object to represent is the URL, not networking in abstract. A <code>URL</code> class would fix a lot of issues.</p><p>First, the URL parser could be the constructor for <code>URL</code>. Instead of returning a four-tuple, it would just return a <code>URL</code> object with proper fields. Instead of passing all the fields to <code>request</code>, <code>request</code> could be a method on URLs. In Chapter 10 we need to pass more arguments to request, including a second URL, and distinguishing the URL being requested by making it the object whose request method is called would make that code clearer.</p><p>Things like <code>url_origin</code> and <code>resolve_url</code> could become other methods on <code>URL</code>s. Plus, it would become possible to test URL parsing directly by just constructing a <code>URL</code> and querying its fields, instead of inspecting the HTTP request. (This would help my students, who use the browser unit tests as part of their assignments.)</p><p>The networking chapter could also be refocused on URLs instead of networking in general. This just required moving some paragraphs around, not a full rewrite, but Chris &amp; I felt it made the chapter more concrete. It also means that we can get to code (for URL parsing) right away, which is more in keeping with the rest of the book.</p><h2>Conclusion</h2><p>Anyway, we&#8217;ve now rewritten the first chapter, and the rest of the book, to introduce a <code>URL</code> class. The updated chapter is <a href="https://browser.engineering/http.html">live on the site</a>. We&#8217;ll be making more modifications to the book, and pushing those as we go, with blog posts when we can. And hopefully soon we&#8217;ll be able to share news about an on-paper publication sometime in 2024!</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>It also introduces a <code>show</code> function, but that&#8217;s rapidly obsoleted, and so doesn&#8217;t really affect later chapters.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Conclusions]]></title><description><![CDATA[With Chapter 16 done, it&#8217;s time for Chris and me to wrap up Web Browser Engineering. Of course, web browsers are massive, with tens of millions of lines of code, and if we wanted to we could probably keep writing new chapters forever. But to keep the book finite, we&#8217;ve put many of those ideas into a &#8220;]]></description><link>https://browserbook.substack.com/p/conclusions</link><guid isPermaLink="false">https://browserbook.substack.com/p/conclusions</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Sat, 12 Aug 2023 20:50:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With <a href="https://browserbook.substack.com/p/chapter-16-reusing-previous-computations">Chapter 16</a> done, it&#8217;s time for Chris and me to wrap up <a href="https://browser.engineering/">Web Browser Engineering</a>. Of course, web browsers are massive, with tens of millions of lines of code, and if we wanted to we could probably keep writing new chapters forever. But to keep the book finite, we&#8217;ve put many of those ideas into a &#8220;<a href="https://browser.engineering/skipped.html">What Wasn&#8217;t Covered</a>&#8221; chapter that lists every chapter we thought of writing, and didn&#8217;t. I think it makes a good summary of what else goes into a browser implementation, and why these programs are such behemoths.</p><p>Since we&#8217;re not writing more chapters, we&#8217;re now working on wrapping up the book. You&#8217;ll see edits (including some major ones) to already-published chapters, and also technical changes to the code that we think will make it clearer. A lot of these involve back-porting changes from later chapters to earlier ones, and we&#8217;ll blog about them as they happen.</p><p>We&#8217;re also working on finishing up various related content in the book. We&#8217;ve just released <a href="https://browser.engineering/change.html">a post-script</a>, <a href="https://browser.engineering/glossary.html">a glossary</a>, and a <a href="https://browser.engineering/onepage.html">one-page version of the book</a>. We&#8217;re working on similar things too. That should set us up to publish a physical copy of the book sometime soon&#8212;stay tuned for an announcement.</p><p>Thanks as always to readers, and our supporters on <a href="https://patreon.com/browserengineering">Patreon</a>. Please continue to give us feedback and tell your friends all about it. And follow <a href="https://browserbook.substack.com/">this blog</a> and <a href="https://indieweb.social/@browserbook">Mastodon</a> (or <a href="https://twitter.com/BrowserBook">Twitter</a>, whatever it might be called) to hear about future updates, and participate in the <a href="https://github.com/browserengineering/book/discussions">Github Discussions</a> with questions and answers for the community.</p>]]></content:encoded></item><item><title><![CDATA[Chapter 16: Reusing Previous Computations]]></title><description><![CDATA[Chapter 16, Reusing Previous Computations, is now finished!]]></description><link>https://browserbook.substack.com/p/chapter-16-reusing-previous-computations</link><guid isPermaLink="false">https://browserbook.substack.com/p/chapter-16-reusing-previous-computations</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 14 Jul 2023 21:36:02 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chapter 16, <a href="https://browser.engineering/invalidation.html">Reusing Previous Computations</a>, is now finished! This chapter focuses on invalidation, a technique that browsers use to avoid rendering work and speed up interactions.</p><p>Invalidation is well known to be a nest of bugs, performance issues, and subtle interactions. In fact, it&#8217;s one of Phil Karlton&#8217;s famous <a href="https://www.karlton.org/2017/12/naming-things-hard/">two hardest problems in computer science</a>&#8212;so it&#8217;s no surprise that he worked on Netscape. Accordingly, this chapter is the most complex one that I&#8217;ve written.</p><p>My key goal was to make sure we not only described the basic techniques of validation, but that our implementation was correct or, better yet, obviously correct. Unfortunately, existing browsers actually don&#8217;t reach this bar&#8212;perhaps Chrome&#8217;s recent <a href="https://developer.chrome.com/articles/layoutng/">LayoutNG architecture</a> is the closest&#8212;so this chapter introduces a new abstraction called <code>ProtectedField</code> that, I hope, makes implementing complex invalidation schemes safe and easy.</p><p>Chris and I went through a huge number of variations in the course of writing this chapter. There are many, many ways to implement invalidation&#8212;it&#8217;s just that lots of them are subtly incorrect, or wouldn&#8217;t scale to a real browser. I feel pretty hopeful that we got it right in the end: <code>ProtectedField</code> is relatively easy to use, allows readers to implement invalidation step by step, and still makes it hard to forget a dependency or forget to invalidate something.</p><p>Another challenge throughout this chapter was the need to make a number of small- and medium-sized changes to previous chapters to make Chapter 16 possible. It turns out that over the four years we&#8217;ve been writing this book, we made a few mistakes. But in software engineering and web-published books, mistakes can be fixed, so we did just that in a few cases. If you look carefully at some of the earlier chapters, you might notice the changes.</p><p>This is the last planned technical chapter of the book. Our hope is now to add some nontechnical content, clean up various design issues that have been bugging us, and move toward a complete, finished book sometime soon. More on that when we can!</p><p>Thanks as always to readers, and our supporters on <a href="https://patreon.com/browserengineering">Patreon</a>. Please continue to give us feedback and tell your friends all about it. And follow our <a href="https://browserbook.substack.com/">blog</a> and <a href="https://indieweb.social/@browserbook">Mastodon</a> (or <a href="https://twitter.com/BrowserBook">Twitter</a> if you need it) to hear about future updates, and participate in the <a href="https://github.com/browserengineering/book/discussions">Github Discussions</a> with questions and answers for the community.</p>]]></content:encoded></item><item><title><![CDATA[Changing Computed CSS Values]]></title><description><![CDATA[I recently merged a PR that changes how the WBE browser handles &#8220;computed styles&#8221;.]]></description><link>https://browserbook.substack.com/p/changing-computed-css-values</link><guid isPermaLink="false">https://browserbook.substack.com/p/changing-computed-css-values</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Sat, 03 Jun 2023 23:28:28 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently merged <a href="https://github.com/browserengineering/book/pull/829">a PR</a> that changes how the WBE browser handles &#8220;computed styles&#8221;. This change affects everything back to Chapter 6, so I wanted to explain what computed styles are, how WBE handled them before, and why I changed it.</p><h2>What are computed style values?</h2><p>In CSS, an element has a "computed style" as well as a "specified style". The specified styles are what you get after matching and cascading assigns a value to each CSS property for that element. But computed styles differ from that because they resolve  relative font sizes and ensure properties like padding only have positive lengths.</p><p>I haven&#8217;t worked much with the modern CSS specifications, but in CSS 2, it always felt to me like computed styles were mostly there for resolving percentage <code>font-size</code> values, so that &#8220;em&#8221; units could be converted into &#8220;px&#8221; units during styling.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> Note that most percentage sizes are resolved during layout; it&#8217;s just <code>font-size</code> that&#8217;s special and has to be resolved during styling.</p><h2>How do browsers handle computed styles?</h2><p>Logically, computed styles are separate from specified styles, so computing<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> them is a separate pass, applied after all of the other elements of the cascade have happened and determined the element&#8217;s specified style.</p><p>However, in the WBE browser, the only computed value is <code>font-size</code>: percentage font sizes resolve into pixel values, so we had some flexibility to implement it in a different way. We chose to write a <code>compute_style</code> function, which you&#8217;d call before storing any value in the style dictionary.</p><p>The advantage of this is that the style dictionary only ends up containing computed styles, which is a kind of conceptual simplification. But watching my students work through the style chapter, I realized that this approach created more problems than it solved.</p><h2>What was the problem?</h2><p>First, it wasn&#8217;t clear why you would do something in <code>compute_style</code> instead of in <code>style</code> itself. I suppose the CSS spec does provide the answer (when it defines the differences between computed and specified styles) but readers and students aren&#8217;t going to be intimately familiar with those steps, and the boundary between these functions then looks pretty vague.</p><p>Second, <code>compute_style</code> had a weird API. Apparently it could output <code>None</code> to mean not to store the value at all, which I wanted to use in case someone used an illegal unit for font size. But this was neither explained in the book nor even correctly handled by all of the code. I think I was trying to shove value parsing into this function, too, but conceptually that&#8217;s a different step of the process (it normally happens during parsing) and adding it here just made things confusing.</p><p>Finally, and most importantly, this design got in the way of Chapter 16. In Chapter 16,<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> we need to track dependencies between CSS values, which means mainly working with style fields, not style values. The <code>compute_style</code> API hindered that because it accessed style fields but returned style values. If the API were otherwise great, I could have worked around it, but given that the API was already bad, it seemed ripe for replacement.</p><h2>The new approach</h2><p>Given all of this, I went back and removed the <code>compute_value</code> function, replacing it with an extra &#8220;phase&#8221; of <code>style</code> that runs after all of the cascading is done but before we recurse to style the children. This avoids having an API boundary at all, which avoids all the challenges with designing a good one and also doesn&#8217;t hinder Chapter 16. As a bonus, it&#8217;s a bit more like a real browser.</p><p>I&#8217;ve merged this change to the book and updated the website, so you can check out <a href="https://browser.engineering/styles.html#inherited-styles">Chapter 6</a> to see the new approach and update your browser to follow suit if you like it.</p><p>See you all again soon!</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>There were other cases where computed and specified styles differed, but everything besides <code>em</code> units felt like edge cases which maybe could have been avoided. I think the Stylo name for this step, <a href="https://github.com/servo/servo/blob/0ea942f00f3be1e6e3e46647de30582a0b0ff1ff/components/style/style_adjuster.rs#L28">StyleAdjuster</a>, appropriately signifies that it just adjusts a bunch of stuff according to fairly ad-hoc rules.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>By the way, terrible terminology. If it were called the element&#8217;s &#8220;resolved styles&#8221;, then we could use &#8220;resolving&#8221; as the verb for computing the resolved style. You can&#8217;t use &#8220;computing&#8221; that way, it is too generic.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Which I promise Chris and I are working <em>really</em> hard on and will be ready &#8220;soon&#8221;.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Chapter 15: Supporting Embedded Content]]></title><description><![CDATA[Images, iframes, and scripting]]></description><link>https://browserbook.substack.com/p/chapter-15-supporting-embedded-content</link><guid isPermaLink="false">https://browserbook.substack.com/p/chapter-15-supporting-embedded-content</guid><dc:creator><![CDATA[Chris Harrelson]]></dc:creator><pubDate>Mon, 20 Mar 2023 15:52:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Chapter 15, <a href="https://browser.engineering/embeds.html">Supporting Embedded Content</a>, is now finished! This chapter adds images and iframes, and covers all many related rendering, performance and security questions.</p><p>You might be surprised that iframes can be implemented in just one chapter&#8217;s worth of text and code. That&#8217;s because it builds on all of the powerful technology built up in prior chapters. I really enjoyed the way this chapter tied into almost every previous chapter&#8217;s code, leveraging it to make a complex feature come together quite quickly.</p><p>Images and iframes are almost the last core feature / architectural concept that we want to cover&#8212;we are nearly done with the book! In fact, unless we change plans, this looks like the last chapter with me as lead author. Honestly, I can&#8217;t believe how far this book has come, nor that it&#8217;s almost done. It&#8217;s been quite an adventure.</p><p>Thanks as always to readers, and our supporters on <a href="https://patreon.com/browserengineering">Patreon</a>. Please continue to give us feedback and tell your friends all about it. And follow our <a href="https://browserbook.substack.com/">blog</a> and <a href="https://indieweb.social/@browserbook">Mastodon</a> (or <a href="https://twitter.com/BrowserBook">Twitter</a> if you need it) to hear about future updates, and participate in the <a href="https://github.com/browserengineering/book/discussions">GitHub Discussions</a> with questions and answers for the community.</p>]]></content:encoded></item><item><title><![CDATA[Refactoring BlockLayout]]></title><description><![CDATA[Earlier today, I pushed a major change to Chapter 5 of Web Browser Engineering, unifying the BlockLayout and InlineLayout classes.]]></description><link>https://browserbook.substack.com/p/refactoring-blocklayout</link><guid isPermaLink="false">https://browserbook.substack.com/p/refactoring-blocklayout</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Tue, 10 Jan 2023 20:14:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Earlier today, I pushed a major change to <a href="https://browser.engineering/layout.html">Chapter 5</a> of <a href="https://browser.engineering/">Web Browser Engineering</a>, unifying the <code>BlockLayout</code> and <code>InlineLayout</code> classes. Since we published Chapter 5 <a href="https://browserbook.substack.com/p/chapter-5">almost two years ago</a>, I wanted to explain the change and why Chris and I made it.</p><h1>How Layout Works in WBE</h1><p>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.</p><p>Until this change, the layout tree had two key types of nodes: <code>BlockLayout</code> and <code>InlineLayout</code>. <code>BlockLayout</code> nodes were used for things like <code>&lt;div&gt;</code> or <code>&lt;body&gt;</code>, elements that contain a sequence of vertically stacked blocks, while <code>InlineLayout</code> nodes were used for things like <code>&lt;p&gt;</code> or <code>&lt;h1&gt;</code>, which contain text laid out left to right in lines.</p><p>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.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> BlockLayout and InlineLayout construct their children differently (<code>BlockLayout</code> creates <code>BlockLayout</code>s and <code>InlineLayout</code>s; <code>InlineLayout</code>s create <code>LineLayout</code>s and <code>TextLayout</code>s) but compute their sizes and positions more or less identically.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> 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.</p><p>When we were first writing Chapter 5, it looked like splitting the two classes lead to cleaner code overall, so we went with that.</p><h1>Why Change?</h1><p>First of all, since we wrote Chapter 5, we&#8217;ve written a lot more chapters, and that&#8217;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 <code>BlockLayout</code> and <code>InlineLayout</code>. Typically, those changes have to be duplicated across both classes (ugly!), and it&#8217;s pretty easy to forget change both in the same way, meaning that it&#8217;s easy to end up with rare bugs or to have to fix the same bug twice. That&#8217;s frustrating.</p><p>Second, I&#8217;ve <a href="https://browser.engineering/classes.html">taught the material in the book</a> a couple of times, and I&#8217;ve found that it&#8217;s hard for students to keep in mind the distinction between <code>BlockLayout</code> and <code>InlineLayout</code>. It&#8217;s not that it&#8217;s hard to differentiate between paragraphs of text and a section containing paragraphs; it&#8217;s just that a paragraph is still a block, so if you&#8217;re implementing, say, background images, it seems natural to do that in <code>BlockLayout</code>, even if your test page uses a paragraph. That leads to false starts and confusions. It doesn&#8217;t help, by the way, that CSS has the concept of <code>display: block</code> and <code>display: inline</code>, and those <em>don&#8217;t</em> correspond to <code>BlockLayout</code> versus <code>InlineLayout</code> (the book doesn&#8217;t implement the <code>display</code> property).</p><p>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  <code>InlineLayout</code> and <code>BlockLayout</code>: it is a node&#8217;s <em>parent</em> that creates the <code>InlineLayout</code> or <code>BlockLayout</code>, so the node&#8217;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&#8217;s already going to prove a very complicated chapter, and would be avoided entirely by unifying the <code>InlineLayout</code> and <code>BlockLayout</code> classes.</p><h1>Making the Change</h1><p>I know many readers are following along with the book, implementing their own web browsers and <a href="https://browserbook.substack.com/p/shuheis-browser">sometimes even going way beyond</a> the book. It&#8217;s up to you whether you want to update your browser with the unified <code>BlockLayout</code>, but you may want to, and if you do it&#8217;ll probably be easier to incorporate Chapter 16 when it is eventually published.</p><p>I think the change won&#8217;t be too bad to incorporate. I recommend doing it in a few steps.</p><p>First, track down where <code>InlineLayout</code>s are created in your code. Most likely, they are <em>only</em> created by <code>BlockLayout</code>&#8217;s layout method, inside the tree creation <code>for</code> loop. Remove that code; once we unify <code>BlockLayout</code> and <code>InlineLayout</code>, that <code>for</code> loop will only create <code>BlockLayout</code>s.</p><p>Second, copy all the <code>InlineLayout</code> methods into <code>BlockLayout</code>. That includes <code>recurse</code>, <code>text</code>, <code>input</code>, <code>new_line</code>, and <code>get_font</code>.</p><p>Third, verify that the <code>paint</code> method and the constructor are the same for <code>InlineLayout</code> or <code>BlockLayout</code>. It&#8217;s possible that they&#8217;re different&#8212;maybe only <code>InlineLayout</code> has backgrounds, or only <code>BlockLayout</code> supports visual effects. In that case, you&#8217;ll want to merge the two <code>paint</code> methods. It should be possible to support all the paint features that either supports.</p><p>At this point the only thing left unmerged should be the <code>layout</code> methods of the two classes. These two methods should have a lot of duplication&#8212;they should compute sizes and positions in the same way, and both should recursively call <code>layout</code> on their children&#8212;but they will have different tree creation code. You&#8217;ll want to use a conditional to unify the tree creation code, like this:</p><pre><code>class BlockLayout:
    def layout(self, zoom):
        # ...
        mode = layout_mode(self.node)
        if mode == "block":
            # BlockLayout tree creation code
        else:
            # InlineLayout tree creation code
        # ...</code></pre><p>The <code>BlockLayout</code> tree creation code here is a <code>for</code> loop, while the <code>InlineLayout</code> tree creation code calls <code>new_line</code> followed by <code>recurse</code>.</p><p>Once you&#8217;ve made these changes, verify that your browser works, that <code>InlineLayout</code> has no references in your code, and that all its methods have been copied into <code>BlockLayout</code>, and then delete the <code>InlineLayout</code> class.</p><h1>Future Changes</h1><p>As much as I would love to be &#8220;done&#8221; with the already-published chapters of WBE, we&#8217;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&#8217;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&#8217;ve pushed the changes without announcement, but if we need any more major changes we&#8217;ll put up a blog post.</p><p>But don&#8217;t let these changes keep you from hacking away on your own browser. One of the goals of this book is to give <em>you</em> ownership of a web browser. If you don&#8217;t like our changes, it&#8217;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&#8217;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!</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Both happen in the <code>layout</code> method in WBE. In a real browser, these two steps are often distinct&#8212;there may even be some intermediate steps&#8212;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.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>In Chapter 5, the computation of height is actually different for <code>BlockLayout</code> and <code>InlineLayout</code>, but by Chapter 7, when <code>LineLayout</code> is introduced, they become identical.</p></div></div>]]></content:encoded></item><item><title><![CDATA[JavaScript in JavaScript]]></title><description><![CDATA[The ongoing project to run the Web Browser Engineering browser in your browser has already involved writing a Python-to-JS compiler and writing a mock networking library using async/await.]]></description><link>https://browserbook.substack.com/p/javascript-in-javascript</link><guid isPermaLink="false">https://browserbook.substack.com/p/javascript-in-javascript</guid><dc:creator><![CDATA[Pavel Panchekha]]></dc:creator><pubDate>Fri, 04 Nov 2022 20:17:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BqrY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde84bcf9-1437-4bea-8250-c84406fe8842_1213x1213.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The <a href="https://browserbook.substack.com/p/why-widgets">ongoing project</a> to run the <a href="https://browser.engineering/">Web Browser Engineering</a> browser in <em>your</em> browser has already involved writing a <a href="https://browserbook.substack.com/p/compiling-python-to-js">Python-to-JS compiler</a> and writing a <a href="https://browserbook.substack.com/p/python-networking-from-javascript">mock networking library</a> using async/await. That was enough to get Chapters 1&#8211;8 working.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> But for <a href="https://browser.engineering/scripts.html">Chapter 9</a> and <a href="https://browser.engineering/security.html">10</a>, we needed to run JavaScript, and that was more of a challenge than expected.</p><h2>Not Eval</h2><p>Basically, this should be easy&#8212;&#8220;how do I run JavaScript in a web browser&#8221;&#8212;that&#8217;s not hard, that&#8217;s what browsers do! JavaScript even has an <code>eval</code> function that runs JavaScript source code! It&#8217;s the Python version of the browser that needs additional libraries to run JavaScript.</p><p>But it turned out to be harder than expected. Running JavaScript isn&#8217;t hard, but isolating it from the rest of the browser is.</p><p>This gets a little confusing, so remember that there&#8217;s two browsers here: a <em>host</em> browser, which is Chrome or Firefox or whatever you&#8217;re running, and a <em>virtual  </em>browser which is the compiled-to-JavaScript version of the WBE browser. We want to run JavaScript in the virtual browser by downloading a virtual script; set up a virtual execution environment by defining virtual constants like <code>document</code> and <code>Node</code>; and then run the virtual JavaScript in that virtual environment to modify the virtual page. But if I run the virtual JavaScript with <code>eval</code>, it&#8217;ll attempt to access the host <code>document</code> and <code>Node</code> variables and modify the host browser&#8217;s host page.</p><p>We <em>can</em> get around this using the JavaScript <code>with</code> command or the even weirder distinction between <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#description">direct and indirect </a><code>eval</code><a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-2" href="#footnote-2" target="_self">2</a> to make the virtual JavaScript run inside a scope, potentially one where host definitions of <code>document</code> and <code>Node</code> are shadowed by the virtual definitions. But unless we shadow <em>everything</em> the host defines (and we won&#8217;t, browsers are constantly adding <em>new</em> host definitions) the virtual script can probably &#8220;escape&#8221; the scope and start mucking with global state. Not good.</p><h2>Isolating with Web Workers</h2><p>Luckily, JavaScript <em>does</em> provide a workable isolation mechanism in the form of Web Workers. Web Workers are basically share-nothing threads for JavaScript; each Web Worker runs in an isolated environment and has no access to the environment that created it. That sounded perfect for my use case.</p><p>I set up a simple Web Worker based JavaScript execution library like so. First, when you create a <code>JSInterpreter</code>, I create a new Web Worker:</p><pre><code>class JSInterpreter {
    constructor() {
        this.worker = new Worker("/widgets/dukpy.js");
        this.worker.onmessage = this.onmessage.bind(this);
        this.promise_stack = [];
    }
}
</code></pre><p>To run a script in the interpreter, we just send it over with <code>postMessage:</code></p><pre><code>class JSInterpreter {
    evaljs(code, replacements) {
        return new Promise((resolve, reject) =&gt; {
            this.promise_stack.push(resolve);
            this.worker.postMessage({
                "type": "eval",
                "body": code,
                "bindings": replacements,
            });
        });
    }
}
</code></pre><p>I send over the source code we want executed and then return a <code>Promise</code>. That <code>Promise</code> will resolve when the virtual script finishes executing and wants to return a value; for that reason I save its <code>resolve</code> function for later.</p><p>On the WebWorker side, when we receive this message we just run <code>eval</code>:</p><pre><code>addEventListener("message", (e) =&gt; {
    switch (e.data.type) {
    case "eval":
        dukpy = e.data.bindings;
        let val = eval?.(e.data.body);
        if (val instanceof Function) val = null;
        $$POSTMESSAGE({"type": "return", "data": val});
        break;
    }
});
</code></pre><p>Basically, we call <code>eval</code> on the <code>body</code> of the message, using the <em>indirect </em><code>eval</code> style for reasons that I remember losing an hour to but I don&#8217;t remember the details of.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-3" href="#footnote-3" target="_self">3</a> Then we send that return value<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-4" href="#footnote-4" target="_self">4</a> back to the virtual browser with <code>postMessage</code>, where the weird name is to avoid a script overwriting <code>postMessage</code> and making everything break.</p><p>Finally, back in the virtual browser, we receive this message and resolve the promise:</p><pre><code>class JSInterpreter {
    async onmessage(e) {
        switch (e.data.type) {
        case "return":
            this.promise_stack.pop()(e.data.data);
            break;
        }
    }
}</code></pre><p>This basically works, and lets me execute JavaScript in a Web Worker and receive the results.</p><h2>The DOM</h2><p>But the virtual browser running virtual scripts is just one half of JavaScript support. The other half is virtual scripts calling into the virtual browser. Specifically, we want virtual scripts to be able to call browser APIs like <code>querySelectorAll</code> or <code>innerHTML</code>.</p><p>The challenge here is pretty mundane: these APIs are synchronous, in that you call them without using <code>await</code> and expect results in the form of values, not <code>Promise</code>s. We can send an API call request to the virtual browser no problem, using <code>postMessage</code>. But inside a Web Worker, we can&#8217;t receive data from the main script (which runs the virtual browser) except by receiving a <code>postMessage</code> back, and that requires us to first finish executing the current script. We need some form of synchronous cross-thread communication, and JavaScript mostly doesn&#8217;t provide that.</p><p>Mostly! It turns out there are at least two support methods for communicating across Web Workers without using messages: <code>localStorage</code> and <code>SharedArrayBuffer</code>s.</p><p>I haven&#8217;t tried the <code>localStorage</code> route, and it seems janky, so let&#8217;s talk about <code>SharedArrayBuffer</code>. This feature was originally added to JavaScript to facilitate stuff like WebAssembly and WebGL, so it&#8217;s kind of obscure. Basically, you can allocate a <code>SharedArrayBuffer</code> and send it, via <code>postMessage</code>, to a Web Worker. Then both the main script and the Worker can modify it, and those modifications are immediately visible in both threads. Moreover, the <code>Atomics</code> set of functions allows various forms of locking, blocking, and atomically modifying the shared buffer.</p><p>This is perfect for making calls from virtual scripts to the virtual browser. The virtual script can send a <code>postMessage</code> describing the API call it wants to make, and then wait on a prearranged <code>SharedArrayBuffer</code> for the return value. In code, this looks like this:</p><pre><code>function call_python() {
    let args = Array.from(arguments);
    let fn = args.shift();
    $$POSTMESSAGE({
        "type": "call",
        "fn": fn,
        "args": args,
    });

    Atomics.wait($$FLAGARRAY, 0, 0);

    let len = $$FLAGARRAY[0];
    Atomics.store($$FLAGARRAY, 0, 0);
    if (len &gt; 0) {
        let buffer = new Uint8Array(len);
        for (let i = 0; i &lt; buffer.length; i++) {
            buffer[i] = $$READARRAY[i];
        }
        let result = JSON.parse(new TextDecoder().decode(buffer));
        return result;
    }
}</code></pre><p>There&#8217;s a lot of code here, but it&#8217;s ultimately not that bad. The first block of code sends a API call request to the virtual browser. The second block, with an <code>Atomics.wait</code> instruction, blocks the virtual script until a pre-arranged &#8220;flag array&#8221; is changed. Once it is, a length is read from the flag array, and then that many bytes are read from a pre-arranged &#8220;read array&#8221; which are decoded from JSON to be the return value of the API call.</p><p>(Recall that in the book&#8217;s Python version of all of this, API requests like <code>querySelectorAll</code> just return a list of integer handles, which are then wrapped on the JavaScript side into <code>Node</code> objects, so JSON is more than rich enough to represent all the data we need to pass back and forth.)</p><p>On the virtual browser side, things are similarly not <em>too</em> bad: when the virtual browser receives a <code>call</code> message, it calls the requested function with the requested arguments, encodes the result to JSON, and stores it in the read buffer before updating the flag buffer:</p><pre><code>class JSInterpreter {
    async onmessage(e) {
        switch (e.data.type) {
        case "call":
            let fn = this.function_table[e.data.fn]
            let res = await fn.call(window, ... e.data.args);

            let json_result = JSON.stringify(res);
            let bytes = new TextEncoder().encode(json_result);
            if (bytes.length &gt; this.write_buffer.length)
                throw new JSExecutionError(e.data.fn);

            for (let i = 0; i &lt; bytes.length; i++) {
                Atomics.store(this.write_buffer, i, bytes[i]);
            }

            Atomics.store(this.flag_buffer, 0, bytes.length);
            Atomics.notify(this.flag_buffer, 0);
            break;
        }
    }
}</code></pre><p>Here there are four steps. First, run the requested function. Next, encode it to JSON and check that there&#8217;s enough space in the buffer to store it all.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-5" href="#footnote-5" target="_self">5</a> Finally, store the result to the read buffer<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-6" href="#footnote-6" target="_self">6</a> and then unblock the waiting Worker with <code>Atomics.notify</code>.</p><p>By the way, in response to an API call, the virtual browser might execute more JavaScript&#8212;which is why the promises used when returning have to be a stack.</p><h2>Security</h2><p>So after a few hours of debugging this all worked, and things were basically good&#8212;except for the fact that every browser turns off <code>SharedArrayBuffer</code> for security! This is part of the fallout from <a href="https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)">Spectre</a>, a class of vulnerability where very accurate timing measurements can reveal information about other processes executing on the same CPU. The current state of things is that you must choose between <code>SharedArrayBuffer</code> and <code>iframe</code>s.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-7" href="#footnote-7" target="_self">7</a></p><p>The Web Browser Engineering book uses <code>iframe</code>s for a bunch of stuff, but most prominently it uses them to embed the JavaScript browser into the book. So we can&#8217;t turn off <code>iframe</code>s. But we need <code>SharedArrayBuffer</code> to run JavaScript in the embedded browser. A dilemma.</p><p>For now, we&#8217;ve gone with a pretty limited solution. The JavaScript-enabled virtual browser isn&#8217;t embedded into the book pages. Instead, there&#8217;s a link to a separate page containing only the virtual browser. On that page, <code>iframe</code>s aren&#8217;t used and <code>SharedArrayBuffer</code> is enabled instead.</p><p>Frankly, this isn&#8217;t an ideal solution, so one of the things on my todo list is to investigate using <code>localStorage</code> for synchronous communication between threads instead. If anyone has done that, I&#8217;d love to hear more (how long does it take? do you just busy wait?). Otherwise, uhh, please don&#8217;t use anything described in this post in production. It&#8217;s definitely a bad idea. But do enjoy the JavaScript-enabled browsers linked from <a href="https://browser.engineering/scripts.html">Chapters 9</a> and <a href="https://browser.engineering/security.html">10</a>!</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Perhaps I&#8217;ll write about the mock Tkinter implementation another time.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-2" href="#footnote-anchor-2" class="footnote-number" contenteditable="false" target="_self">2</a><div class="footnote-content"><p>Honestly, one of the ugliest features of the web platform that I&#8217;m aware of.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-3" href="#footnote-anchor-3" class="footnote-number" contenteditable="false" target="_self">3</a><div class="footnote-content"><p>Basically I need to make sure that if one <code>eval</code> message defines a constant, another <code>eval</code> message can use it, and for that I need the definition to occur in the global scope, not the local scope of this one execution of the <code>message</code> handler. </p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-4" href="#footnote-anchor-4" class="footnote-number" contenteditable="false" target="_self">4</a><div class="footnote-content"><p>You might be wondering why I test if the return value is a function. The only situation in our browser, and in real browsers mostly, where the return value of <code>eval</code> is examined, is the <code>true</code>/<code>false</code> return value of an event handler, which determines whether the default behavior is run. But functions would be returned accidentally if the last statement in a script were a function definition, so here I filter that out.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-5" href="#footnote-anchor-5" class="footnote-number" contenteditable="false" target="_self">5</a><div class="footnote-content"><p>The shared read/write array has some fixed length, so it&#8217;s possible the return value overflows that, in which case this code throws an error. Given how limited the book&#8217;s browser&#8217;s capabilities are, I&#8217;m not too worried about this.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-6" href="#footnote-anchor-6" class="footnote-number" contenteditable="false" target="_self">6</a><div class="footnote-content"><p>I think you probably don&#8217;t need to use <code>Atomics.store</code> for each byte here, but I didn&#8217;t want to learn about the <code>SharedArrayBuffer</code> memory model and performance is not a concern.</p></div></div><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-7" href="#footnote-anchor-7" class="footnote-number" contenteditable="false" target="_self">7</a><div class="footnote-content"><p>You make the choice via the <code>Cross-Origin-Opener-Policy</code> and <code>Cross-Origin-Embedder-Policy</code> HTTP headers. There&#8217;s some work ongoing to make these policies a little looser, but right now it&#8217;s pretty much a strict choice between <code>SharedArrayBuffer</code> and <code>iframe</code>s.</p></div></div>]]></content:encoded></item></channel></rss>