Bỏ qua menu, tới nội dung
Thành viên The MONA Group
Thành viên The MONA Group
Sản phẩm và viết theo yêu cầu
1900 636 648Tư vấn miễn phí, 8h–22h
MONA OS 2.0Hệ thống quản trị MONA tự phát triển, nối 7 phần mềm nội bộ về một dữ liệu, có Gấu Quản Trị trả lời câu hỏi điều hành.7 phần mềm1 trục dữ liệu8 AI AgentXem MONA OS hoạt động
Dự án giáo dụcThe IELTS WorkshopPhần mềm quản lý chuỗi 11 cơ sở luyện thi, kèm app cho học viên20238 tháng12 ngườiXem hồ sơ dự án Xem thêm dự án Khoa Y
Dự án bán lẻHình minh hoạ gấu MONA bán trang sức tại quầy kính, cạnh màn hình bán hàng và tồn khoNgọc Thành JewelryPhần mềm quản lý chuỗi cửa hàng trang sức, tích hợp xuất hoá đơn20246 tháng7 ngườiXem hồ sơ dự án
Dự án sản xuấtHình minh hoạ gấu MONA hướng dẫn công nhân học bài trên phần mềm đào tạo trong phòng họp nhà máyPolytex Far Eastern Việt NamPhần mềm đào tạo công nhân viên chạy trong mạng nội bộ nhà máy20234 tháng8 ngườiXem hồ sơ dự án
Dự án nhân sựHình minh hoạ gấu MONA đứng lớp đào tạo cho bác sĩ, điều dưỡng bằng phần mềm học trực tuyếnBệnh viện Hoàn Mỹ Sài GònViệc đào tạo nhân sự chuyển từ Excel sang phần mềm MONA viết riêng20247 tuần13 moduleXem hồ sơ dự án
Sản phẩm có sẵnHình minh hoạ gấu MONA phục vụ bàn, khách quét mã QR trên bàn để gọi mónnhahang.aiGọi món tại bàn bằng mã QR, kết nối bếp, quản lý bàn, định lượng nguyên liệuNhà hàngQuán ănChuỗi ăn uốngXem nhahang.ai
Sản phẩm có sẵnMONA HouseTự lập hoá đơn tiền phòng, gửi mã QR qua Zalo, báo có khi khách chuyển khoảnNhà trọCăn hộ cho thuêXem MONA House
Sản phẩm có sẵnMONA NHTQĐặt hàng Taobao, 1688, Tmall, ví khách hàng và theo dõi vận đơn cho công ty nhập hàngTaobao1688TmallXem MONA NHTQ
Tư vấn miễn phí, 8h–22h mỗi ngày
Kiến thức phần mềm

Phát hiện chuyển tab khi thi online: Page Visibility API làm được gì và không làm được gì

Lê Minh NhựtCập nhật · Đăng · 10 phút đọc

Phát hiện chuyển tab khi thi online: Page Visibility API làm được gì và không làm được gì

Trong lần chạy thật của MONA trên Chrome 156 và macOS 14.4.1 ngày 29/10/2026, visibilitychange báo “hidden” khi trang thi bị chuyển sang tab khác, thu nhỏ cửa sổ hoặc bị một cửa sổ khác che kín. Sự kiện blur của cửa sổ kêu trong cả ba trường hợp đó, kêu cả khi tiêu điểm sang một ứng dụng khác, và kêu nhầm khi thí sinh chỉ bấm vào một iframe nằm trong chính trang thi. Khi một cửa sổ khác chỉ che nửa trang, visibilitychange im lặng và chỉ còn blur; một chiếc điện thoại đặt cạnh không làm sự kiện nào kêu (suy ra từ cách hai sự kiện gắn với cửa sổ trình duyệt, MONA chưa thử bằng điện thoại thật). Vì vậy Page Visibility API chỉ là một tín hiệu để ghi lại, không đủ để kết luận gian lận.

Trang thử ghi bốn loại sự kiện kèm trạng thái của trang

Trang thử gồm một nút vào toàn màn hình, một ô nhập, một iframe và một khung nhật ký. Mỗi sự kiện in ra một dòng có giây kể từ lúc tải, tên sự kiện, document.visibilityState, document.hasFocus() và trạng thái toàn màn hình. Đây là toàn bộ file lab.html đã chạy trong các lần thử bên dưới:

