Photo by Denny Müller on Unsplash
[Basic knowledge][Internet] Browsers hoạt động như thế nào?
Browsers là thứ chúng ta dùng hàng ngày để truy cập website, nhưng ẩn sâu bên trong chúng là cả một mớ hỗn độn, phức tạp ...
Internet phát triển qua hàng thập kỉ, bên cạnh nó là sự phát triển không ngừng của các trình duyệt web, hay chúng ta còn thường gọi là browsers.
Các trình duyệt chúng ta có thể điểm qua như
- Internet Explorer
- Chrome
- Firefox
- Safari
- Opera
- …
Đương nhiên, cũng có các trình duyệt khác trên mobile nữa, nhưng tôi sẽ không quá đi sâu vào nó.
Vào cái thời mà IE (Internet Explorer) còn thống trị, làm mưa làm bão thì chúng ta gần như không thể biết được bên trong IE là gì, chúng ta buộc phải coi nó là một “black box”. Nhưng thời thế đã thay đổi IE giờ đã bị khai tử, và thay vào đó là hàng loạt các trình duyệt phát triển dựa trên open source (mã nguồn mở). Và đây là thời điểm để chúng ta đi sâu vào bên trong chúng, để xem thực sự thì bên trong trình duyệt có gì, và cách chúng hoạt động như thế nào??
Chức năng chính của browsers
Chức năng chính của trình duyệt là hiển thị tài nguyên web bạn truy cập, bằng cách request tài nguyên đó từ server và hiển thị trong cửa sổ trình duyệt. Tài nguyên thường là tài liệu HTML, nhưng cũng có thể là PDF, hình ảnh hoặc một số loại nội dung khác. Vị trí của tài nguyên được chỉ định bởi người dùng bằng cách sử dụng URI (Uniform Resource Identifier - Định danh tài nguyên đồng nhất).
Cách trình duyệt diễn giải và hiển thị các tệp HTML được chỉ định trong thông số kỹ thuật HTML và CSS. Các thông số kỹ thuật này được duy trì bởi tổ chức W3C (World Wide Web Consortium), là tổ chức tiêu chuẩn cho web. Trong nhiều năm, các trình duyệt chỉ tuân theo một phần thông số kỹ thuật và phát triển các tiện ích mở rộng (extensions) của riêng chúng. Điều đó gây khó khăn cho các nhà phát triển web (web developer). Nhưng cho đến thời điểm hiện tại, điều này gần như là không còn.
Browsers tuy có nhiều bên phát triển khác nhau, nhưng nhìn chung, chúng đều có các thành phần sau
- Nơi gõ địa chỉ web - URI
- Các nút Back và forward
- Tuỳ chọn bookmarking
- Refresh và stop buttons dùng để tải lại, hoặc dừng tải trang.
- Home button
- …
Giao diện của các browsers được hình thành và phát triển qua nhiều năm dựa trên những feedback, đánh giá của người dùng. Các đặc tả HTML chỉ là một phần bên trong, chúng không ảnh hưởng tới các thành phần kể trên.
Kiến trúc của browsers
Browsers nói chúng, có kiến trúc cơ bản gồm các thành phần sau
- The user interface: bao gồm thanh địa chỉ, nút back/forward, bookmarking, v.v.. Mọi phần của browser trừ cửa sổ nơi bạn nhìn thấy trang web được load.
- The browser engine: đây là phần quan trọng nhất của trình duyệt, nó chịu trách nhiệm kết nối, đối ứng các thao tác của người dùng với tầng rendering engine.
- The rendering engine: chịu trách nhiệm hiển thị nội dung trang web được truy cập. Ví dụ: nếu nội dụng bạn request là HTML, rendering engine sẽ phân tích HTML và CSS, sau đó sẽ hiển thị nội dung đã được phân tích, xử lý ra ngoài màn hình.
- Networking: chịu trách nhiệm quản lý việc call networks bằng các giao thức tiêu chuẩn như HTTP hoặc FTP. Nó cũng xem xét các vấn đề bảo mật liên quan đến giao tiếp internet.
- UI backend: thành phần này sử dụng các phương pháp giao diện người dùng của hệ điều hành cơ bản. Nó chủ yếu được sử dụng để vẽ các thành phần cơ bản như (window và boxes).
- JavaScript interpreter: chịu trách nhiệm phân tích và thực thi mã JavaScript
- Data storage: web browser cần lưu trữ nhiều loại dữ liệu khác nhau ở ngay tại trình duyệt, chẳng hạn như cookies. Các trình duyệt cũng hộ trợ việc lưu trữ các dữ liệu khác như localStorage, IndexedDB, WebSQL và FileSystem.
NOTE: Các trình duyệt như Chrome chạy nhiều phiên bản của rendering engine: một phiên bản cho mỗi tab, nghĩa là mỗi tab chạy trong một quy trình riêng biệt.
Quá trình lấy Server response
Navigation
Người dùng nhập trên trang web muốn truy cập lên trình duyệt, và bấm nút enter, hoặc nút truy cập (tuỳ theo mỗi trình duyệt sẽ có giao diện khác nhau). Sẽ tuỳ vào từng trang web, mà latency (độ trễ) để hiển thị trang web sẽ khác nhau ⇒ đây cũng là một điểm mà các web developer cần chú ý.
DNS lookup
Từ tên miền (mà người dùng truy cập). sẽ qua một quá trình gọi là phân giải tiên miền. Mục đích là để tìm được địa chỉ IP của server (muốn truy cập). Qua quá trình này, tên miền, sẽ được phân tích thành địa chỉ IP (của server) tương ứng.
https://hoangpn.com => 76.76.21.21
TCP Handshake
Khi browser đã biết được IP của server. Giữa chúng sẽ thực hiện một quá trình để có thể kết nối với nhau được gọi là TCP handshake, hay còn gọi là bắt tay 3 bước
Cơ chế này được thiết kế để cả 2 bên (server và browser) có thể thương lượng các tham số của việc kết nối (thông qua TCP socket) trước khi truyền dữ liệu (gửi và nhận) cho nhau.
Kỹ thuật bắt tay ba bước thường được gọi là "SYN-SYN-ACK" - hay chính xác hơn là SYN, SYN-ACK, ACK. Điều này có nghĩa là có thêm request qua lại giữa browser và server mà request web vẫn chưa được thực hiện.
TLS Negotiation
Đối với các kết nối an toàn được thiết lập thông qua HTTPS, cần có một "handshake" khác. Sự bắt tay này, hay đúng hơn là TLS negotiation, xác định mật mã nào sẽ được sử dụng để mã hóa giao tiếp, xác minh server và thiết lập kết nối an toàn trước khi bắt đầu truyền dữ liệu thực tế. Điều này yêu cầu thêm 3 request qua lại đến server trước khi request web thực sự được gửi.
⇒ Sau 8 request qua lại, thì web request sẽ được thực thi.
Response
Khi web request được gửi đi, server sẽ phẩn hồi bằng cách trả về các response, thường là các file HTML.
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>My simple page</title>
<link rel="stylesheet" src="styles.css" />
<script src="myscript.js"></script>
</head>
<body>
<h1 class="heading">My Page</h1>
<p>A paragraph with a <a href="https://example.com/about">link</a></p>
<div>
<img src="myimage.jpg" alt="image description" />
</div>
<script src="anotherscript.js"></script>
</body>
</html>
Response cho request ban đầu này chứa byte dữ liệu đầu tiên nhận được. Time to First Byte (TTFB) là khoảng thời gian từ khi người dùng đưa ra yêu cầu — chẳng hạn bằng cách nhấp vào liên kết — đến khi nhận được gói (packet) HTML đầu tiên này. Phần gói (packet) đầu tiên thường là 14KB.
TCP Slow Start / 14KB rule
Response đầu tiên sẽ là 14KB. Đây là một phần của TCP slow start, một thuật toán cân bằng tốc độ của kết nối mạng (network connection). Khởi động chậm dần dần làm tăng lượng dữ liệu truyền cho đến khi xác định được băng thông tối đa của mạng.
Trong TCP slow start, sau khi nhận được gói (packet) ban đầu, server sẽ tăng gấp đôi kích thước của packet tiếp theo lên khoảng 28KB. Các packet tiếp theo sẽ tăng kích thước cho đến khi đạt đến ngưỡng xác định trước hoặc xảy ra hiện tượng tắc nghẽn.
Rendering basic flow
Khi trình duyệt nhận được phần dữ liệu đầu tiên (first chunk) từ server, chúng sẽ không hiển thị ngay lên được trên giao diện web, mà sẽ trải qua một quá trình để xử lý dữ liệu (nhận được từ server) đó.
Các bước cơ bản bao gồm:
- Trang HTML yêu cầu được rendering engine phân tích cú pháp thành nhiều phần (các chunks), bao gồm các file CSS riêng và trong file HTML. Các phần tử HTML sau đó được chuyển đổi thành các DOM nodes để tạo thành “content tree” hoặc “DOM tree.”
- Đồng thời, trình duyệt cũng tạo ra một render tree. Render tree này bao gồm cả thông tin tạo kiểu (CSS) cũng như các hướng dẫn trực quan xác định thứ tự mà các phần tử sẽ được hiển thị. Render tree đảm bảo rằng nội dung được hiển thị theo thứ tự mong muốn.
- Render tree sẽ phải trải qua quá trình bố trí hiển thị (layout process). Khi render tree được tạo, các giá trị vị trí hoặc kích thước không được gán ngay và render tree, mà phải trải qua một quá trình tính toán các giá trị để đánh giá vị trí mong muốn, quá trình này được gọi là bố trí hiển thị (layout process). Trong quá trình này, mọi node đều được gán tọa độ chính xác. Điều này đảm bảo rằng mọi node đều xuất hiện ở một vị trí chính xác trên màn hình
- Bước cuối cùng là vẽ lên màn hình, trong đó render tree được duyệt qua toàn bộ và phương thức paint() được gọi để vẽ từng node trên màn hình bằng cách sử dụng lớp UI backend layer.
NOTE: mỗi trình duyệt đều có rendering engine riêng của nó, và đây chính là điều khó khăn đối với các web developers - sẽ cần phải coding sao cho tương thích với mọi trình duyệt. Đồng thời việc kiểm thử của các tester cũng sẽ cần phải đáp ứng trên nhiều trình duyệt khác nhau. Nhưng nhìn chung, việc này đã đơn giản hơn rất nhiều vào thời điểm hiện tại.
Parsing
Parsing - phân tích cú pháp là bước mà trình duyệt sẽ thực hiện để chuyển đổi dữ liệu nhận được (từ server) thành DOM - Document Object Model và CSSOM - CSS Object Model, đây là bước đầu tiên để trình duyệt có thể hiển thị dữ liệu lên giao diện người dùng.
DOM cũng được hiển thị và có thể được điều khiển thông qua các API khác nhau trong mã Javascript
Ngay cả khi HTML của trang lớn hơn packet 14KB ban đầu, trình duyệt vẫn sẽ bắt đầu parsing và cố gắng hiển thị trải nghiệm dựa trên dữ liệu mà nó có. ⇒ Đây là một điều cần lưu ý cho các web developers nếu muốn tối ưu hoá hiệu suất của trang web. Nhưng tất cả mọi thứ đó, chúng ta cần ghi nhớ rằng HTML, CSS và JavaScript phải trải qua quá trình phân tích cú pháp (parse).
Building DOM tree
Bước đầu tiên là xử lý HTML markup và xây dựng DOM tree. Phân tích cú pháp HTML liên quan đến mã hóa và xây dựng tree. HTML code bao gồm thẻ bắt đầu và thẻ kết thúc, cũng như tên và các giá trị thuộc tính. Nếu tài liệu được định dạng tốt, việc phân tích cú pháp nó sẽ đơn giản và nhanh hơn.
DOM tree là bản mô tả nội dung của tài liệu. Phần tử là thẻ đầu tiên và là root node của cây tài liệu. Cây này sẽ phản ánh các mối quan hệ và phân cấp giữa các thẻ khác nhau. Các thẻ được lồng trong các thẻ khác là các node con. Số lượng DOM node càng lớn thì thời gian xây dựng DOM tree càng mất nhiều thời gian.
Khi trình phân tích cú pháp tìm thấy các tài nguyên non-blocking, như hình ảnh, trình duyệt sẽ yêu cầu các tài nguyên đó và tiếp tục phân tích cú pháp.
Việc phân tích cú pháp có thể tiếp tục khi gặp phải file CSS, <script>
- đặc biệt là những thẻ không có thuộc tính async hoặc defer (chặn quá trình hiển thị và tạm dừng phân tích cú pháp HTML). Việc phân tích cú pháp có thể bị dừng lại nếu gặp các thẻ đặc <script>
, đặc biệt là các thẻ không có thuộc tính như async, defer.
Mặc dù trình quét tải trước của trình duyệt đẩy nhanh quá trình này, nhưng quá nhiều tập lệnh vẫn có thể ảnh hưởng tới việc hiển thị trang web.
Preload scanner
Preload scanner là một quá trình chạy ngầm (background), chạy độc lập với quá trình browsers xây dựng DOM tree. Preload scanner sẽ phân tích cú pháp thông qua nội dung có sẵn, và sẽ request các tài nguyên có độ ưu tiên cao như CSS, JS, web fonts ⇒ nhằm giảm tốc độ load trang.
<link rel="stylesheet" src="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description" />
<script src="anotherscript.js" async></script>
Ở ví dụ trên, trong khi luồng chính đang phân tích cú pháp HTML và CSS, preload scanner sẽ tìm các tập lệnh (scripts) và hình ảnh, đồng thời bắt đầu tải chúng xuống.
Để đảm bảo scripts không block quá trình parsing, hãy thêm thuộc tính async
hoặc thuộc tính defer
nếu thứ tự thực thi và phân tích cú pháp JavaScript là quan trọng.
Việc chờ để có CSS sẽ không block việc parsing HTML, nhưng nó sẽ block JS vì JS thường được sử dụng để truy vấn tác động của thuộc tính CSS lên các phần tử (Element).
Building the CSSOM
Bước quan trọng tiếp theo trong việc xử lý rendering là xử lý CSS và xây dựng cây CSSOM - CSS Object Model. CSSOM tương tự như DOM. DOM và CSSOM đều là tree. Chúng là các cấu trúc dữ liệu độc lập. Trình duyệt chuyển đổi các quy tắc CSS thành một bản đồ các style mà nó có thể hiểu và hoạt động. Trình duyệt duyệt qua từng bộ quy tắc trong CSS, tạo một cây gồm các nodes với các mối quan hệ cha (parent), con (child) và anh chị em (sibling) dựa trên các bộ chọn CSS (selectors).
Giống như với HTML, trình duyệt sẽ phải chuyển đổi các quy tắc CSS thành một thứ mà nó có thể hiểu được. Do đó, nó sẽ lặp đi lặp lại quá trình CSS-to-object.
Việc áp dụng CSS sẽ được xếp chồng (ghi đè lên nhau) từ việc phân tích CSSOM.
Nhìn chung, việc áp dụng, hiển thị CSS sẽ rất nhanh, tổng thời tian để tạo CSSOM thường ít hơn thời gian cần cho một lần phân giải DNS thành IP.
Các web developers có thể trực tiếp chỉnh sửa CSS trên công cụ nhà phát triển (developer tool) trên mỗi trình duyệt tương ứng.
JavaScript Compilation
Trong khi CSS được xử lý phân tích cú pháp và hình thành CSSOM, các nội dung khác, bao gồm các file JavaScript sẽ được tải xuống (nhờ preload scanner). JavaScript được thông dịch, biên dịch, phân tích cú pháp và thực thi. Các tập lệnh được phân tích cú pháp thành cây cú pháp trừu tượng (abstract syntax trees). Một số công cụ trình duyệt lấy abstract syntax trees và chuyển toàn bộ vào một trình thông dịch, xuất ra mã bytecode - được thực thi trên luồng chính. Đây được gọi là biên dịch JavaScript.
Render
Rendering là bước bao gồm:
- style
- layout
- paint
- một số trường hợp sẽ có thêm compositing
CSSOM và DOM được tạo trong bước phân tích cú pháp được kết hợp thành một cây kết xuất (render tree). Render tree được sử dụng để tính toán bố cục của mọi phần tử hiển thị, sau đó sẽ vẽ chúng lên màn hình. Trong một số trường hợp, nội dung có thể được nâng cấp lên các lớp riêng của chúng và được tổng hợp, cải thiện hiệu suất bằng cách vẽ các phần của màn hình trên GPU thay vì CPU, giải phóng luồng chính.
Style
Sau khi hình thành Render Tree. Browsers sẽ duyệt qua từng node (có thể hiển thị) bắt đầu từ DOM root. Các thẻ sẽ không được hiển thị:
<head>
và các thẻ con của nó- bất kì thẻ nào với thuộc tính
display: none
- nodes với thuộc tính
visibility: hidden
⇒ chú ý là thẻ này vẫn được đưa vào render tree.
Mỗi nodes hiển thị có các quy tắc CSSOM được áp dụng cho chúng. Render tree chứa tất cả các nodes hiển thị có nội dung và kiểu được tính toán dựa trên CSS cascade.
Layout
Layout là quá trình lần đầu tiên kích thước và vị trí của các nodes được xác định. Các lần sau được gọi là Reflow.
Layout là quá trình
- Xác định chiều rộng
- Xác định chiều cao
- Xác định vị trí của tất cả các nodes trong Render Tree
- Xác định kích thước và vị trí của từng đối tượng trên trang
Reflow là quá trình bất kỳ xác định kích thước và vị trí tiếp theo của phần từ trên trang, hoặc toàn bộ documents.
Khi render tree được xây dựng, quá trình layout sẽ bắt đầu. Render tree sẽ xác định các nodes nào được hiển thị (và nodes không hiển thị) cùng với style của chúng, nhưng sẽ không xác định kích thước, hoặc vị trí của mỗi nodes. Để xác định kích thước và vị trí chính xác của từng nodes, browsers duyệt hết Render Tree, bắt đầu từ root của nó.
Trên trang web, gần như mọi thứ đều là một box. Các thiết bị (devices) khác nhau sẽ có các định dạng kích thước khác nhau. Các định dạng kích thước này sẽ là cơ sở để quá trình layout diễn ra.
Paint
Bước cuối cùng trong việc rendering là pain - vẽ các nodes riêng lẻ lên trên màn hình.
Trong giai đoạn này, browsers chuyển đổi từng box ( đã được tính toán ở bước layout) thành các pixel trên màn hình. Mọi nodes sẽ để được chuyển đổi thành pixel tương ứng, bao gồm: text, colors, borders, shadows, và các phần tử khác như buttons, images. Và browsers cần thực hiện việc này một cách nhanh chóng.
Để đảm bảo scrolling và animation một cách mượt mà, mọi thứ sẽ được thực hiện ở luồng chính (main thread), bao gồm cả tính toán style, reflow và paint.
Ví dụ ở màn hình iPad 2048 X 1536, có hơn 3.145.000 pixel được vẽ trên màn hình. Để bước paint, reflow + paint diễn ra một cách nhanh chóng, các bản vẽ sẽ được chia thành nhiều lớp chồng chéo lên nhau ⇒ nếu điều này cần thiết phải thực hiện thì sẽ xuất hiện bước tiếp theo là compositing
Painting có thể chia nhỏ các nodes trong Render tree thành các lớp khác nhau. việc chia các lớp trên GPU (thay vì luồng chính trên CPU) sẽ giúp cải thiện hiệu suất của việc paint và re-paint. Nhưng điểm trừ của nó là sẽ tốn kém khi quản lý memory. Vì vậy chúng ta cần cân nhắn để sử dụng chúng một cách hiệu quả.
Compositing
Khi các tài liệu được vẽ ở các layers khác nhau, chúng cần được tổng hợp lại để đảm bảo rằng có thể hiển thị đúng (theo đúng thứ tự + nội dung chính xác)
Khi trang tiếp tục load các nội dung khác (assets), reflows có thể xảy ra, và chúng sẽ tạo ra một quá trình re-paint và re-composite.
Interactivity
Main thread (luồng chính) có thể sẽ phải đợi để xử lý hoàn tất nếu trong trường hợp chúng ta load file JS (sau events onload
)
Time to Interactive (TTI) là thời gian tính từ request đầu tiên, tra cứu DNS và kết nối SSL đến thời điểm trang tương tác (interactive)
Interactive là thời điểm sau lần Painting toàn bộ nội dung đầu tiên - khi trang có responds tương tác của người dùng trong vòng 50ms. Nếu luồng chính bị chiếm dụng để parsing, compiling, và thực thi JavaScript, nó sẽ không khả dụng và do đó không thể đáp ứng các tương tác của người dùng trong thời gian này (dưới 50 mili giây).
Chúng ta cùng xét ví dụ dưới đây.
Trong ví dụ này, quá trình load nội dung DOM mất hơn 1,5s và luồng chính bị chiếm hoàn toàn trong toàn bộ thời gian đó, không phản hồi với các sự kiện click chuột (hoặc chạm vào màn hình cảm ứng). Chúng ta có thể thấy hình ảnh được tải nhanh, nhưng do file anotherscript.js
có dung lượng 2MB và kết nối mạng bị chậm, do đó người dùng có thể nhìn thấy trang cực nhanh, nhưng không thể thao tác mà không bị giật, lag cho đến khi file JS trên được tải xuống, parsing, và executing. Và đương nhiên, đó sẽ không phải là một trải nghiệm người dùng tốt.
Tổng kết
Qua các phần trình bày bên trên, chúng ta đã nắm được cơ bản về Browsers, và cách chúng hoạt động. Tưởng chừng như đơn giản và hiển nhiên, nhưng việc chúng ta có thể truy cập website như ngày này, đã trải qua rất nhiều công đoạn để có thể giúp cho chúng ta có trải nghiệm mượt mà
Hy vọng các kiến thức trên sẽ giúp ích cho các bạn ít nhiều.
Nhưng mọi khi, tuy đã rà soát lại, nhưng cũng sẽ không tránh khỏi việc có sai sót. Mong các bạn để lại góp ý, và cùng giúp nhau tiến bộ nhé.