واجهة برمجة تطبيقات مراحل نشاط الصفحة

Browser Support

  • Chrome: 68.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

في بعض الأحيان، تعلّق المتصفّحات الحديثة الصفحات أو تتجاهلها تمامًا عندما تكون موارد النظام محدودة. في المستقبل، ستسعى المتصفحات إلى تنفيذ ذلك بشكل استباقي، ما يؤدي إلى استهلاك أقل للطاقة والذاكرة. توفّر Page Lifecycle API خطافات دورة الحياة حتى تتمكّن صفحاتك من التعامل بأمان مع هذه الإجراءات التي يتّخذها المتصفّح بدون التأثير في تجربة المستخدم. اطّلِع على واجهة برمجة التطبيقات لمعرفة ما إذا كان عليك تنفيذ هذه الميزات في تطبيقك.

الخلفية

تُعد دورة حياة التطبيق إحدى الطرق الرئيسية التي تدير بها أنظمة التشغيل الحديثة الموارد. على أجهزة Android وiOS وأحدث إصدارات Windows، يمكن لنظام التشغيل بدء التطبيقات وإيقافها في أي وقت. ويسمح ذلك لهذه المنصات بتبسيط الموارد وإعادة تخصيصها في الأماكن التي تفيد المستخدم بشكل أفضل.

على الويب، لم تكن هناك دورة حياة من هذا النوع، ويمكن إبقاء التطبيقات نشطة إلى أجل غير مسمى. عند تشغيل عدد كبير من صفحات الويب، يمكن أن يتم استهلاك موارد النظام المهمة، مثل الذاكرة ووحدة المعالجة المركزية والبطارية والشبكة، بشكل مفرط، ما يؤدي إلى تجربة سيئة للمستخدم النهائي.

على الرغم من أنّ منصة الويب تتضمّن منذ فترة طويلة أحداثًا مرتبطة بحالات مراحل النشاط، مثل load وunload وvisibilitychange، فإنّ هذه الأحداث لا تسمح للمطوّرين إلا بالاستجابة لتغييرات حالات مراحل النشاط التي يبدأها المستخدم. لكي يعمل الويب بشكل موثوق على الأجهزة ذات الطاقة المنخفضة (ويكون أكثر مراعاة للموارد بشكل عام على جميع المنصات)، تحتاج المتصفحات إلى طريقة لاستعادة موارد النظام وإعادة تخصيصها بشكل استباقي.

في الواقع، تتّخذ المتصفّحات اليوم إجراءات نشطة للحفاظ على الموارد في الصفحات ضمن علامات التبويب في الخلفية، وتودّ العديد من المتصفّحات (خاصةً Chrome) أن تتّخذ المزيد من هذه الإجراءات للحدّ من استخدامها الإجمالي للموارد.

تكمن المشكلة في أنّه ليس لدى المطوّرين أي طريقة للاستعداد لهذه الأنواع من التدخّلات التي يبدأها النظام أو حتى معرفة أنّها تحدث. وهذا يعني أنّ المتصفّحات يجب أن تكون حذرة وإلا ستتسبّب في تعطّل صفحات الويب.

تحاول Page Lifecycle API حلّ هذه المشكلة من خلال:

  • تقديم مفهوم حالات مراحل النشاط وتوحيده على الويب
  • تحديد حالات جديدة يبدأها النظام وتسمح للمتصفحات بالحدّ من الموارد التي يمكن أن تستهلكها علامات التبويب المخفية أو غير النشطة
  • إنشاء واجهات برمجة تطبيقات وأحداث جديدة تتيح لمطوّري الويب الاستجابة للانتقالات من وإلى هذه الحالات الجديدة التي يبدأها النظام

يوفّر هذا الحلّ إمكانية التوقّع التي يحتاجها مطوّرو الويب لإنشاء تطبيقات مقاومة لتدخّلات النظام، كما أنّه يتيح للمتصفّحات تحسين موارد النظام بشكل أكثر فعالية، ما يعود بالنفع في النهاية على جميع مستخدمي الويب.

