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

دعم المتصفح

  • Chrome: 68
  • الحافة: 79.
  • Firefox: غير متوافق
  • Safari: غير متوافق

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

الخلفية

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

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

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

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

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

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

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

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

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

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

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

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

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

الولايات

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

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

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

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

الحالات التالية المحتمَلة:
passive (من خلال الحدث 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)

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

تم التجاهل

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

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

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

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

الفعاليات

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

الاسم التفاصيل
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

الميزات الجديدة التي تمت إضافتها في الإصدار 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() المحدّدة سابقًا، يمكنك رصد جميع التغييرات في حالة 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 دائمًا لرصد عمليات إزالة التحميل المحتمَلة للصفحات (المعروفة أيضًا باسم الحالة terminated) بدلاً من الحدث 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) والبطارية والشبكة، وهو ما يصبّ في مصلحة المستخدمين.