ממשק API של מחזור החיים של דפים

תמיכה בדפדפן

  • 68
  • 79
  • x
  • x

כיום, דפדפנים מודרניים משעים דפים או מוחקים אותם לחלוטין כשיש הגבלה על משאבי המערכת. בעתיד, דפדפנים ירצו לעשות זאת באופן יזום, כדי שהם יצרו פחות אנרגיה וזיכרון. Page Lifecycle API מספק קטעי הוק (hooks) למחזור החיים כדי שהדפים שלכם יוכלו לטפל בבטחה בהתערבויות בדפדפן האלה, בלי להשפיע על חוויית המשתמש. מומלץ לבדוק את ה-API כדי להחליט אם כדאי להטמיע את התכונות האלה באפליקציה.

רקע

מחזור החיים של האפליקציה הוא הדרך העיקרית לניהול משאבים במערכות הפעלה מודרניות. ב-Android, ב-iOS ובגרסאות האחרונות של Windows, מערכת ההפעלה יכולה להפעיל ולהפסיק את האפליקציות בכל שלב. זה מאפשר לפלטפורמות האלה לייעל את המשאבים ולהקצות אותם מחדש במקום שבו הם יועילו למשתמש.

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

בפלטפורמת האינטרנט יש כבר הרבה זמן אירועים שקשורים למצבים של מחזור החיים — כמו load, unload ו-visibilitychange – אבל האירועים האלה מאפשרים למפתחים להגיב רק לשינויים במצב מחזור החיים ביוזמת המשתמש. כדי שהאינטרנט יפעל בצורה אמינה במכשירים עם צריכת חשמל נמוכה (ועם מוּדעוּת רבה יותר למשאבים באופן כללי בכל הפלטפורמות), דפדפנים צריכים דרך יזומה של משאבים והקצאה מחדש של משאבי המערכת.

למעשה, דפדפנים כיום כבר נוקטים אמצעים אקטיביים לשימור משאבים בדפים שבכרטיסיות הרקע, ודפדפנים רבים (במיוחד Chrome) רוצים לעשות הרבה יותר מזה – כדי לצמצם את טביעת הרגל הכוללת של המשאבים שלהם.

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

Page Lifecycle API מנסה לפתור את הבעיה הזו על ידי:

  • השקה של תפיסת מחזור החיים באינטרנט וסטנדרטית שלה.
  • הגדרת מצבים חדשים ביוזמת המערכת שמאפשרים לדפדפנים להגביל את המשאבים שאפשר להשתמש בהם בכרטיסיות מוסתרות או לא פעילות.
  • יצירת ממשקי API ואירועים חדשים שמאפשרים למפתחי אתרים להגיב להעברות אל המצבים החדשים ביוזמת המערכת ומהם.

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

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

סקירה כללית על מצבים ואירועים של מחזור החיים של דף

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

ייתכן שהדרך הקלה ביותר להסביר את מצבי מחזור החיים של הדף, וכן את האירועים שמעידים על מעברים ביניהם, היא באמצעות תרשים:

מצב ה-API של מחזור החיים של הדף וזרימת האירועים. ייצוג חזותי של המצב וזרימת האירוע שמתוארים במסמך הזה.

מדינות

הטבלה הבאה מסבירה בפירוט על כל מצב. הוא גם מפרט את המצבים האפשריים שיכולים להופיע לפני ואחרי, ואת האירועים שהמפתחים יכולים להשתמש בהם כדי לצפות בשינויים.

ארץ תיאור
פעיל

דף במצב פעיל אם הוא גלוי ויש לו מיקוד של קלט.

מצבים קודמים אפשריים:
פסיבי (דרך האירוע focus)
frozen (דרך האירוע resume, ולאחר מכן האירוע pageshow)

המצבים הבאים אפשריים:
פסיבי (באמצעות האירוע blur)

פסיבי

דף נמצא במצב פסיבי אם הוא גלוי ואין לו מיקוד קלט.

מצבים קודמים אפשריים:
פעיל (באמצעות האירוע blur)
מוסתר (דרך האירוע visibilitychange)
קפוא (דרך האירוע resume, ולאחר מכן21/}pageshow