في ما تبقّى من هذه المشاركة، سنتعرّف على ميزات "دورة حياة الصفحة" الجديدة ونستكشف علاقتها بجميع الحالات والأحداث الحالية لمنصة الويب. سيقدّم أيضًا توصيات وأفضل الممارسات بشأن أنواع العمل الذي يجب (ولا يجب) أن ينفّذه المطوّرون في كل حالة.

نظرة عامة على حالات وأحداث مراحل نشاط الصفحة

جميع حالات "دورة حياة الصفحة" منفصلة ولا تتداخل مع بعضها، ما يعني أنّه لا يمكن أن تكون الصفحة في أكثر من حالة واحدة في الوقت نفسه. ويمكن بشكل عام رصد معظم التغييرات التي تطرأ على حالة دورة حياة الصفحة من خلال أحداث نموذج المستند (DOM) (راجِع اقتراحات المطوّرين لكل حالة لمعرفة الاستثناءات).

ربما تكون أسهل طريقة لشرح حالات "دورة حياة الصفحة"، بالإضافة إلى الأحداث التي تشير إلى عمليات الانتقال بينها، هي باستخدام مخطط:

تمثّل هذه السمة عرضًا مرئيًا لحالة تدفّق الأحداث الموضّحة في هذا المستند.
مخطّط انسيابي لحالات وأحداث Page Lifecycle API

الولايات

يوضّح الجدول التالي كل حالة بالتفصيل. وتتضمّن أيضًا الحالات المحتملة التي يمكن أن تسبق الحالة الحالية وتليها، بالإضافة إلى الأحداث التي يمكن للمطوّرين استخدامها لمراقبة التغييرات.

ولاية الوصف
نشط

تكون الصفحة في حالة نشطة إذا كانت مرئية ولديها تركيز الإدخال.

الحالات السابقة المحتملة:
غير نشط (عبر حدث focus)
مجمَّد (عبر حدث resume، ثم حدث pageshow)

الحالات التالية المحتملة:
غير نشط (من خلال حدث blur)

غير نشط

تكون الصفحة في حالة غير نشطة إذا كانت مرئية ولم يكن التركيز على الإدخال فيها.

الحالات السابقة المحتملة:
نشط (من خلال حدث blur)
مخفي (من خلال حدث visibilitychange)
مجمّد (من خلال حدث resume، ثم حدث pageshow)

الحالات التالية المحتملة:
نشط (من خلال حدث focus)
مخفي (من خلال حدث visibilitychange)

مخفية

تكون الصفحة في حالة مخفية إذا لم تكن مرئية (ولم يتم تجميدها أو تجاهلها أو إنهاؤها).

الحالات السابقة المحتملة:
غير نشط (عبر حدث visibilitychange)
مجمَّد (عبر حدث resume، ثم حدث pageshow)

الحالات التالية المحتملة:
غير نشط (من خلال الحدث visibilitychange)
مجمّد (من خلال الحدث freeze)
تم تجاهله (لم يتم تشغيل أي أحداث)
تم إنهاؤه (لم يتم تشغيل أي أحداث)

مجمَّد

في حالة التجميد، يعلّق المتصفّح تنفيذ المهام القابلة للتجميد في قوائم انتظار المهام الخاصة بالصفحة إلى أن يتم إلغاء تجميد الصفحة. وهذا يعني أنّ بعض العناصر، مثل مؤقّتات JavaScript وعمليات رد الاتصال التي يتم تنفيذها عند استرداد البيانات، لن تعمل. يمكن إكمال المهام التي يتم تنفيذها حاليًا (والأهم من ذلك، معاودة الاتصال freeze)، ولكن قد تكون محدودة في ما يمكنها فعله ومدة تنفيذها.

تجمّد المتصفّحات الصفحات للحفاظ على وحدة المعالجة المركزية (CPU) والبطارية واستخدام البيانات، كما أنّها تفعل ذلك لتتيح عمليات التنقّل السريع بين الصفحات السابقة واللاحقة، ما يجنّب المستخدم الحاجة إلى إعادة تحميل الصفحة بالكامل.

الحالات السابقة المحتملة:
hidden (عبر حدث freeze)

