How Blink works
bit.ly/how-blink-works
Author: haraken@
Last update: 2018 Aug 14
Status: PUBLIC

Working on Blink is not easy. It’s not easy for new Blink developers because there are a lot of Blink-specific concepts and coding conventions that have been introduced to implement a very fast rendering engine. It’s not easy even for experienced Blink developers because Blink is huge and extremely sensitive to performance, memory and security.

This document aims at providing a 10k foot overview of “how Blink works”, which I hope will help Blink developers get familiar with the architecture quickly:

  • The document is NOT a thorough tutorial of Blink’s detailed architectures and coding rules (which are likely to change and be outdated). Rather the document concisely describes Blink’s fundamentals that are not likely to change in the short term and points out resources you can read if you want to learn more.
  • The document does NOT explain specific features (e.g., ServiceWorkers, editing). Rather the document explains fundamental features used by a broad range of the code base (e.g., memory management, V8 APIs).

For more general information about Blink’s development, see the Chromium wiki page.
What Blink does
Process / thread architecture
Processes
Threads
Initialization of Blink
Directory structure
Content public APIs and Blink public APIs
Directory structure and dependencies
WTF
Memory management
Task scheduling
Page, Frame, Document, DOMWindow etc
Concepts
OOPIF
Detached Frame / Document
Web IDL bindings
V8 and Blink
Isolate, Context, World
V8 APIs
V8 wrappers
Rendering pipeline
Questions?

What Blink does

Blink is a rendering engine of the web platform. Roughly speaking, Blink implements everything that renders content inside a browser tab:

  • Implement the specs of the web platform (e.g., HTML standard), including DOM, CSS and Web IDL
  • Embed V8 and run JavaScript
  • Request resources from the underlying network stack
  • Build DOM trees
  • Calculate style and layout
  • Embed Chrome Compositor and draw graphics

Blink is embedded by many customers such as Chromium, Android WebView and Opera via content public APIs.

Blink运行机制概览 - 图1

From the code base perspective, “Blink” normally means //third_party/blink/. From the project perspective, “Blink” normally means projects that implement web platform features. Code that implements web platform features span //third_party/blink/, //content/renderer/, //content/browser/ and other places.

Process / thread architecture

Processes

Chromium has a multi-process architecture. Chromium has one browser process and N sandboxed renderer processes. Blink runs in a renderer process.

How many renderer processes are created? For security reasons, it is important to isolate memory address regions between cross-site documents (this is called Site Isolation). Conceptually each renderer process should be dedicated to at most one site. Realistically, however, it’s sometimes too heavy to limit each renderer process to a single site when users open too many tabs or the device does not have enough RAM. Then a renderer process may be shared by multiple iframes or tabs loaded from different sites. This means that iframes in one tab may be hosted by different renderer processes and that iframes in different tabs may be hosted by the same renderer process. There is no 1:1 mapping between renderer processes, iframes and tabs.

Given that a renderer process runs in a sandbox, Blink needs to ask the browser process to dispatch system calls (e.g., file access, play audio) and access user profile data (e.g., cookie, passwords). This browser-renderer process communication is realized by Mojo. (Note: In the past we were using Chromium IPC and a bunch of places are still using it. However, it’s deprecated and uses Mojo under the hood.) On the Chromium side, Servicification is ongoing and abstracting the browser process as a set of “service”s. From the Blink perspective, Blink can just use Mojo to interact with the services and the browser process.

Blink运行机制概览 - 图2

If you want to learn more:

  • Multi-process Architecture
  • Mojo programming in Blink: platform/mojo/MojoProgrammingInBlink.md

    Threads

    How many threads are created in a renderer process?

Blink has one main thread, N worker threads and a couple of internal threads.

Almost all important things happen on the main thread. All JavaScript (except workers), DOM, CSS, style and layout calculations run on the main thread. Blink is highly optimized to maximize the performance of the main thread, assuming the mostly single-threaded architecture.

Blink may create multiple worker threads to run Web Workers, ServiceWorker and Worklets.

Blink and V8 may create a couple of internal threads to handle webaudio, database, GC etc.

For cross-thread communications, you have to use message passing using PostTask APIs. Shared memory programming is discouraged except a couple of places that really need to use it for performance reasons. This is why you don’t see many MutexLocks in the Blink code base.

Blink运行机制概览 - 图3

If you want to learn more:

On the other hand, Blink is never finalized; i.e., the renderer process is forcibly exited without being cleaned up. One reason is performance. The other reason is in general it’s really hard to clean up everything in the renderer process in a gracefully ordered manner (and it’s not worth the effort).

Directory structure

Content public APIs and Blink public APIs

Content public APIs are the API layer that enables embedders to embed the rendering engine. Content public APIs must be carefully maintained because they are exposed to embedders.

Blink public APIs are the API layer that exposes functionalities from //third_party/blink/ to Chromium. This API layer is just historical artifact inherited from WebKit. In the WebKit era, Chromium and Safari shared the implementation of WebKit, so the API layer was needed to expose functionalities from WebKit to Chromium and Safari. Now that Chromium is the only embedder of //third_party/blink/, the API layer does not make sense. We’re actively decreasing # of Blink public APIs by moving web-platform code from Chromium to Blink (the project is called Onion Soup).

Blink运行机制概览 - 图4

Directory structure and dependencies

//third_party/blink/ has the following directories. See this document for a more detailed definition of these directories:

  • platform/
    • A collection of lower level features of Blink that are factored out of a monolithic core/. e.g., geometry and graphics utils.
  • core/ and modules/
    • The implementation of all web-platform features defined in the specs. core/ implements features tightly coupled with DOM. modules/ implements more self-contained features. e.g. webaudio, indexeddb.
  • bindings/core/ and bindings/modules/
    • Conceptually bindings/core/ is part of core/, and bindings/modules/ is part of modules/. Files that heavily use V8 APIs are put in bindings/{core,modules}.
  • controller/
    • A set of high-level libraries that use core/ and modules/. e.g., devtools front-end.