<!doctype html>
<html lang="vi"><meta charset="utf-8"><title>Lab chuyển tab</title>
<body style="font:16px system-ui;margin:20px">
<button id="fs">Vào toàn màn hình</button>
<input id="ans" placeholder="Ô trả lời"> 
<iframe id="fr" srcdoc="<input id=x placeholder='ô trong iframe'>" style="width:260px;height:50px"></iframe>
<pre id="log" style="background:#eef;padding:8px;min-height:200px"></pre>
<script>
const t0 = performance.now();
const logEl = document.getElementById("log");
window.__log = [];
function write(name) {
  const line = [
    ((performance.now() - t0) / 1000).toFixed(2).padStart(7) + "s",
    name.padEnd(17),
    "state=" + document.visibilityState,
    "hasFocus=" + document.hasFocus(),
    "fullscreen=" + (document.fullscreenElement ? "yes" : "no"),
  ].join("  ");
  window.__log.push(line);
  logEl.textContent += line + "\n";
}
document.addEventListener("visibilitychange", () => write("visibilitychange"));
window.addEventListener("blur", () => write("window.blur"));
window.addEventListener("focus", () => write("window.focus"));
document.addEventListener("fullscreenchange", () => write("fullscreenchange"));
document.getElementById("fs").onclick = () => document.documentElement.requestFullscreen();
write("load");
</script></body></html>

Muốn chạy lại, làm theo các bước sau:

  1. Lưu đoạn mã trên thành lab.html.
  2. Chạy python3 -m http.server 5181 trong thư mục chứa file.
  3. Mở http://localhost:5181/lab.html trong Chrome.
  4. Làm lần lượt từng thao tác trong bảng ở phần sau, rồi đọc khung nhật ký.

Hai trường visibilityState và hasFocus() cho biết trang đang ở trạng thái nào trong ba trạng thái mà tài liệu Page Lifecycle của Chrome định nghĩa: active (đang hiện và có tiêu điểm), passive (đang hiện nhưng không có tiêu điểm) và hidden (không hiện). Hai sự kiện focus và blur chuyển trang giữa active và passive, còn visibilitychange chuyển giữa passive và hidden.

Nhật ký thật khi chuyển tab, che cửa sổ và thu nhỏ

MONA mở Chrome 156.0.8078.48 bằng hồ sơ riêng và điều khiển nó qua giao thức CDP (Chrome DevTools Protocol) thô: kích hoạt tab, mở cửa sổ mới ở vị trí và kích thước định sẵn, thu nhỏ cửa sổ. Các đoạn sau là đầu ra nguyên văn của script điều khiển, cho thao tác 1 (chuyển tab), 2 (cửa sổ đặt cạnh), 4 (che kín), 5 (Finder) và 8 (thu nhỏ):

### 1. open a new tab in the same window (it takes the foreground)
   2.91s  window.blur        state=visible  hasFocus=false  fullscreen=no
   2.92s  visibilitychange   state=hidden  hasFocus=false  fullscreen=no

### 2. open a second Chrome window beside the exam window (no overlap)
   6.12s  window.blur        state=visible  hasFocus=false  fullscreen=no

### 4. second window covers the exam window completely
  12.41s  window.blur        state=visible  hasFocus=false  fullscreen=no
  13.52s  visibilitychange   state=hidden  hasFocus=false  fullscreen=no

### 5. switch to another application (Finder activated)
  15.83s  window.blur        state=visible  hasFocus=false  fullscreen=no

### 8. minimize the window (CDP Browser.setWindowBounds)
  46.61s  window.blur        state=visible  hasFocus=false  fullscreen=no
  46.61s  visibilitychange   state=hidden  hasFocus=false  fullscreen=no

Nhật ký cho thấy ba điều. Khi chuyển tab, blur (2,91 giây) và visibilitychange sang hidden (2,92 giây) đến cách nhau 0,01 giây. Khi một cửa sổ khác che kín trang, blur đến lúc 12,41 giây nhưng hidden chỉ đến lúc 13,52 giây, chậm khoảng 1,1 giây vì Chrome phải nhận ra cửa sổ bị che. Khi khôi phục cửa sổ, focus đến lúc 14,03 giây khi visibilityState vẫn là hidden, rồi 0,09 giây sau mới có visibilitychange sang visible. Trang thi tính giờ rời tab từ visibilitychange sẽ chậm hơn mốc blur đúng khoảng đó.