الحالات التالية المحتملة:
نشط (من خلال الحدث resume، ثم الحدث pageshow)
غير نشط (من خلال الحدث resume، ثم الحدث pageshow)
مخفي (من خلال الحدث resume)
تم تجاهله (لم يتم تشغيل أي أحداث)

تم إنهاء الاشتراك

تكون الصفحة في حالة إنهاء بعد أن يبدأ المتصفّح في إلغاء تحميلها وإزالتها من الذاكرة. لا يمكن بدء مهام جديدة في هذه الحالة، وقد يتم إيقاف المهام الجارية إذا استغرقت وقتًا طويلاً جدًا.

الحالات السابقة المحتملة:
hidden (عبر حدث pagehide)

الحالات التالية المحتملة:
NONE

تم تجاهلها

تكون الصفحة في حالة تم تجاهلها عندما يلغي المتصفّح تحميلها بهدف الحفاظ على الموارد. لا يمكن تنفيذ أي مهام أو عمليات ردّ اتصال للأحداث أو أي نوع من JavaScript في هذه الحالة، لأنّ عمليات الإلغاء تحدث عادةً في ظل قيود على الموارد، حيث يكون من المستحيل بدء عمليات جديدة.

في حالة التجاهل، تظل علامة التبويب نفسها (بما في ذلك عنوان علامة التبويب ورمز الموقع المفضّل) مرئية للمستخدم عادةً على الرغم من أنّ الصفحة لم تعُد متاحة.

الحالات السابقة المحتملة:
مخفية (لم يتم إطلاق أي أحداث)
مجمّدة (لم يتم إطلاق أي أحداث)

الحالات التالية المحتملة:
NONE

الفعاليات

ترسل المتصفحات الكثير من الأحداث، ولكن لا يشير إلى حدوث تغيير محتمل في حالة "دورة حياة الصفحة" سوى جزء صغير منها. يوضّح الجدول التالي جميع الأحداث المتعلّقة بدورة الحياة، ويسرد الحالات التي يمكن أن تنتقل إليها ومنها.

الاسم التفاصيل
focus

تم التركيز على عنصر DOM.

ملاحظة: لا يشير حدث focus بالضرورة إلى تغيير في الحالة. لا يشير إلى تغيير الحالة إلا إذا لم تكن الصفحة تتضمّن تركيز إدخال من قبل.

الحالات السابقة المحتملة:
passive

الحالات الحالية المحتملة:
نشط

blur

فقد عنصر DOM التركيز.

ملاحظة: لا يشير حدث blur بالضرورة إلى تغيير في الحالة. ولا تشير إلى تغيير الحالة إلا إذا لم يعُد التركيز على الصفحة (أي إذا لم يتم تبديل التركيز من عنصر إلى آخر).

الحالات السابقة المحتملة:
نشط

الحالات الحالية المحتملة:
غير نشط

visibilitychange

تغيّرت قيمة visibilityState الخاصة بالمستند. ويمكن أن يحدث ذلك عندما ينتقل المستخدم إلى صفحة جديدة أو يبدّل علامات التبويب أو يغلق علامة تبويب أو يصغّر المتصفّح أو يغلقه أو يبدّل التطبيقات على أنظمة تشغيل الأجهزة الجوّالة.

الحالات السابقة المحتملة:
غير نشط
مخفي

الحالات الحالية المحتملة:
غير نشط
مخفي

freeze *

تم تجميد الصفحة للتو. لن يتم بدء أي مهمة قابلة للتجميد في قوائم مهام الصفحة.

الحالات السابقة المحتملة:
hidden

الحالات الحالية المحتملة:
مجمَّد

resume *

استأنف المتصفّح عرض صفحة مجمّدة.

الحالات السابقة المحتملة:
مجمَّدة

الحالات الحالية المحتملة:
نشط (إذا كان متبوعًا بالحدث pageshow)
غير نشط (إذا كان متبوعًا بالحدث pageshow)
مخفي

pageshow

يتم الانتقال إلى إدخال في سجلّ الجلسة.

