foreword
This article is a translation of Onion 's " inside look at modern web browser " series by Mario Kosaka . The translation here does not refer to literal translation, but to express what the author wants to express based on personal understanding, and will try to add some relevant content to help everyone understand better.
input to the compositing thread
This article is the last in a four-part series exploring the inner workings of Chrome. In the previous article, we explored the process of rendering pages by the browser and learned a little about the composition thread . In this article, we will take a look at the composition thread ( compositor) to ensure a smooth user experience.
Input events from the browser's point of view
When you hear "input events," you probably only think of the user typing in a text box or clicking on a page, but from the browser's point of view, an input actually means something from Any gestures made by the user. So user 滚动页面
, 触碰屏幕
and 移动鼠标
and other operations can be regarded as input events from the user.
The browser process is the first place to receive this event when the user performs some gesture such as touching the screen. However, the browser process can only know where the user's gestures occur but not how to deal with them, because the content in the tab (tab) is responsible for the rendering process of the page. So the browser process will send the event type (eg touchstart
) and the coordinates (coordinates) to the rendering process. In order to properly handle this event, the renderer process will find the event's target object (target) and run the event-bound listener function (listener).
Figure 1: Click events are routed from the browser process to the renderer process
The composition thread receives the input event
In the previous article, we looked at how the compositing thread ensures a smooth scrolling experience by merging layers that are already rasterized on the page. If the current page does not have any event listeners for user events, the composition thread can create a new composition frame to respond to the event without the participation of the main thread at all. But what if the page has some event listeners? How does the synthetic thread determine whether the event needs to be routed to the main thread for processing?
Figure 2: Viewport hovering over page layer
Understanding non-fast scrollable regions - non-fast scrollable region
Because the JavaScript script of the page is run in the main thread (main thread), when a page is composed, the composition thread will mark those areas of the page where event listeners are registered as "non-fast scrolling areas" (Non- fast Scrollable Region). Knowing this information, when a user event occurs in these areas, the composition thread sends the input event to the main thread for processing. If the input event does not occur in a non-fast scrolling area, the compositing thread does not require the participation of the main thread to compose a new frame.
Figure 3: Schematic diagram of a user event occurring in a non-fast scrolling area
Be careful when you write event listeners
A common pattern in web development is event delegation. Since the event will bubble, you can bind an event listener function to the top-level element as the event delegate of all its child elements, so that the events of the child nodes can be uniformly handled by the top-level element. So you may have seen or written code similar to the following:
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
})
Only one event listener can serve all elements, which is quite affordable at first glance. However, if you look at this code from the browser's point of view, you will find that after binding the event listener to the body element above, the entire page is actually marked as a non-fast scrolling area, which means that even if you Some areas of the page don't care if there is user input at all. When a user input event occurs, the composition thread will notify the main thread every time and will wait for the main thread to finish processing it before working. So in this case the compositing thread loses its ability to provide a smooth user experience (smooth scrolling ability).
Figure 4: Schematic diagram of event processing of the page when the entire page is a non-fast scrolling area
To mitigate this from happening, you can pass the passive:true
option to the event listener. This option tells the browser that you still want to listen for events on the main thread, but the compositing thread can also continue compositing new frames.
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
passive:false
, the browser will know if it has called preventDefault
after executing the callback function, if not called preventDefault
, then it will execute the default behavior, that is, scrolling. Causes scrolling is not smooth.
passive:true
is to tell the browser not to call preventDefault
, and the browser can execute the scroll directly without considering the callback function. At this time, even if you call preventDefault
in the callback function, it will not take effect.
为了避免这一问题,大部分浏览器( Safari
Internet Explorer
除外) Window
、 Document
Document.body
上的wheel
、 mousewheel
、 touchstart
touchmove
事件的passive
更改is true
. This way, the event listener can not cancel the event , nor prevent the page from rendering when the user scrolls.
Still want to remind everyone, when you don't need to call preventDefault
, monitor scroll
or touchmove
, set passive
to true
preventDefault and browser
For a scroll event mousewheel
(non-standard) / touch
(mobile event) / wheel
(standard), the browser needs to call his The callback function, and then go to perform the default behavior (in this case, scrolling). However, it takes time to execute the callback function. If a time-consuming loop is placed in the callback function, it will wait for the loop to finish before scrolling. The page performance is that the page will scroll after a period of time after sliding the wheel. There will be Noticeable delay.
In addition, since executing the callback function requires the main thread of the browser, when the main thread is busy performing other tasks, it will be too late to execute the callback function, and the page will also experience a sense of delay.
Improve scrolling performance with passive
Find the event target
When the composition thread sends an input event to the main thread, the first thing the main thread does is to 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 out which object is depicted on the x and y coordinates of the input event.
Figure 5: The main thread traverses the drawing record to determine which object is at the x,y coordinates
Minimize the number of events sent to the main thread
In the previous article we said that the display refresh rate is usually 60 times a second and that we can achieve smooth animation of the page by keeping the execution frequency of the JavaScript code in line with the screen refresh rate. For user input, the touch screen generally triggers 60 to 120 click events per second, while the mouse generally triggers 100 events per second, so the trigger frequency of input events is actually much higher than the refresh rate of our screen.
If a continuously fired event such as touchmove
is sent to the main thread 120 times per second, because the screen refresh rate is relatively slow, it may trigger excessive click tests and execution of JavaScript code .
Figure 6: Events overwhelm the screen refresh timeline, causing the page to freeze
为了最大程度地减少对主线程的过多调用,Chrome会合并连续事件( wheel
, mousewheel
, mousemove
, pointermove
, touchmove
), and delay scheduling until the next requestAnimationFrame
.
Figure 7: The same event axis as before, but this time the events are merged and dispatched lazily
keydown
, keyup
, mouseup
, mousedown
, touchstart
touchend
Events that occur relatively infrequently are dispatched to the main thread immediately.
Use getCoalesecedEvents to get intra-frame events
For most web applications, merging 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, Merging events may make the lines drawn on the page less smooth and continuous. In this case you can use mouse events getCoalescedEvents
to get the details of the events being synthesized.
Figure 8: Smooth touch gestures on the left, less continuous gestures after event synthesis on the right
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.
}
});
Next step
In this series of articles, we explored the inner workings of the browser using the Chrome browser as an example. If you've never wondered why DevTools recommends you use the passive:true
option in the event listener or write the async
attribute in the script tag, I hope this series of articles will give you Some reasons why browsers need this information to provide a faster and smoother user experience.
Learn how to measure performance
Performance tuning may vary from site to site, and you'll want to measure your site's performance and determine the best solution for improving your site's performance. You can check out some of the tutorials from the Chrome DevTools team to learn how you can measure your site's performance .
Add Feature Policy to your site
If you want to go a step further, you can check out Feature Policy , a new web platform feature that can provide some protection for your application to behave and prevent you from making mistakes when you build your project. For example, if you want to ensure that your application code does not block the parsing of pages, you can run your application in the synchronius scripts policy. The specific method is to set sync-script
to 'none', so that JavaScript code that blocks page parsing will be prohibited from executing. The advantage of this is that your code doesn't block the parsing of the page, and the browser doesn't have to worry about the parser pausing.
Summarize
The above is all the content related to the browser architecture and operating principle. When we develop web applications in the future, we should not only consider the elegance of the code, but also think more about how the browser parses and runs our code. , so as to provide users with a better user experience.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。