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

Mustaq Ahmed
Joe Medley
Joe Medley

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

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

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

איך פועלת הגרסה השנייה של User Activation?

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

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

מה משתנה?

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

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

ריכזנו כאן שתי דוגמאות עם חלונות קופצים (שנפתחים באמצעות window.open()) שממחישות איך User Activation v2 מאפשרת לקבל התנהגות עקבית של ממשקי API עם תנאי גישה שמבוססים על הפעלה.

קריאות setTimeout() מוצמדות

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

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

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

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

בלי User Activation v2, בורר האירועים השני נכשל בכל הדפדפנים שבדקנו. (אפילו הניסיון הראשון נכשל בחלק מהמקרים).

קריאות postMessage() בכמה דומיינים

הנה דוגמה מהדמו של postMessage(). נניח שמטפל 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);
});

בלי User Activation v2, מסגרת ההורה לא יכולה לפתוח חלון קופץ כשמקבלים את ההודעה השנייה. גם ההודעה הראשונה נכשלת אם היא "מוצמדת" למסגרת אחרת שמקורה מחוץ לאתר (כלומר, אם הנמען הראשון מעביר את ההודעה לנמען אחר).

האפשרות הזו פועלת עם User Activation v2, גם בטופס המקורי וגם עם שרשור.