يمكن أن يكون ذلك إما تحميل صفحة جديدة تمامًا أو صفحة تم جلبها من ذاكرة التخزين المؤقت للصفحات. إذا تم استرداد الصفحة من ذاكرة التخزين المؤقت للصفحات، ستكون قيمة السمة persisted للحدث هي true، وإلا ستكون false.

الحالات السابقة المحتملة:
مجمَّد (سيتم أيضًا تشغيل حدث resume)

الحالات الحالية المحتملة:
نشط
غير نشط
مخفي

pagehide

يتم الانتقال من إدخال في سجلّ الجلسة.

إذا كان المستخدم ينتقل إلى صفحة أخرى وكان المتصفّح قادرًا على إضافة الصفحة الحالية إلى ذاكرة التخزين المؤقت للصفحات السابقة/التالية لإعادة استخدامها لاحقًا، ستكون قيمة السمة persisted للحدث هي true. عندما تكون القيمة true، تكون الصفحة في حالة تجميد، وإلا تكون في حالة إنهاء.

الحالات السابقة المحتملة:
hidden

الحالات الحالية المحتملة:
frozen (event.persisted هي true، يتبعها الحدث freeze)
terminated (event.persisted هي false، يتبعها الحدث unload)

beforeunload

سيتم إلغاء تحميل النافذة والمستند وموارده قريبًا. سيظل المستند مرئيًا وسيظل بإمكانك إلغاء الحدث في هذه المرحلة.

ملاحظة مهمة: يجب استخدام الحدث beforeunload فقط لتنبيه المستخدم بشأن التغييرات غير المحفوظة. بعد حفظ هذه التغييرات، من المفترض أن تتم إزالة الحدث. ويجب عدم إضافته مطلقًا بشكل غير مشروط إلى الصفحة، لأنّ ذلك قد يؤدي إلى تدهور الأداء في بعض الحالات. راجِع قسم واجهات برمجة التطبيقات القديمة للحصول على التفاصيل.

الحالات السابقة المحتملة:
hidden

الحالات الحالية المحتملة:
terminated

unload

يتم إلغاء تحميل الصفحة.

تحذير: لا يُنصح مطلقًا باستخدام الحدث unload لأنّه غير موثوق به ويمكن أن يؤثّر سلبًا في الأداء في بعض الحالات. يمكنك الاطّلاع على قسم واجهات برمجة التطبيقات القديمة للحصول على مزيد من التفاصيل.

الحالات السابقة المحتملة:
hidden

الحالات الحالية المحتملة:
terminated

* يشير إلى حدث جديد محدّد بواسطة Page Lifecycle API

الميزات الجديدة التي تمت إضافتها في الإصدار 68 من Chrome

يعرض الرسم البياني السابق حالتين يبدأ فيهما النظام الإجراء بدلاً من أن يبدأه المستخدم، وهما التجميد والتجاهل. كما ذكرنا سابقًا، تتجمّد المتصفحات حاليًا أحيانًا وتتجاهل علامات التبويب المخفية (حسب تقديرها)، ولكن لا يمكن للمطوّرين معرفة وقت حدوث ذلك.

في الإصدار 68 من Chrome، يمكن للمطوّرين الآن مراقبة وقت تجميد علامة تبويب مخفية وإلغاء تجميدها من خلال الاستماع إلى الحدثَين 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 على إصدار Chrome لأجهزة الكمبيوتر (يتم تتبُّع التوافق مع Android في هذه المشكلة). لتحديد ما إذا تم تجاهل صفحة أثناء وجودها في علامة تبويب مخفية، يمكنك فحص قيمة هذه السمة عند تحميل الصفحة (ملاحظة: يجب إعادة تحميل الصفحات التي تم تجاهلها لاستخدامها مرة أخرى).

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

للحصول على نصائح بشأن الإجراءات المهمة التي يجب اتّخاذها في حدثَي freeze وresume، بالإضافة إلى كيفية التعامل مع الصفحات التي يتم تجاهلها والاستعداد لها، يمكنك الاطّلاع على اقتراحات المطوّرين لكل حالة.

تقدّم الأقسام العديدة التالية نظرة عامة على كيفية ملاءمة هذه الميزات الجديدة لحالات وأحداث منصة الويب الحالية.

