Tìm hiểu về trình duyệt web hiện đại (phần 3)

Mariko Kosaka

Hoạt động bên trong của một Quy trình kết xuất

Đây là phần 3 của loạt blog gồm 4 phần tìm hiểu cách hoạt động của trình duyệt. Trước đây, chúng tôi đã đề cập đến cấu trúc đa tiến trìnhluồng điều hướng. Trong bài đăng này, chúng ta sẽ xem xét những gì xảy ra bên trong quy trình kết xuất đồ hoạ.

Quy trình kết xuất ảnh hưởng đến nhiều khía cạnh về hiệu suất của web. Vì có rất nhiều vấn đề đang diễn ra bên trong quá trình kết xuất đồ hoạ, nên bài đăng này chỉ mang tính tổng quan chung. Nếu muốn tìm hiểu kỹ hơn, bạn có thể tham khảo phần Hiệu suất của Kiến thức cơ bản về web để tham khảo thêm nhiều tài nguyên khác.

Quy trình kết xuất sẽ xử lý nội dung trên web

Quy trình kết xuất chịu trách nhiệm về mọi hoạt động diễn ra bên trong một thẻ. Trong quá trình kết xuất đồ hoạ, luồng chính sẽ xử lý hầu hết mã bạn gửi cho người dùng. Đôi khi, các phần của JavaScript được xử lý bởi các luồng worker nếu bạn sử dụng trình chạy web hoặc trình chạy dịch vụ. Các luồng của trình kết hợp và đường quét cũng chạy bên trong các quy trình kết xuất đồ hoạ để kết xuất một trang một cách hiệu quả và mượt mà.

Công việc cốt lõi của quy trình kết xuất là chuyển HTML, CSS và JavaScript thành một trang web mà người dùng có thể tương tác.

Quy trình kết xuất
Hình 1: Quy trình kết xuất với luồng chính, luồng worker, luồng tổng hợp và luồng đường quét bên trong

Phân tích cú pháp

Xây dựng DOM

Khi quy trình kết xuất nhận được thông báo cam kết cho một quy trình điều hướng và bắt đầu nhận dữ liệu HTML, luồng chính sẽ bắt đầu phân tích cú pháp chuỗi văn bản (HTML) và chuyển chuỗi văn bản đó thành một đối tượng D chèn M odel (DOM).

DOM là phần trình bày nội bộ của trình duyệt, cũng như cấu trúc dữ liệu và API mà nhà phát triển web có thể tương tác thông qua JavaScript.

Việc phân tích cú pháp tài liệu HTML vào DOM được xác định theo Tiêu chuẩn HTML. Bạn có thể nhận thấy rằng việc cấp dữ liệu HTML cho trình duyệt sẽ không bao giờ gây ra lỗi. Ví dụ: thiếu thẻ đóng </p> là một HTML hợp lệ. Thẻ đánh dấu bị lỗi như Hi! <b>I'm <i>Chrome</b>!</i> (thẻ b bị đóng trước khi thẻ i) được xử lý như thể bạn đã viết Hi! <b>I'm <i>Chrome</i></b><i>!</i>. Lý do là thông số kỹ thuật HTML được thiết kế để xử lý những lỗi đó một cách linh hoạt. Nếu muốn biết cách thực hiện những việc này, bạn có thể đọc phần "Giới thiệu về cách xử lý lỗi và các trường hợp lạ trong trình phân tích cú pháp" trong thông số kỹ thuật HTML.

Đang tải tài nguyên phụ

Một trang web thường sử dụng các tài nguyên bên ngoài như hình ảnh, CSS và JavaScript. Các tệp đó cần được tải từ mạng hoặc bộ nhớ đệm. Luồng chính có thể yêu cầu lần lượt từng mã khi tìm thấy chúng trong khi phân tích cú pháp để tạo DOM, nhưng để tăng tốc, tính năng "tải trước trình quét" sẽ chạy đồng thời. Nếu có các nội dung như <img> hoặc <link> trong tài liệu HTML, việc tải trước trình quét sẽ hiển thị các mã thông báo do trình phân tích cú pháp HTML tạo và gửi yêu cầu đến chuỗi mạng trong quá trình trình duyệt.

