หากต้องการป้องกันไม่ให้สคริปต์ที่เป็นอันตรายละเมิด API ที่มีความละเอียดอ่อน เช่น ป๊อปอัป เต็มหน้าจอ เบราว์เซอร์จะควบคุมการเข้าถึง API เหล่านั้นผ่านการเปิดใช้งานผู้ใช้ การเปิดใช้งานของผู้ใช้ คือสถานะของเซสชันการท่องเว็บที่เกี่ยวข้องกับการดำเนินการของผู้ใช้ นั่นคือ สถานะ "ใช้งานอยู่" บ่งบอกว่าผู้ใช้กำลังโต้ตอบกับหน้าเว็บอยู่หรือโต้ตอบเสร็จสิ้นตั้งแต่การโหลดหน้าเว็บ ท่าทางสัมผัสของผู้ใช้เป็นคํายอดนิยมแต่ทําให้เข้าใจผิดสําหรับแนวคิดเดียวกัน ตัวอย่างเช่น ท่าทางสัมผัสการตวัดนิ้วหรือตวัดนิ้วของผู้ใช้ไม่ได้เปิดใช้งานหน้าเว็บ จึงไม่เรียกใช้งานผู้ใช้จากวัตถุประสงค์ของสคริปต์
เบราว์เซอร์หลักๆ ในปัจจุบันมีพฤติกรรมที่แตกต่างกันไปอย่างมากเกี่ยวกับวิธีที่การเปิดใช้งานของผู้ใช้จะควบคุม API ที่ป้องกันการเปิดใช้งาน ใน Chrome การใช้งานเป็นไปตามโมเดลที่ใช้โทเค็นซึ่งมีความซับซ้อนเกินกว่าจะกำหนดลักษณะการทำงานที่สอดคล้องกันใน API ที่มีการจำกัดการเปิดใช้งานทั้งหมด ตัวอย่างเช่น Chrome อนุญาตการเข้าถึง API ที่มีการจำกัดการเปิดใช้งานได้ผ่าน
postMessage()
และ
การเรียก setTimeout()
รวมถึงการ
สัญญา, XHR,การโต้ตอบกับเกมแพด ฯลฯ โปรดทราบว่าบางโค้ด
เหล่านี้เป็นที่นิยมแต่เป็นข้อบกพร่องที่เกิดขึ้นมานานแล้ว
ในเวอร์ชัน 72 Chrome จะจัดส่งการเปิดใช้งานผู้ใช้เวอร์ชัน 2 ซึ่งทำให้ความพร้อมในการเปิดใช้งานผู้ใช้เสร็จสมบูรณ์สำหรับ API ที่มีการจำกัดการเปิดใช้งานทั้งหมด วิธีนี้ช่วยแก้ปัญหาความไม่สอดคล้องกันที่กล่าวถึงข้างต้น (และอีกมากมาย เช่น MessageChannels) ซึ่งเราเชื่อว่าจะช่วยให้พัฒนาเว็บเกี่ยวกับการเปิดใช้งานผู้ใช้ได้ง่ายขึ้น ยิ่งไปกว่านั้น การใช้งานใหม่นี้ยังใช้ข้อมูลอ้างอิงสำหรับข้อกำหนดใหม่ที่เสนอมา ซึ่งมีเป้าหมายเพื่อนำเบราว์เซอร์ทั้งหมดมารวมกันในระยะยาว
การเปิดใช้งานผู้ใช้ v2 ทำงานอย่างไร
API ใหม่จะคงสถานะการเปิดใช้งานผู้ใช้แบบ 2 บิตไว้ทุกๆ ออบเจ็กต์ window
ในลำดับชั้นของเฟรม ได้แก่ บิตติดหนึบสำหรับสถานะการเปิดใช้งานของผู้ใช้ในอดีต (หากเฟรมเคยเห็นการเปิดใช้งานของผู้ใช้) และบิตชั่วคราวสำหรับสถานะปัจจุบัน (หากเฟรมเห็นการเปิดใช้งานผู้ใช้ในเวลาประมาณ 1 วินาที) บิตติดหนึบจะไม่รีเซ็ตระหว่างอายุการใช้งานของเฟรมหลังจากตั้งค่าแล้ว ระบบจะตั้งค่าบิตชั่วคราวสำหรับทุกการโต้ตอบของผู้ใช้ และจะรีเซ็ตหลังจากช่วงหมดอายุ (ประมาณ 1 วินาที) หรือผ่านการเรียก API ที่ใช้การเปิดใช้งาน (เช่น window.open()
)
โปรดทราบว่า API ที่มีการจำกัดการเปิดใช้งานต่างกันจะอาศัยการเปิดใช้งานของผู้ใช้ด้วยวิธีที่ต่างกัน และ API ใหม่จะไม่เปลี่ยนแปลงลักษณะการทำงานของ API เหล่านี้ เช่น อนุญาตให้มีป๊อปอัปเพียง 1 ป๊อปอัปต่อการเปิดใช้งานของผู้ใช้ 1 ครั้ง เนื่องจาก window.open()
ใช้การเปิดใช้งานของผู้ใช้เหมือนที่เคยเป็นมาก่อน Navigator.prototype.vibrate()
จะยังคงมีผลหากเฟรม (หรือเฟรมย่อยใดๆ ของเฟรมนั้น) เคยเห็นการดำเนินการของผู้ใช้ เป็นต้น
สิ่งที่เปลี่ยนแปลงไป
- การเปิดใช้งานผู้ใช้เวอร์ชัน 2 ทำให้แนวคิดของระดับการเข้าถึงการเปิดใช้งานของผู้ใช้ข้ามขอบเขตของเฟรมเป็นทางการ กล่าวคือ การโต้ตอบของผู้ใช้กับเฟรมหนึ่งๆ จะเปิดใช้เฟรมทั้งหมดที่มีเฟรม (และเฉพาะเฟรมเหล่านั้นเท่านั้น) โดยไม่คำนึงถึงต้นทาง (ใน Chrome 72 เรามีวิธีแก้ปัญหาชั่วคราวเพื่อขยายระดับการเข้าถึงไปยังเฟรมต้นทางเดียวกันทั้งหมด เราจะนำวิธีแก้ปัญหานี้ออกเมื่อมีวิธีส่งการเปิดใช้งานผู้ใช้ไปยังเฟรมย่อยอย่างชัดแจ้ง)
- เมื่อมีการเรียก API ที่ป้องกันการเปิดใช้งานจากเฟรมที่เปิดใช้งาน แต่นอกโค้ดเครื่องจัดการเหตุการณ์ API ดังกล่าวจะทำงานตราบใดที่สถานะการเปิดใช้งานของผู้ใช้เป็น "ใช้งานอยู่" (เช่น ยังไม่หมดอายุหรือไม่มีการใช้งาน) ก่อนการเปิดใช้งานของผู้ใช้เวอร์ชัน 2 จะล้มเหลวโดยไม่มีเงื่อนไข
- การโต้ตอบของผู้ใช้ที่ไม่มีการใช้งานหลายรายการภายในช่วงเวลาหมดอายุจะรวมกันเป็นการเปิดใช้งานครั้งเดียวที่สอดคล้องกับการโต้ตอบสุดท้าย
ตัวอย่างความสอดคล้องใน API ที่มีการจำกัดการเปิดใช้งาน
ต่อไปนี้เป็นตัวอย่าง 2 รายการที่มีหน้าต่างป๊อปอัป (เปิดด้วย window.open()
) ที่แสดงให้เห็นว่าการเปิดใช้งานผู้ใช้เวอร์ชัน 2 ทำให้ลักษณะการทำงานของ API ที่มีการจำกัดการเปิดใช้งานนั้นสอดคล้องกันอย่างไร
การเรียกใช้ setTimeout()
ที่ผูกไว้
ตัวอย่างนี้มาจากการสาธิต setTimeout()
หากตัวแฮนเดิล click
พยายามเปิดป๊อปอัปภายใน 1 วินาที ตัวแฮนเดิลดังกล่าวจะดำเนินการสำเร็จไม่ว่าโค้ดจะ "เขียน" ล่าช้าอย่างไร การเปิดใช้งานผู้ใช้เวอร์ชัน 2 เป็นไปตามความคาดหวังนี้ ดังนั้นเครื่องจัดการเหตุการณ์แต่ละรายการต่อไปนี้จึงเปิดป๊อปอัปใน click
(โดยมีความล่าช้า 100 มิลลิวินาที)
function popupAfter100ms() {
setTimeout(callWindowOpen, 100);
}
function asyncPopupAfter100ms() {
setTimeout(popupAfter100ms, 0);
}
someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);
หากไม่เปิดใช้งานผู้ใช้ v2 เครื่องจัดการเหตุการณ์ที่ 2 จะล้มเหลวในทุกเบราว์เซอร์ที่เราทดสอบ (แม้แต่ลิงก์แรกก็ไม่สำเร็จ ในบางกรณี)
การเรียกใช้ postMessage()
ข้ามโดเมน
นี่คือตัวอย่างจากการสาธิต postMessage()
สมมติว่าตัวแฮนเดิล click
ในเฟรมย่อยแบบข้ามต้นทางส่งข้อความ 2 ข้อความไปยังเฟรมหลักโดยตรง เฟรมหลักควรเปิดป๊อปอัปได้เมื่อได้รับข้อความใดข้อความหนึ่งต่อไปนี้ (ไม่ใช่ทั้ง 2 ข้อความ)
// 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);
});
หากไม่เปิดใช้งานผู้ใช้เวอร์ชัน 2 เฟรมหลักจะเปิดป๊อปอัปไม่ได้เมื่อได้รับข้อความที่ 2 แม้แต่ข้อความแรกก็จะไม่สำเร็จหาก "เชน" กับเฟรมแบบข้ามต้นทางอีกเฟรม (กล่าวคือ ผู้รับคนแรกส่งต่อข้อความไปยังอีกเฟรมหนึ่ง)
ซึ่งใช้ได้กับการเปิดใช้งานผู้ใช้เวอร์ชัน 2 ทั้งในรูปแบบเดิมและในเชน