كيفية مراقبة حالات "دورة حياة الصفحة" في الرمز

في الحالات النشطة وغير النشطة والمخفية، يمكن تنفيذ رمز JavaScript الذي يحدّد حالة "دورة حياة الصفحة" الحالية من واجهات برمجة التطبيقات الحالية لمنصة الويب.

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

من ناحية أخرى، لا يمكن رصد حالتَي التجميد والإنهاء إلا في أداة معالجة الأحداث الخاصة بكل منهما (freeze وpagehide) أثناء تغيُّر الحالة.

كيفية رصد تغييرات الحالة

استنادًا إلى الدالة getState() المحدّدة سابقًا، يمكنك مراقبة جميع تغييرات حالة PageLifecycle باستخدام الرمز التالي.

// 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().
  • تحدّد هذه السمة دالة تقبل حالة تالية، وفي حال حدوث تغيير، تسجّل التغييرات في الحالة في وحدة التحكّم.
  • تضيف هذه السمة أدوات معالجة الأحداث للتسجيل لجميع أحداث مراحل النشاط الضرورية، والتي بدورها تستدعي logStateChange()، مع تمرير الحالة التالية.

يجب الانتباه إلى أنّ جميع أدوات معالجة الأحداث تتم إضافتها إلى window، وأنّها جميعًا تمرّر {capture: true}. وهناك بضعة أسباب لذلك:

  • لا تستهدف جميع أحداث Page Lifecycle الهدف نفسه. يتم تشغيل pagehide وpageshow على window، ويتم تشغيل visibilitychange وfreeze وresume على document، ويتم تشغيل focus وblur على عناصر DOM الخاصة بهما.
  • لا تتصاعد معظم هذه الأحداث، ما يعني أنّه من المستحيل إضافة أدوات معالجة الأحداث غير الالتقاطية إلى عنصر سلف مشترك ومراقبة جميع هذه الأحداث.
  • يتم تنفيذ مرحلة الالتقاط قبل مرحلتَي الهدف أو الفقاعة، لذا فإنّ إضافة أدوات معالجة الأحداث في هذه المرحلة يساعد في ضمان تنفيذها قبل أن يتمكّن رمز آخر من إلغائها.

اقتراحات للمطوّرين لكل حالة

بصفتك مطوّرًا، من المهم أن تفهم حالات "دورة حياة الصفحة" وأن تعرف كيفية مراقبتها في الرمز البرمجي، لأنّ نوع العمل الذي يجب (والذي لا يجب) تنفيذه يعتمد بشكل كبير على حالة صفحتك.

على سبيل المثال، من الواضح أنّه ليس من المنطقي عرض إشعار مؤقت للمستخدم إذا كانت الصفحة في حالة مخفية. على الرغم من أنّ هذا المثال واضح جدًا، هناك اقتراحات أخرى غير واضحة تستحق التوضيح.

ولاية اقتراحات للمطوّرين
Active

حالة النشاط هي الفترة الأكثر أهمية بالنسبة إلى المستخدم، وبالتالي، هي الفترة الأكثر أهمية لكي تكون صفحتك متجاوبة مع إدخال المستخدم.

يجب تقليل أولوية أي عمل غير مرتبط بواجهة المستخدم قد يؤدي إلى حظر سلسلة التعليمات الرئيسية إلى فترات الخمول أو نقله إلى عامل ويب.

Passive

في حالة الوضع غير النشط، لا يتفاعل المستخدم مع الصفحة، ولكن يظل بإمكانه رؤيتها. وهذا يعني أنّه يجب أن تظل تحديثات واجهة المستخدم والرسوم المتحركة سلسة، ولكن توقيت حدوث هذه التحديثات أقل أهمية.

عندما تتغيّر حالة الصفحة من نشطة إلى غير نشطة، يكون هذا الوقت مناسبًا للاحتفاظ بحالة التطبيق غير المحفوظة.

Hidden

عندما تتغيّر حالة الصفحة من غير نشطة إلى مخفية، من المحتمل ألا يتفاعل المستخدم معها مرة أخرى إلى أن يعيد تحميلها.

