การทำให้การเปิดใช้งานของผู้ใช้สอดคล้องกันในทุก API

Mustaq Ahmed
Joe Medley
Joe Medley

หากต้องการป้องกันไม่ให้สคริปต์ที่เป็นอันตรายละเมิด API ที่มีความละเอียดอ่อน เช่น ป๊อปอัป แบบเต็มหน้าจอ เบราว์เซอร์จะควบคุมการเข้าถึง API เหล่านั้นผ่านผู้ใช้ การเปิดใช้งาน การเปิดใช้งานผู้ใช้คือสถานะของเซสชันการท่องเว็บซึ่งเกี่ยวข้องกับ กับการดำเนินการของผู้ใช้: การกระทำที่ "ใช้งานอยู่" โดยทั่วไปจะระบุว่าผู้ใช้ โต้ตอบกับหน้าเว็บอยู่ในขณะนี้ หรือมีการโต้ตอบตั้งแต่หน้าเว็บนี้ โหลด ท่าทางสัมผัสของผู้ใช้เป็นคำยอดนิยมแต่ทำให้เข้าใจผิดในแนวคิดเดียวกัน สำหรับ ตัวอย่างเช่น ท่าทางสัมผัสการปัดหรือสะบัดโดยผู้ใช้ไม่ได้เปิดใช้งานหน้าเว็บ ไม่ใช่การเปิดใช้งานผู้ใช้ ในแง่สคริปต์

เบราว์เซอร์หลักๆ ในปัจจุบันมีพฤติกรรมที่แตกต่างกันอย่างมากเกี่ยวกับการเปิดใช้งานของผู้ใช้ ควบคุม API ที่ป้องกันการเปิดใช้งาน ใน Chrome การใช้งาน บนโมเดลที่ใช้โทเค็นซึ่งมีความซับซ้อนเกินกว่าจะกำหนดความสอดคล้องกัน ใน API ที่มีการเปิดใช้งาน ตัวอย่างเช่น Chrome ได้รับ การปล่อยให้ API ที่มีการเปิดใช้งานได้ไม่สมบูรณ์ผ่านทาง postMessage() และ การโทร setTimeout() ครั้ง และการเปิดใช้งานผู้ใช้ไม่ได้ ที่รองรับ Promises XHR การโต้ตอบกับเกมแพด ฯลฯ โปรดทราบว่า เป็นข้อบกพร่องที่เป็นที่นิยมแต่มีมาอย่างยาวนาน

ในเวอร์ชัน 72 Chrome จะจัดส่ง User Activation v2 ซึ่งทำให้ ความพร้อมในการเปิดใช้งานสำหรับ API ที่มีการเปิดใช้งานทั้งหมดเสร็จสมบูรณ์ วิธีนี้จะช่วยแก้ปัญหา ความไม่สอดคล้องข้างต้น (และอื่นๆ อีกเล็กน้อย เช่น MessageChannels) ซึ่งเราเชื่อว่าจะช่วยให้เว็บมีความสะดวกยิ่งขึ้น การพัฒนาการเปิดใช้งานผู้ใช้ ยิ่งไปกว่านั้น การใช้งานรูปแบบใหม่นี้ยังช่วยให้ การใช้ข้อมูลอ้างอิงสำหรับ ข้อกำหนดใหม่ ซึ่งมีเป้าหมายที่จะรวมเบราว์เซอร์ทั้งหมดไว้ด้วยกันในระยะยาว

การเปิดใช้งานผู้ใช้ v2 ทำงานอย่างไร

API ใหม่จะคงสถานะการเปิดใช้งานผู้ใช้แบบ 2 บิตไว้ทุกครั้งที่ออบเจ็กต์ window ในลำดับชั้นเฟรม: บิตติดหนึบสำหรับสถานะการเปิดใช้งานของผู้ใช้ในอดีต (หาก เฟรมเคยเห็นการเปิดใช้งานของผู้ใช้) และบิตชั่วคราวสำหรับสถานะปัจจุบัน (หากเฟรมเห็นว่าผู้ใช้เปิดใช้งานภายใน 1 วินาที) แท่งติดหนึบ ไม่รีเซ็ตในระหว่างอายุการใช้งานของเฟรมหลังจากตั้งค่าแล้ว บิตชั่วคราว จะได้รับการตั้งค่าทุกครั้งที่มีการโต้ตอบของผู้ใช้ และจะรีเซ็ตหลังจากหมดอายุ ช่วงเวลา (ประมาณ 1 วินาที) หรือผ่านการเรียกไปยัง API ที่ใช้การเปิดใช้งาน (เช่น window.open())