DOM
Hình 2: Luồng chính phân tích cú pháp HTML và xây dựng cây DOM

JavaScript có thể chặn quá trình phân tích cú pháp

Khi tìm thấy thẻ <script>, trình phân tích cú pháp HTML sẽ tạm dừng việc phân tích cú pháp tài liệu HTML và phải tải, phân tích cú pháp và thực thi mã JavaScript. Tại sao? vì JavaScript có thể thay đổi hình dạng của tài liệu bằng cách sử dụng document.write() để thay đổi toàn bộ cấu trúc DOM (tổng quan về mô hình phân tích cú pháp trong phần thông số kỹ thuật HTML có sơ đồ hợp lý). Đây là lý do tại sao trình phân tích cú pháp HTML phải đợi JavaScript chạy trước khi có thể tiếp tục phân tích cú pháp tài liệu HTML. Nếu bạn muốn tìm hiểu về những gì xảy ra trong quá trình thực thi JavaScript, thì nhóm V8 đã có các buổi trò chuyện và đăng bài trên blog về chủ đề này.

Gợi ý cho trình duyệt về cách bạn muốn tải tài nguyên

Có nhiều cách mà nhà phát triển web có thể gửi gợi ý cho trình duyệt để tải tài nguyên hiệu quả. Nếu JavaScript của bạn không sử dụng document.write(), bạn có thể thêm thuộc tính async hoặc defer vào thẻ <script>. Sau đó, trình duyệt sẽ tải và chạy mã JavaScript một cách không đồng bộ và không chặn quá trình phân tích cú pháp. Bạn cũng có thể sử dụng mô-đun JavaScript nếu phù hợp. <link rel="preload"> là một cách để thông báo cho trình duyệt rằng tài nguyên này chắc chắn cần thiết cho việc điều hướng hiện tại và bạn muốn tải xuống càng sớm càng tốt. Bạn có thể đọc thêm về vấn đề này trong bài viết Ưu tiên tài nguyên – Sử dụng trình duyệt để trợ giúp.

Tính toán kiểu

Việc có một DOM không đủ để biết giao diện của trang vì chúng tôi có thể tạo kiểu cho các phần tử trang trong CSS. Luồng chính phân tích cú pháp CSS và xác định kiểu đã tính toán cho mỗi nút DOM. Đây là thông tin về loại kiểu được áp dụng cho từng phần tử dựa trên bộ chọn CSS. Bạn có thể xem thông tin này trong mục computed của Công cụ cho nhà phát triển.

Kiểu tính toán
Hình 3: Chuỗi chính phân tích cú pháp CSS để thêm kiểu đã tính toán

Ngay cả khi bạn không cung cấp CSS nào, mỗi nút DOM vẫn có một kiểu đã tính toán. Thẻ <h1> hiển thị lớn hơn thẻ <h2> và lề được xác định cho mỗi phần tử. Nguyên nhân là do trình duyệt có một biểu định kiểu mặc định. Nếu muốn biết CSS mặc định của Chrome như thế nào, bạn có thể xem mã nguồn tại đây.

Bố cục

Hiện tại, quy trình kết xuất đã biết cấu trúc của tài liệu và kiểu cho từng nút, nhưng điều đó vẫn chưa đủ để kết xuất một trang. Hãy tưởng tượng bạn đang cố gắng mô tả một bức tranh cho bạn mình qua điện thoại. "Có một hình tròn lớn màu đỏ và một hình vuông nhỏ màu xanh dương" không đủ thông tin để bạn bè của bạn biết chính xác bức tranh sẽ trông như thế nào.

trò chơi máy fax
Hình 4: Một người đứng trước bức tranh, đường dây điện thoại kết nối với người kia