غالبًا ما يكون الانتقال إلى الحالة مخفي هو آخر تغيير في الحالة يمكن للمطوّرين ملاحظته بشكل موثوق (ينطبق ذلك بشكل خاص على الأجهزة الجوّالة، إذ يمكن للمستخدمين إغلاق علامات التبويب أو تطبيق المتصفّح نفسه، ولا يتم تنشيط الأحداث beforeunload وpagehide وunload في هذه الحالات).

وهذا يعني أنّه عليك التعامل مع حالة الإخفاء باعتبارها النهاية المحتملة لجلسة المستخدم. وبعبارة أخرى، يجب حفظ أي حالة تطبيق لم يتم حفظها وإرسال أي بيانات إحصائية لم يتم إرسالها.

عليك أيضًا التوقّف عن إجراء تعديلات على واجهة المستخدم (لأنّ المستخدم لن يراها)، والتوقّف عن تنفيذ أي مهام لا يريد المستخدم أن يتم تنفيذها في الخلفية.

Frozen

في حالة التجميد، يتم تعليق المهام القابلة للتجميد في قوائم المهام إلى أن يتم إلغاء تجميد الصفحة، وهو ما قد لا يحدث أبدًا (على سبيل المثال، إذا تم تجاهل الصفحة).

هذا يعني أنّه عند تغيير حالة الصفحة من مخفية إلى مجمّدة، من الضروري إيقاف أي مؤقتات أو إغلاق أي اتصالات قد تؤثر في علامات التبويب الأخرى المفتوحة من المصدر نفسه إذا تم تجميدها، أو تؤثر في قدرة المتصفح على وضع الصفحة في ذاكرة التخزين المؤقت للخلف/للأمام.

وعلى وجه الخصوص، من المهم أن:

  • أغلِق جميع اتصالات IndexedDB المفتوحة.
  • إغلاق اتصالات BroadcastChannel المفتوحة
  • إغلاق اتصالات WebRTC النشطة
  • أوقِف أي عمليات استطلاع للشبكة أو أغلق أي اتصالات Web Socket مفتوحة.
  • إلغاء أي Web Locks معلّقة

عليك أيضًا الحفاظ على أي حالة عرض ديناميكية (مثل موضع التمرير في عرض قائمة لا نهائية) في sessionStorage (أو IndexedDB من خلال commit()) التي تريد استعادتها إذا تم تجاهل الصفحة وإعادة تحميلها لاحقًا.

إذا انتقلت الصفحة من الحالة مجمّدة إلى الحالة مخفية، يمكنك إعادة فتح أي اتصالات مغلقة أو إعادة بدء أي عمليات استطلاع أوقفتها عندما تم تجميد الصفحة في البداية.

Terminated

بشكل عام، ليس عليك اتخاذ أي إجراء عندما تنتقل الصفحة إلى الحالة منتهية.

بما أنّ الصفحات التي يتم إلغاء تحميلها نتيجةً لإجراء اتّخذه المستخدم تنتقل دائمًا إلى الحالة مخفية قبل الانتقال إلى الحالة تم إنهاؤها، يجب تنفيذ منطق إنهاء الجلسة (مثل الاحتفاظ بحالة التطبيق وإرسال البيانات إلى "إحصاءات Google") في الحالة مخفية.

أيضًا (كما هو موضّح في الاقتراحات بشأن حالة مخفية)، من المهم جدًا أن يدرك المطوّرون أنّه لا يمكن رصد الانتقال إلى حالة إنهاء بشكل موثوق في العديد من الحالات (خاصةً على الأجهزة الجوّالة)، لذا من المحتمل أن يفقد المطوّرون الذين يعتمدون على أحداث الإنهاء (مثل beforeunload وpagehide وunload) البيانات.

Discarded

لا يمكن للمطوّرين ملاحظة الحالة تم تجاهلها في الوقت الذي يتم فيه تجاهل الصفحة. ويرجع ذلك إلى أنّه يتم عادةً تجاهل الصفحات بسبب قيود الموارد، ولا يمكن في معظم الحالات إلغاء تجميد صفحة للسماح بتشغيل البرنامج النصي استجابةً لحدث تجاهل.

