Design: الاقتراح: Async / Await JS API

تم إنشاؤها على ٢٦ يوليو ٢٠٢١  ·  16تعليقات  ·  مصدر: WebAssembly/design

تم تطوير هذا الاقتراح بالتعاون مع fmccabe و thibaudmichaud و lukewagner و kripken ، جنبًا إلى جنب مع التعليقات من Stacks Subgroup (مع تصويت غير رسمي بالموافقة على التقدم إلى المرحلة 0 اليوم). لاحظ أنه نظرًا لضيق الوقت ، فإن الخطة هي تقديم عرض تقديمي سريع جدًا (أي 5 دقائق) والتصويت للتقدم إلى المرحلة 1 في 3 أغسطس. لتسهيل ذلك ، نشجع الأشخاص بشدة على إثارة مخاوفهم هنا في وقت مبكر حتى نتمكن من تحديد ما إذا كانت هناك أي مخاوف كبيرة تستحق دفع العرض التقديمي + التصويت إلى تاريخ لاحق مع مزيد من الوقت.

الغرض من هذا الاقتراح هو توفير تفاعل فعال نسبيًا ومتوافقًا نسبيًا بين وعود JavaScript و WebAssembly ولكن مع العمل في ظل القيود التي تنص على أن التغييرات الوحيدة هي على JS API وليس إلى Core wasm.
من المتوقع أن يؤدي اقتراح Stack-Switching في النهاية إلى توسيع WebAssembly الأساسي بوظيفة تنفيذ العمليات التي نقدمها في هذا الاقتراح مباشرةً داخل WebAssembly ، جنبًا إلى جنب مع العديد من عمليات تبديل المكدس القيمة الأخرى ، ولكن حالة الاستخدام الخاصة هذه للتبديل المكدس كانت الاستعجال الكافي لاستحقاق مسار أسرع عبر JS API فقط.
لمزيد من المعلومات ، يرجى الرجوع إلى الملاحظات والشرائح الخاصة باجتماع المجموعة الفرعية Stack في 28 يونيو 2021 ، والتي توضح بالتفصيل سيناريوهات الاستخدام والعوامل التي أخذناها في الاعتبار وتلخص الأساس المنطقي لكيفية وصولنا إلى التصميم التالي.

تحديث: بعد التعليقات التي تلقتها مجموعة Stacks الفرعية من TC39 ، فإن هذا الاقتراح يسمح فقط بتعليق حزم WebAssembly - لا يُجري أي تغييرات على لغة JavaScript ، ولا يُمكّن بشكل غير مباشر الدعم المنفصل asycn / await في JavaScript.

يعتمد هذا (بشكل فضفاض) على اقتراح أنواع js ، والذي يقدم WebAssembly.Function كفئة فرعية من Function .

واجهه المستخدم

الاقتراح هو إضافة الواجهة والمنشئ والطرق التالية إلى JS API ، مع مزيد من التفاصيل حول دلالاتها أدناه.

interface Suspender {
   constructor();
   Function suspendOnReturnedPromise(Function func); // import wrapper
   // overloaded: WebAssembly.Function suspendOnReturnedPromise(WebAssembly.Function func);
   WebAssembly.Function returnPromiseOnSuspend(WebAssembly.Function func); // export wrapper
}

مثال

فيما يلي مثال على كيف نتوقع أن يستخدم المرء واجهة برمجة التطبيقات هذه.
في سيناريوهات الاستخدام الخاصة بنا ، وجدنا أنه من المفيد النظر في وحدات WebAssembly في أنها تشتمل على عمليات استيراد وتصدير "متزامنة" و "غير متزامنة".
تدعم واجهة برمجة تطبيقات JS الحالية عمليات الاستيراد والتصدير "المتزامنة" فقط.
تُستخدم أساليب واجهة Suspender لتغليف الواردات والصادرات ذات الصلة من أجل جعل "غير متزامن" ، حيث يربط كائن Suspender نفسه صراحةً هذه الواردات والصادرات معًا لتسهيل كل من التنفيذ والتركيب.

WebAssembly ( demo.wasm ):