Bố cục là một quá trình tìm hình dạng của các phần tử. Luồng chính đi qua DOM và các kiểu được tính toán, đồng thời tạo cây bố cục có thông tin như toạ độ x y và kích thước hộp giới hạn. Cây bố cục có thể có cấu trúc tương tự như cây DOM, nhưng chỉ chứa thông tin liên quan đến nội dung hiển thị trên trang. Nếu bạn áp dụng display: none, phần tử đó không thuộc cây bố cục (tuy nhiên, phần tử có visibility: hidden nằm trong cây bố cục). Tương tự, nếu áp dụng một lớp giả có nội dung như p::before{content:"Hi!"}, thì lớp đó sẽ được đưa vào cây bố cục ngay cả khi lớp đó không nằm trong DOM.

bố cục
Hình 5: Luồng chính đi qua cây DOM với các kiểu đã tính toán và cây bố cục tạo
Hình 6: Bố cục hộp cho một đoạn di chuyển do thay đổi ngắt dòng

Xác định bố cục của trang là một nhiệm vụ khó khăn. Ngay cả bố cục trang đơn giản nhất như một khối lệnh từ trên xuống dưới cũng phải xem xét độ lớn của phông chữ và vị trí ngắt dòng vì những yếu tố đó ảnh hưởng đến kích thước và hình dạng của một đoạn văn bản; sau đó ảnh hưởng đến vị trí cần đặt của đoạn sau.

CSS có thể làm cho phần tử nổi sang một bên, che mục tràn và thay đổi hướng viết. Bạn có thể hình dung giai đoạn bố cục này có một nhiệm vụ quan trọng. Trong Chrome, cả một đội ngũ kỹ sư làm việc trên bố cục. Nếu bạn muốn xem thông tin chi tiết về công việc của họ, một vài cuộc trò chuyện từ BlinkOn Conference sẽ được ghi lại và khá thú vị để xem.

Sơn

trò chơi vẽ
Hình 7: Một người ngồi trước khung vẽ, tay cầm cọ vẽ, tự hỏi mình nên vẽ hình tròn trước hay hình vuông trước

Việc có DOM, kiểu và bố cục vẫn chưa đủ để hiển thị một trang. Giả sử bạn đang cố gắng tái tạo một bức tranh. Bạn biết kích thước, hình dạng và vị trí của các phần tử, nhưng bạn vẫn phải đánh giá thứ tự vẽ các phần tử đó.

Ví dụ: bạn có thể đặt z-index cho một số phần tử nhất định. Trong trường hợp đó, việc vẽ theo thứ tự các phần tử được viết trong HTML sẽ dẫn đến việc hiển thị không chính xác.

chỉ mục z không thành công
Hình 8: Các phần tử trang xuất hiện theo thứ tự mã đánh dấu HTML, dẫn đến việc hình ảnh hiển thị không chính xác vì không tính đến chỉ mục z

Ở bước vẽ này, luồng chính đi qua cây bố cục để tạo các bản ghi về màu vẽ. Bản ghi vẽ là ghi chú về quy trình vẽ, chẳng hạn như "nền trước tiên, sau đó đến văn bản, sau đó là hình chữ nhật". Nếu bạn đã vẽ trên phần tử <canvas> bằng JavaScript, thì quy trình này có thể quen thuộc với bạn.

hồ sơ sơn
Hình 9: Luồng chính đi qua cây bố cục và tạo bản ghi màu vẽ

Việc cập nhật quy trình kết xuất gây tốn kém

Hình 10: Các cây DOM+Style, Layout và Paint theo thứ tự được tạo

Điều quan trọng nhất cần nắm bắt trong quy trình kết xuất là ở mỗi bước, kết quả của thao tác trước đó sẽ được dùng để tạo dữ liệu mới. Ví dụ: nếu có gì thay đổi trong cây bố cục, thì bạn cần tạo lại thứ tự Paint cho các phần bị ảnh hưởng của tài liệu.