המצבים הבאים אפשריים:
פעיל (באמצעות האירוע focus)
מוסתר (באמצעות האירוע visibilitychange)

סמויה

דף נמצא במצב מוסתר אם הוא אינו גלוי (והוא לא הוקפא, נמחק או נסגר).

מצבים קודמים אפשריים:
פסיבי (באמצעות האירוע visibilitychange
frozen (דרך האירוע resume, ולאחר מכן האירוע pageshow)

המצבים הבאים האפשריים:
פסיבי (באמצעות האירוע visibilitychange
frozen (באמצעות האירוע freeze)
בוטל (אף אירוע לא הופעל)
(לא הופעלו אירועים )

נתקע

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

דפדפנים מקפיאים דפים כדי לשמר את השימוש במעבד (CPU), בסוללה או בנתונים. הם גם מאפשרים ניווט מהיר יותר לדף הקודם/הבא, כך שלא צריך לטעון מחדש את הדף במלואו.

מצבים קודמים אפשריים:
מוסתר (באמצעות האירוע freeze)

המצבים הבאים האפשריים:
active (באמצעות האירוע resume, ואז pageshow האירוע)
פסיבי resume, ולאחר מכן pageshow האירוע)
מוסתר ב המוסתרכיוםלא


resume

הסתיימה

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

מצבים קודמים אפשריים:
מוסתר (באמצעות האירוע pagehide)

המצבים הבאים אפשריים:
ללא

נמחקה

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

במצב פריטים שהוסרו, הכרטיסייה עצמה (כולל שם הכרטיסייה וסמל האתר ) בדרך כלל גלויה למשתמש, גם אם הדף לא נמצא.

מצבים קודמים אפשריים:
מוסתר (לא הופעלו אירועים)
מוקפא (לא הופעלו אירועים)

המצבים הבאים אפשריים:
ללא

אירועים

דפדפנים שולחים הרבה אירועים, אבל רק חלק קטן מהם מצביע על שינוי אפשרי במצב מחזור החיים של הדף. בטבלה הבאה מפורטים כל האירועים שקשורים למחזור החיים, ומפורטות המצבים שאליהם הם עשויים לעבור או מהם.

שם פרטים
focus

רכיב DOM קיבל מיקוד.

הערה: אירוע focus לא בהכרח מצביע על שינוי במצב. הוא מסמן שינוי במצב רק אם הדף לא התמקד קודם לכן בקלט.

מצבים קודמים אפשריים:
פסיבי

מצבים נוכחיים אפשריים:
פעיל

blur

רכיב DOM איבד מיקוד.

הערה: אירוע blur לא בהכרח מצביע על שינוי במצב. התכונה מציינת שינוי במצב רק אם הדף כבר לא מתמקד בקלט (כלומר, הדף לא רק העברת את המיקוד מאלמנט אחד לאחר).

מצבים קודמים אפשריים:
פעיל

מצבים קיימים אפשריים:
פסיבי

visibilitychange

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

מצבים קודמים אפשריים:
פסיבי
מוסתר

מצבים נוכחיים אפשריים:
פסיבי
מוסתר

freeze *

הדף הוקפא. אף משימה שניתנת להקפאה בתורי המשימות בדף לא תופעל.

מצבים קודמים אפשריים:
מוסתר

מצבים נוכחיים אפשריים:
קפוא

resume *

הדפדפן חזר לדף שהופא.

מצבים קודמים אפשריים:
הקפאה

מצבים נוכחיים אפשריים:
active (אם מופיע האירוע pageshow)
פסיבי (אם מופיע האירוע pageshow
מוסתר

pageshow

רשומה של היסטוריית סשנים מתבצעת אל.

זו יכולה להיות טעינת דף חדשה או דף שנלקח מהמטמון לדף הקודם/הבא. אם הדף נלקח מהמטמון לדף הקודם/הבא, מאפיין persisted של האירוע הוא true, אחרת הוא false.

מצבים קודמים אפשריים:
קפוא (אירוע resume היה מופעל גם כן)

מצבים נוכחיים אפשריים:
active
פסיבי
מוסתר

pagehide

מתבצעת העברה של רשומה מהיסטוריית הסשנים.

אם המשתמש עובר לדף אחר והדפדפן יכול להוסיף את הדף הנוכחי למטמון לדף הקודם/הבא כדי שאפשר יהיה להשתמש בו שוב מאוחר יותר, המאפיין persisted של האירוע הוא true. כשהערך הוא true, הדף נכנס למצב קפוא, אחרת הוא עובר למצב סגור.

מצבים קודמים אפשריים:
מוסתר

מצבים נוכחיים אפשריים:
הקפאה (event.persisted נכון, freeze האירוע עוקב)
הסתיים (event.persisted הוא false, unload האירוע עוקב)

beforeunload

הטעינה של החלון, המסמך והמשאבים שלו עומדת להתבטל. המסמך עדיין גלוי ובשלב הזה עדיין אפשר לבטל את האירוע.

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

מצבים קודמים אפשריים:
מוסתר

מצבים נוכחיים אפשריים:
נסגר

unload

מתבצעת הסרה של טעינת הדף.

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

מצבים קודמים אפשריים:
מוסתר

מצבים נוכחיים אפשריים:
סגור

* מציין אירוע חדש שהוגדר על ידי ה-API של מחזור החיים של הדף

נוספו תכונות חדשות ב-Chrome 68

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

ב-Chrome בגרסה 68, מפתחים יכולים עכשיו לראות מתי כרטיסייה מוסתרת קופאת או לא מוקפאת, על ידי האזנה לאירועים freeze ו-resume ב-document.

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

בגרסה 68 של Chrome, האובייקט document כולל עכשיו גם את המאפיין wasDiscarded. כדי לקבוע אם דף נמחק כשהוא נמצא בכרטיסייה מוסתרת, אפשר לבדוק את הערך של המאפיין הזה בזמן הטעינה של הדף (הערה: צריך לטעון מחדש דפים שהוסרו כדי להשתמש בהם שוב).

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

כדי לקבל עצות לגבי הדברים שחשוב לעשות באירועים freeze ו-resume, וגם מידע לגבי אופן הטיפול בדפים נמחקים והכנה שלהם, אפשר לעיין בהמלצות למפתחים בכל מצב.

בחלקים הבאים מופיעה סקירה כללית על האופן שבו התכונות החדשות משתלבות עם המצבים והאירועים הקיימים בפלטפורמת האינטרנט.

צפייה במצבים של מחזור החיים של הדף בקוד

במצבים אקטיביים, פסיביים ומוסתרים אפשר להריץ קוד JavaScript שקובע את המצב הנוכחי של מחזור החיים של הדף מממשקי ה-API הקיימים של פלטפורמת האינטרנט.

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

מצד שני, ניתן לזהות את המצבים קפואים וסגורים רק ב-event listener המתאים (freeze ו-pagehide) בזמן שינוי המצב.

מעקב אחר שינויים במצב

על סמך הפונקציה getState() שהוגדרה למעלה, תוכלו לראות את כל השינויים במצבי מחזור החיים של הדף באמצעות הקוד הבא.

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// Options used for all event listeners.
const opts = {capture: true};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState(), opts));
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, opts);

