جعل عملية تنشيط المستخدمين متسقة عبر واجهات برمجة التطبيقات

Mustaq Ahmed
Joe Medley
Joe Medley

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

تعرِض المتصفحات الرئيسية حاليًا سلوكًا متباينًا على نطاق واسع حول كيفية التحكّم في واجهات برمجة التطبيقات التي تتطلب تفعيلًا من خلال تنشيط المستخدمين. في Chrome، استندت عملية التنفيذ إلى نموذج قائم على رموز مميّزة وتبيّن أنّه معقّد للغاية في تحديد سلوك متّسق في جميع واجهات برمجة التطبيقات المحمية ببوابة تفعيل. على سبيل المثال، كان Chrome يسمح بالوصول غير الكامل إلى واجهات برمجة التطبيقات التي تتطلب التفعيل من خلال طلبات postMessage() وsetTimeout()، ولم يكن تفعيل المستخدمين متاحًا باستخدام Promises وXHR والتفاعل مع وحدة التحكّم في الألعاب وما إلى ذلك. يُرجى العِلم أنّ بعض هذه الأخطاء شائعة ولكنها قديمة.

في الإصدار 72، يوفّر Chrome الإصدار 2 من User Activation، ما يجعل توفّر خيار تفعيل المستخدم مكتملاً لجميع واجهات برمجة التطبيقات التي تتضمّن بوابة تفعيل. يحلّ ذلك مشكلة عدم الاتساق المذكورة أعلاه (وبعض المشاكل الأخرى، مثل MessageChannels)، ما نعتقد أنّه سيسهّل تطوير الويب في ما يتعلّق بتنشيط المستخدِم. إضافةً إلى ذلك، توفّر طريقة التنفيذ الجديدة تنفيذًا مرجعيًا لمواصفات جديدة مُقترَحة تهدف إلى تجميع كل المتصفّحات معًا على المدى الطويل.

كيف تعمل الإصدار 2 من ميزة "تفعيل المستخدِم"؟

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

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

ما الذي سيتغيّر؟

  • يُضفي الإصدار 2 من User Activation تيسيرًا لمفهوم إمكانية رؤية تفعيل المستخدم عبر حدود الإطارات: يؤدي تفاعل المستخدم مع إطار معيّن إلى تفعيل جميع الإطارات التي تحتوي على الإطارات (وتلك الإطارات فقط) بغض النظر عن مصدرها. (في الإصدار 72 من Chrome، لدينا حلّ مؤقت لتوسيع نطاق الرؤية ليشمل جميع الإطارات التي لها مصدر واحد. سنزيل هذا الحلّ البديل بعد أن تتوفّر لدينا طريقة ل تمرير تفعيل المستخدم إلى الإطارات الفرعية بشكل صريح.)
  • عند طلب واجهة برمجة تطبيقات متاحة بعد التفعيل من إطار نشط ولكن خارج رمز معالِج الأحداث، ستعمل هذه الواجهة طالما كانت حالة تفعيل المستخدِم "نشطة" (على سبيل المثال، لم تنته صلاحيتها ولم يتم استخدامها). قبل الإصدار 2 من User Activation، كان من المؤكد أنّه سيتعذّر إكمال عملية التفعيل.
  • يتم دمج تفاعلات المستخدِم المتعدّدة غير المستخدَمة خلال الفاصل الزمني لانتهاء الصلاحية في عملية تفعيل واحدة تتوافق مع التفاعل الأخير.

أمثلة على الاتساق في واجهات برمجة التطبيقات المحظورة على عملية التفعيل

في ما يلي مثالان يتضمّنان نافذتَي منبثقتَين (يتم فتحهما باستخدام window.open()) يُظهران كيف تجعل User Activation v2 سلوك واجهات برمجة التطبيقات التي تتطلب التفعيل متّسقًا.

طلبات setTimeout() متسلسلة

هذا المثال مأخوذ من العرض التوضيحي setTimeout(). إذا حاول معالِج click فتح نافذة منبثقة في غضون ثانية، من المتوقّع أن يتم النجاح بغض النظر عن كيفية "إنشاء" الرمز للتأخير. تلبي الإصدار 2 من User Activation هذا التوقّع، لذا يفتح كلّ من معالِجات الأحداث التالية نافذة منبثقة عند click (مع تأخير 100 ملي ثانية):

function popupAfter100ms() {
  setTimeout(callWindowOpen, 100);
}

function asyncPopupAfter100ms() {
  setTimeout(popupAfter100ms, 0);
}

someButton.addEventListener('click', popupAfter100ms);
someButton.addEventListener('click', asyncPopupAfter100ms);

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

مكالمات postMessage() على جميع النطاقات

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

// Parent frame code
window.addEventListener('message', e => {
  if (e.data === 'open_popup' && e.origin === child_origin)
    window.open('about:blank');
});

// Child frame code:
someButton.addEventListener('click', () => {
  parent.postMessage('hi_there', parent_origin);
  parent.postMessage('open_popup', parent_origin);
});

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

يعمل هذا الإجراء مع الإصدار 2 من User Activation (تفعيل المستخدم)، سواء في شكله الأصلي أو مع التسلسل.