Đả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. Kích hoạt của người dùng là trạng thái của một phiên duyệt web liên quan đến 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 hiện đang tương tác với trang hoặc đã hoàn tất một hành động tương tác kể từ khi tải trang. Cử chỉ của người dùng là một cụm từ phổ biến nhưng gây hiểu lầm cho 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 và do đó, theo quan điểm của tập lệnh thì thao tác kích hoạt của người dùng thì không.

Các trình duyệt lớn hiện nay cho thấy hành vi rất khác nhau liên quan đến cách hoạt động kích hoạt của người dùng kiểm soát các API được 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 hoá ra lại quá phức tạp để xác định một hành vi nhất quán trên tất cả các API có tính năng kích hoạt. Ví dụ: Chrome đã cho phép truy cập không đầy đủ vào các API được chỉ định kích hoạt thông qua postMessage()setTimeout() lệnh gọi; và thao tác kích hoạt người dùng không được hỗ trợ với Promises, XHR, Tương tác với Gamepad, v.v. Xin lưu ý rằng một vài trong số này là các lỗi phổ biến nhưng đã tồn tại từ lâu.

Trong phiên bản 72, Chrome sẽ vận chuyển tính năng 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 của người dùng cho tất cả các API có chế độ 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, việc triển khai mới cung cấp cách triển khai tham chiếu cho một quy cách mới được đề xuất nhằm tổng hợp tất cả trình duyệt 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 2 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 trước đây của người dùng (nếu một khung hình đã 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 hình đã thấy người dùng kích hoạt trong khoảng một giây). Bit cố định không bao giờ đặt lại trong thời gian hoạt động của khung hình sau khi được đặt. Bit tạm thời được đặt trên mọi hoạt động 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 một lệnh gọi đến một API sử dụng quá trình kích hoạt (ví dụ: window.open()).

Xin lưu ý rằng các API có chế độ kích hoạt khác nhau dựa 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 này. Ví dụ: chỉ cho phép một cửa sổ bật lên cho mỗi lần kích hoạt của người dùng, vì window.open() sử dụng kích hoạt của người dùng như trước đây, Navigator.prototype.vibrate() sẽ 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 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 hình: 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 khả năng 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 phụ một cách rõ ràng.)
  • Khi một API có giới hạn 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 hay 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ẽ kết hợp 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ó chế độ kích hoạt

Dưới đây là 2 ví dụ có 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 phiên bản 2 giúp hành vi của các API được kích hoạt trở nên nhất quán.

Cuộc gọi setTimeout() theo chuỗi

Ví dụ này lấy từ bản minh hoạ setTimeout() của chúng tôi. Nếu trình xử lý click cố mở cửa sổ bật lên trong vòng một giây, thì chiến dịch đó sẽ thành công bất kể mã "kết hợp" độ trễ như thế nào. Tính năng Kích hoạt người dùng phiên bản 2 đá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ễ là 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. (Ngay cả bước đầu tiên cũng không thành công trong một số trường hợp.)

Cuộc gọi postMessage() giữa nhiều miền

Dưới đây là ví dụ từ bản minh hoạ postMessage() của chúng tôi. 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ở cửa sổ bật lên khi nhận được một trong các thông báo này (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 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 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).

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 ban đầu và theo chuỗi.