כדי למנוע מסקריפטים זדוניים לנצל לרעה ממשקי API רגישים כמו חלונות קופצים, מסך מלא וכו', הדפדפנים שולטים בגישה לממשקי ה-API האלה באמצעות הפעלה על ידי המשתמש. הפעלת משתמש היא המצב של סשן הגלישה ביחס לפעולות של המשתמש: מצב 'פעיל' בדרך כלל מציין שהמשתמש יוצר כרגע אינטראקציה עם הדף, או שהשלים אינטראקציה מאז טעינת הדף. מחוות משתמש הוא מונח פופולרי אבל מטעה שמתייחס לאותה רעיון. לדוגמה, מחווה של החלקה או תנועת משיכה של משתמש לא מפעילה דף, ולכן היא לא נחשבת, מבחינת הסקריפט, להפעלה של משתמש.
בדפדפנים המובילים היום יש הבדלים משמעותיים באופן שבו הפעלת המשתמש קובעת את הרשאות הגישה לממשקי ה-API עם בקרת גישה מבוססת-הפעלה. ההטמעה ב-Chrome התבססה על מודל שמבוסס על אסימונים, שהתברר כמורכב מדי כדי להגדיר התנהגות עקבית בכל ממשקי ה-API שמופעלים באמצעות הפעלה. לדוגמה, Chrome אפשר
לגשת גישה חלקית לממשקי API מוגבלים להפעלה באמצעות
postMessage()
וsetTimeout()
קריאות; והפעלה של משתמשים לא
נתמכה באמצעות Promises,
XHR,
אינטראקציה עם גיימפליי וכו'. שימו לב שחלק מהבאגים האלה
פופולריים אבל ותיקים.
בגרסת 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 עם תנאי הפעלה ממסגרת מופעלת אבל מחוץ לקוד של טיפול באירוע, הוא יפעל כל עוד מצב ההפעלה של המשתמש הוא 'פעיל' (למשל, לא פג התוקף שלו ולא נוצל). לפני הפעלת המשתמש גרסה 2, היא הייתה תיכשל ללא תנאי.
- כמה אינטראקציות של משתמשים שלא נוצלו במהלך מרווח הזמן של תוקף המבצע מתמזגות להפעלה אחת שתואם לאינטראקציה האחרונה.
דוגמאות לעקביות בממשקי API עם תנאי הפעלה
ריכזנו כאן שתי דוגמאות עם חלונות קופצים (שנפתחים באמצעות window.open()
) שממחישות איך User Activation v2 מאפשרת לקבל התנהגות עקבית של ממשקי API עם תנאי גישה שמבוססים על הפעלה.
קריאות setTimeout()
מוצמדות
הדוגמה הזו מופיעה בדוגמה של setTimeout()
.
אם handler של 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, גם בטופס המקורי וגם עם שרשור.