البحث الشامل عن RenderingNG: BlinkNG

Stefan Zager
Stefan Zager
Chris Harrelson
Chris Harrelson

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

بدأ Blink الحياة كشوكة لشوكة WebKit، وهي نفسها شوكة من KHTML، والتي تم إنشاؤها في عام 1998. تحتوي هذه الأداة على بعض من أقدم الرموز (والأكثر أهمية) في Chromium، وبحلول عام 2014 كانت تُظهر عمرها بالتأكيد. خلال هذا العام، شرعنا في تنفيذ مجموعة من المشاريع الطموحة تحت شعار ما نطلق عليه اسم BlinkNG، وذلك بهدف معالجة أوجه القصور الطويلة الأمد في تنظيم رمز Blink وبنيته. سوف تستكشف هذه المقالة BlinkNG ومشاريعه التي يتألف منها: لماذا قمنا بها، وما أنجزه، والمبادئ التوجيهية التي شكلت تصميمه، وفرص التحسينات المستقبلية التي يتحملها.

مسار العرض قبل BlinkNG وبعده.

العرض قبل NG

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

  • هل يلزم تحديث ناتج النمط أو التخطيط أو الطلاء؟
  • متى تصبح هذه البيانات "النهائية" قيمة؟
  • متى يمكنني تعديل هذه البيانات؟
  • متى سيتم حذف هذا العنصر؟

هناك العديد من الأمثلة على ذلك، بما في ذلك:

سينشئ النمط ComputedStyle استنادًا إلى أوراق الأنماط. ولكن ComputedStyle لم تكن قابلة للتغيير؛ وفي بعض الحالات، قد يتم تعديله من خلال مراحل العملية اللاحقة.

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

سيُنشئ النمط بنى بيانات ملحقة تحدد مسار التركيب، ويتم تعديل بُنى البيانات هذه في مكانها الصحيح في كل مرحلة بعد النمط.

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

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

التغييرات التي أجريناها

تتكون BlinkNG من العديد من المشروعات الفرعية، الكبيرة والصغيرة، وكلها بهدف مشترك هو القضاء على العقبات المعمارية التي تم وصفها سابقًا. تشترك هذه المشاريع في بعض المبادئ التوجيهية المصممة لجعل مسار العرض أكثر من مجرد مسار فعلي:

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

قد تؤدي القائمة الكاملة لمشاريع BlinkNG الفرعية إلى جعل القراءة مملة، ولكن فيما يلي بعض النتائج الخاصة.

دورة حياة المستند

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

  • إذا كنا بصدد تعديل سمة ComputedStyle، يجب أن تكون دورة حياة المستند kInStyleRecalc.
  • إذا كانت حالة DocumentLifecycle هي kStyleClean أو في تاريخ لاحق، على NeedsStyleRecalc() أن تعرض القيمة false لأي عقدة مرفقة.
  • عند الدخول في مرحلة الطلاء، يجب أن تكون حالة دورة الحياة kPrePaintClean.

خلال عملية تنفيذ BlinkNG، أزلنا بشكل منهجي مسارات الرموز التي انتهكت هذه المتغيّرات وتميّزنا بمزيد من التأكيدات في الرمز البرمجي لضمان عدم تراجعنا.

إذا كنت قد اختبرت ترميز عرض على مستوى منخفض، فلعلك تطرح السؤال التالي على نفسك: "كيف وصلت إلى هنا؟" كما ذكرنا سابقًا، هناك مجموعة متنوعة من نقاط الدخول في مسار العرض. في السابق، تضمنت هذه المسارات مسارات الاستدعاءات المتكررة والداخلية، والأماكن التي دخلنا فيها المسار في مرحلة متوسطة، بدلاً من البدء من البداية. في مسار BlinkNG، قمنا بتحليل مسارات الاستدعاء هذه وتوصلنا إلى أنها جميعها قابلة للاختزال ضمن سيناريوهين أساسيين:

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

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

نمط الأنابيب والتخطيط والطلاء المسبق

بشكلٍ عام، تكون مراحل العرض قبل الطلاء مسؤولة عن ما يلي:

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

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

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

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

فيما يلي بعض المشروعات المهمة التي تخلصت من العيوب المعمارية في مراحل العرض قبل الطلاء.

Project Squad: تنظيم مرحلة النمط

تعامل هذا المشروع مع اثنين من العيوب الرئيسيين في مرحلة النمط منعته من التركيز على المسار الصحيح:

هناك ناتجان أساسيان لمرحلة النمط: ComputedStyle، يتضمنان نتيجة تشغيل خوارزمية التتابع في CSS على شجرة DOM. وشجرة LayoutObjects، التي تحدد ترتيب العمليات لمرحلة التخطيط. من الناحية النظرية، يجب أن يتم تشغيل خوارزمية التتابع بصرامة قبل إنشاء شجرة التخطيط؛ ولكن في السابق، كانت هاتان العمليتان متداخلتين. نجح فريق Project Squad في تقسيم هاتين الميزتَين إلى مرحلتَين متتاليتَين منفصلتَين.

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

LayoutNG: توسيع مرحلة التخطيط

شكّل هذا المشروع الضخم أحد أهم ركائز RenderingNG، وقد شكّل إعادة صياغة كاملة لمرحلة عرض التنسيق. لن نحقق العدالة للمشروع بأكمله هنا، ولكن هناك بعض الجوانب البارزة لمشروع BlinkNG ككل:

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

مرحلة ما قبل الطلاء

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

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

أشجار الخصائص: الأشكال الهندسية المتسقة

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

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

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

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

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

المركّب بعد الطلاء: طلاء الأنابيب والتركيب

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

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

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

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

كانت خطتنا هي التخلص بمرور الوقت من جميع كائنات DisableCompositingQueryAssert لمواقع الاتصال، ثم إعلان الرمز آمنًا وصحيحًا. لكن ما اكتشفناه هو أن عددًا من الاستدعاءات كان من المستحيل في الأساس إزالة ما دامت الطبقات قد حدثت قبل الطلاء. (لقد تمكنا أخيرًا من إزالته في الآونة الأخيرة فقط.) كان هذا هو السبب الأول الذي تم اكتشافه لمشروع "Composite After Paint". ما تعلمناه هو أنه حتى إذا كانت لديك مرحلة خط أنابيب محددة جيدًا لإحدى العمليات، فستتعطل في النهاية إذا كانت في المكان الخطأ داخل مسار العملية.

كان السبب الثاني لمشروع Composite After Paint هو خطأ التركيب الأساسي. وتوضيح هذا الخطأ هو أنّ عناصر DOM لا تمثّل تمثيلاً جيدًا بنسبة عرض إلى ارتفاع 1:1 لمخطط الطبقات الفعال أو الكامل لمحتوى صفحات الويب. ونظرًا لأن التركيب كان قبل الطلاء، فهو يعتمد بطبيعتها بشكل أكبر أو أقل على عناصر DOM، وليس قوائم العرض أو أشجار الخصائص. يتشابه هذا إلى حد كبير مع السبب الذي دفعنا إلى طرح أشجار الخصائص، وكما هو الحال مع أشجار الممتلكات، ينقطع الحل مباشرةً إذا اكتشفت مرحلة المسار الصحيحة وقمت بتشغيلها في الوقت المناسب وتزويدها بهياكل البيانات الرئيسية الصحيحة. وكما هو الحال مع أشجار الممتلكات، كانت هذه فرصة جيدة لضمان أنه بمجرد اكتمال مرحلة الطلاء، سيكون ناتجها غير قابل للتغيير لجميع مراحل المسار اللاحقة.

المزايا