نتيجةً لذلك، عليك الاستعداد لاحتمال تجاهل التغيير من مخفية إلى مجمّدة، وبعد ذلك يمكنك الاستجابة لاستعادة صفحة تم تجاهلها في وقت تحميل الصفحة من خلال التحقّق من document.wasDiscarded.

مرة أخرى، بما أنّه لا يتم تنفيذ موثوقية أحداث مراحل النشاط وترتيبها بشكل متسق في جميع المتصفحات، فإنّ أسهل طريقة لاتباع النصائح الواردة في الجدول هي استخدام PageLifecycle.js.

واجهات برمجة التطبيقات القديمة لمراحل النشاط التي يجب تجنُّبها

يجب تجنُّب الأحداث التالية قدر الإمكان.

حدث إلغاء التحميل

يتعامل العديد من المطوّرين مع الحدث unload على أنّه ردّ اتصال مضمون ويستخدمونه كإشارة لنهاية الجلسة من أجل حفظ الحالة وإرسال بيانات الإحصاءات، ولكنّ إجراء ذلك غير موثوق به على الإطلاق، خاصةً على الأجهزة الجوّالة. لا يتم تنشيط حدث unload في العديد من حالات إلغاء التحميل النموذجية، بما في ذلك إغلاق علامة تبويب من مبدّل علامات التبويب على الأجهزة الجوّالة أو إغلاق تطبيق المتصفّح من مبدّل التطبيقات.

لهذا السبب، من الأفضل دائمًا الاعتماد على الحدث visibilitychange لتحديد وقت انتهاء الجلسة، والاعتماد على حالة الإخفاء كـ آخر وقت موثوق لحفظ بيانات التطبيق والمستخدم.

بالإضافة إلى ذلك، يمكن أن يؤدي مجرد توفّر معالج أحداث 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.
    event.returnValue = true;
  }
});

بدلاً من ذلك، يمكنك إجراء ما يلي (لأنّه لا يضيف أداة معالجة beforeunload إلا عند الحاجة إليها، ويزيلها عندما لا تكون هناك حاجة إليها):