Nhật ký sự kiện thật của trang thử khi chuyển tab, đổi cửa sổ và vào toàn màn hình
Toàn bộ 27 dòng nhật ký của hai đợt thử trên Chrome 156, chụp từ chính trang lab.html ngày 29/10/2026.

Nếu kiểm thử trang thi bằng Playwright, hãy kiểm lại trước khi tin kết quả. MONA chạy cùng kịch bản (chuyển tab, mở cửa sổ khác, che kín, kích hoạt Finder) qua Playwright trên Chrome có giao diện, và nhật ký không ghi một dòng blur hay visibilitychange nào, trong khi cùng thao tác qua CDP thô thì có. Nguyên nhân MONA chưa xác minh, vì vậy các số đo trong bài lấy từ lần chạy bằng CDP thô. Khi kiểm thử trang thi tự động, hãy so một lần chạy bằng tay hoặc CDP thô với lần chạy bằng công cụ.

Chuẩn chỉ cho phép hai trạng thái, và Chrome coi che nửa cửa sổ là vẫn hiện

Mục 6.2 của chuẩn HTML của WHATWG (nhóm duy trì chuẩn HTML) giao cho trình duyệt quyết định trạng thái hiển thị của hệ thống. Ví dụ trong chuẩn là cửa sổ bị thu nhỏ, tab nằm nền hoặc bộ chuyển tác vụ che trang. Tài liệu chỉ có hai giá trị, “hidden” và “visible”, và visibilitychange chỉ bắn khi giá trị thật sự đổi. Chuẩn không viết quy tắc bắt buộc nào cho cửa sổ bị che, do đó “che bao nhiêu thì tính là hidden” do từng trình duyệt chọn.

Mục 6.2 Page visibility trong chuẩn HTML của WHATWG, định nghĩa trạng thái hidden và visible
Mục 6.2 Page visibility trong chuẩn HTML của WHATWG: hai giá trị, giá trị ban đầu là hidden, và bước bắn visibilitychange. WHATWG HTML Standard, chụp ngày 29/10/2026.

Trang MDN (Mozilla Developer Network) về Page Visibility API liệt kê ba tình huống làm visibilitychange kích hoạt: thu nhỏ cửa sổ, chuyển sang tab khác, và tài liệu bị cửa sổ khác che hoàn toàn. Số đo của MONA khớp ba tình huống này và thêm một mốc cụ thể: trên Chrome 156 và macOS 14.4.1, cửa sổ che nửa trang thi không làm trang thành hidden (bước 3 của nhật ký, chỉ có blur). Firefox, Safari, Edge và Windows chưa được thử.

Trang MDN về Page Visibility API nêu ba tình huống làm visibilitychange kích hoạt
Đoạn “Concepts and usage” của MDN nêu ba tình huống kích hoạt visibilitychange. MDN Web Docs, chụp ngày 29/10/2026.

Blur báo nhiều hơn visibilitychange, kể cả những lần thí sinh không làm gì sai

MDN gọi blur và focus là “imperfect proxy” (đại diện chưa hoàn hảo) cho việc trang bị ẩn: chúng cho biết cửa sổ mất tiêu điểm, chưa chắc trang đã bị ẩn. Bảng trạng thái của Chrome mô tả đúng khoảng trống này, vì trang passive đang hiện nhưng không có tiêu điểm:

Bảng trạng thái active, passive, hidden của Page Lifecycle API trên Chrome for Developers
Bảng trạng thái active, passive và hidden kèm sự kiện chuyển trạng thái. Chrome for Developers, chụp ngày 29/10/2026.

Trường hợp báo nhầm MONA đo được là bấm vào iframe nằm trong trang thi, chẳng hạn khung nhúng video hoặc khung chấm điểm. Dòng nhật ký có hasFocus=true và state=visible nhưng vẫn có blur:

### 6. move keyboard focus into an iframe on the same page
  35.24s  window.blur        state=visible  hasFocus=true  fullscreen=no

Nếu trang thi có iframe, đừng tính mỗi blur là một lần rời trang. Kiểm document.hasFocus() ngay trong xử lý blur cũng chưa đủ, vì như dòng trên cho thấy, giá trị này vẫn là true khi tiêu điểm nằm trong iframe cùng trang. Ghi cả hai trường vào nhật ký, như trang thử đã làm, để người chấm tự phân biệt hai trường hợp.

