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

توافق المتصفّح

  • Chrome: 68
  • ‫Edge: 79
  • Firefox: غير متوافق
  • Safari: غير متوافق

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

الخلفية

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

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

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

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

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

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

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

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

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

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

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

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

تمثيل مرئي لتدفق الحالة والحدث موصوف في هذا المستند.
حالة وتدفق الحدث في Page Lifecycle API.

الولايات

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

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

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

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

الحالات التالية المحتملة:
سلبي (من خلال فعالية blur)

غير نشِطة

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

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

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

مخفية

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

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

الحالات التالية المحتمَلة:
passive (من خلال الحدث visibilitychange)
frozen (من خلال الحدث freeze)
discarded (لم يتم تشغيل أي أحداث)
terminated (لم يتم تشغيل أي أحداث)

مُعلَّق

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

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

الحالات السابقة المحتملة:
مخفية (من خلال فعالية freeze)

الحالات التالية المحتمَلة:
نشطة (عبر الحدث resume، ثم pageshow الأحداث)
سلبي (عبر الحدث resume، ثم الحدث pageshow)
مخفية{/20 (من خلال الحدث resume)
مخفية{/20

تم إنهاء

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

الحالات السابقة المحتمَلة:
مخفي (من خلال الحدث pagehide)

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

تم تجاهلها

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

في الحالة مُهمَلة، تكون علامة التبويب نفسها (بما في ذلك عنوان علامة التبويب ورمز الشارة) مرئية للمستخدم عادةً على الرغم من اختفاء الصفحة.

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

الحالات التالية المحتملة:
لا ينطبق

الفعاليات

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

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

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

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

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

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

blur

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

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

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

الحالات الحالية المحتمَلة:
passive

visibilitychange

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

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

الحالات الحالية المحتمَلة:
سلبية
مخفية

freeze *

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

الحالات السابقة المحتمَلة:
مخفي

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

resume *

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

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

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

pageshow

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

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

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

الحالات الحالية المحتمَلة:
نشط
سلبي
مخفي

pagehide

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

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

الحالات السابقة المحتمَلة:
مخفي

الحالات الحالية المحتمَلة:
مجمّد (event.persisted صحيح، يليه الحدث freeze)
متوقف (event.persisted خطأ، يتبعه الحدث unload)

beforeunload

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

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

الحالات السابقة المحتمَلة:
مخفي

الحالات الحالية المحتمَلة:
تم إنهاؤها

unload

يتم تفريغ الصفحة.

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

الحالات السابقة المحتمَلة:
مخفي

الحالات الحالية المحتمَلة:
تم إنهاء

* يشير إلى حدث جديد محدّد من خلال Page Lifecycle API

ميزات جديدة أُضيفت في Chrome 68

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

في الإصدار 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() المحدّدة سابقًا، يمكنك رصد جميع التغييرات في حالة Page Lifecycle باستخدام الرمز البرمجي التالي.

// 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}. وهناك بضعة أسباب لذلك:

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

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

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

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

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

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

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

Passive

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

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

Hidden

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

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

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

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

Frozen

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

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

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

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

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

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

Terminated

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

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

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

Discarded

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

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

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

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

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

حدث Unload

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

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

بالإضافة إلى ذلك، يمكن أن يؤدي مجرد توفّر معالِج حدث unload مسجَّل (من خلال onunload أو addEventListener()) إلى منع المتصفّحات من وضع الصفحات في التخزين المؤقت للصفحات لتحميل الصفحات بشكلٍ أسرع للرجوع والتقديم.

في جميع المتصفّحات الحديثة، ننصح دائمًا باستخدام حدث pagehide لرصد أي عمليات إلغاء محتملة للصفحات (أي حالة تم الإنهاء) بدلاً من حدث unload. إذا كنت بحاجة إلى إتاحة الإصدار 10 من Internet Explorer والإصدارات الأقدم، عليك رصد ميزة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);
  }
});

بدلاً من ذلك، يمكنك إجراء ما يلي (بما أنّه لا تتم إضافة مستمع 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);
});

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

لماذا لا تتوفّر حالة "التحميل"؟

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

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

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

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

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

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

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

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

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

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

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

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

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

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

بالنسبة إلى الرموز البرمجية التي يجب أن تعمل اليوم، يتوفّر للمطوّرين خياران:

  • استخدام ميزة "تخزين الجلسة": تخزين الجلسة هو إجراء متزامن ويتم الاحتفاظ به عند تجاهل الصفحات.
  • استخدام IndexedDB من خدمة Worker: يمكن لخدمة Worker تخزين البيانات في IndexedDB بعد إغلاق الصفحة أو تجاهلها. في مستمع أحداث freeze أو pagehide، يمكنك إرسال البيانات إلى عامل الخدمة من خلال postMessage()، ويمكن لعامل الخدمة التعامل مع حفظ البيانات.

اختبار تطبيقك في حالتَي التجميد والتجاهل

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

واجهة مستخدم Chrome التي تم تجاهلها
واجهة مستخدم Chrome Discards

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

ملخّص

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

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