يشير Blink إلى تنفيذ Chromium لـ منصّة الويب، ويشمل جميع مراحل المعالجة قبل الدمج، وينتهي بـ إرسال المكوّن المرئي. يمكنك الاطّلاع على مزيد من المعلومات حول بنية عرض المحتوى في Blink في مقالة سابقة ضمن هذه السلسلة.
بدأ Blink كإصدار مُشتق من WebKit، وهو نفسه إصدار مشتق من KHTML الذي يعود إلى عام 1998. يحتوي على بعض أقدم الرموز البرمجية (والأهم) في Chromium، وبحلول عام 2014، كان قد حان وقت التحديث. في ذلك العام، بدأنا مجموعة من المشاريع الطموحة تحت اسم BlinkNG بهدف معالجة أوجه القصور المستمرة في تنظيم رمز Blink وبنائه. ستتناول هذه المقالة مشروع BlinkNG والمشاريع المكونة له: سبب تنفيذها، وما تم إنجازه، والمبادئ الإرشادية التي شكلت تصميمها، وفرص التحسينات المستقبلية التي يوفّرها.
العرض قبل الإصدار الأحدث
كان يتم تقسيم مسار العرض في Blink دائمًا من الناحية النظرية إلى مراحل (الأسلوب والتنسيق والرسم وما إلى ذلك)، ولكن كانت الحواجز التي تفصل بين المستويات غير فعّالة. بشكل عام، كانت البيانات المرتبطة بالعرض تتألف من عناصر قابلة للتغيير وطويلة الأمد. كان من الممكن تعديل هذه العناصر في أي وقت، وقد تم تعديلها بالفعل، كما كان يتم إعادة استخدامها بشكل متكرر من خلال تعديلات العروض المتعاقبة. كان من المستحيل الإجابة بشكل موثوق عن أسئلة بسيطة مثل:
- هل يجب تعديل ناتج التصميم أو التنسيق أو الطلاء؟
- متى ستحصل هذه البيانات على قيمتها "النهائية"؟
- متى يكون من المقبول تعديل هذه البيانات؟
- متى سيتم حذف هذا العنصر؟
هناك العديد من الأمثلة على ذلك، بما في ذلك:
كان النمط يُنشئ ComputedStyle
استنادًا إلى أوراق الأنماط، ولكنّ ComputedStyle
لم يكن ثابتًا، ففي بعض الحالات، يتم تعديله من خلال مراحل المعالجة اللاحقة.
سينشئ الأسلوب شجرة من LayoutObject
، ثم يضيف التنسيق تعليقات توضيحية إلى هذه العناصر باستخدام معلومات الحجم والموضع. في بعض الحالات، قد يؤدي التنسيق إلى تعديل بنية الشجرة. لم يكن هناك فصل واضح بين مدخلات التنسيق ومخرجاته.
كان الأسلوب يُنشئ هياكل بيانات تكميلية تحدّد مسار التركيب، وكان يتم تعديل هياكل البيانات هذه في كل مرحلة بعد الأسلوب.
على مستوى أدنى، تتألف أنواع بيانات العرض إلى حد كبير من أشجار متخصّصة (مثل شجرة DOM وشجرة الأنماط وشجرة التنسيق وشجرة سمة الطلاء)، ويتم تنفيذ مراحل العرض على شكل عمليات تنقّل متكرّرة في الأشجار. من الناحية المثالية، يجب أن تكون جولة الشجرة محدودة: عند معالجة عقدة شجرة معيّنة، يجب ألا نحصل على أي معلومات خارج الشجرة الفرعية التي تتأصل في تلك العقدة. لم يكن هذا صحيحًا أبدًا قبل RenderingNG، إذ كانت عمليات التنقّل في الشجرة تحصل على معلومات من أسلاف العقدة التي تتم معالجتها بشكل متكرّر. وقد جعل ذلك النظام هشًا جدًا وأكثر عرضة للخطأ. وكان من المستحيل أيضًا بدء جولة في الشجرة من أي مكان آخر غير جذر الشجرة.
أخيرًا، كانت هناك العديد من عمليات الإعداد لاستخدام مسار العرض في جميع أنحاء الرمز: تنسيقات إجبارية يتم تشغيلها بواسطة JavaScript، وتعديلات جزئية يتم تشغيلها أثناء تحميل المستند، وتعديلات إجبارية للاستعداد لاستهداف الأحداث، وتعديلات مجدوَلة يطلبها نظام العرض، وواجهات برمجة تطبيقات مخصّصة لا تظهر إلا للرمز الاختباري، على سبيل المثال لا الحصر. وكان هناك أيضًا بعض المسارات المتكرّرة والمتكرّرة في مسار العرض (أي القفزة إلى بداية مرحلة من منتصف مرحلة أخرى). كان لكلّ من هذه الخطوات سلوكًا خاصًا به، وفي بعض الحالات، كانت نتيجة العرض تعتمد على الطريقة التي تم بها بدء تحديث العرض.
التغييرات التي أجريناها
يتألّف BlinkNG من العديد من المشاريع الفرعية، الكبيرة والصغيرة، التي تشترك جميعها في هدف إزالة أوجه القصور المعمارية الموضّحة سابقًا. تتشارك هذه المشاريع بعض المبادئ الإرشادية المصمّمة لجعل مسار العرض أكثر كفاءة:
- نقطة دخول موحدة: يجب دائمًا الدخول إلى مسار الإحالة الناجحة في البداية.
- المراحل الوظيفية: يجب أن تحتوي كل مرحلة على مدخلات ومخرجات محدّدة جيدًا، ويجب أن يكون سلوكها وظيفيًا، أي محدّدًا وقابلاً للتكرار، ويجب أن تعتمد المخرجات على المدخلات المحدّدة فقط.
- المدخلات الثابتة: يجب أن تكون مدخلات أي مرحلة ثابتة بشكل فعّال أثناء تشغيل المرحلة.
- المخرجات الثابتة: بعد انتهاء مرحلة معيّنة، يجب أن تكون المخرجات ثابتة خلال الفترة المتبقية من تحديث العرض.
- اتساق نقاط الفحص: في نهاية كل مرحلة، يجب أن تكون بيانات العرض التي تم إنشاؤها حتى الآن في حالة متسقة ذاتيًا.
- إزالة تكرار الأعمال: لا تحسب كل عنصر إلا مرة واحدة.
إنّ القائمة الكاملة للمشاريع الفرعية في BlinkNG ستكون طويلة جدًا، ولكن في ما يلي بعض النتائج المهمة.
دورة حياة المستند
تتتبّع فئة DocumentLifecycle مستوى تقدّمنا خلال عملية المعالجة. ويسمح لنا ذلك بإجراء عمليات تحقّق أساسية تفرض الثوابت المذكورة سابقًا، مثل:
- إذا كنا بصدد تعديل سمة ComputedStyle، يجب أن تكون دورة حياة المستند
kInStyleRecalc
. - إذا كانت حالة DocumentLifecycle هي
kStyleClean
أو إصدار أحدث، يجب أن تعرضNeedsStyleRecalc()
القيمة false لأي عقدة مرفقة. - عند الدخول إلى مرحلة دورة حياة الطلاء، يجب أن تكون حالة دورة الحياة هي
kPrePaintClean
.
على مدار عملية تنفيذ BlinkNG، أزلنا بشكل منهجي مسارات الرموز البرمجية التي تنتهك هذه الشروط الثابتة، ونشرنا العديد من الجمل الأخرى في جميع أنحاء الرمز البرمجي لضمان عدم حدوث أي تراجع.
إذا سبق لك أن اطّلعت على رمز التقديم من المستوى الأدنى، قد تسأل نفسك: "كيف وصلت إلى هنا؟" كما ذكرنا سابقًا، هناك مجموعة متنوعة من نقاط الدخول إلى مسار التقديم. في السابق، كان ذلك يشمل مسارات المكالمات المتكررة والمتكرّرة، والأماكن التي دخلنا فيها إلى مسار التنفيذ في مرحلة وسيطة بدلاً من البدء من البداية. خلال عملية BlinkNG، حلّلنا مسارات المكالمات هذه وتبيّن لنا أنّه يمكن تقليلها إلى سيناريوهَين أساسيَين:
- يجب تعديل جميع بيانات العرض، على سبيل المثال، عند إنشاء وحدات بكسل جديدة للعرض أو إجراء اختبار مطابقة لاستهداف الأحداث.
- نحتاج إلى قيمة محدّثة لاستعلام معيّن يمكن الإجابة عنه بدون تعديل جميع بيانات العرض. ويشمل ذلك معظم طلبات البحث التي تستخدم JavaScript، مثل
node.offsetTop
.
تتوفّر الآن نقطتَا دخول فقط إلى مسار العرض، تتوافقان مع هذين السيناريوهَين. تمّت إزالة مسارات الرموز البرمجية المتكرّرة أو إعادة تنظيمها، ولم يعُد من الممكن الدخول إلى مسار الإحالة الناجحة بدءًا من مرحلة وسيطة. وقد أدّى ذلك إلى القضاء على الكثير من الغموض حول وقت حدوث تحديثات العرض وكيفية حدوثها، ما سهّل كثيرًا فهم سلوك النظام.
أسلوب معالجة الصور وتنسيقها وعمليات ما قبل التلوين
بشكلٍ جماعي، تتولّى مراحل التقديم قبل الرسم ما يلي:
- تشغيل خوارزمية تسلسل الأنماط لاحتساب سمات الأنماط النهائية لعقد DOM
- إنشاء شجرة التنسيق التي تمثّل التسلسل الهرمي للمستطيلات في المستند
- تحديد معلومات الحجم والموضع لجميع المربّعات
- تقريب شكل الوحدات الفرعية للبكسل أو تثبيتها على حدود وحدات البكسل الكاملة للرسم
- تحديد خصائص الطبقات المركبة (التحويل الأفيني أو الفلاتر أو الشفافية أو أي شيء آخر يمكن تسريعه باستخدام وحدة معالجة الرسومات)
- تحديد المحتوى الذي تغيّر منذ مرحلة الرسم السابقة والذي يجب رسمه أو إعادة رسمه (إبطال الرسم)
لم تتغيّر هذه القائمة، ولكن قبل BlinkNG، كان يتم تنفيذ الكثير من هذا العمل بطريقة خاصة، على مراحل متعددة من العرض، مع الكثير من الوظائف المكرّرة وغير الفعّالة. على سبيل المثال، كانت مرحلة style مسؤولة دائمًا بشكل أساسي عن احتساب سمات الأسلوب النهائية للعقد، ولكن كانت هناك بعض الحالات الخاصة التي لم نحدّد فيها قيم سمات الأسلوب النهائية إلا بعد اكتمال مرحلة style. لم تكن هناك نقطة رسمية أو قابلة للتنفيذ في عملية العرض يمكننا من خلالها القول بالتأكيد أنّ معلومات الأنماط كانت كاملة وغير قابلة للتغيير.
من الأمثلة الجيدة الأخرى على المشاكل التي كانت تحدث قبل BlinkNG هي عدم صلاحية الطلاء. في السابق، كان يتمّ إلغاء صلاحية الطلاء في جميع مراحل العرض التي تسبق الطلاء. عند تعديل رمز التصميم أو التنسيق، كان من الصعب معرفة التغييرات التي يجب إجراؤها على منطق إلغاء القيمة للرسم، وكان من السهل ارتكاب خطأ يؤدي إلى أخطاء في إلغاء القيمة أو زيادتها. يمكنك الاطّلاع على مزيد من المعلومات عن التفاصيل الدقيقة لنظام إبطال الطلاء القديم في المقالة من هذه السلسلة المخصّصة LayoutNG.
إنّ ربط هندسة تنسيق وحدات البكسل الفرعية بحدود وحدات البكسل الكاملة للرسم هو مثال على تنفيذنا وظائف متعددة لنفس الوظيفة وتنفيذ الكثير من الأعمال المتكررة. كان هناك مسار رمز واحد لالتقاط البكسل يستخدمه نظام الطلاء، ومسار رمز منفصل تمامًا يُستخدَم كلما احتجنا إلى إجراء عملية حسابية لمرة واحدة أثناء التشغيل للتنسيقات المستندة إلى البكسل خارج رمز الطلاء. لا داعي للقول إنّ كل عملية تنفيذ كانت بها أخطاء، ولم تكن نتائجها متطابقة دائمًا. ولأنّه لم يكن هناك ذاكرة تخزين مؤقت لهذه المعلومات، كان النظام يُجري أحيانًا عملية الحساب نفسها بشكل متكرّر، ما يشكّل عبئًا آخر على الأداء.
في ما يلي بعض المشاريع المهمة التي تمكّنت من إزالة العيوب المعمارية في مراحل التقديم قبل الطلاء.
Project Squad: Pipelining the style phase
عالج هذا المشروع نقصانَين رئيسيَّين في مرحلة التصميم منعتَا من نقله إلى قناة الإصدار بسلاسة:
هناك ناتجَان أساسيان لمرحلة الأنماط: ComputedStyle
، الذي يحتوي على نتيجة تنفيذ خوارزمية CSS المتسلسلة على شجرة DOM، وشجرة LayoutObjects
التي تحدّد ترتيب العمليات لمرحلة التنسيق. من الناحية النظرية، يجب تنفيذ خوارزمية التسلسل بدقة قبل إنشاء شجرة التنسيق، ولكن في السابق، كان يتم تداخل هاتين العمليتين. نجحت شركة Project Squad في تقسيم هذين المقياسَين إلى مرحلتين متتاليتَين ومفصّلتَين.
في السابق، لم تكن السمة ComputedStyle
تحصل دائمًا على قيمتها النهائية أثناء إعادة احتساب الأنماط، فقد حدثت بعض الحالات التي تم فيها تعديل السمة ComputedStyle
خلال مرحلة لاحقة من مسار الإحالة الناجحة. نجحت فِرق Project Squad في إعادة تنظيم مسارات الرموز البرمجية هذه، بحيث لا يتم تعديل ComputedStyle
مطلقًا بعد مرحلة الأنماط.
LayoutNG: معالجة مرحلة التنسيق في مسار مُعدّ
هذا المشروع الضخم، الذي يُعدّ أحد الركائز الأساسية في RenderingNG، هو إعادة كتابة كاملة لمرحلة عرض التنسيق. لن نعرض هنا تفاصيل المشروع بالكامل، ولكن هناك بعض الجوانب البارزة في مشروع BlinkNG بشكل عام:
- في السابق، كانت مرحلة التنسيق تتلقّى شجرة من
LayoutObject
أنشأتها مرحلة النمط، وتُضيف تعليقات توضيحية إلى الشجرة تتضمّن معلومات عن الحجم والموضع. وبالتالي، لم يتم فصل المدخلات عن المخرجات بوضوح. قدّمت LayoutNG شجرة الأجزاء، وهي الإخراج الأساسي للقراءة فقط للتنسيق، وتعمل كمدخل أساسي لمراحل العرض اللاحقة. - أضافت LayoutNG خاصية الحصر إلى التنسيق: عند احتساب حجم
LayoutObject
معيّن وموقعه، لم يعُد نبحث خارج الشجرة الفرعية التي تعود جذورها إلى هذا العنصر. يتم احتساب جميع المعلومات اللازمة لتعديل تنسيق عنصر معيّن مسبقًا وتقديمها كإدخال للقراءة فقط في الخوارزمية. - في السابق، كانت هناك حالات استثنائية لا تعمل فيها خوارزمية التنسيق بشكلٍ دقيق: كانت نتيجة الخوارزمية تعتمد على آخر تعديل سابق على التنسيق. وقد تمّت إزالة هذه الحالات من خلال LayoutNG.
مرحلة ما قبل الرسم
في السابق، لم تكن هناك مرحلة رسمية لعرض الصورة قبل الطلاء، بل كانت مجرد مجموعة من عمليات ما بعد التنسيق. نشأت مرحلة الرسم المُسبَق من إدراك أنّ هناك بعض الوظائف ذات الصلة التي يمكن تنفيذها على أفضل وجه من خلال التنقّل المنهجي في شجرة التنسيق بعد اكتمال التنسيق، وأهمها:
- إصدار عمليات إلغاء طلاء: من الصعب جدًا إجراء عملية إلغاء الطلاء بشكل صحيح أثناء عملية التنسيق، عندما تكون لدينا معلومات غير مكتملة. من الأسهل بكثير تنفيذ ذلك بشكل صحيح، ويمكن أن يكون ذلك فعّالاً جدًا، إذا تم تقسيمه إلى عمليتين مختلفتَين: أثناء التصميم والأسلوب، يمكن وضع علامة منطقية بسيطة على المحتوى "قد تحتاج إلى إلغاء صحة الطلاء". أثناء التنقّل في شجرة النموذج قبل الطلاء، نتحقّق من هذه الإشارات ونصدر عمليات إلغاء حسب الضرورة.
- إنشاء أشجار خصائص الطلاء: وهي عملية موضّحة بالتفصيل في ما بعد.
- احتساب مواقع الطلاء المُقتطعة بالبكسل وتسجيلها: يمكن استخدام النتائج المسجّلة من خلال مرحلة الطلاء، وكذلك من خلال أي رمز لاحق يحتاج إليها، بدون أي عمليات حسابية زائدة.
أشجار المواقع: هندسة متّسقة
تمّ تقديم أشجار المواقع في وقت مبكر من RenderingNG للتعامل مع تعقيد الانتقال للأعلى أو للأسفل، والذي يختلف هيكله على الويب عن جميع الأنواع الأخرى من التأثيرات المرئية. قبل توفّر أشجار المواقع، كان مجمع Chromium يستخدم تسلسلاً هرميًا واحدًا "للطبقة" لتمثيل العلاقة الهندسية للمحتوى المجمّع، ولكن سرعان ما تعذّر ذلك بسبب التعقيدات الكاملة للميزات، مثل position:fixed. نما التسلسل الهرمي للطبقات مؤشرات إضافية غير محلية تشير إلى "العنصر الرئيسي للانتقال السلس" أو "العنصر الرئيسي للقطع" للطبقة، وأصبح من الصعب جدًا فهم الرمز البرمجي بعد فترة قصيرة.
تم حلّ هذه المشكلة في أشجار المواقع من خلال تمثيل جوانب المحتوى التي تتضمّن محتوى إضافيًا قابلاً للتمرير ولقطات من المحتوى بشكل منفصل عن جميع التأثيرات المرئية الأخرى. وقد سمح ذلك بتصميم البنية المرئية والتنقّل الحقيقية للمواقع الإلكترونية بشكل صحيح. بعد ذلك، كان "كلّ ما علينا فعله" هو تنفيذ الخوارزميات على قمة أشجار المواقع، مثل تحويل مساحة الشاشة للطبقات المركبة، أو تحديد الطبقات التي يتم التمرير فيها والطبقات التي لا يتم التمرير فيها.
في الواقع، لاحظنا قريبًا أنّ هناك العديد من المواضع الأخرى في الرمز البرمجي التي تم فيها طرح أسئلة هندسية مشابهة. (تتوفّر قائمة أكثر اكتمالاً في المشاركة حول بنية البيانات الرئيسية.) كان بعضها يتضمّن عمليات تنفيذ مكرّرة لإجراء مماثل لإجراء رمز المُركّب، وكان بعضها يتضمّن مجموعة فرعية مختلفة من الأخطاء، ولم يُنشئ أي منها نموذجًا صحيحًا لبنية الموقع الإلكتروني. بعد ذلك، أصبح الحلّ واضحًا: تجميع جميع خوارزميات الهندسة في مكان واحد وإعادة صياغة كل الرموز البرمجية لاستخدامها.
تعتمد هذه الخوارزميات بدورها على أشجار المواقع، ولهذا السبب تشكّل أشجار المواقع بنية بيانات مفتاحية، أي بنية مستخدَمة في جميع مراحل مسار المعالجة في RenderingNG. لتحقيق هذا الهدف المتمثل في توحيد رموز الهندسة، كان علينا تقديم مفهوم أشجار المواقع في مرحلة مبكرة جدًا من عملية المعالجة، أي في مرحلة ما قبل الطلاء، وتغيير جميع واجهات برمجة التطبيقات التي تعتمد عليها الآن لتتطلب تنفيذ مرحلة ما قبل الطلاء قبل تنفيذها.
هذه القصة هي جانب آخر من نمط إعادة صياغة BlinkNG: تحديد العمليات الحسابية الرئيسية وإعادة صياغتها لتجنُّب تكرارها وإنشاء مراحل مسار بيانات محدّدة جيدًا تُنشئ هياكل البيانات التي تغذّيها. نحسب أشجار المواقع في النقطة التي تتوفّر فيها جميع المعلومات اللازمة تمامًا، ونحرص على أن لا تتغيّر أشجار المواقع أثناء تنفيذ مراحل التقديم اللاحقة.
التركيب بعد الطلاء: إنشاء مسار عمل للطلاء والتركيب
التقسيم إلى طبقات هو عملية تحديد محتوى DOM الذي ينتقل إلى طبقته المركبة (التي تمثّل بدورها نسيج وحدة معالجة الرسومات). قبل RenderingNG، كان يتمّ تقسيم الطبقات قبل الطلاء، وليس بعده (اطّلِع على هذا الرابط لمعرفة المسار الحالي، مع ملاحظة تغيير الترتيب). سنحدّد أولاً أجزاء DOM التي تمّت إضافتها إلى الطبقة المركبة، وبعد ذلك فقط سنرسم قوائم العرض لهذه النسيج. وبطبيعة الحال، كانت القرارات تعتمد على عوامل مثل عناصر DOM التي كانت تتحرك أو تنتقل للأعلى أو للأسفل أو كانت لها عمليات تحويل ثلاثية الأبعاد، والعناصر التي يتم رسمها فوق بعضها.
وقد أدّى ذلك إلى حدوث مشاكل كبيرة، لأنّه كان يتطلّب بشكل أو بآخر توفُّر تبعيات دائرية في الرمز، ما يشكّل مشكلة كبيرة لقناة العرض. لنوضّح ذلك من خلال مثال. لنفترض أنّنا نحتاج إلى إبطال الطلاء (أي أنّنا نحتاج إلى إعادة رسم قائمة العرض ثم تحويلها إلى رسومات نقطية مرة أخرى). قد تنشأ الحاجة إلى إلغاء الصلاحية من تغيير في نموذج DOM أو من تغيير في النمط أو التنسيق. ولكن بالطبع، نريد إلغاء صلاحية الأجزاء التي تغيّرت فقط. وهذا يعني معرفة الطبقات المركبة التي تأثّرت، ثم إلغاء جزء من قوائم العرض أو كلّها لهذه الطبقات.
وهذا يعني أنّ إلغاء الصلاحية يعتمد على DOM والنمط والتنسيق وقرارات الطبقات السابقة (السابقة: تعني الإطار المعروض السابق). لكنّ تقسيم الطبقات الحالي يعتمد على كل هذه العوامل أيضًا. وبما أنّنا لم يكن لدينا نسختان من جميع بيانات التقسيم إلى طبقات، كان من الصعب التمييز بين قرارات التقسيم إلى طبقات السابقة والمستقبلية. لذلك، انتهى بنا المطاف بالكثير من الرموز البرمجية التي تتضمّن استنتاجات دائرية. وقد أدّى ذلك أحيانًا إلى ظهور رمز غير منطقي أو غير صحيح، أو حتى إلى حدوث أعطال أو مشاكل في الأمان، إذا لم نكن حريصين جدًا.
لحلّ هذه المشكلة، طرحنا في وقت مبكر مفهوم عنصر DisableCompositingQueryAsserts
. في معظم الأحيان، إذا حاولت الرموز البرمجية الاستعلام عن قرارات وضع الطبقات السابقة، سيؤدي ذلك إلى تعذُّر تأكيد العبارة وتعطُّل المتصفّح إذا كان في وضع تصحيح الأخطاء. وقد ساعدنا ذلك في تجنّب إدخال أخطاء جديدة. وفي كل حالة كان فيها الرمز البرمجي بحاجة بشكل مشروع إلى الاستعلام عن قرارات الطبقات السابقة، أضفنا رمزًا للسماح بذلك من خلال تخصيص عنصر DisableCompositingQueryAsserts
.
كانت خطتنا هي التخلص من جميع عناصر مواقع الاتصال DisableCompositingQueryAssert
بمرور الوقت، ثم الإعلان عن أنّ الرمز آمن وصحيح. ولكن تبيّن لنا أنّه كان من المستحيل إزالة عدد من المكالمات طالما أنّ وضع الطبقات تم قبل الطلاء. (لقد تمكّنا أخيرًا من إزالته مؤخرًا جدًا). كان هذا هو السبب الأول الذي تم اكتشافه لمشروع "التركيب بعد الطلاء". تبيّن لنا أنّه حتى إذا كانت لديك مرحلة مسار إحالة ناجحة محدّدة جيدًا لعملية معيّنة، إذا كانت في المكان الخطأ في مسار الإحالة الناجحة، ستواجه مشكلة في النهاية.
السبب الثاني لمشروع "التركيب بعد التلوين" هو الخطأ الأساسي في الدمج. إحدى طرق توضيح هذا الخطأ هي أنّ عناصر نموذج DOM ليست تمثيلًا جيدًا بنسبة 1:1 لنظام تقسيم طبقات فعّال أو كامل لمحتوى صفحات الويب. وبما أنّ عملية الدمج كانت قبل عملية الرسم، كانت تعتمد بشكل أساسي على عناصر DOM، وليس على قوائم العرض أو أشجار المواقع. يشبه ذلك كثيرًا السبب الذي أدّى إلى تقديمنا أشجار المواقع، وكما هو الحال مع أشجار المواقع، يظهر الحلّ مباشرةً إذا حدّدت مرحلة مسار الإحالة الناجحة الصحيحة ونفّذتها في الوقت المناسب وزوّدتها ببنى البيانات الرئيسية الصحيحة. وكما هو الحال مع أشجار المواقع، كانت هذه فرصة جيدة لضمان أنّه بعد اكتمال مرحلة الطلاء، يصبح الناتج ثابتًا لجميع مراحل المعالجة اللاحقة.
المزايا
كما رأيت، تحقّق مسار العرض المحدد جيدًا مزايا هائلة على المدى الطويل. وهناك المزيد من الفوائد:
- موثوقية محسّنة بشكل كبير: هذه الميزة واضحة جدًا. إنّ الرموز البرمجية المنظَّمة التي تتضمّن واجهات محدّدة وسهلة الفهم تكون أسهل في الفهم والكتابة والاختبار. وهذا يجعلها أكثر موثوقية. ويؤدي ذلك أيضًا إلى جعل الرمز أكثر أمانًا وثباتًا، مع تقليل الأعطال والأخطاء الناتجة عن استخدام الرموز البرمجية بعد انتهاء صلاحيتها.
- تغطية موسّعة للاختبار: خلال فترة BlinkNG، أضفنا عددًا كبيرًا من الاختبارات الجديدة إلى مجموعتنا. ويشمل ذلك اختبارات الوحدة التي توفّر عملية تحقّق مركزة من الوظائف الداخلية، واختبارات الرجوع التي تمنعنا من إعادة إدخال الأخطاء القديمة التي أصلحناها (هناك الكثير منها)، والكثير من الإضافات إلى مجموعة اختبارات منصة الويب المتاحة للجميع والتي يتم الحفاظ عليها بشكل جماعي، والتي تستخدمها جميع المتصفّحات لقياس الامتثال لمعايير الويب.
- سهولة التوسيع: إذا تم تقسيم النظام إلى مكوّنات واضحة، ليس من الضروري فهم المكوّنات الأخرى بأي مستوى من التفاصيل من أجل تحقيق تقدّم في النظام الحالي. يسهّل ذلك على الجميع إضافة قيمة إلى رمز العرض بدون الحاجة إلى أن يكونوا خبراء في هذا المجال، كما يسهّل أيضًا فهم سلوك النظام بأكمله.
- الأداء: إنّ تحسين الخوارزميات المكتوبة برمز معقد هو أمر صعب بما يكفي، ولكن يكاد يكون من المستحيل تحقيق أشياء أكبر، مثل الانتقال المتسلسل العام للرسوم المتحرّكة أو العمليات والمهام لعزل الموقع الإلكتروني بدون مسار عمل كهذا. يمكن أن تساعدنا تقنية "المعالجة المتعددة" في تحسين الأداء بشكل كبير، ولكنها أيضًا معقدة للغاية.
- تحقيق الأرباح والاحتواء: هناك العديد من الميزات الجديدة التي تتيحها BlinkNG لتشغيل مسار الإحالة الناجحة بطرق جديدة ومبتكرة. على سبيل المثال، ماذا لو أردنا تشغيل مسار التقديم فقط إلى أن تنتهي صلاحية ميزانية معيّنة؟ أم هل يجب تخطي عرض الأشجار الفرعية المعروف أنّها غير ملائمة للمستخدم في الوقت الحالي؟ وهذا ما تتيحه سمة CSS content-visibility. ماذا عن جعل نمط المكوّن يعتمد على تنسيقه؟ وهذا ما يُعرف باسم طلبات البحث عن الحِزم.
دراسة حالة: طلبات البحث عن الحاويات
طلبات حاويات البيانات هي ميزة مُرتقبة بشدة في منصة الويب القادمة (لقد كانت الميزة الأولى الأكثر طلبًا من مطوّري CSS لعدة سنوات). إذا كان الأمر رائعًا، لماذا لم يتم طرحه بعد؟ ويعود السبب في ذلك إلى أنّ تنفيذ طلبات البحث عن الحاوية يتطلّب فهمًا وإدارةً دقيقَين جدًا للعلاقة بين رمز التصميم والأسلوب. لنلقِ نظرة عن كثب.
يسمح طلب الحاوية للأنماط التي تنطبق على عنصر بالاعتماد على الحجم المنظَّم لأحد الأجداد. بما أنّه يتم احتساب الحجم المعروض أثناء التنسيق، يعني ذلك أنّنا نحتاج إلى تنفيذ إعادة احتساب الأنماط بعد التنسيق، ولكن يتم تنفيذ إعادة احتساب الأنماط قبل التنسيق. هذه المفارقة هي السبب الكامل لعدم تمكّننا من تنفيذ طلبات البحث عن الحاويات قبل BlinkNG.
كيف يمكننا حلّ هذه المشكلة؟ ألا يرتبط ذلك بمسار عمل قديم، أي المشكلة نفسها التي تم حلّها في مشاريع مثل Composite After Paint؟ والأسوأ من ذلك، ماذا لو غيّرت الأنماط الجديدة حجم العنصر الأصلي؟ ألا سيؤدي ذلك أحيانًا إلى حلقة لا تنتهي؟
من حيث المبدأ، يمكن حلّ مشكلة الاعتماد المتبادل من خلال استخدام سمة CSS contain التي تسمح بالعرض خارج عنصر بدون الاعتماد على العرض ضمن الشجرة الفرعية لهذا العنصر. وهذا يعني أنّ الأنماط الجديدة التي تطبّقها الحاوية لا يمكن أن تؤثّر في حجم الحاوية، لأنّ طلبات البحث عن الحاوية تتطلّب احتواء.
ولكن هذا لم يكن كافيًا، وكان من الضروري تقديم نوع أقل صرامة من الحظر من مجرد حظر الحجم. ويعود السبب في ذلك إلى أنّه من الشائع أن يتمكّن عنصر حاوية طلبات البحث من تغيير حجمه في اتجاه واحد فقط (عادةً ما يكون الحجم المُحدد) استنادًا إلى الأبعاد المضمّنة فيه. لذلك، تمت إضافة مفهوم الحدّ من الحجم المضمّن. ولكن كما يمكنك الاطّلاع عليه من خلال الملاحظة الطويلة جدًا في ذلك القسم، لم يكن واضحًا لفترة طويلة ما إذا كان من الممكن تضمين الحجم في النص.
إنّ وصف الحاوية بلغة المواصفات المجردة هو أمر، وتنفيذها بشكل صحيح هو أمر آخر. تذكَّر أنّ أحد أهداف BlinkNG هو تطبيق مبدأ الحاوية على عمليات التنقّل في الشجرة التي تشكّل المنطق الرئيسي للعرض: عند التنقّل في شجرة فرعية، يجب ألا تكون هناك حاجة إلى أي معلومات من خارج الشجرة الفرعية. اتضح أنّه من السهل جدًا تنفيذ ميزة احتواء CSS إذا كان رمز العرض يلتزم بمبدأ الاحتواء.
المستقبل: دمج الصور خارج سلسلة التعليمات الرئيسية وغير ذلك
إنّ مسار المعالجة المعروض هنا متقدم قليلاً عن مسار التنفيذ الحالي لنظام RenderingNG. يُظهر ذلك أنّ وضع الطبقات غير مفعّل في سلسلة التعليمات الرئيسية، في حين أنّه لا يزال مفعّلاً في الوقت الحالي. ومع ذلك، ما مِن شكّ في أنّه سيتم تنفيذ ذلك بعد فترة قصيرة، خاصةً بعد أن تم طرح ميزة "التركيب بعد الطلاء" وأصبح من الممكن تطبيق الطبقات بعد الطلاء.
لفهم أهمية ذلك، ومعرفة النتائج الأخرى التي قد يؤدي إليها، علينا النظر إلى بنية محرّك العرض من منظور أعلى قليلاً. إنّ أحد العوائق الأكثر استمرارًا في تحسين أداء Chromium هو الحقيقة البسيطة المتمثّلة في أنّ سلسلة المهام الرئيسية لبرنامج التحويل تعالج كلّ من منطق التطبيق الرئيسي (أي تشغيل النص البرمجي) والجزء الأكبر من التحويل. ونتيجةً لذلك، غالبًا ما تكون سلسلة التعليمات الرئيسية مشغولة بالعديد من المهام، وغالبًا ما يكون ازدحام سلسلة التعليمات الرئيسية هو السبب في حدوث عرقلة في المتصفّح بأكمله.
والخبر السار هو أنّه ليس عليك القبول بذلك. يعود هذا الجانب من بنية Chromium إلى أيام KHTML، عندما كان التنفيذ المتعدّد المواضيع هو نموذج البرمجة السائد. عندما أصبحت المعالجات متعددة النوى شائعة في الأجهزة المخصّصة للمستهلكين، تم دمج افتراض الخيط الواحد بالكامل في Blink (المعروفة سابقًا باسم WebKit). لقد أردنا إدخال المزيد من المواضيع إلى محرّك العرض منذ فترة طويلة، ولكن كان ذلك مستحيلًا في النظام القديم. كان أحد الأهداف الرئيسية لميزة "العرض بتكنولوجيا جديدة" هو الخروج من هذه المشكلة، وجعل نقل عمل العرض جزئيًا أو كليًا إلى سلسلة تعليمات أخرى (أو سلاسل تعليمات) ممكنًا.
الآن وقد أوشكت تقنية BlinkNG على اكتمالها، بدأنا في استكشاف هذا المجال. والالتزام غير المحظور هو أول خطوة في تغيير نموذج تسلسل المهام لبرنامج التحويل. عملية الربط في أداة الدمج (أو الربط فقط) هي خطوة مزامنة بين سلسلة التعليمات الرئيسية وسلسلة التعليمات الخاصة بأداة الدمج. أثناء الحفظ، نُنشئ نُسخًا من بيانات التقديم التي يتم إنشاؤها في سلسلة التعليمات الرئيسية، لاستخدامها من قِبل رمز الدمج في مرحلة ما بعد الإنتاج الذي يتم تشغيله في سلسلة التعليمات الخاصة ببرنامج الدمج. أثناء حدوث هذه المزامنة، يتم إيقاف تنفيذ سلسلة التعليمات الرئيسية بينما يتم تشغيل رمز النسخ على سلسلة تعليمات أداة الدمج. ويتم ذلك لضمان عدم تعديل سلسلة المهام الرئيسية لبيانات العرض أثناء نسخ سلسلة مهام أداة الدمج لها.
سيؤدي استخدام ميزة "الالتزام غير المحظور" إلى عدم الحاجة إلى إيقاف سلسلة التعليمات الرئيسية والانتظار إلى أن تنتهي مرحلة التنفيذ، بل ستستمر سلسلة التعليمات الرئيسية في تنفيذ المهام بينما يتم تنفيذ عملية التنفيذ بشكل متزامن في سلسلة تعليمات أداة الدمج. وسيؤدي ذلك إلى تقليل الوقت المخصّص لعرض العمل على سلسلة المحادثات الرئيسية، ما سيقلّل من الازدحام في سلسلة المحادثات الرئيسية ويُحسِّن الأداء. اعتبارًا من وقت كتابة هذه السطور (آذار/مارس 2022)، لدينا نموذج أوّلي صالح لميزة "الالتزام غير المُعيق"، ونحن نستعد لإجراء تحليل مفصّل لتأثيرها في الأداء.
في انتظار الإطلاق، التركيب خارج سلسلة التعليمات الرئيسية، بهدف جعل محرّك العرض يتطابق مع الرسم التوضيحي من خلال نقل التقسيم إلى طبقات خارج سلسلة التعليمات الرئيسية إلى سلسلة تعليمات عامل. مثل "الالتزام غير المحظور"، سيؤدي ذلك إلى تقليل الازدحام في سلسلة التعليمات الرئيسية عن طريق تقليل حجم العمل المرتبط بالعرض. لم يكن من الممكن تنفيذ مشروع كهذا بدون التحسينات المعمارية التي أدخلناها على ميزة "التركيب بعد الطلاء".
وهناك المزيد من المشاريع في الطريق (قصدنا التورية). لدينا أخيرًا أساس يتيح لنا تجربة إعادة توزيع عمل التقديم، ونتطلّع إلى معرفة ما يمكن تحقيقه.