window.addEventListener('pagehide', (event) => {
  // If the event's persisted property is `true` the page is about
  // to enter the back/forward cache, which is also in the frozen state.
  // If the event's persisted property is not `true` the page is
  // about to be unloaded.
  logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);

הקוד שלמעלה מבצע שלוש פעולות:

  • הגדרת המצב הראשוני באמצעות הפונקציה getState().
  • מגדירה פונקציה שמקבלת את המצב הבא, ואם יש שינוי, מתעדת את שינויי המצב במסוף.
  • המערכת מוסיפה פונקציות event listener לתיעוד של כל האירועים הנדרשים במחזור החיים, שנקראות logStateChange() ועוברות את המצב הבא.

חשוב לציין בקוד שלמעלה שכל פונקציות event listener מתווספות ל-window וכולם מעבירים את {capture: true}. יכולות להיות לכך כמה סיבות:

  • לא לכל האירועים במחזור החיים של הדף יש יעד זהה. pagehide ו-pageshow מופעלים ב-window; visibilitychange, freeze ו-resume מופעלים ב-document, ו-focus ו-blur מופעלים ברכיבי ה-DOM שלהם.
  • רוב האירועים לא מופיעים בבועות, כך שאי אפשר להוסיף פונקציות event listener שלא מתעדים לרכיב אב משותף ולקלוט את כולם.
  • שלב הצילום מתבצע לפני שלבי היעד או הבועה, ולכן הוספה של מאזינים עוזרת לוודא שהם יפעלו לפני שקוד אחר יכול לבטל אותם.

המלצות של מפתחים לכל מצב

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

לדוגמה, בבירור לא הגיוני להציג התראה זמנית למשתמש אם הדף במצב סמוי. אומנם הדוגמה הזו ברורה מאליו, אבל יש המלצות נוספות שלא כל כך ברורות מאליהן וכדאי להתייחס אליהן.

ארץ המלצות למפתחים
Active

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

צריך לתת עדיפות ל תקופות ללא פעילות או להוריד ל-worker כל עבודה שלא מבוססת על ממשק משתמש, שעלולה לחסום את ה-thread הראשי.

Passive

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

כשהדף משתנה מפעיל לסביל, זה זמן טוב לשמור על מצב האפליקציה שלא נשמרה.

Hidden

אם סטטוס הדף משתנה מסביל למוסתר, יכול להיות שהמשתמש לא יבצע אינטראקציה נוספת עם הדף עד שהוא ייטען מחדש.

לרוב, המעבר למצב מוסתר הוא השינוי האחרון במצב שמפתחים יכולים לתעד באופן מהימן (הדבר נכון במיוחד בניידים, כי המשתמשים יכולים לסגור כרטיסיות או את אפליקציית הדפדפן עצמה, והאירועים beforeunload, pagehide ו-unload לא מופעלים במקרים האלה).

כלומר, צריך להתייחס למצב מוסתר כאל הסיום הצפוי של הסשן של המשתמש. במילים אחרות, לשמור כל מצב אפליקציה שלא נשמר ולשלוח נתוני ניתוח שלא נשלחו.

בנוסף, צריך להפסיק לבצע עדכונים של ממשק המשתמש (כי המשתמש לא יוכל לראות אותם) ולהפסיק משימות שהמשתמש לא רוצה שיפעל ברקע.

Frozen

במצב הקפאה, משימות שקופאות ב תורי המשימות מושהות עד לביטול הקפאת הדף. מצב כזה יכול לא לקרות אף פעם (למשל, אם הדף נמחק).

המשמעות היא שכאשר הדף משתנה ממוסתר לקפוא, חשוב להפסיק את כל הטיימרים או לנתק חיבורים שעלולים להשפיע על כרטיסיות פתוחות אחרות באותו מקור או על היכולת של הדפדפן להעביר את הדף למטמון לדף הקודם/הבא.

באופן ספציפי, חשוב לבצע את הפעולות הבאות:

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

אם הדף מועבר מהוקפא בחזרה למוסתר, אפשר לפתוח מחדש חיבורים שנסגרו או להפעיל מחדש סקרים שהפסקתם כשהדף הוקפא בהתחלה.

Terminated

בדרך כלל לא צריך לבצע שום פעולה כשהדף עובר למצב סגור.

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

כמו כן (כפי שמצוין בהמלצות למצב מוסתר), חשוב מאוד שהמפתחים יבינו שבמקרים רבים לא ניתן לזהות באופן מהימן את המעבר למצב נסגר (במיוחד בנייד), ולכן מפתחים שתלויים באירועי סיום (למשל beforeunload, pagehide ו-unload) עלולים לאבד נתונים.

Discarded

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

כתוצאה מכך, עליך להתכונן לאפשרות של ביטול השינוי ממוסתר למוקפא, ואז אפשר להגיב לשחזור של דף שנמחק בזמן הטעינה של הדף על ידי סימון הערך document.wasDiscarded.

שוב, מכיוון שהאמינות והסדר של אירועים במחזור החיים לא מיושמים באופן עקבי בכל הדפדפנים, הדרך הקלה ביותר לפעול לפי העצה בטבלה שלמעלה היא להשתמש ב-PageLifecycle.js.

ממשקי API מדור קודם של מחזור החיים שיש להימנע מהם

האירוע 'הסרת הנתונים שנטענו'

מפתחים רבים מתייחסים לאירוע unload כאל קריאה חוזרת מובטחת, ומשתמשים בו בתור אות לסוף הסשן כדי לשמור את המצב ולשלוח נתונים לניתוח, אבל הפעולה הזו מאוד לא אמינה, במיוחד בניידים! האירוע unload לא מופעל במצבים נפוצים רבים של הסרת נתונים שנטענו, כולל סגירת כרטיסייה מהכלי למעבר בין כרטיסיות בנייד או סגירת אפליקציית הדפדפן במעבר בין אפליקציות.

לכן תמיד עדיף להסתמך על האירוע visibilitychange על מנת לקבוע מתי סשן מסתיים, ולהחשיב את המצב המוסתר בתור הזמן המהימן האחרון לשמירה של נתוני אפליקציות ומשתמשים.

בנוסף, רק הנוכחות של handler רשום באירועים של unload (באמצעות onunload או addEventListener()) יכולה למנוע מדפדפנים להוסיף דפים למטמון לדף הקודם/הבא כדי לטעון מחדש ולהריץ קדימה ואחורה מהר יותר.

בכל הדפדפנים המודרניים, מומלץ להשתמש תמיד באירוע pagehide כדי לזהות טעינות אפשריות של דפים (כלומר המצב סגור) ולא את האירוע unload. אם אתם צריכים לתמוך ב-Internet Explorer בגרסה 10 ומטה, עליכם לזהות את האירוע pagehide ולהשתמש ב-unload רק אם הדפדפן לא תומך ב-pagehide:

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

window.addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
});

