1

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).

img
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?

tutieshi_480x300_7s
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.

img
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).

img
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});

image-20220913145605406.png
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除外) WindowDocument Document.body上的wheelmousewheeltouchstart 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.

img
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 .

img
Figure 6: Events overwhelm the screen refresh timeline, causing the page to freeze

为了最大程度地减少对主线程的过多调用,Chrome会合并连续事件( wheelmousewheelmousemovepointermove , touchmove ), and delay scheduling until the next requestAnimationFrame .

img
Figure 7: The same event axis as before, but this time the events are merged and dispatched lazily

keydownkeyupmouseupmousedowntouchstart 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.

img
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.


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。


引用和评论

0 条评论