Dependencies flow in the following order:

  • Chromium => controller/ => modules/ and bindings/modules/ => core/ and bindings/core/ => platform/ => low-level primitives such as //base, //v8 and //cc

Blink carefully maintains the list of low-level primitives exposed to //third_party/blink/.

If you want to learn more:

  • Directory structure and dependencies: blink/renderer/README.md

    WTF

    WTF is a “Blink-specific base” library and located at platform/wtf/. We are trying to unify coding primitives between Chromium and Blink as much as possible, so WTF should be small. This library is needed because there are a number of types, containers and macros that really need to be optimized for Blink’s workload and Oilpan (Blink GC). If types are defined in WTF, Blink has to use the WTF types instead of types defined in //base or std libraries. The most popular ones are vectors, hashsets, hashmaps and strings. Blink should use WTF::Vector, WTF::HashSet, WTF::HashMap, WTF::String and WTF::AtomicString instead of std::vector, std::set, std::map and std::string.

If you want to learn more:

You can allocate an object on PartitionAlloc’s heap by using USING_FAST_MALLOC():

  1. class SomeObject {
  2. USING_FAST_MALLOC(SomeObject);
  3. static std::unique_ptr<SomeObject> Create() {
  4. return std::make_unique<SomeObject>(); // Allocated on PartitionAlloc's heap.
  5. }
  6. };

The lifetime of objects allocated by PartitionAlloc should be managed by scoped_refptr<> or std::unique_ptr<>. It is strongly discouraged to manage the lifetime manually. Manual delete is banned in Blink.

You can allocate an object on Oilpan’s heap by using GarbageCollected:

  1. class SomeObject : public GarbageCollected<SomeObject> {
  2. static SomeObject* Create() {
  3. return new SomeObject; // Allocated on Oilpan's heap.
  4. }
  5. };

The lifetime of objects allocated by Oilpan is automatically managed by garbage collection. You have to use special pointers (e.g., Member<>, Persistent<>) to hold objects on Oilpan’s heap. See this API reference to get familiar with programming restrictions about Oilpan. The most important restriction is that you are not allowed to touch any other Oilpan’s object in a destructor of Oilpan’s object (because the destruction order is not guaranteed).

If you use neither USING_FAST_MALLOC() nor GarbageCollected, objects are allocated on system malloc’s heap. This is strongly discouraged in Blink. All Blink objects should be allocated by PartitionAlloc or Oilpan, as follows:

  • Use Oilpan by default.
  • Use PartitionAlloc only when 1) the lifetime of the object is very clear and std::unique_ptr<> or scoped_refptr<> is enough, 2) allocating the object on Oilpan introduces a lot of complexity or 3) allocating the object on Oilpan introduces a lot of unnecessary pressure to the garbage collection runtime.

Regardless of whether you use PartitionAlloc or Oilpan, you have to be really careful not to create dangling pointers (Note: raw pointers are strongly discouraged) or memory leaks.

If you want to learn more:

All tasks in a renderer process should be posted to Blink Scheduler with proper task types, like this:

  1. // Post a task to frame's scheduler with a task type of kNetworking
  2. frame->GetTaskRunner(TaskType::kNetworking)->PostTask(..., WTF::Bind(&Function));

Blink Scheduler maintains multiple task queues and smartly prioritizes tasks to maximize user-perceived performance. It is important to specify proper task types to let Blink Scheduler schedule the tasks correctly and smartly.

If you want to learn more:

  • How to post tasks: third_party/blink/renderer/platform/scheduler/TaskSchedulingInBlink.md

    Page, Frame, Document, DOMWindow etc

    Concepts

    Page, Frame, Document, ExecutionContext and DOMWindow are the following concepts:

  • A Page corresponds to a concept of a tab (if OOPIF explained below is not enabled). Each renderer process may contain multiple tabs.

  • A Frame corresponds to a concept of a frame (the main frame or an iframe). Each Page may contain one or more Frames that are arranged in a tree hierarchy.
  • A DOMWindow corresponds to a window object in JavaScript. Each Frame has one DOMWindow.
  • A Document corresponds to a window.document object in JavaScript. Each Frame has one Document.
  • An ExecutionContext is a concept that abstracts a Document (for the main thread) and a WorkerGlobalScope (for a worker thread).

Renderer process : Page = 1 : N.

Page : Frame = 1 : M.

Frame : DOMWindow : Document (or ExecutionContext) = 1 : 1 : 1 at any point in time, but the mapping may change over time. For example, consider the following code:

  1. iframe.contentWindow.location.href = "https://example.com";

In this case, a new DOMWindow and a new Document are created for https://example.com. However, the Frame may be reused.

(Note: Precisely speaking, there are some cases where a new Document is created but the DOMWindow and the Frame are reused. There are even more complex cases.)

If you want to learn more:

  • core/frame/FrameLifecycle.md

    Out-of-Process iframes (OOPIF)

    Site Isolation makes things more secure but even more complex. :) The idea of Site Isolation is to create one renderer process per site. (A site is a page’s registrable domain + 1 label, and its URL scheme. For example, https://mail.example.com and https://chat.example.com are in the same site, but https://noodles.com and https://pumpkins.com are not.) If a Page contains one cross-site iframe, the Page may be hosted by two renderer processes. Consider the following page:
    1. <!-- https://example.com -->
    2. <body>
    3. <iframe src="https://example2.com"></iframe>
    4. </body>

The main frame and the