האירוע beforeunload

באירוע beforeunload יש בעיה דומה לאירוע unload. מבחינה היסטורית, נוכחות של אירוע beforeunload עלולה למנוע מצב שבו דפים מתאימים למטמון לדף הקודם/הבא. בדפדפנים מודרניים אין את ההגבלה הזו. למרות שדפדפנים מסוימים, ליתר ביטחון, לא יפעילו את האירוע beforeunload כשמנסים להוסיף דף למטמון לדף הקודם/הבא, כלומר האירוע לא מהימן בתור אות של סוף סשן. בנוסף, דפדפנים מסוימים (כולל Chrome) מחייבים אינטראקציה של המשתמש בדף לפני שהם מאפשרים לאירוע beforeunload לפעול, וזה משפיע עוד יותר על האמינות שלו.

הבדל אחד בין beforeunload ל-unload הוא שיש שימושים לגיטימיים ב-beforeunload. לדוגמה, אם תרצו להזהיר את המשתמש שיש לו שינויים שלא נשמרו, הוא יאבד אם ימשיך בהסרת הדף.

מכיוון שיש סיבות תקפות להשתמש ב-beforeunload, מומלץ רק להוסיף מאזינים ל-beforeunload כאשר משתמש כולל שינויים שלא נשמרו, ואז להסיר אותם מיד לאחר השמירה.