Toàn màn hình cho biết lúc nào thoát, không cho biết thoát vì lý do gì

Nút trong lab.html gọi document.documentElement.requestFullscreen(), và fullscreenchange bắn cả lúc vào lẫn lúc ra. MDN về Fullscreen API cho biết người dùng thoát được bất cứ lúc nào bằng Esc hoặc F11, và chuyển tab hay Alt-Tab cũng thoát toàn màn hình. Nhật ký khi MONA mở một tab mới lúc trang đang toàn màn hình:

### 7. requestFullscreen() with a user gesture
  38.28s  fullscreenchange   state=visible  hasFocus=true  fullscreen=yes

### 7c. new tab while in fullscreen
  41.40s  fullscreenchange   state=visible  hasFocus=true  fullscreen=no
  41.40s  window.blur        state=visible  hasFocus=false  fullscreen=no
  41.40s  visibilitychange   state=hidden  hasFocus=false  fullscreen=no

Ba sự kiện đến cùng một mốc 41,40 giây, theo thứ tự fullscreenchange, blur, visibilitychange, trang thi nhận được cả ba và có thể ghép chúng thành một lần rời tab. Phím Esc thật thì MONA chưa thử: macOS từ chối cho script gửi phím, và lệnh Escape gửi qua CDP không thoát được toàn màn hình (bước 7b: sau lệnh vẫn là fs=True). Vì vậy chưa có số đo cho việc fullscreenchange đến một mình, không kèm blur hay visibilitychange.

Bảng các tình huống: sự kiện nào bắt được, sự kiện nào bỏ sót

Bảng dưới gộp mọi thao tác đã chạy và những tình huống chưa chạy. Cột cuối nói rõ mỗi ô lấy từ lần chạy thật, từ tài liệu hay chưa có số đo:

Tình huống visibilitychange blur Mức chắc chắn
Chuyển sang tab khác trong cùng cửa sổ hidden Có Chạy thật, Chrome 156
Thu nhỏ cửa sổ trình duyệt hidden Có Chạy thật, Chrome 156
Cửa sổ khác che kín trang thi hidden, chậm khoảng 1,1 giây Có Chạy thật, Chrome 156
Cửa sổ khác che nửa trang thi Không Có Chạy thật, Chrome 156
Cửa sổ khác đặt cạnh, không che Không Có, lúc tiêu điểm sang cửa sổ đó Chạy thật, Chrome 156
Chuyển sang ứng dụng khác (Finder) Không, khi cửa sổ ứng dụng đó không che trang Có Chạy thật, Chrome 156
Bấm vào iframe trong chính trang thi Không Có (báo nhầm, hasFocus() vẫn là true) Chạy thật, Chrome 156
Điện thoại hay máy thứ hai đặt cạnh Không có sự kiện nào gắn với thiết bị đó Không có sự kiện nào gắn với thiết bị đó Chưa thử bằng thiết bị thật; suy ra từ việc hai sự kiện chỉ gắn với cửa sổ trình duyệt
Chia đôi màn hình (Split View, Snap) Chưa có số đo Chưa có số đo Chưa thử; gần nhất là dòng “che nửa trang”
Chuyển ứng dụng trên điện thoại hay máy tính bảng hidden theo tài liệu Chrome Chưa có số đo Tài liệu Page Lifecycle của Chrome; chưa thử trên máy thật

Hai dòng để lại ít dấu nhất là thiết bị thứ hai, không có sự kiện nào, và cửa sổ che nửa trang, chỉ có một blur lẻ. Mọi con số trong bảng đo trên một máy, một phiên bản Chrome và một hệ điều hành; đổi trình duyệt hoặc hệ điều hành thì phải đo lại. Với thiết bị thứ hai, nhật ký của trang thi trông giống hệt một thí sinh ngồi yên làm bài.

Đọc nhật ký thế nào để quyết định ghi hay chặn

Nếu cần một quy tắc dựa trên các số đo trên, MONA khuyên ghi mọi sự kiện kèm giờ và cho người chấm xem, thay vì tự động hủy bài. Đây là khuyến nghị của bài, rút ra từ nhật ký phía trên, chưa phải kết quả đo trên bài thi thật. Các mẫu sau xuất hiện trong nhật ký đã chạy và cách đọc từng mẫu:

Mẫu trong nhật ký Cách đọc
blur rồi visibilitychange sang hidden cách nhau dưới 0,1 giây (2,91 và 2,92 giây) Rời tab hoặc thu nhỏ cửa sổ. Tín hiệu mạnh.
blur rồi hidden cách nhau khoảng 1 giây (12,41 và 13,52 giây) Cửa sổ khác che kín trang thi. Tín hiệu mạnh.
fullscreenchange sang “no”, blur, hidden cùng một mốc (41,40 giây) Rời tab khi đang toàn màn hình. Tín hiệu mạnh.
blur đơn lẻ, state=visible, hasFocus=false (15,83 giây) Tiêu điểm sang ứng dụng hoặc cửa sổ khác mà trang vẫn hiện. Tín hiệu yếu, ghi lại để người chấm xem.
blur đơn lẻ, state=visible, hasFocus=true (35,24 giây) Tiêu điểm vào iframe trong trang. Không tính là vi phạm.

Điện thoại bên cạnh và cửa sổ che nửa trang không để lại mẫu nào ở bảng trên, vì vậy cần biện pháp khác ngoài trình duyệt, ví dụ đề thi xáo trộn theo thí sinh hoặc người coi thi. Trang phần mềm chống gian lận thi online giới thiệu các phần mềm làm việc đó, danh sách phần mềm thi trắc nghiệm online cho biết mỗi phần mềm có những tính năng nào, và bài phần mềm chấm thi trắc nghiệm nói về khâu chấm sau khi thi xong.

Hỏi đáp

Page Visibility API chạy được trên những trình duyệt nào?

MDN ghi API này ở mức “Baseline widely available” khi MONA xem trang ngày 29/10/2026, tức chạy trên các trình duyệt chính. Số đo trong bài chỉ có Chrome 156; Firefox, Safari và Edge chưa được thử.

Trên điện thoại, chuyển sang ứng dụng khác có làm trang thành hidden không?

Tài liệu Page Lifecycle của Chrome ghi chuyển ứng dụng trên di động là một lý do làm visibilitychange kích hoạt, và nói hidden là trạng thái cuối cùng mà trang còn quan sát chắc chắn được, nhất là trên di động. MONA chưa thử trên điện thoại thật, vì vậy bảng ở trên để trống ô đó.

Có cần tự động nộp bài khi blur kêu không?

Đừng dựa riêng vào blur, vì nhật ký trên cho thấy bấm vào iframe trong trang cũng làm blur kêu với hasFocus=true. Ghi sự kiện kèm giờ, đếm riêng số lần hidden, và để người chấm quyết định.

Trang thi ép thí sinh ở lại toàn màn hình được không?

Không ép được. Trong lần thử, requestFullscreen() chạy với cử chỉ người dùng giả lập qua CDP và vào được toàn màn hình, còn MDN ghi người dùng thoát bất cứ lúc nào bằng Esc hoặc F11. Trang chỉ nhận fullscreenchange sau khi việc thoát đã xảy ra.

fullscreenchange có cho biết thí sinh bấm Esc không?

Không có số đo cho Esc thật. Sự kiện chỉ nói trạng thái toàn màn hình đã đổi: lần thoát do mở tab mới trong nhật ký cũng gọi đúng sự kiện này, kèm blur và visibilitychange.

MONA LMS (LMS là hệ thống quản lý học tập, xem LMS là gì) là phần mềm quản lý trường học và trung tâm giáo dục của MONA. Trang thi đang có ô nhập hay khung nhúng khiến blur báo nhầm thì gọi hotline 1900 636 648 kèm nhật ký sự kiện của một lượt thi, để xem mẫu nào cần đếm và mẫu nào bỏ. Trang thi viết riêng cho một trường hoặc một doanh nghiệp thuộc nhóm phần mềm viết theo yêu cầu của MONA.

Đội MONA gặp khách hàng để khảo sát quy trình trước khi làm phần mềm

Kiến thức phần mềm

Gửi MONA quy trình đang chạy để nhận hướng làm phần mềm

Đội MONA Software khảo sát nghiệp vụ, thiết kế cơ sở dữ liệu, lập trình, bàn giao và đi cùng qua giai đoạn vận hành thử.