(module
    (import "js" "init_state" (func $init_state (result f64)))
    (import "js" "compute_delta" (func $compute_delta (result f64)))
    (global $state f64)
    (func $init (global.set $state (call $init_state)))
    (start $init)
    (func $get_state (export "get_state") (result f64) (global.get $state))
    (func $update_state (export "update_state") (result f64)
      (global.set (f64.add (global.get $state) (call $compute_delta)))
      (global.get $state)
    )
)

نص ( data.txt ):

19827.987

جافا سكريبت:

var suspender = new Suspender();
var init_state = () => 2.71;
var compute_delta = () => fetch('data.txt').then(res => res.text()).then(txt => parseFloat(txt));
var importObj = {js: {
    init_state: init_state,
    compute_delta: suspender.suspendOnReturnedPromise(compute_delta)
}};

fetch('demo.wasm').then(response =>
    response.arrayBuffer()
).then(buffer =>
    WebAssembly.instantiate(buffer, importObj)
).then(({module, instance}) => {
    var get_state = instance.exports.get_state;
    var update_state = suspender.returnPromiseOnSuspend(instance.exports.update_state);
    ...
});

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

يمكننا سد فجوة التزامن هذه باستخدام JS API الجديدة.
في المثال ، يتم تغليف استيراد وحدة WebAssembly باستخدام suspender.suspendOnReturnedPromise ، ويتم تغليف التصدير باستخدام suspender.returnPromiseOnSuspend ، وكلاهما يستخدم نفس suspender .
هذا suspender يتصل بالاثنين معًا.
يجعل الأمر كذلك ، في حالة إرجاع الاستيراد (غير المغلف) لوعدًا ، يقوم التصدير (المغلف) بإرجاع الوعد ، مع "تعليق" جميع الحسابات بينهما حتى يتم حل وعد الاستيراد.
يؤدي التفاف التصدير بشكل أساسي إلى إضافة علامة async ، كما أن التفاف الاستيراد يضيف بشكل أساسي علامة await ، ولكن على عكس JavaScript ، لا يتعين علينا صراحة وضع مؤشر ترابط async / await على طول الطريق من خلال جميع وظائف WebAssembly الوسيطة!

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

تخصيص

A Suspender في إحدى الحالات التالية:

  • غير نشط - لا يتم استخدامه في الوقت الحالي
  • نشط [ caller ] - عنصر التحكم داخل Suspender ، حيث caller هي الوظيفة التي استدعت إلى Suspender وتتوقع externref يعاد
  • معلق - في انتظار بعض الوعد لحلها