במילים אחרות, אל תעשו את זה (מכיוון שהוא מוסיף האזנה ל-beforeunload ללא תנאי):

addEventListener('beforeunload', (event) => {
  // A function that returns `true` if the page has unsaved changes.
  if (pageHasUnsavedChanges()) {
    event.preventDefault();

    // Legacy support for older browsers.
    return (event.returnValue = true);
  }
});

במקום זאת, צריך לבצע את הפעולה הזו (כי היא מוסיפה את ה-listener של beforeunload רק כשצריך, ומסירה אותו כשצריך):

const beforeUnloadListener = (event) => {
  event.preventDefault();
  
  // Legacy support for older browsers.
  return (event.returnValue = true);
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  removeEventListener('beforeunload', beforeUnloadListener);
});

שאלות נפוצות

למה אין מצב 'טעינה'?

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

הדף שלי עובד חשוב כשהוא מוסתר. איך אפשר למנוע את הקפאת הדף או מחיקתו?

יש הרבה סיבות לגיטימיות לכך שלא כדאי להקפיא דפי אינטרנט בזמן שהם פועלים במצב סמוי. הדוגמה הברורה ביותר היא אפליקציה שמשמיעה מוזיקה.

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

בשלב זה, Chrome יעמוד בדרישות מחמירות לגבי מחיקת דפים, ויעשה זאת רק כשהוא יהיה בטוח שהדבר לא ישפיע על המשתמשים. לדוגמה, דפים שבוצעה לגביהם אחת מהפעולות הבאות במצב מוסתר לא יימחקו, אלא אם הם כפופים למגבלות משאבים קיצוניות:

  • האודיו מושמע
  • שימוש ב-WebRTC
  • עדכון הכותרת או סמל האתר של הטבלה
  • הצגת התראות
  • שליחת התראות

