הפעלה עקבית של משתמשים בכל ממשקי ה-API

Mustaq Ahmed
Joe Medley
Joe Medley

כדי למנוע מסקריפטים זדוניים לנצל לרעה ממשקי API רגישים כמו חלונות קופצים, מסך מלא וכו', הדפדפנים שולטים בגישה לממשקי ה-API האלה באמצעות הפעלה. הפעלת משתמשים היא המצב של סשן הגלישה ביחס לדרישות לפעולות משתמש: "פעיל" מרמז בדרך כלל שהמשתמש מבצע אינטראקציה כרגע עם הדף, או השלים אינטראקציה מאז הדף טעינה. תנועת משתמש היא מונח פופולרי אך מטעה שקשור לאותו רעיון. עבור לדוגמה, תנועת החלקה או החלקה של משתמש לא מפעילה דף, מבחינת הסקריפט, לא מדובר בהפעלה של משתמש.

בדפדפנים נפוצים כיום יש התנהגות שונה באופן משמעותי לגבי הפעלת המשתמשים שולטת בממשקי ה-API המוגבלים להפעלה. ההטמעה ב-Chrome מבוססת על במודל מבוסס-אסימונים שהוכיח שהוא מורכב מדי מכדי להגדיר בכל ממשקי ה-API שמחייבים הפעלה. לדוגמה, Chrome מתן גישה חלקית לממשקי API מוגבלים להפעלה דרך postMessage() ו- setTimeout() שיחות; והפעלה של משתמשים נתמכים באמצעות Promise, XHR, אינטראקציה עם לחצני המשחק וכו'. שימו לב: הם באגים ותיקים אך פופולריים.

בגרסה 72, Chrome שולח את גרסה 2 של הפעלת המשתמש, שהופכת את המשתמשים זמינות ההפעלה הושלמה עבור כל ממשקי ה-API שמוגבלים להפעלה. הפעולה הזו פותרים את הבעיה חוסר ההתאמה שצוינו למעלה (ועוד כמה, כמו MessageChannels), שעשוי להקל על האינטרנט סביב הפעלת משתמשים. בנוסף, ההטמעה החדשה מספקת יישום של קובץ עזר מפרט חדש שמטרתו לרכז את כל הדפדפנים יחד בטווח הארוך.

איך פועלת גרסה 2 של הפעלת המשתמש?

ה-API החדש שומר על מצב הפעלה של משתמש בשתי ביט בכל אובייקט window בהיררכיית הפריימים: ביט במיקום קבוע למצב ההפעלה ההיסטורי של המשתמש (אם ראה אי פעם הפעלה של משתמש), וביט זמני במצב הנוכחי (אם פריים מזהה הפעלה של משתמש תוך כשנייה). החלק הדביק אף פעם לא מתאפסת במהלך משך החיים של המסגרת לאחר הגדרתה. החלק הזמני מוגדר בכל אינטראקציה של המשתמש, והוא מתאפס לאחר תפוגת התוקף מרווח זמן (כשנייה) או באמצעות קריאה לממשק API שצורך הפעלה (למשל, window.open()).

שימו לב שממשקי API שונים עם הגבלת הפעלה מסתמכים על הפעלת משתמשים דרכים; ה-API החדש לא משנה אף אחת מההתנהגויות האלה, שספציפיות ל-API. לדוגמה מותר להשתמש רק בחלון קופץ אחד לכל הפעלה של משתמש, כי האפליקציה window.open() צורכת הפעלת משתמש כמו בעבר, Navigator.prototype.vibrate() ממשיך להיות יעילות אם פריים (או תת-מסגרות שלו) זיהו אי פעם פעולה של משתמש, וכן הלאה.

מה משתנה?

  • גרסה 2 של הפעלת המשתמש מפשטת את המושג 'הרשאות גישה להפעלה של משתמש' מעבר לגבולות הפריים: אינטראקציה של משתמש עם מסגרת מסוימת להפעיל את כל הפריימים שמכילים (ורק את הפריימים האלה) ללא קשר המקור. (ב-Chrome 72, יש לנו פתרון זמני לצורך הרחבת חשיפה לכל הפריימים ממקור זהה. נבטל את המעקף הזה ברגע ש יש דרך להעביר במפורש את הפעלת המשתמש למסגרות משנה.)
  • כשנשלחת קריאה ל-API עם הגבלת הפעלה ממסגרת מופעלת, אבל מחוץ לקוד של הגורם המטפל באירועים, הוא יפעל כל עוד הפעלת המשתמש. המצב הוא 'פעיל' (למשל, צריכת הנתונים שלו כבר לא בתוקף. לפני המשתמש את גרסה 2 היא תיכשל ללא תנאי.
  • אינטראקציות מרובות של משתמשים שלא נמצאות בשימוש במסגרת השילוב של מרווח זמן התפוגה להפעלה אחת שתואמת לאינטראקציה האחרונה.

דוגמאות לעקביות בממשקי API עם הגבלות הפעלה

הנה שתי דוגמאות עם חלונות קופצים (שנפתחו באמצעות window.open()) להראות איך הפעלת גרסה 2 של המשתמש מייצרת את ההתנהגות של ממשקי API מוגבלים להפעלה. עקביים.

setTimeout() שיחות כרוכות בשרשרת

מקור הדוגמה ההדגמה שלנו בנושא setTimeout(). אם handler של click מנסה לפתוח חלון קופץ בתוך שנייה, הוא צפוי ללא קשר לאופן שבו הקוד "כותב" את העיכוב. גרסה 2 של הפעלת המשתמש עומדת בדרישות את הציפייה הזאת, כך שכל אחד מהגורמים המטפלים באירועים הבאים פותח חלון קופץ click (בעיכוב של 100 אלפיות השנייה):

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

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

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

אם לא מפעילים את המשתמש בגרסה 2, הגורם המטפל באירועים השני נכשל בכל הדפדפנים, נבדק. (גם הראשון נכשל במקרים מסוימים.)

שיחות postMessage() בכמה דומיינים

הנה דוגמה מ- ההדגמה שלנו בנושא postMessage(). נניח ש-handler של click בתת-מסגרת ממקורות שונים שולח ישירות שתי הודעות למסגרת ההורה. במסגרת ההורה צריכה להיות אפשרות לפתוח חלון קופץ קבלת אחת מההודעות האלו (אבל לא את שתיהן):

// 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 של הפעלת המשתמש, גם בגרסה המקורית וגם יצירת שרשרת.