تؤكد الطريقة suspender.returnPromiseOnSuspend(func) أن func هو WebAssembly.Function مع نوع دالة على شكل [ti*] -> [to] ثم تُرجع WebAssembly.Function بنوع الوظيفة [ti*] -> [externref] يقوم بما يلي عند استدعائه بالوسيطات args :

  1. مصائد إذا كانت حالة suspender غير نشطة
  2. تغيير حالة suspender إلى نشط [ caller ] (حيث caller هو المتصل الحالي)
  3. يتيح أن يكون result نتيجة استدعاء func(args) (أو أي فخ أو استثناء تم إلقاؤه)
  4. يؤكد أن حالة suspender نشطة [ caller' ] لبعض caller' (يجب ضمان ذلك ، على الرغم من أن المتصل قد تغير)
  5. تغيير حالة suspender إلى غير نشط
  6. إرجاع (أو إعادة عرض) result إلى caller'

الطريقة suspender.suspendOnReturnedPromise(func)

  • إذا كان func هو WebAssembly.Function ، فإنه يؤكد أن نوع وظيفته على الشكل [t*] -> [externref] وإرجاع WebAssembly.Function مع نوع الوظيفة [t*] -> [externref] ؛
  • بخلاف ذلك ، يؤكد أن func هو Function وإرجاع Function .

في كلتا الحالتين ، تقوم الدالة التي تم إرجاعها بواسطة suspender.suspendOnReturnedPromise(func) بما يلي عند استدعائها باستخدام الوسيطات args :

  1. يتيح أن يكون result نتيجة استدعاء func(args) (أو أي اعتراض أو استثناء تم إلقاؤه)
  2. إذا لم يكن result وعدًا مرتجعًا ، فحينئذٍ يُرجع (أو يعيد حفظه) result
  3. مصائد إذا كانت حالة suspender غير نشطة [ caller ] لبعض caller
  4. يتيح أن يكون frames إطارات المكدس منذ caller
  5. الاعتراضات في حالة وجود أي إطارات للوظائف غير القابلة للتعليق في frames
  6. يغير حالة suspender إلى معلق
  7. لعرض نتيجة result.then(onFulfilled, onRejected) مع الدالات onFulfilled و onRejected التي تقوم بما يلي:

    1. يؤكد أن حالة suspender معلقة (يجب ضمانها)

    2. تغيير حالة suspender إلى نشط [ caller' ] ، حيث caller' هو المتصل بـ onFulfilled / onRejected



      • في حالة onFulfilled ، يتم تحويل القيمة المحددة إلى externref وإرجاعها إلى frames


      • في حالة onRejected ، يتم طرح القيمة المحددة حتى frames كاستثناء وفقًا لواجهة برمجة تطبيقات JS لاقتراح معالجة الاستثناءات



تكون الوظيفة قابلة للتعليق إذا كانت كذلك

  • التي تحددها وحدة WebAssembly ،
  • تم إرجاعها suspendOnReturnedPromise ،
  • تم إرجاعه returnPromiseOnSuspend ،
  • أو يتم إنشاؤها عن طريق إنشاء دالة مضيف لوظيفة قابلة للتعليق

الأهم من ذلك ، أن الوظائف المكتوبة في JavaScript غير قابلة للتعليق ، وتتوافق مع التعليقات الواردة من أعضاء TC39 ، ووظائف المضيف (باستثناء القليل منها المذكورة أعلاه) غير قابلة للتعليق ، بما يتوافق مع التعليقات الواردة من المشرفين على صيانة المحرك.

تطبيق

فيما يلي إستراتيجية تنفيذ لهذا الاقتراح.
إنه يفترض دعم المحرك للتبديل المكدس ، وهو بالطبع المكان الذي تكمن فيه تحديات التنفيذ الرئيسية.

هناك نوعان من الحزم: مكدس مضيف (وجافا سكريبت) ومكدس WebAssembly. يحتوي كل حزمة WebAssembly على حقل معلق يسمى suspender . كل موضوع له مكدس مضيف.

كل Suspender يحتوي على حقلين مرجعيين للمكدس: أحدهما يسمى caller والآخر يسمى suspended .

  • في حالة غير نشط ، كلا الحقلين فارغين.
  • في الحالة النشطة ، يشير الحقل caller إلى المكدس (المعلق) للمتصل ، والحقل suspended خالٍ
  • في ولاية المعلقة، و suspended المراجع حقل (مع وقف التنفيذ) WebAssembly كومة ترتبط حاليا مع حمالة، و caller الحقل فارغة.

suspender.returnPromiseOnSuspend(func)(args) تم تنفيذه بواسطة

  1. التحقق من أن suspender.caller و suspended.suspended فارغين (وإلا فإن الملاءمة)
  2. السماح لـ stack أن يكون مكدس WebAssembly المخصص حديثًا والمرتبط بـ suspender
  3. التبديل إلى stack وتخزين المكدس السابق في suspender.caller
  4. جعل result هو نتيجة func(args) (أو أي فخ أو استثناء تم إلقاؤه)
  5. التبديل إلى suspender.caller وتعيينه على قيمة خالية
  6. تحرير stack
  7. عائد (أو إعادة نمو) result

suspender.suspendOnReturnedPromise(func)(args) بواسطة

  1. استدعاء func(args) ، اصطياد أي فخ أو استثناء
  2. إذا لم يكن result وعدًا مرتجعًا ، فالإرجاع (أو إعادة الإسقاط) result
  3. التحقق من أن suspender.caller ليس فارغًا (الملاءمة وإلا)
  4. دع stack هو المكدس الحالي
  5. بينما stack ليس حزمة WebAssembly المرتبطة بـ suspender :

    • التحقق من أن stack عبارة عن مكدس WebAssembly (الملاءمة بخلاف ذلك)

    • تحديث stack ليصبح stack.suspender.caller

  6. التبديل إلى suspender.caller ، وتعيينه على null ، وتخزين المكدس السابق في suspender.suspended
  7. إرجاع نتيجة result.then(onFulfilled, onRejected) بالوظائف onFulfilled و onRejected التي تم تنفيذها بواسطة

    1. التبديل إلى suspender.suspended ، وتعيينه على قيمة خالية ، وتخزين المكدس السابق في suspender.caller



      • في حالة onFulfilled ، يتم تحويل القيمة المعطاة إلى externref وإعادتها


      • في حالة onRejected ، إعادة طرح القيمة المعطاة



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

ال 16 كومينتر

هل من الممكن كشف واجهة برمجة تطبيقات تتلقى وظيفة / مولد غير متزامن (مزامنة أو غير متزامن) ثم تحويلها إلى وظيفة قابلة للتعليق؟

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

هل النية أن يكون Suspender جزءًا من JS أم أنه واجهة برمجة تطبيقات منفصلة؟ هل هو حصري لـ wasm ( WebAssembly.Suspender )؟ يبدو لي أنه ينبغي مناقشة هذا الاقتراح في TC39.

لا يُقصد به على وجه التحديد التأثير على برامج JS. بتعبير أدق ، ستؤدي محاولة تعليق دالة JS إلى حدوث اعتراض. لقد واجهنا بعض المشاكل لضمان ذلك.
ومع ذلك ، يمكنني رفعه مع Shu-yu للحصول على رأيه.

عذرًا ، chicoxyzzy ، أرى أنني نسيت تضمين بعض السياق / التحديثات من مجموعة Stacks الفرعية. تمت كتابة مقترحات تبديل المكدس الأقدم مع توقع أنه يجب أن تكون قادرًا على التقاط إطارات JavaScript / مضيفة في مكدسات معلقة. ومع ذلك ، تلقينا تعليقات من الأشخاص في TC39 مفادها أن هناك مخاوف من أن هذا سيؤثر بشكل كبير على نظام JS البيئي ، وتلقينا تعليقات من منفذي مضيفين مفادها أن هناك مخاوف من عدم قدرة جميع الأطر المضيفة على تحمل التعليق. لذا ، فإن مجموعة Stacks الفرعية منذ ذلك الحين تضمن أن التصميمات لا تلتقط سوى إطارات WebAssembly (المرتبطة) في مكدسات معلقة ، وهذا الاقتراح يلبي هذه الخاصية. لقد قمت بتحديث OP لتضمين هذه الملاحظة الهامة.

إنه لأمر رائع أن نرى التقدم هنا. هل هناك أي أمثلة على كيفية استخدام ذلك في تكامل ESM في Wasm؟

الأخبار السيئة هي أنه نظرًا لأن هذا كله موجود في JS API ، فلا يمكنك ببساطة استيراد وحدة ESM وحصل على دعم تبديل المكدس للوعود. والخبر السار هو أنه لا يزال بإمكانك استخدام وحدات ESM مع واجهة برمجة التطبيقات هذه ، فقط مع بعض وحدات JS ESM كلصق.

على وجه الخصوص ، قمت بإعداد ثلاث وحدات ESM: foo-exports.js ، foo-wasm.wasm ، و foo-imports.js . تقوم الوحدة النمطية foo-imports.js بإنشاء الحمّالة ، وتستخدمها لتغليف جميع الواردات "غير المتزامنة" التي تنتج الوعد والتي تتطلبها foo-wasm.wasm ، وتقوم بتصدير الحمّالة وتلك الواردات. foo-wasm.wasm بعد ذلك باستيراد جميع عمليات الاستيراد "غير المتزامنة" من foo-imports.js وجميع عمليات الاستيراد "المتزامنة" مباشرةً من الوحدات النمطية الخاصة بها (أو ، بالطبع ، يمكنك أيضًا توكيلها من خلال foo-imports.js ، والذي يمكنه تصديرها بدون تغليف). أخيرًا ، يستورد foo-exports.js المعلق من foo-imports.js ، ويستورد صادرات foo-wasm.wasm ، ويلتف الصادرات "غير المتزامنة" باستخدام الحمالة ، ثم يصدر (غير مغلف) "متزامن" الصادرات والصادرات المغلفة "غير المتزامنة". يقوم العملاء بعد ذلك بالاستيراد من foo-exports.js ولا يلمسوا (أو يحتاجون إلى معرفة) مباشرةً (أو يحتاجون إلى معرفة) foo-wasm.wasm أو foo-imports.js .

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

هل كان ذلك مفهومًا ، وهل تعتقد أنه سيخدم احتياجاتك (وإن كان محرجًا)؟

أتفهم الحاجة إلى التغليف ، على الأقل أثناء استيراد WebAssembly.Module من نوع Wasm غير ممكن بعد (وآمل أن يكون ذلك في الوقت المناسب).

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

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

كانت التعليقات التي تلقيتها هي أن هناك رغبة في عدم تغيير تكامل ESM أيضًا

هل يمكنك التعمق في معرفة مصدر هذه الملاحظات؟ هناك مجال كبير لتوسيع تكامل ESM مع دلالات تكامل ذات مستوى أعلى ، وهي مساحة لا أشعر بها قد تم استكشافها بشكل كامل ، ومن ثم فإنني أطرحها. لم أسمع عن مقاومة لتحسين هذا المجال في الماضي. يمكن أن تكون رؤية هذا كمنطقة للحلاوة مفيدة لمطوري JS في السماح باستيراد / تصدير Promise المباشر.

تجدر الإشارة إلى أن هذا الاقتراح يعيق القدرة على أن تكون وحدة JS واحدة في دورة ما هي المستورد والمستورد إلى وحدة Wasm والتي لا يزال بإمكانها العمل في الوقت الحالي لاستيراد الوظائف بفضل رفع وظيفة دورة JS في تكامل ESM ، ولكن لن تدعم هذه الدورة الرفع باستخدام غلاف تعبير Suspender حول الوظيفة المستوردة.

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

لقد أخطأت في قراءة تعليق @ Jack-Works ، وقمت بتعديل تعليقي أعلاه.

شكرًا RossTate على التوضيحات ، نعم أقترح استكشاف إمكانية مطابقة سياقات تعليق الاستيراد والتصدير عبر البيانات الوصفية في الملف الثنائي نفسه لإبلاغ تكامل المضيف ، ولكن لا أتوقع ذلك في MVP بأي وسيلة. أنا أيضًا أغتنم الفرصة للإشارة إلى أن تكامل ESM هو مساحة قد تستفيد من السكر بشكل عام ، بشكل منفصل عن JS API الأساسي.

للتوضيح ، كان التحدي الذي أشرت إليه هو أن أي خيارات أضفناها إلى WebAssembly.instantiate() (أو الإصدارات الجديدة من WebAssembly.instantiate() مع معلمات جديدة) يجب أن تظهر بطريقة ما عند تحميل wasm عبر ESM - التكامل ، وليس أن التكامل ESM كان ثابتًا.

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

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

اعجبتني هذه الفكرة. أظن أننا سنجد أنفسنا نريد قسمًا مخصصًا مشابهًا لتكامل Wasm GC / JS-ESM (أو جزء من نفس القسم). لست متأكدًا إلى أي مدى قد يكون هذا القسم المخصص متعدد اللغات ، ولكن في كلتا الحالتين ، من المحتمل أن يكون أقل عمومية قليلاً من أنواع الواجهة ، ويميل أيضًا إلى استخدامه داخل أحد المكونات ، وليس فقط بينهما.

هل يريد أي شخص كتابة نوع من الجوهر أو التمهيدي لوصف التصميم الأساسي لهذا القسم المخصص؟

يبدو أن هذا خيار ممكن. كما ذكرت ، تمت مناقشة خيارات مماثلة في اقتراح GC ، كما هو الحال في WebAssembly / gc # 203. من المقرر مبدئيًا مناقشة تكامل JS في المجموعة الفرعية GC في المجموعة الفرعية غدًا ، لذلك قد يكون من الجيد الاحتفاظ بالاتصال المحتمل بهذا الاقتراح في الاعتبار أثناء تلك المناقشة (أو قد يتبين أنه غير ذي صلة ، اعتمادًا على كيفية سير المناقشة).

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

JimmyVV picture JimmyVV  ·  4تعليقات

spidoche picture spidoche  ·  4تعليقات

konsoletyper picture konsoletyper  ·  6تعليقات

beriberikix picture beriberikix  ·  7تعليقات

thysultan picture thysultan  ·  4تعليقات