preface
This article is my translation of Mario Kosaka’s inside Look at Modern Web Browser series. Translation here does not mean literal translation, but to express the meaning of the author based on personal understanding, and try to add some relevant content to help people better understand.
Input to the composite thread
This article is the last in a four-part series exploring the inner workings of Chrome. In the last article, we looked at how browsers render pages and learned a bit about compositor threads. In this article, we look at what compositor threads do to ensure a smooth user experience as users enter content on a web page.
View input events from the browser’s perspective
When you hear “input events,” you probably think of the user typing something into a text box or clicking on a page, but from the browser’s perspective, input really means something like a gesture from the user. So scrolling, touching the screen and moving the mouse can all be viewed as input events from the user.
When a user makes a gesture such as touching a screen, the browser process is the first place to receive the event. However, the browser process only knows where the user’s gesture is occurring, not how to process it, because the contents of the TAB are the responsibility of the render process. So the browser process sends the type of the event (such as TouchStart) along with coordinates (coordinates) to the renderer. To handle the event correctly, the renderer finds the event’s target and runs a listener bound to the event.
Click events are routed from the browser process to the renderer process
The synthetic thread receives an input event
In the last article, we looked at how composition threads ensure a smooth scroll experience by merging the rasterized layers of the page. If the current page does not have any event listeners for user events, the composite thread can create a new composite frame in response to the event without the involvement of the main thread. But what if the page has event listeners? How does the synthetic thread determine if this event needs to be routed to the main thread?
Understand the non-fast scrollable region
Because the JavaScript of a page runs in the main thread, when a page is synthesized, the synthesized thread marks the regions of the page where event listeners are registered as “non-fast Scrollable regions.” Knowing this information, when a user event occurs in these areas, the composite thread sends the input event to the main thread for processing. If the input event does not occur in a non-fast scrolling region, the composition thread synthesizes a new frame without the involvement of the main thread.
The non-fast scroll area has a sketch of when user events occur
Be careful when you write event listeners
A common pattern in Web development is Event Delegation. Since events can bubble, you can bind the top-level element with an event listener to act as an event delegate for all its children, so that the node’s events can be handled uniformly by the top-level element. So you’ve probably seen or written something like this:
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
})
Copy the code
All elements can be served with a single event listener, which at first glance seems economical. But if you go to have a look at the code from the perspective of the browser, you will find the above to the body element binding after the event listener is actually the entire page is marked as a non fast scroll area, which means that certain areas of the page even if you don’t care if there are any user input, when user input events occur, The composite thread tells the main thread each time and waits for it to finish processing. In this case, the composite threads lose their ability to provide smooth scrolling ability.
An event handling diagram of a page when the entire page is a non-fast scrolling area
To mitigate this, you can pass the passive: true option to the event listener. This option tells the browser that you are still listening for events in the main thread, but the compositing thread can continue compositing new frames.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
Copy the code
Find the target object of the event
When a synthetic thread sends an input event to the main thread, the first thing the main thread does is find the target of the event through a hit test. The specific hit test process is to traverse the paint records generated in the rendering pipeline to find which object is depicted on the X and Y coordinates of the input event.
The main thread iterates through the drawing record to determine which object is on the x and y coordinates
Minimize the number of events sent to the main thread
In the last article we talked about how the display refresh rate is usually 60 times a second and how we can achieve a smooth animation on a page by keeping the rate of JavaScript code execution consistent with the rate of screen refresh. Touch screens typically trigger between 60 and 120 clicks per second for user input, while mice typically trigger 100 clicks per second, so input events are triggered much more often than our screens refresh.
If continuously triggered events such as TouchMove are sent to the main thread 120 times per second, it can trigger excessive click testing and JavaScript code execution because the screen is relatively slow to refresh.
Events flooded the screen refresh timeline, causing the page to lag
To minimize excessive calls to the main thread, Chrome merges successive events (such as wheel, MouseWheel, Mousemove, Pointermove, TouchMove) and delays scheduling until the next requestAnimationFrame.
Same axis of events as before, but this time events are merged and delayed
Any relatively infrequent events such as keyDown, KeyUp, Mouseup, MouseDown, TouchStart, and Touchend are immediately sent to the main thread.
Use getCoalesecedEvents to get intra-frame events
For most Web applications, merge events should be enough to provide a good user experience, however, if you are building an application that draws based on the user’s TouchMove coordinates, merge events may cause the lines on the page to be less smooth and continuous. In this case, you can use the getCoalescedEvents mouse event to get the details of the synthesized event.
On the left is a smooth touch gesture, and on the right is a less continuous gesture after composing the event
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.}});Copy the code
The next step
In this series of articles, we explore the inner workings of Chrome as an example. If you’ve never before wondered why DevTools recommends you use the passive: True option in event listeners or write async properties in script tags, I hope this series of articles has given you some reason why browsers need this information to provide a faster and smoother user experience.
Learn how to measure performance
Performance adjustments may vary from site to site, so it’s up to you to measure your site’s performance and determine what works best for your site. You can check out some tutorials from the Chrome DevTools team to learn how to measure the performance of your site.
Add a Feature Policy to your site
If you want to take this one step further, you can check out Feature Policy, a new Web platform Feature that provides some protection when you build a project to make your application behave in certain ways and prevent you from making mistakes. For example, if you want to make sure that your application code doesn’t block parsing of your page, you can run your application in the Synchronius scripts policy. To do this, set sync-script to ‘None’ so that JavaScript code that blocks page parsing is disabled. This has the advantage of preventing your code from blocking the parsing of the page, and the browser doesn’t have to worry about the parser stopping.
conclusion
This is all about the browser architecture and operating principles. When we develop web applications in the future, we should not only consider the elegance of the code, but also think about how the browser parses and runs our code, so as to provide a better user experience.
Keep an eye on my technical updates
I am the green onion of the attack, pay attention to me and I progress together into a full stack engineer on my own!
A peek into the modern browser Architecture (Part 4)
Follow my personal official account to get my latest technical updates!