Nếu bạn đang tạo ảnh động cho phần tử, trình duyệt phải chạy các thao tác này giữa mọi khung hình. Hầu hết các màn hình của chúng tôi đều làm mới màn hình 60 lần một giây (60 khung hình/giây). Ảnh động sẽ trông mượt mà đối với mắt người khi bạn di chuyển mọi thứ trên màn hình ở mọi khung hình. Tuy nhiên, nếu ảnh động bỏ lỡ các khung hình ở giữa thì trang sẽ xuất hiện "giật".

jage bị giật do thiếu khung hình
Hình 11: Khung ảnh động trên tiến trình

Ngay cả khi hoạt động kết xuất của bạn bắt kịp với quá trình làm mới màn hình, các tính toán này vẫn chạy trên luồng chính, có nghĩa là hoạt động này có thể bị chặn khi ứng dụng của bạn đang chạy JavaScript.

jage bị giật do JavaScript
Hình 12: Khung ảnh động trên tiến trình, nhưng một khung bị JavaScript chặn

Bạn có thể chia hoạt động JavaScript thành các phần nhỏ và lên lịch chạy trên mọi khung hình bằng cách sử dụng requestAnimationFrame(). Để biết thêm thông tin về chủ đề này, vui lòng xem bài viết Tối ưu hoá quá trình thực thi JavaScript. Bạn cũng có thể chạy JavaScript trong Web Worker để tránh chặn luồng chính.

yêu cầu khung ảnh động
Hình 13: Các đoạn JavaScript nhỏ chạy trên một tiến trình có khung ảnh động

Kết hợp

Bạn sẽ vẽ một trang như thế nào?

Hình 14: Ảnh động về quá trình tạo điểm ảnh đơn thuần

Bây giờ, trình duyệt đã biết cấu trúc của tài liệu, kiểu của từng phần tử, hình dạng của trang và thứ tự vẽ, làm cách nào để trình duyệt vẽ một trang? Việc chuyển thông tin này thành các pixel trên màn hình được gọi là tạo điểm ảnh.

Có thể cách xử lý đơn giản là quét các phần bên trong khung nhìn. Nếu người dùng cuộn trang, hãy di chuyển khung đã tạo điểm ảnh và điền các phần còn thiếu bằng cách tạo điểm ảnh khác. Đây là cách Chrome xử lý việc tạo điểm ảnh khi phát hành lần đầu tiên. Tuy nhiên, trình duyệt hiện đại chạy một quy trình phức tạp hơn được gọi là kết hợp.

Kết hợp là gì

Hình 15: Ảnh động của quy trình kết hợp

Kết hợp là kỹ thuật để chia các phần của một trang thành nhiều lớp, tạo điểm ảnh riêng biệt và kết hợp dưới dạng một trang trong một chuỗi riêng biệt được gọi là chuỗi trình tổng hợp. Trong trường hợp cuộn, vì các lớp đã được tạo điểm ảnh, bạn chỉ cần kết hợp một khung mới. Bạn có thể tạo ảnh động theo cách tương tự bằng cách di chuyển các lớp và kết hợp một khung mới.

Bạn có thể xem cách trang web của mình được chia thành các lớp trong Công cụ cho nhà phát triển bằng bảng điều khiển Lớp.

Chia thành các lớp

Để tìm ra phần tử nào cần nằm trong lớp nào, luồng chính sẽ đi qua cây bố cục để tạo cây lớp (phần này được gọi là "Cập nhật cây lớp" trong bảng điều khiển hiệu suất Công cụ cho nhà phát triển). Nếu một số phần nhất định trên một trang vốn là lớp riêng biệt (chẳng hạn như trình đơn trượt vào) không xuất hiện, thì bạn có thể gợi ý cho trình duyệt bằng cách sử dụng thuộc tính will-change trong CSS.

cây lớp
Hình 16: Luồng chính đi qua cây bố cục tạo cây lớp

Bạn có thể muốn cung cấp lớp cho mọi phần tử, nhưng việc kết hợp quá số lượng lớp có thể khiến hoạt động chậm hơn so với việc tạo điểm ảnh cho các phần nhỏ của trang trong mọi khung hình. Vì vậy, bạn cần đo lường hiệu suất kết xuất của ứng dụng. Để biết thêm về chủ đề này, hãy xem phần Gắn vào thuộc tính chỉ trình kết hợp và Quản lý số lớp.