كما رأيت، يؤدي مسار العرض المحدّد جيدًا إلى فوائد هائلة على المدى الطويل. هناك أكثر مما قد تعتقد:

  • الموثوقية المحسّنة بشكل كبير: هذه الإجابة واضحة ومباشرة. إن الرموز البرمجية الأكثر وضوحًا ذات واجهات محددة ومفهومة يسهل فهمها وكتابتها واختبارها. هذا يجعلها أكثر موثوقية. بالإضافة إلى ذلك، يجعل الرمز البرمجي أكثر أمانًا واستقرارًا، من خلال تقليل الأعطال وتقليل الأخطاء التي تحدث بعد الاستخدام.
  • توسيع نطاق التغطية التجريبية: أثناء استخدام BlinkNG، أضفنا عددًا كبيرًا من الاختبارات الجديدة إلى الحزمة. ويشمل ذلك اختبارات الوحدة التي توفّر تحقّقًا مركّزًا من العناصر الداخلية. اختبارات الانحدار التي تمنعنا من إعادة تقديم الأخطاء القديمة التي أصلحناها (كثيرة!) والكثير من الإضافات المتاحة للجميع إلى مجموعة أدوات اختبار النظام الأساسي للويب التي يتم الاحتفاظ بها بشكلٍ جماعي، والتي تستخدمها جميع المتصفّحات لقياس مدى التوافق مع معايير الويب.
  • سهولة في التوسيع: في حال تقسيم النظام إلى مكوّنات واضحة، ليس من الضروري فهم المكوّنات الأخرى على أي مستوى من التفاصيل لإحراز تقدّم في ما يتعلق بالعنصر الحالي. يسهّل ذلك على الجميع إضافة قيمة إلى الرمز البرمجي للعرض بدون الحاجة إلى أن يكونوا خبراء في المجال، كما يسهِّل عليهم فهم سلوك النظام بأكمله.
  • الأداء: يُعدّ تحسين الخوارزميات المكتوبة بلغة سباغيتي أمرًا صعبًا إلى حدّ كبير، ولكن يستحيل تقريبًا تحقيق إنجازات أكبر مثل التمرير المتسلسل عام والرسوم المتحركة أو العمليات وسلاسل المحادثات لعزل المواقع الإلكترونية بدون هذا المسار. يمكن أن يساعدنا التوازي في تحسين الأداء بشكل هائل، ولكنه أيضًا معقد للغاية.
  • الإنتاج والاحتواء: هناك العديد من الميزات الجديدة التي توفّرها BlinkNG وتتّبع مسار التعلُّم بطرق جديدة ومبتكرة. على سبيل المثال، ماذا لو أردنا تشغيل مسار العرض فقط حتى تنتهي صلاحية الميزانية؟ أو هل يمكنك تخطّي عملية عرض الأشجار الفرعية المعروفة بأنّها غير مهمة للمستخدم في الوقت الحالي؟ وهذا ما تفعّله خاصية content-visibility في CSS. ماذا عن جعل نمط المكون يعتمد على تخطيطه؟ وهي عبارة عن طلبات بحث الحاوية.

دراسة حالة: طلبات بحث الحاويات

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

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

كيف يمكننا حل هذه المشكلة؟ أليست هذه تبعية لمسار عكسي، بمعنى أن نفس المشكلة التي تم حلها في مشروعات مثل Composite After Paint؟ والأسوأ من ذلك، ماذا لو غيّرت الأنماط الجديدة حجم الأصل؟ ألن يؤدي ذلك أحيانًا إلى تكرار لا نهائي؟

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

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

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

المستقبل: عند إنشاء سلسلة محادثات خارج الموضوع الرئيسي، وما بعدها

مسار العرض الموضّح هنا يسبق عملية تنفيذ RenderingNG الحالية قليلاً. إنه يُظهر تحديد الطبقات على أنها خارج سلسلة التعليمات الرئيسية، في حين أنها لا تزال حاليًا في سلسلة التعليمات الرئيسية. ومع ذلك، لا يستغرق ذلك سوى وقت طويل قبل الانتهاء من ذلك، والآن بعد أن تم شحن Composite After Paint وتحديد الطبقات بعد الطلاء.

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

الخبر السار هو أنّ الأمر ليس هكذا! يعود هذا الجانب من بنية Chromium إلى أيام KHTML، عندما كان تنفيذ سلسلة محادثات واحدة هو النموذج السائد. بحلول الوقت الذي أصبحت فيه المعالجات متعددة النواة شائعة في الأجهزة من النوع الاستهلاكي، تم دمج الافتراض ذي السلسلة الفردية في Blink (المعروف سابقًا باسم WebKit). ومنذ فترة طويلة، أردنا إضافة المزيد من سلاسل المحادثات إلى محرّك العرض، ولكن كان من المستحيل ببساطة في النظام القديم. كان أحد الأهداف الرئيسية لعرض NG هو البحث عن محتوى جديد وإتاحة إمكانية نقل أعمال العرض، جزئيًا أو كليًا، إلى سلسلة محادثات أخرى (أو سلسلة محادثات).

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

سيؤدي الالتزام بعدم الحظر إلى التوقف عن تنفيذ سلسلة التعليمات الرئيسية وانتظار انتهاء مرحلة الإتمام، ستستمر سلسلة التعليمات الرئيسية في العمل بشكل متزامن مع سلسلة تعليمات أداة الإنشاء. سيتمثّل التأثير الصافي لعملية "عدم الحظر" في تقليل الوقت المخصّص لعرض العمل على سلسلة التعليمات الرئيسية، ما سيؤدي إلى تقليل ازدحام سلسلة التعليمات الرئيسية وتحسين الأداء. بدءًا من هذه الرسالة (آذار/مارس 2022)، وضعنا نموذجًا أوّليًا عمليًا لبرنامج Non-Block Commit، ونستعدّ لإجراء تحليل مفصّل لتأثيره على الأداء.

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

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