Trình duyệt có hai engine riêng biệt: một dựng hình HTML/CSS, một chạy JavaScript. Xem Blink+V8, Gecko+SpiderMonkey, WebKit+JSCore ghép cặp ra sao.
"Engine trình duyệt" là một trong những thuật ngữ hay bị dùng nhầm nhất trong phát triển web, bởi trình duyệt thực ra không chỉ có một engine — nó có hai, làm hai công việc hoàn toàn khác nhau. Engine dựng hình biến HTML và CSS thành các điểm ảnh trên màn hình. Engine JavaScript thực thi các script khiến trang trở nên tương tác. Chúng chạy song song, do cùng một hãng xây dựng, và người ta cứ liên tục nói về chúng như thể là một thứ. Không phải vậy, và sự khác biệt này quan trọng hơn cả chuyện chữ nghĩa: những đặc điểm riêng của engine JavaScript là một tín hiệu nhận diện danh tính độc lập, tách biệt hoàn toàn khỏi bất cứ điều gì engine dựng hình tiết lộ.
Điểm chính
- Trình duyệt ghép một engine dựng hình (bố cục), vốn phân tích HTML/CSS và vẽ điểm ảnh, với một engine JavaScript riêng biệt, vốn thực thi script — đây là hai phần mềm khác nhau.
- Ba cặp thực sự tồn tại: Blink + V8 (Chrome, Edge và các trình duyệt Chromium khác), Gecko + SpiderMonkey (Firefox), và WebKit + JavaScriptCore (Safari, và theo quy định của Apple, gần như mọi trình duyệt trên iOS).
- Một lần tải trang chạy cả hai nửa cùng lúc: chuỗi phân tích → tạo kiểu → bố cục → vẽ của engine dựng hình diễn ra song song với engine JavaScript thực thi các script có thể chỉnh sửa ngay chính chuỗi đó giữa chừng.
- Các engine JavaScript có dấu vân tay riêng có thể phát hiện được — cách làm tròn số thực dấu phẩy động, cách diễn đạt thông báo lỗi, hành vi của
TypedArray, thời gian JIT "khởi động nóng" — tất cả đều độc lập với việc engine dựng hình đứng cạnh nó là loại nào. - Công cụ kiểm tra nhân trình duyệt của BrowserInsight kiểm tra cả hai nửa, vì một trình duyệt giả mạo đáng tin phải giả được hành vi của cả engine dựng hình lẫn engine JavaScript.
Hai engine, một trình duyệt
Mở bất kỳ trình duyệt nào, và bên dưới lớp vỏ là hai phần mềm riêng biệt phối hợp để hiển thị trang cho bạn. Engine dựng hình (đôi khi gọi là engine bố cục) đọc HTML và CSS của trang rồi xác định cần vẽ gì. Engine JavaScript đọc và thực thi mã <script> của trang — các phép tính, thao tác DOM, xử lý sự kiện, mọi thứ mang tính động.
Đây thực sự là hai mã nguồn tách biệt, có nhiệm vụ khác nhau, được phát triển và đánh số phiên bản độc lập với nhau, ngay cả khi cùng thuộc một dự án trình duyệt. Engine dựng hình hoàn toàn không biết cách thực thi JavaScript; engine JavaScript cũng hoàn toàn không biết cách bố cục một thẻ <div>. Thứ kết nối chúng là một lớp liên kết (binding) cho phép JavaScript gọi vào DOM mà engine dựng hình duy trì — đây cũng là lý do vì sao "engine JavaScript đã thay đổi trang" và "engine dựng hình đã vẽ trang" là hai sự kiện tách biệt, không phải một.
Bài hướng dẫn về engine trình duyệt của chúng tôi đã đi sâu vào Blink, Gecko và WebKit với tư cách là những engine dựng hình. Bài viết này nói về nửa còn lại mà bài hướng dẫn đó chỉ lướt qua: engine JavaScript hoạt động ngay bên cạnh mỗi engine đó.
Ba cặp thực sự tồn tại
Dù thị trường có hàng chục cái tên trình duyệt khác nhau, số engine dựng hình và engine JavaScript thực sự được dùng chỉ có ba mỗi loại, và mỗi hãng ghép cặp engine của riêng mình:
| Trình duyệt | Engine dựng hình | Engine JavaScript |
|---|---|---|
| Chrome, Edge, Opera, Brave và các trình duyệt Chromium khác | Blink | V8 |
| Firefox | Gecko | SpiderMonkey |
| Safari, và gần như mọi trình duyệt trên iOS/iPadOS | WebKit | JavaScriptCore (còn gọi là Nitro) |
Blink + V8. Cả hai đều do Google duy trì. V8 biên dịch JavaScript thành mã máy qua nhiều tầng biên dịch tức thời (JIT), đánh đổi thời gian biên dịch lấy tốc độ thực thi khi dần nhận ra hàm nào đang chạy "nóng". Blink chuyển nội dung <script> cho V8, và cung cấp cho môi trường chạy của V8 các đối tượng DOM mà nó có thể gọi vào.
Gecko + SpiderMonkey. Cặp đôi của Mozilla, được phát triển song song từ thời Netscape. Cuộc đại tu JIT "Warp" của SpiderMonkey, phát hành cùng Firefox 83, đã tái cấu trúc hệ thống phân tầng để giảm mức dùng bộ nhớ và tăng tốc các trang thực tế — một ví dụ tốt cho việc một engine JavaScript có thể tiến hóa độc lập với bất kỳ thay đổi nào của engine dựng hình diễn ra cùng lúc.
WebKit + JavaScriptCore. Cặp đôi của Apple, và cũng là cặp mà các trình duyệt trên iOS không thể thoát khỏi. JavaScriptCore chạy mã qua bốn tầng tăng dần — trình thông dịch LLInt, rồi lần lượt đến các trình biên dịch JIT Baseline, DFG và FTL — mỗi tầng đánh đổi thêm thời gian biên dịch để lấy thêm tốc độ thực thi khi một hàm chứng minh được rằng nó đáng để tối ưu hóa. Cả bốn tầng đều chạy trên cùng một định dạng bytecode, thứ mà WebKit đã viết lại vào năm 2019 để giảm khoảng một nửa lượng bộ nhớ nó chiếm.
Vì Apple yêu cầu các trình duyệt trên iOS phải dùng WebKit, "Chrome cho iPhone" thực chất đang chạy các quy ước giao diện của Blink trên nền WebKit và JavaScriptCore — chứ không phải V8. Nó chỉ "cảm giác" giống Chrome thôi. Bên dưới, cả hai engine đều là của Apple. Từ iOS 17.4, Đạo luật Thị trường Kỹ thuật số của EU đã bắt đầu nới lỏng quy định này, nhưng trên thực tế các trình duyệt iOS vẫn chạy WebKit — cả trong lẫn ngoài EU.
Quan sát cả hai engine hoạt động trong một lần tải trang
Một lần tải trang duy nhất sẽ cho thấy rõ sự phân chia này. Khi trình duyệt của bạn tải một trang, đại khái những điều sau sẽ xảy ra:
- Phân tích (Parse) — engine dựng hình đọc mã HTML thô và xây dựng cây DOM; đọc CSS và xây dựng cây kiểu dáng.
- Bắt đầu thực thi script — ngay khi engine gặp thẻ
<script>(hoặc một script đã được phân tích, bị hoãn thực thi, được kích hoạt), engine JavaScript sẽ tiếp quản, chạy mã có thể đọc và viết lại DOM mà engine dựng hình vừa xây dựng. - Tạo kiểu + Bố cục — engine dựng hình kết hợp DOM và CSS thành cây dựng hình, rồi tính toán vị trí và kích thước của từng khối, có tính đến mọi thay đổi mà JavaScript đã thực hiện.
- Vẽ (Paint) — engine dựng hình biến bố cục đó thành các điểm ảnh thực tế.
Nếu một script chỉnh sửa DOM sau lần vẽ đầu tiên — thêm một phần tử, đổi một class, thêm hiệu ứng động cho một kiểu dáng — bước 3 và 4 sẽ chạy lại, nhưng chỉ cho vùng bị ảnh hưởng. Vòng lặp này, với engine JavaScript và engine dựng hình lần lượt trao quyền điều khiển cho nhau, lặp lại suốt vòng đời của trang. Không engine nào có thể làm công việc của engine kia, nhưng nếu một trong hai chậm lại thì cả vòng lặp bị nghẽn: engine dựng hình không thể vẽ nội dung mà một engine JavaScript đang bị kẹt chưa tính xong, và một engine JavaScript nhanh cũng bị lãng phí nếu engine dựng hình không thể bố cục kết quả của nó đủ nhanh.
Vì sao sự phân chia này quan trọng với việc nhận diện và phát hiện
Đây không chỉ là một chú thích về kiến trúc — đây chính là lý do việc nhận diện trình duyệt phải kiểm tra hai thứ, chứ không phải một.
Engine dựng hình để lại dấu vết hành vi qua cách nó vẽ: cách dựng chữ, mức hỗ trợ tính năng CSS, cách làm tròn trong bố cục. Engine JavaScript để lại một bộ dấu vết hoàn toàn tách biệt qua cách nó tính toán: cách làm tròn số thực dấu phẩy động trong các trường hợp biên, cách diễn đạt chính xác của các thông báo lỗi có sẵn (văn bản TypeError khác nhau tinh tế giữa V8, SpiderMonkey và JavaScriptCore), hành vi biên của các phương thức TypedArray và Array, và thời gian JIT "khởi động nóng" — cần bao nhiêu lần gọi trước khi một hàm "nóng" được tối ưu hóa. Không điều nào trong số đó liên quan đến bố cục cả.
Một ví dụ cụ thể bạn có thể tự kiểm chứng trong bất kỳ console nào: new Error().stack. V8 mở đầu chuỗi bằng tên và nội dung lỗi, rồi ghi mỗi khung theo dạng at fnName (url:line:column). SpiderMonkey và JavaScriptCore đều bỏ phần mở đầu đó và ghi khung theo dạng fnName@url:line:column. Cùng một ngôn ngữ, cùng một chuẩn, nhưng ra ba dạng chuỗi khác nhau — hoàn toàn do engine JavaScript nào đang thực thi quyết định, và bất kỳ script nào cũng đọc được chỉ bằng một câu lệnh.
Chính sự độc lập đó khiến các tín hiệu từ engine JavaScript trở nên có giá trị trong việc phát hiện giả mạo. Một script giả mạo chuỗi user-agent, hoặc ghi đè các thuộc tính navigator để tự nhận là một trình duyệt khác, chỉ thay đổi những gì trang khai báo — nhưng không thay đổi được việc engine JavaScript nào thực sự đang thực thi mã bên dưới. Nếu một trang tự nhận là Safari trên iOS, nhưng cách diễn đạt thông báo lỗi và hành vi số thực dấu phẩy động của engine JavaScript trên đó lại khớp với V8 chứ không phải JavaScriptCore, đó là một mâu thuẫn có thật mà một script có thể đo trực tiếp, không liên quan gì đến việc dựng hình.
Đây chính là lý do công cụ kiểm tra nhân trình duyệt của BrowserInsight không dừng lại ở việc phát hiện engine dựng hình — nó cũng dò xét hành vi của engine JavaScript. Một màn giả mạo đáng tin phải làm cho cả hai nửa của trình duyệt nhất quán với nhau, điều này khó hơn nhiều so với việc chỉnh sửa một chuỗi ký tự.
Câu hỏi thường gặp
Blink có phải là V8 không?
Không. Blink là engine dựng hình của Google — nó phân tích HTML/CSS và vẽ trang. V8 là engine JavaScript riêng biệt khác của Google — nó thực thi script. Cả hai đều nằm trong Chrome và các trình duyệt Chromium khác, cả hai đều do Google xây dựng, nhưng chúng là những mã nguồn tách biệt, có nhiệm vụ khác nhau.
Firefox có dùng cùng engine JavaScript với Chrome không?
Không. Firefox ghép Gecko (dựng hình) với SpiderMonkey (JavaScript), engine riêng của Mozilla, không liên quan gì đến V8 của Google. Chrome, Edge, Brave và các trình duyệt Chromium khác đều dùng V8.
Safari dùng engine JavaScript nào?
JavaScriptCore, còn gọi là Nitro — engine riêng của Apple, ghép cùng engine dựng hình WebKit. Vì Apple yêu cầu các trình duyệt trên iOS phải dùng WebKit, trình duyệt trên iPhone cũng chạy JavaScriptCore, bất kể nó tự nhận là trình duyệt nào. Từ iOS 17.4, Đạo luật Thị trường Kỹ thuật số của EU đã cho phép dùng engine khác trên iOS, nhưng trên thực tế gần như mọi trình duyệt iOS vẫn xuất xưởng với WebKit và JavaScriptCore.
Hai trình duyệt có thể dùng chung engine dựng hình nhưng khác engine JavaScript không?
Trong số các trình duyệt phổ biến hiện nay thì không — mỗi hãng đều phát hành engine dựng hình và engine JavaScript của mình như một cặp cố định (Blink+V8, Gecko+SpiderMonkey, WebKit+JavaScriptCore). Về mặt kỹ thuật thì việc trộn lẫn chúng là khả thi, vì đây là các dự án tách biệt, nhưng chưa trình duyệt phổ biến nào làm vậy.
Kết luận
Engine trình duyệt và engine JavaScript không phải là một, dù cùng một nhóm nhỏ các hãng xây dựng cả hai, và trình duyệt thường chỉ được gọi bằng một cái tên duy nhất. Blink ghép với V8, Gecko với SpiderMonkey, WebKit với JavaScriptCore — ba cặp thực sự độc lập, đảm nhận hai công việc thực sự độc lập, phối hợp với nhau trong mỗi lần tải trang. Vì engine JavaScript để lại dấu vết hành vi riêng, tách biệt hoàn toàn với bất cứ điều gì engine dựng hình tiết lộ, một phép kiểm tra thực sự đáng tin cho câu hỏi "đây là trình duyệt gì" phải xem xét cả hai nửa. Hãy chạy công cụ kiểm tra nhân trình duyệt của BrowserInsight để xem trình duyệt của bạn thực sự đang chạy cặp engine nào.
Đọc thêm:


