Đả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
Liên khúc Joe
Joe Medley

Để ngăn các tập lệnh độc hại lợi dụng các API nhạy cảm như cửa sổ bật lên, toàn màn hình, v.v., các trình duyệt sẽ kiểm soát quyền truy cập vào các API đó thông qua việc kích hoạt người dùng. Kích hoạt của người dùng là trạng thái của phiên duyệt web đối với các thao tác của người dùng: trạng thái "đang hoạt động" thường ngụ ý rằng người dùng hiện đ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 dễ gây hiểu lầm về cùng một ý tưởng. Ví dụ: thao tác vuốt hoặc xoay của người dùng không kích hoạt trang. Do đó, xét trên khía cạnh tập lệnh, thao tác kích hoạt của người dùng không phải là thao tác kích hoạt.

Các trình duyệt chính hiện nay cho thấy hành vi đa dạng trên phạm vi rộng về cách kích hoạt của người dùng sẽ kiểm soát các API được kiểm soát kích hoạt. Trong Chrome, quy trình triển khai dựa trên mô hình dựa trên mã thông báo hoá ra quá phức tạp để xác định hành vi nhất quán trên tất cả các API được kiểm soát kích hoạt. Ví dụ: Chrome đã cho phép truy cập không đầy đủ vào các API được kiểm soát kích hoạt thông qua các lệnh gọi postMessage()setTimeout(); và quá trình kích hoạt của người dùng không được hỗ trợ với các tính năng Promises, 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à các lỗi phổ biến nhưng vẫn tồn tại lâu dài.

Trong phiên bản 72, Chrome sẽ cung cấp tuỳ chọn Kích hoạt người dùng phiên bản 2, giúp hoàn tất khả năng kích hoạt người dùng cho tất cả các API được kiểm soát kích hoạt. Điều này giúp giải quyết những điểm không thống nhất nêu trên (và một số điểm khác, như MessageChannels), mà chúng tôi tin sẽ giúp quá trình phát triển web trở nên dễ dàng hơn khi người dùng kích hoạt. Hơn nữa, cách triển khai mới cung cấp phương thức triển khai tham chiếu cho một quy cách mới được đề xuất nhằm tập hợp tất cả trình duyệt về lâu dài.

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 của người dùng 2 bit ở 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 của người dùng trước đây (nếu một khung đã từng thấy người dùng kích hoạt) và một bit tạm thời cho trạng thái hiện tại (nếu một khung đã thấy người dùng kích hoạt sau khoảng một giây). Bit cố định không bao giờ được đặt lại trong suốt thời gian hoạt động của khung hình sau khi được đặt. Bit tạm thời được đặt mỗi khi người dùng tương tác 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 một lệnh gọi đến một API sử dụng kích hoạt (ví dụ: window.open()).

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

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

  • Kích hoạt người dùng phiên bản 2 chính thức hoá khái niệm về khả năng hiển thị kích hoạt của người dùng trên các ranh giới khung: giờ đây, khi người dùng tương tác với một khung cụ thể, hành độ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ỉ các 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 khả năng hiển thị cho tất cả các khung cùng nguồn gốc. Chúng tôi sẽ loại bỏ giải pháp này sau khi có cách chuyển nội dung kích hoạt của người dùng đến các khung phụ một cách rõ ràng.)
  • Khi một API kiểm soát kích hoạt được gọi từ một khung đã kích hoạt nhưng từ bên ngoài mã của trình xử lý sự kiện, API này 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 và chưa được sử dụng). Trước khi Kích hoạt người dùng phiên bản 2, nó sẽ bị lỗi vô điều kiện.
  • Nhiều lượt tương tác không sử dụng 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 duy nhất tương ứng với lượt tương tác cuối cùng.

Ví dụ về tính nhất quán trong các API được kiểm soát kích hoạt

Dưới đây là 2 ví dụ với cửa sổ bật lên (mở bằng window.open()) cho thấy cách tính năng Kích hoạt người dùng v2 giúp nhất quán cho hành vi của các API được kiểm soát kích hoạt.

Cuộc gọi setTimeout() đã kết nố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ở cửa sổ bật lên trong vòng một giây, thì dự kiến sẽ thành công bất kể mã "kết hợp" độ trễ như thế nào. Kích hoạt người dùng v2 đáp ứng kỳ vọng 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ó Kích hoạt người dùng v2, trình xử lý sự kiện thứ hai sẽ không hoạt động trong tất cả các trình duyệt mà chúng tôi đã kiểm thử. (Ngay cả động tác đầu tiên cũng sẽ thất bại trong một số trường hợp.)

Cuộc gọi postMessage() trên nhiều miền

Sau đây là ví dụ từ bản minh hoạ postMessage() của chúng tôi. Giả sử một trình xử lý click trong một khung phụ nhiều nguồn gốc gửi hai thông điệp trực tiếp đến khung mẹ. Khung mẹ phải mở được cửa sổ bật lên khi nhận được một trong hai thông báo này (nhưng không được 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ó Kích hoạt người dùng phiên bản 2, khung gốc 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 sẽ thất bại nếu được "liên kết" với một khung nhiều nguồn gốc khác (nói cách khác, nếu trình nhận đầu tiên chuyển tiếp thông báo sang một khung khác).

Tính năng 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 gốc và chuỗi.