const beforeUnloadListener = (event) => {
  event.preventDefault();

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

// A function that adds a `beforeunload` listener if there are unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

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

الأسئلة الشائعة

لماذا لا توجد حالة "جارٍ التحميل"؟

تحدّد Page Lifecycle API الحالات على أنّها منفصلة ولا تتداخل مع بعضها. بما أنّه يمكن تحميل الصفحة في الحالة النشطة أو غير النشطة أو المخفية، وبما أنّه يمكن تغيير الحالات أو حتى إنهاء التحميل قبل اكتماله، فإنّ حالة التحميل المنفصلة لا معنى لها في هذا النموذج.

تنفّذ صفحتي مهام مهمة عندما تكون مخفية، كيف يمكنني منع تجميدها أو تجاهلها؟

هناك العديد من الأسباب المشروعة التي تمنع تجميد صفحات الويب أثناء تشغيلها في الخلفية. وأوضح مثال على ذلك هو تطبيق يشغّل الموسيقى.

هناك أيضًا حالات يكون فيها تجاهل Chrome للصفحة أمرًا محفوفًا بالمخاطر، مثل إذا كانت الصفحة تحتوي على نموذج يتضمّن بيانات أدخلها المستخدم ولم يتم إرسالها، أو إذا كانت الصفحة تتضمّن معالج beforeunload يعرض تحذيرًا عند إلغاء تحميل الصفحة.

في الوقت الحالي، سيتوخى Chrome الحذر عند تجاهل الصفحات ولن يفعل ذلك إلا عندما يكون متأكدًا من أنّه لن يؤثر في المستخدمين. على سبيل المثال، لن يتم تجاهل الصفحات التي تم رصدها وهي تنفّذ أيًا مما يلي أثناء كونها في حالة مخفية، إلا في حال وجود قيود شديدة على الموارد:

  • تشغيل الصوت
  • استخدام WebRTC
  • تعديل عنوان الجدول أو رمز الموقع المفضّل
  • عرض التنبيهات
  • إرسال إشعارات فورية

للاطّلاع على قائمة الميزات الحالية المستخدَمة لتحديد ما إذا كان يمكن تجميد علامة تبويب أو تجاهلها بأمان، يُرجى الرجوع إلى الأساليب التجريبية للتجميد والتجاهل في Chrome.

ما هي ميزة "التخزين المؤقت للصفحات"؟

التخزين المؤقت للصفحات هو مصطلح يُستخدم لوصف عملية تحسين التنقّل التي تنفّذها بعض المتصفحات، ما يجعل استخدام أزرار الرجوع إلى الصفحة السابقة والانتقال إلى الصفحة التالية أسرع.

عندما ينتقل المستخدم من صفحة إلى أخرى، تجمد هذه المتصفحات نسخة من تلك الصفحة حتى يمكن استئنافها بسرعة في حال عاد المستخدم إلى الصفحة السابقة باستخدام زر الرجوع أو زر التقدم. يُرجى العِلم أنّ إضافة معالج أحداث unload يمنع إمكانية إجراء هذا التحسين.

من جميع النواحي، يكون التجميد هذا مماثلاً من الناحية الوظيفية للتجميد الذي تنفّذه المتصفحات للحفاظ على وحدة المعالجة المركزية/البطارية، ولهذا السبب، يُعدّ جزءًا من حالة دورة الحياة المجمّدة.

إذا لم يكن بإمكاني تشغيل واجهات برمجة التطبيقات غير المتزامنة في الحالتَين المجمدة أو المنتهية، كيف يمكنني حفظ البيانات في IndexedDB؟

في حالتي التجميد والإنهاء، يتم تعليق المهام القابلة للتجميد في قوائم المهام الخاصة بالصفحة، ما يعني أنّه لا يمكن استخدام واجهات برمجة التطبيقات غير المتزامنة والمستندة إلى معاودة الاتصال بشكل موثوق.

على الرغم من أنّ معظم واجهات برمجة تطبيقات IndexedDB تستند إلى دوال ردّ النداء، فإنّ طريقة commit() في واجهة IDBTransaction توفّر طريقة لبدء عملية التنفيذ في معاملة نشطة بدون انتظار إرسال الأحداث من الطلبات المعلقة. يوفّر ذلك طريقة موثوقة لحفظ البيانات في قاعدة بيانات IndexedDB في معالج الأحداث freeze أو visibilitychange، لأنّ عملية الحفظ يتم تنفيذها على الفور بدلاً من وضعها في قائمة انتظار في مهمة منفصلة.

اختبار تطبيقك في حالتي التجميد والإلغاء

لاختبار سلوك تطبيقك في حالتي التجميد والإلغاء، يمكنك الانتقال إلى chrome://discards لتجميد أي من علامات التبويب المفتوحة أو إلغائها.

واجهة مستخدم عمليات تجاهل Chrome
واجهة مستخدم "تجاهل علامات التبويب" في Chrome

يتيح لك ذلك التأكّد من أنّ صفحتك تعالج بشكل صحيح الحدثَين freeze وresume بالإضافة إلى العلامة document.wasDiscarded عند إعادة تحميل الصفحات بعد تجاهلها.

ملخّص

على المطوّرين الذين يريدون احترام موارد النظام على أجهزة المستخدمين إنشاء تطبيقاتهم مع مراعاة حالات "دورة حياة الصفحة". من الضروري ألا تستهلك صفحات الويب موارد النظام بشكل مفرط في الحالات التي لا يتوقّعها المستخدم.

كلما بدأ المزيد من المطوّرين في تنفيذ واجهات برمجة التطبيقات الجديدة لدورة حياة الصفحة، أصبح من الآمن للمتصفّحات تجميد الصفحات التي لا يتم استخدامها وإلغاؤها. وهذا يعني أنّ المتصفحات ستستهلك مقدارًا أقل من الذاكرة ووحدة المعالجة المركزية والبطارية وموارد الشبكة، ما يصب في مصلحة المستخدمين.