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

Mustaq Ahmed
Joe Medley
Joe Medley

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

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

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

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

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

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

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

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

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

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

سلسلة مكالمات setTimeout()

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

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

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

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

وبدون الإصدار 2 من User Activation، يتعذّر تفعيل معالج الحدث الثاني في جميع المتصفحات التي اختبرناها. (حتى لو فشلت المحاولة الأولى في بعض الحالات).

مكالمات "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);
});

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

وتعمل هذه الاستراتيجية مع الإصدار 2 من User Activation، سواءً بالصيغة الأصلية أو بالسلسلة.