Đảm bảo hoạt động kích hoạt của người dùng nhất quán trên các API

Mustaq Ahmed
Joe Medley
Joe Medley

Để ngăn các tập lệnh độc hại lạm dụng các API nhạy cảm như cửa sổ bật lên, chế độ toàn màn hình, v.v., trình duyệt sẽ kiểm soát quyền truy cập vào các API đó thông qua hoạt động kích hoạt của người dùng. Hoạt động kích hoạt người dùng là trạng thái của một phiên duyệt web liên quan đến các hành động của người dùng: trạng thái "đang hoạt động" thường ngụ ý rằng người dùng đang tương tác với trang hoặc đã hoàn tất một lượt tương tác kể từ khi tải trang. Cử chỉ của người dùng là một thuật ngữ phổ biến nhưng gây hiểu lầm về cùng một ý tưởng. Ví dụ: cử chỉ vuốt hoặc hất của người dùng không kích hoạt trang và do đó, không phải là hoạt động kích hoạt người dùng theo quan điểm của tập lệnh.

Các trình duyệt chính hiện nay cho thấy hành vi khác biệt đáng kể về cách hoạt động kích hoạt của người dùng kiểm soát các API có cổng kích hoạt. Trong Chrome, việc triển khai dựa trên mô hình dựa trên mã thông báo, nhưng mô hình này lại quá phức tạp để xác định hành vi nhất quán trên tất cả các API có cổng kích hoạt. Ví dụ: Chrome đã cho phép quyền truy cập không đầy đủ vào các API có cổng kích hoạt thông qua các lệnh gọi postMessage()setTimeout(); đồng thời không hỗ trợ việc kích hoạt người dùng bằng Promise, XHR, Tương tác với tay điều khiển trò chơi, v.v. Xin lưu ý rằng một số lỗi trong số này là lỗi phổ biến nhưng đã tồn tại t��� lâu.

Trong phiên bản 72, Chrome sẽ cung cấp User Activation v2 (Kích hoạt người dùng phiên bản 2) để hoàn tất việc cung cấp tính năng kích hoạt người dùng cho tất cả các API có cổng kích hoạt. Việc này sẽ giúp giải quyết những điểm không thống nhất nêu trên (và một vài điểm không thống nhất khác, chẳng hạn như MessageChannels). Chúng tôi tin rằng việc này sẽ giúp đơn giản hoá việc phát triển web xung quanh việc kích hoạt người dùng. Hơn nữa, phương thức triển khai mới cung cấp một phương thức triển khai tham chiếu cho một thông số kỹ thuật mới được đề xuất nhằm mục đích kết hợp tất cả trình duyệt với nhau về lâu dài.

Tính năng Kích hoạt người dùng phiên bản 2 hoạt động như thế nào?

API mới duy trì trạng thái kích hoạt người dùng hai bit tại mọi đối tượng window trong hệ phân cấp khung: một bit cố định cho trạng thái kích hoạt người dùng trước đây (nếu một khung đã từng thấy một lượt kích hoạt người dùng) và một bit tạm thời cho trạng thái hiện tại (nếu một khung đã thấy một lượt kích hoạt người dùng trong khoảng một giây). Bit cố định không bao giờ đặt lại trong suốt thời gian hoạt động của khung sau khi được đặt. Bit tạm thời được đặt trên mọi lượt tương tác của người dùng và được đặt lại sau khoảng thời gian hết hạn (khoảng một giây) hoặc thông qua lệnh gọi đến một API tiêu thụ hoạt động kích hoạt (ví dụ: window.open()).

Xin lưu ý rằng các API có cổng kích hoạt khác nhau dựa vào việc kích hoạt người dùng theo nhiều cách; API mới không thay đổi bất kỳ hành vi nào dành riêng cho API này. Ví dụ: chỉ cho phép một cửa sổ bật lên cho mỗi lượt kích hoạt người dùng vì window.open() sử dụng lượt kích hoạt người dùng như trước đây, Navigator.prototype.vibrate() tiếp tục hiệu quả nếu một khung (hoặc bất kỳ khung con nào của khung đó) từng thấy hành động của người dùng, v.v.

Điều gì sẽ thay đổi?

  • Tính năng Kích hoạt người dùng phiên bản 2 chính thức hoá khái niệm chế độ hiển thị kích hoạt của người dùng trên các ranh giới khung: giờ đây, hoạt động tương tác của người dùng với một khung cụ thể sẽ kích hoạt tất cả các khung chứa (và chỉ những khung đó) bất kể nguồn gốc của chúng. (Trong Chrome 72, chúng tôi đã có một giải pháp tạm thời để mở rộng phạm vi hiển thị cho tất cả các khung có cùng nguồn gốc. Chúng tôi sẽ xoá giải pháp này khi có cách để chuyển kích hoạt người dùng đến các khung hình phụ một cách rõ ràng.)
  • Khi một API có cổng kích hoạt được gọi từ một khung đã kích hoạt nhưng từ bên ngoài mã trình xử lý sự kiện, API đó sẽ hoạt động miễn là trạng thái kích hoạt của người dùng là "đang hoạt động" (ví dụ: chưa hết hạn hoặc chưa được sử dụng). Trước khi Kích hoạt người dùng phiên bản 2, hoạt động này sẽ không thành công vô điều kiện.
  • Nhiều lượt tương tác không dùng đến của người dùng trong khoảng thời gian hết hạn sẽ hợp nhất thành một lượt kích hoạt tương ứng với lượt tương tác gần đây nhất.

Ví dụ về tính nhất quán trong các API có cổng kích hoạt

Dưới đây là hai ví dụ về cửa sổ bật lên (mở bằng window.open()) cho thấy cách User Activation v2 giúp hành vi của các API có cổng kích hoạt nhất quán.

Các lệnh gọi setTimeout() theo chuỗi

Ví dụ này được lấy từ bản minh hoạ setTimeout() của chúng tôi. Nếu trình xử lý click cố gắng mở một cửa sổ bật lên trong vòng một giây, thì trình xử lý này dự kiến sẽ thành công bất kể mã "sắp xếp" độ trễ như thế nào. User Activation v2 đáp ứng mong đợi này, vì vậy, mỗi trình xử lý sự kiện sau đây sẽ mở một cửa sổ bật lên trên click (với độ trễ 100 mili giây):

function popupAfter100ms() {
  setTimeout(callWindowOpen, 100);
}

function asyncPopupAfter100ms() {
  setTimeout(popupAfter100ms, 0);
}

someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);

Nếu không có tính năng Kích hoạt người dùng phiên bản 2, trình xử lý sự kiện thứ hai sẽ không thành công trong tất cả các trình duyệt mà chúng tôi đã thử nghiệm. (Thậm chí lần đầu tiên cũng không thành công trong một số trường hợp.)

Lệnh gọi postMessage() trên nhiều miền

Sau đây là ví dụ về minh hoạ postMessage(). Giả sử trình xử lý click trong một khung phụ trên nhiều nguồn gốc gửi hai thông báo trực tiếp đến khung chính. Khung mẹ phải có thể mở một cửa sổ bật lên khi nhận được một trong hai thông báo sau (nhưng không phải cả hai):

// Parent frame code
window.addEventListener('message', e => {
  if (e.data === 'open_popup' && e.origin === child_origin)
    window.open('about:blank');
});

// Child frame code:
someButton.addEventListener('click', () => {
  parent.postMessage('hi_there', parent_origin);
  parent.postMessage('open_popup', parent_origin);
});

Nếu không có tính năng Kích hoạt người dùng phiên bản 2, khung mẹ sẽ không thể mở cửa sổ bật lên khi nhận được thông báo thứ hai. Ngay cả thông báo đầu tiên cũng không thành công nếu thông báo đó "được xâu chuỗi" với một khung khác trên nhiều nguồn gốc (nói cách khác, nếu dịch vụ nhận đầu tiên chuyển tiếp thông báo đến một khung khác).

Phương thức này hoạt động với tính năng Kích hoạt người dùng phiên bản 2, cả ở dạng ban đầu và với tính năng tạo chuỗi.