Đường quét và tổng hợp ngoài luồng chính

Sau khi tạo cây lớp và xác định đơn đặt hàng màu, luồng chính sẽ chuyển thông tin đó với luồng trình tổng hợp. Sau đó, luồng trình tổng hợp sẽ tạo điểm ảnh cho từng lớp. Lớp có thể lớn như toàn bộ chiều dài của một trang, vì vậy, luồng tổng hợp chia các lớp này thành các thẻ thông tin và gửi từng thẻ thông tin đến các chuỗi đường quét. Luồng đường quét tạo điểm ảnh cho từng thẻ thông tin và lưu trữ các thẻ đó trong bộ nhớ GPU.

đường quét
Hình 17: Các luồng đường quét tạo bitmap của thẻ thông tin và gửi đến GPU

Luồng trình tổng hợp có thể ưu tiên nhiều luồng đường quét để có thể tạo điểm ảnh cho những nội dung trong khung nhìn (hoặc gần đó) trước. Một lớp cũng có nhiều ô cho các độ phân giải khác nhau để xử lý những việc như hành động phóng to.

Sau khi tạo điểm ảnh cho các thẻ thông tin, luồng trình tổng hợp sẽ thu thập thông tin thẻ thông tin có tên là draw quads để tạo một khung trình tổng hợp.

Vẽ hình tứ giác Chứa thông tin như vị trí của thẻ thông tin trong bộ nhớ và vị trí trên trang để vẽ thẻ thông tin khi xét đến việc kết hợp trang.
Khung bộ kết hợp Một tập hợp các ô vuông vẽ đại diện cho một khung của một trang.

Sau đó, một khung trình kết hợp được gửi tới quá trình trình duyệt thông qua IPC. Tại thời điểm này, bạn có thể thêm một khung trình kết hợp khác từ luồng giao diện người dùng để thay đổi giao diện người dùng của trình duyệt hoặc từ các quy trình kết xuất đồ hoạ khác cho tiện ích. Các khung của bộ kết hợp này được gửi đến GPU để hiển thị trên màn hình. Nếu có một sự kiện cuộn xuất hiện, luồng của trình tổng hợp sẽ tạo một khung trình kết hợp khác để gửi đến GPU.

tổng hợp
Hình 18: Luồng tổng hợp tạo khung kết hợp. Khung được gửi đến quá trình của trình duyệt rồi đến GPU

Lợi ích của việc kết hợp là bạn có thể thực hiện việc này mà không cần đến luồng chính. Luồng trình tổng hợp không cần phải đợi tính toán kiểu hoặc thực thi JavaScript. Đây là lý do chỉ kết hợp ảnh động được xem là tốt nhất để có hiệu suất mượt mà. Nếu cần tính toán lại bố cục hoặc màu vẽ, thì bạn phải tham gia vào luồng chính.

Tổng kết

Trong bài đăng này, chúng ta đã xem xét quy trình kết xuất từ phân tích cú pháp đến kết hợp. Hy vọng rằng giờ đây bạn đã có thể đọc thêm về hoạt động tối ưu hoá hiệu suất của trang web.

Trong bài đăng tiếp theo cũng như cuối cùng của loạt bài đăng này, chúng ta sẽ xem xét luồng trình kết hợp một cách chi tiết hơn cũng như xem điều gì sẽ xảy ra khi hoạt động đầu vào của người dùng như mouse moveclick xuất hiện.

Bạn có thích bài đăng này không? Nếu bạn có bất kỳ câu hỏi hoặc đề xuất nào cho bài đăng trong tương lai, tôi muốn được nghe bạn chia sẻ trong phần bình luận bên dưới hoặc @kosamari trên Twitter.

Tiếp theo: Dữ liệu đầu vào sẽ được chuyển đến trình tổng hợp