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

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

  • 68
  • 79
  • x
  • x

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

الخلفية

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

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

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

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

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

تحاول واجهة برمجة تطبيقات دورة حياة الصفحة حل هذه المشكلة من خلال:

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

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

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

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

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

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

تمثيل مرئي للحالة وتدفق الحدث الموضّح في هذا المستند.
حالة واجهة برمجة تطبيقات دورة حياة الصفحة ومسار الحدث

الولايات

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

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

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

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

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

سلبي

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

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

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

مخفي

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

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

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

مجمّد

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

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

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

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


resume

تم إنهاؤه

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

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

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

تم التجاهل

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

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

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

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

فعاليات

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

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

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

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

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

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

blur

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

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

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

الحالات الحالية المحتملة:
سلبية

visibilitychange

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

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

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

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

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

في إصدار Chrome 68، يمكن للمطوّرين الآن ملاحظة أنّ علامة التبويب المخفية يتم تجميدها وإلغاء تجميدها من خلال الاستماع إلى حدثَي freeze وresume على document.

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

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

بدايةً من Chrome 68، يشتمل الكائن 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() التي تم تحديدها سابقًا، يمكنك ملاحظة جميع تغييرات حالة دورة حياة الصفحة باستخدام الرمز التالي.

// 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 (أو قاعدة البيانات المفهرسة من خلال commit()) التي تريد استعادتها إذا تم تجاهل الصفحة وإعادة تحميلها لاحقًا.

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

Terminated

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

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

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

Discarded

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

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

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

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

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

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

يتعامل العديد من المطوّرين مع حدث 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);
});

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

لماذا لا تظهر حالة "التحميل"؟

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ملخّص

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

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