למידע על תכונות הרשימה הנוכחיות שמשמשות כדי לקבוע אם ניתן להקפיא או למחוק כרטיסייה באופן בטוח, ראו: Heurism for Freezing & המורשים ב-Chrome.

מהו מטמון לדף הקודם/הבא?

מטמון לדף הקודם/הבא הוא מונח שמתאר אופטימיזציה של ניווט בחלק מהדפדפנים, שמאפשרת להשתמש בלחצנים 'הקודם' ו'הבא' מהר יותר.

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

לכל מטרה ולכל מטרה, פעולת ההקפאה הזו זהה לזו של הדפדפנים שמקפיאים, כדי לחסוך במעבד (CPU) או בסוללה. לכן היא נחשבת לחלק ממצב מחזור החיים קפוא.

אם אי אפשר להפעיל ממשקי API אסינכרוניים במצב קפוא או הסתיים, איך אפשר לשמור נתונים ב-IndexedDB?

במצבים קפואים או שהסתיימו, משימות שניתנות להקפאה בתורי המשימות של הדף מושעים, ולכן אי אפשר להשתמש באופן מהימן בממשקי API אסינכרוניים ומבוססי קריאה חוזרת, כמו IndexedDB.

בעתיד, נוסיף את השיטה commit() לאובייקטים מסוג IDBTransaction, שתהיה למפתחים אפשרות לבצע באופן אפקטיבי עסקאות מסוג 'כתיבה בלבד' שלא מחייבות קריאות חוזרות (callback). במילים אחרות, אם המפתח רק כותב נתונים ל-IndexedDB ולא מבצע טרנזקציה מורכבת שכוללת קריאות וכתיבה, השיטה commit() תוכל לסיים את התהליך לפני שתורי המשימות יושעו (בהנחה שמסד הנתונים IndexedDB כבר פתוח).

עם זאת, עבור קוד שצריך לפעול היום, יש למפתחים שתי אפשרויות:

  • שימוש באחסון הפעלה: אחסון של פעילות באתר הוא סינכרוני ומופיע יחד עם מחיקת דפים.
  • שימוש ב-IndexedDB מ-Service Worker:קובץ שירות (service worker) יכול לאחסן נתונים ב-IndexedDB אחרי שהדף נסגר או נמחק. ב-event listener freeze או pagehide אפשר לשלוח נתונים ל-Service Worker דרך postMessage(), וה-Service Worker יכול לטפל בשמירת הנתונים.

בדיקת האפליקציה במצב 'קפוא' או 'נמחק'

כדי לבדוק איך האפליקציה מתנהגת במצב 'קפוא' או 'נמחקה', תוכלו להיכנס לכתובת chrome://discards כדי להקפיא או למחוק את אחת מהכרטיסיות הפתוחות.

ממשק המשתמש נמחק ב-Chrome

כך תוכלו לוודא שהדף יטפל בצורה נכונה באירועים freeze וב-resume, וגם בדגל document.wasDiscarded כשהדפים נטענים מחדש אחרי מחיקת תוכן.

סיכום

מפתחים שרוצים לכבד את משאבי המערכת של המכשירים שלהם צריכים לבנות את האפליקציות שלהם תוך התחשבות במצבים של מחזור החיים של הדף. חשוב מאוד שדפי אינטרנט לא ינצלו יותר מדי משאבי מערכת במצבים שבהם המשתמש לא היה מצפה

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