โปรดทราบว่า API ที่มีการกำหนดเปิดใช้งานที่แตกต่างกันอาศัยการเปิดใช้งานของผู้ใช้ใน API วิธีการ API ใหม่จะไม่เปลี่ยนแปลงลักษณะการทำงานเฉพาะ API เหล่านี้ เช่น อนุญาตป๊อปอัปเพียง 1 รายการเท่านั้นต่อการเปิดใช้งานของผู้ใช้ เนื่องจาก window.open() ใช้เวลา การเปิดใช้งานของผู้ใช้ตามที่เคยเป็นมา Navigator.prototype.vibrate() ยังคง มีประสิทธิภาพหากเฟรม (หรือเฟรมย่อยใดๆ ของเฟรมอื่น) เคยเห็นการกระทำของผู้ใช้ เป็นต้น

สิ่งที่เปลี่ยนแปลงไป

  • การเปิดใช้งานผู้ใช้ v2 ทำให้แนวคิดของระดับการเข้าถึงการเปิดใช้งานผู้ใช้เป็นทางการ ข้ามขอบเขตของเฟรม: การโต้ตอบของผู้ใช้กับเฟรมหนึ่งๆ เปิดใช้งานทุกเฟรมที่มี (และเฉพาะเฟรมเหล่านั้น) โดยไม่คำนึงถึง (ใน Chrome 72 เรามีวิธีแก้ปัญหาชั่วคราวในการขยาย การมองเห็นเฟรมต้นทางเดียวกันทั้งหมด เราจะนำวิธีแก้ปัญหาเบื้องต้นนี้ออกเมื่อ มีวิธีที่จะ ส่งการเปิดใช้งานผู้ใช้ไปยังเฟรมย่อยอย่างชัดแจ้ง)
  • เมื่อมีการเรียก API ที่มีการกำหนดการเปิดใช้งานจากเฟรมที่เปิดใช้งานแล้ว แต่ ภายนอกโค้ดเครื่องจัดการเหตุการณ์ โค้ดจะทำงานตราบใดที่การเปิดใช้งานของผู้ใช้ สถานะเป็น "ใช้งานอยู่" (เช่น ยังไม่หมดอายุหรือไม่มีการใช้งาน) ก่อนผู้ใช้ การเปิดใช้งานเวอร์ชัน 2 จะไม่สำเร็จโดยไม่มีเงื่อนไข
  • การโต้ตอบของผู้ใช้ที่ไม่ได้ใช้งานหลายครั้งภายในช่วงเวลาหมดอายุจะผสานรวม เป็นการเปิดใช้งานครั้งเดียวที่ตรงกับการโต้ตอบสุดท้าย

ตัวอย่างของความสม่ำเสมอของ API ที่มีการกำหนดเปิดใช้งาน

ต่อไปนี้เป็นตัวอย่าง 2 รายการที่มีหน้าต่างป๊อปอัป (เปิดโดยใช้ window.open()) ที่ แสดงให้เห็นว่า User Activation v2 สร้างลักษณะการทำงานของ API ที่มีการเปิดใช้ได้อย่างไร มีความสม่ำเสมอ

การโทร setTimeout() สายที่มีโซ่

ตัวอย่างนี้มาจาก การสาธิต setTimeout() ของเรา หากเครื่องจัดการ click พยายามเปิดป๊อปอัปภายใน 1 วินาที ก็คาดว่า ประสบความสำเร็จไม่ว่าโค้ดจะ "เขียน" อย่างไร การหน่วงเวลา พบกับการเปิดใช้งานผู้ใช้ v2 ดังนั้นเครื่องจัดการเหตุการณ์ต่อไปนี้แต่ละรายการจะเปิดป๊อปอัป 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);
});

หากไม่มีการเปิดใช้งานผู้ใช้ v2 เฟรมหลักจะไม่สามารถเปิดป๊อปอัปเมื่อได้รับ ข้อความที่ 2 แม้ว่าข้อความแรกจะล้มเหลวหากมีการ "เชื่อมโยง" ไปยังที่อื่น เฟรมแบบข้ามต้นทาง (กล่าวคือ หากผู้รับรายแรกส่งต่อข้อความ ไปยังอีกโดเมนหนึ่ง)

ซึ่งใช้งานได้กับ User Activation v2 ทั้งในรูปแบบเดิมและ การทำสายโซ่